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