/* * (c) copyright 1987 by the Vrije Universiteit, Amsterdam, The Netherlands. * See the copyright notice in the ACK home directory, in the file "Copyright". */ /* * cvmach.c - convert ack.out to Mach-o * * Mostly pinched from aelflod (util/amisc/aelflod.c), which pinched * from the ARM cv (mach/arm/cv/cv.c), which pinched from the m68k2 cv * (mach/m68k2/cv/cv.c). The code to read ack.out format using * libobject is pinched from the Xenix i386 cv (mach/i386/cv/cv.c). */ #include #include #include #include #include #include #include #include #include /* Header and section table of ack.out */ struct outhead outhead; struct outsect outsect[S_MAX]; uint32_t ack_off_char; /* Offset of string table in ack.out */ int bigendian; /* Emit big-endian Mach-o? */ int cpu_type; uint32_t entry; /* Virtual address of entry point */ uint32_t sz_thread_command; char *outputfile = NULL; /* Name of output file, or NULL */ char *program; /* Name of current program: argv[0] */ FILE *output; /* Output stream */ #define writef(a, b, c) fwrite((a), (b), (c), output) /* Segment numbers in ack.out */ enum { TEXT = 0, ROM, DATA, BSS, NUM_SEGMENTS }; /* Constants from Mach headers */ #define MH_MAGIC 0xfeedface #define MH_EXECUTE 2 #define LC_SEGMENT 1 #define LC_SYMTAB 2 #define LC_UNIXTHREAD 5 #define CPU_TYPE_X86 7 #define CPU_SUBTYPE_X86_ALL 3 #define x86_THREAD_STATE32 1 #define x86_THREAD_STATE32_COUNT 16 #define CPU_TYPE_POWERPC 18 #define CPU_SUBTYPE_POWERPC_ALL 0 #define PPC_THREAD_STATE 1 #define PPC_THREAD_STATE_COUNT 40 #define VM_PROT_NONE 0x0 #define VM_PROT_READ 0x1 #define VM_PROT_WRITE 0x2 #define VM_PROT_EXECUTE 0x4 /* sizes of Mach structs */ #define SZ_MACH_HEADER 28 #define SZ_SEGMENT_COMMAND 56 #define SZ_SECTION_HEADER 68 #define SZ_SYMTAB_COMMAND 24 #define SZ_THREAD_COMMAND_BF_STATE 16 #define SZ_NLIST 12 /* the page size for x86 and PowerPC */ #define CV_PGSZ 4096 /* u modulo page size */ #define pg_mod(u) ((u) & (CV_PGSZ - 1)) /* u rounded down to whole pages */ #define pg_trunc(u) ((u) & ~(CV_PGSZ - 1)) /* u rounded up to whole pages */ #define pg_round(u) pg_trunc((u) + (CV_PGSZ - 1)) const char zero_pg[CV_PGSZ] = { 0 }; /* * machseg[0]: __PAGEZERO with address 0, size CV_PGSZ * machseg[1]: __TEXT for ack TEXT, ROM * machseg[2]: __DATA for ack DATA, BSS */ struct { const char *ms_name; uint32_t ms_vmaddr; uint32_t ms_vmsize; uint32_t ms_fileoff; uint32_t ms_filesize; uint32_t ms_prot; uint32_t ms_nsects; } machseg[3] = { "__PAGEZERO", 0, CV_PGSZ, 0, 0, VM_PROT_NONE, 0, "__TEXT", 0, 0, 0, 0, VM_PROT_READ | VM_PROT_EXECUTE, 2, "__DATA", 0, 0, 0, 0, VM_PROT_READ | VM_PROT_WRITE, 2, }; static void usage(void) { fprintf(stderr, "Usage: %s -m \n", program); exit(1); } /* Produce an error message and exit. */ static void fatal(const char* s, ...) { va_list ap; fprintf(stderr, "%s: ",program) ; va_start(ap, s); vfprintf(stderr, s, ap); va_end(ap); fprintf(stderr, "\n"); if (outputfile) unlink(outputfile); exit(1); } void rd_fatal(void) { fatal("read error"); } /* Returns n such that 2**n == a. */ static uint32_t log2u(uint32_t a) { uint32_t n = 0; while (a) { a >>= 1; n++; } return n - 1; } /* Writes a byte. */ static void emit8(uint8_t value) { writef(&value, 1, 1); } /* Writes out a 16-bit value in the appropriate endianness. */ static void emit16(uint16_t value) { unsigned char buffer[2]; if (bigendian) { buffer[0] = (value >> 8) & 0xFF; buffer[1] = (value >> 0) & 0xFF; } else { buffer[1] = (value >> 8) & 0xFF; buffer[0] = (value >> 0) & 0xFF; } writef(buffer, 1, sizeof(buffer)); } /* Writes out a 32-bit value in the appropriate endianness. */ static void emit32(uint32_t value) { unsigned char buffer[4]; if (bigendian) { buffer[0] = (value >> 24) & 0xFF; buffer[1] = (value >> 16) & 0xFF; buffer[2] = (value >> 8) & 0xFF; buffer[3] = (value >> 0) & 0xFF; } else { buffer[3] = (value >> 24) & 0xFF; buffer[2] = (value >> 16) & 0xFF; buffer[1] = (value >> 8) & 0xFF; buffer[0] = (value >> 0) & 0xFF; } writef(buffer, 1, sizeof(buffer)); } /* Copies the contents of a section from the input stream * to the output stream. */ static void emit_section(int section_nr) { struct outsect *section = &outsect[section_nr]; size_t blocksize; uint32_t n = section->os_flen; char buffer[BUFSIZ]; rd_outsect(section_nr); while (n > 0) { blocksize = (n > BUFSIZ) ? BUFSIZ : n; rd_emit(buffer, (long)blocksize); writef(buffer, 1, blocksize); n -= blocksize; } /* Zero fill any remaining space. */ n = section->os_size - section->os_flen; while (n > 0) { blocksize = (n > sizeof(zero_pg)) ? sizeof(zero_pg) : n; writef(zero_pg, 1, blocksize); n -= blocksize; } } static void emit_lc_segment(int i) { uint32_t sz; int flags, maxprot; char namebuf[16]; if (i == 0) { /* special values for __PAGEZERO */ maxprot = VM_PROT_NONE; flags = 4; /* SG_NORELOC */ } else { maxprot = VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE; flags = 0; } /* * The size of this command includes the size of its section * headers, see emit_section_header(). */ sz = SZ_SEGMENT_COMMAND + machseg[i].ms_nsects * SZ_SECTION_HEADER; /* Use strncpy() to pad namebuf with '\0' bytes. */ strncpy(namebuf, machseg[i].ms_name, sizeof(namebuf)); emit32(LC_SEGMENT); /* command */ emit32(sz); /* size of command */ writef(namebuf, 1, sizeof(namebuf)); emit32(machseg[i].ms_vmaddr); /* vm address */ emit32(machseg[i].ms_vmsize); /* vm size */ emit32(machseg[i].ms_fileoff); /* file offset */ emit32(machseg[i].ms_filesize); /* file size */ emit32(maxprot); /* max protection */ emit32(machseg[i].ms_prot); /* initial protection */ emit32(machseg[i].ms_nsects); /* number of Mach sections */ emit32(flags); /* flags */ } static void emit_section_header(int ms, const char *name, int os) { uint32_t fileoff, flags; char namebuf[16]; switch (os) { case TEXT: /* S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SOME_INSTRUCTIONS */ flags = 0x80000400; break; case BSS: flags = 0x1; /* S_ZEROFILL */ break; default: flags = 0x0; /* S_REGULAR */ break; } if (os == BSS) fileoff = 0; else fileoff = machseg[ms].ms_fileoff + (outsect[os].os_base - machseg[ms].ms_vmaddr); /* name of Mach section */ strncpy(namebuf, name, sizeof(namebuf)); writef(namebuf, 1, sizeof(namebuf)); /* name of Mach segment */ strncpy(namebuf, machseg[ms].ms_name, sizeof(namebuf)); writef(namebuf, 1, sizeof(namebuf)); emit32(outsect[os].os_base); /* vm address */ emit32(outsect[os].os_size); /* vm size */ emit32(fileoff); /* file offset */ emit32(log2u(outsect[os].os_lign)); /* alignment */ emit32(0); /* offset of relocations */ emit32(0); /* number of relocations */ emit32(flags); /* flags */ emit32(0); /* reserved */ emit32(0); /* reserved */ } static void emit_lc_symtab(void) { uint32_t off1, off2; /* Symbol table will be at next page after machseg[2]. */ off1 = pg_round(machseg[2].ms_fileoff + machseg[2].ms_filesize); /* String table will be after symbol table. */ off2 = off1 + 12 * outhead.oh_nname; emit32(LC_SYMTAB); /* command */ emit32(SZ_SYMTAB_COMMAND); /* size of command */ emit32(off1); /* offset of symbol table */ emit32(outhead.oh_nname); /* number of symbols */ emit32(off2); /* offset of string table */ emit32(1 + outhead.oh_nchar); /* size of string table */ } static void emit_lc_unixthread(void) { int i, ireg, ts, ts_count; /* * The thread state has ts_count registers. The ireg'th * register holds the entry point. We can set other registers * to zero. At execution time, the kernel will allocate a * stack and set the stack pointer. */ switch (cpu_type) { case CPU_TYPE_X86: ireg = 10; /* eip */ ts = x86_THREAD_STATE32; ts_count = x86_THREAD_STATE32_COUNT; break; case CPU_TYPE_POWERPC: ireg = 0; /* srr0 */ ts = PPC_THREAD_STATE; ts_count = PPC_THREAD_STATE_COUNT; break; } emit32(LC_UNIXTHREAD); /* command */ emit32(sz_thread_command); /* size of command */ emit32(ts); /* thread state */ emit32(ts_count); /* thread state count */ for (i = 0; i < ts_count; i++) { if (i == ireg) emit32(entry); else emit32(0); } } static void emit_symbol(struct outname *np) { uint32_t soff; uint8_t type; uint8_t sect; uint16_t desc; if (np->on_type & S_STB) { /* stab for debugger */ type = np->on_type >> 8; desc = np->on_desc; } else { desc = 0; switch (np->on_type & S_TYP) { case S_UND: type = 0x0; /* N_UNDF */ break; case S_ABS: type = 0x2; /* N_ABS */ break; default: type = 0xe; /* N_SECT */ break; } if (np->on_type & S_EXT) type |= 0x1; /* N_EXT */ } switch (np->on_type & S_TYP) { case S_MIN + TEXT: sect = 1; break; case S_MIN + ROM: sect = 2; break; case S_MIN + DATA: sect = 3; break; case S_MIN + BSS: case S_MIN + NUM_SEGMENTS: sect = 4; break; default: sect = 0; /* NO_SECT */ break; } /* * To find the symbol's name, ack.out uses an offset from the * beginning of the file, but Mach-o uses an offset into the * string table. Both formats use offset 0 for a symbol with * no name. We will prepend a '\0' at offset 0, so every * named symbol needs + 1. */ if (np->on_foff) soff = np->on_foff - ack_off_char + 1; else soff = 0; emit32(soff); emit8(type); emit8(sect); emit16(desc); emit32(np->on_valu); } static void emit_symtab(void) { struct outname *names, *np; int i; char *chars; /* Using calloc(a, b) to check if a * b would overflow. */ names = calloc(outhead.oh_nname, sizeof(struct outname)); if (!names) fatal("out of memory"); chars = malloc(outhead.oh_nchar); if (!names || !chars) fatal("out of memory"); rd_name(names, outhead.oh_nname); rd_string(chars, outhead.oh_nchar); ack_off_char = OFF_CHAR(outhead); /* Emit each symbol entry. */ for (i = 0, np = names; i < outhead.oh_nname; i++, np++) emit_symbol(np); /* * Emit the string table. The first character of a Mach-o * string table must be '\0', so we prepend a '\0'. */ emit8(0); writef(chars, 1, outhead.oh_nchar); } int main(int argc, char *argv[]) { uint32_t end, pad[3], sz, sz_load_cmds; int cpu_subtype, fd, mflag = 0; /* General housecleaning and setup. */ output = stdout; program = argv[0]; /* Read in and process any flags. */ while ((argc > 1) && (argv[1][0] == '-')) { switch (argv[1][1]) { case 'm': /* machine cpu type */ mflag = 1; cpu_type = atoi(&argv[1][2]); break; case 'h': /* help */ default: usage(); } argv++; argc--; } if (!mflag) usage(); /* Check cpu type. */ switch (cpu_type) { case CPU_TYPE_X86: bigendian = 0; cpu_subtype = CPU_SUBTYPE_X86_ALL; sz_thread_command = 4 * x86_THREAD_STATE32_COUNT; break; case CPU_TYPE_POWERPC: bigendian = 1; cpu_subtype = CPU_SUBTYPE_POWERPC_ALL; sz_thread_command = 4 * PPC_THREAD_STATE_COUNT; break; default: /* Can't emit LC_UNIXTHREAD for unknown cpu. */ fatal("unknown cpu type -m%d", cpu_type); } sz_thread_command += SZ_THREAD_COMMAND_BF_STATE; /* Process the rest of the arguments. */ switch (argc) { case 1: /* No parameters --- read from stdin, write to stdout. */ rd_fdopen(0); break; case 3: /* Both input and output files specified. */ /* Use mode 0777 to allow executing the output file. */ fd = open(argv[2], O_CREAT | O_TRUNC | O_WRONLY, 0777); if (fd < 0) fatal("unable to open output file."); output = fdopen(fd, "w"); if (!output) fatal("unable to open output file."); outputfile = argv[2]; /* FALLTHROUGH */ case 2: /* Input file specified. */ if (! rd_open(argv[1])) fatal("unable to open input file."); break; default: usage(); } rd_ohead(&outhead); if (BADMAGIC(outhead)) fatal("Not an ack object file."); if (outhead.oh_flags & HF_LINK) fatal("Contains unresolved references."); if (outhead.oh_nrelo > 0) fprintf(stderr, "Warning: relocation information present."); if (outhead.oh_nsect != NUM_SEGMENTS && outhead.oh_nsect != NUM_SEGMENTS + 1 ) { fatal("Input file must have %d sections, not %ld\n", NUM_SEGMENTS, (long)outhead.oh_nsect); } rd_sect(outsect, outhead.oh_nsect); /* * machseg[1] will start at a page boundary and include the * Mach header and load commands before ack TEXT and ROM. * * Find our entry point (immediately after the load commands) * and check that TEXT begins there. */ machseg[1].ms_vmaddr = pg_trunc(outsect[TEXT].os_base); sz_load_cmds = 3 * SZ_SEGMENT_COMMAND + 4 * SZ_SECTION_HEADER + SZ_SYMTAB_COMMAND + sz_thread_command; entry = machseg[1].ms_vmaddr + SZ_MACH_HEADER + sz_load_cmds; if (entry != outsect[TEXT].os_base) { fatal("text segment must have base 0x%lx, not 0x%lx" "\n\t(suggest em_led -b0:0x%lx)", (unsigned long)entry, (unsigned long)outsect[TEXT].os_base, (unsigned long)entry); } /* Pad for alignment between TEXT and ROM. */ sz = outsect[ROM].os_base - outsect[TEXT].os_base; pad[0] = sz - outsect[TEXT].os_size; if (sz < outsect[TEXT].os_size || pad[0] >= outsect[ROM].os_lign) fatal("the rom segment must follow the text segment."); /* * Pad between ROM and DATA such that we can map machseg[2] at * a page boundary with DATA at its correct base address. * * For example, if ROM ends at 0x2bed and DATA begins at * 0x3000, then we pad to the page boundary. If ROM ends at * 0x2bed and DATA begins at 0x3bf0, then pad = 3 and we map * the page twice, at both 0x2000 and 0x3000. */ end = outsect[ROM].os_base + outsect[ROM].os_size; pad[1] = pg_mod(outsect[DATA].os_base - end); sz = end - machseg[1].ms_vmaddr; machseg[1].ms_vmsize = machseg[1].ms_filesize = sz; machseg[2].ms_vmaddr = pg_trunc(outsect[DATA].os_base); machseg[2].ms_fileoff = pg_trunc(sz + pad[1]); if (machseg[2].ms_vmaddr < end && machseg[2].ms_vmaddr >= machseg[1].ms_vmaddr) fatal("the data and rom segments are too close." "\n\t(suggest em_led -a2:%d)", (int)CV_PGSZ); if (outsect[BSS].os_flen != 0) fatal("the bss space contains initialized data."); sz = outsect[BSS].os_base - outsect[DATA].os_base; if (sz < outsect[DATA].os_size || sz - outsect[DATA].os_size >= outsect[BSS].os_lign) fatal("the bss segment must follow the data segment."); end = outsect[DATA].os_base + outsect[DATA].os_size; machseg[2].ms_filesize = end - machseg[2].ms_vmaddr; end = outsect[BSS].os_base + outsect[BSS].os_size; machseg[2].ms_vmsize = end - machseg[2].ms_vmaddr; if (outhead.oh_nsect == NUM_SEGMENTS + 1) { if (outsect[NUM_SEGMENTS].os_base != outsect[BSS].os_base + outsect[BSS].os_size) fatal("end segment must follow bss"); if (outsect[NUM_SEGMENTS].os_size != 0) fatal("end segment must be empty"); } /* * Pad to page boundary between BSS and symbol table. * * Also, some versions of Mac OS X refuse to load any * executable smaller than 4096 bytes (1 page). */ pad[2] = pg_mod(-(uint32_t)machseg[2].ms_filesize); /* Emit the Mach header. */ emit32(MH_MAGIC); /* magic */ emit32(cpu_type); /* cpu type */ emit32(cpu_subtype); /* cpu subtype */ emit32(MH_EXECUTE); /* file type */ emit32(5); /* number of load commands */ emit32(sz_load_cmds); /* size of load commands */ emit32(0); /* flags */ emit_lc_segment(0); emit_lc_segment(1); emit_section_header(1, "__text", TEXT); emit_section_header(1, "__rom", ROM); emit_lc_segment(2); emit_section_header(2, "__data", DATA); emit_section_header(2, "__bss", BSS); emit_lc_symtab(); emit_lc_unixthread(); /* Emit non-empty sections. */ emit_section(TEXT); writef(zero_pg, 1, pad[0]); emit_section(ROM); writef(zero_pg, 1, pad[1]); emit_section(DATA); writef(zero_pg, 1, pad[2]); emit_symtab(); if (ferror(output)) fatal("write error"); return 0; }