#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <libc.h>
#include <mach-o/loader.h>
#include <mach-o/fat.h>
#include <mach/mach.h>
#include "stuff/openstep_mach.h"
#include <mach/mach_error.h>
#include "stuff/allocate.h"
#include "stuff/errors.h"
#include "stuff/round.h"
#include "stuff/bytesex.h"
__private_extern__
char *progname = NULL;
static char *input,
*output;
struct extract {
char *segname;
char *sectname;
char *filename;
long found;
struct extract *next;
} *extracts;
struct replace {
char *segname;
char *sectname;
char *filename;
long found;
long size;
struct replace *next;
} *replaces;
struct rep_seg {
long modified;
long fileoff;
long filesize;
long vmsize;
long padsize;
struct segment_command *sgp;
} *segs;
struct rep_sect {
struct replace *rp;
long offset;
struct section *sp;
} *sects;
static void *input_addr;
static unsigned long input_size;
static long input_mode;
static struct mach_header *mhp;
static struct load_command
*load_commands;
static long pagesize = 8192;
static enum bool swapped;
static enum byte_sex host_byte_sex = UNKNOWN_BYTE_SEX;
static enum byte_sex target_byte_sex = UNKNOWN_BYTE_SEX;
static void map_input(
void);
static void extract_sections(
void);
static void replace_sections(
void);
static int cmp_qsort(
const struct rep_seg *seg1,
const struct rep_seg *seg2);
static void usage(
void);
int
main(
int argc,
char *argv[],
char *envp[])
{
int i;
struct extract *ep;
struct replace *rp;
progname = argv[0];
host_byte_sex = get_host_byte_sex();
for (i = 1; i < argc; i++) {
if(argv[i][0] == '-'){
switch(argv[i][1]){
case 'e':
if(i + 4 > argc){
error("missing arguments to %s option", argv[i]);
usage();
}
ep = allocate(sizeof(struct extract));
ep->segname = argv[i + 1];
ep->sectname = argv[i + 2];
ep->filename = argv[i + 3];
ep->found = 0;
ep->next = extracts;
extracts = ep;
i += 3;
break;
case 'r':
if(i + 4 > argc){
error("missing arguments to %s option", argv[i]);
usage();
}
rp = allocate(sizeof(struct replace));
rp->segname = argv[i + 1];
rp->sectname = argv[i + 2];
rp->filename = argv[i + 3];
rp->next = replaces;
replaces = rp;
i += 3;
break;
case 'o':
if(output != NULL)
fatal("more than one %s option", argv[i]);
output = argv[i + 1];
i += 1;
break;
default:
error("unrecognized option: %s", argv[i]);
usage();
}
}
else{
if(input != NULL){
fatal("only one input file can be specified");
usage();
}
input = argv[i];
}
}
if(input == NULL){
error("no input file specified");
usage();
}
if(replaces != NULL && output == NULL)
fatal("output file must be specified via -o <filename> when "
"replacing a section");
if(extracts == NULL && replaces == NULL){
error("no -extract or -replace options specified");
usage();
}
map_input();
if(extracts != NULL)
extract_sections();
if(replaces != NULL)
replace_sections();
return(0);
}
static
void
map_input(void)
{
int fd;
unsigned long i;
struct stat stat_buf;
kern_return_t r;
struct load_command l, *lcp;
struct segment_command *sgp;
struct section *sp;
struct symtab_command *stp;
struct symseg_command *ssp;
if((fd = open(input, O_RDONLY)) == -1)
system_fatal("can't open input file: %s", input);
if(fstat(fd, &stat_buf) == -1)
system_fatal("Can't stat input file: %s", input);
input_size = stat_buf.st_size;
input_mode = stat_buf.st_mode;
if((r = map_fd((int)fd, (vm_offset_t)0, (vm_offset_t *)&input_addr,
(boolean_t)TRUE, (vm_size_t)input_size)) != KERN_SUCCESS)
mach_fatal(r, "Can't map input file: %s", input);
close(fd);
if(sizeof(struct mach_header) > input_size)
fatal("truncated or malformed object (mach header would extend "
"past the end of the file) in: %s", input);
mhp = (struct mach_header *)input_addr;
#ifdef __BIG_ENDIAN__
if(mhp->magic == FAT_MAGIC)
#endif
#ifdef __LITTLE_ENDIAN__
if(mhp->magic == SWAP_LONG(FAT_MAGIC))
#endif
fatal("file: %s is a fat file (%s only operates on Mach-O files, "
"use lipo(1) on it to get a Mach-O file)", input, progname);
host_byte_sex = get_host_byte_sex();
if(mhp->magic == SWAP_LONG(MH_MAGIC)){
swapped = TRUE;
target_byte_sex = host_byte_sex == BIG_ENDIAN_BYTE_SEX ?
LITTLE_ENDIAN_BYTE_SEX : BIG_ENDIAN_BYTE_SEX;
swap_mach_header(mhp, host_byte_sex);
}
else if(mhp->magic == MH_MAGIC){
swapped = FALSE;
target_byte_sex = host_byte_sex;
}
else
fatal("bad magic number (file is not a Mach-O file) in: %s", input);
if(mhp->sizeofcmds + sizeof(struct mach_header) > input_size)
fatal("truncated or malformed object (load commands would extend "
"past the end of the file) in: %s", input);
load_commands = (struct load_command *)((char *)input_addr +
sizeof(struct mach_header));
lcp = load_commands;
for(i = 0; i < mhp->ncmds; i++){
l = *lcp;
if(swapped)
swap_load_command(&l, host_byte_sex);
if(l.cmdsize % sizeof(long) != 0)
error("load command %ld size not a multiple of sizeof(long) "
"in: %s", i, input);
if(l.cmdsize <= 0)
fatal("load command %ld size is less than or equal to zero "
"in: %s", i, input);
if((char *)lcp + l.cmdsize >
(char *)load_commands + mhp->sizeofcmds)
fatal("load command %ld extends past end of all load commands "
"in: %s", i, input);
switch(l.cmd){
case LC_SEGMENT:
sgp = (struct segment_command *)lcp;
sp = (struct section *)((char *)sgp +
sizeof(struct segment_command));
if(swapped)
swap_segment_command(sgp, host_byte_sex);
if(swapped)
swap_section(sp, sgp->nsects, host_byte_sex);
break;
case LC_SYMTAB:
stp = (struct symtab_command *)lcp;
if(swapped)
swap_symtab_command(stp, host_byte_sex);
break;
case LC_SYMSEG:
ssp = (struct symseg_command *)lcp;
if(swapped)
swap_symseg_command(ssp, host_byte_sex);
break;
default:
*lcp = l;
break;
}
lcp = (struct load_command *)((char *)lcp + l.cmdsize);
}
}
static
void
extract_sections(void)
{
unsigned long i, j, errors;
struct load_command *lcp;
struct segment_command *sgp;
struct section *sp;
struct extract *ep;
int fd;
lcp = load_commands;
for(i = 0; i < mhp->ncmds; i++){
if(lcp->cmd == LC_SEGMENT){
sgp = (struct segment_command *)lcp;
sp = (struct section *)((char *)sgp +
sizeof(struct segment_command));
for(j = 0; j < sgp->nsects; j++){
ep = extracts;
while(ep != NULL){
if(ep->found == 0 &&
strncmp(ep->segname, sp->segname,
sizeof(sp->segname)) == 0 &&
strncmp(ep->sectname, sp->sectname,
sizeof(sp->sectname)) == 0){
if(sp->flags == S_ZEROFILL)
fatal("meaningless to extract zero fill "
"section (%s,%s) in: %s", sp->segname,
sp->sectname, input);
if(sp->offset + sp->size > input_size)
fatal("truncated or malformed object (section "
"contents of (%s,%s) extends past the "
"end of the file) in: %s", sp->segname,
sp->sectname, input);
if((fd = open(ep->filename, O_WRONLY | O_CREAT |
O_TRUNC, 0666)) == -1)
system_fatal("can't create: %s", ep->filename);
if(write(fd, (char *)input_addr + sp->offset,
sp->size) != (int)sp->size)
system_fatal("can't write: %s", ep->filename);
if(close(fd) == -1)
system_fatal("can't close: %s", ep->filename);
ep->found = 1;
}
ep = ep->next;
}
sp++;
}
}
lcp = (struct load_command *)((char *)lcp + lcp->cmdsize);
}
errors = 0;
ep = extracts;
while(ep != NULL){
if(ep->found == 0){
error("section (%s,%s) not found in: %s", ep->segname,
ep->sectname, input);
errors = 1;
}
ep = ep->next;
}
if(errors != 0)
exit(1);
}
static
void
replace_sections(void)
{
unsigned long i, j, k, l, errors, nsegs, nsects, high_reloc_seg;
unsigned long low_noreloc_seg, high_noreloc_seg, low_linkedit, oldvmaddr;
unsigned long oldoffset, newvmaddr, newoffset, oldsectsize, newsectsize;
struct load_command lc, *lcp;
struct segment_command *sgp, *linkedit_sgp;
struct section *sp;
struct symtab_command *stp;
struct symseg_command *ssp;
struct replace *rp;
struct stat stat_buf;
int outfd, sectfd;
vm_address_t sect_addr, pad_addr;
long size;
kern_return_t r;
errors = 0;
high_reloc_seg = 0;
low_noreloc_seg = input_size;
high_noreloc_seg = 0;
low_linkedit = input_size;
nsegs = 0;
segs = allocate(mhp->ncmds * sizeof(struct rep_seg));
bzero(segs, mhp->ncmds * sizeof(struct rep_seg));
nsects = 0;
stp = NULL;
ssp = NULL;
linkedit_sgp = NULL;
lcp = load_commands;
for(i = 0; i < mhp->ncmds; i++){
switch(lcp->cmd){
case LC_SEGMENT:
sgp = (struct segment_command *)lcp;
sp = (struct section *)((char *)sgp +
sizeof(struct segment_command));
segs[nsegs++].sgp = sgp;
nsects += sgp->nsects;
if(strcmp(sgp->segname, SEG_LINKEDIT) != 0){
if(sgp->flags & SG_NORELOC){
if(sgp->filesize != 0){
if(sgp->fileoff + sgp->filesize > high_noreloc_seg)
high_noreloc_seg = sgp->fileoff + sgp->filesize;
if(sgp->fileoff < low_noreloc_seg)
low_noreloc_seg = sgp->fileoff;
}
}
else{
if(sgp->filesize != 0 &&
sgp->fileoff + sgp->filesize > high_reloc_seg)
high_reloc_seg = sgp->fileoff + sgp->filesize;
}
}
else{
if(linkedit_sgp != NULL)
fatal("more than one " SEG_LINKEDIT " segment found "
"in: %s", input);
linkedit_sgp = sgp;
}
for(j = 0; j < sgp->nsects; j++){
if(sp->nreloc != 0 && sp->reloff < low_linkedit)
low_linkedit = sp->reloff;
rp = replaces;
while(rp != NULL){
if(rp->found == 0 &&
strncmp(rp->segname, sp->segname,
sizeof(sp->segname)) == 0 &&
strncmp(rp->sectname, sp->sectname,
sizeof(sp->sectname)) == 0){
if(sp->flags == S_ZEROFILL){
error("can't replace zero fill section (%.16s,"
"%.16s) in: %s", sp->segname,
sp->sectname, input);
errors = 1;
}
if((sgp->flags & SG_NORELOC) == 0){
error("can't replace section (%.16s,%.16s) "
"in: %s because it requires relocation",
sp->segname, sp->sectname, input);
errors = 1;
}
if(sp->offset + sp->size > input_size)
fatal("truncated or malformed object (section "
"contents of (%.16s,%.16s) extends "
"past the end of the file) in: %s",
sp->segname, sp->sectname, input);
rp->found = 1;
}
rp = rp->next;
}
sp++;
}
break;
case LC_SYMTAB:
if(stp != NULL)
fatal("more than one symtab_command found in: %s", input);
stp = (struct symtab_command *)lcp;
if(stp->nsyms != 0 && stp->symoff < low_linkedit)
low_linkedit = stp->symoff;
if(stp->strsize != 0 && stp->stroff < low_linkedit)
low_linkedit = stp->stroff;
break;
case LC_DYSYMTAB:
fatal("current limitation, can't process files with "
"LC_DYSYMTAB load command as in: %s", input);
break;
case LC_SYMSEG:
if(ssp != NULL)
fatal("more than one symseg_command found in: %s", input);
ssp = (struct symseg_command *)lcp;
if(ssp->size != 0 && ssp->offset < low_linkedit)
low_linkedit = ssp->offset;
break;
case LC_THREAD:
case LC_UNIXTHREAD:
case LC_LOADFVMLIB:
case LC_IDFVMLIB:
case LC_IDENT:
case LC_FVMFILE:
case LC_PREPAGE:
case LC_LOAD_DYLIB:
case LC_LOAD_WEAK_DYLIB:
case LC_REEXPORT_DYLIB:
case LC_ID_DYLIB:
case LC_LOAD_DYLINKER:
case LC_ID_DYLINKER:
break;
default:
error("unknown load command %ld (result maybe bad)", i);
break;
}
lcp = (struct load_command *)((char *)lcp + lcp->cmdsize);
}
rp = replaces;
while(rp != NULL){
if(rp->found == 0){
error("section (%s,%s) not found in: %s", rp->segname,
rp->sectname, input);
errors = 1;
}
else{
if(stat(rp->filename, &stat_buf) == -1){
system_error("Can't stat file: %s to replace section "
"(%s,%s) with", rp->filename, rp->segname,
rp->sectname);
errors = 1;
}
rp->size = stat_buf.st_size;
}
rp = rp->next;
}
if(errors != 0)
exit(1);
if(high_reloc_seg > low_noreloc_seg ||
high_reloc_seg > low_linkedit ||
high_noreloc_seg > low_linkedit)
fatal("contents of input file: %s not in an order that the "
"specified sections can be replaced by this program", input);
qsort(segs, nsegs, sizeof(struct rep_seg),
(int (*)(const void *, const void *))cmp_qsort);
sects = allocate(nsects * sizeof(struct rep_sect));
bzero(sects, nsects * sizeof(struct rep_sect));
oldvmaddr = 0;
newvmaddr = 0;
if(nsegs > 1)
oldoffset = segs[0].sgp->fileoff;
else
oldoffset = 0;
newoffset = 0;
k = 0;
for(i = 0; i < nsegs; i++){
if(segs[i].sgp->vmaddr != oldvmaddr)
fatal("addresses of input file: %s not in an order that the "
"specified sections can be replaced by this program",
input);
segs[i].filesize = segs[i].sgp->filesize;
segs[i].vmsize = segs[i].sgp->vmsize;
segs[i].sgp->vmaddr = newvmaddr;
if(segs[i].sgp->filesize != 0){
if(segs[i].sgp->fileoff != oldoffset)
fatal("segment offsets of input file: %s not in an order "
"that the specified sections can be replaced by this "
"program", input);
segs[i].fileoff = segs[i].sgp->fileoff;
if(strcmp(segs[i].sgp->segname, SEG_LINKEDIT) != 0 ||
i != nsegs - 1)
segs[i].sgp->fileoff = newoffset;
sp = (struct section *)((char *)(segs[i].sgp) +
sizeof(struct segment_command));
oldsectsize = 0;
newsectsize = 0;
if(segs[i].sgp->flags & SG_NORELOC){
for(j = 0; j < segs[i].sgp->nsects; j++){
sects[k + j].sp = sp;
sects[k + j].offset = sp->offset;
oldsectsize += sp->size;
rp = replaces;
while(rp != NULL){
if(strncmp(rp->segname, sp->segname,
sizeof(sp->segname)) == 0 &&
strncmp(rp->sectname, sp->sectname,
sizeof(sp->sectname)) == 0){
sects[k + j].rp = rp;
segs[i].modified = 1;
sp->size = round(rp->size, 1 << sp->align);
break;
}
rp = rp->next;
}
sp->offset = newoffset + newsectsize;
sp->addr = newvmaddr + newsectsize;
newsectsize += sp->size;
sp++;
}
if(strcmp(segs[i].sgp->segname, SEG_LINKEDIT) != 0 ||
i != nsegs - 1){
if(segs[i].sgp->filesize != round(oldsectsize,
pagesize))
fatal("contents of input file: %s not in a format "
"that the specified sections can be replaced "
"by this program", input);
segs[i].sgp->filesize = round(newsectsize, pagesize);
segs[i].sgp->vmsize = round(newsectsize, pagesize);
segs[i].padsize = segs[i].sgp->filesize - newsectsize;
}
}
if(strcmp(segs[i].sgp->segname, SEG_LINKEDIT) != 0 ||
i != nsegs - 1){
oldoffset += segs[i].filesize;
newoffset += segs[i].sgp->filesize;
}
}
oldvmaddr += segs[i].vmsize;
newvmaddr += segs[i].sgp->vmsize;
k += segs[i].sgp->nsects;
}
if(oldoffset != low_linkedit)
fatal("contents of input file: %s not in an order that the "
"specified sections can be replaced by this program", input);
for(i = 0; i < nsegs; i++){
sp = (struct section *)((char *)(segs[i].sgp) +
sizeof(struct segment_command));
for(j = 0; j < segs[i].sgp->nsects; j++){
if(sp->nreloc != 0)
sp->reloff += newoffset - oldoffset;
sp++;
}
}
if(stp != NULL){
if(stp->nsyms != 0)
stp->symoff += newoffset - oldoffset;
if(stp->strsize != 0)
stp->stroff += newoffset - oldoffset;
}
if(ssp != NULL){
if(ssp->size != 0)
ssp->offset += newoffset - oldoffset;
}
if(linkedit_sgp != NULL){
linkedit_sgp->fileoff += newoffset - oldoffset;
}
if((outfd = open(output, O_CREAT | O_WRONLY | O_TRUNC ,input_mode))
== -1)
system_fatal("can't create output file: %s", output);
if((r = vm_allocate(mach_task_self(), &pad_addr, pagesize, 1)) !=
KERN_SUCCESS)
mach_fatal(r, "vm_allocate() failed");
k = 0;
for(i = 0; i < nsegs; i++){
if(segs[i].modified){
for(j = 0; j < segs[i].sgp->nsects; j++){
sp = sects[k + j].sp;
rp = sects[k + j].rp;
if(rp != NULL){
if((sectfd = open(rp->filename, O_RDONLY)) == -1)
system_fatal("can't open file: %s to replace "
"section (%s,%s) with", rp->filename,
rp->segname, rp->sectname);
if((r = map_fd(sectfd, 0, §_addr, TRUE, rp->size))
!= KERN_SUCCESS)
mach_fatal(r, "Can't map file: %s", rp->filename);
for(l = rp->size + 1; l < sp->size; l++)
*((char *)sect_addr + l) = '\0';
if(write(outfd, (char *)sect_addr,sp->size) !=
(int)sp->size)
system_fatal("can't write new section contents for "
"section (%s,%s) to output file: %s",
rp->segname, rp->sectname, output);
if(close(sectfd) == -1)
system_error("can't close file: %s to replace "
"section (%s,%s) with", rp->filename,
rp->segname, rp->sectname);
if((r = vm_deallocate(mach_task_self(), sect_addr,
rp->size)) != KERN_SUCCESS)
mach_fatal(r, "Can't deallocate memory for mapped "
"file: %s", rp->filename);
}
else{
if(sects[k + j].offset + sp->size > input_size)
fatal("truncated or malformed object file: %s "
"(section (%.16s,%.16s) extends past the "
"end of the file)",input, sp->segname,
sp->sectname);
if(write(outfd,(char *)input_addr + sects[k + j].offset,
sp->size) != (int)sp->size)
system_fatal("can't write section contents for "
"section (%s,%s) to output file: %s",
rp->segname, rp->sectname, output);
}
sp++;
}
if(write(outfd, (char *)pad_addr, segs[i].padsize) !=
segs[i].padsize)
system_fatal("can't write segment padding for segment %s to"
" output file: %s", segs[i].sgp->segname,
output);
}
else{
if(strcmp(segs[i].sgp->segname, SEG_LINKEDIT) != 0 ||
i != nsegs - 1){
if(segs[i].fileoff + segs[i].sgp->filesize > input_size)
fatal("truncated or malformed object file: %s "
"(segment: %s extends past the end of "
"the file)", input, segs[i].sgp->segname);
if(write(outfd, (char *)input_addr + segs[i].fileoff,
segs[i].sgp->filesize) != (int)segs[i].sgp->filesize)
system_fatal("can't write segment contents for "
"segment: %s to output file: %s",
segs[i].sgp->segname, output);
}
}
k += segs[i].sgp->nsects;
}
size = input_size - low_linkedit;
if(write(outfd, (char *)input_addr + low_linkedit, size) != size)
system_fatal("can't write link edit information to output file: %s",
output);
lseek(outfd, 0, L_SET);
size = sizeof(struct mach_header) + mhp->sizeofcmds;
if(swapped){
lcp = load_commands;
for(i = 0; i < mhp->ncmds; i++){
lc = *lcp;
switch(lcp->cmd){
case LC_SEGMENT:
sgp = (struct segment_command *)lcp;
sp = (struct section *)((char *)sgp +
sizeof(struct segment_command));
swap_section(sp, sgp->nsects, host_byte_sex);
swap_segment_command(sgp, host_byte_sex);
break;
case LC_SYMTAB:
stp = (struct symtab_command *)lcp;
swap_symtab_command(stp, host_byte_sex);
break;
case LC_SYMSEG:
ssp = (struct symseg_command *)lcp;
swap_symseg_command(ssp, host_byte_sex);
break;
default:
swap_load_command(lcp, host_byte_sex);
break;
}
lcp = (struct load_command *)((char *)lcp + lc.cmdsize);
}
swap_mach_header(mhp, host_byte_sex);
}
if(write(outfd, input_addr, size) != size)
system_fatal("can't write headers to output file: %s", output);
if(close(outfd) == -1)
system_fatal("can't close output file: %s", output);
}
static
int
cmp_qsort(
const struct rep_seg *seg1,
const struct rep_seg *seg2)
{
return((long)(seg1->sgp->vmaddr) - (long)(seg2->sgp->vmaddr));
}
static
void
usage(void)
{
fprintf(stderr, "Usage: %s <input file> [-extract <segname> <sectname> "
"<filename>] ...\n\t[[-replace <segname> <sectname> "
"<filename>] ... -output <filename>]\n", progname);
exit(1);
}