1 /*
   2  * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /*
  25  *
  26  * File Layout generated by JMachORelocObject
  27  *
  28  * MachO Header
  29  * Load Commands
  30  *   LC_SEGMENT_64
  31  *    - Sections
  32  *   LC_VERSION_MIN_MAX
  33  *   LC_SYMTAB
  34  *   LC_DYSYMTAB
  35  * Section Data
  36  * Relocation entries
  37  * Symbol table
  38  *
  39  */
  40 
  41 
  42 
  43 package jdk.tools.jaotc.binformat.macho;
  44 
  45 import java.io.IOException;
  46 import java.util.ArrayList;
  47 import java.util.Collection;
  48 import java.util.List;
  49 import java.util.Map;
  50 
  51 import jdk.tools.jaotc.binformat.BinaryContainer;
  52 import jdk.tools.jaotc.binformat.ByteContainer;
  53 import jdk.tools.jaotc.binformat.CodeContainer;
  54 import jdk.tools.jaotc.binformat.ReadOnlyDataContainer;
  55 import jdk.tools.jaotc.binformat.Relocation;
  56 import jdk.tools.jaotc.binformat.Relocation.RelocType;
  57 import jdk.tools.jaotc.binformat.Symbol;
  58 import jdk.tools.jaotc.binformat.Symbol.Kind;
  59 import jdk.tools.jaotc.binformat.macho.MachO.dysymtab_command;
  60 import jdk.tools.jaotc.binformat.macho.MachO.mach_header_64;
  61 import jdk.tools.jaotc.binformat.macho.MachO.nlist_64;
  62 import jdk.tools.jaotc.binformat.macho.MachO.reloc_info;
  63 import jdk.tools.jaotc.binformat.macho.MachO.section_64;
  64 import jdk.tools.jaotc.binformat.macho.MachO.segment_command_64;
  65 import jdk.tools.jaotc.binformat.macho.MachO.symtab_command;
  66 import jdk.tools.jaotc.binformat.macho.MachO.version_min_command;
  67 
  68 public class JMachORelocObject {
  69 
  70     private final BinaryContainer binContainer;
  71 
  72     private final MachOContainer machoContainer;
  73 
  74     private final int segmentSize;
  75 
  76     public JMachORelocObject(BinaryContainer binContainer, String outputFileName) {
  77         this.binContainer = binContainer;
  78         this.machoContainer = new MachOContainer(outputFileName);
  79         this.segmentSize = binContainer.getCodeSegmentSize();
  80     }
  81 
  82     private void createByteSection(ArrayList<MachOSection> sections,
  83                     ByteContainer c, String sectName, String segName, int scnFlags) {
  84 
  85         if (c.getByteArray().length == 0) {
  86             // System.out.println("Skipping creation of " + sectName + " section, no data\n");
  87         }
  88 
  89         MachOSection sect = new MachOSection(sectName,
  90                         segName,
  91                         c.getByteArray(),
  92                         scnFlags,
  93                         c.hasRelocations(),
  94                         segmentSize);
  95         // Add this section to our list
  96         sections.add(sect);
  97 
  98         // Record the section Id (0 relative)
  99         c.setSectionId(sections.size() - 1);
 100 
 101         // TODO: Clear out code section data to allow for GC
 102         // c.clear();
 103     }
 104 
 105     private void createCodeSection(ArrayList<MachOSection> sections, CodeContainer c) {
 106         createByteSection(sections, c, /* c.getContainerName() */ "__text", "__TEXT",
 107                         section_64.S_ATTR_PURE_INSTRUCTIONS |
 108                                         section_64.S_ATTR_SOME_INSTRUCTIONS);
 109     }
 110 
 111     private void createReadOnlySection(ArrayList<MachOSection> sections, ReadOnlyDataContainer c) {
 112         createByteSection(sections, c, c.getContainerName(), "__TEXT",
 113                         section_64.S_ATTR_SOME_INSTRUCTIONS);
 114     }
 115 
 116     private void createReadWriteSection(ArrayList<MachOSection> sections, ByteContainer c) {
 117         createByteSection(sections, c, c.getContainerName(), "__DATA", section_64.S_REGULAR);
 118     }
 119 
 120     /**
 121      * Creates an MachO relocatable object.
 122      *
 123      * @param relocationTable
 124      * @param symbols
 125      * @throws IOException throws {@code IOException} as a result of file system access failures.
 126      */
 127     public void createMachORelocObject(Map<Symbol, List<Relocation>> relocationTable, Collection<Symbol> symbols) throws IOException {
 128         // Allocate MachO Header
 129         // with 4 load commands
 130         // LC_SEGMENT_64
 131         // LC_VERSION_MIN_MACOSX
 132         // LC_SYMTAB
 133         // LC_DYSYMTAB
 134 
 135         MachOHeader mh = new MachOHeader();
 136 
 137         ArrayList<MachOSection> sections = new ArrayList<>();
 138 
 139         // Create Sections contained in the main Segment LC_SEGMENT_64
 140 
 141         createCodeSection(sections, binContainer.getCodeContainer());
 142         createReadOnlySection(sections, binContainer.getMetaspaceNamesContainer());
 143         createReadOnlySection(sections, binContainer.getKlassesOffsetsContainer());
 144         createReadOnlySection(sections, binContainer.getMethodsOffsetsContainer());
 145         createReadOnlySection(sections, binContainer.getKlassesDependenciesContainer());
 146         createReadOnlySection(sections, binContainer.getMethodMetadataContainer());
 147         createReadOnlySection(sections, binContainer.getStubsOffsetsContainer());
 148         createReadOnlySection(sections, binContainer.getHeaderContainer().getContainer());
 149         createReadOnlySection(sections, binContainer.getCodeSegmentsContainer());
 150         createReadOnlySection(sections, binContainer.getConstantDataContainer());
 151         createReadOnlySection(sections, binContainer.getConfigContainer());
 152         createReadWriteSection(sections, binContainer.getKlassesGotContainer());
 153         createReadWriteSection(sections, binContainer.getCountersGotContainer());
 154         createReadWriteSection(sections, binContainer.getMetadataGotContainer());
 155         createReadWriteSection(sections, binContainer.getMethodStateContainer());
 156         createReadWriteSection(sections, binContainer.getOopGotContainer());
 157         createReadWriteSection(sections, binContainer.getExtLinkageGOTContainer());
 158 
 159         // Update the Header sizeofcmds size.
 160         // This doesn't include the Header struct size
 161         mh.setCmdSizes(4, segment_command_64.totalsize +
 162                         (section_64.totalsize * sections.size()) +
 163                         version_min_command.totalsize +
 164                         symtab_command.totalsize +
 165                         dysymtab_command.totalsize);
 166 
 167         // Initialize file offset for data past commands
 168         int fileOffset = mach_header_64.totalsize + mh.getCmdSize();
 169         // and round it up
 170         fileOffset = (fileOffset + (sections.get(0).getAlign() - 1)) & ~((sections.get(0).getAlign() - 1));
 171         long address = 0;
 172         int segmentOffset = fileOffset;
 173 
 174         for (int i = 0; i < sections.size(); i++) {
 175             MachOSection sect = sections.get(i);
 176             fileOffset = (fileOffset + (sect.getAlign() - 1)) & ~((sect.getAlign() - 1));
 177             address = (address + (sect.getAlign() - 1)) & ~((sect.getAlign() - 1));
 178             sect.setOffset(fileOffset);
 179             sect.setAddr(address);
 180             fileOffset += sect.getSize();
 181             address += sect.getSize();
 182         }
 183 
 184         // File size for Segment data
 185         int segSize = fileOffset - segmentOffset;
 186 
 187         // Create the LC_SEGMENT_64 Segment which contains the MachOSections
 188         MachOSegment seg = new MachOSegment(segment_command_64.totalsize +
 189                         (section_64.totalsize * sections.size()),
 190                         segmentOffset,
 191                         segSize,
 192                         sections.size());
 193 
 194         MachOVersion vers = new MachOVersion();
 195 
 196         // Get symbol data from BinaryContainer object's symbol tables
 197         MachOSymtab symtab = createMachOSymbolTables(sections, symbols);
 198 
 199         // Create LC_DYSYMTAB command
 200         MachODySymtab dysymtab = new MachODySymtab(symtab.getNumLocalSyms(),
 201                         symtab.getNumGlobalSyms(),
 202                         symtab.getNumUndefSyms());
 203 
 204         // Create the Relocation Tables
 205         MachORelocTable machORelocs = createMachORelocTable(sections, relocationTable, symtab);
 206         // Calculate file offset for relocation data
 207         fileOffset = (fileOffset + (MachORelocTable.getAlign() - 1)) & ~((MachORelocTable.getAlign() - 1));
 208 
 209         // Update relocation sizing information in each section
 210         for (int i = 0; i < sections.size(); i++) {
 211             MachOSection sect = sections.get(i);
 212             if (sect.hasRelocations()) {
 213                 int nreloc = machORelocs.getNumRelocs(i);
 214                 sect.setReloff(fileOffset);
 215                 sect.setRelcount(nreloc);
 216                 fileOffset += (nreloc * reloc_info.totalsize);
 217             }
 218         }
 219 
 220         // Calculate and set file offset for symbol table data
 221         fileOffset = (fileOffset + (MachOSymtab.getAlign() - 1)) & ~((MachOSymtab.getAlign() - 1));
 222         symtab.setOffset(fileOffset);
 223 
 224         // Write Out Header
 225         machoContainer.writeBytes(mh.getArray());
 226         // Write out first Segment
 227         machoContainer.writeBytes(seg.getArray());
 228         // Write out sections within first Segment
 229         for (int i = 0; i < sections.size(); i++) {
 230             MachOSection sect = sections.get(i);
 231             machoContainer.writeBytes(sect.getArray());
 232         }
 233 
 234         // Write out LC_VERSION_MIN_MACOSX command
 235         machoContainer.writeBytes(vers.getArray());
 236 
 237         // Write out LC_SYMTAB command
 238         symtab.calcSizes();
 239         machoContainer.writeBytes(symtab.getCmdArray());
 240 
 241         // Write out LC_DYSYMTAB command
 242         machoContainer.writeBytes(dysymtab.getArray());
 243 
 244         // Write out data associated with each Section
 245         for (int i = 0; i < sections.size(); i++) {
 246             MachOSection sect = sections.get(i);
 247             machoContainer.writeBytes(sect.getDataArray(), sect.getAlign());
 248         }
 249 
 250         // Write out the relocation tables for all sections
 251         for (int i = 0; i < sections.size(); i++) {
 252             if (machORelocs.getNumRelocs(i) > 0) {
 253                 machoContainer.writeBytes(machORelocs.getRelocData(i), MachORelocTable.getAlign());
 254             }
 255         }
 256 
 257         // Write out data associated with LC_SYMTAB
 258         machoContainer.writeBytes(symtab.getDataArray(), MachOSymtab.getAlign());
 259 
 260         machoContainer.close();
 261     }
 262 
 263     /**
 264      * Construct MachO symbol data from BinaryContainer object's symbol tables. Both dynamic MachO
 265      * symbol table and MachO symbol table are created from BinaryContainer's symbol info.
 266      *
 267      * @param sections
 268      * @param symbols
 269      */
 270     private static MachOSymtab createMachOSymbolTables(ArrayList<MachOSection> sections,
 271                     Collection<Symbol> symbols) {
 272         MachOSymtab symtab = new MachOSymtab();
 273         // First, create the initial null symbol. This is a local symbol.
 274         symtab.addSymbolEntry("", (byte) nlist_64.N_UNDF, (byte) 0, 0);
 275 
 276         // Now create MachO symbol entries for all symbols.
 277         for (Symbol symbol : symbols) {
 278             int sectionId = symbol.getSection().getSectionId();
 279 
 280             // Symbol offsets are relative to the section memory addr
 281             long sectionAddr = sections.get(sectionId).getAddr();
 282 
 283             MachOSymbol machoSymbol = symtab.addSymbolEntry(symbol.getName(),
 284                             getMachOTypeOf(symbol),
 285                             (byte) sectionId,
 286                             symbol.getOffset() + sectionAddr);
 287             symbol.setNativeSymbol(machoSymbol);
 288         }
 289 
 290         // Now that all symbols are enterred, update the
 291         // symbol indexes. This is necessary since they will
 292         // be reordered based on local, global and undefined.
 293         symtab.updateIndexes();
 294 
 295         return (symtab);
 296     }
 297 
 298     private static byte getMachOTypeOf(Symbol sym) {
 299         Kind kind = sym.getKind();
 300         byte type = nlist_64.N_UNDF;
 301 
 302         // Global or Local
 303         if (sym.getBinding() == Symbol.Binding.GLOBAL) {
 304             type = nlist_64.N_EXT;
 305         }
 306         // If Function or Data, add section type
 307         if (kind == Symbol.Kind.NATIVE_FUNCTION ||
 308                         kind == Symbol.Kind.JAVA_FUNCTION ||
 309                         kind == Symbol.Kind.OBJECT) {
 310             type |= (nlist_64.N_SECT);
 311         }
 312 
 313         return (type);
 314     }
 315 
 316     /**
 317      * Construct a MachO relocation table from BinaryContainer object's relocation tables.
 318      *
 319      * @param sections
 320      * @param relocationTable
 321      * @param symtab
 322      */
 323     private MachORelocTable createMachORelocTable(ArrayList<MachOSection> sections,
 324                     Map<Symbol, List<Relocation>> relocationTable,
 325                     MachOSymtab symtab) {
 326 
 327         MachORelocTable machORelocTable = new MachORelocTable(sections.size());
 328         /*
 329          * For each of the symbols with associated relocation records, create a MachO relocation
 330          * entry.
 331          */
 332         for (Map.Entry<Symbol, List<Relocation>> entry : relocationTable.entrySet()) {
 333             List<Relocation> relocs = entry.getValue();
 334             Symbol symbol = entry.getKey();
 335 
 336             for (Relocation reloc : relocs) {
 337                 createRelocation(symbol, reloc, machORelocTable);
 338             }
 339         }
 340 
 341         for (Map.Entry<Symbol, Relocation> entry : binContainer.getUniqueRelocationTable().entrySet()) {
 342             createRelocation(entry.getKey(), entry.getValue(), machORelocTable);
 343         }
 344 
 345         return (machORelocTable);
 346     }
 347 
 348     private static void createRelocation(Symbol symbol, Relocation reloc, MachORelocTable machORelocTable) {
 349         RelocType relocType = reloc.getType();
 350 
 351         int machORelocType = getMachORelocationType(relocType);
 352         MachOSymbol sym = (MachOSymbol) symbol.getNativeSymbol();
 353         int symno = sym.getIndex();
 354         int sectindex = reloc.getSection().getSectionId();
 355         int offset = reloc.getOffset();
 356         int pcrel = 0;
 357         int length = 0;
 358         int isextern = 1;
 359 
 360         switch (relocType) {
 361             case JAVA_CALL_DIRECT:
 362             case STUB_CALL_DIRECT:
 363             case FOREIGN_CALL_INDIRECT_GOT: {
 364                 // Create relocation entry
 365                 int addend = -4; // Size in bytes of the patch location
 366                 // Relocation should be applied at the location after call operand
 367                 offset = offset + reloc.getSize() + addend;
 368                 pcrel = 1;
 369                 length = 2;
 370                 break;
 371             }
 372             case JAVA_CALL_INDIRECT: {
 373                 // Do nothing.
 374                 return;
 375             }
 376             case METASPACE_GOT_REFERENCE:
 377             case EXTERNAL_PLT_TO_GOT: {
 378                 int addend = -4; // Size of 32-bit address of the GOT
 379                 /*
 380                  * Relocation should be applied before the test instruction to the move instruction.
 381                  * reloc.getOffset() points to the test instruction after the instruction that loads
 382                  * the address of polling page. So set the offset appropriately.
 383                  */
 384                 offset = offset + addend;
 385                 pcrel = 1;
 386                 length = 2;
 387                 break;
 388             }
 389             case EXTERNAL_GOT_TO_PLT: {
 390                 // this is load time relocations
 391                 pcrel = 0;
 392                 length = 3;
 393                 break;
 394             }
 395             default:
 396                 throw new InternalError("Unhandled relocation type: " + relocType);
 397         }
 398         machORelocTable.createRelocationEntry(sectindex, offset, symno,
 399                         pcrel, length, isextern,
 400                         machORelocType);
 401     }
 402 
 403     private static int getMachORelocationType(RelocType relocType) {
 404         int machORelocType = 0;
 405         switch (MachOTargetInfo.getMachOArch()) {
 406             case mach_header_64.CPU_TYPE_X86_64:
 407                 // Return X86_64_RELOC_* entries based on relocType
 408                 if (relocType == RelocType.JAVA_CALL_DIRECT ||
 409                                 relocType == RelocType.FOREIGN_CALL_INDIRECT_GOT) {
 410                     machORelocType = reloc_info.X86_64_RELOC_BRANCH;
 411                 } else if (relocType == RelocType.STUB_CALL_DIRECT) {
 412                     machORelocType = reloc_info.X86_64_RELOC_BRANCH;
 413                 } else if (relocType == RelocType.JAVA_CALL_INDIRECT) {
 414                     machORelocType = reloc_info.X86_64_RELOC_NONE;
 415                 } else if (relocType == RelocType.METASPACE_GOT_REFERENCE ||
 416                                 relocType == RelocType.EXTERNAL_PLT_TO_GOT) {
 417                     machORelocType = reloc_info.X86_64_RELOC_BRANCH;
 418                 } else if (relocType == RelocType.EXTERNAL_GOT_TO_PLT) {
 419                     machORelocType = reloc_info.X86_64_RELOC_UNSIGNED;
 420                 } else {
 421                     assert false : "Unhandled relocation type: " + relocType;
 422                 }
 423                 break;
 424             default:
 425                 System.out.println("Relocation Type mapping: Unhandled architecture");
 426         }
 427         return machORelocType;
 428     }
 429 }