Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

perf disasm: Allow configuring what disassemblers to use

The perf tools annotation code used for a long time parsing the output
of binutils's objdump (or its reimplementations, like llvm's) to then
parse and augment it with samples, allow navigation, etc.

More recently disassemblers from the capstone and llvm (libraries, not
parsing the output of tools using those libraries to mimic binutils's
objdump output) were introduced.

So when all those methods are available, there is a static preference
for a series of attempts of disassembling a binary, with the 'llvm,
capstone, objdump' sequence being hard coded.

This patch allows users to change that sequence, specifying via a 'perf
config' 'annotate.disassemblers' entry which and in what order
disassemblers should be attempted.

As alluded to in the comments in the source code of this series, this
flexibility is useful for users and developers alike, elliminating the
requirement to rebuild the tool with some specific set of libraries to
see how the output of disassembling would be for one of these methods.

root@x1:~# rm -f ~/.perfconfig
root@x1:~# perf annotate -v --stdio2 update_load_avg
<SNIP>
symbol__disassemble:
filename=/usr/lib/debug/lib/modules/6.11.4-201.fc40.x86_64/vmlinux,
sym=update_load_avg, start=0xffffffffb6148fe0, en>
annotating [0x6ff7170]
/usr/lib/debug/lib/modules/6.11.4-201.fc40.x86_64/vmlinux :
[0x7407ca0] update_load_avg
Disassembled with llvm
annotate.disassemblers=llvm,capstone,objdump
Samples: 66 of event 'cpu_atom/cycles/P', 10000 Hz,
Event count (approx.): 5185444, [percent: local period]
update_load_avg()
/usr/lib/debug/lib/modules/6.11.4-201.fc40.x86_64/vmlinux
Percent 0xffffffff81148fe0 <update_load_avg>:
1.61 pushq %r15
pushq %r14
1.00 pushq %r13
movl %edx,%r13d
1.90 pushq %r12
pushq %rbp
movq %rsi,%rbp
pushq %rbx
movq %rdi,%rbx
subq $0x18,%rsp
15.14 movl 0x1a4(%rdi),%eax

root@x1:~# perf config annotate.disassemblers=capstone
root@x1:~# cat ~/.perfconfig
# this file is auto-generated.
[annotate]
disassemblers = capstone
root@x1:~#
root@x1:~# perf annotate -v --stdio2 update_load_avg
<SNIP>
Disassembled with capstone
annotate.disassemblers=capstone
Samples: 66 of event 'cpu_atom/cycles/P', 10000 Hz,
Event count (approx.): 5185444, [percent: local period]
update_load_avg()
/usr/lib/debug/lib/modules/6.11.4-201.fc40.x86_64/vmlinux
Percent 0xffffffff81148fe0 <update_load_avg>:
1.61 pushq %r15
pushq %r14
1.00 pushq %r13
movl %edx,%r13d
1.90 pushq %r12
pushq %rbp
movq %rsi,%rbp
pushq %rbx
movq %rdi,%rbx
subq $0x18,%rsp
15.14 movl 0x1a4(%rdi),%eax
root@x1:~# perf config annotate.disassemblers=objdump,capstone
root@x1:~# perf config annotate.disassemblers
annotate.disassemblers=objdump,capstone
root@x1:~# cat ~/.perfconfig
# this file is auto-generated.
[annotate]
disassemblers = objdump,capstone
root@x1:~# perf annotate -v --stdio2 update_load_avg
Executing: objdump --start-address=0xffffffff81148fe0 \
--stop-address=0xffffffff811497aa \
-d --no-show-raw-insn -S -C "$1"
Disassembled with objdump
annotate.disassemblers=objdump,capstone
Samples: 66 of event 'cpu_atom/cycles/P', 10000 Hz,
Event count (approx.): 5185444, [percent: local period]
update_load_avg()
/usr/lib/debug/lib/modules/6.11.4-201.fc40.x86_64/vmlinux
Percent

Disassembly of section .text:

ffffffff81148fe0 <update_load_avg>:
#define DO_ATTACH 0x4

ffffffff81148fe0 <update_load_avg>:
#define DO_ATTACH 0x4
#define DO_DETACH 0x8

/* Update task and its cfs_rq load average */
static inline void update_load_avg(struct cfs_rq *cfs_rq,
struct sched_entity *se,
int flags)
{
1.61 push %r15
push %r14
1.00 push %r13
mov %edx,%r13d
1.90 push %r12
push %rbp
mov %rsi,%rbp
push %rbx
mov %rdi,%rbx
sub $0x18,%rsp
}

/* rq->task_clock normalized against any time
this cfs_rq has spent throttled */
static inline u64 cfs_rq_clock_pelt(struct cfs_rq *cfs_rq)
{
if (unlikely(cfs_rq->throttle_count))
15.14 mov 0x1a4(%rdi),%eax
root@x1:~#

After adding a way to select the disassembler from the command line a
'perf test' comparing the output of the various diassemblers should be
introduced, to test these codebases.

Acked-by: Ian Rogers <irogers@google.com>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Athira Rajeev <atrajeev@linux.vnet.ibm.com>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Kan Liang <kan.liang@linux.intel.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Steinar H. Gunderson <sesse@google.com>
Link: https://lore.kernel.org/r/20241111151734.1018476-4-acme@kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>

+96 -6
+13
tools/perf/Documentation/perf-config.txt
··· 247 247 These are in control of addresses, jump function, source code 248 248 in lines of assembly code from a specific program. 249 249 250 + annotate.disassemblers:: 251 + Choose the disassembler to use: "objdump", "llvm", "capstone", 252 + if not specified it will first try, if available, the "llvm" one, 253 + then, if it fails, "capstone", and finally the original "objdump" 254 + based one. 255 + 256 + Choosing a different one is useful when handling some feature that 257 + is known to be best support at some point by one of the options, 258 + to compare the output when in doubt about some bug, etc. 259 + 260 + This can be a list, in order of preference, the first one that works 261 + finishes the process. 262 + 250 263 annotate.addr2line:: 251 264 addr2line binary to use for file names and line numbers. 252 265
+6
tools/perf/util/annotate.c
··· 2116 2116 opt->offset_level = ANNOTATION__MAX_OFFSET_LEVEL; 2117 2117 else if (opt->offset_level < ANNOTATION__MIN_OFFSET_LEVEL) 2118 2118 opt->offset_level = ANNOTATION__MIN_OFFSET_LEVEL; 2119 + } else if (!strcmp(var, "annotate.disassemblers")) { 2120 + opt->disassemblers_str = strdup(value); 2121 + if (!opt->disassemblers_str) { 2122 + pr_err("Not enough memory for annotate.disassemblers\n"); 2123 + return -1; 2124 + } 2119 2125 } else if (!strcmp(var, "annotate.hide_src_code")) { 2120 2126 opt->hide_src_code = perf_config_bool("hide_src_code", value); 2121 2127 } else if (!strcmp(var, "annotate.jump_arrows")) {
+6
tools/perf/util/annotate.h
··· 34 34 #define ANNOTATION__BR_CNTR_WIDTH 30 35 35 #define ANNOTATION_DUMMY_LEN 256 36 36 37 + // llvm, capstone, objdump 38 + #define MAX_DISASSEMBLERS 3 39 + 37 40 struct annotation_options { 38 41 bool hide_src_code, 39 42 use_offset, ··· 52 49 annotate_src, 53 50 full_addr; 54 51 u8 offset_level; 52 + u8 nr_disassemblers; 55 53 int min_pcnt; 56 54 int max_lines; 57 55 int context; 58 56 char *objdump_path; 59 57 char *disassembler_style; 58 + const char *disassemblers_str; 59 + const char *disassemblers[MAX_DISASSEMBLERS]; 60 60 const char *prefix; 61 61 const char *prefix_strip; 62 62 unsigned int percent_type;
+71 -6
tools/perf/util/disasm.c
··· 2213 2213 return err; 2214 2214 } 2215 2215 2216 + static int annotation_options__init_disassemblers(struct annotation_options *options) 2217 + { 2218 + char *disassembler; 2219 + 2220 + if (options->disassemblers_str == NULL) { 2221 + const char *default_disassemblers_str = 2222 + #ifdef HAVE_LIBLLVM_SUPPORT 2223 + "llvm," 2224 + #endif 2225 + #ifdef HAVE_LIBCAPSTONE_SUPPORT 2226 + "capstone," 2227 + #endif 2228 + "objdump"; 2229 + 2230 + options->disassemblers_str = strdup(default_disassemblers_str); 2231 + if (!options->disassemblers_str) 2232 + goto out_enomem; 2233 + } 2234 + 2235 + disassembler = strdup(options->disassemblers_str); 2236 + if (disassembler == NULL) 2237 + goto out_enomem; 2238 + 2239 + while (1) { 2240 + char *comma = strchr(disassembler, ','); 2241 + 2242 + if (comma != NULL) 2243 + *comma = '\0'; 2244 + 2245 + options->disassemblers[options->nr_disassemblers++] = strim(disassembler); 2246 + 2247 + if (comma == NULL) 2248 + break; 2249 + 2250 + disassembler = comma + 1; 2251 + 2252 + if (options->nr_disassemblers >= MAX_DISASSEMBLERS) { 2253 + pr_debug("annotate.disassemblers can have at most %d entries, ignoring \"%s\"\n", 2254 + MAX_DISASSEMBLERS, disassembler); 2255 + break; 2256 + } 2257 + } 2258 + 2259 + return 0; 2260 + 2261 + out_enomem: 2262 + pr_err("Not enough memory for annotate.disassemblers\n"); 2263 + return -1; 2264 + } 2265 + 2216 2266 int symbol__disassemble(struct symbol *sym, struct annotate_args *args) 2217 2267 { 2268 + struct annotation_options *options = args->options; 2218 2269 struct map *map = args->ms.map; 2219 2270 struct dso *dso = map__dso(map); 2220 2271 char symfs_filename[PATH_MAX]; 2221 2272 bool delete_extract = false; 2222 2273 struct kcore_extract kce; 2274 + const char *disassembler; 2223 2275 bool decomp = false; 2224 2276 int err = dso__disassemble_filename(dso, symfs_filename, sizeof(symfs_filename)); 2225 2277 ··· 2331 2279 } 2332 2280 } 2333 2281 2334 - err = symbol__disassemble_llvm(symfs_filename, sym, args); 2335 - if (err == 0) 2282 + err = annotation_options__init_disassemblers(options); 2283 + if (err) 2336 2284 goto out_remove_tmp; 2337 2285 2338 - err = symbol__disassemble_capstone(symfs_filename, sym, args); 2339 - if (err == 0) 2340 - goto out_remove_tmp; 2286 + err = -1; 2341 2287 2342 - err = symbol__disassemble_objdump(symfs_filename, sym, args); 2288 + for (int i = 0; i < options->nr_disassemblers && err != 0; ++i) { 2289 + disassembler = options->disassemblers[i]; 2343 2290 2291 + if (!strcmp(disassembler, "llvm")) 2292 + err = symbol__disassemble_llvm(symfs_filename, sym, args); 2293 + else if (!strcmp(disassembler, "capstone")) 2294 + err = symbol__disassemble_capstone(symfs_filename, sym, args); 2295 + else if (!strcmp(disassembler, "objdump")) 2296 + err = symbol__disassemble_objdump(symfs_filename, sym, args); 2297 + else 2298 + pr_debug("Unknown disassembler %s, skipping...\n", disassembler); 2299 + } 2300 + 2301 + if (err == 0) { 2302 + pr_debug("Disassembled with %s\nannotate.disassemblers=%s\n", 2303 + disassembler, options->disassemblers_str); 2304 + } 2344 2305 out_remove_tmp: 2345 2306 if (decomp) 2346 2307 unlink(symfs_filename);