/*
* Copyright 2016-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.facebook.buck.macho;
import com.facebook.buck.charset.NulTerminatedCharsetDecoder;
import com.facebook.buck.util.HumanReadableException;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.primitives.UnsignedInteger;
import com.google.common.primitives.UnsignedLong;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.StandardCharsets;
public class SegmentCommandUtils {
private SegmentCommandUtils() {}
/**
* VM size field of the SegmentCommand needs to be aligned. This method returns the alignment for
* the given value.
*
* @param value The value that needs to be aligned.
* @return Aligned value.
*/
public static int alignValue(int value) {
/**
* According to lld sources, different CPU architectures have different code alignment (see
* links below):
*
* <p>- x86_64 has 2Kb code alignment - x86 has 2Kb code alignment - arm64 has 4Kb code
* alignment - arm has 4Kb code alignment
*
* <p>Since Mach O file does not have this information, but sections have to be aligned (even
* though not all of them appear to be aligned), we will align to 32Kb. Thus, it potentially
* should cover all possible alignments and even more that needed.
*
* <p>https://github.com/llvm-mirror/lld/blob/master/lib/ReaderWriter/MachO/ArchHandler_x86_64.cpp
* https://github.com/llvm-mirror/lld/blob/master/lib/ReaderWriter/MachO/ArchHandler_x86.cpp
* https://github.com/llvm-mirror/lld/blob/master/lib/ReaderWriter/MachO/ArchHandler_arm64.cpp
* https://github.com/llvm-mirror/lld/blob/master/lib/ReaderWriter/MachO/ArchHandler_arm.cpp
*/
final int universalAlignment = 32 * 1024;
return universalAlignment * (int) (Math.ceil((double) value / universalAlignment));
}
/**
* This method returns the amount of bytes that segment command is taking. Segment's section bytes
* are laid out in binary after the segment command header.
*
* @param segmentCommand Command which header size is being determined
* @return The size of the segment command header, after which the sections are present.
*/
@VisibleForTesting
static int getSegmentCommandHeaderSize(SegmentCommand segmentCommand) {
if (segmentCommand.getLoadCommandCommonFields().getCmd().equals(SegmentCommand.LC_SEGMENT_64)) {
return SegmentCommand.SIZE_IN_BYTES_64_BIT;
} else {
return SegmentCommand.SIZE_IN_BYTES_32_BIT;
}
}
/**
* Updates the given command in the given buffer with the new given command.
*
* @param buffer The buffer which holds all data.
* @param old The old command that needs to be updated with the contents of the new command in the
* given buffer.
* @param updated The updated command, which bytes will be used to override the old commad.
* @throws IOException
*/
public static void updateSegmentCommand(
ByteBuffer buffer, SegmentCommand old, SegmentCommand updated) {
Preconditions.checkArgument(
old.getLoadCommandCommonFields().getOffsetInBinary()
== updated.getLoadCommandCommonFields().getOffsetInBinary());
Preconditions.checkArgument(
old.getLoadCommandCommonFields()
.getCmd()
.equals(updated.getLoadCommandCommonFields().getCmd()));
Preconditions.checkArgument(
old.getLoadCommandCommonFields()
.getCmdsize()
.equals(updated.getLoadCommandCommonFields().getCmdsize()));
buffer.position(old.getLoadCommandCommonFields().getOffsetInBinary());
writeCommandToBuffer(
updated,
buffer,
updated.getLoadCommandCommonFields().getCmd().equals(SegmentCommand.LC_SEGMENT_64));
}
/**
* Enumerates the sections in the given segment command by calling the given callback.
*
* @param buffer The buffer which holds all data.
* @param magicInfo Mach Header Magic info.
* @param segmentCommand The SegmentCommand which Sections should be enumerated.
* @param callback The Function object which should be called on each Section. The argument of the
* function is the Section object. If Function returns Boolean.TRUE then enumeration will
* continue; otherwise enumeration will stop and callback will not be called anymore.
* @throws IOException
*/
public static void enumerateSectionsInSegmentLoadCommand(
ByteBuffer buffer,
MachoMagicInfo magicInfo,
SegmentCommand segmentCommand,
NulTerminatedCharsetDecoder decoder,
Function<Section, Boolean> callback)
throws IOException {
final int sectionHeaderSize = SectionUtils.sizeOfSectionHeader(magicInfo.is64Bit());
final int sectionsOffset =
segmentCommand.getLoadCommandCommonFields().getOffsetInBinary()
+ SegmentCommandUtils.getSegmentCommandHeaderSize(segmentCommand);
for (int i = 0; i < segmentCommand.getNsects().intValue(); i++) {
int offsetInBinary = sectionsOffset + sectionHeaderSize * i;
buffer.position(offsetInBinary);
Section section = SectionUtils.createFromBuffer(buffer, magicInfo.is64Bit(), decoder);
boolean shouldContinue = callback.apply(section);
if (!shouldContinue) {
break;
}
}
}
@SuppressWarnings("PMD.PrematureDeclaration")
public static SegmentCommand createFromBuffer(
ByteBuffer buffer, NulTerminatedCharsetDecoder decoder) {
LoadCommandCommonFields fields = LoadCommandCommonFieldsUtils.createFromBuffer(buffer);
Preconditions.checkArgument(SegmentCommand.VALID_CMD_VALUES.contains(fields.getCmd()));
boolean is64Bit = fields.getCmd().equals(SegmentCommand.LC_SEGMENT_64);
String segname;
try {
segname = decoder.decodeString(buffer);
} catch (CharacterCodingException e) {
throw new HumanReadableException(
e, "Cannot read segname for SegmentCommand at %d", fields.getOffsetInBinary());
}
buffer.position(
fields.getOffsetInBinary()
+ LoadCommandCommonFields.CMD_AND_CMDSIZE_SIZE
+ SegmentCommand.SEGNAME_SIZE_IN_BYTES);
return SegmentCommand.of(
fields,
segname,
UnsignedLong.fromLongBits(is64Bit ? buffer.getLong() : buffer.getInt() & 0xFFFFFFFFL),
UnsignedLong.fromLongBits(is64Bit ? buffer.getLong() : buffer.getInt() & 0xFFFFFFFFL),
UnsignedLong.fromLongBits(is64Bit ? buffer.getLong() : buffer.getInt() & 0xFFFFFFFFL),
UnsignedLong.fromLongBits(is64Bit ? buffer.getLong() : buffer.getInt() & 0xFFFFFFFFL),
buffer.getInt(),
buffer.getInt(),
UnsignedInteger.fromIntBits(buffer.getInt()),
UnsignedInteger.fromIntBits(buffer.getInt()));
}
public static void writeCommandToBuffer(
SegmentCommand command, ByteBuffer buffer, boolean is64Bit) {
LoadCommandCommonFieldsUtils.writeCommandToBuffer(command.getLoadCommandCommonFields(), buffer);
byte[] segnameStringBytes = command.getSegname().getBytes(StandardCharsets.UTF_8);
buffer
.put(segnameStringBytes)
.get(new byte[SegmentCommand.SEGNAME_SIZE_IN_BYTES - segnameStringBytes.length]);
if (is64Bit) {
buffer
.putLong(command.getVmaddr().longValue())
.putLong(command.getVmsize().longValue())
.putLong(command.getFileoff().longValue())
.putLong(command.getFilesize().longValue());
} else {
buffer
.putInt(command.getVmaddr().intValue())
.putInt(command.getVmsize().intValue())
.putInt(command.getFileoff().intValue())
.putInt(command.getFilesize().intValue());
}
buffer
.putInt(command.getMaxprot())
.putInt(command.getInitprot())
.putInt(command.getNsects().intValue())
.putInt(command.getFlags().intValue());
}
}