create-diff-object: percpu support

The -fdata-sections gcc flag doesn't work with objects in the
.data..percpu section.  Any function which uses a percpu variable
references this section, causing the section to get incorrectly included
in the patch module.

Manually convert these section references to object symbol references so
that the needed symbol can be found in vmlinux.

Also, the core module symbol verification code will fail when looking up
a percpu variable, because sprint_symbol doesn't think a percpu address
is a valid kernel address.  So rewrite the symbol verification code to
use kallsyms_on_each_symbol() instead.  It's not ideal performance-wise:
it seems to cost about 1ms per symbol lookup.  I think that's acceptable
for now.  In the future we may want to try to get a better upstream
kallsyms interface.
This commit is contained in:
Josh Poimboeuf 2014-05-26 21:56:06 -05:00
parent 6aee215b04
commit 0ebbed244e
2 changed files with 58 additions and 36 deletions

View File

@ -69,6 +69,11 @@ struct kpatch_backtrace_args {
int ret;
};
struct kpatch_kallsyms_args {
char *name;
unsigned long addr;
};
/*
* The kpatch core module has a state machine which allows for proper
* synchronization with kpatch_ftrace_handler() when it runs in NMI context.
@ -421,41 +426,29 @@ static void kpatch_remove_funcs_from_filter(struct kpatch_func *funcs,
}
}
int kpatch_verify_symbol_match(char *name, unsigned long addr)
static int kpatch_kallsyms_callback(void *data, const char *name,
struct module *mod,
unsigned long addr)
{
struct kpatch_kallsyms_args *args = data;
if (args->addr == addr && !strcmp(args->name, name))
return 1;
return 0;
}
static int kpatch_verify_symbol_match(char *name, unsigned long addr)
{
int ret;
unsigned long offset;
char buf[KSYM_SYMBOL_LEN], *symname, *offsetstr, *pos;
sprint_symbol(buf, addr);
struct kpatch_kallsyms_args args = {
.name = name,
.addr = addr,
};
/*
* sprint_symbol() doesn't return an error if it can't find the symbol.
* It returns the addr we passed in as a hex string in 'name'.
* Check for this and, if so, error.
*/
if (!strncmp(buf, "0x", 2)) {
pr_err("failed to find named symbol\n");
return -EINVAL;
}
/* hack out the symbol name and offset */
/* example format for buf 'printk+0x0/0x8e' */
symname = buf;
pos = strchr(buf, '+');
*pos = '\0';
pos += 3; /* skip the "+0x" */
offsetstr = pos;
pos = strchr(offsetstr, '/');
*pos = '\0';
ret = kstrtoul(offsetstr, 16, &offset);
if (ret) {
pr_err("failed to parse symbol offset\n");
return ret;
}
if (strcmp(symname, name) || offset != 0) {
ret = kallsyms_on_each_symbol(kpatch_kallsyms_callback, &args);
if (!ret) {
pr_err("base kernel mismatch for symbol '%s'\n", name);
pr_err("expected address was 0x%016lx\n", addr);
return -EINVAL;

View File

@ -688,20 +688,49 @@ void kpatch_replace_sections_syms(struct kpatch_elf *kelf)
{
struct section *sec;
struct rela *rela;
struct symbol *sym;
list_for_each_entry(sec, &kelf->sections, list) {
if (!is_rela_section(sec))
continue;
list_for_each_entry(rela, &sec->relas, list) {
if (rela->sym->type != STT_SECTION ||
!rela->sym->sec || !rela->sym->sec->sym)
/*
* Replace references to bundled sections with their
* symbols.
*/
if (rela->sym->type == STT_SECTION &&
rela->sym->sec && rela->sym->sec->sym) {
log_debug("replacing %s with %s\n",
rela->sym->name,
rela->sym->sec->sym->name);
rela->sym = rela->sym->sec->sym;
continue;
}
log_debug("replacing %s with %s\n",
rela->sym->name, rela->sym->sec->sym->name);
/*
* .data..percpu is a special data section whose data
* symbols aren't bundled with sections when using
* -fdata-sections. We need to replace the section
* references with their corresponding objects.
*/
if (strcmp(rela->sym->name, ".data..percpu"))
continue;
list_for_each_entry(sym, &kelf->symbols, list) {
rela->sym = rela->sym->sec->sym;
if (sym->sec != rela->sym->sec ||
sym->sym.st_value != rela->addend)
continue;
log_debug("replacing %s with %s\n",
rela->sym->name, sym->name);
rela->sym = sym;
break;
}
}
}
}