/*
 * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/*
 *
 * File Layout generated by JMachORelocObject
 *
 * MachO Header
 * Load Commands
 *   LC_SEGMENT_64
 *    - Sections
 *   LC_VERSION_MIN_MAX
 *   LC_SYMTAB
 *   LC_DYSYMTAB
 * Section Data
 * Relocation entries
 * Symbol table
 *
 */

package jdk.tools.jaotc.binformat.macho;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import jdk.tools.jaotc.binformat.BinaryContainer;
import jdk.tools.jaotc.binformat.ByteContainer;
import jdk.tools.jaotc.binformat.CodeContainer;
import jdk.tools.jaotc.binformat.ReadOnlyDataContainer;
import jdk.tools.jaotc.binformat.Relocation;
import jdk.tools.jaotc.binformat.Relocation.RelocType;
import jdk.tools.jaotc.binformat.Symbol;
import jdk.tools.jaotc.binformat.Symbol.Kind;
import jdk.tools.jaotc.binformat.macho.MachO.dysymtab_command;
import jdk.tools.jaotc.binformat.macho.MachO.mach_header_64;
import jdk.tools.jaotc.binformat.macho.MachO.nlist_64;
import jdk.tools.jaotc.binformat.macho.MachO.reloc_info;
import jdk.tools.jaotc.binformat.macho.MachO.section_64;
import jdk.tools.jaotc.binformat.macho.MachO.segment_command_64;
import jdk.tools.jaotc.binformat.macho.MachO.symtab_command;
import jdk.tools.jaotc.binformat.macho.MachO.version_min_command;

public class JMachORelocObject {

    private final BinaryContainer binContainer;

    private final MachOContainer machoContainer;

    private final int segmentSize;

    public JMachORelocObject(BinaryContainer binContainer, String outputFileName) {
        this.binContainer = binContainer;
        this.machoContainer = new MachOContainer(outputFileName);
        this.segmentSize = binContainer.getCodeSegmentSize();
    }

    private void createByteSection(ArrayList<MachOSection> sections,
                    ByteContainer c, String sectName, String segName, int scnFlags) {

        if (c.getByteArray().length == 0) {
            // System.out.println("Skipping creation of " + sectName + " section, no data\n");
        }

        MachOSection sect = new MachOSection(sectName,
                        segName,
                        c.getByteArray(),
                        scnFlags,
                        c.hasRelocations(),
                        segmentSize);
        // Add this section to our list
        sections.add(sect);

        // Record the section Id (0 relative)
        c.setSectionId(sections.size() - 1);

        // TODO: Clear out code section data to allow for GC
        // c.clear();
    }

    private void createCodeSection(ArrayList<MachOSection> sections, CodeContainer c) {
        createByteSection(sections, c, /* c.getContainerName() */ "__text", "__TEXT",
                        section_64.S_ATTR_PURE_INSTRUCTIONS |
                                        section_64.S_ATTR_SOME_INSTRUCTIONS);
    }

    private void createReadOnlySection(ArrayList<MachOSection> sections, ReadOnlyDataContainer c) {
        createByteSection(sections, c, c.getContainerName(), "__TEXT",
                        section_64.S_ATTR_SOME_INSTRUCTIONS);
    }

    private void createReadWriteSection(ArrayList<MachOSection> sections, ByteContainer c) {
        createByteSection(sections, c, c.getContainerName(), "__DATA", section_64.S_REGULAR);
    }

    
Creates an MachO relocatable object.
Params:
  • relocationTable –
  • symbols –
Throws:
  • IOException – throws IOException as a result of file system access failures.
/** * Creates an MachO relocatable object. * * @param relocationTable * @param symbols * @throws IOException throws {@code IOException} as a result of file system access failures. */
public void createMachORelocObject(Map<Symbol, List<Relocation>> relocationTable, Collection<Symbol> symbols) throws IOException { // Allocate MachO Header // with 4 load commands // LC_SEGMENT_64 // LC_VERSION_MIN_MACOSX // LC_SYMTAB // LC_DYSYMTAB MachOHeader mh = new MachOHeader(); ArrayList<MachOSection> sections = new ArrayList<>(); // Create Sections contained in the main Segment LC_SEGMENT_64 createCodeSection(sections, binContainer.getCodeContainer()); createReadOnlySection(sections, binContainer.getMetaspaceNamesContainer()); createReadOnlySection(sections, binContainer.getKlassesOffsetsContainer()); createReadOnlySection(sections, binContainer.getMethodsOffsetsContainer()); createReadOnlySection(sections, binContainer.getKlassesDependenciesContainer()); createReadOnlySection(sections, binContainer.getMethodMetadataContainer()); createReadOnlySection(sections, binContainer.getStubsOffsetsContainer()); createReadOnlySection(sections, binContainer.getHeaderContainer().getContainer()); createReadOnlySection(sections, binContainer.getCodeSegmentsContainer()); createReadOnlySection(sections, binContainer.getConstantDataContainer()); createReadOnlySection(sections, binContainer.getConfigContainer()); createReadWriteSection(sections, binContainer.getKlassesGotContainer()); createReadWriteSection(sections, binContainer.getCountersGotContainer()); createReadWriteSection(sections, binContainer.getMetadataGotContainer()); createReadWriteSection(sections, binContainer.getMethodStateContainer()); createReadWriteSection(sections, binContainer.getOopGotContainer()); createReadWriteSection(sections, binContainer.getExtLinkageGOTContainer()); // Update the Header sizeofcmds size. // This doesn't include the Header struct size mh.setCmdSizes(4, segment_command_64.totalsize + (section_64.totalsize * sections.size()) + version_min_command.totalsize + symtab_command.totalsize + dysymtab_command.totalsize); // Initialize file offset for data past commands int fileOffset = mach_header_64.totalsize + mh.getCmdSize(); // and round it up fileOffset = (fileOffset + (sections.get(0).getAlign() - 1)) & ~((sections.get(0).getAlign() - 1)); long address = 0; int segmentOffset = fileOffset; for (int i = 0; i < sections.size(); i++) { MachOSection sect = sections.get(i); fileOffset = (fileOffset + (sect.getAlign() - 1)) & ~((sect.getAlign() - 1)); address = (address + (sect.getAlign() - 1)) & ~((sect.getAlign() - 1)); sect.setOffset(fileOffset); sect.setAddr(address); fileOffset += sect.getSize(); address += sect.getSize(); } // File size for Segment data int segSize = fileOffset - segmentOffset; // Create the LC_SEGMENT_64 Segment which contains the MachOSections MachOSegment seg = new MachOSegment(segment_command_64.totalsize + (section_64.totalsize * sections.size()), segmentOffset, segSize, sections.size()); MachOVersion vers = new MachOVersion(); // Get symbol data from BinaryContainer object's symbol tables MachOSymtab symtab = createMachOSymbolTables(sections, symbols); // Create LC_DYSYMTAB command MachODySymtab dysymtab = new MachODySymtab(symtab.getNumLocalSyms(), symtab.getNumGlobalSyms(), symtab.getNumUndefSyms()); // Create the Relocation Tables MachORelocTable machORelocs = createMachORelocTable(sections, relocationTable, symtab); // Calculate file offset for relocation data fileOffset = (fileOffset + (MachORelocTable.getAlign() - 1)) & ~((MachORelocTable.getAlign() - 1)); // Update relocation sizing information in each section for (int i = 0; i < sections.size(); i++) { MachOSection sect = sections.get(i); if (sect.hasRelocations()) { int nreloc = machORelocs.getNumRelocs(i); sect.setReloff(fileOffset); sect.setRelcount(nreloc); fileOffset += (nreloc * reloc_info.totalsize); } } // Calculate and set file offset for symbol table data fileOffset = (fileOffset + (MachOSymtab.getAlign() - 1)) & ~((MachOSymtab.getAlign() - 1)); symtab.setOffset(fileOffset); // Write Out Header machoContainer.writeBytes(mh.getArray()); // Write out first Segment machoContainer.writeBytes(seg.getArray()); // Write out sections within first Segment for (int i = 0; i < sections.size(); i++) { MachOSection sect = sections.get(i); machoContainer.writeBytes(sect.getArray()); } // Write out LC_VERSION_MIN_MACOSX command machoContainer.writeBytes(vers.getArray()); // Write out LC_SYMTAB command symtab.calcSizes(); machoContainer.writeBytes(symtab.getCmdArray()); // Write out LC_DYSYMTAB command machoContainer.writeBytes(dysymtab.getArray()); // Write out data associated with each Section for (int i = 0; i < sections.size(); i++) { MachOSection sect = sections.get(i); machoContainer.writeBytes(sect.getDataArray(), sect.getAlign()); } // Write out the relocation tables for all sections for (int i = 0; i < sections.size(); i++) { if (machORelocs.getNumRelocs(i) > 0) { machoContainer.writeBytes(machORelocs.getRelocData(i), MachORelocTable.getAlign()); } } // Write out data associated with LC_SYMTAB machoContainer.writeBytes(symtab.getDataArray(), MachOSymtab.getAlign()); machoContainer.close(); }
Construct MachO symbol data from BinaryContainer object's symbol tables. Both dynamic MachO symbol table and MachO symbol table are created from BinaryContainer's symbol info.
Params:
  • sections –
  • symbols –
/** * Construct MachO symbol data from BinaryContainer object's symbol tables. Both dynamic MachO * symbol table and MachO symbol table are created from BinaryContainer's symbol info. * * @param sections * @param symbols */
private static MachOSymtab createMachOSymbolTables(ArrayList<MachOSection> sections, Collection<Symbol> symbols) { MachOSymtab symtab = new MachOSymtab(); // First, create the initial null symbol. This is a local symbol. symtab.addSymbolEntry("", (byte) nlist_64.N_UNDF, (byte) 0, 0); // Now create MachO symbol entries for all symbols. for (Symbol symbol : symbols) { int sectionId = symbol.getSection().getSectionId(); // Symbol offsets are relative to the section memory addr long sectionAddr = sections.get(sectionId).getAddr(); MachOSymbol machoSymbol = symtab.addSymbolEntry(symbol.getName(), getMachOTypeOf(symbol), (byte) sectionId, symbol.getOffset() + sectionAddr); symbol.setNativeSymbol(machoSymbol); } // Now that all symbols are enterred, update the // symbol indexes. This is necessary since they will // be reordered based on local, global and undefined. symtab.updateIndexes(); return (symtab); } private static byte getMachOTypeOf(Symbol sym) { Kind kind = sym.getKind(); byte type = nlist_64.N_UNDF; // Global or Local if (sym.getBinding() == Symbol.Binding.GLOBAL) { type = nlist_64.N_EXT; } // If Function or Data, add section type if (kind == Symbol.Kind.NATIVE_FUNCTION || kind == Symbol.Kind.JAVA_FUNCTION || kind == Symbol.Kind.OBJECT) { type |= (nlist_64.N_SECT); } return (type); }
Construct a MachO relocation table from BinaryContainer object's relocation tables.
Params:
  • sections –
  • relocationTable –
  • symtab –
/** * Construct a MachO relocation table from BinaryContainer object's relocation tables. * * @param sections * @param relocationTable * @param symtab */
private MachORelocTable createMachORelocTable(ArrayList<MachOSection> sections, Map<Symbol, List<Relocation>> relocationTable, MachOSymtab symtab) { MachORelocTable machORelocTable = new MachORelocTable(sections.size()); /* * For each of the symbols with associated relocation records, create a MachO relocation * entry. */ for (Map.Entry<Symbol, List<Relocation>> entry : relocationTable.entrySet()) { List<Relocation> relocs = entry.getValue(); Symbol symbol = entry.getKey(); for (Relocation reloc : relocs) { createRelocation(symbol, reloc, machORelocTable); } } for (Map.Entry<Symbol, Relocation> entry : binContainer.getUniqueRelocationTable().entrySet()) { createRelocation(entry.getKey(), entry.getValue(), machORelocTable); } return (machORelocTable); } private static void createRelocation(Symbol symbol, Relocation reloc, MachORelocTable machORelocTable) { RelocType relocType = reloc.getType(); int machORelocType = getMachORelocationType(relocType); MachOSymbol sym = (MachOSymbol) symbol.getNativeSymbol(); int symno = sym.getIndex(); int sectindex = reloc.getSection().getSectionId(); int offset = reloc.getOffset(); int pcrel = 0; int length = 0; int isextern = 1; switch (relocType) { case JAVA_CALL_DIRECT: case STUB_CALL_DIRECT: case FOREIGN_CALL_INDIRECT_GOT: { // Create relocation entry int addend = -4; // Size in bytes of the patch location // Relocation should be applied at the location after call operand offset = offset + reloc.getSize() + addend; pcrel = 1; length = 2; break; } case JAVA_CALL_INDIRECT: { // Do nothing. return; } case METASPACE_GOT_REFERENCE: case EXTERNAL_PLT_TO_GOT: { int addend = -4; // Size of 32-bit address of the GOT /* * Relocation should be applied before the test instruction to the move instruction. * reloc.getOffset() points to the test instruction after the instruction that loads * the address of polling page. So set the offset appropriately. */ offset = offset + addend; pcrel = 1; length = 2; break; } case EXTERNAL_GOT_TO_PLT: { // this is load time relocations pcrel = 0; length = 3; break; } default: throw new InternalError("Unhandled relocation type: " + relocType); } machORelocTable.createRelocationEntry(sectindex, offset, symno, pcrel, length, isextern, machORelocType); } private static int getMachORelocationType(RelocType relocType) { int machORelocType = 0; switch (MachOTargetInfo.getMachOArch()) { case mach_header_64.CPU_TYPE_X86_64: // Return X86_64_RELOC_* entries based on relocType if (relocType == RelocType.JAVA_CALL_DIRECT || relocType == RelocType.FOREIGN_CALL_INDIRECT_GOT) { machORelocType = reloc_info.X86_64_RELOC_BRANCH; } else if (relocType == RelocType.STUB_CALL_DIRECT) { machORelocType = reloc_info.X86_64_RELOC_BRANCH; } else if (relocType == RelocType.JAVA_CALL_INDIRECT) { machORelocType = reloc_info.X86_64_RELOC_NONE; } else if (relocType == RelocType.METASPACE_GOT_REFERENCE || relocType == RelocType.EXTERNAL_PLT_TO_GOT) { machORelocType = reloc_info.X86_64_RELOC_BRANCH; } else if (relocType == RelocType.EXTERNAL_GOT_TO_PLT) { machORelocType = reloc_info.X86_64_RELOC_UNSIGNED; } else { assert false : "Unhandled relocation type: " + relocType; } break; default: System.out.println("Relocation Type mapping: Unhandled architecture"); } return machORelocType; } }