/*- * Copyright (c) 1997-2000 Doug Rabson * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #ifdef __i386__ #define FREEBSD_AOUT 1 #include <sys/param.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/malloc.h> #include <sys/proc.h> #include <sys/namei.h> #include <sys/fcntl.h> #include <sys/vnode.h> #include <sys/linker.h> #include "linker_if.h" #ifndef __ELF__ #include <vm/vm.h> #include <vm/pmap.h> #include <machine/vmparam.h> #endif #include <a.out.h> #include <link.h> typedef struct aout_file { struct linker_file lf; /* Common fields */ int preloaded; /* Was this pre-loader */ char* address; /* Load address */ struct _dynamic* dynamic; /* Symbol table etc. */ } *aout_file_t; static int link_aout_link_preload(linker_class_t lc, const char* modname, linker_file_t*); static int link_aout_link_preload_finish(linker_file_t); static int link_aout_load_file(linker_class_t lc, const char*, linker_file_t*); static int link_aout_lookup_symbol(linker_file_t, const char*, c_linker_sym_t*); static int link_aout_symbol_values(linker_file_t file, c_linker_sym_t sym, linker_symval_t* symval); static int link_aout_search_symbol(linker_file_t lf, caddr_t value, c_linker_sym_t* sym, long* diffp); static void link_aout_unload_file(linker_file_t); static void link_aout_unload_preload(linker_file_t); static int link_aout_lookup_set(linker_file_t, const char*, void ***, void ***, int*); static kobj_method_t link_aout_methods[] = { KOBJMETHOD(linker_lookup_symbol, link_aout_lookup_symbol), KOBJMETHOD(linker_symbol_values, link_aout_symbol_values), KOBJMETHOD(linker_search_symbol, link_aout_search_symbol), KOBJMETHOD(linker_unload, link_aout_unload_file), KOBJMETHOD(linker_load_file, link_aout_load_file), KOBJMETHOD(linker_link_preload, link_aout_link_preload), KOBJMETHOD(linker_link_preload_finish, link_aout_link_preload_finish), KOBJMETHOD(linker_lookup_set, link_aout_lookup_set), { 0, 0 } }; static struct linker_class link_aout_class = { "a.out", link_aout_methods, sizeof(struct aout_file) }; static int relocate_file(aout_file_t af); /* * The kernel symbol table starts here. */ extern struct _dynamic __DYNAMIC; static void link_aout_init(void* arg) { #ifndef __ELF__ struct _dynamic* dp = &__DYNAMIC; #endif linker_add_class(&link_aout_class); #ifndef __ELF__ if (dp) { aout_file_t af; linker_kernel_file = linker_make_file(kernelname, &link_aout_class); if (linker_kernel_file == NULL) panic("link_aout_init: Can't create linker structures for kernel"); af = (aout_file_t) linker_kernel_file; af->address = 0; af->dynamic = dp; linker_kernel_file->address = (caddr_t) KERNBASE; linker_kernel_file->size = -(long)linker_kernel_file->address; } #endif } SYSINIT(link_aout, SI_SUB_KLD, SI_ORDER_THIRD, link_aout_init, 0); static int link_aout_link_preload(linker_class_t lc, const char* filename, linker_file_t* result) { caddr_t modptr, baseptr; char *type; struct exec *ehdr; aout_file_t af; linker_file_t lf; /* Look to see if we have the module preloaded. */ modptr = preload_search_by_name(filename); if (modptr == NULL) return ENOENT; if (((type = (char *)preload_search_info(modptr, MODINFO_TYPE)) == NULL) || strcmp(type, "a.out module") || ((baseptr = preload_search_info(modptr, MODINFO_ADDR)) == NULL) || ((ehdr = (struct exec *)preload_search_info(modptr, MODINFO_METADATA | MODINFOMD_AOUTEXEC)) == NULL)) return(0); /* we can't handle this */ /* Register with kld */ lf = linker_make_file(filename, &link_aout_class); if (lf == NULL) { return(ENOMEM); } af = (aout_file_t) lf; /* Looks like we can handle this one */ filename = preload_search_info(modptr, MODINFO_NAME); af->preloaded = 1; af->address = baseptr; /* Assume _DYNAMIC is the first data item. */ af->dynamic = (struct _dynamic*)(af->address + ehdr->a_text); if (af->dynamic->d_version != LD_VERSION_BSD) { linker_file_unload(lf); return(0); /* we can't handle this */ } af->dynamic->d_un.d_sdt = (struct section_dispatch_table *) ((char *)af->dynamic->d_un.d_sdt + (vm_offset_t)af->address); lf->address = af->address; lf->size = ehdr->a_text + ehdr->a_data + ehdr->a_bss; *result = lf; return(0); } static int link_aout_link_preload_finish(linker_file_t lf) { aout_file_t af; int error; af = (aout_file_t) lf; error = relocate_file(af); if (error) { linker_file_unload(lf); return(error); } return(0); } static int link_aout_load_file(linker_class_t lc, const char* filename, linker_file_t* result) { struct nameidata nd; struct proc* p = curproc; /* XXX */ int error = 0; int resid, flags; struct exec header; aout_file_t af; linker_file_t lf = 0; NDINIT(&nd, LOOKUP, FOLLOW, UIO_SYSSPACE, filename, p); flags = FREAD; error = vn_open(&nd, &flags, 0); if (error) return error; NDFREE(&nd, NDF_ONLY_PNBUF); /* * Read the a.out header from the file. */ error = vn_rdwr(UIO_READ, nd.ni_vp, (void*) &header, sizeof header, 0, UIO_SYSSPACE, IO_NODELOCKED, p->p_ucred, &resid, p); if (error) goto out; if (N_BADMAG(header) || !(N_GETFLAG(header) & EX_DYNAMIC)) goto out; /* * We have an a.out file, so make some space to read it in. */ lf = linker_make_file(filename, &link_aout_class); if (lf == NULL) { error = ENOMEM; goto out; } af = (aout_file_t) lf; af->address = malloc(header.a_text + header.a_data + header.a_bss, M_LINKER, M_WAITOK); /* * Read the text and data sections and zero the bss. */ error = vn_rdwr(UIO_READ, nd.ni_vp, (void*) af->address, header.a_text + header.a_data, 0, UIO_SYSSPACE, IO_NODELOCKED, p->p_ucred, &resid, p); if (error) goto out; bzero(af->address + header.a_text + header.a_data, header.a_bss); /* * Assume _DYNAMIC is the first data item. */ af->dynamic = (struct _dynamic*) (af->address + header.a_text); if (af->dynamic->d_version != LD_VERSION_BSD) { error = ENOEXEC; goto out; } af->dynamic->d_un.d_sdt = (struct section_dispatch_table *) ((char *)af->dynamic->d_un.d_sdt + (vm_offset_t)af->address); lf->address = af->address; lf->size = header.a_text + header.a_data + header.a_bss; error = linker_load_dependancies(lf); if (error) goto out; error = relocate_file(af); if (error) goto out; *result = lf; out: if (error && lf) linker_file_unload(lf); VOP_UNLOCK(nd.ni_vp, 0, p); vn_close(nd.ni_vp, FREAD, p->p_ucred, p); return error; } static void link_aout_unload_file(linker_file_t file) { aout_file_t af = (aout_file_t) file; if (af->preloaded) { link_aout_unload_preload(file); return; } if (af->address) free(af->address, M_LINKER); } static void link_aout_unload_preload(linker_file_t file) { if (file->filename) preload_delete_name(file->filename); } /* * XXX i386 dependant. */ static long read_relocation(struct relocation_info* r, char* addr) { int length = r->r_length; if (length == 0) return *(u_char*) addr; else if (length == 1) return *(u_short*) addr; else if (length == 2) return *(u_int*) addr; else printf("link_aout: unsupported relocation size %d\n", r->r_length); return 0; } static void write_relocation(struct relocation_info* r, char* addr, long value) { int length = r->r_length; if (length == 0) *(u_char*) addr = value; else if (length == 1) *(u_short*) addr = value; else if (length == 2) *(u_int*) addr = value; else printf("link_aout: unsupported relocation size %d\n", r->r_length); } #define AOUT_RELOC(af, type, off) (type*) ((af)->address + (off)) static int relocate_file(aout_file_t af) { struct relocation_info* rel; struct relocation_info* erel; struct relocation_info* r; struct nzlist* symbolbase; char* stringbase; struct nzlist* np; char* sym; long relocation; rel = AOUT_RELOC(af, struct relocation_info, LD_REL(af->dynamic)); erel = AOUT_RELOC(af, struct relocation_info, LD_REL(af->dynamic) + LD_RELSZ(af->dynamic)); symbolbase = AOUT_RELOC(af, struct nzlist, LD_SYMBOL(af->dynamic)); stringbase = AOUT_RELOC(af, char, LD_STRINGS(af->dynamic)); for (r = rel; r < erel; r++) { char* addr; if (r->r_address == 0) break; addr = AOUT_RELOC(af, char, r->r_address); if (r->r_extern) { np = &symbolbase[r->r_symbolnum]; sym = &stringbase[np->nz_strx]; if (sym[0] != '_') { printf("link_aout: bad symbol name %s\n", sym); relocation = 0; } else relocation = (intptr_t) linker_file_lookup_symbol(&af->lf, sym + 1, np->nz_type != (N_SETV+N_EXT)); if (!relocation) { printf("link_aout: symbol %s not found\n", sym); return ENOENT; } relocation += read_relocation(r, addr); if (r->r_jmptable) { printf("link_aout: can't cope with jump table relocations\n"); continue; } if (r->r_pcrel) relocation -= (intptr_t) af->address; if (r->r_copy) { printf("link_aout: can't cope with copy relocations\n"); continue; } write_relocation(r, addr, relocation); } else { write_relocation(r, addr, (intptr_t)(read_relocation(r, addr) + af->address)); } } return 0; } static long symbol_hash_value(aout_file_t af, const char* name) { long hashval; const char* p; hashval = '_'; /* fake a starting '_' for C symbols */ for (p = name; *p; p++) hashval = (hashval << 1) + *p; return (hashval & 0x7fffffff) % LD_BUCKETS(af->dynamic); } int link_aout_lookup_symbol(linker_file_t file, const char* name, c_linker_sym_t* sym) { aout_file_t af = (aout_file_t) file; long hashval; struct rrs_hash* hashbase; struct nzlist* symbolbase; char* stringbase; struct rrs_hash* hp; struct nzlist* np; char* cp; if (LD_BUCKETS(af->dynamic) == 0) return 0; hashbase = AOUT_RELOC(af, struct rrs_hash, LD_HASH(af->dynamic)); symbolbase = AOUT_RELOC(af, struct nzlist, LD_SYMBOL(af->dynamic)); stringbase = AOUT_RELOC(af, char, LD_STRINGS(af->dynamic)); restart: hashval = symbol_hash_value(af, name); hp = &hashbase[hashval]; if (hp->rh_symbolnum == -1) return ENOENT; while (hp) { np = (struct nzlist *) &symbolbase[hp->rh_symbolnum]; cp = stringbase + np->nz_strx; /* * Note: we fake the leading '_' for C symbols. */ if (cp[0] == '_' && !strcmp(cp + 1, name)) break; if (hp->rh_next == 0) hp = NULL; else hp = &hashbase[hp->rh_next]; } if (hp == NULL) /* * Not found. */ return ENOENT; /* * Check for an aliased symbol, whatever that is. */ if (np->nz_type == N_INDR+N_EXT) { name = stringbase + (++np)->nz_strx + 1; /* +1 for '_' */ goto restart; } /* * Check this is an actual definition of the symbol. */ if (np->nz_value == 0) return ENOENT; if (np->nz_type == N_UNDF+N_EXT && np->nz_value != 0) { if (np->nz_other == AUX_FUNC) /* weak function */ return ENOENT; } *sym = (linker_sym_t) np; return 0; } static int link_aout_symbol_values(linker_file_t file, c_linker_sym_t sym, linker_symval_t* symval) { aout_file_t af = (aout_file_t) file; const struct nzlist* np = (const struct nzlist*) sym; char* stringbase; long numsym = LD_STABSZ(af->dynamic) / sizeof(struct nzlist); struct nzlist *symbase; /* Is it one of ours? It could be another module... */ symbase = AOUT_RELOC(af, struct nzlist, LD_SYMBOL(af->dynamic)); if (np < symbase) return ENOENT; if ((np - symbase) > numsym) return ENOENT; stringbase = AOUT_RELOC(af, char, LD_STRINGS(af->dynamic)); symval->name = stringbase + np->nz_strx + 1; /* +1 for '_' */ if (np->nz_type == N_UNDF+N_EXT && np->nz_value != 0) { symval->value = 0; symval->size = np->nz_value; } else { symval->value = AOUT_RELOC(af, char, np->nz_value); symval->size = np->nz_size; } return 0; } static int link_aout_search_symbol(linker_file_t lf, caddr_t value, c_linker_sym_t* sym, long* diffp) { aout_file_t af = (aout_file_t) lf; u_long off = (uintptr_t) (void *) value; u_long diff = off; u_long sp_nz_value; struct nzlist* sp; struct nzlist* ep; struct nzlist* best = 0; for (sp = AOUT_RELOC(af, struct nzlist, LD_SYMBOL(af->dynamic)), ep = (struct nzlist *) ((caddr_t) sp + LD_STABSZ(af->dynamic)); sp < ep; sp++) { if (sp->nz_name == 0) continue; sp_nz_value = sp->nz_value + (uintptr_t) (void *) af->address; if (off >= sp_nz_value) { if (off - sp_nz_value < diff) { diff = off - sp_nz_value; best = sp; if (diff == 0) break; } else if (off - sp_nz_value == diff) { best = sp; } } } if (best == 0) *diffp = off; else *diffp = diff; *sym = (linker_sym_t) best; return 0; } /* * Look up a linker set on an a.out + gnu LD system. */ struct generic_linker_set { int ls_length; void *ls_items[1]; }; static int link_aout_lookup_set(linker_file_t lf, const char *name, void ***startp, void ***stopp, int *countp) { c_linker_sym_t sym; linker_symval_t symval; void **start, **stop; int error, count; struct generic_linker_set *setp; error = link_aout_lookup_symbol(lf, name, &sym); if (error) return error; link_aout_symbol_values(lf, sym, &symval); if (symval.value == 0) return ESRCH; setp = (struct generic_linker_set *)symval.value; count = setp->ls_length; start = &setp->ls_items[0]; stop = &setp->ls_items[count]; if (startp) *startp = start; if (stopp) *stopp = stop; if (countp) *countp = count; return 0; } #endif /* !__alpha__ */