blob: 58a794352d73566eca7f7ade5a46a0ae6f6406d0 [file] [log] [blame]
#if defined(ELF32BIT)
#define PERBIT(x) x##32
#define ElfPERBIT(x) Elf32_##x
#define ELFPERBIT(x) ELF32_##x
/* 32-bit unsigned integer */
#define Elf32_Uint Elf32_Word
#elif defined(ELF64BIT)
#define PERBIT(x) x##64
#define ElfPERBIT(x) Elf64_##x
#define ELFPERBIT(x) ELF64_##x
/* 64-bit unsigned integer */
#define Elf64_Uint Elf64_Xword
#else
# error "Undefined ELF word length"
#endif
static void *PERBIT(get_section)(struct elf_file *module,
const char *secname,
ElfPERBIT(Shdr) **sechdr,
unsigned long *secsize)
{
void *data = module->data;
unsigned long len = module->len;
int conv = module->conv;
ElfPERBIT(Ehdr) *hdr;
ElfPERBIT(Shdr) *sechdrs;
ElfPERBIT(Off) e_shoff;
ElfPERBIT(Half) e_shnum, e_shstrndx;
ElfPERBIT(Off) secoffset;
const char *secnames;
unsigned int i;
*secsize = 0;
if (len <= 0 || len < sizeof(*hdr))
return NULL;
hdr = data;
e_shoff = END(hdr->e_shoff, conv);
e_shnum = END(hdr->e_shnum, conv);
e_shstrndx = END(hdr->e_shstrndx, conv);
if (len < e_shoff + e_shnum * sizeof(sechdrs[0]))
return NULL;
sechdrs = data + e_shoff;
if (len < END(sechdrs[e_shstrndx].sh_offset, conv))
return NULL;
/* Find section by name; return header, pointer and size. */
secnames = data + END(sechdrs[e_shstrndx].sh_offset, conv);
for (i = 1; i < e_shnum; i++) {
if (streq(secnames + END(sechdrs[i].sh_name, conv), secname)) {
*secsize = END(sechdrs[i].sh_size, conv);
secoffset = END(sechdrs[i].sh_offset, conv);
if (sechdr)
*sechdr = sechdrs + i;
if (len < secoffset + *secsize)
return NULL;
return data + secoffset;
}
}
return NULL;
}
/* Load the given section: NULL on error. */
static void *PERBIT(load_section)(struct elf_file *module,
const char *secname,
unsigned long *secsize)
{
return PERBIT(get_section)(module, secname, NULL, secsize);
}
static struct string_table *PERBIT(load_strings)(struct elf_file *module,
const char *secname,
struct string_table *tbl)
{
unsigned long size;
const char *strings;
strings = PERBIT(load_section)(module, secname, &size);
if (strings) {
if (strings[size-1] != 0) {
warn("%s may be corrupt; an unterminated string"
" was found at the end of section %s\n",
module->pathname, secname);
}
/* Skip any zero padding. */
while (!strings[0]) {
strings++;
if (size-- <= 1)
return tbl;
}
for (; strings; strings = next_string(strings, &size))
tbl = NOFAIL(strtbl_add(strings, tbl));
}
return tbl;
}
static struct string_table *PERBIT(load_symbols)(struct elf_file *module,
uint64_t **versions)
{
struct string_table *symtbl = NULL;
if (versions) {
static const char crc[] = "__crc_";
static const int crc_len = sizeof(crc) - 1;
unsigned int num_syms, i;
unsigned long size;
ElfPERBIT(Sym) *syms;
char *strings;
int conv;
*versions = NULL;
strings = PERBIT(load_section)(module, ".strtab", &size);
syms = PERBIT(load_section)(module, ".symtab", &size);
if (!strings || !syms)
goto fallback;
num_syms = size / sizeof(syms[0]);
*versions = NOFAIL(calloc(sizeof(**versions), num_syms));
conv = module->conv;
for (i = 1; i < num_syms; i++) {
const char *name;
name = strings + END(syms[i].st_name, conv);
if (strncmp(name, crc, crc_len) != 0)
continue;
name += crc_len;
symtbl = NOFAIL(strtbl_add(name, symtbl));
(*versions)[symtbl->cnt - 1] = END(syms[i].st_value,
conv);
}
if (!symtbl) {
/* Either this module does not export any symbols, or
* it was compiled without CONFIG_MODVERSIONS. If the
* latter, we will print a warning in load_dep_syms,
* so just silently fallback to __ksymtab_strings in
* both cases.
*/
free(*versions);
*versions = NULL;
goto fallback;
}
return symtbl;
}
fallback:
return PERBIT(load_strings)(module, "__ksymtab_strings", symtbl);
}
static char *PERBIT(get_aliases)(struct elf_file *module, unsigned long *size)
{
return PERBIT(load_section)(module, ".modalias", size);
}
static char *PERBIT(get_modinfo)(struct elf_file *module, unsigned long *size)
{
return PERBIT(load_section)(module, ".modinfo", size);
}
#ifndef STT_REGISTER
#define STT_REGISTER 13 /* Global register reserved to app. */
#endif
static struct string_table *PERBIT(load_dep_syms)(struct elf_file *module,
struct string_table **types,
uint64_t **versions)
{
unsigned int i, num_syms;
unsigned int j, num_symvers, versions_size;
unsigned long size;
char *strings;
ElfPERBIT(Sym) *syms;
ElfPERBIT(Ehdr) *hdr;
struct PERBIT(modver_info) **symvers;
int handle_register_symbols;
struct string_table *names;
int conv;
names = NULL;
*types = NULL;
symvers = NULL;
num_symvers = versions_size = 0;
if (versions) {
int ok = 1;
*versions = NULL;
struct PERBIT(modver_info) *symvers_sec;
symvers_sec = module->ops->load_section(module, "__versions",
&size);
if (!symvers_sec) {
warn("%s is built without modversions",
module->pathname);
ok = 0;
}
if (size % sizeof(symvers[0]) != 0) {
warn("invalid __versions section size in %s",
module->pathname);
ok = 0;
}
if (ok) {
num_symvers = size / sizeof(symvers_sec[0]);
/* symvers is used to keep track of each visited entry.
* The table also contains the fake struct_module /
* module_layout symbol which we don't want to miss.
*/
symvers = NOFAIL(malloc(num_symvers *
sizeof(symvers[0])));
for (j = 0; j < num_symvers; j++)
symvers[j] = &symvers_sec[j];
} else {
versions = NULL;
}
}
strings = PERBIT(load_section)(module, ".strtab", &size);
syms = PERBIT(load_section)(module, ".symtab", &size);
if (!strings || !syms) {
warn("Couldn't find symtab and strtab in module %s\n",
module->pathname);
goto out;
}
num_syms = size / sizeof(syms[0]);
hdr = module->data;
conv = module->conv;
if (versions) {
versions_size = num_syms;
*versions = NOFAIL(calloc(sizeof(**versions), versions_size));
}
handle_register_symbols =
(END(hdr->e_machine, conv) == EM_SPARC ||
END(hdr->e_machine, conv) == EM_SPARCV9);
for (i = 1; i < num_syms; i++) {
if (END(syms[i].st_shndx, conv) == SHN_UNDEF) {
/* Look for symbol */
const char *name;
int weak;
name = strings + END(syms[i].st_name, conv);
/* Not really undefined: sparc gcc 3.3 creates
U references when you have global asm
variables, to avoid anyone else misusing
them. */
if (handle_register_symbols
&& (ELFPERBIT(ST_TYPE)(END(syms[i].st_info, conv))
== STT_REGISTER))
continue;
weak = (ELFPERBIT(ST_BIND)(END(syms[i].st_info, conv))
== STB_WEAK);
names = NOFAIL(strtbl_add(name, names));
*types = NOFAIL(strtbl_add(weak ? weak_sym : undef_sym,
*types));
if (!versions)
continue;
/* Not optimal, but the number of required symbols
* is usually not huge and this is only called by
* depmod.
*/
for (j = 0; j < num_symvers; j++) {
struct PERBIT(modver_info) *info = symvers[j];
if (!info)
continue;
if (streq(name, info->name)) {
(*versions)[names->cnt - 1] =
END(info->crc, conv);
symvers[j] = NULL;
break;
}
}
}
}
/* add struct_module / module_layout */
for (j = 0; j < num_symvers; j++) {
struct PERBIT(modver_info) *info = symvers[j];
if (!info)
continue;
if ((names ? names->cnt : 0) >= versions_size) {
versions_size++;
*versions = NOFAIL(realloc(*versions, versions_size));
}
names = NOFAIL(strtbl_add(info->name, names));
*types = NOFAIL(strtbl_add(undef_sym, *types));
(*versions)[names->cnt - 1] = END(info->crc, conv);
}
out:
free(symvers);
return names;
}
static void *PERBIT(deref_sym)(ElfPERBIT(Ehdr) *hdr,
ElfPERBIT(Shdr) *sechdrs,
ElfPERBIT(Sym) *sym,
unsigned int *secsize,
int conv)
{
/* In BSS? Happens for empty device tables on
* recent GCC versions. */
if (END(sechdrs[END(sym->st_shndx, conv)].sh_type,conv) == SHT_NOBITS)
return NULL;
if (secsize)
*secsize = END(sym->st_size, conv);
return (void *)hdr
+ END(sechdrs[END(sym->st_shndx, conv)].sh_offset, conv)
+ END(sym->st_value, conv);
}
/* FIXME: Check size, unless we end up using aliases anyway --RR */
static void PERBIT(fetch_tables)(struct elf_file *module,
struct module_tables *tables)
{
unsigned int i;
unsigned long size;
char *strings;
ElfPERBIT(Ehdr) *hdr;
ElfPERBIT(Sym) *syms;
ElfPERBIT(Shdr) *sechdrs;
int conv;
hdr = module->data;
conv = module->conv;
sechdrs = (void *)hdr + END(hdr->e_shoff, conv);
strings = PERBIT(load_section)(module, ".strtab", &size);
syms = PERBIT(load_section)(module, ".symtab", &size);
/* Don't warn again: we already have above */
if (!strings || !syms)
return;
memset(tables, 0x00, sizeof(struct module_tables));
for (i = 0; i < size / sizeof(syms[0]); i++) {
char *name = strings + END(syms[i].st_name, conv);
if (!tables->pci_table && streq(name, "__mod_pci_device_table")) {
tables->pci_size = PERBIT(PCI_DEVICE_SIZE);
tables->pci_table = PERBIT(deref_sym)(hdr, sechdrs, &syms[i],
NULL, conv);
}
else if (!tables->usb_table && streq(name, "__mod_usb_device_table")) {
tables->usb_size = PERBIT(USB_DEVICE_SIZE);
tables->usb_table = PERBIT(deref_sym)(hdr, sechdrs, &syms[i],
NULL, conv);
}
else if (!tables->ccw_table && streq(name, "__mod_ccw_device_table")) {
tables->ccw_size = PERBIT(CCW_DEVICE_SIZE);
tables->ccw_table = PERBIT(deref_sym)(hdr, sechdrs, &syms[i],
NULL, conv);
}
else if (!tables->ieee1394_table && streq(name, "__mod_ieee1394_device_table")) {
tables->ieee1394_size = PERBIT(IEEE1394_DEVICE_SIZE);
tables->ieee1394_table = PERBIT(deref_sym)(hdr, sechdrs, &syms[i],
NULL, conv);
}
else if (!tables->pnp_table && streq(name, "__mod_pnp_device_table")) {
tables->pnp_size = PERBIT(PNP_DEVICE_SIZE);
tables->pnp_table = PERBIT(deref_sym)(hdr, sechdrs, &syms[i],
NULL, conv);
}
else if (!tables->pnp_card_table && streq(name, "__mod_pnp_card_device_table")) {
tables->pnp_card_size = PERBIT(PNP_CARD_DEVICE_SIZE);
tables->pnp_card_table = PERBIT(deref_sym)(hdr, sechdrs, &syms[i],
NULL, conv);
tables->pnp_card_offset = PERBIT(PNP_CARD_DEVICE_OFFSET);
}
else if (!tables->input_table && streq(name, "__mod_input_device_table")) {
tables->input_size = PERBIT(INPUT_DEVICE_SIZE);
tables->input_table = PERBIT(deref_sym)(hdr, sechdrs, &syms[i],
&tables->input_table_size,
conv);
}
else if (!tables->serio_table && streq(name, "__mod_serio_device_table")) {
tables->serio_size = PERBIT(SERIO_DEVICE_SIZE);
tables->serio_table = PERBIT(deref_sym)(hdr, sechdrs, &syms[i],
NULL, conv);
}
else if (!tables->of_table && streq(name, "__mod_of_device_table")) {
tables->of_size = PERBIT(OF_DEVICE_SIZE);
tables->of_table = PERBIT(deref_sym)(hdr, sechdrs, &syms[i],
NULL, conv);
}
}
}
/*
* strip_section - tell the kernel to ignore the named section
*/
static void PERBIT(strip_section)(struct elf_file *module, const char *secname)
{
void *p;
ElfPERBIT(Shdr) *sechdr;
unsigned long secsize;
p = PERBIT(get_section)(module, secname, &sechdr, &secsize);
if (p) {
ElfPERBIT(Uint) mask;
mask = ~((ElfPERBIT(Uint))SHF_ALLOC);
sechdr->sh_flags &= END(mask, module->conv);
}
}
static int PERBIT(dump_modversions)(struct elf_file *module)
{
unsigned long secsize;
struct PERBIT(modver_info) *info;
int n = 0;
info = module->ops->load_section(module, "__versions", &secsize);
if (!info)
return 0; /* not a kernel module */
if (secsize % sizeof(*info) != 0)
return -1; /* invalid section size */
for (n = 0; n < secsize / sizeof(*info); n++) {
#if defined(ELF32BIT)
printf("0x%08lx\t%s\n", (unsigned long)
#else /* defined(ELF64BIT) */
printf("0x%08llx\t%s\n", (unsigned long long)
#endif
END(info[n].crc, module->conv),
skip_dot(info[n].name));
}
return n;
}
struct module_ops PERBIT(mod_ops) = {
.load_section = PERBIT(load_section),
.load_strings = PERBIT(load_strings),
.load_symbols = PERBIT(load_symbols),
.load_dep_syms = PERBIT(load_dep_syms),
.fetch_tables = PERBIT(fetch_tables),
.get_aliases = PERBIT(get_aliases),
.get_modinfo = PERBIT(get_modinfo),
.strip_section = PERBIT(strip_section),
.dump_modvers = PERBIT(dump_modversions),
};
#undef PERBIT
#undef ElfPERBIT
#undef ELFPERBIT