Merge pull request #1056 from jpoimboe/dynrela

dynrela cleanups
This commit is contained in:
Yannick Cote 2020-04-14 09:07:06 -04:00 committed by GitHub
commit 5432c86ded
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 399 additions and 397 deletions

View File

@ -171,6 +171,32 @@ static int is_gcc6_localentry_bundled_sym(struct symbol *sym)
}
#endif
/*
* On ppc64le, when a function references data, it does so indirectly, via the
* .toc section. So there are *two* levels of relas:
*
* 1) the original function rela, referring to the .toc section; and
*
* 2) the .toc section rela, referring to the data needed by the function.
*
* For example:
*
* Relocation section '.rela.text.netlink_release' at offset 0xcadf0 contains 44 entries:
* ...
* 0000000000000398 0000007300000032 R_PPC64_TOC16_HA 0000000000000000 .toc + 138
* 00000000000003a0 0000007300000040 R_PPC64_TOC16_LO_DS 0000000000000000 .toc + 138
*
* Relocation section '.rela.toc' at offset 0xcc6b0 contains 46 entries:
* ...
* 0000000000000138 0000002a00000026 R_PPC64_ADDR64 0000000000000000 .text.deferred_put_nlk_sk + 8
*
* The below function takes the "first level" rela as input, and, if it refers
* to .toc, returns the "second level" rela, which is the one that refers to
* the actual data symbol.
*
* In some rare cases, a .toc entry has constant data, and thus has no
* corresponding rela. In that case, NULL is returned.
*/
static struct rela *toc_rela(const struct rela *rela)
{
if (rela->type != R_PPC64_TOC16_HA &&
@ -2714,14 +2740,17 @@ static void kpatch_create_patches_sections(struct kpatch_elf *kelf,
struct section *sec, *relasec;
struct symbol *sym, *strsym;
struct rela *rela;
struct lookup_result result;
struct lookup_result symbol;
struct kpatch_patch_func *funcs;
/* count patched functions */
nr = 0;
list_for_each_entry(sym, &kelf->symbols, list)
if (sym->type == STT_FUNC && sym->status == CHANGED && !sym->parent)
nr++;
list_for_each_entry(sym, &kelf->symbols, list) {
if (sym->type != STT_FUNC || sym->status != CHANGED ||
sym->parent)
continue;
nr++;
}
/* create text/rela section pair */
sec = create_section_pair(kelf, ".kpatch.funcs", sizeof(*funcs), nr);
@ -2739,72 +2768,69 @@ static void kpatch_create_patches_sections(struct kpatch_elf *kelf,
/* populate sections */
index = 0;
list_for_each_entry(sym, &kelf->symbols, list) {
if (sym->type == STT_FUNC && sym->status == CHANGED && !sym->parent) {
if (sym->bind == STB_LOCAL) {
if (lookup_local_symbol(table, sym->name,
&result))
ERROR("lookup_local_symbol %s",
sym->name);
} else {
if(lookup_global_symbol(table, sym->name,
&result))
ERROR("lookup_global_symbol %s",
sym->name);
}
log_debug("lookup for %s @ 0x%016lx len %lu\n",
sym->name, result.value, result.size);
if (sym->type != STT_FUNC || sym->status != CHANGED ||
sym->parent)
continue;
/*
* Convert global symbols to local so other objects in
* the patch module (like the patch callback object's init
* code) won't link to this function and call it before
* its relocations have been applied.
*/
sym->bind = STB_LOCAL;
sym->sym.st_info = (unsigned char)
GELF_ST_INFO(sym->bind, sym->type);
if (!lookup_symbol(table, sym->name, &symbol))
ERROR("can't find symbol '%s' in symbol table", sym->name);
/* add entry in text section */
funcs[index].old_addr = result.value;
funcs[index].old_size = result.size;
funcs[index].new_size = sym->sym.st_size;
funcs[index].sympos = result.pos;
if (sym->bind == STB_LOCAL && symbol.global)
ERROR("can't find local symbol '%s' in symbol table", sym->name);
/*
* Add a relocation that will populate
* the funcs[index].new_addr field at
* module load time.
*/
ALLOC_LINK(rela, &relasec->relas);
rela->sym = sym;
rela->type = ABSOLUTE_RELA_TYPE;
rela->addend = 0;
rela->offset = (unsigned int)(index * sizeof(*funcs));
log_debug("lookup for %s: obj=%s sympos=%lu size=%lu",
sym->name, symbol.objname, symbol.sympos,
symbol.size);
/*
* Add a relocation that will populate
* the funcs[index].name field.
*/
ALLOC_LINK(rela, &relasec->relas);
rela->sym = strsym;
rela->type = ABSOLUTE_RELA_TYPE;
rela->addend = offset_of_string(&kelf->strings, sym->name);
rela->offset = (unsigned int)(index * sizeof(*funcs) +
offsetof(struct kpatch_patch_func, name));
/*
* Convert global symbols to local so other objects in the
* patch module (like the patch callback object's init code)
* won't link to this function and call it before its
* relocations have been applied.
*/
sym->bind = STB_LOCAL;
sym->sym.st_info = (unsigned char)
GELF_ST_INFO(sym->bind, sym->type);
/*
* Add a relocation that will populate
* the funcs[index].objname field.
*/
ALLOC_LINK(rela, &relasec->relas);
rela->sym = strsym;
rela->type = ABSOLUTE_RELA_TYPE;
rela->addend = objname_offset;
rela->offset = (unsigned int)(index * sizeof(*funcs) +
offsetof(struct kpatch_patch_func,objname));
/* add entry in text section */
funcs[index].old_addr = symbol.addr;
funcs[index].old_size = symbol.size;
funcs[index].new_size = sym->sym.st_size;
funcs[index].sympos = symbol.sympos;
index++;
}
/*
* Add a relocation that will populate the
* funcs[index].new_addr field at module load time.
*/
ALLOC_LINK(rela, &relasec->relas);
rela->sym = sym;
rela->type = ABSOLUTE_RELA_TYPE;
rela->addend = 0;
rela->offset = (unsigned int)(index * sizeof(*funcs));
/*
* Add a relocation that will populate the funcs[index].name
* field.
*/
ALLOC_LINK(rela, &relasec->relas);
rela->sym = strsym;
rela->type = ABSOLUTE_RELA_TYPE;
rela->addend = offset_of_string(&kelf->strings, sym->name);
rela->offset = (unsigned int)(index * sizeof(*funcs) +
offsetof(struct kpatch_patch_func, name));
/*
* Add a relocation that will populate the funcs[index].objname
* field.
*/
ALLOC_LINK(rela, &relasec->relas);
rela->sym = strsym;
rela->type = ABSOLUTE_RELA_TYPE;
rela->addend = objname_offset;
rela->offset = (unsigned int)(index * sizeof(*funcs) +
offsetof(struct kpatch_patch_func,objname));
index++;
}
/* sanity check, index should equal nr */
@ -2820,76 +2846,178 @@ static int kpatch_is_core_module_symbol(char *name)
!strcmp(name, "kpatch_shadow_get"));
}
/*
* If the patched code refers to a symbol, for example, calls a function
* or stores a pointer to a function somewhere, the address of that symbol
* must be resolved somehow before the patch is applied. The symbol may be
* present in the original code too, so the patch may refer either to that
* version of the symbol (dynrela is used for that) or to its patched
* version directly (with a normal relocation).
*
* Dynrelas may be needed for the symbols not present in this object file
* (rela->sym->sec is NULL), because it is unknown if the patched versions
* of these symbols exist and where they are.
*
* The patched code can usually refer to a symbol from this object file
* directly. If it is a function, this may also improve performance because
* it will not be needed to call the original function first, find the
* patched one and then use Ftrace to pass control to it.
*
* There is an exception though, at least on x86. It is safer to use
* a dynrela if the patched code stores a pointer to a function somewhere
* (relocation of type R_X86_64_32S). The function could be used as
* a callback and some kinds of callbacks are called asynchronously. If
* the patch module sets such callback and is unloaded shortly after,
* the kernel could try to call the function via an invalid pointer and
* would crash. With dynrela, the kernel would call the original function
* in that case.
*/
static int function_ptr_rela(const struct rela *rela)
{
const struct rela *rela_toc = toc_rela(rela);
return (rela_toc && rela_toc->sym->type == STT_FUNC &&
!rela_toc->sym->parent &&
/* skip switch table on PowerPC */
rela_toc->addend == (int)rela_toc->sym->sym.st_value &&
(rela->type == R_X86_64_32S ||
rela->type == R_PPC64_TOC16_HA ||
rela->type == R_PPC64_TOC16_LO_DS));
}
static int may_need_dynrela(const struct rela *rela)
static bool need_dynrela(struct lookup_table *table, const struct rela *rela)
{
struct lookup_result symbol;
/*
* References to .TOC. are treated specially by the module loader and
* These references are treated specially by the module loader and
* should never be converted to dynrelas.
*/
if (rela->type == R_PPC64_REL16_HA || rela->type == R_PPC64_REL16_LO ||
rela->type == R_PPC64_REL64)
return 0;
if (!rela->sym->sec)
return 1;
rela->type == R_PPC64_REL64 || rela->type == R_PPC64_ENTRY)
return false;
/*
* Nested functions used as callbacks are a special case.
* They are not supposed to be visible outside of the
* function that defines them. Their names may differ in
* the original and the patched kernels which makes it
* difficult to use dynrelas. Fortunately, nested functions
* are rare and are unlikely to be used as asynchronous
* callbacks, so the patched code can refer to them directly.
* It seems, one can only distinguish such functions by their
* names containing a dot. Other kinds of functions with
* such names (e.g. optimized copies of functions) are
* unlikely to be used as callbacks.
* On powerpc, the function prologue generated by GCC 6 has the
* sequence:
*
* .globl my_func
* .type my_func, @function
* .quad .TOC.-my_func
* my_func:
* .reloc ., R_PPC64_ENTRY ; optional
* ld r2,-8(r12)
* add r2,r2,r12
* .localentry my_func, .-my_func
*
* The R_PPC64_ENTRY is optional and its symbol might have an empty
* name. Leave it as a normal rela.
*/
return (function_ptr_rela(rela) &&
toc_rela(rela)->sym->status != NEW &&
!strchr(toc_rela(rela)->sym->name, '.'));
if (rela->type == R_PPC64_ENTRY)
return false;
/*
* Allow references to core module symbols to remain as normal
* relas. They should be exported.
*/
if (kpatch_is_core_module_symbol(rela->sym->name))
return false;
if (rela->sym->sec) {
/*
* Internal symbols usually don't need dynrelas, because they
* live in the patch module and can be relocated normally.
*
* There's one exception: function pointers.
*
* If the rela references a function pointer, we convert it to
* a dynrela, so that the function pointer will refer to the
* original function rather than the patched function. This
* can prevent crashes in cases where the function pointer is
* called asynchronously after the patch module has been
* unloaded.
*/
if (!function_ptr_rela(rela))
return false;
/*
* Function pointers which refer to _nested_ functions are a
* special case. They are not supposed to be visible outside
* of the function that defines them. Their names may differ
* in the original and the patched kernels which makes it
* difficult to use dynrelas. Fortunately, nested functions
* are rare and are unlikely to be used as asynchronous
* callbacks, so the patched code can refer to them directly.
* It seems, one can only distinguish such functions by their
* names containing a dot. Other kinds of functions with such
* names (e.g. optimized copies of functions) are unlikely to
* be used as callbacks.
*
* Function pointers to *new* functions don't have this issue,
* just use a normal rela for them.
*/
return toc_rela(rela)->sym->status != NEW &&
!strchr(toc_rela(rela)->sym->name, '.');
}
if (!lookup_symbol(table, rela->sym->name, &symbol)) {
/*
* Assume the symbol lives in another .o in the patch module.
* A normal rela should work.
*/
return false;
}
if (rela->sym->bind == STB_LOCAL) {
if (symbol.global)
ERROR("can't find local symbol '%s' in symbol table",
rela->sym->name);
/*
* The symbol is (formerly) local. Use a dynrela to access the
* original version of the symbol in the patched object.
*/
return true;
}
if (symbol.exported) {
if (is_gcc6_localentry_bundled_sym(rela->sym)) {
/*
* On powerpc, the symbol is global and exported, but
* it was also in the changed object file. In this
* case the rela refers to the 'localentry' point, so a
* normal rela wouldn't work. Force a dynrela so it
* can be handled correctly by the livepatch relocation
* code.
*/
return true;
}
if (!strcmp(symbol.objname, "vmlinux")) {
/*
* The symbol is exported by vmlinux. Use a normal
* rela.
*/
return false;
}
/*
* The symbol is exported by the to-be-patched module, or by
* another module which the patched module depends on. Use a
* dynrela because of late module loading: the patch module may
* be loaded before the to-be-patched (or other) module.
*/
return true;
}
if (symbol.global) {
/*
* The symbol is global in the to-be-patched object, but not
* exported. Use a dynrela to work around the fact that it's
* an unexported sybmbol.
*/
return true;
}
/*
* The symbol is global and not exported, but it's not in the parent
* object. The only explanation is that it's defined in another object
* in the patch module. A normal rela should resolve it.
*/
return false;
}
/*
* kpatch_create_intermediate_sections()
*
* The primary purpose of this function is to convert some relas (also known as
* relocations) to dynrelas (also known as dynamic relocations or livepatch
* relocations or klp relas).
*
* If the patched code refers to a symbol, for example, if it calls a function
* or stores a pointer to a function somewhere or accesses some global data,
* the address of that symbol must be resolved somehow before the patch is
* applied.
*
* If the symbol lives outside the patch module, and if it's not exported by
* vmlinux (e.g., with EXPORT_SYMBOL) then the rela needs to be converted to a
* dynrela so the livepatch code can resolve it at runtime.
*/
static void kpatch_create_intermediate_sections(struct kpatch_elf *kelf,
struct lookup_table *table,
char *objname,
@ -2901,9 +3029,8 @@ static void kpatch_create_intermediate_sections(struct kpatch_elf *kelf,
struct symbol *strsym, *ksym_sec_sym;
struct kpatch_symbol *ksyms;
struct kpatch_relocation *krelas;
struct lookup_result result;
char *sym_objname;
int ret, vmlinux, external;
struct lookup_result symbol;
bool vmlinux;
vmlinux = !strcmp(objname, "vmlinux");
@ -2915,25 +3042,22 @@ static void kpatch_create_intermediate_sections(struct kpatch_elf *kelf,
if (!strcmp(sec->name, ".rela.kpatch.funcs"))
continue;
list_for_each_entry(rela, &sec->relas, list) {
nr++; /* upper bound on number of kpatch relas and symbols */
/* upper bound on number of kpatch relas and symbols */
nr++;
/*
* Relocation section '.rela.toc' at offset 0xcc6b0 contains 46 entries:
* ...
* 0000000000000138 0000002a00000026 R_PPC64_ADDR64 0000000000000000 .text.deferred_put_nlk_sk + 8
* We set 'need_dynrela' here in the first pass because
* the .toc section's 'need_dynrela' values are
* dependent on all the other sections. Otherwise, if
* we did this analysis in the second pass, we'd have
* to convert .toc dynrelas at the very end.
*
* Relocation section '.rela.text.netlink_release' at offset 0xcadf0 contains 44 entries:
* ...
* 0000000000000398 0000007300000032 R_PPC64_TOC16_HA 0000000000000000 .toc + 138
* 00000000000003a0 0000007300000040 R_PPC64_TOC16_LO_DS 0000000000000000 .toc + 138
*
* On PowerPC, may_need_dynrela() should be using rela's reference in .rela.toc for
* the rela like in the example, where the sym name is .toc + offset. In such case,
* the checks are performed on both rela and its reference in .rela.toc. Where the
* rela is checked for rela->type and its corresponding rela in .rela.toc for function
* pointer/switch label. If rela->need_dynrela needs to be set, it's referenced rela
* in (.rela.toc)->need_dynrela is set, as they represent the function sym.
* Specifically, this is needed for the powerpc
* internal symbol function pointer check which is done
* via .toc indirection in need_dynrela().
*/
if (may_need_dynrela(rela))
if (need_dynrela(table, rela))
toc_rela(rela)->need_dynrela = 1;
}
}
@ -2972,138 +3096,21 @@ static void kpatch_create_intermediate_sections(struct kpatch_elf *kelf,
if (!rela->need_dynrela)
continue;
/*
* Allow references to core module symbols to remain as
* normal relas, since the core module may not be
* compiled into the kernel, and they should be
* exported anyway.
*/
if (kpatch_is_core_module_symbol(rela->sym->name))
continue;
if (!lookup_symbol(table, rela->sym->name, &symbol))
ERROR("can't find symbol '%s' in symbol table",
rela->sym->name);
external = 0;
/*
* sym_objname is the name of the object to which
* rela->sym belongs. We'll need this to build
* ".klp.sym." symbol names later on.
*
* By default sym_objname is the name of the
* component being patched (vmlinux or module).
* If it's an external symbol, sym_objname
* will get reassigned appropriately.
*/
sym_objname = objname;
/*
* On ppc64le, the function prologue generated by GCC 6
* has the sequence:
*
* .globl my_func
* .type my_func, @function
* .quad .TOC.-my_func
* my_func:
* .reloc ., R_PPC64_ENTRY ; optional
* ld r2,-8(r12)
* add r2,r2,r12
* .localentry my_func, .-my_func
*
* The R_PPC64_ENTRY is optional and its symbol might
* have an empty name. Leave it as a normal rela.
*/
if (rela->type == R_PPC64_ENTRY)
continue;
if (rela->sym->bind == STB_LOCAL) {
/* An unchanged local symbol */
ret = lookup_local_symbol(table,
rela->sym->name, &result);
if (ret)
ERROR("lookup_local_symbol %s needed for %s",
rela->sym->name, sec->base->name);
}
else if (vmlinux) {
/*
* We have a patch to vmlinux which references
* a global symbol. Use a normal rela for
* exported symbols and a dynrela otherwise.
*/
#ifdef __powerpc64__
/*
* An exported symbol might be local to an
* object file and any access to the function
* might be through localentry (toc+offset)
* instead of global offset.
*
* fs/proc/proc_sysctl::sysctl_head_grab:
* 166: 0000000000000000 256 FUNC GLOBAL DEFAULT [<localentry>: 8] 42 unregister_sysctl_table
* 167: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND .TOC.
*
* These type of symbols have a type of
* STT_FUNC. Treat them like local symbols.
* They will be handled by the livepatch
* relocation code.
*/
if (lookup_is_exported_symbol(table, rela->sym->name)) {
if (rela->sym->type != STT_FUNC)
continue;
}
#else
if (lookup_is_exported_symbol(table, rela->sym->name))
continue;
#endif
/*
* If lookup_global_symbol() fails, assume the
* symbol is defined in another object in the
* patch module.
*/
if (lookup_global_symbol(table, rela->sym->name,
&result))
continue;
} else {
/*
* We have a patch to a module which references
* a global symbol. Try to find the symbol in
* the module being patched.
*/
if (lookup_global_symbol(table, rela->sym->name,
&result)) {
/*
* Not there, see if the symbol is
* exported, and set sym_objname to the
* object the exported symbol belongs
* to. If it's not exported, assume sym
* is provided by another .o in the
* patch module.
*/
sym_objname = lookup_exported_symbol_objname(table, rela->sym->name);
if (!sym_objname)
sym_objname = pmod_name;
/*
* For a symbol exported by vmlinux, use
* the original rela.
*
* For a symbol exported by a module,
* convert to a dynrela because the
* module might not be loaded yet.
*/
if (!strcmp(sym_objname, "vmlinux"))
continue;
external = 1;
}
}
log_debug("lookup for %s @ 0x%016lx len %lu\n",
rela->sym->name, result.value, result.size);
log_debug("lookup for %s: obj=%s sympos=%lu",
rela->sym->name, symbol.objname,
symbol.sympos);
/* Fill in ksyms[index] */
if (vmlinux)
ksyms[index].src = result.value;
ksyms[index].src = symbol.addr;
else
/* for modules, src is discovered at runtime */
ksyms[index].src = 0;
ksyms[index].pos = result.pos;
ksyms[index].sympos = symbol.sympos;
ksyms[index].type = rela->sym->type;
ksyms[index].bind = rela->sym->bind;
@ -3119,7 +3126,7 @@ static void kpatch_create_intermediate_sections(struct kpatch_elf *kelf,
ALLOC_LINK(rela2, &ksym_sec->rela->relas);
rela2->sym = strsym;
rela2->type = ABSOLUTE_RELA_TYPE;
rela2->addend = offset_of_string(&kelf->strings, sym_objname);
rela2->addend = offset_of_string(&kelf->strings, symbol.objname);
rela2->offset = (unsigned int)(index * sizeof(*ksyms) + \
offsetof(struct kpatch_symbol, objname));
@ -3129,7 +3136,7 @@ static void kpatch_create_intermediate_sections(struct kpatch_elf *kelf,
rela->addend -= rela->sym->sym.st_value;
krelas[index].addend = rela->addend;
krelas[index].type = rela->type;
krelas[index].external = external;
krelas[index].external = !vmlinux && symbol.exported;
/* add rela to fill in krelas[index].dest field */
ALLOC_LINK(rela2, &krela_sec->rela->relas);
@ -3605,10 +3612,11 @@ int main(int argc, char *argv[])
kpatch_elf_teardown(kelf_patched);
/* create symbol lookup table */
lookup = lookup_open(parent_symtab, mod_symvers, hint, base_locals);
for (sym_comp = base_locals; sym_comp && sym_comp->name; sym_comp++) {
lookup = lookup_open(parent_symtab, parent_name, mod_symvers, hint,
base_locals);
for (sym_comp = base_locals; sym_comp && sym_comp->name; sym_comp++)
free(sym_comp->name);
}
free(base_locals);
free(hint);

View File

@ -67,7 +67,7 @@ static struct symbol *find_or_add_ksym_to_symbols(struct kpatch_elf *kelf,
objname = strings + rela->addend;
snprintf(pos, 32, "%lu", ksym->pos);
snprintf(pos, 32, "%lu", ksym->sympos);
/* .klp.sym.objname.name,pos */
snprintf(buf, 256, KLP_SYM_PREFIX "%s.%s,%s", objname, name, pos);

View File

@ -97,7 +97,7 @@ static void create_dynamic_rela_sections(struct kpatch_elf *kelf, struct section
dynrelas[index].addend = krelas[index].addend;
dynrelas[index].type = krelas[index].type;
dynrelas[index].external = krelas[index].external;
dynrelas[index].sympos = ksym->pos;
dynrelas[index].sympos = ksym->sympos;
/* dest */
ALLOC_LINK(rela, &dynsec->rela->relas);

View File

@ -26,7 +26,7 @@
struct kpatch_symbol {
unsigned long src;
unsigned long pos;
unsigned long sympos;
unsigned char bind, type;
char *name;
char *objname; /* object to which this sym belongs */

View File

@ -35,12 +35,13 @@
#include <gelf.h>
#include <unistd.h>
#include <libgen.h>
#include <stdbool.h>
#include "lookup.h"
#include "log.h"
struct object_symbol {
unsigned long value;
unsigned long addr;
unsigned long size;
char *name;
int type, bind;
@ -56,6 +57,7 @@ struct lookup_table {
struct object_symbol *obj_syms;
struct export_symbol *exp_syms;
struct object_symbol *local_syms;
char *objname;
};
#define for_each_obj_symbol(ndx, iter, table) \
@ -166,13 +168,15 @@ static void find_local_syms(struct lookup_table *table, char *hint,
if (!locals_match(table, i, child_locals))
continue;
if (table->local_syms)
ERROR("find_local_syms for %s: found_dup", hint);
ERROR("found duplicate matches for %s local symbols in %s symbol table",
hint, table->objname);
table->local_syms = sym;
}
if (!table->local_syms)
ERROR("find_local_syms for %s: couldn't find in vmlinux symbol table", hint);
ERROR("couldn't find matching %s local symbols in %s symbol table",
hint, table->objname);
}
/* Strip the path and replace '-' with '_' */
@ -200,41 +204,49 @@ static char *make_modname(char *modname)
static void symtab_read(struct lookup_table *table, char *path)
{
FILE *file;
long unsigned int value;
unsigned int i = 0;
long unsigned int addr;
int alloc_nr = 0, i = 0;
int matched;
bool skip = false;
char line[256], name[256], size[16], type[16], bind[16], ndx[16];
if ((file = fopen(path, "r")) == NULL)
ERROR("fopen");
while (fgets(line, 256, file)) {
matched = sscanf(line, "%*s %lx %s %s %s %*s %s %s\n",
&value, size, type, bind, ndx, name);
/*
* First, get an upper limit on the number of entries for allocation
* purposes:
*/
while (fgets(line, 256, file))
alloc_nr++;
if (matched == 5) {
name[0] = '\0';
matched++;
}
if (matched != 6 ||
!strcmp(ndx, "UND") ||
!strcmp(type, "SECTION"))
continue;
table->obj_nr++;
}
table->obj_syms = malloc(table->obj_nr * sizeof(*table->obj_syms));
table->obj_syms = malloc(alloc_nr * sizeof(*table->obj_syms));
if (!table->obj_syms)
ERROR("malloc table.obj_syms");
memset(table->obj_syms, 0, table->obj_nr * sizeof(*table->obj_syms));
memset(table->obj_syms, 0, alloc_nr * sizeof(*table->obj_syms));
rewind(file);
/* Now read the actual entries: */
while (fgets(line, 256, file)) {
/*
* On powerpc, "readelf -s" shows both .dynsym and .symtab
* tables. .dynsym is just a subset of .symtab, so skip it to
* avoid duplicates.
*/
if (strstr(line, ".dynsym")) {
skip = true;
continue;
} else if (strstr(line, ".symtab")) {
skip = false;
continue;
}
if (skip)
continue;
matched = sscanf(line, "%*s %lx %s %s %s %*s %s %s\n",
&value, size, type, bind, ndx, name);
&addr, size, type, bind, ndx, name);
if (matched == 5) {
name[0] = '\0';
@ -246,7 +258,7 @@ static void symtab_read(struct lookup_table *table, char *path)
!strcmp(type, "SECTION"))
continue;
table->obj_syms[i].value = value;
table->obj_syms[i].addr = addr;
table->obj_syms[i].size = strtoul(size, NULL, 0);
if (!strcmp(bind, "LOCAL")) {
@ -274,9 +286,12 @@ static void symtab_read(struct lookup_table *table, char *path)
table->obj_syms[i].name = strdup(name);
if (!table->obj_syms[i].name)
ERROR("strdup");
i++;
}
table->obj_nr = i;
fclose(file);
}
@ -347,8 +362,9 @@ static void symvers_read(struct lookup_table *table, char *path)
fclose(file);
}
struct lookup_table *lookup_open(char *symtab_path, char *symvers_path,
char *hint, struct sym_compare_type *locals)
struct lookup_table *lookup_open(char *symtab_path, char *objname,
char *symvers_path, char *hint,
struct sym_compare_type *locals)
{
struct lookup_table *table;
@ -357,6 +373,7 @@ struct lookup_table *lookup_open(char *symtab_path, char *symvers_path,
ERROR("malloc table");
memset(table, 0, sizeof(*table));
table->objname = objname;
symtab_read(table, symtab_path);
symvers_read(table, symvers_path);
find_local_syms(table, hint, locals);
@ -382,20 +399,20 @@ void lookup_close(struct lookup_table *table)
free(table);
}
int lookup_local_symbol(struct lookup_table *table, char *name,
struct lookup_result *result)
static bool lookup_local_symbol(struct lookup_table *table, char *name,
struct lookup_result *result)
{
struct object_symbol *sym;
unsigned long pos = 0;
int i, match = 0, in_file = 0;
unsigned long sympos = 0;
int i, in_file = 0;
if (!table->local_syms)
return 1;
return false;
memset(result, 0, sizeof(*result));
for_each_obj_symbol(i, sym, table) {
if (sym->bind == STB_LOCAL && !strcmp(sym->name, name))
pos++;
sympos++;
if (table->local_syms == sym) {
in_file = 1;
@ -409,22 +426,59 @@ int lookup_local_symbol(struct lookup_table *table, char *name,
break;
if (sym->bind == STB_LOCAL && !strcmp(sym->name, name)) {
match = 1;
break;
if (result->objname)
ERROR("duplicate local symbol found for %s", name);
result->objname = table->objname;
result->addr = sym->addr;
result->size = sym->size;
result->sympos = sympos;
result->global = false;
result->exported = false;
}
}
if (!match)
return 1;
result->pos = pos;
result->value = sym->value;
result->size = sym->size;
return 0;
return !!result->objname;
}
int lookup_global_symbol(struct lookup_table *table, char *name,
struct lookup_result *result)
static bool lookup_exported_symbol(struct lookup_table *table, char *name,
struct lookup_result *result)
{
struct export_symbol *sym;
int i;
if (result)
memset(result, 0, sizeof(*result));
for_each_exp_symbol(i, sym, table) {
if (!strcmp(sym->name, name)) {
if (!result)
return true;
if (result->objname)
ERROR("duplicate exported symbol found for %s", name);
result->objname = sym->objname;
result->addr = 0; /* determined at runtime */
result->size = 0; /* not used for exported symbols */
result->sympos = 0; /* always 0 for exported symbols */
result->global = true;
result->exported = true;
}
}
return result && result->objname;
}
bool is_exported(struct lookup_table *table, char *name)
{
return lookup_exported_symbol(table, name, NULL);
}
static bool lookup_global_symbol(struct lookup_table *table, char *name,
struct lookup_result *result)
{
struct object_symbol *sym;
int i;
@ -433,91 +487,30 @@ int lookup_global_symbol(struct lookup_table *table, char *name,
for_each_obj_symbol(i, sym, table) {
if ((sym->bind == STB_GLOBAL || sym->bind == STB_WEAK) &&
!strcmp(sym->name, name)) {
result->value = sym->value;
result->size = sym->size;
result->pos = 0; /* always 0 for global symbols */
return 0;
if (result->objname)
ERROR("duplicate global symbol found for %s", name);
result->objname = table->objname;
result->addr = sym->addr;
result->size = sym->size;
result->sympos = 0; /* always 0 for global symbols */
result->global = true;
result->exported = is_exported(table, name);
}
}
return 1;
return !!result->objname;
}
int lookup_is_exported_symbol(struct lookup_table *table, char *name)
bool lookup_symbol(struct lookup_table *table, char *name,
struct lookup_result *result)
{
struct export_symbol *sym, *match = NULL;
int i;
if (lookup_local_symbol(table, name, result))
return true;
for_each_exp_symbol(i, sym, table) {
if (!strcmp(sym->name, name)) {
if (match)
ERROR("duplicate exported symbol found for %s", name);
match = sym;
}
}
if (lookup_global_symbol(table, name, result))
return true;
return !!match;
return lookup_exported_symbol(table, name, result);
}
/*
* lookup_exported_symbol_objname - find the object/module an exported
* symbol belongs to.
*/
char *lookup_exported_symbol_objname(struct lookup_table *table, char *name)
{
struct export_symbol *sym, *match = NULL;
int i;
for_each_exp_symbol(i, sym, table) {
if (!strcmp(sym->name, name)) {
if (match)
ERROR("duplicate exported symbol found for %s", name);
match = sym;
}
}
if (match)
return match->objname;
return NULL;
}
#if 0 /* for local testing */
static void find_this(struct lookup_table *table, char *sym, char *hint)
{
struct lookup_result result;
if (hint)
lookup_local_symbol(table, sym, hint, &result);
else
lookup_global_symbol(table, sym, &result);
printf("%s %s w/ %s hint at 0x%016lx len %lu pos %lu\n",
hint ? "local" : "global", sym, hint ? hint : "no",
result.value, result.size, result.pos);
}
int main(int argc, char **argv)
{
struct lookup_table *vmlinux;
if (argc != 2)
return 1;
vmlinux = lookup_open(argv[1]);
printf("printk is%s exported\n",
lookup_is_exported_symbol(vmlinux, "__fentry__") ? "" : " not");
printf("meminfo_proc_show is%s exported\n",
lookup_is_exported_symbol(vmlinux, "meminfo_proc_show") ? "" : " not");
find_this(vmlinux, "printk", NULL);
find_this(vmlinux, "pages_to_scan_show", "ksm.c");
find_this(vmlinux, "pages_to_scan_show", "huge_memory.c");
find_this(vmlinux, "pages_to_scan_show", NULL); /* should fail */
lookup_close(vmlinux);
return 0;
}
#endif

View File

@ -1,12 +1,16 @@
#ifndef _LOOKUP_H_
#define _LOOKUP_H_
#include <stdbool.h>
struct lookup_table;
struct lookup_result {
unsigned long value;
char *objname;
unsigned long addr;
unsigned long size;
unsigned long pos;
unsigned long sympos;
bool global, exported;
};
struct sym_compare_type {
@ -14,14 +18,11 @@ struct sym_compare_type {
int type;
};
struct lookup_table *lookup_open(char *symtab_path, char *symvers_path,
char *hint, struct sym_compare_type *locals);
struct lookup_table *lookup_open(char *symtab_path, char *objname,
char *symvers_path, char *hint,
struct sym_compare_type *locals);
void lookup_close(struct lookup_table *table);
int lookup_local_symbol(struct lookup_table *table, char *name,
struct lookup_result *result);
int lookup_global_symbol(struct lookup_table *table, char *name,
struct lookup_result *result);
int lookup_is_exported_symbol(struct lookup_table *table, char *name);
char *lookup_exported_symbol_objname(struct lookup_table *table, char *name);
bool lookup_symbol(struct lookup_table *table, char *name,
struct lookup_result *result);
#endif /* _LOOKUP_H_ */