/*
* 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.google.common.base.Preconditions;
import com.google.common.primitives.UnsignedInteger;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
public class SymTabCommandUtils {
private static final byte SLASH_BYTE = (byte) 0x2F;
private static final byte NUL_BYTE = (byte) 0x00;
private SymTabCommandUtils() {}
public static SymTabCommand createFromBuffer(ByteBuffer buffer) {
return SymTabCommand.of(
LoadCommandCommonFieldsUtils.createFromBuffer(buffer),
UnsignedInteger.fromIntBits(buffer.getInt()),
UnsignedInteger.fromIntBits(buffer.getInt()),
UnsignedInteger.fromIntBits(buffer.getInt()),
UnsignedInteger.fromIntBits(buffer.getInt()));
}
public static void writeCommandToBuffer(SymTabCommand command, ByteBuffer buffer) {
LoadCommandCommonFieldsUtils.writeCommandToBuffer(command.getLoadCommandCommonFields(), buffer);
buffer
.putInt(command.getSymoff().intValue())
.putInt(command.getNsyms().intValue())
.putInt(command.getStroff().intValue())
.putInt(command.getStrsize().intValue());
}
/**
* Creates and returns the Nlist entry at the given index for the given SymTabCommand.
*
* @param buffer The buffer which holds all data.
* @param command The command that describes the Nlist entries.
* @param index Index of the entry that should be returned.
* @param is64Bit Indicates if binary is 64 bit or not.
* @return The Nlist entry at given index for the given SymTabCommand.
* @throws IOException
*/
public static Nlist getNlistAtIndex(
ByteBuffer buffer, SymTabCommand command, int index, boolean is64Bit) {
final int nlistSizeInBytes = NlistUtils.getSizeInBytes(is64Bit);
final int offset = command.getSymoff().intValue() + index * nlistSizeInBytes;
buffer.position(offset);
return NlistUtils.createFromBuffer(buffer, is64Bit);
}
/**
* Returns the string entry from the string table described by the given Nlist entry of the given
* command.
*
* @param buffer The buffer which holds all data.
* @param command SymTabCommand that has reference to the string table.
* @param nlist The entry which describes the reference to the string table which value should be
* returned.
* @return The value from string table that Nlist refers to.
* @throws IOException
*/
public static String getStringTableEntryForNlist(
ByteBuffer buffer, SymTabCommand command, Nlist nlist, NulTerminatedCharsetDecoder decoder)
throws IOException {
int entryIndex = nlist.getN_strx().intValue();
Preconditions.checkArgument(entryIndex != 0, "Nlist has null strx value");
int offset = command.getStroff().intValue() + nlist.getN_strx().intValue();
buffer.position(offset);
return decoder.decodeString(buffer);
}
/**
* Quick check if Nlist string table entry is a null value.
*
* @param nlist Nlist which strx value should be checked for null value.
* @return true if Nlist string table value is null.
*/
public static boolean stringTableEntryIsNull(Nlist nlist) {
return nlist.getN_strx().intValue() == 0;
}
/**
* Quick check if Nlist string table entry starts with a slash symbol. Nlist's string value must
* not be null.
*
* @param nlist Nlist which strx value should be checked .
* @return true if Nlist string table value is null.
*/
public static boolean stringTableEntryStartsWithSlash(
ByteBuffer buffer, SymTabCommand command, Nlist nlist) {
Preconditions.checkArgument(
!stringTableEntryIsNull(nlist), "Provided nlist object has null string table value");
int offset = command.getStroff().intValue() + nlist.getN_strx().intValue();
buffer.position(offset);
byte value = buffer.get();
buffer.position(offset);
return value == SLASH_BYTE;
}
/**
* Quick check if Nlist string table entry ends with a slash symbol. Nlist's string value must not
* be null.
*
* @param nlist Nlist which strx value should be checked .
* @return true if Nlist string table value is null.
*/
public static boolean stringTableEntryEndsWithSlash(
ByteBuffer buffer, SymTabCommand command, Nlist nlist) {
Preconditions.checkArgument(!stringTableEntryIsNull(nlist));
int offset = command.getStroff().intValue() + nlist.getN_strx().intValue();
buffer.position(offset);
byte lastNonNullValue;
byte lastValue = 0;
do {
lastNonNullValue = lastValue;
lastValue = buffer.get();
} while (lastValue != NUL_BYTE);
buffer.position(offset);
return lastNonNullValue == SLASH_BYTE;
}
/**
* Quick check if Nlist string table entry is an empty string value. Nlist's string value must not
* be null.
*
* @param nlist Nlist which strx value should be checked for empty string value.
* @return true if Nlist string table value is empty string.
*/
public static boolean stringTableEntryIsEmptyString(
ByteBuffer buffer, SymTabCommand command, Nlist nlist) {
Preconditions.checkArgument(
!stringTableEntryIsNull(nlist), "Provided nlist object has null string table value");
int offset = command.getStroff().intValue() + nlist.getN_strx().intValue();
buffer.position(offset);
byte value = buffer.get();
buffer.position(offset);
return value == NUL_BYTE;
}
/**
* This method updates the given buffer by updating the given SymTabCommand to reflect the
* insertion of the entry into string table.
*
* @param buffer The buffer which holds all the data that needs to be updated.
* @param command The old SymTabCommand that does not reflect the updated string table yet.
* @param newEntryContents Contents of the new string table entry that previously has been
* inserted into the string table.
* @return New SymTabCommand that is updated to reflect the availability of the new entry in its
* string table.
* @throws IOException
*/
public static SymTabCommand updateSymTabCommand(
ByteBuffer buffer, SymTabCommand command, String newEntryContents) {
SymTabCommand updatedCommand =
getUpdatedSymTabCommandWithNewEntryContents(command, newEntryContents);
writeCommand(buffer, command, updatedCommand);
return updatedCommand;
}
/**
* @param entry The contents of the string table entry.
* @return The actual size of the entry for the given string.
*/
public static int sizeOfStringTableEntryWithContents(String entry) {
return entry.getBytes(StandardCharsets.UTF_8).length + 1; // + 1 for null terminator
}
/**
* Inserts the given string into the string buffer and returns its location in the string table.
*
* @param buffer the buffer which contains all data that should be modified. Buffer should be
* ready to accept new bytes at its end.
* @param command SymTabCommand which string table is being updated.
* @param newEntryContents the value for new entry.
* @return the location of the newly inserted entry in the string table.
* @throws IOException
*/
public static UnsignedInteger insertNewStringTableEntry(
ByteBuffer buffer, SymTabCommand command, String newEntryContents) {
buffer.position(command.getStroff().intValue() + command.getStrsize().intValue());
buffer.put(newEntryContents.getBytes(StandardCharsets.UTF_8));
buffer.put(NUL_BYTE);
return command.getStrsize();
}
/**
* Updates the given SymTabCommand with the new data in the given buffer.
*
* @param buffer The buffer which holds the data for the old command
* @param command Old command
* @param updatedCommand New command that should replace the old command
* @throws IOException
*/
private static void writeCommand(
ByteBuffer buffer, SymTabCommand command, SymTabCommand updatedCommand) {
Preconditions.checkArgument(
command.getLoadCommandCommonFields().getOffsetInBinary()
== updatedCommand.getLoadCommandCommonFields().getOffsetInBinary());
Preconditions.checkArgument(
command
.getLoadCommandCommonFields()
.getCmd()
.equals(updatedCommand.getLoadCommandCommonFields().getCmd()));
Preconditions.checkArgument(
command
.getLoadCommandCommonFields()
.getCmdsize()
.equals(updatedCommand.getLoadCommandCommonFields().getCmdsize()));
buffer.position(command.getLoadCommandCommonFields().getOffsetInBinary());
writeCommandToBuffer(updatedCommand, buffer);
}
/**
* Constructs new SymTabCommand which would contain the given string table entry.
*
* @param command The old command which must be updated to include the given string table entry.
* @param newEntryContents String that was inserted into string table.
* @return new command which string table size is updated to include the new string table entry.
*/
private static SymTabCommand getUpdatedSymTabCommandWithNewEntryContents(
SymTabCommand command, String newEntryContents) {
return command.withStrsize(
command
.getStrsize()
.plus(
UnsignedInteger.fromIntBits(sizeOfStringTableEntryWithContents(newEntryContents))));
}
}