mirror of
https://github.com/dynup/kpatch
synced 2025-05-09 11:27:57 +00:00
commit
5432c86ded
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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 */
|
||||
|
@ -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
|
||||
|
@ -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_ */
|
||||
|
Loading…
Reference in New Issue
Block a user