]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
objtool: Disassemble code with libopcodes instead of running objdump
authorAlexandre Chartre <alexandre.chartre@oracle.com>
Fri, 21 Nov 2025 09:53:13 +0000 (10:53 +0100)
committerPeter Zijlstra <peterz@infradead.org>
Fri, 21 Nov 2025 14:30:07 +0000 (15:30 +0100)
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 <alexandre.chartre@oracle.com>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Acked-by: Josh Poimboeuf <jpoimboe@kernel.org>
Link: https://patch.msgid.link/20251121095340.464045-4-alexandre.chartre@oracle.com
tools/objtool/.gitignore
tools/objtool/Build
tools/objtool/Makefile
tools/objtool/arch/loongarch/decode.c
tools/objtool/arch/powerpc/decode.c
tools/objtool/arch/x86/decode.c
tools/objtool/check.c
tools/objtool/disas.c
tools/objtool/include/objtool/arch.h
tools/objtool/include/objtool/check.h
tools/objtool/include/objtool/disas.h

index 4faa4dd72f350010a497b09020fb03338a99297d..759303657bd7c5b35f2747d85bc1a611bc9fe1e7 100644 (file)
@@ -1,5 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-only
 arch/x86/lib/inat-tables.c
 /objtool
+feature
+FEATURE-DUMP.objtool
 fixdep
 libsubcmd/
index 17e50a1766d0e42e40769b9ea28ee5bf18849830..9d1e8f28ef953fd133392eb590fca86ab96f9b91 100644 (file)
@@ -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
index 021f55b7bd87237d03e4f072da2dccd40942ea4d..df793ca6fc1a1e0d1b92dded559f5e8b44c9c617 100644 (file)
@@ -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:
 
index 0115b97c526b83d24300c43dd8bcf062569cc040..1de86ebb637d5992fef0a915528190675bddf478 100644 (file)
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 #include <string.h>
 #include <objtool/check.h>
+#include <objtool/disas.h>
 #include <objtool/warn.h>
 #include <asm/inst.h>
 #include <asm/orc_types.h>
@@ -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 */
index 3a9b748216edc9359d14c8da94ac0e5508681196..4f68b402e7855940b1617e24ce3051a5b2f468f5 100644 (file)
@@ -3,6 +3,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <objtool/check.h>
+#include <objtool/disas.h>
 #include <objtool/elf.h>
 #include <objtool/arch.h>
 #include <objtool/warn.h>
@@ -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 */
index cc85db7b65a4127c168e3efbf60d7a6cd29ad535..83e9c604ce1058d3df6548a869a34ee5bf2866a3 100644 (file)
@@ -16,6 +16,7 @@
 
 #include <asm/orc_types.h>
 #include <objtool/check.h>
+#include <objtool/disas.h>
 #include <objtool/elf.h>
 #include <objtool/arch.h>
 #include <objtool/warn.h>
@@ -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 */
index 8b1a6a5185d3f4322594f8911885fe426beabe99..21d45a35f3c975a3f494869465eb9d34b49f4ce1 100644 (file)
@@ -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;
 
index 7a18e51d43e62ba01c0140beab936c8d547c8306..11ac2ec04afc18ce8ea0cc11415352d484f69112 100644 (file)
@@ -4,18 +4,56 @@
  */
 
 #include <objtool/arch.h>
+#include <objtool/check.h>
 #include <objtool/disas.h>
 #include <objtool/warn.h>
 
+#include <bfd.h>
 #include <linux/string.h>
+#include <tools/dis-asm-compat.h>
 
 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);
 }
index d89f8b5ec14e36c071016d7668bebc9950d00104..18c0e69ee61701bba82c28f61dc09c484a25fa69 100644 (file)
@@ -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 <bfd.h>
+#include <dis-asm.h>
+
+int arch_disas_info_init(struct disassemble_info *dinfo);
+
+#endif /* DISAS */
+
 #endif /* _ARCH_H */
index d73b0c3ae1ee3a372d3e1deff74f68643f56b4b2..674f57466d1250a5b2e0a1ade63ee8b882a356e3 100644 (file)
@@ -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 */
index 5c543b69fc61255a42976f87a2ab16bfa954c899..3ec3ce2e4e6f0106aa9e03323827d0a47c5ae41d 100644 (file)
@@ -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 <objtool/warn.h>
+
+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 */