create-diff-object: refactor dynrela conversion

The dynrela (aka .klp.rela) conversion logic is notoriously complex and
fragile.  Simplify it and improve the comments.

This is mainly a cosmetic change.  In theory it shouldn't change
functionality or break anything.

Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com>
This commit is contained in:
Josh Poimboeuf 2018-03-22 15:22:10 -05:00
parent d2089a4d72
commit 0a3e6c5f42
3 changed files with 259 additions and 243 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 &&
@ -2746,16 +2772,15 @@ static void kpatch_create_patches_sections(struct kpatch_elf *kelf,
sym->parent)
continue;
if (sym->bind == STB_LOCAL) {
if (!lookup_local_symbol(table, sym->name, &symbol))
ERROR("lookup_local_symbol %s", sym->name);
} else {
if (!lookup_global_symbol(table, sym->name, &symbol))
ERROR("lookup_global_symbol %s", sym->name);
}
if (!lookup_symbol(table, sym->name, &symbol))
ERROR("can't find symbol '%s' in symbol table", sym->name);
log_debug("lookup for %s @ 0x%016lx len %lu\n",
sym->name, symbol.addr, symbol.size);
if (sym->bind == STB_LOCAL && symbol.global)
ERROR("can't find local symbol '%s' in symbol table", sym->name);
log_debug("lookup for %s: obj=%s sympos=%lu size=%lu",
sym->name, symbol.objname, symbol.sympos,
symbol.size);
/*
* Convert global symbols to local so other objects in the
@ -2821,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,
@ -2903,8 +3030,7 @@ static void kpatch_create_intermediate_sections(struct kpatch_elf *kelf,
struct kpatch_symbol *ksyms;
struct kpatch_relocation *krelas;
struct lookup_result symbol;
char *sym_objname;
int vmlinux, external;
bool vmlinux;
vmlinux = !strcmp(objname, "vmlinux");
@ -2916,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;
}
}
@ -2973,129 +3096,13 @@ 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 */
if (!lookup_local_symbol(table, rela->sym->name,
&symbol))
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,
&symbol))
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,
&symbol)) {
/*
* 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, symbol.addr, symbol.size);
log_debug("lookup for %s: obj=%s sympos=%lu",
rela->sym->name, symbol.objname,
symbol.sympos);
/* Fill in ksyms[index] */
if (vmlinux)
@ -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);

View File

@ -399,8 +399,8 @@ void lookup_close(struct lookup_table *table)
free(table);
}
bool 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 sympos = 0;
@ -434,14 +434,51 @@ bool lookup_local_symbol(struct lookup_table *table, char *name,
result->addr = sym->addr;
result->size = sym->size;
result->sympos = sympos;
result->global = false;
result->exported = false;
}
}
return !!result->objname;
}
bool 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;
@ -458,47 +495,22 @@ bool lookup_global_symbol(struct lookup_table *table, char *name,
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 !!result->objname;
}
bool 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;
}
/*
* 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;
return lookup_exported_symbol(table, name, result);
}

View File

@ -10,6 +10,7 @@ struct lookup_result {
unsigned long addr;
unsigned long size;
unsigned long sympos;
bool global, exported;
};
struct sym_compare_type {
@ -21,11 +22,7 @@ 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);
bool lookup_local_symbol(struct lookup_table *table, char *name,
struct lookup_result *result);
bool lookup_global_symbol(struct lookup_table *table, char *name,
struct lookup_result *result);
bool 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_ */