[PATCH 22/27] objtool: Add options to write/read exported noreturns to/from a file
Josh Poimboeuf <[email protected]>
| Newsgroups | gmane.linux.kernel,gmane.linux.kernel.rust,gmane.linux.kbuild.devel |
|---|---|
| Message-ID | <109a1033440e9bb33a73dc42599049f18f60df1c.1787890035.git.jpoimboe@kernel.org> |
Add --noreturns-write which can be used to write vmlinux's exported noreturns to a file, and --noreturn-read which can be used to read them when running objtool on a module. Signed-off-by: Josh Poimboeuf <[email protected]> --- tools/objtool/builtin-check.c | 12 ++ tools/objtool/check.c | 148 +++++++++++++++++++++++- tools/objtool/include/objtool/builtin.h | 2 + tools/objtool/include/objtool/elf.h | 1 + 4 files changed, 162 insertions(+), 1 deletion(-) diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c index 75b11dc85010e..c4c4210f84662 100644 --- a/tools/objtool/builtin-check.c +++ b/tools/objtool/builtin-check.c @@ -101,6 +101,8 @@ static const struct option check_options[] = { OPT_BOOLEAN(0, "module", &opts.module, "object is part of a kernel module"), OPT_BOOLEAN(0, "mnop", &opts.mnop, "nop out mcount call sites"), OPT_BOOLEAN(0, "no-unreachable", &opts.no_unreachable, "skip 'unreachable instruction' warnings"), + OPT_STRING(0, "noreturns-read", &opts.noreturns_read, "file", "read exported noreturns from a file"), + OPT_STRING(0, "noreturns-write", &opts.noreturns_write, "file", "write exported noreturns to a file"), OPT_STRING('o', "output", &opts.output, "file", "output file name"), OPT_BOOLEAN(0, "sec-address", &opts.sec_address, "print section addresses in warnings"), OPT_BOOLEAN(0, "stats", &opts.stats, "print statistics"), @@ -180,6 +182,16 @@ static bool opts_valid(void) return false; } + if (opts.noreturns_write && !opts.link) { + ERROR("--noreturns-write requires --link"); + return false; + } + + if (opts.noreturns_write && !opts.stackval && !opts.orc && !opts.uaccess) { + ERROR("--noreturns-write requires --stackval, --orc, or --uaccess"); + return false; + } + if (opts.disas || opts.hack_jump_label || opts.hack_noinstr || diff --git a/tools/objtool/check.c b/tools/objtool/check.c index d27303220c29d..086e8f9eca867 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -339,6 +339,120 @@ static void detect_noreturns(struct objtool_file *file) } } +static bool noreturns_changed(const char *buf, size_t len) +{ + bool changed = true; + char *old = NULL; + FILE *fp; + + fp = fopen(opts.noreturns_write, "r"); + if (!fp) + return true; + + old = malloc(len + 1); + if (!old) + goto out; + + if (fread(old, 1, len + 1, fp) != len) + goto out; + + if (memcmp(old, buf, len)) + goto out; + + changed = false; + +out: + free(old); + fclose(fp); + return changed; +} + +/* + * This is called for vmlinux.o. Write the vmlinux.o noreturn list to a file + * so it can be read in module objtool runs by read_noreturns(). + */ +static int write_noreturns(struct objtool_file *file) +{ + struct symbol *func; + char *buf, *pos; + size_t len = 0; + FILE *fp; + + for_each_sym(file->elf, func) { + if (is_noreturn(func) && func->exported) + len += strlen(func->name) + 1; + } + + buf = malloc(len + 1); + if (!buf) { + ERROR_GLIBC("malloc"); + return -1; + } + + pos = buf; + for_each_sym(file->elf, func) { + if (is_noreturn(func) && func->exported) + pos += sprintf(pos, "%s\n", func->name); + } + + /* + * Only write the file when the contents have change to avoid relinking + * all the modules unnecessarily. + */ + if (!noreturns_changed(buf, len)) { + free(buf); + return 0; + } + + fp = fopen(opts.noreturns_write, "w"); + if (!fp) { + ERROR_GLIBC("fopen"); + return -1; + } + + if (fwrite(buf, 1, len, fp) != len) { + ERROR_GLIBC("fwrite"); + return -1; + } + + free(buf); + + if (fclose(fp)) { + ERROR_GLIBC("fclose"); + return -1; + } + + return 0; +} + +/* + * This is called for modules. Read the noreturn list generated by the + * vmlinux.o pass and mark the corresponding undefined symbols noreturn. + */ +static int read_noreturns(struct objtool_file *file) +{ + char line[SYM_NAME_LEN]; + struct symbol *func; + FILE *fp; + + fp = fopen(opts.noreturns_read, "r"); + if (!fp) { + ERROR("can't open '%s'", opts.noreturns_read); + return -1; + } + + while (fgets(line, sizeof(line), fp)) { + line[strcspn(line, "\n")] = '\0'; + + func = find_global_symbol_by_name(file->elf, line); + if (func && is_undef_sym(func)) + func->_noreturn = 1; + } + + fclose(fp); + return 0; +} + static void init_cfi_state(struct cfi_state *cfi) { int i; @@ -2236,6 +2350,24 @@ static int add_jump_table_alts(struct objtool_file *file) return 0; } +static void read_exports(struct objtool_file *file) +{ + struct section *sec; + struct symbol *func; + struct reloc *reloc; + + sec = find_section_by_name(file->elf, ".export_symbol"); + if (!sec || !sec->rsec) + return; + + for_each_reloc(sec->rsec, reloc) { + func = find_func_by_offset(reloc->sym->sec, + reloc->sym->offset + reloc_addend(reloc)); + if (func) + func->exported = 1; + } +} + static void set_func_state(struct cfi_state *state) { state->cfa = initial_func_cfi.cfa; @@ -2720,8 +2852,10 @@ int decode_file(struct objtool_file *file) * both validate_functions() and validate_noinstr_sections() -- and by * validate_unret(). */ - if (validate_branch_enabled() || opts.noinstr || opts.unret) + if (validate_branch_enabled() || opts.noinstr || opts.unret) { + read_exports(file); detect_noreturns(file); + } if (read_unwind_hints(file)) return -1; @@ -4888,6 +5022,12 @@ int check(struct objtool_file *file) objtool_disas_ctx = disas_ctx; } + if (opts.noreturns_read) { + ret = read_noreturns(file); + if (ret) + goto out; + } + ret = decode_file(file); if (ret) goto out; @@ -4995,6 +5135,12 @@ int check(struct objtool_file *file) goto out; } + if (opts.noreturns_write && !opts.dryrun) { + ret = write_noreturns(file); + if (ret) + goto out; + } + if (opts.stats) { printf("nr_insns_visited: %ld\n", nr_insns_visited); printf("nr_cfi: %ld\n", nr_cfi); diff --git a/tools/objtool/include/objtool/builtin.h b/tools/objtool/include/objtool/builtin.h index 349690bb1c50e..fb33d57a54c95 100644 --- a/tools/objtool/include/objtool/builtin.h +++ b/tools/objtool/include/objtool/builtin.h @@ -41,6 +41,8 @@ struct opts { bool mnop; bool module; bool no_unreachable; + const char *noreturns_read; + const char *noreturns_write; const char *output; bool sec_address; bool stats; diff --git a/tools/objtool/include/objtool/elf.h b/tools/objtool/include/objtool/elf.h index 0e3593d993ac8..adf6e1322c6df 100644 --- a/tools/objtool/include/objtool/elf.h +++ b/tools/objtool/include/objtool/elf.h @@ -100,6 +100,7 @@ struct symbol { u8 fake : 1; u8 _noreturn : 1; u8 ignore_noreturn : 1; + u8 exported : 1; struct list_head pv_target; struct reloc *relocs; struct section *group_sec; -- 2.55.0