/* Copy a LC_SEGMENT load command other than the __DATA segment from the input file to the output file, adjusting the file offset of the segment and the file offsets of sections contained in it. */ static void copy_segment (struct load_command *lc) { struct segment_command *scp = (struct segment_command *) lc; unsigned long old_fileoff = scp->fileoff; struct section *sectp; int j; scp->fileoff = curr_file_offset; sectp = (struct section *) (scp + 1); for (j = 0; j < scp->nsects; j++) { sectp->offset += curr_file_offset - old_fileoff; sectp++; } printf ("Writing segment %-16.16s @ %#8lx (%#8lx/%#8lx @ %#10lx)\n", scp->segname, (long) (scp->fileoff), (long) (scp->filesize), (long) (scp->vmsize), (long) (scp->vmaddr)); if (!unexec_copy (scp->fileoff, old_fileoff, scp->filesize)) unexec_error ("cannot copy segment from input to output file"); curr_file_offset += ROUNDUP_TO_PAGE_BOUNDARY (scp->filesize); if (!unexec_write (curr_header_offset, lc, lc->cmdsize)) unexec_error ("cannot write load command to header"); curr_header_offset += lc->cmdsize; }
/* Take a snapshot of Emacs and make a Mach-O format executable file from it. The file names of the output and input files are outfile and infile, respectively. The three other parameters are ignored. */ void unexec (const char *outfile, const char *infile) { if (in_dumped_exec) unexec_error ("Unexec from a dumped executable is not supported."); pagesize = getpagesize (); infd = open (infile, O_RDONLY, 0); if (infd < 0) { unexec_error ("cannot open input file `%s'", infile); } outfd = open (outfile, O_WRONLY | O_TRUNC | O_CREAT, 0755); if (outfd < 0) { close (infd); unexec_error ("cannot open output file `%s'", outfile); } build_region_list (); read_load_commands (); find_emacs_zone_regions (); unexec_regions_merge (); in_dumped_exec = 1; dump_it (); close (outfd); }
/* Copy a LC_SEGMENT load command for the EMACS_READ_ONLY segment from the input file to the output file, adjusting the file offset of the segment and the file offsets of sections contained in it. The VM protection is changed to read-only, and the sections are dumped from memory. */ static void copy_emacs_read_only_segment (struct load_command *lc) { struct segment_command *scp = (struct segment_command *) lc; unsigned long old_fileoff = scp->fileoff; struct section *sectp; int j; scp->fileoff = curr_file_offset; scp->maxprot = scp->initprot = VM_PROT_READ; printf ("Writing segment %-16.16s @ %#8lx (%#8lx/%#8lx @ %#10lx)\n", scp->segname, (long) (scp->fileoff), (long) (scp->filesize), (long) (scp->vmsize), (long) (scp->vmaddr)); sectp = (struct section *) (scp + 1); for (j = 0; j < scp->nsects; j++) { sectp->offset += curr_file_offset - old_fileoff; if (!unexec_write (sectp->offset, (void *) sectp->addr, sectp->size)) unexec_error ("cannot write section %.16s", sectp->sectname); printf (" section %-16.16s at %#8lx - %#8lx (sz: %#8lx)\n", sectp->sectname, (long) (sectp->offset), (long) (sectp->offset + sectp->size), (long) (sectp->size)); sectp++; } curr_file_offset += ROUNDUP_TO_PAGE_BOUNDARY (scp->filesize); if (!unexec_write (curr_header_offset, lc, lc->cmdsize)) unexec_error ("cannot write load command to header"); curr_header_offset += lc->cmdsize; }
/* Rebase r_address in the relocation table. */ static void rebase_reloc_address (off_t reloff, int nrel, long linkedit_delta, long diff) { int i; struct relocation_info reloc_info; struct scattered_relocation_info *sc_reloc_info = (struct scattered_relocation_info *) &reloc_info; for (i = 0; i < nrel; i++, reloff += sizeof (reloc_info)) { if (lseek (infd, reloff - linkedit_delta, L_SET) != reloff - linkedit_delta) unexec_error ("rebase_reloc_table: cannot seek to reloc_info"); if (!unexec_read (&reloc_info, sizeof (reloc_info))) unexec_error ("rebase_reloc_table: cannot read reloc_info"); if (sc_reloc_info->r_scattered == 0 && reloc_info.r_type == GENERIC_RELOC_VANILLA) { reloc_info.r_address -= diff; if (!unexec_write (reloff, &reloc_info, sizeof (reloc_info))) unexec_error ("rebase_reloc_table: cannot write reloc_info"); } } }
static void unexec_write (int fd, long position, char *buf, int bytes) { int n_written; int remains = bytes; position = unexec_seek (fd, position); if (bytes < 0) unexec_error ("Attempted write of %d bytes in %s", 0, (char *) bytes, "unexec() output file", 0); errno = 0; while (remains > 0) { n_written = write (fd, buf, remains); if (n_written <= 0) unexec_error ("Write failed for 0x%x bytes at offset 0x%x in %s", 1, (char *) bytes, (char *) position, "unexec() output file"); buf += n_written; remains -= n_written; } return; }
static void unexec_read (int fd, long position, char *buf, int bytes) { int n_read; int remains = bytes; position = unexec_seek (fd, position); if (bytes < 0) unexec_error ("Attempted read of %d bytes", 0, (char *) bytes, 0, 0); errno = 0; while (remains > 0) { n_read = read (fd, buf, remains); if (n_read <= 0) unexec_error ("Read failed for 0x%x bytes at offset 0x%x in %s", 1, (char *) bytes, (char *) position, "unexec() output file"); buf += n_read; remains -= n_read; } return; }
/* Fix up relocation entries. */ static void unrelocate (const char *name, off_t reloff, int nrel, vm_address_t base) { int i, unreloc_count; struct relocation_info reloc_info; struct scattered_relocation_info *sc_reloc_info = (struct scattered_relocation_info *) &reloc_info; vm_address_t location; for (unreloc_count = 0, i = 0; i < nrel; i++) { if (lseek (infd, reloff, L_SET) != reloff) unexec_error ("unrelocate: %s:%d cannot seek to reloc_info", name, i); if (!unexec_read (&reloc_info, sizeof (reloc_info))) unexec_error ("unrelocate: %s:%d cannot read reloc_info", name, i); reloff += sizeof (reloc_info); if (sc_reloc_info->r_scattered == 0) switch (reloc_info.r_type) { case GENERIC_RELOC_VANILLA: location = base + reloc_info.r_address; if (location >= data_segment_scp->vmaddr && location < (data_segment_scp->vmaddr + data_segment_scp->vmsize)) { off_t src_off = data_segment_old_fileoff + (location - data_segment_scp->vmaddr); off_t dst_off = data_segment_scp->fileoff + (location - data_segment_scp->vmaddr); if (!unexec_copy (dst_off, src_off, 1 << reloc_info.r_length)) unexec_error ("unrelocate: %s:%d cannot copy original value", name, i); unreloc_count++; } break; default: unexec_error ("unrelocate: %s:%d cannot handle type = %d", name, i, reloc_info.r_type); } else switch (sc_reloc_info->r_type) { #if defined (__ppc__) case PPC_RELOC_PB_LA_PTR: /* nothing to do for prebound lazy pointer */ break; #endif default: unexec_error ("unrelocate: %s:%d cannot handle scattered type = %d", name, i, sc_reloc_info->r_type); } } if (nrel > 0) printf ("Fixed up %d/%d %s relocation entries in data segment.\n", unreloc_count, nrel, name); }
/* Copy a LC_DYLD_INFO(_ONLY) load command from the input file to the output file, adjusting the file offset fields. */ static void copy_dyld_info (struct load_command *lc, long delta) { struct dyld_info_command *dip = (struct dyld_info_command *) lc; if (dip->rebase_off > 0) dip->rebase_off += delta; if (dip->bind_off > 0) dip->bind_off += delta; if (dip->weak_bind_off > 0) dip->weak_bind_off += delta; if (dip->lazy_bind_off > 0) dip->lazy_bind_off += delta; if (dip->export_off > 0) dip->export_off += delta; printf ("Writing "); print_load_command_name (lc->cmd); printf (" command\n"); if (!unexec_write (curr_header_offset, lc, lc->cmdsize)) unexec_error ("cannot write dyld info command to header"); curr_header_offset += lc->cmdsize; }
/* Copy a LC_DYSYMTAB load command from the input file to the output file, adjusting the file offset fields. */ static void copy_dysymtab (struct load_command *lc, long delta) { struct dysymtab_command *dstp = (struct dysymtab_command *) lc; vm_address_t base; #ifdef _LP64 /* First writable segment address. */ base = data_segment_scp->vmaddr; #else /* First segment address in the file (unless MH_SPLIT_SEGS set). */ base = 0; #endif unrelocate ("local", dstp->locreloff, dstp->nlocrel, base); unrelocate ("external", dstp->extreloff, dstp->nextrel, base); if (dstp->nextrel > 0) { dstp->extreloff += delta; } if (dstp->nlocrel > 0) { dstp->locreloff += delta; } if (dstp->nindirectsyms > 0) dstp->indirectsymoff += delta; printf ("Writing LC_DYSYMTAB command\n"); if (!unexec_write (curr_header_offset, lc, lc->cmdsize)) unexec_error ("cannot write symtab command to header"); curr_header_offset += lc->cmdsize; }
static void unexec_fchmod (int fd, int mode) { errno = 0; if (-1 == fchmod (fd, mode)) unexec_error ("fchmod() failed for descriptor %d", 1, (char *) fd, 0, 0); return; }
static void unexec_fstat (int fd, struct stat *statptr) { errno = 0; if (-1 == fstat (fd, statptr)) unexec_error ("fstat() failed for descriptor %d", 1, (char *) fd, 0, 0); return; }
/* Take a snapshot of Gcl and make a Mach-O format executable file from it. The file names of the output and input files are outfile and infile, respectively. The three other parameters are ignored. */ void unexec (char *outfile, char *infile, void *start_data, void *start_bss, void *entry_address) { reset_unexec_globals(); pagesize = getpagesize (); if ((infd = open (infile, O_RDONLY, 0)) < 0) unexec_error ("cannot open input file `%s'", infile); if ((outfd = open (outfile, O_WRONLY | O_TRUNC | O_CREAT, 0755)) < 0) { close (infd); unexec_error ("cannot open output file `%s'", outfile); } read_load_commands_and_dump(); close (outfd); }
static long unexec_seek (int fd, long position) { long seek_value; if (fd <= 0) unexec_error ("No file open in which to seek", 0, 0, 0, 0); errno = 0; if (position < 0) seek_value = (long) lseek (fd, 0, L_INCR); else seek_value = (long) lseek (fd, position, L_SET); if (seek_value < 0) unexec_error ("Failed to do a seek to 0x%x in %s", 1, (char *) position, "unexec() output file", 0); return seek_value; }
/* Copy other kinds of load commands from the input file to the output file, ones that do not require adjustments of file offsets. */ static void copy_other (struct load_command *lc) { printf ("Writing "); print_load_command_name (lc->cmd); printf (" command\n"); if (!unexec_write (curr_header_offset, lc, lc->cmdsize)) unexec_error ("cannot write symtab command to header"); curr_header_offset += lc->cmdsize; }
/* Copy a LC_SYMTAB load command from the input file to the output file, adjusting the file offset fields. */ static void copy_symtab (struct load_command *lc, long delta) { struct symtab_command *stp = (struct symtab_command *) lc; stp->symoff += delta; stp->stroff += delta; printf ("Writing LC_SYMTAB command\n"); if (!unexec_write (curr_header_offset, lc, lc->cmdsize)) unexec_error ("cannot write symtab command to header"); curr_header_offset += lc->cmdsize; }
static void find_emacs_zone_regions (void) { num_unexec_regions = 0; emacs_zone->introspect->enumerator (mach_task_self (), 0, MALLOC_PTR_REGION_RANGE_TYPE | MALLOC_ADMIN_REGION_RANGE_TYPE, (vm_address_t) emacs_zone, unexec_reader, unexec_regions_recorder); if (num_unexec_regions == MAX_UNEXEC_REGIONS) unexec_error ("find_emacs_zone_regions: too many regions"); }
/* Copy a LC_TWOLEVEL_HINTS load command from the input file to the output file, adjusting the file offset fields. */ static void copy_twolevelhints (struct load_command *lc, long delta) { struct twolevel_hints_command *tlhp = (struct twolevel_hints_command *) lc; if (tlhp->nhints > 0) { tlhp->offset += delta; } printf ("Writing LC_TWOLEVEL_HINTS command\n"); if (!unexec_write (curr_header_offset, lc, lc->cmdsize)) unexec_error ("cannot write two level hint command to header"); curr_header_offset += lc->cmdsize; }
static int unexec_open (char *filename, int flag, int mode) { int fd; errno = 0; fd = open (filename, flag, mode); if (fd < 0) { unexec_error ("Failure opening file %s", 1, (void *) filename, 0, 0); return -1; } else return fd; }
/* Copy a LC_FUNCTION_STARTS/LC_DATA_IN_CODE/LC_DYLIB_CODE_SIGN_DRS load command from the input file to the output file, adjusting the data offset field. */ static void copy_linkedit_data (struct load_command *lc, long delta) { struct linkedit_data_command *ldp = (struct linkedit_data_command *) lc; if (ldp->dataoff > 0) ldp->dataoff += delta; printf ("Writing "); print_load_command_name (lc->cmd); printf (" command\n"); if (!unexec_write (curr_header_offset, lc, lc->cmdsize)) unexec_error ("cannot write linkedit data command to header"); curr_header_offset += lc->cmdsize; }
int unexec (char *new_name, char *old_name, unsigned int emacs_edata, unsigned int dummy1, unsigned int dummy2) { /* /dld.sl data */ struct dynamic *ld = 0; /* old and new state */ int old_fd; int new_fd; struct exec old_hdr; struct exec new_hdr; struct stat old_buf; /* some process specific "constants" */ unsigned long n_pagsiz; caddr_t dynamic_beg; caddr_t current_break = (caddr_t) sbrk (0); /* dynamically linked image? -- if so, find dld.sl structures */ if (dynamic_addr) { ld = (struct dynamic *) dynamic_addr; #ifdef DEBUG printf ("dl_text = %#x\n", ld->text); printf ("dl_data = %#x\n", ld->data); printf ("dl_bss = %#x\n", ld->bss); printf ("dl_end = %#x\n", ld->end); printf ("dl_dmodule = %#x\n", ld->dmodule); printf ("dl_dlt = %#x\n", ld->dlt); printf ("dl_plt = %#x\n", ld->plt); #endif } /* open the old and new files, figuring out how big the old one is so that we can map it in */ old_fd = unexec_open (old_name, O_RDONLY, 0); new_fd = unexec_open (new_name, O_RDWR | O_CREAT | O_TRUNC, 0666); /* setup the header and the statbuf for old_fd */ unexec_read (old_fd, 0, (char *) &old_hdr, sizeof (old_hdr)); unexec_fstat (old_fd, &old_buf); /* set up some important constants */ n_pagsiz = EXEC_PAGESIZE; /* setup beginning of data to copy from executable */ if (ld) dynamic_beg = ld->dmodule; else dynamic_beg = (caddr_t)EXEC_ALIGN (old_hdr.a_text) + old_hdr.a_data; /* set up the new exec */ new_hdr = old_hdr; new_hdr.a_text = MASK_DOWN (emacs_edata, n_pagsiz); new_hdr.a_data = MASK_UP (current_break, n_pagsiz) - EXEC_ALIGN(new_hdr.a_text); new_hdr.a_bss = 0; #ifdef DEBUG printf ("old text %#x\n", old_hdr.a_text); printf ("new text %#x\n", new_hdr.a_text); printf ("old data %#x\n", old_hdr.a_data); printf ("new data %#x\n", new_hdr.a_data); printf ("old bss %#x\n", old_hdr.a_bss); printf ("new bss %#x\n", new_hdr.a_bss); #endif /* set up this variable, in case we want to reset "the break" when restarting */ sbrk_of_0_at_unexec = ((unsigned long) MASK_UP (current_break, n_pagsiz)); /* Write out the first approximation to the new file. The sizes of each section will be correct, but there will be a number of corrections that will need to be made. */ { long old_datoff = DATA_OFFSET (old_hdr); long new_datoff = DATA_OFFSET (new_hdr); long old_dataddr = EXEC_ALIGN (old_hdr.a_text); long new_dataddr = EXEC_ALIGN (new_hdr.a_text); long new_mcaloff = MODCAL_OFFSET (new_hdr); long old_mcaloff = MODCAL_OFFSET (old_hdr); long newtext_size = new_hdr.a_text - old_dataddr; long newdata1_size = (unsigned long)dynamic_beg - new_dataddr; long dyn_size = (EXEC_ALIGN (old_hdr.a_text) + old_hdr.a_data) - (unsigned long)dynamic_beg; long newdata2_size = (unsigned long)current_break - ((unsigned long)dynamic_beg + dyn_size); long pad_size = MASK_UP (current_break, n_pagsiz) - ((unsigned long) current_break); #ifdef DEBUG printf ("current break is %#lx\n", current_break); printf ("old_dataddr = %#lx, dynamic_beg = %#lx\n", old_dataddr, dynamic_beg); #endif /* * First, write the text segment with new header -- copy * everything until the start of the data segment from the old * file */ #ifdef DEBUG printf ("copying %#lx bytes of text from 0\n", old_datoff); #endif unexec_copy (new_fd, old_fd, 0, 0, old_datoff); /* pad out the text segment */ #ifdef DEBUG printf ( "text pad size is %#x\n", old_dataddr - old_hdr.a_text); #endif unexec_pad (new_fd, old_dataddr - old_hdr.a_text); /* * Update debug header spoo */ if (new_hdr.a_extension > 0) { new_hdr.a_extension += LESYM_OFFSET(new_hdr) - LESYM_OFFSET(old_hdr); } /* * go back and write the new header. */ unexec_write (new_fd, 0, (char *) &new_hdr, sizeof (new_hdr)); /* * Copy the part of the data segment which becomes text from the * running image. */ #ifdef DEBUG printf ("copying %#lx bytes of new text from %#lx to position %#lx\n", newtext_size, old_dataddr, TEXT_OFFSET(new_hdr) + old_dataddr); #endif unexec_write (new_fd, TEXT_OFFSET(new_hdr) + old_dataddr, (caddr_t)old_dataddr, newtext_size); #ifdef DEBUG printf ("new DATA_OFFSET is %#lx\n", new_datoff); #endif /* * Copy the part of the old data segment which will be data * in the new executable (before the dynamic stuff) * from the running image. */ #ifdef DEBUG printf ("copying %#lx bytes of data from %#lx to position %#lx\n", newdata1_size, new_dataddr, new_datoff); #endif unexec_write (new_fd, new_datoff, (caddr_t)new_dataddr, newdata1_size); /* copy the dynamic part of the data segment from the old executable */ if (dyn_size) { #ifdef DEBUG printf ("copying %#lx bytes of dyn data from executable" " at address %#lx to position %#lx\n", dyn_size, dynamic_beg, new_datoff + newdata1_size); #endif unexec_copy (new_fd, old_fd, old_datoff + newtext_size + newdata1_size, new_datoff + newdata1_size, dyn_size); } /* copy remaining data (old bss) from the running image */ #ifdef DEBUG printf ("copying %#lx bytes of data from %#lx to position %#lx\n", newdata2_size, new_dataddr + newdata1_size + dyn_size, new_datoff + newdata1_size + dyn_size); #endif unexec_write (new_fd, new_datoff + newdata1_size + dyn_size, (caddr_t)(new_dataddr + newdata1_size + dyn_size), newdata2_size); /* pad out the data segment */ #ifdef DEBUG printf ( "pad size is %#x\n", pad_size); #endif unexec_pad (new_fd, pad_size); /* Finally, copy the rest of the junk from the old file. */ #ifdef DEBUG printf ("Copying %#lx bytes of junk from %#lx (old) to %#lx (new)\n", old_buf.st_size - old_mcaloff, old_mcaloff, new_mcaloff); #endif unexec_copy (new_fd, old_fd, old_mcaloff, new_mcaloff, old_buf.st_size - old_mcaloff); { long curpos, offset; struct _debug_header dhdr; int new_header_delta; new_header_delta = LESYM_OFFSET(new_hdr) - LESYM_OFFSET(old_hdr); if ((new_header_delta > 0) && ((offset = EXT_OFFSET(old_hdr)) > 0)) { curpos = lseek(new_fd, 0, SEEK_CUR); lseek(old_fd, offset, 0); if (read(old_fd, &dhdr, sizeof(dhdr)) == sizeof(dhdr)) { dhdr.header_offset += new_header_delta; dhdr.gntt_offset += new_header_delta; dhdr.lntt_offset += new_header_delta; dhdr.slt_offset += new_header_delta; dhdr.vt_offset += new_header_delta; dhdr.xt_offset += new_header_delta; lseek(new_fd, EXT_OFFSET(new_hdr), SEEK_SET); if (write(new_fd, &dhdr, sizeof(dhdr)) != sizeof(dhdr)) { unexec_error("Unable to write debug information to \"%s\"\n", 1, new_name); } lseek(new_fd, curpos, SEEK_SET); } else { unexec_error("Unable to read debug information from \"%s\"\n", 1, old_name); } } } } /* make the output file executable -- then quit */ unexec_fchmod (new_fd, 0755); close (old_fd); close (new_fd); return 0; }
/* Copy a LC_SEGMENT load command for the __DATA segment in the input file to the output file. We assume that only one such segment load command exists in the input file and it contains the sections __data, __bss, __common, __la_symbol_ptr, __nl_symbol_ptr, and __dyld. The first three of these should be dumped from memory and the rest should be copied from the input file. Note that the sections __bss and __common contain no data in the input file because their flag fields have the value S_ZEROFILL. Dumping these from memory makes it necessary to adjust file offset fields in subsequently dumped load commands. Then, create new __DATA segment load commands for regions on the region list other than the one corresponding to the __DATA segment in the input file. */ static void copy_data_segment (struct load_command *lc) { struct segment_command *scp = (struct segment_command *) lc; struct section *sectp; int j; unsigned long header_offset, old_file_offset; /* The new filesize of the segment is set to its vmsize because data blocks for segments must start at region boundaries. Note that this may leave unused locations at the end of the segment data block because the total of the sizes of all sections in the segment is generally smaller than vmsize. */ scp->filesize = scp->vmsize; printf ("Writing segment %-16.16s @ %#8lx (%#8lx/%#8lx @ %#10lx)\n", scp->segname, curr_file_offset, (long)(scp->filesize), (long)(scp->vmsize), (long) (scp->vmaddr)); /* Offsets in the output file for writing the next section structure and segment data block, respectively. */ header_offset = curr_header_offset + sizeof (struct segment_command); sectp = (struct section *) (scp + 1); for (j = 0; j < scp->nsects; j++) { old_file_offset = sectp->offset; sectp->offset = sectp->addr - scp->vmaddr + curr_file_offset; /* The __data section is dumped from memory. The __bss and __common sections are also dumped from memory but their flag fields require changing (from S_ZEROFILL to S_REGULAR). The other three kinds of sections are just copied from the input file. */ if (strncmp (sectp->sectname, SECT_DATA, 16) == 0) { extern char my_edata[]; unsigned long my_size; /* The __data section is basically dumped from memory. But initialized data in statically linked libraries are copied from the input file. In particular, add_image_hook.names and add_image_hook.pointers stored by libarclite_macosx.a, are restored so that they will be reinitialized when the dumped binary is executed. */ my_size = (unsigned long)my_edata - sectp->addr; if (!(sectp->addr <= (unsigned long)my_edata && my_size <= sectp->size)) unexec_error ("my_edata is not in section %s", SECT_DATA); if (!unexec_write (sectp->offset, (void *) sectp->addr, my_size)) unexec_error ("cannot write section %s", SECT_DATA); if (!unexec_copy (sectp->offset + my_size, old_file_offset + my_size, sectp->size - my_size)) unexec_error ("cannot copy section %s", SECT_DATA); if (!unexec_write (header_offset, sectp, sizeof (struct section))) unexec_error ("cannot write section %s's header", SECT_DATA); } else if (strncmp (sectp->sectname, SECT_COMMON, 16) == 0) { sectp->flags = S_REGULAR; if (!unexec_write (sectp->offset, (void *) sectp->addr, sectp->size)) unexec_error ("cannot write section %.16s", sectp->sectname); if (!unexec_write (header_offset, sectp, sizeof (struct section))) unexec_error ("cannot write section %.16s's header", sectp->sectname); } else if (strncmp (sectp->sectname, SECT_BSS, 16) == 0) { extern char *my_endbss_static; unsigned long my_size; sectp->flags = S_REGULAR; /* Clear uninitialized local variables in statically linked libraries. In particular, function pointers stored by libSystemStub.a, which is introduced in Mac OS X 10.4 for binary compatibility with respect to long double, are cleared so that they will be reinitialized when the dumped binary is executed on other versions of OS. */ my_size = (unsigned long)my_endbss_static - sectp->addr; if (!(sectp->addr <= (unsigned long)my_endbss_static && my_size <= sectp->size)) unexec_error ("my_endbss_static is not in section %.16s", sectp->sectname); if (!unexec_write (sectp->offset, (void *) sectp->addr, my_size)) unexec_error ("cannot write section %.16s", sectp->sectname); if (!unexec_write_zero (sectp->offset + my_size, sectp->size - my_size)) unexec_error ("cannot write section %.16s", sectp->sectname); if (!unexec_write (header_offset, sectp, sizeof (struct section))) unexec_error ("cannot write section %.16s's header", sectp->sectname); } else if (strncmp (sectp->sectname, "__la_symbol_ptr", 16) == 0 || strncmp (sectp->sectname, "__nl_symbol_ptr", 16) == 0 || strncmp (sectp->sectname, "__got", 16) == 0 || strncmp (sectp->sectname, "__la_sym_ptr2", 16) == 0 || strncmp (sectp->sectname, "__dyld", 16) == 0 || strncmp (sectp->sectname, "__const", 16) == 0 || strncmp (sectp->sectname, "__cfstring", 16) == 0 || strncmp (sectp->sectname, "__gcc_except_tab", 16) == 0 || strncmp (sectp->sectname, "__program_vars", 16) == 0 || strncmp (sectp->sectname, "__mod_init_func", 16) == 0 || strncmp (sectp->sectname, "__mod_term_func", 16) == 0 || strncmp (sectp->sectname, "__objc_", 7) == 0) { if (!unexec_copy (sectp->offset, old_file_offset, sectp->size)) unexec_error ("cannot copy section %.16s", sectp->sectname); if (!unexec_write (header_offset, sectp, sizeof (struct section))) unexec_error ("cannot write section %.16s's header", sectp->sectname); } else unexec_error ("unrecognized section %.16s in __DATA segment", sectp->sectname); printf (" section %-16.16s at %#8lx - %#8lx (sz: %#8lx)\n", sectp->sectname, (long) (sectp->offset), (long) (sectp->offset + sectp->size), (long) (sectp->size)); header_offset += sizeof (struct section); sectp++; } curr_file_offset += ROUNDUP_TO_PAGE_BOUNDARY (scp->filesize); if (!unexec_write (curr_header_offset, scp, sizeof (struct segment_command))) unexec_error ("cannot write header of __DATA segment"); curr_header_offset += lc->cmdsize; /* Create new __DATA segment load commands for regions on the region list that do not corresponding to any segment load commands in the input file. */ for (j = 0; j < num_unexec_regions; j++) { struct segment_command sc; sc.cmd = LC_SEGMENT; sc.cmdsize = sizeof (struct segment_command); strncpy (sc.segname, SEG_DATA, 16); sc.vmaddr = unexec_regions[j].range.address; sc.vmsize = unexec_regions[j].range.size; sc.fileoff = curr_file_offset; sc.filesize = unexec_regions[j].filesize; sc.maxprot = VM_PROT_READ | VM_PROT_WRITE; sc.initprot = VM_PROT_READ | VM_PROT_WRITE; sc.nsects = 0; sc.flags = 0; printf ("Writing segment %-16.16s @ %#8lx (%#8lx/%#8lx @ %#10lx)\n", sc.segname, (long) (sc.fileoff), (long) (sc.filesize), (long) (sc.vmsize), (long) (sc.vmaddr)); if (!unexec_write (sc.fileoff, (void *) sc.vmaddr, sc.filesize)) unexec_error ("cannot write new __DATA segment"); curr_file_offset += ROUNDUP_TO_PAGE_BOUNDARY (sc.filesize); if (!unexec_write (curr_header_offset, &sc, sc.cmdsize)) unexec_error ("cannot write new __DATA segment's header"); curr_header_offset += sc.cmdsize; mh.ncmds++; } }
/* Copy a LC_DYSYMTAB load command from the input file to the output file, adjusting the file offset fields. */ static void copy_dysymtab (struct load_command *lc, long delta) { struct dysymtab_command *dstp = (struct dysymtab_command *) lc; vm_address_t base; #ifdef _LP64 #if __ppc64__ { int i; base = 0; for (i = 0; i < nlc; i++) if (lca[i]->cmd == LC_SEGMENT) { struct segment_command *scp = (struct segment_command *) lca[i]; if (scp->vmaddr + scp->vmsize > 0x100000000 && (scp->initprot & VM_PROT_WRITE) != 0) { base = data_segment_scp->vmaddr; break; } } } #else /* First writable segment address. */ base = data_segment_scp->vmaddr; #endif #else /* First segment address in the file (unless MH_SPLIT_SEGS set). */ base = 0; #endif unrelocate ("local", dstp->locreloff, dstp->nlocrel, base); unrelocate ("external", dstp->extreloff, dstp->nextrel, base); if (dstp->nextrel > 0) { dstp->extreloff += delta; } if (dstp->nlocrel > 0) { dstp->locreloff += delta; } if (dstp->nindirectsyms > 0) dstp->indirectsymoff += delta; printf ("Writing LC_DYSYMTAB command\n"); if (!unexec_write (curr_header_offset, lc, lc->cmdsize)) unexec_error ("cannot write symtab command to header"); curr_header_offset += lc->cmdsize; #if __ppc64__ /* Check if the relocation base needs to be changed. */ if (base == 0) { vm_address_t newbase = 0; int i; for (i = 0; i < num_unexec_regions; i++) if (unexec_regions[i].range.address + unexec_regions[i].range.size > 0x100000000) { newbase = data_segment_scp->vmaddr; break; } if (newbase) { rebase_reloc_address (dstp->locreloff, dstp->nlocrel, delta, newbase); rebase_reloc_address (dstp->extreloff, dstp->nextrel, delta, newbase); } } #endif }
/* Read header and load commands from input file. Store the latter in the global array lca. Store the total number of load commands in global variable nlc. */ static void read_load_commands (void) { int i; if (!unexec_read (&mh, sizeof (struct mach_header))) unexec_error ("cannot read mach-o header"); if (mh.magic != MH_MAGIC) unexec_error ("input file not in Mach-O format"); if (mh.filetype != MH_EXECUTE) unexec_error ("input Mach-O file is not an executable object file"); #if VERBOSE printf ("--- Header Information ---\n"); printf ("Magic = 0x%08x\n", mh.magic); printf ("CPUType = %d\n", mh.cputype); printf ("CPUSubType = %d\n", mh.cpusubtype); printf ("FileType = 0x%x\n", mh.filetype); printf ("NCmds = %d\n", mh.ncmds); printf ("SizeOfCmds = %d\n", mh.sizeofcmds); printf ("Flags = 0x%08x\n", mh.flags); #endif nlc = mh.ncmds; lca = malloc (nlc * sizeof *lca); for (i = 0; i < nlc; i++) { struct load_command lc; /* Load commands are variable-size: so read the command type and size first and then read the rest. */ if (!unexec_read (&lc, sizeof (struct load_command))) unexec_error ("cannot read load command"); lca[i] = malloc (lc.cmdsize); memcpy (lca[i], &lc, sizeof (struct load_command)); if (!unexec_read (lca[i] + 1, lc.cmdsize - sizeof (struct load_command))) unexec_error ("cannot read content of load command"); if (lc.cmd == LC_SEGMENT) { struct segment_command *scp = (struct segment_command *) lca[i]; if (scp->vmaddr + scp->vmsize > infile_lc_highest_addr) infile_lc_highest_addr = scp->vmaddr + scp->vmsize; if (strncmp (scp->segname, SEG_TEXT, 16) == 0) { struct section *sectp = (struct section *) (scp + 1); int j; for (j = 0; j < scp->nsects; j++) if (sectp->offset < text_seg_lowest_offset) text_seg_lowest_offset = sectp->offset; } } } printf ("Highest address of load commands in input file: %#8lx\n", (unsigned long)infile_lc_highest_addr); printf ("Lowest offset of all sections in __TEXT segment: %#8lx\n", text_seg_lowest_offset); printf ("--- List of Load Commands in Input File ---\n"); printf ("# cmd cmdsize name address size\n"); for (i = 0; i < nlc; i++) { printf ("%1d ", i); print_load_command (lca[i]); } }
/* Build the list of regions that need to be dumped. Regions with addresses above VM_DATA_TOP are omitted. Adjacent regions with identical protection are merged. Note that non-writable regions cannot be omitted because they some regions created at run time are read-only. */ static void build_region_list (void) { task_t target_task = mach_task_self (); vm_address_t address = (vm_address_t) 0; vm_size_t size; struct vm_region_basic_info info; mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT; mach_port_t object_name; struct region_t *r; #if VERBOSE printf ("--- List of All Regions ---\n"); printf (" address size prot maxp\n"); #endif while (vm_region (target_task, &address, &size, VM_REGION_BASIC_INFO, (vm_region_info_t) &info, &info_count, &object_name) == KERN_SUCCESS && info_count == VM_REGION_BASIC_INFO_COUNT) { /* Done when we reach addresses of shared libraries, which are loaded in high memory. */ if (address >= VM_DATA_TOP) break; #if VERBOSE print_region (address, size, info.protection, info.max_protection); #endif /* If a region immediately follows the previous one (the one most recently added to the list) and has identical protection, merge it with the latter. Otherwise create a new list element for it. */ if (region_list_tail && info.protection == region_list_tail->protection && info.max_protection == region_list_tail->max_protection && region_list_tail->address + region_list_tail->size == address) { region_list_tail->size += size; } else { r = malloc (sizeof *r); if (!r) unexec_error ("cannot allocate region structure"); r->address = address; r->size = size; r->protection = info.protection; r->max_protection = info.max_protection; r->next = 0; if (region_list_head == 0) { region_list_head = r; region_list_tail = r; } else { region_list_tail->next = r; region_list_tail = r; } /* Deallocate (unused) object name returned by vm_region. */ if (object_name != MACH_PORT_NULL) mach_port_deallocate (target_task, object_name); } address += size; } printf ("--- List of Regions to be Dumped ---\n"); print_region_list (); }
/* Loop through all load commands and dump them. Then write the Mach header. */ static void dump_it (void) { int i; long linkedit_delta = 0; printf ("--- Load Commands written to Output File ---\n"); for (i = 0; i < nlc; i++) switch (lca[i]->cmd) { case LC_SEGMENT: { struct segment_command *scp = (struct segment_command *) lca[i]; if (strncmp (scp->segname, SEG_DATA, 16) == 0) { /* save data segment file offset and segment_command for unrelocate */ if (data_segment_old_fileoff) unexec_error ("cannot handle multiple DATA segments" " in input file"); data_segment_old_fileoff = scp->fileoff; data_segment_scp = scp; copy_data_segment (lca[i]); } else if (strncmp (scp->segname, EMACS_READ_ONLY_SEGMENT, 16) == 0) { copy_emacs_read_only_segment (lca[i]); } else { if (strncmp (scp->segname, SEG_LINKEDIT, 16) == 0) { if (linkedit_delta) unexec_error ("cannot handle multiple LINKEDIT segments" " in input file"); linkedit_delta = curr_file_offset - scp->fileoff; } copy_segment (lca[i]); } } break; case LC_SYMTAB: copy_symtab (lca[i], linkedit_delta); break; case LC_DYSYMTAB: copy_dysymtab (lca[i], linkedit_delta); break; case LC_TWOLEVEL_HINTS: copy_twolevelhints (lca[i], linkedit_delta); break; #ifdef LC_DYLD_INFO case LC_DYLD_INFO: case LC_DYLD_INFO_ONLY: copy_dyld_info (lca[i], linkedit_delta); break; #endif #ifdef LC_FUNCTION_STARTS case LC_FUNCTION_STARTS: #ifdef LC_DATA_IN_CODE case LC_DATA_IN_CODE: #endif #ifdef LC_DYLIB_CODE_SIGN_DRS case LC_DYLIB_CODE_SIGN_DRS: #endif copy_linkedit_data (lca[i], linkedit_delta); break; #endif default: copy_other (lca[i]); break; } if (curr_header_offset > text_seg_lowest_offset) unexec_error ("not enough room for load commands for new __DATA segments"); printf ("%ld unused bytes follow Mach-O header\n", text_seg_lowest_offset - curr_header_offset); mh.sizeofcmds = curr_header_offset - sizeof (struct mach_header); if (!unexec_write (0, &mh, sizeof (struct mach_header))) unexec_error ("cannot write final header contents"); }