/* * Copyright 2015-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.cxx; import com.facebook.buck.util.MoreStrings; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableCollection; import com.google.common.primitives.Ints; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.nio.file.Path; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Optional; public class Machos { // http://www.opensource.apple.com/source/xnu/xnu-1699.32.7/EXTERNAL_HEADERS/mach-o/loader.h // File magic static final byte[] MH_MAGIC = Ints.toByteArray(0xFEEDFACE); static final byte[] MH_MAGIC_64 = Ints.toByteArray(0xFEEDFACF); static final byte[] MH_CIGAM = Ints.toByteArray(0xCEFAEDFE); static final byte[] MH_CIGAM_64 = Ints.toByteArray(0xCFFAEDFE); // Map segment load command static final int LC_SEGMENT = 0x1; // Symbol table load command static final int LC_SYMTAB = 0x2; // UUID load command static final int LC_UUID = 0x1B; // Map 64 bit segment load command static final int LC_SEGMENT_64 = 0x19; // http://www.opensource.apple.com/source/xnu/xnu-1699.32.7/EXTERNAL_HEADERS/mach-o/stab.h // Description of object file STAB entries static final short N_OSO = (short) 0x66; private Machos() {} static void setUuid(MappedByteBuffer map, byte[] uuid) throws MachoException { int commandsCount = getHeader(map).getCommandsCount(); for (int i = 0; i < commandsCount; i++) { int command = ObjectFileScrubbers.getLittleEndianInt(map); int commandSize = ObjectFileScrubbers.getLittleEndianInt(map); if (LC_UUID == command) { ObjectFileScrubbers.putBytes(map, uuid); return; } else { /* Command body */ ObjectFileScrubbers.getBytes(map, commandSize - 8); } } throw new MachoException("LC_UUID command not found"); } static boolean isMacho(FileChannel file) throws IOException { MappedByteBuffer map = file.map(FileChannel.MapMode.READ_ONLY, 0, MH_MAGIC.length); byte[] magic = ObjectFileScrubbers.getBytes(map, MH_MAGIC.length); return Arrays.equals(MH_MAGIC, magic) || Arrays.equals(MH_CIGAM, magic) || Arrays.equals(MH_MAGIC_64, magic) || Arrays.equals(MH_CIGAM_64, magic); } static void relativizeOsoSymbols(FileChannel file, ImmutableCollection<Path> cellRoots) throws IOException, MachoException { for (Path root : cellRoots) { Preconditions.checkState(root.isAbsolute()); } long size = file.size(); MappedByteBuffer map = file.map(FileChannel.MapMode.READ_WRITE, 0, size); MachoHeader header = getHeader(map); int symbolTableOffset = 0; int symbolTableCount = 0; int stringTableOffset = 0; int stringTableSizePosition = 0; int stringTableSize = 0; boolean symbolTableSegmentFound = false; int segmentSizePosition = 0; int segmentSize = 0; int commandsCount = header.getCommandsCount(); for (int i = 0; i < commandsCount; i++) { int commandStart = map.position(); // NOPMD int command = ObjectFileScrubbers.getLittleEndianInt(map); int commandSize = ObjectFileScrubbers.getLittleEndianInt(map); // NOPMD switch (command) { case LC_SYMTAB: symbolTableOffset = ObjectFileScrubbers.getLittleEndianInt(map); symbolTableCount = ObjectFileScrubbers.getLittleEndianInt(map); stringTableOffset = ObjectFileScrubbers.getLittleEndianInt(map); stringTableSizePosition = map.position(); stringTableSize = ObjectFileScrubbers.getLittleEndianInt(map); symbolTableSegmentFound = true; break; case LC_SEGMENT: /* segment name */ ObjectFileScrubbers.getBytes(map, 16); /* vm address */ ObjectFileScrubbers.getLittleEndianInt(map); /* vm size */ ObjectFileScrubbers.getLittleEndianInt(map); int segmentFileOffset = ObjectFileScrubbers.getLittleEndianInt(map); int segmentFileSizePosition = map.position(); int segmentFileSize = ObjectFileScrubbers.getLittleEndianInt(map); /* maximum vm protection */ ObjectFileScrubbers.getLittleEndianInt(map); /* initial vm protection */ ObjectFileScrubbers.getLittleEndianInt(map); /* number of sections */ ObjectFileScrubbers.getLittleEndianInt(map); /* flags */ ObjectFileScrubbers.getLittleEndianInt(map); if (segmentFileOffset + segmentFileSize == size) { if (segmentSizePosition != 0) { throw new MachoException("multiple map segment commands map string table"); } segmentSizePosition = segmentFileSizePosition; segmentSize = segmentFileSize; } break; case LC_SEGMENT_64: /* segment name */ ObjectFileScrubbers.getBytes(map, 16); /* vm address */ ObjectFileScrubbers.getLittleEndianLong(map); /* vm size */ ObjectFileScrubbers.getLittleEndianLong(map); long segment64FileOffset = ObjectFileScrubbers.getLittleEndianLong(map); int segment64FileSizePosition = map.position(); long segment64FileSize = ObjectFileScrubbers.getLittleEndianLong(map); /* maximum vm protection */ ObjectFileScrubbers.getLittleEndianInt(map); /* initial vm protection */ ObjectFileScrubbers.getLittleEndianInt(map); /* number of sections */ ObjectFileScrubbers.getLittleEndianInt(map); /* flags */ ObjectFileScrubbers.getLittleEndianInt(map); if (segment64FileOffset + segment64FileSize == size) { if (segmentSizePosition != 0) { throw new MachoException("multiple map segment commands map string table"); } segmentSizePosition = segment64FileSizePosition; if (segment64FileSize > Ints.MAX_POWER_OF_TWO) { throw new MachoException("map segment file size too big"); } segmentSize = (int) segment64FileSize; } break; } map.position(commandStart + commandSize); } if (!symbolTableSegmentFound) { throw new MachoException("LC_SYMTAB command not found"); } if (stringTableOffset + stringTableSize != size) { throw new MachoException("String table does not end at end of file"); } if (stringTableSize == 0) { return; } if (segmentSizePosition == 0 || segmentSize == 0) { throw new MachoException("LC_SEGMENT or LC_SEGMENT_64 command for string table not found"); } map.position(stringTableOffset); if (map.get() != 0x20) { throw new MachoException("First character in the string table is not a space"); } if (map.get() != 0x00) { throw new MachoException("Second character in the string table is not a NUL"); } int currentStringTableOffset = map.position(); byte[] stringTableBytes = new byte[stringTableSize]; map.position(stringTableOffset); map.get(stringTableBytes); ByteBuffer stringTable = ByteBuffer.wrap(stringTableBytes); map.position(symbolTableOffset); Map<Integer, Integer> strings = new HashMap<>(); for (int i = 0; i < symbolTableCount; i++) { int stringTableIndexPosition = map.position(); int stringTableIndex = ObjectFileScrubbers.getLittleEndianInt(map); byte type = map.get(); /* section */ map.get(); /* description */ ObjectFileScrubbers.getLittleEndianShort(map); int valuePosition = map.position(); if (header.getIs64Bit()) { /* value */ ObjectFileScrubbers.getLittleEndianLong(map); } else { /* value */ ObjectFileScrubbers.getLittleEndianInt(map); } if (stringTableIndex < 2) { continue; } int position = map.position(); try { int newStringTableIndex; if (strings.containsKey(stringTableIndex)) { newStringTableIndex = strings.get(stringTableIndex); } else { stringTable.position(stringTableIndex); String string = ObjectFileScrubbers.getAsciiString(stringTable); if (type == N_OSO) { for (Path root : cellRoots) { String rootPrefix = root + "/"; Optional<String> fixed = MoreStrings.stripPrefix(string, rootPrefix).map(input -> "./" + input); if (fixed.isPresent()) { string = fixed.get(); break; } } map.position(valuePosition); int lastModifiedValue = ObjectFileCommonModificationDate.COMMON_MODIFICATION_TIME_STAMP; if (header.getIs64Bit()) { ObjectFileScrubbers.putLittleEndianLong(map, lastModifiedValue); } else { ObjectFileScrubbers.putLittleEndianInt(map, lastModifiedValue); } } map.position(currentStringTableOffset); ObjectFileScrubbers.putAsciiString(map, string); newStringTableIndex = currentStringTableOffset - stringTableOffset; currentStringTableOffset = map.position(); strings.put(stringTableIndex, newStringTableIndex); } map.position(stringTableIndexPosition); ObjectFileScrubbers.putLittleEndianInt(map, newStringTableIndex); } finally { map.position(position); } } map.position(stringTableSizePosition); int newStringTableSize = currentStringTableOffset - stringTableOffset; ObjectFileScrubbers.putLittleEndianInt(map, newStringTableSize); map.position(segmentSizePosition); ObjectFileScrubbers.putLittleEndianInt( map, segmentSize + (newStringTableSize - stringTableSize)); file.truncate(currentStringTableOffset); } private static MachoHeader getHeader(MappedByteBuffer map) throws MachoException { byte[] magic = ObjectFileScrubbers.getBytes(map, MH_MAGIC.length); boolean is64bit; if (Arrays.equals(MH_MAGIC, magic) || Arrays.equals(MH_CIGAM, magic)) { is64bit = false; } else if (Arrays.equals(MH_MAGIC_64, magic) || Arrays.equals(MH_CIGAM_64, magic)) { is64bit = true; } else { throw new MachoException("invalid Mach-O magic"); } /* CPU type */ ObjectFileScrubbers.getLittleEndianInt(map); /* CPU subtype */ ObjectFileScrubbers.getLittleEndianInt(map); /* File type */ ObjectFileScrubbers.getLittleEndianInt(map); int commandsCount = ObjectFileScrubbers.getLittleEndianInt(map); /* Commands size */ ObjectFileScrubbers.getLittleEndianInt(map); /* Flags */ ObjectFileScrubbers.getLittleEndianInt(map); if (is64bit) { /* reserved */ ObjectFileScrubbers.getLittleEndianInt(map); } return MachoHeader.of(commandsCount, is64bit); } @SuppressWarnings("serial") public static class MachoException extends Exception { public MachoException(String msg) { super(msg); } } }