/* * 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 static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalToObject; import static org.junit.Assert.assertThat; import com.facebook.buck.charset.NulTerminatedCharsetDecoder; import com.google.common.primitives.UnsignedInteger; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.util.Arrays; import org.junit.Test; public class SymTabCommandUtilsTest { @Test public void testGettingNlistAtIndex64BitBigEndian() throws Exception { checkWithBytes(NlistTestData.getBigEndian64Bit(), true, false); } @Test public void testGettingNlistAtIndex32BitBigEndian() throws Exception { checkWithBytes(NlistTestData.getBigEndian32Bit(), false, false); } @Test public void testGettingNlistAtIndex64BitLittleEndian() throws Exception { checkWithBytes(NlistTestData.getLittleEndian64Bit(), true, true); } @Test public void testGettingNlistAtIndex32BitLittleEndian() throws Exception { checkWithBytes(NlistTestData.getLittleEndian32Bit(), false, true); } private void checkWithBytes(byte[] nlistTemplateBytes, boolean is64Bit, boolean isSwapped) throws IOException { byte[] nlistBytes1 = Arrays.copyOf(nlistTemplateBytes, nlistTemplateBytes.length); if (isSwapped) { nlistBytes1[0] = (byte) 0x01; // strx } else { nlistBytes1[3] = (byte) 0x01; // strx } nlistBytes1[4] = (byte) 0x11; // type byte[] nlistBytes2 = Arrays.copyOf(nlistTemplateBytes, nlistTemplateBytes.length); if (isSwapped) { nlistBytes2[0] = (byte) 0x02; // strx } else { nlistBytes2[3] = (byte) 0x02; // strx } nlistBytes2[4] = (byte) 0x22; // type byte[] commandBytes; if (isSwapped) { commandBytes = SymTabCommandTestData.getLittleEndian(); } else { commandBytes = SymTabCommandTestData.getBigEndian(); } final int cmdSize = commandBytes.length; if (isSwapped) { commandBytes[8] = (byte) cmdSize; // symoff commandBytes[12] = (byte) 2; // nsyms } else { commandBytes[11] = (byte) cmdSize; // symoff commandBytes[15] = (byte) 2; // nsyms } ByteBuffer commandBuffer = ByteBuffer.wrap(commandBytes) .order(isSwapped ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN); SymTabCommand symTabCommand = SymTabCommandUtils.createFromBuffer(commandBuffer); assertThat(symTabCommand.getSymoff(), equalToObject(UnsignedInteger.fromIntBits(cmdSize))); assertThat(symTabCommand.getNsyms(), equalToObject(UnsignedInteger.fromIntBits(2))); ByteBuffer byteBuffer = ByteBuffer.allocate(cmdSize + nlistTemplateBytes.length * 2) .order(commandBuffer.order()) .put(commandBytes) .put(nlistBytes1) .put(nlistBytes2); Nlist entry1 = SymTabCommandUtils.getNlistAtIndex(byteBuffer, symTabCommand, 0, is64Bit); assertThat(entry1.getN_strx(), equalToObject(UnsignedInteger.fromIntBits(0x01))); assertThat(entry1.getN_type(), equalToObject(UnsignedInteger.fromIntBits(0x11))); Nlist entry2 = SymTabCommandUtils.getNlistAtIndex(byteBuffer, symTabCommand, 1, is64Bit); assertThat(entry2.getN_strx(), equalToObject(UnsignedInteger.fromIntBits(0x02))); assertThat(entry2.getN_type(), equalToObject(UnsignedInteger.fromIntBits(0x22))); } @Test public void testGettingStringTableValueForNlist() throws Exception { final String stringTableEntry = "string_table_entry"; byte[] nlistBytes = NlistTestData.getBigEndian64Bit(); nlistBytes[3] = (byte) (0x01); // strx - first entry byte[] commandBytes = SymTabCommandTestData.getBigEndian(); final int cmdSize = commandBytes.length; commandBytes[11] = (byte) cmdSize; // symoff commandBytes[15] = (byte) 1; // nsyms commandBytes[19] = (byte) (cmdSize + nlistBytes.length); // stroff commandBytes[23] = (byte) (stringTableEntry.length() + 1); // strsize ByteBuffer byteBuffer = ByteBuffer.allocate( commandBytes.length + nlistBytes.length + 1 + stringTableEntry.length() + 1) .order(ByteOrder.BIG_ENDIAN) .put(commandBytes) .put(nlistBytes) .put((byte) 0x00) .put(stringTableEntry.getBytes(StandardCharsets.UTF_8)) .put((byte) 0x00); byteBuffer.position(0); SymTabCommand symTabCommand = SymTabCommandUtils.createFromBuffer(byteBuffer); assertThat(symTabCommand.getSymoff(), equalToObject(UnsignedInteger.fromIntBits(cmdSize))); assertThat(symTabCommand.getNsyms(), equalToObject(UnsignedInteger.fromIntBits(1))); byteBuffer.position(cmdSize); Nlist nlist = NlistUtils.createFromBuffer(byteBuffer, true); assertThat(nlist.getN_strx(), equalToObject(UnsignedInteger.fromIntBits(1))); String result = SymTabCommandUtils.getStringTableEntryForNlist( byteBuffer, symTabCommand, nlist, new NulTerminatedCharsetDecoder(StandardCharsets.UTF_8.newDecoder())); assertThat(result, equalToObject(stringTableEntry)); } @Test public void testGettingStringTableEntrySize() throws Exception { assertThat(SymTabCommandUtils.sizeOfStringTableEntryWithContents("abc"), equalTo(4)); } @Test public void testInsertingNewStringTableEntry() throws Exception { byte[] commandBytes = SymTabCommandTestData.getBigEndian(); final int cmdSize = commandBytes.length; commandBytes[11] = (byte) cmdSize; // symoff commandBytes[15] = (byte) 0; // nsyms commandBytes[19] = (byte) cmdSize; // stroff commandBytes[23] = (byte) 20; // strsize final String content = "new_entry"; ByteBuffer byteBuffer = ByteBuffer.allocate( cmdSize + 20 + SymTabCommandUtils.sizeOfStringTableEntryWithContents(content)) .order(ByteOrder.BIG_ENDIAN) .put(commandBytes) .put(new byte[20]); byteBuffer.position(0); SymTabCommand symTabCommand = SymTabCommandUtils.createFromBuffer(byteBuffer); UnsignedInteger offset = SymTabCommandUtils.insertNewStringTableEntry(byteBuffer, symTabCommand, content); assertThat(offset, equalToObject(UnsignedInteger.fromIntBits(20))); byteBuffer.position(symTabCommand.getStroff().plus(offset).intValue()); byte[] entryBytes = new byte[content.length()]; byteBuffer.get(entryBytes, 0, content.length()); assertThat(entryBytes, equalTo(content.getBytes(StandardCharsets.UTF_8))); } @Test public void testUpdatingSymTabCommand() throws Exception { byte[] commandBytes = SymTabCommandTestData.getBigEndian(); final int cmdSize = commandBytes.length; commandBytes[11] = (byte) cmdSize; // symoff commandBytes[15] = (byte) 3; // nsyms commandBytes[18] = (byte) cmdSize; // stroff commandBytes[23] = (byte) 20; // strsize SymTabCommand symTabCommand = SymTabCommandUtils.createFromBuffer( ByteBuffer.wrap(commandBytes).order(ByteOrder.BIG_ENDIAN)); final String content = "new_entry"; ByteBuffer byteBuffer = ByteBuffer.allocate( cmdSize + 20 + SymTabCommandUtils.sizeOfStringTableEntryWithContents(content)) .order(ByteOrder.BIG_ENDIAN) .putInt(SymTabCommand.LC_SYMTAB.intValue()) .putInt(cmdSize) .put(commandBytes); SymTabCommand updated = SymTabCommandUtils.updateSymTabCommand(byteBuffer, symTabCommand, content); assertThat( updated.getStrsize(), equalToObject( symTabCommand .getStrsize() .plus( UnsignedInteger.fromIntBits( SymTabCommandUtils.sizeOfStringTableEntryWithContents(content))))); byteBuffer.position(updated.getLoadCommandCommonFields().getOffsetInBinary()); byte[] updatedBytes = new byte[commandBytes.length]; byteBuffer.get(updatedBytes, 0, updatedBytes.length); SymTabCommand commandFromBuffer = SymTabCommandUtils.createFromBuffer( ByteBuffer.wrap(updatedBytes).order(ByteOrder.BIG_ENDIAN)); assertThat(commandFromBuffer.getSymoff(), equalToObject(updated.getSymoff())); assertThat(commandFromBuffer.getNsyms(), equalToObject(updated.getNsyms())); assertThat(commandFromBuffer.getStroff(), equalToObject(updated.getStroff())); assertThat(commandFromBuffer.getStrsize(), equalToObject(updated.getStrsize())); } @Test public void testCheckingForNulValue() throws Exception { byte[] nlistBytes = NlistTestData.getBigEndian64Bit(); nlistBytes[3] = (byte) 0x00; // strx - first entry byte[] commandBytes = SymTabCommandTestData.getBigEndian(); final int cmdSize = commandBytes.length; commandBytes[11] = (byte) cmdSize; // symoff commandBytes[15] = (byte) 1; // nsyms commandBytes[19] = (byte) (cmdSize + nlistBytes.length); // stroff commandBytes[23] = (byte) 0x00; // strsize ByteBuffer byteBuffer = ByteBuffer.allocate(cmdSize + nlistBytes.length) .order(ByteOrder.BIG_ENDIAN) .put(commandBytes) .put(nlistBytes); byteBuffer.position(cmdSize); Nlist nlist = NlistUtils.createFromBuffer(byteBuffer, false); assertThat(SymTabCommandUtils.stringTableEntryIsNull(nlist), equalTo(true)); } @Test public void testCheckingSlashesAtStartAndEnd() throws Exception { byte[] nlistBytes = NlistTestData.getBigEndian64Bit(); nlistBytes[3] = (byte) 0x01; // strx String entryContents = "/some/path/"; byte[] commandBytes = SymTabCommandTestData.getBigEndian(); final int cmdSize = commandBytes.length; commandBytes[11] = (byte) cmdSize; // symoff commandBytes[15] = (byte) 1; // nsyms commandBytes[19] = (byte) (cmdSize + nlistBytes.length); // stroff commandBytes[23] = (byte) (1 + entryContents.length() + 1); // strsize - nul + contents + nul ByteBuffer byteBuffer = ByteBuffer.allocate(cmdSize + nlistBytes.length + 1 + entryContents.length() + 1) .order(ByteOrder.BIG_ENDIAN) .put(commandBytes) .put(nlistBytes) .put((byte) 0x00) .put(entryContents.getBytes(StandardCharsets.UTF_8)) .put((byte) 0x00); byteBuffer.position(0); SymTabCommand symTabCommand = SymTabCommandUtils.createFromBuffer(byteBuffer); byteBuffer.position(cmdSize); Nlist nlist = NlistUtils.createFromBuffer(byteBuffer, false); assertThat( SymTabCommandUtils.stringTableEntryStartsWithSlash(byteBuffer, symTabCommand, nlist), equalTo(true)); assertThat( SymTabCommandUtils.stringTableEntryEndsWithSlash(byteBuffer, symTabCommand, nlist), equalTo(true)); } @Test public void testCheckingForEmptyString() throws Exception { byte[] nlistBytes = NlistTestData.getBigEndian64Bit(); nlistBytes[3] = (byte) 0x01; // strx byte[] commandBytes = SymTabCommandTestData.getBigEndian(); final int cmdSize = commandBytes.length; commandBytes[11] = (byte) cmdSize; // symoff commandBytes[15] = (byte) 1; // nsyms commandBytes[19] = (byte) (cmdSize + nlistBytes.length); // stroff commandBytes[23] = (byte) (1 + 1); // strsize - nul + nul ByteBuffer byteBuffer = ByteBuffer.allocate(cmdSize + nlistBytes.length + 1 + 1) .order(ByteOrder.BIG_ENDIAN) .put(commandBytes) .put(nlistBytes) .put((byte) 0x00) .put((byte) 0x00); byteBuffer.position(0); SymTabCommand symTabCommand = SymTabCommandUtils.createFromBuffer(byteBuffer); byteBuffer.position(cmdSize); Nlist nlist = NlistUtils.createFromBuffer(byteBuffer, false); assertThat( SymTabCommandUtils.stringTableEntryIsEmptyString(byteBuffer, symTabCommand, nlist), equalTo(true)); } }