+
+/* --------------------------------------------------------------------------
+ * Mach-O specifics
+ * ------------------------------------------------------------------------*/
+
+#if defined(OBJFORMAT_MACHO)
+
+/*
+ Support for MachO linking on Darwin/MacOS X on PowerPC chips
+ by Wolfgang Thaller (wolfgang.thaller@gmx.net)
+
+ I hereby formally apologize for the hackish nature of this code.
+ Things that need to be done:
+ *) implement ocVerifyImage_MachO
+ *) add still more sanity checks.
+*/
+
+
+/*
+ ocAllocateJumpIslands_MachO
+
+ Allocate additional space at the end of the object file image to make room
+ for jump islands.
+
+ PowerPC relative branch instructions have a 24 bit displacement field.
+ As PPC code is always 4-byte-aligned, this yields a +-32MB range.
+ If a particular imported symbol is outside this range, we have to redirect
+ the jump to a short piece of new code that just loads the 32bit absolute
+ address and jumps there.
+ This function just allocates space for one 16 byte jump island for every
+ undefined symbol in the object file. The code for the islands is filled in by
+ makeJumpIsland below.
+*/
+
+static const int islandSize = 16;
+
+static int ocAllocateJumpIslands_MachO(ObjectCode* oc)
+{
+ char *image = (char*) oc->image;
+ struct mach_header *header = (struct mach_header*) image;
+ struct load_command *lc = (struct load_command*) (image + sizeof(struct mach_header));
+ unsigned i;
+
+ for(i=0;i<header->ncmds;i++)
+ {
+ if(lc->cmd == LC_DYSYMTAB)
+ {
+ struct dysymtab_command *dsymLC = (struct dysymtab_command*) lc;
+ unsigned long nundefsym = dsymLC->nundefsym;
+ oc->island_start_symbol = dsymLC->iundefsym;
+ oc->n_islands = nundefsym;
+
+ if(nundefsym > 0)
+ {
+#ifdef USE_MMAP
+ #error ocAllocateJumpIslands_MachO doesnt want USE_MMAP to be defined
+#else
+ oc->image = stgReallocBytes(
+ image, oc->fileSize + islandSize * nundefsym,
+ "ocAllocateJumpIslands_MachO");
+#endif
+ oc->jump_islands = oc->image + oc->fileSize;
+ memset(oc->jump_islands, 0, islandSize * nundefsym);
+ }
+
+ break; // there can be only one LC_DSYMTAB
+ }
+ lc = (struct load_command *) ( ((char*)lc) + lc->cmdsize );
+ }
+ return 1;
+}
+
+static int ocVerifyImage_MachO(ObjectCode* oc)
+{
+ // FIXME: do some verifying here
+ return 1;
+}
+
+static int resolveImports(
+ ObjectCode* oc,
+ char *image,
+ struct symtab_command *symLC,
+ struct section *sect, // ptr to lazy or non-lazy symbol pointer section
+ unsigned long *indirectSyms,
+ struct nlist *nlist)
+{
+ unsigned i;
+
+ for(i=0;i*4<sect->size;i++)
+ {
+ // according to otool, reserved1 contains the first index into the indirect symbol table
+ struct nlist *symbol = &nlist[indirectSyms[sect->reserved1+i]];
+ char *nm = image + symLC->stroff + symbol->n_un.n_strx;
+ void *addr = NULL;
+
+ if((symbol->n_type & N_TYPE) == N_UNDF
+ && (symbol->n_type & N_EXT) && (symbol->n_value != 0))
+ addr = (void*) (symbol->n_value);
+ else if((addr = lookupLocalSymbol(oc,nm)) != NULL)
+ ;
+ else
+ addr = lookupSymbol(nm);
+ if(!addr)
+ {
+ belch("\n%s: unknown symbol `%s'", oc->fileName, nm);
+ return 0;
+ }
+ ASSERT(addr);
+ checkProddableBlock(oc,((void**)(image + sect->offset)) + i);
+ ((void**)(image + sect->offset))[i] = addr;
+ }
+
+ return 1;
+}
+
+static void* makeJumpIsland(
+ ObjectCode* oc,
+ unsigned long symbolNumber,
+ void* target)
+{
+ if(symbolNumber < oc->island_start_symbol ||
+ symbolNumber - oc->island_start_symbol > oc->n_islands)
+ return NULL;
+ symbolNumber -= oc->island_start_symbol;
+
+ void *island = (void*) ((char*)oc->jump_islands + islandSize * symbolNumber);
+ unsigned long *p = (unsigned long*) island;
+
+ // lis r12, hi16(target)
+ *p++ = 0x3d800000 | ( ((unsigned long) target) >> 16 );
+ // ori r12, r12, lo16(target)
+ *p++ = 0x618c0000 | ( ((unsigned long) target) & 0xFFFF );
+ // mtctr r12
+ *p++ = 0x7d8903a6;
+ // bctr
+ *p++ = 0x4e800420;
+
+ return (void*) island;
+}
+
+static char* relocateAddress(
+ ObjectCode* oc,
+ int nSections,
+ struct section* sections,
+ unsigned long address)
+{
+ int i;
+ for(i = 0; i < nSections; i++)
+ {
+ if(sections[i].addr <= address
+ && address < sections[i].addr + sections[i].size)
+ {
+ return oc->image + sections[i].offset + address - sections[i].addr;
+ }
+ }
+ barf("Invalid Mach-O file:"
+ "Address out of bounds while relocating object file");
+ return NULL;
+}
+
+static int relocateSection(
+ ObjectCode* oc,
+ char *image,
+ struct symtab_command *symLC, struct nlist *nlist,
+ int nSections, struct section* sections, struct section *sect)
+{
+ struct relocation_info *relocs;
+ int i,n;
+
+ if(!strcmp(sect->sectname,"__la_symbol_ptr"))
+ return 1;
+ else if(!strcmp(sect->sectname,"__nl_symbol_ptr"))
+ return 1;
+
+ n = sect->nreloc;
+ relocs = (struct relocation_info*) (image + sect->reloff);
+
+ for(i=0;i<n;i++)
+ {
+ if(relocs[i].r_address & R_SCATTERED)
+ {
+ struct scattered_relocation_info *scat =
+ (struct scattered_relocation_info*) &relocs[i];
+
+ if(!scat->r_pcrel)
+ {
+ if(scat->r_length == 2)
+ {
+ unsigned long word = 0;
+ unsigned long* wordPtr = (unsigned long*) (image + sect->offset + scat->r_address);
+ checkProddableBlock(oc,wordPtr);
+
+ // Step 1: Figure out what the relocated value should be
+ if(scat->r_type == GENERIC_RELOC_VANILLA)
+ {
+ word = scat->r_value + sect->offset + ((long) image);
+ }
+ else if(scat->r_type == PPC_RELOC_SECTDIFF
+ || scat->r_type == PPC_RELOC_LO16_SECTDIFF
+ || scat->r_type == PPC_RELOC_HI16_SECTDIFF
+ || scat->r_type == PPC_RELOC_HA16_SECTDIFF)
+ {
+ struct scattered_relocation_info *pair =
+ (struct scattered_relocation_info*) &relocs[i+1];
+
+ if(!pair->r_scattered || pair->r_type != PPC_RELOC_PAIR)
+ barf("Invalid Mach-O file: "
+ "PPC_RELOC_*_SECTDIFF not followed by PPC_RELOC_PAIR");
+
+ word = (unsigned long)
+ (relocateAddress(oc, nSections, sections, scat->r_value)
+ - relocateAddress(oc, nSections, sections, pair->r_value));
+ i++;
+ }
+ else if(scat->r_type == PPC_RELOC_HI16
+ || scat->r_type == PPC_RELOC_LO16
+ || scat->r_type == PPC_RELOC_HA16
+ || scat->r_type == PPC_RELOC_LO14)
+ { // these are generated by label+offset things
+ struct relocation_info *pair = &relocs[i+1];
+ if((pair->r_address & R_SCATTERED) || pair->r_type != PPC_RELOC_PAIR)
+ barf("Invalid Mach-O file: "
+ "PPC_RELOC_* not followed by PPC_RELOC_PAIR");
+
+ if(scat->r_type == PPC_RELOC_LO16)
+ {
+ word = ((unsigned short*) wordPtr)[1];
+ word |= ((unsigned long) relocs[i+1].r_address & 0xFFFF) << 16;
+ }
+ else if(scat->r_type == PPC_RELOC_LO14)
+ {
+ barf("Unsupported Relocation: PPC_RELOC_LO14");
+ word = ((unsigned short*) wordPtr)[1] & 0xFFFC;
+ word |= ((unsigned long) relocs[i+1].r_address & 0xFFFF) << 16;
+ }
+ else if(scat->r_type == PPC_RELOC_HI16)
+ {
+ word = ((unsigned short*) wordPtr)[1] << 16;
+ word |= ((unsigned long) relocs[i+1].r_address & 0xFFFF);
+ }
+ else if(scat->r_type == PPC_RELOC_HA16)
+ {
+ word = ((unsigned short*) wordPtr)[1] << 16;
+ word += ((short)relocs[i+1].r_address & (short)0xFFFF);
+ }
+
+
+ word += (unsigned long) relocateAddress(oc, nSections, sections, scat->r_value)
+ - scat->r_value;
+
+ i++;
+ }
+ else
+ continue; // ignore the others
+
+ if(scat->r_type == GENERIC_RELOC_VANILLA
+ || scat->r_type == PPC_RELOC_SECTDIFF)
+ {
+ *wordPtr = word;
+ }
+ else if(scat->r_type == PPC_RELOC_LO16_SECTDIFF || scat->r_type == PPC_RELOC_LO16)
+ {
+ ((unsigned short*) wordPtr)[1] = word & 0xFFFF;
+ }
+ else if(scat->r_type == PPC_RELOC_HI16_SECTDIFF || scat->r_type == PPC_RELOC_HI16)
+ {
+ ((unsigned short*) wordPtr)[1] = (word >> 16) & 0xFFFF;
+ }
+ else if(scat->r_type == PPC_RELOC_HA16_SECTDIFF || scat->r_type == PPC_RELOC_HA16)
+ {
+ ((unsigned short*) wordPtr)[1] = ((word >> 16) & 0xFFFF)
+ + ((word & (1<<15)) ? 1 : 0);
+ }
+ }
+ }
+
+ continue; // FIXME: I hope it's OK to ignore all the others.
+ }
+ else
+ {
+ struct relocation_info *reloc = &relocs[i];
+ if(reloc->r_pcrel && !reloc->r_extern)
+ continue;
+
+ if(reloc->r_length == 2)
+ {
+ unsigned long word = 0;
+ unsigned long jumpIsland = 0;
+ long offsetToJumpIsland;
+
+ unsigned long* wordPtr = (unsigned long*) (image + sect->offset + reloc->r_address);
+ checkProddableBlock(oc,wordPtr);
+
+ if(reloc->r_type == GENERIC_RELOC_VANILLA)
+ {
+ word = *wordPtr;
+ }
+ else if(reloc->r_type == PPC_RELOC_LO16)
+ {
+ word = ((unsigned short*) wordPtr)[1];
+ word |= ((unsigned long) relocs[i+1].r_address & 0xFFFF) << 16;
+ }
+ else if(reloc->r_type == PPC_RELOC_HI16)
+ {
+ word = ((unsigned short*) wordPtr)[1] << 16;
+ word |= ((unsigned long) relocs[i+1].r_address & 0xFFFF);
+ }
+ else if(reloc->r_type == PPC_RELOC_HA16)
+ {
+ word = ((unsigned short*) wordPtr)[1] << 16;
+ word += ((short)relocs[i+1].r_address & (short)0xFFFF);
+ }
+ else if(reloc->r_type == PPC_RELOC_BR24)
+ {
+ word = *wordPtr;
+ word = (word & 0x03FFFFFC) | (word & 0x02000000) ? 0xFC000000 : 0;
+ }
+
+
+ if(!reloc->r_extern)
+ {
+ long delta =
+ sections[reloc->r_symbolnum-1].offset
+ - sections[reloc->r_symbolnum-1].addr
+ + ((long) image);
+
+ word += delta;
+ }
+ else
+ {
+ struct nlist *symbol = &nlist[reloc->r_symbolnum];
+ char *nm = image + symLC->stroff + symbol->n_un.n_strx;
+ unsigned long symbolAddress = (unsigned long) (lookupSymbol(nm));
+ if(!symbolAddress)
+ {
+ belch("\nunknown symbol `%s'", nm);
+ return 0;
+ }
+
+ if(reloc->r_pcrel)
+ {
+ ASSERT(word == 0);
+ word = symbolAddress;
+ jumpIsland = (long) makeJumpIsland(oc,reloc->r_symbolnum,(void*)word);
+ word -= ((long)image) + sect->offset + reloc->r_address;
+ if(jumpIsland != 0)
+ {
+ offsetToJumpIsland = jumpIsland
+ - (((long)image) + sect->offset + reloc->r_address);
+ }
+ }
+ else
+ {
+ word += symbolAddress;
+ }
+ }
+
+ if(reloc->r_type == GENERIC_RELOC_VANILLA)
+ {
+ *wordPtr = word;
+ continue;
+ }
+ else if(reloc->r_type == PPC_RELOC_LO16)
+ {
+ ((unsigned short*) wordPtr)[1] = word & 0xFFFF;
+ i++; continue;
+ }
+ else if(reloc->r_type == PPC_RELOC_HI16)
+ {
+ ((unsigned short*) wordPtr)[1] = (word >> 16) & 0xFFFF;
+ i++; continue;
+ }
+ else if(reloc->r_type == PPC_RELOC_HA16)
+ {
+ ((unsigned short*) wordPtr)[1] = ((word >> 16) & 0xFFFF)
+ + ((word & (1<<15)) ? 1 : 0);
+ i++; continue;
+ }
+ else if(reloc->r_type == PPC_RELOC_BR24)
+ {
+ if((long)word > (long)0x01FFFFFF || (long)word < (long)0xFFE00000)
+ {
+ // The branch offset is too large.
+ // Therefore, we try to use a jump island.
+ if(jumpIsland == 0)
+ barf("unconditional relative branch out of range: "
+ "no jump island available");
+
+ word = offsetToJumpIsland;
+ if((long)word > (long)0x01FFFFFF || (long)word < (long)0xFFE00000)
+ barf("unconditional relative branch out of range: "
+ "jump island out of range");
+ }
+ *wordPtr = (*wordPtr & 0xFC000003) | (word & 0x03FFFFFC);
+ continue;
+ }
+ }
+ barf("\nunknown relocation %d",reloc->r_type);
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static int ocGetNames_MachO(ObjectCode* oc)
+{
+ char *image = (char*) oc->image;
+ struct mach_header *header = (struct mach_header*) image;
+ struct load_command *lc = (struct load_command*) (image + sizeof(struct mach_header));
+ unsigned i,curSymbol;
+ struct segment_command *segLC = NULL;
+ struct section *sections;
+ struct symtab_command *symLC = NULL;
+ struct dysymtab_command *dsymLC = NULL;
+ struct nlist *nlist;
+ unsigned long commonSize = 0;
+ char *commonStorage = NULL;
+ unsigned long commonCounter;
+
+ for(i=0;i<header->ncmds;i++)
+ {
+ if(lc->cmd == LC_SEGMENT)
+ segLC = (struct segment_command*) lc;
+ else if(lc->cmd == LC_SYMTAB)
+ symLC = (struct symtab_command*) lc;
+ else if(lc->cmd == LC_DYSYMTAB)
+ dsymLC = (struct dysymtab_command*) lc;
+ lc = (struct load_command *) ( ((char*)lc) + lc->cmdsize );
+ }
+
+ sections = (struct section*) (segLC+1);
+ nlist = (struct nlist*) (image + symLC->symoff);
+
+ for(i=0;i<segLC->nsects;i++)
+ {
+ if(sections[i].size == 0)
+ continue;
+
+ if((sections[i].flags & SECTION_TYPE) == S_ZEROFILL)
+ {
+ char * zeroFillArea = stgCallocBytes(1,sections[i].size,
+ "ocGetNames_MachO(common symbols)");
+ sections[i].offset = zeroFillArea - image;
+ }
+
+ if(!strcmp(sections[i].sectname,"__text"))
+ addSection(oc, SECTIONKIND_CODE_OR_RODATA,
+ (void*) (image + sections[i].offset),
+ (void*) (image + sections[i].offset + sections[i].size));
+ else if(!strcmp(sections[i].sectname,"__const"))
+ addSection(oc, SECTIONKIND_RWDATA,
+ (void*) (image + sections[i].offset),
+ (void*) (image + sections[i].offset + sections[i].size));
+ else if(!strcmp(sections[i].sectname,"__data"))
+ addSection(oc, SECTIONKIND_RWDATA,
+ (void*) (image + sections[i].offset),
+ (void*) (image + sections[i].offset + sections[i].size));
+ else if(!strcmp(sections[i].sectname,"__bss")
+ || !strcmp(sections[i].sectname,"__common"))
+ addSection(oc, SECTIONKIND_RWDATA,
+ (void*) (image + sections[i].offset),
+ (void*) (image + sections[i].offset + sections[i].size));
+
+ addProddableBlock(oc, (void*) (image + sections[i].offset),
+ sections[i].size);
+ }
+
+ // count external symbols defined here
+ oc->n_symbols = 0;
+ for(i=dsymLC->iextdefsym;i<dsymLC->iextdefsym+dsymLC->nextdefsym;i++)
+ {
+ if((nlist[i].n_type & N_TYPE) == N_SECT)
+ oc->n_symbols++;
+ }
+ for(i=0;i<symLC->nsyms;i++)
+ {
+ if((nlist[i].n_type & N_TYPE) == N_UNDF
+ && (nlist[i].n_type & N_EXT) && (nlist[i].n_value != 0))
+ {
+ commonSize += nlist[i].n_value;
+ oc->n_symbols++;
+ }
+ }
+ oc->symbols = stgMallocBytes(oc->n_symbols * sizeof(char*),
+ "ocGetNames_MachO(oc->symbols)");
+
+ // insert symbols into hash table
+ for(i=dsymLC->iextdefsym,curSymbol=0;i<dsymLC->iextdefsym+dsymLC->nextdefsym;i++)
+ {
+ if((nlist[i].n_type & N_TYPE) == N_SECT)
+ {
+ char *nm = image + symLC->stroff + nlist[i].n_un.n_strx;
+ ghciInsertStrHashTable(oc->fileName, symhash, nm, image +
+ sections[nlist[i].n_sect-1].offset
+ - sections[nlist[i].n_sect-1].addr
+ + nlist[i].n_value);
+ oc->symbols[curSymbol++] = nm;
+ }
+ }
+
+ // insert local symbols into lochash
+ for(i=dsymLC->ilocalsym;i<dsymLC->ilocalsym+dsymLC->nlocalsym;i++)
+ {
+ if((nlist[i].n_type & N_TYPE) == N_SECT)
+ {
+ char *nm = image + symLC->stroff + nlist[i].n_un.n_strx;
+ ghciInsertStrHashTable(oc->fileName, oc->lochash, nm, image +
+ sections[nlist[i].n_sect-1].offset
+ - sections[nlist[i].n_sect-1].addr
+ + nlist[i].n_value);
+ }
+ }
+
+
+ commonStorage = stgCallocBytes(1,commonSize,"ocGetNames_MachO(common symbols)");
+ commonCounter = (unsigned long)commonStorage;
+ for(i=0;i<symLC->nsyms;i++)
+ {
+ if((nlist[i].n_type & N_TYPE) == N_UNDF
+ && (nlist[i].n_type & N_EXT) && (nlist[i].n_value != 0))
+ {
+ char *nm = image + symLC->stroff + nlist[i].n_un.n_strx;
+ unsigned long sz = nlist[i].n_value;
+
+ nlist[i].n_value = commonCounter;
+
+ ghciInsertStrHashTable(oc->fileName, symhash, nm, (void*)commonCounter);
+ oc->symbols[curSymbol++] = nm;
+
+ commonCounter += sz;
+ }
+ }
+ return 1;
+}
+
+static int ocResolve_MachO(ObjectCode* oc)
+{
+ char *image = (char*) oc->image;
+ struct mach_header *header = (struct mach_header*) image;
+ struct load_command *lc = (struct load_command*) (image + sizeof(struct mach_header));
+ unsigned i;
+ struct segment_command *segLC = NULL;
+ struct section *sections, *la_ptrs = NULL, *nl_ptrs = NULL;
+ struct symtab_command *symLC = NULL;
+ struct dysymtab_command *dsymLC = NULL;
+ struct nlist *nlist;
+ unsigned long *indirectSyms;
+
+ for(i=0;i<header->ncmds;i++)
+ {
+ if(lc->cmd == LC_SEGMENT)
+ segLC = (struct segment_command*) lc;
+ else if(lc->cmd == LC_SYMTAB)
+ symLC = (struct symtab_command*) lc;
+ else if(lc->cmd == LC_DYSYMTAB)
+ dsymLC = (struct dysymtab_command*) lc;
+ lc = (struct load_command *) ( ((char*)lc) + lc->cmdsize );
+ }
+
+ sections = (struct section*) (segLC+1);
+ nlist = (struct nlist*) (image + symLC->symoff);
+
+ for(i=0;i<segLC->nsects;i++)
+ {
+ if(!strcmp(sections[i].sectname,"__la_symbol_ptr"))
+ la_ptrs = §ions[i];
+ else if(!strcmp(sections[i].sectname,"__nl_symbol_ptr"))
+ nl_ptrs = §ions[i];
+ }
+
+ indirectSyms = (unsigned long*) (image + dsymLC->indirectsymoff);
+
+ if(la_ptrs)
+ if(!resolveImports(oc,image,symLC,la_ptrs,indirectSyms,nlist))
+ return 0;
+ if(nl_ptrs)
+ if(!resolveImports(oc,image,symLC,nl_ptrs,indirectSyms,nlist))
+ return 0;
+
+ for(i=0;i<segLC->nsects;i++)
+ {
+ if(!relocateSection(oc,image,symLC,nlist,segLC->nsects,sections,§ions[i]))
+ return 0;
+ }
+
+ /* Free the local symbol table; we won't need it again. */
+ freeHashTable(oc->lochash, NULL);
+ oc->lochash = NULL;
+
+ /*
+ Flush the data & instruction caches.
+ Because the PPC has split data/instruction caches, we have to
+ do that whenever we modify code at runtime.
+ */
+ {
+ int n = (oc->fileSize + islandSize * oc->n_islands) / 4;
+ unsigned long *p = (unsigned long*)oc->image;
+ while(n--)
+ {
+ __asm__ volatile ("dcbf 0,%0\n\tsync\n\ticbi 0,%0"
+ : : "r" (p));
+ p++;
+ }
+ __asm__ volatile ("sync\n\tisync");
+ }
+ return 1;
+}
+
+/*
+ * The Mach-O object format uses leading underscores. But not everywhere.
+ * There is a small number of runtime support functions defined in
+ * libcc_dynamic.a whose name does not have a leading underscore.
+ * As a consequence, we can't get their address from C code.
+ * We have to use inline assembler just to take the address of a function.
+ * Yuck.
+ */
+
+static void machoInitSymbolsWithoutUnderscore()
+{
+ void *p;
+
+#undef Sym
+#define Sym(x) \
+ __asm__ ("lis %0,hi16(" #x ")\n\tori %0,%0,lo16(" #x ")" : "=r" (p)); \
+ ghciInsertStrHashTable("(GHCi built-in symbols)", symhash, #x, p);
+
+ RTS_MACHO_NOUNDERLINE_SYMBOLS
+
+}
+#endif