From 55d2a473f317ab028d78a5c5ca69473643657c3d Mon Sep 17 00:00:00 2001 From: Alexandre Chartre Date: Fri, 21 Nov 2025 10:53:11 +0100 Subject: objtool: Move disassembly functions to a separated file objtool disassembles functions which have warnings. Move the code to do that to a dedicated file. The code is just moved, it is not changed. Signed-off-by: Alexandre Chartre Signed-off-by: Peter Zijlstra (Intel) Acked-by: Josh Poimboeuf Link: https://patch.msgid.link/20251121095340.464045-2-alexandre.chartre@oracle.com --- tools/objtool/disas.c | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 tools/objtool/disas.c (limited to 'tools/objtool/disas.c') diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c new file mode 100644 index 000000000000..3a7cb1b8002e --- /dev/null +++ b/tools/objtool/disas.c @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2015-2017 Josh Poimboeuf + */ + +#include +#include + +#include + +/* 'funcs' is a space-separated list of function names */ +static void disas_funcs(const char *funcs) +{ + const char *objdump_str, *cross_compile; + int size, ret; + char *cmd; + + cross_compile = getenv("CROSS_COMPILE"); + if (!cross_compile) + cross_compile = ""; + + objdump_str = "%sobjdump -wdr %s | gawk -M -v _funcs='%s' '" + "BEGIN { split(_funcs, funcs); }" + "/^$/ { func_match = 0; }" + "/<.*>:/ { " + "f = gensub(/.*<(.*)>:/, \"\\\\1\", 1);" + "for (i in funcs) {" + "if (funcs[i] == f) {" + "func_match = 1;" + "base = strtonum(\"0x\" $1);" + "break;" + "}" + "}" + "}" + "{" + "if (func_match) {" + "addr = strtonum(\"0x\" $1);" + "printf(\"%%04x \", addr - base);" + "print;" + "}" + "}' 1>&2"; + + /* fake snprintf() to calculate the size */ + size = snprintf(NULL, 0, objdump_str, cross_compile, objname, funcs) + 1; + if (size <= 0) { + WARN("objdump string size calculation failed"); + return; + } + + cmd = malloc(size); + + /* real snprintf() */ + snprintf(cmd, size, objdump_str, cross_compile, objname, funcs); + ret = system(cmd); + if (ret) { + WARN("disassembly failed: %d", ret); + return; + } +} + +void disas_warned_funcs(struct objtool_file *file) +{ + struct symbol *sym; + char *funcs = NULL, *tmp; + + for_each_sym(file->elf, sym) { + if (sym->warned) { + if (!funcs) { + funcs = malloc(strlen(sym->name) + 1); + if (!funcs) { + ERROR_GLIBC("malloc"); + return; + } + strcpy(funcs, sym->name); + } else { + tmp = malloc(strlen(funcs) + strlen(sym->name) + 2); + if (!tmp) { + ERROR_GLIBC("malloc"); + return; + } + sprintf(tmp, "%s %s", funcs, sym->name); + free(funcs); + funcs = tmp; + } + } + } + + if (funcs) + disas_funcs(funcs); +} -- cgit v1.2.3 From 1013f2e37bec39b1df5679e1c1e2572ece87c088 Mon Sep 17 00:00:00 2001 From: Alexandre Chartre Date: Fri, 21 Nov 2025 10:53:12 +0100 Subject: objtool: Create disassembly context Create a structure to store information for disassembling functions. For now, it is just a wrapper around an objtool file. Signed-off-by: Alexandre Chartre Signed-off-by: Peter Zijlstra (Intel) Acked-by: Josh Poimboeuf Link: https://patch.msgid.link/20251121095340.464045-3-alexandre.chartre@oracle.com --- tools/objtool/check.c | 6 +++++- tools/objtool/disas.c | 32 ++++++++++++++++++++++++++++++-- tools/objtool/include/objtool/disas.h | 14 ++++++++++++++ tools/objtool/include/objtool/objtool.h | 2 -- 4 files changed, 49 insertions(+), 5 deletions(-) create mode 100644 tools/objtool/include/objtool/disas.h (limited to 'tools/objtool/disas.c') diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 1c7186f7af2f..8b1a6a5185d3 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -4802,6 +4803,7 @@ static void free_insns(struct objtool_file *file) int check(struct objtool_file *file) { + struct disas_context *disas_ctx; int ret = 0, warnings = 0; arch_initial_func_cfi_state(&initial_func_cfi); @@ -4943,7 +4945,9 @@ out: if (opts.verbose) { if (opts.werror && warnings) WARN("%d warning(s) upgraded to errors", warnings); - disas_warned_funcs(file); + disas_ctx = disas_context_create(file); + disas_warned_funcs(disas_ctx); + disas_context_destroy(disas_ctx); } if (opts.backup && make_backup()) diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c index 3a7cb1b8002e..7a18e51d43e6 100644 --- a/tools/objtool/disas.c +++ b/tools/objtool/disas.c @@ -4,10 +4,35 @@ */ #include +#include #include #include +struct disas_context { + struct objtool_file *file; +}; + +struct disas_context *disas_context_create(struct objtool_file *file) +{ + struct disas_context *dctx; + + dctx = malloc(sizeof(*dctx)); + if (!dctx) { + WARN("failed to allocate disassembly context"); + return NULL; + } + + dctx->file = file; + + return dctx; +} + +void disas_context_destroy(struct disas_context *dctx) +{ + free(dctx); +} + /* 'funcs' is a space-separated list of function names */ static void disas_funcs(const char *funcs) { @@ -58,12 +83,15 @@ static void disas_funcs(const char *funcs) } } -void disas_warned_funcs(struct objtool_file *file) +void disas_warned_funcs(struct disas_context *dctx) { struct symbol *sym; char *funcs = NULL, *tmp; - for_each_sym(file->elf, sym) { + if (!dctx) + return; + + for_each_sym(dctx->file->elf, sym) { if (sym->warned) { if (!funcs) { funcs = malloc(strlen(sym->name) + 1); diff --git a/tools/objtool/include/objtool/disas.h b/tools/objtool/include/objtool/disas.h new file mode 100644 index 000000000000..5c543b69fc61 --- /dev/null +++ b/tools/objtool/include/objtool/disas.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. + */ + +#ifndef _DISAS_H +#define _DISAS_H + +struct disas_context; +struct disas_context *disas_context_create(struct objtool_file *file); +void disas_context_destroy(struct disas_context *dctx); +void disas_warned_funcs(struct disas_context *dctx); + +#endif /* _DISAS_H */ diff --git a/tools/objtool/include/objtool/objtool.h b/tools/objtool/include/objtool/objtool.h index 35f926cf9c25..f7051bbe0bcb 100644 --- a/tools/objtool/include/objtool/objtool.h +++ b/tools/objtool/include/objtool/objtool.h @@ -49,6 +49,4 @@ int check(struct objtool_file *file); int orc_dump(const char *objname); int orc_create(struct objtool_file *file); -void disas_warned_funcs(struct objtool_file *file); - #endif /* _OBJTOOL_H */ -- cgit v1.2.3 From 59953303827eceb06d486ba66cc0d71f55ded8ec Mon Sep 17 00:00:00 2001 From: Alexandre Chartre Date: Fri, 21 Nov 2025 10:53:13 +0100 Subject: objtool: Disassemble code with libopcodes instead of running objdump objtool executes the objdump command to disassemble code. Use libopcodes instead to have more control about the disassembly scope and output. If libopcodes is not present then objtool is built without disassembly support. Signed-off-by: Alexandre Chartre Signed-off-by: Peter Zijlstra (Intel) Acked-by: Josh Poimboeuf Link: https://patch.msgid.link/20251121095340.464045-4-alexandre.chartre@oracle.com --- tools/objtool/.gitignore | 2 + tools/objtool/Build | 3 +- tools/objtool/Makefile | 25 +++++ tools/objtool/arch/loongarch/decode.c | 12 +++ tools/objtool/arch/powerpc/decode.c | 12 +++ tools/objtool/arch/x86/decode.c | 12 +++ tools/objtool/check.c | 14 ++- tools/objtool/disas.c | 187 ++++++++++++++++++++++------------ tools/objtool/include/objtool/arch.h | 9 ++ tools/objtool/include/objtool/check.h | 5 + tools/objtool/include/objtool/disas.h | 29 ++++++ 11 files changed, 238 insertions(+), 72 deletions(-) (limited to 'tools/objtool/disas.c') diff --git a/tools/objtool/.gitignore b/tools/objtool/.gitignore index 4faa4dd72f35..759303657bd7 100644 --- a/tools/objtool/.gitignore +++ b/tools/objtool/.gitignore @@ -1,5 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only arch/x86/lib/inat-tables.c /objtool +feature +FEATURE-DUMP.objtool fixdep libsubcmd/ diff --git a/tools/objtool/Build b/tools/objtool/Build index 17e50a1766d0..9d1e8f28ef95 100644 --- a/tools/objtool/Build +++ b/tools/objtool/Build @@ -7,7 +7,8 @@ objtool-y += special.o objtool-y += builtin-check.o objtool-y += elf.o objtool-y += objtool.o -objtool-y += disas.o + +objtool-$(BUILD_DISAS) += disas.o objtool-$(BUILD_ORC) += orc_gen.o orc_dump.o objtool-$(BUILD_KLP) += builtin-klp.o klp-diff.o klp-post-link.o diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile index 021f55b7bd87..df793ca6fc1a 100644 --- a/tools/objtool/Makefile +++ b/tools/objtool/Makefile @@ -70,6 +70,29 @@ OBJTOOL_CFLAGS += $(if $(elfshdr),,-DLIBELF_USE_DEPRECATED) # Always want host compilation. HOST_OVERRIDES := CC="$(HOSTCC)" LD="$(HOSTLD)" AR="$(HOSTAR)" +# +# To support disassembly, objtool needs libopcodes which is provided +# with libbdf (binutils-dev or binutils-devel package). +# +FEATURE_USER = .objtool +FEATURE_TESTS = libbfd disassembler-init-styled +FEATURE_DISPLAY = +include $(srctree)/tools/build/Makefile.feature + +ifeq ($(feature-disassembler-init-styled), 1) + OBJTOOL_CFLAGS += -DDISASM_INIT_STYLED +endif + +BUILD_DISAS := n + +ifeq ($(feature-libbfd),1) + BUILD_DISAS := y + OBJTOOL_CFLAGS += -DDISAS + OBJTOOL_LDFLAGS += -lopcodes +endif + +export BUILD_DISAS + AWK = awk MKDIR = mkdir @@ -103,6 +126,8 @@ clean: $(LIBSUBCMD)-clean $(call QUIET_CLEAN, objtool) $(RM) $(OBJTOOL) $(Q)find $(OUTPUT) -name '*.o' -delete -o -name '\.*.cmd' -delete -o -name '\.*.d' -delete $(Q)$(RM) $(OUTPUT)arch/x86/lib/inat-tables.c $(OUTPUT)fixdep + $(Q)$(RM) -- $(OUTPUT)FEATURE-DUMP.objtool + $(Q)$(RM) -r -- $(OUTPUT)feature FORCE: diff --git a/tools/objtool/arch/loongarch/decode.c b/tools/objtool/arch/loongarch/decode.c index 0115b97c526b..1de86ebb637d 100644 --- a/tools/objtool/arch/loongarch/decode.c +++ b/tools/objtool/arch/loongarch/decode.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include #include +#include #include #include #include @@ -414,3 +415,14 @@ unsigned long arch_jump_table_sym_offset(struct reloc *reloc, struct reloc *tabl return reloc->sym->offset + reloc_addend(reloc); } } + +#ifdef DISAS + +int arch_disas_info_init(struct disassemble_info *dinfo) +{ + return disas_info_init(dinfo, bfd_arch_loongarch, + bfd_mach_loongarch32, bfd_mach_loongarch64, + NULL); +} + +#endif /* DISAS */ diff --git a/tools/objtool/arch/powerpc/decode.c b/tools/objtool/arch/powerpc/decode.c index 3a9b748216ed..4f68b402e785 100644 --- a/tools/objtool/arch/powerpc/decode.c +++ b/tools/objtool/arch/powerpc/decode.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -127,3 +128,14 @@ unsigned int arch_reloc_size(struct reloc *reloc) return 8; } } + +#ifdef DISAS + +int arch_disas_info_init(struct disassemble_info *dinfo) +{ + return disas_info_init(dinfo, bfd_arch_powerpc, + bfd_mach_ppc, bfd_mach_ppc64, + NULL); +} + +#endif /* DISAS */ diff --git a/tools/objtool/arch/x86/decode.c b/tools/objtool/arch/x86/decode.c index cc85db7b65a4..83e9c604ce10 100644 --- a/tools/objtool/arch/x86/decode.c +++ b/tools/objtool/arch/x86/decode.c @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -949,3 +950,14 @@ bool arch_absolute_reloc(struct elf *elf, struct reloc *reloc) return false; } } + +#ifdef DISAS + +int arch_disas_info_init(struct disassemble_info *dinfo) +{ + return disas_info_init(dinfo, bfd_arch_i386, + bfd_mach_i386_i386, bfd_mach_x86_64, + "att"); +} + +#endif /* DISAS */ diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 8b1a6a5185d3..21d45a35f3c9 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -4926,8 +4926,6 @@ int check(struct objtool_file *file) goto out; } - free_insns(file); - if (opts.stats) { printf("nr_insns_visited: %ld\n", nr_insns_visited); printf("nr_cfi: %ld\n", nr_cfi); @@ -4936,8 +4934,10 @@ int check(struct objtool_file *file) } out: - if (!ret && !warnings) + if (!ret && !warnings) { + free_insns(file); return 0; + } if (opts.werror && warnings) ret = 1; @@ -4946,10 +4946,14 @@ out: if (opts.werror && warnings) WARN("%d warning(s) upgraded to errors", warnings); disas_ctx = disas_context_create(file); - disas_warned_funcs(disas_ctx); - disas_context_destroy(disas_ctx); + if (disas_ctx) { + disas_warned_funcs(disas_ctx); + disas_context_destroy(disas_ctx); + } } + free_insns(file); + if (opts.backup && make_backup()) return 1; diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c index 7a18e51d43e6..11ac2ec04afc 100644 --- a/tools/objtool/disas.c +++ b/tools/objtool/disas.c @@ -4,18 +4,56 @@ */ #include +#include #include #include +#include #include +#include struct disas_context { struct objtool_file *file; + disassembler_ftype disassembler; + struct disassemble_info info; }; +#define DINFO_FPRINTF(dinfo, ...) \ + ((*(dinfo)->fprintf_func)((dinfo)->stream, __VA_ARGS__)) + +/* + * Initialize disassemble info arch, mach (32 or 64-bit) and options. + */ +int disas_info_init(struct disassemble_info *dinfo, + int arch, int mach32, int mach64, + const char *options) +{ + struct disas_context *dctx = dinfo->application_data; + struct objtool_file *file = dctx->file; + + dinfo->arch = arch; + + switch (file->elf->ehdr.e_ident[EI_CLASS]) { + case ELFCLASS32: + dinfo->mach = mach32; + break; + case ELFCLASS64: + dinfo->mach = mach64; + break; + default: + return -1; + } + + dinfo->disassembler_options = options; + + return 0; +} + struct disas_context *disas_context_create(struct objtool_file *file) { struct disas_context *dctx; + struct disassemble_info *dinfo; + int err; dctx = malloc(sizeof(*dctx)); if (!dctx) { @@ -24,8 +62,49 @@ struct disas_context *disas_context_create(struct objtool_file *file) } dctx->file = file; + dinfo = &dctx->info; + + init_disassemble_info_compat(dinfo, stdout, + (fprintf_ftype)fprintf, + fprintf_styled); + + dinfo->read_memory_func = buffer_read_memory; + dinfo->application_data = dctx; + + /* + * bfd_openr() is not used to avoid doing ELF data processing + * and caching that has already being done. Here, we just need + * to identify the target file so we call an arch specific + * function to fill some disassemble info (arch, mach). + */ + + dinfo->arch = bfd_arch_unknown; + dinfo->mach = 0; + + err = arch_disas_info_init(dinfo); + if (err || dinfo->arch == bfd_arch_unknown || dinfo->mach == 0) { + WARN("failed to init disassembly arch"); + goto error; + } + + dinfo->endian = (file->elf->ehdr.e_ident[EI_DATA] == ELFDATA2MSB) ? + BFD_ENDIAN_BIG : BFD_ENDIAN_LITTLE; + + disassemble_init_for_target(dinfo); + + dctx->disassembler = disassembler(dinfo->arch, + dinfo->endian == BFD_ENDIAN_BIG, + dinfo->mach, NULL); + if (!dctx->disassembler) { + WARN("failed to create disassembler function"); + goto error; + } return dctx; + +error: + free(dctx); + return NULL; } void disas_context_destroy(struct disas_context *dctx) @@ -33,86 +112,62 @@ void disas_context_destroy(struct disas_context *dctx) free(dctx); } -/* 'funcs' is a space-separated list of function names */ -static void disas_funcs(const char *funcs) +/* + * Disassemble a single instruction. Return the size of the instruction. + */ +static size_t disas_insn(struct disas_context *dctx, + struct instruction *insn) { - const char *objdump_str, *cross_compile; - int size, ret; - char *cmd; - - cross_compile = getenv("CROSS_COMPILE"); - if (!cross_compile) - cross_compile = ""; - - objdump_str = "%sobjdump -wdr %s | gawk -M -v _funcs='%s' '" - "BEGIN { split(_funcs, funcs); }" - "/^$/ { func_match = 0; }" - "/<.*>:/ { " - "f = gensub(/.*<(.*)>:/, \"\\\\1\", 1);" - "for (i in funcs) {" - "if (funcs[i] == f) {" - "func_match = 1;" - "base = strtonum(\"0x\" $1);" - "break;" - "}" - "}" - "}" - "{" - "if (func_match) {" - "addr = strtonum(\"0x\" $1);" - "printf(\"%%04x \", addr - base);" - "print;" - "}" - "}' 1>&2"; - - /* fake snprintf() to calculate the size */ - size = snprintf(NULL, 0, objdump_str, cross_compile, objname, funcs) + 1; - if (size <= 0) { - WARN("objdump string size calculation failed"); - return; + disassembler_ftype disasm = dctx->disassembler; + struct disassemble_info *dinfo = &dctx->info; + + if (insn->type == INSN_NOP) { + DINFO_FPRINTF(dinfo, "nop%d", insn->len); + return insn->len; } - cmd = malloc(size); + /* + * Set the disassembler buffer to read data from the section + * containing the instruction to disassemble. + */ + dinfo->buffer = insn->sec->data->d_buf; + dinfo->buffer_vma = 0; + dinfo->buffer_length = insn->sec->sh.sh_size; - /* real snprintf() */ - snprintf(cmd, size, objdump_str, cross_compile, objname, funcs); - ret = system(cmd); - if (ret) { - WARN("disassembly failed: %d", ret); - return; + return disasm(insn->offset, &dctx->info); +} + +/* + * Disassemble a function. + */ +static void disas_func(struct disas_context *dctx, struct symbol *func) +{ + struct instruction *insn; + size_t addr; + + printf("%s:\n", func->name); + sym_for_each_insn(dctx->file, func, insn) { + addr = insn->offset; + printf(" %6lx: %s+0x%-6lx ", + addr, func->name, addr - func->offset); + disas_insn(dctx, insn); + printf("\n"); } + printf("\n"); } +/* + * Disassemble all warned functions. + */ void disas_warned_funcs(struct disas_context *dctx) { struct symbol *sym; - char *funcs = NULL, *tmp; if (!dctx) return; for_each_sym(dctx->file->elf, sym) { - if (sym->warned) { - if (!funcs) { - funcs = malloc(strlen(sym->name) + 1); - if (!funcs) { - ERROR_GLIBC("malloc"); - return; - } - strcpy(funcs, sym->name); - } else { - tmp = malloc(strlen(funcs) + strlen(sym->name) + 2); - if (!tmp) { - ERROR_GLIBC("malloc"); - return; - } - sprintf(tmp, "%s %s", funcs, sym->name); - free(funcs); - funcs = tmp; - } - } + if (sym->warned) + disas_func(dctx, sym); } - - if (funcs) - disas_funcs(funcs); } diff --git a/tools/objtool/include/objtool/arch.h b/tools/objtool/include/objtool/arch.h index d89f8b5ec14e..18c0e69ee617 100644 --- a/tools/objtool/include/objtool/arch.h +++ b/tools/objtool/include/objtool/arch.h @@ -103,4 +103,13 @@ bool arch_absolute_reloc(struct elf *elf, struct reloc *reloc); unsigned int arch_reloc_size(struct reloc *reloc); unsigned long arch_jump_table_sym_offset(struct reloc *reloc, struct reloc *table); +#ifdef DISAS + +#include +#include + +int arch_disas_info_init(struct disassemble_info *dinfo); + +#endif /* DISAS */ + #endif /* _ARCH_H */ diff --git a/tools/objtool/include/objtool/check.h b/tools/objtool/include/objtool/check.h index d73b0c3ae1ee..674f57466d12 100644 --- a/tools/objtool/include/objtool/check.h +++ b/tools/objtool/include/objtool/check.h @@ -127,4 +127,9 @@ struct instruction *next_insn_same_sec(struct objtool_file *file, struct instruc insn && insn->sec == _sec; \ insn = next_insn_same_sec(file, insn)) +#define sym_for_each_insn(file, sym, insn) \ + for (insn = find_insn(file, sym->sec, sym->offset); \ + insn && insn->offset < sym->offset + sym->len; \ + insn = next_insn_same_sec(file, insn)) + #endif /* _CHECK_H */ diff --git a/tools/objtool/include/objtool/disas.h b/tools/objtool/include/objtool/disas.h index 5c543b69fc61..3ec3ce2e4e6f 100644 --- a/tools/objtool/include/objtool/disas.h +++ b/tools/objtool/include/objtool/disas.h @@ -7,8 +7,37 @@ #define _DISAS_H struct disas_context; +struct disassemble_info; + +#ifdef DISAS + struct disas_context *disas_context_create(struct objtool_file *file); void disas_context_destroy(struct disas_context *dctx); void disas_warned_funcs(struct disas_context *dctx); +int disas_info_init(struct disassemble_info *dinfo, + int arch, int mach32, int mach64, + const char *options); + +#else /* DISAS */ + +#include + +static inline struct disas_context *disas_context_create(struct objtool_file *file) +{ + WARN("Rebuild with libopcodes for disassembly support"); + return NULL; +} + +static inline void disas_context_destroy(struct disas_context *dctx) {} +static inline void disas_warned_funcs(struct disas_context *dctx) {} + +static inline int disas_info_init(struct disassemble_info *dinfo, + int arch, int mach32, int mach64, + const char *options) +{ + return -1; +} + +#endif /* DISAS */ #endif /* _DISAS_H */ -- cgit v1.2.3 From 5d859dff266f7e57664dc6bcf80ef2c66547c58a Mon Sep 17 00:00:00 2001 From: Alexandre Chartre Date: Fri, 21 Nov 2025 10:53:15 +0100 Subject: objtool: Print symbol during disassembly Print symbols referenced during disassembly instead of just printing raw addresses. Also handle address relocation. Signed-off-by: Alexandre Chartre Signed-off-by: Peter Zijlstra (Intel) Acked-by: Josh Poimboeuf Link: https://patch.msgid.link/20251121095340.464045-6-alexandre.chartre@oracle.com --- tools/objtool/check.c | 9 --- tools/objtool/disas.c | 134 ++++++++++++++++++++++++++++++++++ tools/objtool/include/objtool/check.h | 9 +++ 3 files changed, 143 insertions(+), 9 deletions(-) (limited to 'tools/objtool/disas.c') diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 21d45a35f3c9..0999717abc9c 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -134,15 +134,6 @@ static struct instruction *prev_insn_same_sym(struct objtool_file *file, for (insn = next_insn_same_sec(file, insn); insn; \ insn = next_insn_same_sec(file, insn)) -static inline struct symbol *insn_call_dest(struct instruction *insn) -{ - if (insn->type == INSN_JUMP_DYNAMIC || - insn->type == INSN_CALL_DYNAMIC) - return NULL; - - return insn->_call_dest; -} - static inline struct reloc *insn_jump_table(struct instruction *insn) { if (insn->type == INSN_JUMP_DYNAMIC || diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c index 11ac2ec04afc..dee10ab86fa2 100644 --- a/tools/objtool/disas.c +++ b/tools/objtool/disas.c @@ -14,13 +14,144 @@ struct disas_context { struct objtool_file *file; + struct instruction *insn; disassembler_ftype disassembler; struct disassemble_info info; }; +static int sprint_name(char *str, const char *name, unsigned long offset) +{ + int len; + + if (offset) + len = sprintf(str, "%s+0x%lx", name, offset); + else + len = sprintf(str, "%s", name); + + return len; +} + #define DINFO_FPRINTF(dinfo, ...) \ ((*(dinfo)->fprintf_func)((dinfo)->stream, __VA_ARGS__)) +static void disas_print_addr_sym(struct section *sec, struct symbol *sym, + bfd_vma addr, struct disassemble_info *dinfo) +{ + char symstr[1024]; + char *str; + + if (sym) { + sprint_name(symstr, sym->name, addr - sym->offset); + DINFO_FPRINTF(dinfo, "0x%lx <%s>", addr, symstr); + } else { + str = offstr(sec, addr); + DINFO_FPRINTF(dinfo, "0x%lx <%s>", addr, str); + free(str); + } +} + +static void disas_print_addr_noreloc(bfd_vma addr, + struct disassemble_info *dinfo) +{ + struct disas_context *dctx = dinfo->application_data; + struct instruction *insn = dctx->insn; + struct symbol *sym = NULL; + + if (insn->sym && addr >= insn->sym->offset && + addr < insn->sym->offset + insn->sym->len) { + sym = insn->sym; + } + + disas_print_addr_sym(insn->sec, sym, addr, dinfo); +} + +static void disas_print_addr_reloc(bfd_vma addr, struct disassemble_info *dinfo) +{ + struct disas_context *dctx = dinfo->application_data; + struct instruction *insn = dctx->insn; + unsigned long offset; + struct reloc *reloc; + char symstr[1024]; + char *str; + + reloc = find_reloc_by_dest_range(dctx->file->elf, insn->sec, + insn->offset, insn->len); + if (!reloc) { + /* + * There is no relocation for this instruction although + * the address to resolve points to the next instruction. + * So this is an effective reference to the next IP, for + * example: "lea 0x0(%rip),%rdi". The kernel can reference + * the next IP with _THIS_IP_ macro. + */ + DINFO_FPRINTF(dinfo, "0x%lx <_THIS_IP_>", addr); + return; + } + + offset = arch_insn_adjusted_addend(insn, reloc); + + /* + * If the relocation symbol is a section name (for example ".bss") + * then we try to further resolve the name. + */ + if (reloc->sym->type == STT_SECTION) { + str = offstr(reloc->sym->sec, reloc->sym->offset + offset); + DINFO_FPRINTF(dinfo, "0x%lx <%s>", addr, str); + free(str); + } else { + sprint_name(symstr, reloc->sym->name, offset); + DINFO_FPRINTF(dinfo, "0x%lx <%s>", addr, symstr); + } +} + +/* + * Resolve an address into a "+" string. + */ +static void disas_print_address(bfd_vma addr, struct disassemble_info *dinfo) +{ + struct disas_context *dctx = dinfo->application_data; + struct instruction *insn = dctx->insn; + struct instruction *jump_dest; + struct symbol *sym; + bool is_reloc; + + /* + * If the instruction is a call/jump and it references a + * destination then this is likely the address we are looking + * up. So check it first. + */ + jump_dest = insn->jump_dest; + if (jump_dest && jump_dest->sym && jump_dest->offset == addr) { + disas_print_addr_sym(jump_dest->sec, jump_dest->sym, + addr, dinfo); + return; + } + + /* + * If the address points to the next instruction then there is + * probably a relocation. It can be a false positive when the + * current instruction is referencing the address of the next + * instruction. This particular case will be handled in + * disas_print_addr_reloc(). + */ + is_reloc = (addr == insn->offset + insn->len); + + /* + * The call destination offset can be the address we are looking + * up, or 0 if there is a relocation. + */ + sym = insn_call_dest(insn); + if (sym && (sym->offset == addr || (sym->offset == 0 && is_reloc))) { + DINFO_FPRINTF(dinfo, "0x%lx <%s>", addr, sym->name); + return; + } + + if (!is_reloc) + disas_print_addr_noreloc(addr, dinfo); + else + disas_print_addr_reloc(addr, dinfo); +} + /* * Initialize disassemble info arch, mach (32 or 64-bit) and options. */ @@ -69,6 +200,7 @@ struct disas_context *disas_context_create(struct objtool_file *file) fprintf_styled); dinfo->read_memory_func = buffer_read_memory; + dinfo->print_address_func = disas_print_address; dinfo->application_data = dctx; /* @@ -121,6 +253,8 @@ static size_t disas_insn(struct disas_context *dctx, disassembler_ftype disasm = dctx->disassembler; struct disassemble_info *dinfo = &dctx->info; + dctx->insn = insn; + if (insn->type == INSN_NOP) { DINFO_FPRINTF(dinfo, "nop%d", insn->len); return insn->len; diff --git a/tools/objtool/include/objtool/check.h b/tools/objtool/include/objtool/check.h index 674f57466d12..ad9c73504b12 100644 --- a/tools/objtool/include/objtool/check.h +++ b/tools/objtool/include/objtool/check.h @@ -117,6 +117,15 @@ static inline bool is_jump(struct instruction *insn) return is_static_jump(insn) || is_dynamic_jump(insn); } +static inline struct symbol *insn_call_dest(struct instruction *insn) +{ + if (insn->type == INSN_JUMP_DYNAMIC || + insn->type == INSN_CALL_DYNAMIC) + return NULL; + + return insn->_call_dest; +} + struct instruction *find_insn(struct objtool_file *file, struct section *sec, unsigned long offset); -- cgit v1.2.3 From d4e13c21497d0cde73694163908f89d7168c1243 Mon Sep 17 00:00:00 2001 From: Alexandre Chartre Date: Fri, 21 Nov 2025 10:53:16 +0100 Subject: objtool: Store instruction disassembly result When disassembling an instruction store the result instead of directly printing it. Signed-off-by: Alexandre Chartre Signed-off-by: Peter Zijlstra (Intel) Acked-by: Josh Poimboeuf Link: https://patch.msgid.link/20251121095340.464045-7-alexandre.chartre@oracle.com --- tools/objtool/disas.c | 77 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 71 insertions(+), 6 deletions(-) (limited to 'tools/objtool/disas.c') diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c index dee10ab86fa2..89daa121b40b 100644 --- a/tools/objtool/disas.c +++ b/tools/objtool/disas.c @@ -12,9 +12,16 @@ #include #include +/* + * Size of the buffer for storing the result of disassembling + * a single instruction. + */ +#define DISAS_RESULT_SIZE 1024 + struct disas_context { struct objtool_file *file; struct instruction *insn; + char result[DISAS_RESULT_SIZE]; disassembler_ftype disassembler; struct disassemble_info info; }; @@ -34,6 +41,59 @@ static int sprint_name(char *str, const char *name, unsigned long offset) #define DINFO_FPRINTF(dinfo, ...) \ ((*(dinfo)->fprintf_func)((dinfo)->stream, __VA_ARGS__)) +static int disas_result_fprintf(struct disas_context *dctx, + const char *fmt, va_list ap) +{ + char *buf = dctx->result; + int avail, len; + + len = strlen(buf); + if (len >= DISAS_RESULT_SIZE - 1) { + WARN_FUNC(dctx->insn->sec, dctx->insn->offset, + "disassembly buffer is full"); + return -1; + } + avail = DISAS_RESULT_SIZE - len; + + len = vsnprintf(buf + len, avail, fmt, ap); + if (len < 0 || len >= avail) { + WARN_FUNC(dctx->insn->sec, dctx->insn->offset, + "disassembly buffer is truncated"); + return -1; + } + + return 0; +} + +static int disas_fprintf(void *stream, const char *fmt, ...) +{ + va_list arg; + int rv; + + va_start(arg, fmt); + rv = disas_result_fprintf(stream, fmt, arg); + va_end(arg); + + return rv; +} + +/* + * For init_disassemble_info_compat(). + */ +static int disas_fprintf_styled(void *stream, + enum disassembler_style style, + const char *fmt, ...) +{ + va_list arg; + int rv; + + va_start(arg, fmt); + rv = disas_result_fprintf(stream, fmt, arg); + va_end(arg); + + return rv; +} + static void disas_print_addr_sym(struct section *sec, struct symbol *sym, bfd_vma addr, struct disassemble_info *dinfo) { @@ -195,9 +255,8 @@ struct disas_context *disas_context_create(struct objtool_file *file) dctx->file = file; dinfo = &dctx->info; - init_disassemble_info_compat(dinfo, stdout, - (fprintf_ftype)fprintf, - fprintf_styled); + init_disassemble_info_compat(dinfo, dctx, + disas_fprintf, disas_fprintf_styled); dinfo->read_memory_func = buffer_read_memory; dinfo->print_address_func = disas_print_address; @@ -244,6 +303,11 @@ void disas_context_destroy(struct disas_context *dctx) free(dctx); } +static char *disas_result(struct disas_context *dctx) +{ + return dctx->result; +} + /* * Disassemble a single instruction. Return the size of the instruction. */ @@ -254,6 +318,7 @@ static size_t disas_insn(struct disas_context *dctx, struct disassemble_info *dinfo = &dctx->info; dctx->insn = insn; + dctx->result[0] = '\0'; if (insn->type == INSN_NOP) { DINFO_FPRINTF(dinfo, "nop%d", insn->len); @@ -282,10 +347,10 @@ static void disas_func(struct disas_context *dctx, struct symbol *func) printf("%s:\n", func->name); sym_for_each_insn(dctx->file, func, insn) { addr = insn->offset; - printf(" %6lx: %s+0x%-6lx ", - addr, func->name, addr - func->offset); disas_insn(dctx, insn); - printf("\n"); + printf(" %6lx: %s+0x%-6lx %s\n", + addr, func->name, addr - func->offset, + disas_result(dctx)); } printf("\n"); } -- cgit v1.2.3 From 0bb080ba6469a573bc85122153d931334d10a173 Mon Sep 17 00:00:00 2001 From: Alexandre Chartre Date: Fri, 21 Nov 2025 10:53:17 +0100 Subject: objtool: Disassemble instruction on warning or backtrace When an instruction warning (WARN_INSN) or backtrace (BT_INSN) is issued, disassemble the instruction to provide more context. Signed-off-by: Alexandre Chartre Signed-off-by: Peter Zijlstra (Intel) Acked-by: Josh Poimboeuf Link: https://patch.msgid.link/20251121095340.464045-8-alexandre.chartre@oracle.com --- tools/objtool/check.c | 36 +++++++++++++++++++++++++++++------ tools/objtool/disas.c | 5 ++--- tools/objtool/include/objtool/check.h | 2 ++ tools/objtool/include/objtool/disas.h | 13 +++++++++++++ tools/objtool/include/objtool/warn.h | 16 +++++++++++----- 5 files changed, 58 insertions(+), 14 deletions(-) (limited to 'tools/objtool/disas.c') diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 0999717abc9c..4da1f07b3538 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -4792,11 +4792,34 @@ static void free_insns(struct objtool_file *file) free(chunk->addr); } +static struct disas_context *objtool_disas_ctx; + +const char *objtool_disas_insn(struct instruction *insn) +{ + struct disas_context *dctx = objtool_disas_ctx; + + if (!dctx) + return ""; + + disas_insn(dctx, insn); + return disas_result(dctx); +} + int check(struct objtool_file *file) { - struct disas_context *disas_ctx; + struct disas_context *disas_ctx = NULL; int ret = 0, warnings = 0; + /* + * If the verbose or backtrace option is used then we need a + * disassembly context to disassemble instruction or function + * on warning or backtrace. + */ + if (opts.verbose || opts.backtrace) { + disas_ctx = disas_context_create(file); + objtool_disas_ctx = disas_ctx; + } + arch_initial_func_cfi_state(&initial_func_cfi); init_cfi_state(&init_cfi); init_cfi_state(&func_cfi); @@ -4936,11 +4959,12 @@ out: if (opts.verbose) { if (opts.werror && warnings) WARN("%d warning(s) upgraded to errors", warnings); - disas_ctx = disas_context_create(file); - if (disas_ctx) { - disas_warned_funcs(disas_ctx); - disas_context_destroy(disas_ctx); - } + disas_warned_funcs(disas_ctx); + } + + if (disas_ctx) { + disas_context_destroy(disas_ctx); + objtool_disas_ctx = NULL; } free_insns(file); diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c index 89daa121b40b..a030b06c121d 100644 --- a/tools/objtool/disas.c +++ b/tools/objtool/disas.c @@ -303,7 +303,7 @@ void disas_context_destroy(struct disas_context *dctx) free(dctx); } -static char *disas_result(struct disas_context *dctx) +char *disas_result(struct disas_context *dctx) { return dctx->result; } @@ -311,8 +311,7 @@ static char *disas_result(struct disas_context *dctx) /* * Disassemble a single instruction. Return the size of the instruction. */ -static size_t disas_insn(struct disas_context *dctx, - struct instruction *insn) +size_t disas_insn(struct disas_context *dctx, struct instruction *insn) { disassembler_ftype disasm = dctx->disassembler; struct disassemble_info *dinfo = &dctx->info; diff --git a/tools/objtool/include/objtool/check.h b/tools/objtool/include/objtool/check.h index ad9c73504b12..f96aabd7d54d 100644 --- a/tools/objtool/include/objtool/check.h +++ b/tools/objtool/include/objtool/check.h @@ -141,4 +141,6 @@ struct instruction *next_insn_same_sec(struct objtool_file *file, struct instruc insn && insn->offset < sym->offset + sym->len; \ insn = next_insn_same_sec(file, insn)) +const char *objtool_disas_insn(struct instruction *insn); + #endif /* _CHECK_H */ diff --git a/tools/objtool/include/objtool/disas.h b/tools/objtool/include/objtool/disas.h index 3ec3ce2e4e6f..1aee1fbe0bb9 100644 --- a/tools/objtool/include/objtool/disas.h +++ b/tools/objtool/include/objtool/disas.h @@ -17,6 +17,8 @@ void disas_warned_funcs(struct disas_context *dctx); int disas_info_init(struct disassemble_info *dinfo, int arch, int mach32, int mach64, const char *options); +size_t disas_insn(struct disas_context *dctx, struct instruction *insn); +char *disas_result(struct disas_context *dctx); #else /* DISAS */ @@ -38,6 +40,17 @@ static inline int disas_info_init(struct disassemble_info *dinfo, return -1; } +static inline size_t disas_insn(struct disas_context *dctx, + struct instruction *insn) +{ + return -1; +} + +static inline char *disas_result(struct disas_context *dctx) +{ + return NULL; +} + #endif /* DISAS */ #endif /* _DISAS_H */ diff --git a/tools/objtool/include/objtool/warn.h b/tools/objtool/include/objtool/warn.h index a1e3927d8e7c..f32abc7b1be1 100644 --- a/tools/objtool/include/objtool/warn.h +++ b/tools/objtool/include/objtool/warn.h @@ -77,9 +77,11 @@ static inline char *offstr(struct section *sec, unsigned long offset) #define WARN_INSN(insn, format, ...) \ ({ \ struct instruction *_insn = (insn); \ - if (!_insn->sym || !_insn->sym->warned) \ + if (!_insn->sym || !_insn->sym->warned) { \ WARN_FUNC(_insn->sec, _insn->offset, format, \ ##__VA_ARGS__); \ + BT_INSN(_insn, ""); \ + } \ if (_insn->sym) \ _insn->sym->warned = 1; \ }) @@ -87,10 +89,14 @@ static inline char *offstr(struct section *sec, unsigned long offset) #define BT_INSN(insn, format, ...) \ ({ \ if (opts.verbose || opts.backtrace) { \ - struct instruction *_insn = (insn); \ - char *_str = offstr(_insn->sec, _insn->offset); \ - WARN(" %s: " format, _str, ##__VA_ARGS__); \ - free(_str); \ + struct instruction *__insn = (insn); \ + char *_str = offstr(__insn->sec, __insn->offset); \ + const char *_istr = objtool_disas_insn(__insn); \ + int _len; \ + _len = snprintf(NULL, 0, " %s: " format, _str, ##__VA_ARGS__); \ + _len = (_len < 50) ? 50 - _len : 0; \ + WARN(" %s: " format " %*s%s", _str, ##__VA_ARGS__, _len, "", _istr); \ + free(_str); \ } \ }) -- cgit v1.2.3 From 70589843b36fee0c6e73632469da4e5fd11f0968 Mon Sep 17 00:00:00 2001 From: Alexandre Chartre Date: Fri, 21 Nov 2025 10:53:20 +0100 Subject: objtool: Add option to trace function validation Add an option to trace and have information during the validation of specified functions. Functions are specified with the --trace option which can be a single function name (e.g. --trace foo to trace the function with the name "foo"), or a shell wildcard pattern (e.g. --trace foo* to trace all functions with a name starting with "foo"). Signed-off-by: Alexandre Chartre Signed-off-by: Peter Zijlstra (Intel) Acked-by: Josh Poimboeuf Link: https://patch.msgid.link/20251121095340.464045-11-alexandre.chartre@oracle.com --- tools/objtool/Build | 1 + tools/objtool/builtin-check.c | 1 + tools/objtool/check.c | 104 ++++++++++++++++++++++++----- tools/objtool/disas.c | 115 ++++++++++++++++++++++++++++++++ tools/objtool/include/objtool/builtin.h | 1 + tools/objtool/include/objtool/check.h | 6 +- tools/objtool/include/objtool/disas.h | 11 +++ tools/objtool/include/objtool/trace.h | 68 +++++++++++++++++++ tools/objtool/include/objtool/warn.h | 1 + tools/objtool/trace.c | 9 +++ 10 files changed, 299 insertions(+), 18 deletions(-) create mode 100644 tools/objtool/include/objtool/trace.h create mode 100644 tools/objtool/trace.c (limited to 'tools/objtool/disas.c') diff --git a/tools/objtool/Build b/tools/objtool/Build index 9d1e8f28ef95..9982e665d58d 100644 --- a/tools/objtool/Build +++ b/tools/objtool/Build @@ -9,6 +9,7 @@ objtool-y += elf.o objtool-y += objtool.o objtool-$(BUILD_DISAS) += disas.o +objtool-$(BUILD_DISAS) += trace.o objtool-$(BUILD_ORC) += orc_gen.o orc_dump.o objtool-$(BUILD_KLP) += builtin-klp.o klp-diff.o klp-post-link.o diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c index aab7fa9c7e00..3329d370006b 100644 --- a/tools/objtool/builtin-check.c +++ b/tools/objtool/builtin-check.c @@ -103,6 +103,7 @@ static const struct option check_options[] = { 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"), + OPT_STRING(0, "trace", &opts.trace, "func", "trace function validation"), OPT_BOOLEAN('v', "verbose", &opts.verbose, "verbose warnings"), OPT_BOOLEAN(0, "werror", &opts.werror, "return error on warnings"), diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 0fbf0eb37051..409dec9efb49 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -4,6 +4,7 @@ */ #define _GNU_SOURCE /* memmem() */ +#include #include #include #include @@ -15,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -37,7 +39,9 @@ static struct cfi_state init_cfi; static struct cfi_state func_cfi; static struct cfi_state force_undefined_cfi; -static size_t sym_name_max_len; +struct disas_context *objtool_disas_ctx; + +size_t sym_name_max_len; struct instruction *find_insn(struct objtool_file *file, struct section *sec, unsigned long offset) @@ -3556,8 +3560,10 @@ static bool skip_alt_group(struct instruction *insn) return false; /* ANNOTATE_IGNORE_ALTERNATIVE */ - if (insn->alt_group->ignore) + if (insn->alt_group->ignore) { + TRACE_INSN(insn, "alt group ignored"); return true; + } /* * For NOP patched with CLAC/STAC, only follow the latter to avoid @@ -3663,6 +3669,8 @@ static void checksum_update_insn(struct objtool_file *file, struct symbol *func, static int validate_branch(struct objtool_file *file, struct symbol *func, struct instruction *insn, struct insn_state state); +static int do_validate_branch(struct objtool_file *file, struct symbol *func, + struct instruction *insn, struct insn_state state); static int validate_insn(struct objtool_file *file, struct symbol *func, struct instruction *insn, struct insn_state *statep, @@ -3684,8 +3692,10 @@ static int validate_insn(struct objtool_file *file, struct symbol *func, if (!insn->hint && !insn_cfi_match(insn, &statep->cfi)) return 1; - if (insn->visited & visited) + if (insn->visited & visited) { + TRACE_INSN(insn, "already visited"); return 0; + } } else { nr_insns_visited++; } @@ -3722,8 +3732,10 @@ static int validate_insn(struct objtool_file *file, struct symbol *func, * It will be seen later via the * straight-line path. */ - if (!prev_insn) + if (!prev_insn) { + TRACE_INSN(insn, "defer restore"); return 0; + } WARN_INSN(insn, "objtool isn't smart enough to handle this CFI save/restore combo"); return 1; @@ -3751,13 +3763,23 @@ static int validate_insn(struct objtool_file *file, struct symbol *func, return 1; if (insn->alts) { + int i, num_alts; + + num_alts = 0; + for (alt = insn->alts; alt; alt = alt->next) + num_alts++; + + i = 1; for (alt = insn->alts; alt; alt = alt->next) { + TRACE_INSN(insn, "alternative %d/%d", i, num_alts); ret = validate_branch(file, func, alt->insn, *statep); if (ret) { BT_INSN(insn, "(alt)"); return ret; } + i++; } + TRACE_INSN(insn, "alternative DEFAULT"); } if (skip_alt_group(insn)) @@ -3769,10 +3791,16 @@ static int validate_insn(struct objtool_file *file, struct symbol *func, switch (insn->type) { case INSN_RETURN: + TRACE_INSN(insn, "return"); return validate_return(func, insn, statep); case INSN_CALL: case INSN_CALL_DYNAMIC: + if (insn->type == INSN_CALL) + TRACE_INSN(insn, "call"); + else + TRACE_INSN(insn, "indirect call"); + ret = validate_call(file, insn, statep); if (ret) return ret; @@ -3788,13 +3816,18 @@ static int validate_insn(struct objtool_file *file, struct symbol *func, case INSN_JUMP_CONDITIONAL: case INSN_JUMP_UNCONDITIONAL: if (is_sibling_call(insn)) { + TRACE_INSN(insn, "sibling call"); ret = validate_sibling_call(file, insn, statep); if (ret) return ret; } else if (insn->jump_dest) { - ret = validate_branch(file, func, - insn->jump_dest, *statep); + if (insn->type == INSN_JUMP_UNCONDITIONAL) + TRACE_INSN(insn, "unconditional jump"); + else + TRACE_INSN(insn, "jump taken"); + + ret = validate_branch(file, func, insn->jump_dest, *statep); if (ret) { BT_INSN(insn, "(branch)"); return ret; @@ -3804,10 +3837,12 @@ static int validate_insn(struct objtool_file *file, struct symbol *func, if (insn->type == INSN_JUMP_UNCONDITIONAL) return 0; + TRACE_INSN(insn, "jump not taken"); break; case INSN_JUMP_DYNAMIC: case INSN_JUMP_DYNAMIC_CONDITIONAL: + TRACE_INSN(insn, "indirect jump"); if (is_sibling_call(insn)) { ret = validate_sibling_call(file, insn, statep); if (ret) @@ -3820,6 +3855,7 @@ static int validate_insn(struct objtool_file *file, struct symbol *func, break; case INSN_SYSCALL: + TRACE_INSN(insn, "syscall"); if (func && (!next_insn || !next_insn->hint)) { WARN_INSN(insn, "unsupported instruction in callable function"); return 1; @@ -3828,6 +3864,7 @@ static int validate_insn(struct objtool_file *file, struct symbol *func, break; case INSN_SYSRET: + TRACE_INSN(insn, "sysret"); if (func && (!next_insn || !next_insn->hint)) { WARN_INSN(insn, "unsupported instruction in callable function"); return 1; @@ -3836,6 +3873,7 @@ static int validate_insn(struct objtool_file *file, struct symbol *func, return 0; case INSN_STAC: + TRACE_INSN(insn, "stac"); if (!opts.uaccess) break; @@ -3848,6 +3886,7 @@ static int validate_insn(struct objtool_file *file, struct symbol *func, break; case INSN_CLAC: + TRACE_INSN(insn, "clac"); if (!opts.uaccess) break; @@ -3865,6 +3904,7 @@ static int validate_insn(struct objtool_file *file, struct symbol *func, break; case INSN_STD: + TRACE_INSN(insn, "std"); if (statep->df) { WARN_INSN(insn, "recursive STD"); return 1; @@ -3874,6 +3914,7 @@ static int validate_insn(struct objtool_file *file, struct symbol *func, break; case INSN_CLD: + TRACE_INSN(insn, "cld"); if (!statep->df && func) { WARN_INSN(insn, "redundant CLD"); return 1; @@ -3886,8 +3927,10 @@ static int validate_insn(struct objtool_file *file, struct symbol *func, break; } - *dead_end = insn->dead_end; + if (insn->dead_end) + TRACE_INSN(insn, "dead end"); + *dead_end = insn->dead_end; return 0; } @@ -3897,8 +3940,8 @@ static int validate_insn(struct objtool_file *file, struct symbol *func, * each instruction and validate all the rules described in * tools/objtool/Documentation/objtool.txt. */ -static int validate_branch(struct objtool_file *file, struct symbol *func, - struct instruction *insn, struct insn_state state) +static int do_validate_branch(struct objtool_file *file, struct symbol *func, + struct instruction *insn, struct insn_state state) { struct instruction *next_insn, *prev_insn = NULL; bool dead_end; @@ -3907,7 +3950,8 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, if (func && func->ignore) return 0; - while (1) { + do { + insn->trace = 0; next_insn = next_insn_to_validate(file, insn); if (opts.checksum && func && insn->sec) @@ -3930,10 +3974,15 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, ret = validate_insn(file, func, insn, &state, prev_insn, next_insn, &dead_end); - if (dead_end) - break; - if (!next_insn) { + if (!insn->trace) { + if (ret) + TRACE_INSN(insn, "warning (%d)", ret); + else + TRACE_INSN(insn, NULL); + } + + if (!dead_end && !next_insn) { if (state.cfi.cfa.base == CFI_UNDEFINED) return 0; if (file->ignore_unreachables) @@ -3947,7 +3996,20 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, prev_insn = insn; insn = next_insn; - } + + } while (!dead_end); + + return ret; +} + +static int validate_branch(struct objtool_file *file, struct symbol *func, + struct instruction *insn, struct insn_state state) +{ + int ret; + + trace_depth_inc(); + ret = do_validate_branch(file, func, insn, state); + trace_depth_dec(); return ret; } @@ -4408,10 +4470,18 @@ static int validate_symbol(struct objtool_file *file, struct section *sec, if (opts.checksum) checksum_init(func); + if (opts.trace && !fnmatch(opts.trace, sym->name, 0)) { + trace_enable(); + TRACE("%s: validation begin\n", sym->name); + } + ret = validate_branch(file, func, insn, *state); if (ret) BT_INSN(insn, "<=== (sym)"); + TRACE("%s: validation %s\n\n", sym->name, ret ? "failed" : "end"); + trace_disable(); + if (opts.checksum) checksum_finish(func); @@ -4823,8 +4893,6 @@ static void free_insns(struct objtool_file *file) free(chunk->addr); } -static struct disas_context *objtool_disas_ctx; - const char *objtool_disas_insn(struct instruction *insn) { struct disas_context *dctx = objtool_disas_ctx; @@ -4846,8 +4914,10 @@ int check(struct objtool_file *file) * disassembly context to disassemble instruction or function * on warning or backtrace. */ - if (opts.verbose || opts.backtrace) { + if (opts.verbose || opts.backtrace || opts.trace) { disas_ctx = disas_context_create(file); + if (!disas_ctx) + opts.trace = false; objtool_disas_ctx = disas_ctx; } diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c index a030b06c121d..0ca6e6c8559f 100644 --- a/tools/objtool/disas.c +++ b/tools/objtool/disas.c @@ -308,6 +308,121 @@ char *disas_result(struct disas_context *dctx) return dctx->result; } +#define DISAS_INSN_OFFSET_SPACE 10 +#define DISAS_INSN_SPACE 60 + +/* + * Print a message in the instruction flow. If insn is not NULL then + * the instruction address is printed in addition of the message, + * otherwise only the message is printed. In all cases, the instruction + * itself is not printed. + */ +static int disas_vprint(FILE *stream, struct section *sec, unsigned long offset, + int depth, const char *format, va_list ap) +{ + const char *addr_str; + int i, n; + int len; + + len = sym_name_max_len + DISAS_INSN_OFFSET_SPACE; + if (depth < 0) { + len += depth; + depth = 0; + } + + n = 0; + + if (sec) { + addr_str = offstr(sec, offset); + n += fprintf(stream, "%6lx: %-*s ", offset, len, addr_str); + free((char *)addr_str); + } else { + len += DISAS_INSN_OFFSET_SPACE + 1; + n += fprintf(stream, "%-*s", len, ""); + } + + /* print vertical bars to show the code flow */ + for (i = 0; i < depth; i++) + n += fprintf(stream, "| "); + + if (format) + n += vfprintf(stream, format, ap); + + return n; +} + +/* + * Print a message in the instruction flow. If insn is not NULL then + * the instruction address is printed in addition of the message, + * otherwise only the message is printed. In all cases, the instruction + * itself is not printed. + */ +void disas_print_info(FILE *stream, struct instruction *insn, int depth, + const char *format, ...) +{ + struct section *sec; + unsigned long off; + va_list args; + + if (insn) { + sec = insn->sec; + off = insn->offset; + } else { + sec = NULL; + off = 0; + } + + va_start(args, format); + disas_vprint(stream, sec, off, depth, format, args); + va_end(args); +} + +/* + * Print an instruction address (offset and function), the instruction itself + * and an optional message. + */ +void disas_print_insn(FILE *stream, struct disas_context *dctx, + struct instruction *insn, int depth, + const char *format, ...) +{ + char fake_nop_insn[32]; + const char *insn_str; + bool fake_nop; + va_list args; + int len; + + /* + * Alternative can insert a fake nop, sometimes with no + * associated section so nothing to disassemble. + */ + fake_nop = (!insn->sec && insn->type == INSN_NOP); + if (fake_nop) { + snprintf(fake_nop_insn, 32, " (%d bytes)", insn->len); + insn_str = fake_nop_insn; + } else { + disas_insn(dctx, insn); + insn_str = disas_result(dctx); + } + + /* print the instruction */ + len = (depth + 1) * 2 < DISAS_INSN_SPACE ? DISAS_INSN_SPACE - (depth+1) * 2 : 1; + disas_print_info(stream, insn, depth, "%-*s", len, insn_str); + + /* print message if any */ + if (!format) + return; + + if (strcmp(format, "\n") == 0) { + fprintf(stream, "\n"); + return; + } + + fprintf(stream, " - "); + va_start(args, format); + vfprintf(stream, format, args); + va_end(args); +} + /* * Disassemble a single instruction. Return the size of the instruction. */ diff --git a/tools/objtool/include/objtool/builtin.h b/tools/objtool/include/objtool/builtin.h index bb0b25eb08ba..991365c10f0e 100644 --- a/tools/objtool/include/objtool/builtin.h +++ b/tools/objtool/include/objtool/builtin.h @@ -41,6 +41,7 @@ struct opts { const char *output; bool sec_address; bool stats; + const char *trace; bool verbose; bool werror; }; diff --git a/tools/objtool/include/objtool/check.h b/tools/objtool/include/objtool/check.h index f96aabd7d54d..fde958683485 100644 --- a/tools/objtool/include/objtool/check.h +++ b/tools/objtool/include/objtool/check.h @@ -66,7 +66,8 @@ struct instruction { visited : 4, no_reloc : 1, hole : 1, - fake : 1; + fake : 1, + trace : 1; /* 9 bit hole */ struct alt_group *alt_group; @@ -143,4 +144,7 @@ struct instruction *next_insn_same_sec(struct objtool_file *file, struct instruc const char *objtool_disas_insn(struct instruction *insn); +extern size_t sym_name_max_len; +extern struct disas_context *objtool_disas_ctx; + #endif /* _CHECK_H */ diff --git a/tools/objtool/include/objtool/disas.h b/tools/objtool/include/objtool/disas.h index 1aee1fbe0bb9..5db75d06f219 100644 --- a/tools/objtool/include/objtool/disas.h +++ b/tools/objtool/include/objtool/disas.h @@ -19,6 +19,11 @@ int disas_info_init(struct disassemble_info *dinfo, const char *options); size_t disas_insn(struct disas_context *dctx, struct instruction *insn); char *disas_result(struct disas_context *dctx); +void disas_print_info(FILE *stream, struct instruction *insn, int depth, + const char *format, ...); +void disas_print_insn(FILE *stream, struct disas_context *dctx, + struct instruction *insn, int depth, + const char *format, ...); #else /* DISAS */ @@ -51,6 +56,12 @@ static inline char *disas_result(struct disas_context *dctx) return NULL; } +static inline void disas_print_info(FILE *stream, struct instruction *insn, + int depth, const char *format, ...) {} +static inline void disas_print_insn(FILE *stream, struct disas_context *dctx, + struct instruction *insn, int depth, + const char *format, ...) {} + #endif /* DISAS */ #endif /* _DISAS_H */ diff --git a/tools/objtool/include/objtool/trace.h b/tools/objtool/include/objtool/trace.h new file mode 100644 index 000000000000..3f3c830ed114 --- /dev/null +++ b/tools/objtool/include/objtool/trace.h @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. + */ + +#ifndef _TRACE_H +#define _TRACE_H + +#include +#include + +#ifdef DISAS + +extern bool trace; +extern int trace_depth; + +#define TRACE(fmt, ...) \ +({ if (trace) \ + fprintf(stderr, fmt, ##__VA_ARGS__); \ +}) + +#define TRACE_INSN(insn, fmt, ...) \ +({ \ + if (trace) { \ + disas_print_insn(stderr, objtool_disas_ctx, \ + insn, trace_depth - 1, \ + fmt, ##__VA_ARGS__); \ + fprintf(stderr, "\n"); \ + insn->trace = 1; \ + } \ +}) + +static inline void trace_enable(void) +{ + trace = true; + trace_depth = 0; +} + +static inline void trace_disable(void) +{ + trace = false; +} + +static inline void trace_depth_inc(void) +{ + if (trace) + trace_depth++; +} + +static inline void trace_depth_dec(void) +{ + if (trace) + trace_depth--; +} + +#else /* DISAS */ + +#define TRACE(fmt, ...) ({}) +#define TRACE_INSN(insn, fmt, ...) ({}) + +static inline void trace_enable(void) {} +static inline void trace_disable(void) {} +static inline void trace_depth_inc(void) {} +static inline void trace_depth_dec(void) {} + +#endif + +#endif /* _TRACE_H */ diff --git a/tools/objtool/include/objtool/warn.h b/tools/objtool/include/objtool/warn.h index f32abc7b1be1..25ff7942b4d5 100644 --- a/tools/objtool/include/objtool/warn.h +++ b/tools/objtool/include/objtool/warn.h @@ -97,6 +97,7 @@ static inline char *offstr(struct section *sec, unsigned long offset) _len = (_len < 50) ? 50 - _len : 0; \ WARN(" %s: " format " %*s%s", _str, ##__VA_ARGS__, _len, "", _istr); \ free(_str); \ + __insn->trace = 1; \ } \ }) diff --git a/tools/objtool/trace.c b/tools/objtool/trace.c new file mode 100644 index 000000000000..134cc33ffe97 --- /dev/null +++ b/tools/objtool/trace.c @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2025, Oracle and/or its affiliates. + */ + +#include + +bool trace; +int trace_depth; -- cgit v1.2.3 From 9b580accac003767a461bf52d738ad1ab4e8ccfa Mon Sep 17 00:00:00 2001 From: Alexandre Chartre Date: Fri, 21 Nov 2025 10:53:24 +0100 Subject: objtool: Add functions to better name alternatives Add the disas_alt_name() and disas_alt_type_name() to provide a name and a type name for an alternative. This will be used to better name alternatives when tracing their execution. Signed-off-by: Alexandre Chartre Signed-off-by: Peter Zijlstra (Intel) Acked-by: Josh Poimboeuf Link: https://patch.msgid.link/20251121095340.464045-15-alexandre.chartre@oracle.com --- tools/objtool/disas.c | 72 +++++++++++++++++++++++++++++++++++ tools/objtool/include/objtool/disas.h | 12 ++++++ 2 files changed, 84 insertions(+) (limited to 'tools/objtool/disas.c') diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c index 0ca6e6c8559f..b53be240825d 100644 --- a/tools/objtool/disas.c +++ b/tools/objtool/disas.c @@ -3,6 +3,8 @@ * Copyright (C) 2015-2017 Josh Poimboeuf */ +#define _GNU_SOURCE + #include #include #include @@ -450,6 +452,76 @@ size_t disas_insn(struct disas_context *dctx, struct instruction *insn) return disasm(insn->offset, &dctx->info); } +/* + * Provide a name for the type of alternatives present at the + * specified instruction. + * + * An instruction can have alternatives with different types, for + * example alternative instructions and an exception table. In that + * case the name for the alternative instructions type is used. + * + * Return NULL if the instruction as no alternative. + */ +const char *disas_alt_type_name(struct instruction *insn) +{ + struct alternative *alt; + const char *name; + + name = NULL; + for (alt = insn->alts; alt; alt = alt->next) { + if (alt->type == ALT_TYPE_INSTRUCTIONS) { + name = "alternative"; + break; + } + + switch (alt->type) { + case ALT_TYPE_EX_TABLE: + name = "ex_table"; + break; + case ALT_TYPE_JUMP_TABLE: + name = "jump_table"; + break; + default: + name = "unknown"; + break; + } + } + + return name; +} + +/* + * Provide a name for an alternative. + */ +char *disas_alt_name(struct alternative *alt) +{ + char *str = NULL; + + switch (alt->type) { + + case ALT_TYPE_EX_TABLE: + str = strdup("EXCEPTION"); + break; + + case ALT_TYPE_JUMP_TABLE: + str = strdup("JUMP"); + break; + + case ALT_TYPE_INSTRUCTIONS: + /* + * This is a non-default group alternative. Create a unique + * name using the offset of the first original and alternative + * instructions. + */ + asprintf(&str, "ALTERNATIVE %lx.%lx", + alt->insn->alt_group->orig_group->first_insn->offset, + alt->insn->alt_group->first_insn->offset); + break; + } + + return str; +} + /* * Disassemble a function. */ diff --git a/tools/objtool/include/objtool/disas.h b/tools/objtool/include/objtool/disas.h index 5db75d06f219..8959d4c45562 100644 --- a/tools/objtool/include/objtool/disas.h +++ b/tools/objtool/include/objtool/disas.h @@ -6,6 +6,7 @@ #ifndef _DISAS_H #define _DISAS_H +struct alternative; struct disas_context; struct disassemble_info; @@ -24,6 +25,8 @@ void disas_print_info(FILE *stream, struct instruction *insn, int depth, void disas_print_insn(FILE *stream, struct disas_context *dctx, struct instruction *insn, int depth, const char *format, ...); +char *disas_alt_name(struct alternative *alt); +const char *disas_alt_type_name(struct instruction *insn); #else /* DISAS */ @@ -61,6 +64,15 @@ static inline void disas_print_info(FILE *stream, struct instruction *insn, static inline void disas_print_insn(FILE *stream, struct disas_context *dctx, struct instruction *insn, int depth, const char *format, ...) {} +static inline char *disas_alt_name(struct alternative *alt) +{ + return NULL; +} + +static inline const char *disas_alt_type_name(struct instruction *insn) +{ + return NULL; +} #endif /* DISAS */ -- cgit v1.2.3 From 5f326c88973691232c0e56ced83c199d53d86766 Mon Sep 17 00:00:00 2001 From: Alexandre Chartre Date: Fri, 21 Nov 2025 10:53:27 +0100 Subject: objtool: Add the --disas= action Add the --disas= actions to disassemble the specified functions. The function pattern can be a single function name (e.g. --disas foo to disassemble the function with the name "foo"), or a shell wildcard pattern (e.g. --disas foo* to disassemble all functions with a name starting with "foo"). Signed-off-by: Alexandre Chartre Signed-off-by: Peter Zijlstra (Intel) Acked-by: Josh Poimboeuf Link: https://patch.msgid.link/20251121095340.464045-18-alexandre.chartre@oracle.com --- tools/objtool/builtin-check.c | 2 ++ tools/objtool/check.c | 38 ++++++++++++++++++--------------- tools/objtool/disas.c | 27 +++++++++++++++++++++++ tools/objtool/include/objtool/builtin.h | 1 + tools/objtool/include/objtool/disas.h | 2 ++ 5 files changed, 53 insertions(+), 17 deletions(-) (limited to 'tools/objtool/disas.c') diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c index 3329d370006b..a0371312fe55 100644 --- a/tools/objtool/builtin-check.c +++ b/tools/objtool/builtin-check.c @@ -75,6 +75,7 @@ static const struct option check_options[] = { OPT_GROUP("Actions:"), OPT_BOOLEAN(0, "checksum", &opts.checksum, "generate per-function checksums"), OPT_BOOLEAN(0, "cfi", &opts.cfi, "annotate kernel control flow integrity (kCFI) function preambles"), + OPT_STRING_OPTARG('d', "disas", &opts.disas, "function-pattern", "disassemble functions", "*"), OPT_CALLBACK_OPTARG('h', "hacks", NULL, NULL, "jump_label,noinstr,skylake", "patch toolchain bugs/limitations", parse_hacks), OPT_BOOLEAN('i', "ibt", &opts.ibt, "validate and annotate IBT"), OPT_BOOLEAN('m', "mcount", &opts.mcount, "annotate mcount/fentry calls for ftrace"), @@ -176,6 +177,7 @@ static bool opts_valid(void) } if (opts.checksum || + opts.disas || opts.hack_jump_label || opts.hack_noinstr || opts.ibt || diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 4ebadf94f8af..9cd9f9d4f656 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -2611,7 +2611,7 @@ static int decode_sections(struct objtool_file *file) * Must be before add_jump_destinations(), which depends on 'func' * being set for alternatives, to enable proper sibling call detection. */ - if (validate_branch_enabled() || opts.noinstr || opts.hack_jump_label) { + if (validate_branch_enabled() || opts.noinstr || opts.hack_jump_label || opts.disas) { if (add_special_section_alts(file)) return -1; } @@ -4915,14 +4915,15 @@ int check(struct objtool_file *file) int ret = 0, warnings = 0; /* - * If the verbose or backtrace option is used then we need a - * disassembly context to disassemble instruction or function - * on warning or backtrace. + * Create a disassembly context if we might disassemble any + * instruction or function. */ - if (opts.verbose || opts.backtrace || opts.trace) { + if (opts.verbose || opts.backtrace || opts.trace || opts.disas) { disas_ctx = disas_context_create(file); - if (!disas_ctx) + if (!disas_ctx) { + opts.disas = false; opts.trace = false; + } objtool_disas_ctx = disas_ctx; } @@ -5054,20 +5055,20 @@ int check(struct objtool_file *file) } out: - if (!ret && !warnings) { - free_insns(file); - return 0; - } - - if (opts.werror && warnings) - ret = 1; - - if (opts.verbose) { + if (ret || warnings) { if (opts.werror && warnings) - WARN("%d warning(s) upgraded to errors", warnings); - disas_warned_funcs(disas_ctx); + ret = 1; + + if (opts.verbose) { + if (opts.werror && warnings) + WARN("%d warning(s) upgraded to errors", warnings); + disas_warned_funcs(disas_ctx); + } } + if (opts.disas) + disas_funcs(disas_ctx); + if (disas_ctx) { disas_context_destroy(disas_ctx); objtool_disas_ctx = NULL; @@ -5075,6 +5076,9 @@ out: free_insns(file); + if (!ret && !warnings) + return 0; + if (opts.backup && make_backup()) return 1; diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c index b53be240825d..9cc952e03c35 100644 --- a/tools/objtool/disas.c +++ b/tools/objtool/disas.c @@ -4,6 +4,7 @@ */ #define _GNU_SOURCE +#include #include #include @@ -556,3 +557,29 @@ void disas_warned_funcs(struct disas_context *dctx) disas_func(dctx, sym); } } + +void disas_funcs(struct disas_context *dctx) +{ + bool disas_all = !strcmp(opts.disas, "*"); + struct section *sec; + struct symbol *sym; + + for_each_sec(dctx->file->elf, sec) { + + if (!(sec->sh.sh_flags & SHF_EXECINSTR)) + continue; + + sec_for_each_sym(sec, sym) { + /* + * If the function had a warning and the verbose + * option is used then the function was already + * disassemble. + */ + if (opts.verbose && sym->warned) + continue; + + if (disas_all || fnmatch(opts.disas, sym->name, 0) == 0) + disas_func(dctx, sym); + } + } +} diff --git a/tools/objtool/include/objtool/builtin.h b/tools/objtool/include/objtool/builtin.h index 991365c10f0e..e3af664864f3 100644 --- a/tools/objtool/include/objtool/builtin.h +++ b/tools/objtool/include/objtool/builtin.h @@ -28,6 +28,7 @@ struct opts { bool static_call; bool uaccess; int prefix; + const char *disas; /* options: */ bool backtrace; diff --git a/tools/objtool/include/objtool/disas.h b/tools/objtool/include/objtool/disas.h index 8959d4c45562..e8f395eff159 100644 --- a/tools/objtool/include/objtool/disas.h +++ b/tools/objtool/include/objtool/disas.h @@ -15,6 +15,7 @@ struct disassemble_info; struct disas_context *disas_context_create(struct objtool_file *file); void disas_context_destroy(struct disas_context *dctx); void disas_warned_funcs(struct disas_context *dctx); +void disas_funcs(struct disas_context *dctx); int disas_info_init(struct disassemble_info *dinfo, int arch, int mach32, int mach64, const char *options); @@ -40,6 +41,7 @@ static inline struct disas_context *disas_context_create(struct objtool_file *fi static inline void disas_context_destroy(struct disas_context *dctx) {} static inline void disas_warned_funcs(struct disas_context *dctx) {} +static inline void disas_funcs(struct disas_context *dctx) {} static inline int disas_info_init(struct disassemble_info *dinfo, int arch, int mach32, int mach64, -- cgit v1.2.3 From 87343e664252198d2735c9719f711d3e922f3be5 Mon Sep 17 00:00:00 2001 From: Alexandre Chartre Date: Fri, 21 Nov 2025 10:53:29 +0100 Subject: objtool: Print headers for alternatives When using the --disas option, objtool doesn't currently disassemble any alternative. Print an header for each alternative. This identifies places where alternatives are present but alternative code is still not disassembled at the moment. Signed-off-by: Alexandre Chartre Signed-off-by: Peter Zijlstra (Intel) Acked-by: Josh Poimboeuf Link: https://patch.msgid.link/20251121095340.464045-20-alexandre.chartre@oracle.com --- tools/objtool/disas.c | 188 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 182 insertions(+), 6 deletions(-) (limited to 'tools/objtool/disas.c') diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c index 9cc952e03c35..f9b13d56acab 100644 --- a/tools/objtool/disas.c +++ b/tools/objtool/disas.c @@ -29,6 +29,43 @@ struct disas_context { struct disassemble_info info; }; +/* + * Maximum number of alternatives + */ +#define DISAS_ALT_MAX 5 + +/* + * Maximum number of instructions per alternative + */ +#define DISAS_ALT_INSN_MAX 50 + +/* + * Information to disassemble an alternative + */ +struct disas_alt { + struct instruction *orig_insn; /* original instruction */ + struct alternative *alt; /* alternative or NULL if default code */ + char *name; /* name for this alternative */ + int width; /* formatting width */ +}; + +/* + * Wrapper around asprintf() to allocate and format a string. + * Return the allocated string or NULL on error. + */ +static char *strfmt(const char *fmt, ...) +{ + va_list ap; + char *str; + int rv; + + va_start(ap, fmt); + rv = vasprintf(&str, fmt, ap); + va_end(ap); + + return rv == -1 ? NULL : str; +} + static int sprint_name(char *str, const char *name, unsigned long offset) { int len; @@ -314,6 +351,9 @@ char *disas_result(struct disas_context *dctx) #define DISAS_INSN_OFFSET_SPACE 10 #define DISAS_INSN_SPACE 60 +#define DISAS_PRINSN(dctx, insn, depth) \ + disas_print_insn(stdout, dctx, insn, depth, "\n") + /* * Print a message in the instruction flow. If insn is not NULL then * the instruction address is printed in addition of the message, @@ -354,6 +394,19 @@ static int disas_vprint(FILE *stream, struct section *sec, unsigned long offset, return n; } +static int disas_print(FILE *stream, struct section *sec, unsigned long offset, + int depth, const char *format, ...) +{ + va_list args; + int len; + + va_start(args, format); + len = disas_vprint(stream, sec, offset, depth, format, args); + va_end(args); + + return len; +} + /* * Print a message in the instruction flow. If insn is not NULL then * the instruction address is printed in addition of the message, @@ -523,21 +576,144 @@ char *disas_alt_name(struct alternative *alt) return str; } +/* + * Initialize an alternative. The default alternative should be initialized + * with alt=NULL. + */ +static int disas_alt_init(struct disas_alt *dalt, + struct instruction *orig_insn, + struct alternative *alt) +{ + dalt->orig_insn = orig_insn; + dalt->alt = alt; + dalt->name = alt ? disas_alt_name(alt) : strdup("DEFAULT"); + if (!dalt->name) + return -1; + dalt->width = strlen(dalt->name); + + return 0; +} + +/* + * Print all alternatives one above the other. + */ +static void disas_alt_print_compact(char *alt_name, struct disas_alt *dalts, + int alt_count) +{ + struct instruction *orig_insn; + int len; + int i; + + orig_insn = dalts[0].orig_insn; + + len = disas_print(stdout, orig_insn->sec, orig_insn->offset, 0, NULL); + printf("%s\n", alt_name); + + for (i = 0; i < alt_count; i++) + printf("%*s= %s\n", len, "", dalts[i].name); +} + +/* + * Disassemble an alternative. + * + * Return the last instruction in the default alternative so that + * disassembly can continue with the next instruction. Return NULL + * on error. + */ +static void *disas_alt(struct disas_context *dctx, + struct instruction *orig_insn) +{ + struct disas_alt dalts[DISAS_ALT_MAX] = { 0 }; + struct alternative *alt; + int alt_count = 0; + char *alt_name; + int err; + int i; + + alt_name = strfmt("<%s.%lx>", disas_alt_type_name(orig_insn), + orig_insn->offset); + if (!alt_name) { + WARN("Failed to define name for alternative at instruction 0x%lx", + orig_insn->offset); + goto done; + } + + /* + * Initialize the default alternative. + */ + err = disas_alt_init(&dalts[0], orig_insn, NULL); + if (err) { + WARN("%s: failed to initialize default alternative", alt_name); + goto done; + } + + /* + * Initialize all other alternatives. + */ + i = 1; + for (alt = orig_insn->alts; alt; alt = alt->next) { + if (i >= DISAS_ALT_MAX) { + WARN("%s has more alternatives than supported", alt_name); + break; + } + err = disas_alt_init(&dalts[i], orig_insn, alt); + if (err) { + WARN("%s: failed to disassemble alternative", alt_name); + goto done; + } + + i++; + } + alt_count = i; + + /* + * Print default and non-default alternatives. + * + * At the moment, this just prints an header for each alternative. + */ + disas_alt_print_compact(alt_name, dalts, alt_count); + +done: + for (i = 0; i < alt_count; i++) + free(dalts[i].name); + + free(alt_name); + + /* + * Currently we are not disassembling any alternative but just + * printing alternative names. Return NULL to have disas_func() + * resume the disassembly with the default alternative. + */ + return NULL; +} + /* * Disassemble a function. */ static void disas_func(struct disas_context *dctx, struct symbol *func) { + struct instruction *insn_start; struct instruction *insn; - size_t addr; printf("%s:\n", func->name); sym_for_each_insn(dctx->file, func, insn) { - addr = insn->offset; - disas_insn(dctx, insn); - printf(" %6lx: %s+0x%-6lx %s\n", - addr, func->name, addr - func->offset, - disas_result(dctx)); + if (insn->alts) { + insn_start = insn; + insn = disas_alt(dctx, insn); + if (insn) + continue; + /* + * There was an error with disassembling + * the alternative. Resume disassembling + * at the current instruction, this will + * disassemble the default alternative + * only and continue with the code after + * the alternative. + */ + insn = insn_start; + } + + DISAS_PRINSN(dctx, insn, 0); } printf("\n"); } -- cgit v1.2.3 From a4f1599672e7bf494d79928a38fd6aa873e2e50c Mon Sep 17 00:00:00 2001 From: Alexandre Chartre Date: Fri, 21 Nov 2025 10:53:30 +0100 Subject: objtool: Disassemble group alternatives When using the --disas option, disassemble all group alternatives. Jump tables and exception tables (which are handled as alternatives) are not disassembled at the moment. Signed-off-by: Alexandre Chartre Signed-off-by: Peter Zijlstra (Intel) Acked-by: Josh Poimboeuf Link: https://patch.msgid.link/20251121095340.464045-21-alexandre.chartre@oracle.com --- tools/objtool/disas.c | 166 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 149 insertions(+), 17 deletions(-) (limited to 'tools/objtool/disas.c') diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c index f9b13d56acab..ae69bef2eb37 100644 --- a/tools/objtool/disas.c +++ b/tools/objtool/disas.c @@ -47,8 +47,14 @@ struct disas_alt { struct alternative *alt; /* alternative or NULL if default code */ char *name; /* name for this alternative */ int width; /* formatting width */ + char *insn[DISAS_ALT_INSN_MAX]; /* alternative instructions */ }; +#define DALT_DEFAULT(dalt) (!(dalt)->alt) +#define DALT_INSN(dalt) (DALT_DEFAULT(dalt) ? (dalt)->orig_insn : (dalt)->alt->insn) +#define DALT_GROUP(dalt) (DALT_INSN(dalt)->alt_group) +#define DALT_ALTID(dalt) ((dalt)->orig_insn->offset) + /* * Wrapper around asprintf() to allocate and format a string. * Return the allocated string or NULL on error. @@ -506,6 +512,21 @@ size_t disas_insn(struct disas_context *dctx, struct instruction *insn) return disasm(insn->offset, &dctx->info); } +static struct instruction *next_insn_same_alt(struct objtool_file *file, + struct alt_group *alt_grp, + struct instruction *insn) +{ + if (alt_grp->last_insn == insn || alt_grp->nop == insn) + return NULL; + + return next_insn_same_sec(file, insn); +} + +#define alt_for_each_insn(file, alt_grp, insn) \ + for (insn = alt_grp->first_insn; \ + insn; \ + insn = next_insn_same_alt(file, alt_grp, insn)) + /* * Provide a name for the type of alternatives present at the * specified instruction. @@ -594,23 +615,107 @@ static int disas_alt_init(struct disas_alt *dalt, return 0; } +static int disas_alt_add_insn(struct disas_alt *dalt, int index, char *insn_str) +{ + int len; + + if (index >= DISAS_ALT_INSN_MAX) { + WARN("Alternative %lx.%s has more instructions than supported", + DALT_ALTID(dalt), dalt->name); + return -1; + } + + len = strlen(insn_str); + dalt->insn[index] = insn_str; + if (len > dalt->width) + dalt->width = len; + + return 0; +} + +/* + * Disassemble an alternative and store instructions in the disas_alt + * structure. Return the number of instructions in the alternative. + */ +static int disas_alt_group(struct disas_context *dctx, struct disas_alt *dalt) +{ + struct objtool_file *file; + struct instruction *insn; + char *str; + int count; + int err; + + file = dctx->file; + count = 0; + + alt_for_each_insn(file, DALT_GROUP(dalt), insn) { + + disas_insn(dctx, insn); + str = strdup(disas_result(dctx)); + if (!str) + return -1; + + err = disas_alt_add_insn(dalt, count, str); + if (err) + break; + count++; + } + + return count; +} + +/* + * Disassemble the default alternative. + */ +static int disas_alt_default(struct disas_context *dctx, struct disas_alt *dalt) +{ + char *str; + int err; + + if (DALT_GROUP(dalt)) + return disas_alt_group(dctx, dalt); + + /* + * Default alternative with no alt_group: this is the default + * code associated with either a jump table or an exception + * table and no other instruction alternatives. In that case + * the default alternative is made of a single instruction. + */ + disas_insn(dctx, dalt->orig_insn); + str = strdup(disas_result(dctx)); + if (!str) + return -1; + err = disas_alt_add_insn(dalt, 0, str); + if (err) + return -1; + + return 1; +} + /* * Print all alternatives one above the other. */ static void disas_alt_print_compact(char *alt_name, struct disas_alt *dalts, - int alt_count) + int alt_count, int insn_count) { struct instruction *orig_insn; + int i, j; int len; - int i; orig_insn = dalts[0].orig_insn; len = disas_print(stdout, orig_insn->sec, orig_insn->offset, 0, NULL); printf("%s\n", alt_name); - for (i = 0; i < alt_count; i++) + for (i = 0; i < alt_count; i++) { printf("%*s= %s\n", len, "", dalts[i].name); + for (j = 0; j < insn_count; j++) { + if (!dalts[i].insn[j]) + break; + printf("%*s| %s\n", len, "", dalts[i].insn[j]); + } + printf("%*s|\n", len, ""); + } } /* @@ -624,11 +729,15 @@ static void *disas_alt(struct disas_context *dctx, struct instruction *orig_insn) { struct disas_alt dalts[DISAS_ALT_MAX] = { 0 }; + struct instruction *last_insn = NULL; struct alternative *alt; + struct disas_alt *dalt; + int insn_count = 0; int alt_count = 0; char *alt_name; + int count; + int i, j; int err; - int i; alt_name = strfmt("<%s.%lx>", disas_alt_type_name(orig_insn), orig_insn->offset); @@ -639,7 +748,7 @@ static void *disas_alt(struct disas_context *dctx, } /* - * Initialize the default alternative. + * Initialize and disassemble the default alternative. */ err = disas_alt_init(&dalts[0], orig_insn, NULL); if (err) { @@ -647,8 +756,14 @@ static void *disas_alt(struct disas_context *dctx, goto done; } + insn_count = disas_alt_default(dctx, &dalts[0]); + if (insn_count < 0) { + WARN("%s: failed to disassemble default alternative", alt_name); + goto done; + } + /* - * Initialize all other alternatives. + * Initialize and disassemble all other alternatives. */ i = 1; for (alt = orig_insn->alts; alt; alt = alt->next) { @@ -656,35 +771,52 @@ static void *disas_alt(struct disas_context *dctx, WARN("%s has more alternatives than supported", alt_name); break; } - err = disas_alt_init(&dalts[i], orig_insn, alt); + dalt = &dalts[i]; + err = disas_alt_init(dalt, orig_insn, alt); if (err) { WARN("%s: failed to disassemble alternative", alt_name); goto done; } + /* + * Only group alternatives are supported at the moment. + */ + switch (dalt->alt->type) { + case ALT_TYPE_INSTRUCTIONS: + count = disas_alt_group(dctx, dalt); + break; + default: + count = 0; + } + if (count < 0) { + WARN("%s: failed to disassemble alternative %s", + alt_name, dalt->name); + goto done; + } + + insn_count = count > insn_count ? count : insn_count; i++; } alt_count = i; /* * Print default and non-default alternatives. - * - * At the moment, this just prints an header for each alternative. */ - disas_alt_print_compact(alt_name, dalts, alt_count); + disas_alt_print_compact(alt_name, dalts, alt_count, insn_count); + + last_insn = orig_insn->alt_group ? orig_insn->alt_group->last_insn : + orig_insn; done: - for (i = 0; i < alt_count; i++) + for (i = 0; i < alt_count; i++) { free(dalts[i].name); + for (j = 0; j < insn_count; j++) + free(dalts[i].insn[j]); + } free(alt_name); - /* - * Currently we are not disassembling any alternative but just - * printing alternative names. Return NULL to have disas_func() - * resume the disassembly with the default alternative. - */ - return NULL; + return last_insn; } /* -- cgit v1.2.3 From 15e7ad8667b9d1fd4b6bdf06472812416453b7b2 Mon Sep 17 00:00:00 2001 From: Alexandre Chartre Date: Fri, 21 Nov 2025 10:53:31 +0100 Subject: objtool: Print addresses with alternative instructions All alternatives are disassemble side-by-side when using the --disas option. However the address of each instruction is not printed because instructions from different alternatives are not necessarily aligned. Change this behavior to print the address of each instruction. Spaces will appear between instructions from the same alternative when instructions from different alternatives do not have the same alignment. Signed-off-by: Alexandre Chartre Signed-off-by: Peter Zijlstra (Intel) Acked-by: Josh Poimboeuf Link: https://patch.msgid.link/20251121095340.464045-22-alexandre.chartre@oracle.com --- tools/objtool/disas.c | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) (limited to 'tools/objtool/disas.c') diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c index ae69bef2eb37..6083a64f6ae4 100644 --- a/tools/objtool/disas.c +++ b/tools/objtool/disas.c @@ -47,7 +47,11 @@ struct disas_alt { struct alternative *alt; /* alternative or NULL if default code */ char *name; /* name for this alternative */ int width; /* formatting width */ - char *insn[DISAS_ALT_INSN_MAX]; /* alternative instructions */ + struct { + char *str; /* instruction string */ + int offset; /* instruction offset */ + } insn[DISAS_ALT_INSN_MAX]; /* alternative instructions */ + int insn_idx; /* index of the next instruction to print */ }; #define DALT_DEFAULT(dalt) (!(dalt)->alt) @@ -361,10 +365,9 @@ char *disas_result(struct disas_context *dctx) disas_print_insn(stdout, dctx, insn, depth, "\n") /* - * Print a message in the instruction flow. If insn is not NULL then - * the instruction address is printed in addition of the message, - * otherwise only the message is printed. In all cases, the instruction - * itself is not printed. + * Print a message in the instruction flow. If sec is not NULL then the + * address at the section offset is printed in addition of the message, + * otherwise only the message is printed. */ static int disas_vprint(FILE *stream, struct section *sec, unsigned long offset, int depth, const char *format, va_list ap) @@ -607,6 +610,7 @@ static int disas_alt_init(struct disas_alt *dalt, { dalt->orig_insn = orig_insn; dalt->alt = alt; + dalt->insn_idx = 0; dalt->name = alt ? disas_alt_name(alt) : strdup("DEFAULT"); if (!dalt->name) return -1; @@ -615,7 +619,8 @@ static int disas_alt_init(struct disas_alt *dalt, return 0; } -static int disas_alt_add_insn(struct disas_alt *dalt, int index, char *insn_str) +static int disas_alt_add_insn(struct disas_alt *dalt, int index, char *insn_str, + int offset) { int len; @@ -626,7 +631,8 @@ static int disas_alt_add_insn(struct disas_alt *dalt, int index, char *insn_str) } len = strlen(insn_str); - dalt->insn[index] = insn_str; + dalt->insn[index].str = insn_str; + dalt->insn[index].offset = offset; if (len > dalt->width) dalt->width = len; @@ -641,12 +647,14 @@ static int disas_alt_group(struct disas_context *dctx, struct disas_alt *dalt) { struct objtool_file *file; struct instruction *insn; + int offset; char *str; int count; int err; file = dctx->file; count = 0; + offset = 0; alt_for_each_insn(file, DALT_GROUP(dalt), insn) { @@ -655,9 +663,10 @@ static int disas_alt_group(struct disas_context *dctx, struct disas_alt *dalt) if (!str) return -1; - err = disas_alt_add_insn(dalt, count, str); + err = disas_alt_add_insn(dalt, count, str, offset); if (err) break; + offset += insn->len; count++; } @@ -685,7 +694,7 @@ static int disas_alt_default(struct disas_context *dctx, struct disas_alt *dalt) str = strdup(disas_result(dctx)); if (!str) return -1; - err = disas_alt_add_insn(dalt, 0, str); + err = disas_alt_add_insn(dalt, 0, str, 0); if (err) return -1; @@ -710,9 +719,11 @@ static void disas_alt_print_compact(char *alt_name, struct disas_alt *dalts, for (i = 0; i < alt_count; i++) { printf("%*s= %s\n", len, "", dalts[i].name); for (j = 0; j < insn_count; j++) { - if (!dalts[i].insn[j]) + if (!dalts[i].insn[j].str) break; - printf("%*s| %s\n", len, "", dalts[i].insn[j]); + disas_print(stdout, orig_insn->sec, + orig_insn->offset + dalts[i].insn[j].offset, 0, + "| %s\n", dalts[i].insn[j].str); } printf("%*s|\n", len, ""); } @@ -811,7 +822,7 @@ done: for (i = 0; i < alt_count; i++) { free(dalts[i].name); for (j = 0; j < insn_count; j++) - free(dalts[i].insn[j]); + free(dalts[i].insn[j].str); } free(alt_name); -- cgit v1.2.3 From 78df4590c568731cfa12de9ecb888b3b0c141db2 Mon Sep 17 00:00:00 2001 From: Alexandre Chartre Date: Fri, 21 Nov 2025 10:53:32 +0100 Subject: objtool: Disassemble exception table alternatives When using the --disas option, also disassemble exception tables (EX_TABLE). Signed-off-by: Alexandre Chartre Signed-off-by: Peter Zijlstra (Intel) Acked-by: Josh Poimboeuf Link: https://patch.msgid.link/20251121095340.464045-23-alexandre.chartre@oracle.com --- tools/objtool/disas.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) (limited to 'tools/objtool/disas.c') diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c index 6083a64f6ae4..018aba37b996 100644 --- a/tools/objtool/disas.c +++ b/tools/objtool/disas.c @@ -639,6 +639,26 @@ static int disas_alt_add_insn(struct disas_alt *dalt, int index, char *insn_str, return 0; } +/* + * Disassemble an exception table alternative. + */ +static int disas_alt_extable(struct disas_alt *dalt) +{ + struct instruction *alt_insn; + char *str; + + alt_insn = dalt->alt->insn; + str = strfmt("resume at 0x%lx <%s+0x%lx>", + alt_insn->offset, alt_insn->sym->name, + alt_insn->offset - alt_insn->sym->offset); + if (!str) + return -1; + + disas_alt_add_insn(dalt, 0, str, 0); + + return 1; +} + /* * Disassemble an alternative and store instructions in the disas_alt * structure. Return the number of instructions in the alternative. @@ -790,12 +810,16 @@ static void *disas_alt(struct disas_context *dctx, } /* - * Only group alternatives are supported at the moment. + * Only group alternatives and exception tables are + * supported at the moment. */ switch (dalt->alt->type) { case ALT_TYPE_INSTRUCTIONS: count = disas_alt_group(dctx, dalt); break; + case ALT_TYPE_EX_TABLE: + count = disas_alt_extable(dalt); + break; default: count = 0; } -- cgit v1.2.3 From 7e017720aae87dc2ca2471ac295e34e2b240e5f5 Mon Sep 17 00:00:00 2001 From: Alexandre Chartre Date: Fri, 21 Nov 2025 10:53:33 +0100 Subject: objtool: Disassemble jump table alternatives When using the --disas option, also disassemble jump tables. Signed-off-by: Alexandre Chartre Signed-off-by: Peter Zijlstra (Intel) Acked-by: Josh Poimboeuf Link: https://patch.msgid.link/20251121095340.464045-24-alexandre.chartre@oracle.com --- tools/objtool/disas.c | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) (limited to 'tools/objtool/disas.c') diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c index 018aba37b996..326e16c9f30a 100644 --- a/tools/objtool/disas.c +++ b/tools/objtool/disas.c @@ -639,6 +639,34 @@ static int disas_alt_add_insn(struct disas_alt *dalt, int index, char *insn_str, return 0; } +static int disas_alt_jump(struct disas_alt *dalt) +{ + struct instruction *orig_insn; + struct instruction *dest_insn; + char suffix[2] = { 0 }; + char *str; + + orig_insn = dalt->orig_insn; + dest_insn = dalt->alt->insn; + + if (orig_insn->type == INSN_NOP) { + if (orig_insn->len == 5) + suffix[0] = 'q'; + str = strfmt("jmp%-3s %lx <%s+0x%lx>", suffix, + dest_insn->offset, dest_insn->sym->name, + dest_insn->offset - dest_insn->sym->offset); + } else { + str = strfmt("nop%d", orig_insn->len); + } + + if (!str) + return -1; + + disas_alt_add_insn(dalt, 0, str, 0); + + return 1; +} + /* * Disassemble an exception table alternative. */ @@ -809,10 +837,7 @@ static void *disas_alt(struct disas_context *dctx, goto done; } - /* - * Only group alternatives and exception tables are - * supported at the moment. - */ + count = -1; switch (dalt->alt->type) { case ALT_TYPE_INSTRUCTIONS: count = disas_alt_group(dctx, dalt); @@ -820,8 +845,9 @@ static void *disas_alt(struct disas_context *dctx, case ALT_TYPE_EX_TABLE: count = disas_alt_extable(dalt); break; - default: - count = 0; + case ALT_TYPE_JUMP_TABLE: + count = disas_alt_jump(dalt); + break; } if (count < 0) { WARN("%s: failed to disassemble alternative %s", -- cgit v1.2.3 From 4aae0d3f77b1104e55847870d15c3749ca575fcf Mon Sep 17 00:00:00 2001 From: Alexandre Chartre Date: Fri, 21 Nov 2025 10:53:34 +0100 Subject: objtool: Fix address references in alternatives When using the --disas option, alternatives are disassembled but address references in non-default alternatives can be incorrect. The problem is that alternatives are shown as if they were replacing the original code of the alternative. So if an alternative is referencing an address inside the alternative then the reference has to be adjusted to the location of the original code. Signed-off-by: Alexandre Chartre Signed-off-by: Peter Zijlstra (Intel) Acked-by: Josh Poimboeuf Link: https://patch.msgid.link/20251121095340.464045-25-alexandre.chartre@oracle.com --- tools/objtool/disas.c | 70 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 66 insertions(+), 4 deletions(-) (limited to 'tools/objtool/disas.c') diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c index 326e16c9f30a..f8917c8405d3 100644 --- a/tools/objtool/disas.c +++ b/tools/objtool/disas.c @@ -24,6 +24,7 @@ struct disas_context { struct objtool_file *file; struct instruction *insn; + bool alt_applied; char result[DISAS_RESULT_SIZE]; disassembler_ftype disassembler; struct disassemble_info info; @@ -160,6 +161,43 @@ static void disas_print_addr_sym(struct section *sec, struct symbol *sym, } } +static bool disas_print_addr_alt(bfd_vma addr, struct disassemble_info *dinfo) +{ + struct disas_context *dctx = dinfo->application_data; + struct instruction *orig_first_insn; + struct alt_group *alt_group; + unsigned long offset; + struct symbol *sym; + + /* + * Check if we are processing an alternative at the original + * instruction address (i.e. if alt_applied is true) and if + * we are referencing an address inside the alternative. + * + * For example, this happens if there is a branch inside an + * alternative. In that case, the address should be updated + * to a reference inside the original instruction flow. + */ + if (!dctx->alt_applied) + return false; + + alt_group = dctx->insn->alt_group; + if (!alt_group || !alt_group->orig_group || + addr < alt_group->first_insn->offset || + addr > alt_group->last_insn->offset) + return false; + + orig_first_insn = alt_group->orig_group->first_insn; + offset = addr - alt_group->first_insn->offset; + + addr = orig_first_insn->offset + offset; + sym = orig_first_insn->sym; + + disas_print_addr_sym(orig_first_insn->sec, sym, addr, dinfo); + + return true; +} + static void disas_print_addr_noreloc(bfd_vma addr, struct disassemble_info *dinfo) { @@ -167,6 +205,9 @@ static void disas_print_addr_noreloc(bfd_vma addr, struct instruction *insn = dctx->insn; struct symbol *sym = NULL; + if (disas_print_addr_alt(addr, dinfo)) + return; + if (insn->sym && addr >= insn->sym->offset && addr < insn->sym->offset + insn->sym->len) { sym = insn->sym; @@ -232,8 +273,9 @@ static void disas_print_address(bfd_vma addr, struct disassemble_info *dinfo) */ jump_dest = insn->jump_dest; if (jump_dest && jump_dest->sym && jump_dest->offset == addr) { - disas_print_addr_sym(jump_dest->sec, jump_dest->sym, - addr, dinfo); + if (!disas_print_addr_alt(addr, dinfo)) + disas_print_addr_sym(jump_dest->sec, jump_dest->sym, + addr, dinfo); return; } @@ -490,13 +532,22 @@ void disas_print_insn(FILE *stream, struct disas_context *dctx, /* * Disassemble a single instruction. Return the size of the instruction. + * + * If alt_applied is true then insn should be an instruction from of an + * alternative (i.e. insn->alt_group != NULL), and it is disassembled + * at the location of the original code it is replacing. When the + * instruction references any address inside the alternative then + * these references will be re-adjusted to replace the original code. */ -size_t disas_insn(struct disas_context *dctx, struct instruction *insn) +static size_t disas_insn_common(struct disas_context *dctx, + struct instruction *insn, + bool alt_applied) { disassembler_ftype disasm = dctx->disassembler; struct disassemble_info *dinfo = &dctx->info; dctx->insn = insn; + dctx->alt_applied = alt_applied; dctx->result[0] = '\0'; if (insn->type == INSN_NOP) { @@ -515,6 +566,17 @@ size_t disas_insn(struct disas_context *dctx, struct instruction *insn) return disasm(insn->offset, &dctx->info); } +size_t disas_insn(struct disas_context *dctx, struct instruction *insn) +{ + return disas_insn_common(dctx, insn, false); +} + +static size_t disas_insn_alt(struct disas_context *dctx, + struct instruction *insn) +{ + return disas_insn_common(dctx, insn, true); +} + static struct instruction *next_insn_same_alt(struct objtool_file *file, struct alt_group *alt_grp, struct instruction *insn) @@ -706,7 +768,7 @@ static int disas_alt_group(struct disas_context *dctx, struct disas_alt *dalt) alt_for_each_insn(file, DALT_GROUP(dalt), insn) { - disas_insn(dctx, insn); + disas_insn_alt(dctx, insn); str = strdup(disas_result(dctx)); if (!str) return -1; -- cgit v1.2.3 From 56967b9a772298ad276858ddab5a655b1d167623 Mon Sep 17 00:00:00 2001 From: Alexandre Chartre Date: Fri, 21 Nov 2025 10:53:37 +0100 Subject: objtool: Improve naming of group alternatives Improve the naming of group alternatives by showing the feature name and flags used by the alternative. Signed-off-by: Alexandre Chartre Signed-off-by: Peter Zijlstra (Intel) Acked-by: Josh Poimboeuf Link: https://patch.msgid.link/20251121095340.464045-28-alexandre.chartre@oracle.com --- tools/objtool/disas.c | 58 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 52 insertions(+), 6 deletions(-) (limited to 'tools/objtool/disas.c') diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c index f8917c8405d3..731c4495b53c 100644 --- a/tools/objtool/disas.c +++ b/tools/objtool/disas.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -60,6 +61,21 @@ struct disas_alt { #define DALT_GROUP(dalt) (DALT_INSN(dalt)->alt_group) #define DALT_ALTID(dalt) ((dalt)->orig_insn->offset) +#define ALT_FLAGS_SHIFT 16 +#define ALT_FLAG_NOT (1 << 0) +#define ALT_FLAG_DIRECT_CALL (1 << 1) +#define ALT_FEATURE_MASK ((1 << ALT_FLAGS_SHIFT) - 1) + +static int alt_feature(unsigned int ft_flags) +{ + return (ft_flags & ALT_FEATURE_MASK); +} + +static int alt_flags(unsigned int ft_flags) +{ + return (ft_flags >> ALT_FLAGS_SHIFT); +} + /* * Wrapper around asprintf() to allocate and format a string. * Return the allocated string or NULL on error. @@ -635,7 +651,12 @@ const char *disas_alt_type_name(struct instruction *insn) */ char *disas_alt_name(struct alternative *alt) { + char pfx[4] = { 0 }; char *str = NULL; + const char *name; + int feature; + int flags; + int num; switch (alt->type) { @@ -649,13 +670,37 @@ char *disas_alt_name(struct alternative *alt) case ALT_TYPE_INSTRUCTIONS: /* - * This is a non-default group alternative. Create a unique - * name using the offset of the first original and alternative - * instructions. + * This is a non-default group alternative. Create a name + * based on the feature and flags associated with this + * alternative. Use either the feature name (it is available) + * or the feature number. And add a prefix to show the flags + * used. + * + * Prefix flags characters: + * + * '!' alternative used when feature not enabled + * '+' direct call alternative + * '?' unknown flag */ - asprintf(&str, "ALTERNATIVE %lx.%lx", - alt->insn->alt_group->orig_group->first_insn->offset, - alt->insn->alt_group->first_insn->offset); + + feature = alt->insn->alt_group->feature; + num = alt_feature(feature); + flags = alt_flags(feature); + str = pfx; + + if (flags & ~(ALT_FLAG_NOT | ALT_FLAG_DIRECT_CALL)) + *str++ = '?'; + if (flags & ALT_FLAG_DIRECT_CALL) + *str++ = '+'; + if (flags & ALT_FLAG_NOT) + *str++ = '!'; + + name = arch_cpu_feature_name(num); + if (!name) + str = strfmt("%sFEATURE 0x%X", pfx, num); + else + str = strfmt("%s%s", pfx, name); + break; } @@ -892,6 +937,7 @@ static void *disas_alt(struct disas_context *dctx, WARN("%s has more alternatives than supported", alt_name); break; } + dalt = &dalts[i]; err = disas_alt_init(dalt, orig_insn, alt); if (err) { -- cgit v1.2.3 From 07d70b271a6fc4f546b153081f3685931561be7b Mon Sep 17 00:00:00 2001 From: Alexandre Chartre Date: Fri, 21 Nov 2025 10:53:38 +0100 Subject: objtool: Compact output for alternatives with one instruction When disassembling, if an instruction has alternatives which are all made of a single instruction then print each alternative on a single line (instruction + description) so that the output is more compact. Signed-off-by: Alexandre Chartre Signed-off-by: Peter Zijlstra (Intel) Acked-by: Josh Poimboeuf Link: https://patch.msgid.link/20251121095340.464045-29-alexandre.chartre@oracle.com --- tools/objtool/disas.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'tools/objtool/disas.c') diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c index 731c4495b53c..a4f905eac4e6 100644 --- a/tools/objtool/disas.c +++ b/tools/objtool/disas.c @@ -863,6 +863,7 @@ static void disas_alt_print_compact(char *alt_name, struct disas_alt *dalts, int alt_count, int insn_count) { struct instruction *orig_insn; + int width; int i, j; int len; @@ -871,6 +872,27 @@ static void disas_alt_print_compact(char *alt_name, struct disas_alt *dalts, len = disas_print(stdout, orig_insn->sec, orig_insn->offset, 0, NULL); printf("%s\n", alt_name); + /* + * If all alternatives have a single instruction then print each + * alternative on a single line. Otherwise, print alternatives + * one above the other with a clear separation. + */ + + if (insn_count == 1) { + width = 0; + for (i = 0; i < alt_count; i++) { + if (dalts[i].width > width) + width = dalts[i].width; + } + + for (i = 0; i < alt_count; i++) { + printf("%*s= %-*s (if %s)\n", len, "", width, + dalts[i].insn[0].str, dalts[i].name); + } + + return; + } + for (i = 0; i < alt_count; i++) { printf("%*s= %s\n", len, "", dalts[i].name); for (j = 0; j < insn_count; j++) { -- cgit v1.2.3 From aff95e0d4e277c53fa274f4a5b6854849f3fc84d Mon Sep 17 00:00:00 2001 From: Alexandre Chartre Date: Fri, 21 Nov 2025 10:53:39 +0100 Subject: objtool: Add wide output for disassembly Add the --wide option to provide a wide output when disassembling. With this option, the disassembly of alternatives is displayed side-by-side instead of one above the other. Signed-off-by: Alexandre Chartre Signed-off-by: Peter Zijlstra (Intel) Acked-by: Josh Poimboeuf Link: https://patch.msgid.link/20251121095340.464045-30-alexandre.chartre@oracle.com --- tools/objtool/builtin-check.c | 1 + tools/objtool/disas.c | 95 ++++++++++++++++++++++++++++++++- tools/objtool/include/objtool/builtin.h | 1 + 3 files changed, 96 insertions(+), 1 deletion(-) (limited to 'tools/objtool/disas.c') diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c index a0371312fe55..b780df513715 100644 --- a/tools/objtool/builtin-check.c +++ b/tools/objtool/builtin-check.c @@ -107,6 +107,7 @@ static const struct option check_options[] = { OPT_STRING(0, "trace", &opts.trace, "func", "trace function validation"), OPT_BOOLEAN('v', "verbose", &opts.verbose, "verbose warnings"), OPT_BOOLEAN(0, "werror", &opts.werror, "return error on warnings"), + OPT_BOOLEAN(0, "wide", &opts.wide, "wide output"), OPT_END(), }; diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c index a4f905eac4e6..f04bc14bef39 100644 --- a/tools/objtool/disas.c +++ b/tools/objtool/disas.c @@ -856,6 +856,95 @@ static int disas_alt_default(struct disas_context *dctx, struct disas_alt *dalt) return 1; } +/* + * For each alternative, if there is an instruction at the specified + * offset then print this instruction, otherwise print a blank entry. + * The offset is an offset from the start of the alternative. + * + * Return the offset for the next instructions to print, or -1 if all + * instructions have been printed. + */ +static int disas_alt_print_insn(struct disas_alt *dalts, int alt_count, + int insn_count, int offset) +{ + struct disas_alt *dalt; + int offset_next; + char *str; + int i, j; + + offset_next = -1; + + for (i = 0; i < alt_count; i++) { + dalt = &dalts[i]; + j = dalt->insn_idx; + if (j == -1) { + printf("| %-*s ", dalt->width, ""); + continue; + } + + if (dalt->insn[j].offset == offset) { + str = dalt->insn[j].str; + printf("| %-*s ", dalt->width, str ?: ""); + if (++j < insn_count) { + dalt->insn_idx = j; + } else { + dalt->insn_idx = -1; + continue; + } + } else { + printf("| %-*s ", dalt->width, ""); + } + + if (dalt->insn[j].offset > 0 && + (offset_next == -1 || + (dalt->insn[j].offset < offset_next))) + offset_next = dalt->insn[j].offset; + } + printf("\n"); + + return offset_next; +} + +/* + * Print all alternatives side-by-side. + */ +static void disas_alt_print_wide(char *alt_name, struct disas_alt *dalts, int alt_count, + int insn_count) +{ + struct instruction *orig_insn; + int offset_next; + int offset; + int i; + + orig_insn = dalts[0].orig_insn; + + /* + * Print an header with the name of each alternative. + */ + disas_print_info(stdout, orig_insn, -2, NULL); + + if (strlen(alt_name) > dalts[0].width) + dalts[0].width = strlen(alt_name); + printf("| %-*s ", dalts[0].width, alt_name); + + for (i = 1; i < alt_count; i++) + printf("| %-*s ", dalts[i].width, dalts[i].name); + + printf("\n"); + + /* + * Print instructions for each alternative. + */ + offset_next = 0; + do { + offset = offset_next; + disas_print(stdout, orig_insn->sec, orig_insn->offset + offset, + -2, NULL); + offset_next = disas_alt_print_insn(dalts, alt_count, insn_count, + offset); + } while (offset_next > offset); +} + /* * Print all alternatives one above the other. */ @@ -993,7 +1082,11 @@ static void *disas_alt(struct disas_context *dctx, /* * Print default and non-default alternatives. */ - disas_alt_print_compact(alt_name, dalts, alt_count, insn_count); + + if (opts.wide) + disas_alt_print_wide(alt_name, dalts, alt_count, insn_count); + else + disas_alt_print_compact(alt_name, dalts, alt_count, insn_count); last_insn = orig_insn->alt_group ? orig_insn->alt_group->last_insn : orig_insn; diff --git a/tools/objtool/include/objtool/builtin.h b/tools/objtool/include/objtool/builtin.h index e3af664864f3..b9e229ed4dc0 100644 --- a/tools/objtool/include/objtool/builtin.h +++ b/tools/objtool/include/objtool/builtin.h @@ -45,6 +45,7 @@ struct opts { const char *trace; bool verbose; bool werror; + bool wide; }; extern struct opts opts; -- cgit v1.2.3 From c0a67900dc129825c48d3638480297aa00f39c00 Mon Sep 17 00:00:00 2001 From: Alexandre Chartre Date: Fri, 21 Nov 2025 10:53:40 +0100 Subject: objtool: Trim trailing NOPs in alternative When disassembling alternatives replace trailing NOPs with a single indication of the number of bytes covered with NOPs. Signed-off-by: Alexandre Chartre Signed-off-by: Peter Zijlstra (Intel) Acked-by: Josh Poimboeuf Link: https://patch.msgid.link/20251121095340.464045-31-alexandre.chartre@oracle.com --- tools/objtool/disas.c | 78 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 73 insertions(+), 5 deletions(-) (limited to 'tools/objtool/disas.c') diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c index f04bc14bef39..441b9306eafc 100644 --- a/tools/objtool/disas.c +++ b/tools/objtool/disas.c @@ -52,6 +52,7 @@ struct disas_alt { struct { char *str; /* instruction string */ int offset; /* instruction offset */ + int nops; /* number of nops */ } insn[DISAS_ALT_INSN_MAX]; /* alternative instructions */ int insn_idx; /* index of the next instruction to print */ }; @@ -727,7 +728,7 @@ static int disas_alt_init(struct disas_alt *dalt, } static int disas_alt_add_insn(struct disas_alt *dalt, int index, char *insn_str, - int offset) + int offset, int nops) { int len; @@ -740,6 +741,7 @@ static int disas_alt_add_insn(struct disas_alt *dalt, int index, char *insn_str, len = strlen(insn_str); dalt->insn[index].str = insn_str; dalt->insn[index].offset = offset; + dalt->insn[index].nops = nops; if (len > dalt->width) dalt->width = len; @@ -752,6 +754,7 @@ static int disas_alt_jump(struct disas_alt *dalt) struct instruction *dest_insn; char suffix[2] = { 0 }; char *str; + int nops; orig_insn = dalt->orig_insn; dest_insn = dalt->alt->insn; @@ -762,14 +765,16 @@ static int disas_alt_jump(struct disas_alt *dalt) str = strfmt("jmp%-3s %lx <%s+0x%lx>", suffix, dest_insn->offset, dest_insn->sym->name, dest_insn->offset - dest_insn->sym->offset); + nops = 0; } else { str = strfmt("nop%d", orig_insn->len); + nops = orig_insn->len; } if (!str) return -1; - disas_alt_add_insn(dalt, 0, str, 0); + disas_alt_add_insn(dalt, 0, str, 0, nops); return 1; } @@ -789,7 +794,7 @@ static int disas_alt_extable(struct disas_alt *dalt) if (!str) return -1; - disas_alt_add_insn(dalt, 0, str, 0); + disas_alt_add_insn(dalt, 0, str, 0, 0); return 1; } @@ -805,11 +810,13 @@ static int disas_alt_group(struct disas_context *dctx, struct disas_alt *dalt) int offset; char *str; int count; + int nops; int err; file = dctx->file; count = 0; offset = 0; + nops = 0; alt_for_each_insn(file, DALT_GROUP(dalt), insn) { @@ -818,7 +825,8 @@ static int disas_alt_group(struct disas_context *dctx, struct disas_alt *dalt) if (!str) return -1; - err = disas_alt_add_insn(dalt, count, str, offset); + nops = insn->type == INSN_NOP ? insn->len : 0; + err = disas_alt_add_insn(dalt, count, str, offset, nops); if (err) break; offset += insn->len; @@ -834,6 +842,7 @@ static int disas_alt_group(struct disas_context *dctx, struct disas_alt *dalt) static int disas_alt_default(struct disas_context *dctx, struct disas_alt *dalt) { char *str; + int nops; int err; if (DALT_GROUP(dalt)) @@ -849,7 +858,8 @@ static int disas_alt_default(struct disas_context *dctx, struct disas_alt *dalt) str = strdup(disas_result(dctx)); if (!str) return -1; - err = disas_alt_add_insn(dalt, 0, str, 0); + nops = dalt->orig_insn->type == INSN_NOP ? dalt->orig_insn->len : 0; + err = disas_alt_add_insn(dalt, 0, str, 0, nops); if (err) return -1; @@ -995,6 +1005,62 @@ static void disas_alt_print_compact(char *alt_name, struct disas_alt *dalts, } } +/* + * Trim NOPs in alternatives. This replaces trailing NOPs in alternatives + * with a single indication of the number of bytes covered with NOPs. + * + * Return the maximum numbers of instructions in all alternatives after + * trailing NOPs have been trimmed. + */ +static int disas_alt_trim_nops(struct disas_alt *dalts, int alt_count, + int insn_count) +{ + struct disas_alt *dalt; + int nops_count; + const char *s; + int offset; + int count; + int nops; + int i, j; + + count = 0; + for (i = 0; i < alt_count; i++) { + offset = 0; + nops = 0; + nops_count = 0; + dalt = &dalts[i]; + for (j = insn_count - 1; j >= 0; j--) { + if (!dalt->insn[j].str || !dalt->insn[j].nops) + break; + offset = dalt->insn[j].offset; + free(dalt->insn[j].str); + dalt->insn[j].offset = 0; + dalt->insn[j].str = NULL; + nops += dalt->insn[j].nops; + nops_count++; + } + + /* + * All trailing NOPs have been removed. If there was a single + * NOP instruction then re-add it. If there was a block of + * NOPs then indicate the number of bytes than the block + * covers (nop*). + */ + if (nops_count) { + s = nops_count == 1 ? "" : "*"; + dalt->insn[j + 1].str = strfmt("nop%s%d", s, nops); + dalt->insn[j + 1].offset = offset; + dalt->insn[j + 1].nops = nops; + j++; + } + + if (j > count) + count = j; + } + + return count + 1; +} + /* * Disassemble an alternative. * @@ -1083,6 +1149,8 @@ static void *disas_alt(struct disas_context *dctx, * Print default and non-default alternatives. */ + insn_count = disas_alt_trim_nops(dalts, alt_count, insn_count); + if (opts.wide) disas_alt_print_wide(alt_name, dalts, alt_count, insn_count); else -- cgit v1.2.3 From 6ec33db1aaf06a76fb063610e668f8e12f32ebbf Mon Sep 17 00:00:00 2001 From: Ingo Molnar Date: Mon, 1 Dec 2025 10:42:27 +0100 Subject: objtool: Fix segfault on unknown alternatives So 'objtool --link -d vmlinux.o' gets surprised by this endbr64+endbr64 pattern in ___bpf_prog_run(): ___bpf_prog_run: 1e7680: ___bpf_prog_run+0x0 push %r12 1e7682: ___bpf_prog_run+0x2 mov %rdi,%r12 1e7685: ___bpf_prog_run+0x5 push %rbp 1e7686: ___bpf_prog_run+0x6 xor %ebp,%ebp 1e7688: ___bpf_prog_run+0x8 push %rbx 1e7689: ___bpf_prog_run+0x9 mov %rsi,%rbx 1e768c: ___bpf_prog_run+0xc movzbl (%rbx),%esi 1e768f: ___bpf_prog_run+0xf movzbl %sil,%edx 1e7693: ___bpf_prog_run+0x13 mov %esi,%eax 1e7695: ___bpf_prog_run+0x15 mov 0x0(,%rdx,8),%rdx 1e769d: ___bpf_prog_run+0x1d jmp 0x1e76a2 <__x86_indirect_thunk_rdx> 1e76a2: ___bpf_prog_run+0x22 endbr64 1e76a6: ___bpf_prog_run+0x26 endbr64 1e76aa: ___bpf_prog_run+0x2a mov 0x4(%rbx),%edx And crashes due to blindly dereferencing alt->insn->alt_group. Bail out on NULL ->alt_group, which produces this warning and continues with the disassembly, instead of a segfault: .git/O/vmlinux.o: warning: objtool: : failed to disassemble alternative Cc: Alexandre Chartre Cc: Peter Zijlstra (Intel) Cc: Josh Poimboeuf Cc: linux-kernel@vger.kernel.org Signed-off-by: Ingo Molnar --- tools/objtool/disas.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'tools/objtool/disas.c') diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c index 441b9306eafc..2b5059f55e40 100644 --- a/tools/objtool/disas.c +++ b/tools/objtool/disas.c @@ -684,6 +684,9 @@ char *disas_alt_name(struct alternative *alt) * '?' unknown flag */ + if (!alt->insn->alt_group) + return NULL; + feature = alt->insn->alt_group->feature; num = alt_feature(feature); flags = alt_flags(feature); -- cgit v1.2.3