/* Create a section containing all the symbols for an object. Copyright 2000 Keith Owens April 2000 This file was hacked from the Linux modutils. It performs the same function as the normal kallsyms but has been hardwired to assume a pcc bit target for the kernel, no matter which build machine is used. This will only work if the build machine is little endian. It is a temporary measure to allow i386->ppc cross compile. modutils 2.5 will eventually have full cross compile support. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include /*======================================================================*/ /* Machine-specific elf macros for the PowerPC. */ #define ELFCLASSM ELFCLASS32 #define ELFDATAM ELFDATA2MSB #define MATCH_MACHINE(x) (x == EM_PPC) #define SHT_RELM SHT_RELA #define Elf32_RelM Elf32_Rela #ifndef ElfW # if ELFCLASSM == ELFCLASS32 # define ElfW(x) Elf32_ ## x # define ELFW(x) ELF32_ ## x # else # define ElfW(x) Elf64_ ## x # define ELFW(x) ELF64_ ## x # endif #endif /*======================================================================*/ /* Format of data in the kallsyms section. * Most of the fields are small numbers but the total size and all * offsets can be large so use the 32/64 bit types for these fields. * * Do not use sizeof() on these structures, modutils may be using extra * fields. Instead use the size fields in the header to access the * other bits of data. */ struct kallsyms_header { int size; /* Size of this header */ ElfW(Word) total_size; /* Total size of kallsyms data */ int sections; /* Number of section entries */ ElfW(Off) section_off; /* Offset to first section entry */ int section_size; /* Size of one section entry */ int symbols; /* Number of symbol entries */ ElfW(Off) symbol_off; /* Offset to first symbol entry */ int symbol_size; /* Size of one symbol entry */ ElfW(Off) string_off; /* Offset to first string */ ElfW(Addr) start; /* Start address of first section */ ElfW(Addr) end; /* End address of last section */ }; struct kallsyms_section { ElfW(Addr) start; /* Start address of section */ ElfW(Word) size; /* Size of this section */ ElfW(Off) name_off; /* Offset to section name */ ElfW(Word) flags; /* Flags from section */ }; struct kallsyms_symbol { ElfW(Off) section_off; /* Offset to section that owns this symbol */ ElfW(Addr) symbol_addr; /* Address of symbol */ ElfW(Off) name_off; /* Offset to symbol name */ }; #define KALLSYMS_SEC_NAME "__kallsyms" #define KALLSYMS_IDX 2 /* obj_kallsyms creates kallsyms as section 2 */ /*======================================================================*/ #define MATCH_MACHINE(x) (x == EM_PPC) #define MODUTILS_VERSION "kallsyms i386->ppc" struct obj_string_patch_struct; struct obj_symbol_patch_struct; struct obj_section { ElfW(Shdr) header; const char *name; char *contents; struct obj_section *load_next; int idx; }; struct obj_symbol { struct obj_symbol *next; /* hash table link */ const char *name; long value; unsigned long size; int secidx; /* the defining section index/module */ int info; int ksymidx; /* for export to the kernel symtab */ int r_type; /* relocation type */ }; /* Hardcode the hash table size. We shouldn't be needing so many symbols that we begin to degrade performance, and we get a big win by giving the compiler a constant divisor. */ #define HASH_BUCKETS 521 struct obj_file { ElfW(Ehdr) header; ElfW(Addr) baseaddr; struct obj_section **sections; struct obj_section *load_order; struct obj_section **load_order_search_start; struct obj_string_patch_struct *string_patches; struct obj_symbol_patch_struct *symbol_patches; int (*symbol_cmp)(const char *, const char *); unsigned long (*symbol_hash)(const char *); unsigned long local_symtab_size; struct obj_symbol **local_symtab; struct obj_symbol *symtab[HASH_BUCKETS]; const char *filename; char *persist; }; enum obj_reloc { obj_reloc_ok, obj_reloc_overflow, obj_reloc_dangerous, obj_reloc_unhandled, obj_reloc_constant_gp }; /* Error logging */ int errors; const char *error_file; void error(const char *fmt, ...) __attribute__((format(printf, 1, 2))); void error(const char *fmt, ...) { va_list args; if (error_file) fprintf(stderr, "%s: ", error_file); va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); putc('\n', stderr); errors++; } /*======================================================================*/ static void * xmalloc(size_t size) { void *ptr = malloc(size); if (!ptr) { error("Out of memory"); exit(1); } return ptr; } /*======================================================================*/ static void * xrealloc(void *old, size_t size) { void *ptr = realloc(old, size); if (!ptr) { error("Out of memory"); exit(1); } return ptr; } /*======================================================================*/ static char * xstrdup(const char *s) { char *n = strdup(s); if (!n) { error("Out of memory"); exit(1); } return n; } #ifndef FALSE #define FALSE 0 #endif #ifndef TRUE #define TRUE ~FALSE #endif /*======================================================================*/ struct ppc_plt_entry { struct ppc_plt_entry *next; ElfW(Addr) addend; int offset; int inited; }; struct ppc_file { struct obj_file file; struct obj_section *plt; }; struct ppc_symbol { struct obj_symbol sym; struct ppc_plt_entry *plt_entries; }; struct obj_file * arch_new_file (void) { struct ppc_file *f; f = xmalloc(sizeof(struct ppc_file)); f->plt = NULL; return &f->file; } struct obj_section * arch_new_section (void) { return xmalloc(sizeof(struct obj_section)); } struct obj_symbol * arch_new_symbol (void) { struct ppc_symbol *p; p = xmalloc(sizeof(struct ppc_symbol)); p->plt_entries = NULL; return &p->sym; } #if defined (ARCH_alpha) #define ARCH_SHF_SHORT SHF_ALPHA_GPREL #elif defined (ARCH_ia64) #define ARCH_SHF_SHORT SHF_IA_64_SHORT #else #define ARCH_SHF_SHORT 0 #endif static int obj_load_order_prio(struct obj_section *a) { unsigned long af, ac; af = a->header.sh_flags; ac = 0; if (a->name[0] != '.' || strlen(a->name) != 10 || strcmp(a->name + 5, ".init")) ac |= 64; if (af & SHF_ALLOC) ac |= 32; if (af & SHF_EXECINSTR) ac |= 16; if (!(af & SHF_WRITE)) ac |= 8; if (a->header.sh_type != SHT_NOBITS) ac |= 4; /* Desired order is P S AC & 7 .data 1 0 4 .got 1 1 3 .sdata 1 1 1 .sbss 0 1 1 .bss 0 0 0 */ if (strcmp (a->name, ".got") == 0) ac |= 2; if (af & ARCH_SHF_SHORT) ac = (ac & ~4) | 1; return ac; } static void obj_insert_section_load_order (struct obj_file *f, struct obj_section *sec) { struct obj_section **p; int prio = obj_load_order_prio(sec); for (p = f->load_order_search_start; *p ; p = &(*p)->load_next) if (obj_load_order_prio(*p) < prio) break; sec->load_next = *p; *p = sec; } /*======================================================================*/ static int arch_load_proc_section(struct obj_section *sec, int fp) { /* Assume it's just a debugging section that we can safely ignore ... */ sec->contents = NULL; return 0; } /*======================================================================*/ /* Standard ELF hash function. */ static inline unsigned long obj_elf_hash_n(const char *name, unsigned long n) { unsigned long h = 0; unsigned long g; unsigned char ch; while (n > 0) { ch = *name++; h = (h << 4) + ch; if ((g = (h & 0xf0000000)) != 0) { h ^= g >> 24; h &= ~g; } n--; } return h; } static unsigned long obj_elf_hash (const char *name) { return obj_elf_hash_n(name, strlen(name)); } static struct obj_symbol * obj_add_symbol (struct obj_file *f, const char *name, unsigned long symidx, int info, int secidx, ElfW(Addr) value, unsigned long size) { struct obj_symbol *sym; unsigned long hash = f->symbol_hash(name) % HASH_BUCKETS; int n_type = ELFW(ST_TYPE)(info); int n_binding = ELFW(ST_BIND)(info); for (sym = f->symtab[hash]; sym; sym = sym->next) if (f->symbol_cmp(sym->name, name) == 0) { int o_secidx = sym->secidx; int o_info = sym->info; int o_type = ELFW(ST_TYPE)(o_info); int o_binding = ELFW(ST_BIND)(o_info); /* A redefinition! Is it legal? */ if (secidx == SHN_UNDEF) return sym; else if (o_secidx == SHN_UNDEF) goto found; else if (n_binding == STB_GLOBAL && o_binding == STB_LOCAL) { /* Cope with local and global symbols of the same name in the same object file, as might have been created by ld -r. The only reason locals are now seen at this level at all is so that we can do semi-sensible things with parameters. */ struct obj_symbol *nsym, **p; nsym = arch_new_symbol(); nsym->next = sym->next; nsym->ksymidx = -1; /* Excise the old (local) symbol from the hash chain. */ for (p = &f->symtab[hash]; *p != sym; p = &(*p)->next) continue; *p = sym = nsym; goto found; } else if (n_binding == STB_LOCAL) { /* Another symbol of the same name has already been defined. Just add this to the local table. */ sym = arch_new_symbol(); sym->next = NULL; sym->ksymidx = -1; f->local_symtab[symidx] = sym; goto found; } else if (n_binding == STB_WEAK) return sym; else if (o_binding == STB_WEAK) goto found; /* Don't unify COMMON symbols with object types the programmer doesn't expect. */ else if (secidx == SHN_COMMON && (o_type == STT_NOTYPE || o_type == STT_OBJECT)) return sym; else if (o_secidx == SHN_COMMON && (n_type == STT_NOTYPE || n_type == STT_OBJECT)) goto found; else { /* Don't report an error if the symbol is coming from the kernel or some external module. */ if (secidx <= SHN_HIRESERVE) error("%s multiply defined", name); return sym; } } /* Completely new symbol. */ sym = arch_new_symbol(); sym->next = f->symtab[hash]; f->symtab[hash] = sym; sym->ksymidx = -1; if (ELFW(ST_BIND)(info) == STB_LOCAL && symidx != -1) { if (symidx >= f->local_symtab_size) error("local symbol %s with index %ld exceeds local_symtab_size %ld", name, (long) symidx, (long) f->local_symtab_size); else f->local_symtab[symidx] = sym; } found: sym->name = name; sym->value = value; sym->size = size; sym->secidx = secidx; sym->info = info; sym->r_type = 0; /* should be R_arch_NONE for all arch */ return sym; } static struct obj_symbol * obj_find_symbol (struct obj_file *f, const char *name) { struct obj_symbol *sym; unsigned long hash = f->symbol_hash(name) % HASH_BUCKETS; for (sym = f->symtab[hash]; sym; sym = sym->next) if (f->symbol_cmp(sym->name, name) == 0) return sym; return NULL; } /* Standard method of finding relocation symbols, sets isym */ #define obj_find_relsym(isym, f, find, rel, symtab, strtab) \ { \ unsigned long symndx = ELFW(R_SYM)((rel)->r_info); \ ElfW(Sym) *extsym = (symtab)+symndx; \ if (ELFW(ST_BIND)(extsym->st_info) == STB_LOCAL) { \ isym = (typeof(isym)) (f)->local_symtab[symndx]; \ } \ else { \ const char *name; \ if (extsym->st_name) \ name = (strtab) + extsym->st_name; \ else \ name = (f)->sections[extsym->st_shndx]->name; \ isym = (typeof(isym)) obj_find_symbol((find), name); \ } \ } /*======================================================================*/ static void kallsyms_usage (void) { fputs("Usage:\n" "kallsyms [-Vh] file\n" "\n" " -V, --version Show version\n" " -h, --help Print this message.\n" " file Filename of a kernel (e.g. vmlinux)\n" ,stderr); } /*======================================================================*/ static void fix_Ehdr(ElfW(Ehdr) *header) { header->e_type = ntohs(header->e_type); header->e_machine = ntohs(header->e_machine); header->e_version = ntohl(header->e_version); header->e_entry = ntohl(header->e_entry); header->e_phoff = ntohl(header->e_phoff); header->e_shoff = ntohl(header->e_shoff); header->e_flags = ntohl(header->e_flags); header->e_ehsize = ntohs(header->e_ehsize); header->e_phentsize = ntohs(header->e_phentsize); header->e_phnum = ntohs(header->e_phnum); header->e_shentsize = ntohs(header->e_shentsize); header->e_shnum = ntohs(header->e_shnum); header->e_shstrndx = ntohs(header->e_shstrndx); } static void unfix_Ehdr(ElfW(Ehdr) *header) { header->e_type = htons(header->e_type); header->e_machine = htons(header->e_machine); header->e_version = htonl(header->e_version); header->e_entry = htonl(header->e_entry); header->e_phoff = htonl(header->e_phoff); header->e_shoff = htonl(header->e_shoff); header->e_flags = htonl(header->e_flags); header->e_ehsize = htons(header->e_ehsize); header->e_phentsize = htons(header->e_phentsize); header->e_phnum = htons(header->e_phnum); header->e_shentsize = htons(header->e_shentsize); header->e_shnum = htons(header->e_shnum); header->e_shstrndx = htons(header->e_shstrndx); } static void fix_Shdr(ElfW(Shdr) *header) { header->sh_name = ntohl(header->sh_name); header->sh_type = ntohl(header->sh_type); header->sh_flags = ntohl(header->sh_flags); header->sh_addr = ntohl(header->sh_addr); header->sh_offset = ntohl(header->sh_offset); header->sh_size = ntohl(header->sh_size); header->sh_link = ntohl(header->sh_link); header->sh_info = ntohl(header->sh_info); header->sh_addralign = ntohl(header->sh_addralign); header->sh_entsize = ntohl(header->sh_entsize); } static void unfix_Shdr(ElfW(Shdr) *header) { header->sh_name = htonl(header->sh_name); header->sh_type = htonl(header->sh_type); header->sh_flags = htonl(header->sh_flags); header->sh_addr = htonl(header->sh_addr); header->sh_offset = htonl(header->sh_offset); header->sh_size = htonl(header->sh_size); header->sh_link = htonl(header->sh_link); header->sh_info = htonl(header->sh_info); header->sh_addralign = htonl(header->sh_addralign); header->sh_entsize = htonl(header->sh_entsize); } static void fix_Sym(ElfW(Sym) *sym) { sym->st_name = ntohl(sym->st_name); sym->st_value = ntohl(sym->st_value); sym->st_size = ntohl(sym->st_size); sym->st_shndx = ntohs(sym->st_shndx); } static void unfix_Sym(ElfW(Sym) *sym) { sym->st_name = htonl(sym->st_name); sym->st_value = htonl(sym->st_value); sym->st_size = htonl(sym->st_size); sym->st_shndx = htons(sym->st_shndx); } static void fix_RelM(ElfW(RelM) *rel) { rel->r_offset = ntohl(rel->r_offset); rel->r_info = ntohl(rel->r_info); #if Elf32_RelM == Elf32_Rela rel->r_addend = ntohl(rel->r_addend); #endif } static void unfix_RelM(ElfW(RelM) *rel) { rel->r_offset = htonl(rel->r_offset); rel->r_info = htonl(rel->r_info); #if Elf32_RelM == Elf32_Rela rel->r_addend = htonl(rel->r_addend); #endif } static void unfix_kallsyms_header(struct kallsyms_header *hdr) { hdr->size = htonl(hdr->size); hdr->total_size = htonl(hdr->total_size); hdr->sections = htonl(hdr->sections); hdr->section_off = htonl(hdr->section_off); hdr->section_size = htonl(hdr->section_size); hdr->symbols = htonl(hdr->symbols); hdr->symbol_off = htonl(hdr->symbol_off); hdr->symbol_size = htonl(hdr->symbol_size); hdr->string_off = htonl(hdr->string_off); hdr->start = htonl(hdr->start); hdr->end = htonl(hdr->end); } static void unfix_kallsyms_section(struct kallsyms_section *sec) { sec->start = htonl(sec->start); sec->size = htonl(sec->size); sec->name_off = htonl(sec->name_off); sec->flags = htonl(sec->flags); } static void unfix_kallsyms_symbols(struct kallsyms_symbol *syms, int count) { int i; for (i = 0; i < count; i++) { syms[i].section_off = htonl(syms[i].section_off); syms[i].symbol_addr = htonl(syms[i].symbol_addr); syms[i].name_off = htonl(syms[i].name_off); } } static struct obj_file * obj_load (int fp, Elf32_Half e_type, const char *filename) { struct obj_file *f; ElfW(Shdr) *section_headers; int shnum, i; char *shstrtab; /* Read the file header. */ f = arch_new_file(); memset(f, 0, sizeof(*f)); f->symbol_cmp = strcmp; f->symbol_hash = obj_elf_hash; f->load_order_search_start = &f->load_order; lseek(fp, 0, SEEK_SET); if (read(fp, &f->header, sizeof(f->header)) != sizeof(f->header)) { error("error reading ELF header %s: %m", filename); return NULL; } fix_Ehdr(&f->header); if (f->header.e_ident[EI_MAG0] != ELFMAG0 || f->header.e_ident[EI_MAG1] != ELFMAG1 || f->header.e_ident[EI_MAG2] != ELFMAG2 || f->header.e_ident[EI_MAG3] != ELFMAG3) { error("%s is not an ELF file", filename); return NULL; } if (f->header.e_ident[EI_CLASS] != ELFCLASSM || f->header.e_ident[EI_DATA] != ELFDATAM || f->header.e_ident[EI_VERSION] != EV_CURRENT || !MATCH_MACHINE(f->header.e_machine)) { error("ELF file %s not for this architecture", filename); return NULL; } if (f->header.e_type != e_type && e_type != ET_NONE) { switch (e_type) { case ET_REL: error("ELF file %s not a relocatable object", filename); break; case ET_EXEC: error("ELF file %s not an executable object", filename); break; default: error("ELF file %s has wrong type, expecting %d got %d", filename, e_type, f->header.e_type); break; } return NULL; } /* Read the section headers. */ if (f->header.e_shentsize != sizeof(ElfW(Shdr))) { error("section header size mismatch %s: %lu != %lu", filename, (unsigned long)f->header.e_shentsize, (unsigned long)sizeof(ElfW(Shdr))); return NULL; } shnum = f->header.e_shnum; f->sections = xmalloc(sizeof(struct obj_section *) * shnum); memset(f->sections, 0, sizeof(struct obj_section *) * shnum); section_headers = alloca(sizeof(ElfW(Shdr)) * shnum); lseek(fp, f->header.e_shoff, SEEK_SET); if (read(fp, section_headers, sizeof(ElfW(Shdr))*shnum) != sizeof(ElfW(Shdr))*shnum) { error("error reading ELF section headers %s: %m", filename); return NULL; } /* Read the section data. */ for (i = 0; i < shnum; ++i) { struct obj_section *sec; f->sections[i] = sec = arch_new_section(); memset(sec, 0, sizeof(*sec)); sec->header = section_headers[i]; fix_Shdr(&sec->header); sec->idx = i; switch (sec->header.sh_type) { case SHT_NULL: case SHT_NOTE: case SHT_NOBITS: /* ignore */ break; case SHT_PROGBITS: case SHT_SYMTAB: case SHT_STRTAB: case SHT_RELM: if (sec->header.sh_size > 0) { sec->contents = xmalloc(sec->header.sh_size); lseek(fp, sec->header.sh_offset, SEEK_SET); if (read(fp, sec->contents, sec->header.sh_size) != sec->header.sh_size) { error("error reading ELF section data %s: %m", filename); return NULL; } } else sec->contents = NULL; break; #if SHT_RELM == SHT_REL case SHT_RELA: if (sec->header.sh_size) { error("RELA relocations not supported on this architecture %s", filename); return NULL; } break; #else case SHT_REL: if (sec->header.sh_size) { error("REL relocations not supported on this architecture %s", filename); return NULL; } break; #endif default: if (sec->header.sh_type >= SHT_LOPROC) { if (arch_load_proc_section(sec, fp) < 0) return NULL; break; } error("can't handle sections of type %ld %s", (long)sec->header.sh_type, filename); return NULL; } } /* Do what sort of interpretation as needed by each section. */ shstrtab = f->sections[f->header.e_shstrndx]->contents; for (i = 0; i < shnum; ++i) { struct obj_section *sec = f->sections[i]; sec->name = shstrtab + sec->header.sh_name; } for (i = 0; i < shnum; ++i) { struct obj_section *sec = f->sections[i]; /* .modinfo and .modstring should be contents only but gcc has no * attribute for that. The kernel may have marked these sections as * ALLOC, ignore the allocate bit. */ if (strcmp(sec->name, ".modinfo") == 0 || strcmp(sec->name, ".modstring") == 0) sec->header.sh_flags &= ~SHF_ALLOC; if (sec->header.sh_flags & SHF_ALLOC) obj_insert_section_load_order(f, sec); switch (sec->header.sh_type) { case SHT_SYMTAB: { unsigned long nsym, j; char *strtab; ElfW(Sym) *sym; if (sec->header.sh_entsize != sizeof(ElfW(Sym))) { error("symbol size mismatch %s: %lu != %lu", filename, (unsigned long)sec->header.sh_entsize, (unsigned long)sizeof(ElfW(Sym))); return NULL; } nsym = sec->header.sh_size / sizeof(ElfW(Sym)); strtab = f->sections[sec->header.sh_link]->contents; sym = (ElfW(Sym) *) sec->contents; /* Allocate space for a table of local symbols. */ j = f->local_symtab_size = sec->header.sh_info; f->local_symtab = xmalloc(j *= sizeof(struct obj_symbol *)); memset(f->local_symtab, 0, j); /* Insert all symbols into the hash table. */ for (j = 1, ++sym; j < nsym; ++j, ++sym) { const char *name; fix_Sym(sym); if (sym->st_name) name = strtab+sym->st_name; else name = f->sections[sym->st_shndx]->name; obj_add_symbol(f, name, j, sym->st_info, sym->st_shndx, sym->st_value, sym->st_size); unfix_Sym(sym); } } break; } } /* second pass to add relocation data to symbols */ for (i = 0; i < shnum; ++i) { struct obj_section *sec = f->sections[i]; switch (sec->header.sh_type) { case SHT_RELM: { unsigned long nrel, j, nsyms; ElfW(RelM) *rel; struct obj_section *symtab; char *strtab; if (sec->header.sh_entsize != sizeof(ElfW(RelM))) { error("relocation entry size mismatch %s: %lu != %lu", filename, (unsigned long)sec->header.sh_entsize, (unsigned long)sizeof(ElfW(RelM))); return NULL; } nrel = sec->header.sh_size / sizeof(ElfW(RelM)); rel = (ElfW(RelM) *) sec->contents; fix_RelM(rel); symtab = f->sections[sec->header.sh_link]; nsyms = symtab->header.sh_size / symtab->header.sh_entsize; strtab = f->sections[symtab->header.sh_link]->contents; /* Save the relocate type in each symbol entry. */ for (j = 0; j < nrel; ++j, ++rel) { struct obj_symbol *intsym; unsigned long symndx; symndx = ELFW(R_SYM)(rel->r_info); if (symndx) { if (symndx >= nsyms) { error("%s: Bad symbol index: %08lx >= %08lx", filename, symndx, nsyms); continue; } obj_find_relsym(intsym, f, f, rel, (ElfW(Sym) *)(symtab->contents), strtab); intsym->r_type = ELFW(R_TYPE)(rel->r_info); } } unfix_RelM(rel); } break; } } f->filename = xstrdup(filename); return f; } /*======================================================================*/ #define EXPAND_BY 4096 /* Arbitrary */ /* Append a string to the big list of strings */ static void append_string (const char *s, char **strings, ElfW(Word) *strings_size, ElfW(Word) *strings_left) { int l = strlen(s) + 1; while (l > *strings_left) { *strings = xrealloc(*strings, *strings_size += EXPAND_BY); *strings_left += EXPAND_BY; } memcpy((char *)*strings+*strings_size-*strings_left, s, l); *strings_left -= l; } /* Append a symbol to the big list of symbols */ static void append_symbol (const struct kallsyms_symbol *s, struct kallsyms_symbol **symbols, ElfW(Word) *symbols_size, ElfW(Word) *symbols_left) { int l = sizeof(*s); while (l > *symbols_left) { *symbols = xrealloc(*symbols, *symbols_size += EXPAND_BY); *symbols_left += EXPAND_BY; } memcpy((char *)*symbols+*symbols_size-*symbols_left, s, l); *symbols_left -= l; } /* qsort compare routine to sort symbols */ static const char *sym_strings; static int symbol_compare (const void *a, const void *b) { struct kallsyms_symbol *c = (struct kallsyms_symbol *) a; struct kallsyms_symbol *d = (struct kallsyms_symbol *) b; if (c->symbol_addr > d->symbol_addr) return(1); if (c->symbol_addr < d->symbol_addr) return(-1); return(strcmp(sym_strings+c->name_off, sym_strings+d->name_off)); } /* Extract all symbols from the input obj_file, ignore ones that are * no use for debugging, build an output obj_file containing only the * kallsyms section. * * The kallsyms section is a bit unusual. It deliberately has no * relocatable data, all "pointers" are represented as byte offsets * into the the section. This means it can be stored anywhere without * relocation problems. In particular it can be stored within a kernel * image, it can be stored separately from the kernel image, it can be * appended to a module just before loading, it can be stored in a * separate area etc. * * Format of the kallsyms section. * * Header: * Size of header. * Total size of kallsyms data, including strings. * Number of loaded sections. * Offset to first section entry from start of header. * Size of each section entry, excluding the name string. * Number of symbols. * Offset to first symbol entry from start of header. * Size of each symbol entry, excluding the name string. * * Section entry - one per loaded section. * Start of section[1]. * Size of section. * Offset to name of section, from start of strings. * Section flags. * * Symbol entry - one per symbol in the input file[2]. * Offset of section that owns this symbol, from start of section data. * Address of symbol within the real section[1]. * Offset to name of symbol, from start of strings. * * Notes: [1] This is an exception to the "represent pointers as * offsets" rule, it is a value, not an offset. The start * address of a section or a symbol is extracted from the * obj_file data which may contain absolute or relocatable * addresses. If the addresses are relocatable then the * caller must adjust the section and/or symbol entries in * kallsyms after relocation. * [2] Only symbols that fall within loaded sections are stored. */ static int obj_kallsyms (struct obj_file *fin, struct obj_file **fout_result) { struct obj_file *fout; int i, loaded = 0, *fin_to_allsym_map; struct obj_section *isec, *osec; struct kallsyms_header *a_hdr; struct kallsyms_section *a_sec; ElfW(Off) sec_off; struct kallsyms_symbol *symbols = NULL, a_sym; ElfW(Word) symbols_size = 0, symbols_left = 0; char *strings = NULL, *p; ElfW(Word) strings_size = 0, strings_left = 0; ElfW(Off) file_offset; static char strtab[] = "\000" KALLSYMS_SEC_NAME; /* Create the kallsyms section. */ fout = arch_new_file(); memset(fout, 0, sizeof(*fout)); fout->symbol_cmp = strcmp; fout->symbol_hash = obj_elf_hash; fout->load_order_search_start = &fout->load_order; /* Copy file characteristics from input file and modify to suit */ memcpy(&fout->header, &fin->header, sizeof(fout->header)); fout->header.e_type = ET_REL; /* Output is relocatable */ fout->header.e_entry = 0; /* No entry point */ fout->header.e_phoff = 0; /* No program header */ file_offset = sizeof(fout->header); /* Step over Elf header */ fout->header.e_shoff = file_offset; /* Section headers next */ fout->header.e_phentsize = 0; /* No program header */ fout->header.e_phnum = 0; /* No program header */ fout->header.e_shnum = KALLSYMS_IDX+1; /* Initial, strtab, kallsyms */ fout->header.e_shstrndx = KALLSYMS_IDX-1; /* strtab */ file_offset += fout->header.e_shentsize * fout->header.e_shnum; /* Populate the section data for kallsyms itself */ fout->sections = xmalloc(sizeof(*(fout->sections))*fout->header.e_shnum); memset(fout->sections, 0, sizeof(*(fout->sections))*fout->header.e_shnum); fout->sections[0] = osec = arch_new_section(); memset(osec, 0, sizeof(*osec)); osec->header.sh_type = SHT_NULL; osec->header.sh_link = SHN_UNDEF; fout->sections[KALLSYMS_IDX-1] = osec = arch_new_section(); memset(osec, 0, sizeof(*osec)); osec->name = ".strtab"; osec->header.sh_type = SHT_STRTAB; osec->header.sh_link = SHN_UNDEF; osec->header.sh_offset = file_offset; osec->header.sh_size = sizeof(strtab); osec->contents = xmalloc(sizeof(strtab)); memcpy(osec->contents, strtab, sizeof(strtab)); file_offset += osec->header.sh_size; fout->sections[KALLSYMS_IDX] = osec = arch_new_section(); memset(osec, 0, sizeof(*osec)); osec->name = KALLSYMS_SEC_NAME; osec->header.sh_name = 1; /* Offset in strtab */ osec->header.sh_type = SHT_PROGBITS; /* Load it */ osec->header.sh_flags = SHF_ALLOC; /* Read only data */ osec->header.sh_link = SHN_UNDEF; osec->header.sh_addralign = sizeof(ElfW(Word)); file_offset = (file_offset + osec->header.sh_addralign - 1) & -(osec->header.sh_addralign); osec->header.sh_offset = file_offset; /* How many loaded sections are there? */ for (i = 0; i < fin->header.e_shnum; ++i) { if (fin->sections[i]->header.sh_flags & SHF_ALLOC) ++loaded; } /* Initial contents, header + one entry per input section. No strings. */ osec->header.sh_size = sizeof(*a_hdr) + loaded*sizeof(*a_sec); a_hdr = (struct kallsyms_header *) osec->contents = xmalloc(osec->header.sh_size); memset(osec->contents, 0, osec->header.sh_size); a_hdr->size = sizeof(*a_hdr); a_hdr->sections = loaded; a_hdr->section_off = a_hdr->size; a_hdr->section_size = sizeof(*a_sec); a_hdr->symbol_off = osec->header.sh_size; a_hdr->symbol_size = sizeof(a_sym); a_hdr->start = (ElfW(Addr))(~0); /* Map input section numbers to kallsyms section offsets. */ sec_off = 0; /* Offset to first kallsyms section entry */ fin_to_allsym_map = xmalloc(sizeof(*fin_to_allsym_map)*fin->header.e_shnum); for (i = 0; i < fin->header.e_shnum; ++i) { isec = fin->sections[i]; if (isec->header.sh_flags & SHF_ALLOC) { fin_to_allsym_map[isec->idx] = sec_off; sec_off += a_hdr->section_size; } else fin_to_allsym_map[isec->idx] = -1; /* Ignore this section */ } /* Copy the loaded section data. */ a_sec = (struct kallsyms_section *) ((char *) a_hdr + a_hdr->section_off); for (i = 0; i < fin->header.e_shnum; ++i) { isec = fin->sections[i]; if (!(isec->header.sh_flags & SHF_ALLOC)) continue; a_sec->start = isec->header.sh_addr; a_sec->size = isec->header.sh_size; a_sec->flags = isec->header.sh_flags; a_sec->name_off = strings_size - strings_left; append_string(isec->name, &strings, &strings_size, &strings_left); if (a_sec->start < a_hdr->start) a_hdr->start = a_sec->start; if (a_sec->start+a_sec->size > a_hdr->end) a_hdr->end = a_sec->start+a_sec->size; unfix_kallsyms_section(a_sec); ++a_sec; } /* Build the kallsyms symbol table from the symbol hashes. */ for (i = 0; i < HASH_BUCKETS; ++i) { struct obj_symbol *sym = fin->symtab[i]; for (sym = fin->symtab[i]; sym ; sym = sym->next) { if (!sym || sym->secidx >= fin->header.e_shnum) continue; if ((a_sym.section_off = fin_to_allsym_map[sym->secidx]) == -1) continue; if (strcmp(sym->name, "gcc2_compiled.") == 0 || strncmp(sym->name, "__insmod_", 9) == 0) continue; a_sym.symbol_addr = sym->value; if (fin->header.e_type == ET_REL) a_sym.symbol_addr += fin->sections[sym->secidx]->header.sh_addr; a_sym.name_off = strings_size - strings_left; append_symbol(&a_sym, &symbols, &symbols_size, &symbols_left); append_string(sym->name, &strings, &strings_size, &strings_left); ++a_hdr->symbols; } } free(fin_to_allsym_map); /* Sort the symbols into ascending order by address and name */ sym_strings = strings; /* For symbol_compare */ qsort((char *) symbols, (unsigned) a_hdr->symbols, sizeof(* symbols), symbol_compare); sym_strings = NULL; /* Put the lot together */ osec->header.sh_size = a_hdr->total_size = a_hdr->symbol_off + a_hdr->symbols*a_hdr->symbol_size + strings_size - strings_left; a_hdr = (struct kallsyms_header *) osec->contents = xrealloc(a_hdr, a_hdr->total_size); p = (char *)a_hdr + a_hdr->symbol_off; unfix_kallsyms_symbols(symbols, a_hdr->symbols); memcpy(p, symbols, a_hdr->symbols*a_hdr->symbol_size); free(symbols); p += a_hdr->symbols*a_hdr->symbol_size; a_hdr->string_off = p - (char *)a_hdr; memcpy(p, strings, strings_size - strings_left); free(strings); unfix_kallsyms_header(a_hdr); *fout_result = fout; return 0; } int main (int argc, char **argv) { struct option long_opts[] = { {"version", 0, 0, 'V'}, {"help", 0, 0, 'h'}, {0, 0, 0, 0} }; char *filename = NULL; int fp; struct obj_file *fin, *fout; int i, c; error_file = "kallsyms_i386_ppc"; /* To handle repeated calls from combined modprobe */ errors = optind = 0; /* Process the command line. */ while ((c = getopt_long(argc, argv, "Vh", &long_opts[0], NULL)) != EOF) switch (c) { case 'V': fputs("kallsyms version " MODUTILS_VERSION "\n", stderr); return(0); case 'h': /* Print the usage message. */ kallsyms_usage(); return(0); default: kallsyms_usage(); return(1); } if (optind != argc-1) { kallsyms_usage(); return(1); } filename = argv[optind++]; error_file = filename; if ((fp = open(filename, O_RDONLY)) < 0) { error("%s: %m", filename); return 1; } if ((fin = obj_load(fp, ET_EXEC, filename)) == NULL) { close(fp); return 1; } close(fp); error_file = "kallsyms_i386_ppc"; if (obj_kallsyms(fin, &fout)) return 1; /* Write the extracted data */ unfix_Ehdr(&fout->header); fwrite(&fout->header, sizeof(fout->header), 1, stdout); fix_Ehdr(&fout->header); for (i = 0; i < fout->header.e_shnum; ++i) { unfix_Shdr(&fout->sections[i]->header); fwrite(&fout->sections[i]->header, fout->header.e_shentsize, 1, stdout); fix_Shdr(&fout->sections[i]->header); } for (i = 0; i < fout->header.e_shnum; ++i) { if (fout->sections[i]->header.sh_size) { fseek(stdout, fout->sections[i]->header.sh_offset, SEEK_SET); fwrite(fout->sections[i]->contents, fout->sections[i]->header.sh_size, 1, stdout); } } return 0; }