/*- * Copyright (c) 1997 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: src/sys/kern/link_aout.c,v 1.26 1999/12/24 15:33:36 bde Exp $ * $DragonFly: src/sys/kern/link_aout.c,v 1.21 2006/12/23 00:35:04 swildner Exp $ */ #define FREEBSD_AOUT 1 #include #include #include #include #include #include #include #include #include #include #include #ifndef __ELF__ #include #include #include #endif #include #include #include #define _AOUT_INCLUDE_ #include #include static int link_aout_load_module(const char*, linker_file_t*); static int link_aout_load_file(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_module(linker_file_t); static int link_aout_lookup_set(linker_file_t lf, const char *name, void ***startp, void ***stopp, int *countp); static struct linker_class_ops link_aout_class_ops = { link_aout_load_module, }; static struct linker_file_ops link_aout_file_ops = { link_aout_lookup_symbol, link_aout_symbol_values, link_aout_search_symbol, link_aout_unload_file, link_aout_lookup_set }; static struct linker_file_ops link_aout_module_ops = { link_aout_lookup_symbol, link_aout_symbol_values, link_aout_search_symbol, link_aout_unload_module, link_aout_lookup_set }; typedef struct aout_file { char* address; /* Load address */ struct _dynamic* dynamic; /* Symbol table etc. */ } *aout_file_t; static int load_dependancies(linker_file_t lf); static int relocate_file(linker_file_t lf); /* * 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("a.out", NULL, &link_aout_class_ops); #ifndef __ELF__ if (dp) { aout_file_t af; af = kmalloc(sizeof(struct aout_file), M_LINKER, M_NOWAIT); if (af == NULL) panic("link_aout_init: Can't create linker structures for kernel"); bzero(af, sizeof(*af)); af->address = 0; af->dynamic = dp; linker_kernel_file = linker_make_file(kernelname, af, &link_aout_file_ops); if (linker_kernel_file == NULL) panic("link_aout_init: Can't create linker structures for kernel"); linker_kernel_file->address = (caddr_t) KERNBASE; linker_kernel_file->size = -(long)linker_kernel_file->address; linker_current_file = linker_kernel_file; linker_kernel_file->flags |= LINKER_FILE_LINKED; } #endif } SYSINIT(link_aout, SI_SUB_KLD, SI_ORDER_THIRD, link_aout_init, 0); static int link_aout_load_module(const char* filename, linker_file_t* result) { caddr_t modptr, baseptr; char *type; struct exec *ehdr; aout_file_t af; linker_file_t lf; int error; /* Look to see if we have the module preloaded. */ if ((modptr = preload_search_by_name(filename)) == NULL) return(link_aout_load_file(filename, result)); /* It's preloaded, check we can handle it and collect information. */ 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 */ /* Looks like we can handle this one */ af = kmalloc(sizeof(struct aout_file), M_LINKER, M_WAITOK); bzero(af, sizeof(*af)); 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) { kfree(af, M_LINKER); 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); /* Register with kld */ lf = linker_make_file(filename, af, &link_aout_module_ops); if (lf == NULL) { kfree(af, M_LINKER); return(ENOMEM); } lf->address = af->address; lf->size = ehdr->a_text + ehdr->a_data + ehdr->a_bss; /* Try to load dependancies */ if (((error = load_dependancies(lf)) != 0) || ((error = relocate_file(lf)) != 0)) { linker_file_unload(lf); return(error); } lf->flags |= LINKER_FILE_LINKED; *result = lf; return(0); } static int link_aout_load_file(const char* filename, linker_file_t* result) { struct nlookupdata nd; struct thread *td = curthread; struct proc *p = td->td_proc; struct vnode *vp; int error = 0; int resid; struct exec header; aout_file_t af; linker_file_t lf; char *pathname; KKASSERT(p != NULL); if (p->p_ucred == NULL) { kprintf("link_aout_load_file: cannot load '%s' from filesystem" " this early\n", filename); return ENOENT; } pathname = linker_search_path(filename); if (pathname == NULL) return ENOENT; error = nlookup_init(&nd, pathname, UIO_SYSSPACE, NLC_FOLLOW|NLC_LOCKVP); if (error == 0) error = vn_open(&nd, NULL, FREAD, 0); kfree(pathname, M_LINKER); if (error) { nlookup_done(&nd); return error; } vp = nd.nl_open_vp; nd.nl_open_vp = NULL; nlookup_done(&nd); /* * Read the a.out header from the file. */ error = vn_rdwr(UIO_READ, vp, (void*) &header, sizeof header, 0, UIO_SYSSPACE, IO_NODELOCKED, p->p_ucred, &resid); 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. */ af = kmalloc(sizeof(struct aout_file), M_LINKER, M_WAITOK); bzero(af, sizeof(*af)); af->address = kmalloc(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, vp, (void*) af->address, header.a_text + header.a_data, 0, UIO_SYSSPACE, IO_NODELOCKED, p->p_ucred, &resid); 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) { kfree(af->address, M_LINKER); kfree(af, M_LINKER); 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 = linker_make_file(filename, af, &link_aout_file_ops); if (lf == NULL) { kfree(af->address, M_LINKER); kfree(af, M_LINKER); error = ENOMEM; goto out; } lf->address = af->address; lf->size = header.a_text + header.a_data + header.a_bss; if ((error = load_dependancies(lf)) != 0 || (error = relocate_file(lf)) != 0) { linker_file_unload(lf); goto out; } lf->flags |= LINKER_FILE_LINKED; *result = lf; out: vn_unlock(vp); vn_close(vp, FREAD); return error; } static void link_aout_unload_file(linker_file_t file) { aout_file_t af = file->priv; if (af) { if (af->address) kfree(af->address, M_LINKER); kfree(af, M_LINKER); } } static void link_aout_unload_module(linker_file_t file) { aout_file_t af = file->priv; if (af) kfree(af, M_LINKER); if (file->filename) preload_delete_name(file->filename); } #define AOUT_RELOC(af, type, off) (type*) ((af)->address + (off)) static int load_dependancies(linker_file_t lf) { aout_file_t af = lf->priv; linker_file_t lfdep; long off; struct sod* sodp; char* name; char* filename = 0; int error = 0; /* * All files are dependant on /kernel. */ if (linker_kernel_file) { linker_kernel_file->refs++; linker_file_add_dependancy(lf, linker_kernel_file); } off = LD_NEED(af->dynamic); /* * Load the dependancies. */ while (off != 0) { sodp = AOUT_RELOC(af, struct sod, off); name = AOUT_RELOC(af, char, sodp->sod_name); error = linker_load_file(name, &lfdep); if (error) goto out; error = linker_file_add_dependancy(lf, lfdep); if (error) goto out; off = sodp->sod_next; } out: if (filename) kfree(filename, M_TEMP); return error; } /* * 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 kprintf("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 kprintf("link_aout: unsupported relocation size %d\n", r->r_length); } static int relocate_file(linker_file_t lf) { aout_file_t af = lf->priv; 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] != '_') { kprintf("link_aout: bad symbol name %s\n", sym); kprintf("link_aout: symbol %s not found\n", sym); return ENOENT; } else { if (linker_file_lookup_symbol(lf, sym + 1, (np->nz_type != (N_SETV+N_EXT)), (caddr_t *)&relocation)) { kprintf("link_aout: symbol %s not found\n", sym); return ENOENT; } } relocation += read_relocation(r, addr); if (r->r_jmptable) { kprintf("link_aout: can't cope with jump table relocations\n"); continue; } if (r->r_pcrel) relocation -= (intptr_t) af->address; if (r->r_copy) { kprintf("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 = file->priv; 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 = file->priv; 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 = lf->priv; 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; }