.. | .. |
---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
---|
1 | 2 | /* |
---|
2 | 3 | * Copyright (C) 2011, Red Hat Inc, Arnaldo Carvalho de Melo <acme@redhat.com> |
---|
3 | 4 | * |
---|
4 | 5 | * Parts came from builtin-annotate.c, see those files for further |
---|
5 | 6 | * copyright notes. |
---|
6 | | - * |
---|
7 | | - * Released under the GPL v2. (and only v2, not any later version) |
---|
8 | 7 | */ |
---|
9 | 8 | |
---|
10 | 9 | #include <errno.h> |
---|
11 | 10 | #include <inttypes.h> |
---|
12 | | -#include "util.h" |
---|
| 11 | +#include <libgen.h> |
---|
| 12 | +#include <stdlib.h> |
---|
| 13 | +#include <bpf/bpf.h> |
---|
| 14 | +#include <bpf/btf.h> |
---|
| 15 | +#include <bpf/libbpf.h> |
---|
| 16 | +#include <linux/btf.h> |
---|
| 17 | +#include "util.h" // hex_width() |
---|
13 | 18 | #include "ui/ui.h" |
---|
14 | 19 | #include "sort.h" |
---|
15 | 20 | #include "build-id.h" |
---|
16 | 21 | #include "color.h" |
---|
17 | 22 | #include "config.h" |
---|
18 | | -#include "cache.h" |
---|
| 23 | +#include "dso.h" |
---|
| 24 | +#include "env.h" |
---|
| 25 | +#include "map.h" |
---|
| 26 | +#include "maps.h" |
---|
19 | 27 | #include "symbol.h" |
---|
| 28 | +#include "srcline.h" |
---|
20 | 29 | #include "units.h" |
---|
21 | 30 | #include "debug.h" |
---|
22 | 31 | #include "annotate.h" |
---|
23 | 32 | #include "evsel.h" |
---|
24 | 33 | #include "evlist.h" |
---|
| 34 | +#include "bpf-event.h" |
---|
25 | 35 | #include "block-range.h" |
---|
26 | 36 | #include "string2.h" |
---|
| 37 | +#include "util/event.h" |
---|
27 | 38 | #include "arch/common.h" |
---|
28 | 39 | #include <regex.h> |
---|
29 | 40 | #include <pthread.h> |
---|
30 | 41 | #include <linux/bitops.h> |
---|
31 | 42 | #include <linux/kernel.h> |
---|
| 43 | +#include <linux/string.h> |
---|
| 44 | +#include <subcmd/parse-options.h> |
---|
| 45 | +#include <subcmd/run-command.h> |
---|
32 | 46 | |
---|
33 | 47 | /* FIXME: For the HE_COLORSET */ |
---|
34 | 48 | #include "ui/browser.h" |
---|
.. | .. |
---|
42 | 56 | #define DARROW_CHAR ((unsigned char)'.') |
---|
43 | 57 | #define UARROW_CHAR ((unsigned char)'-') |
---|
44 | 58 | |
---|
45 | | -#include "sane_ctype.h" |
---|
| 59 | +#include <linux/ctype.h> |
---|
46 | 60 | |
---|
47 | 61 | struct annotation_options annotation__default_options = { |
---|
48 | 62 | .use_offset = true, |
---|
.. | .. |
---|
134 | 148 | return 0; |
---|
135 | 149 | } |
---|
136 | 150 | |
---|
| 151 | +#include "arch/arc/annotate/instructions.c" |
---|
137 | 152 | #include "arch/arm/annotate/instructions.c" |
---|
138 | 153 | #include "arch/arm64/annotate/instructions.c" |
---|
| 154 | +#include "arch/csky/annotate/instructions.c" |
---|
139 | 155 | #include "arch/x86/annotate/instructions.c" |
---|
140 | 156 | #include "arch/powerpc/annotate/instructions.c" |
---|
141 | 157 | #include "arch/s390/annotate/instructions.c" |
---|
| 158 | +#include "arch/sparc/annotate/instructions.c" |
---|
142 | 159 | |
---|
143 | 160 | static struct arch architectures[] = { |
---|
| 161 | + { |
---|
| 162 | + .name = "arc", |
---|
| 163 | + .init = arc__annotate_init, |
---|
| 164 | + }, |
---|
144 | 165 | { |
---|
145 | 166 | .name = "arm", |
---|
146 | 167 | .init = arm__annotate_init, |
---|
.. | .. |
---|
148 | 169 | { |
---|
149 | 170 | .name = "arm64", |
---|
150 | 171 | .init = arm64__annotate_init, |
---|
| 172 | + }, |
---|
| 173 | + { |
---|
| 174 | + .name = "csky", |
---|
| 175 | + .init = csky__annotate_init, |
---|
151 | 176 | }, |
---|
152 | 177 | { |
---|
153 | 178 | .name = "x86", |
---|
.. | .. |
---|
170 | 195 | .comment_char = '#', |
---|
171 | 196 | }, |
---|
172 | 197 | }, |
---|
| 198 | + { |
---|
| 199 | + .name = "sparc", |
---|
| 200 | + .init = sparc__annotate_init, |
---|
| 201 | + .objdump = { |
---|
| 202 | + .comment_char = '#', |
---|
| 203 | + }, |
---|
| 204 | + }, |
---|
173 | 205 | }; |
---|
174 | 206 | |
---|
175 | 207 | static void ins__delete(struct ins_operands *ops) |
---|
.. | .. |
---|
183 | 215 | } |
---|
184 | 216 | |
---|
185 | 217 | static int ins__raw_scnprintf(struct ins *ins, char *bf, size_t size, |
---|
186 | | - struct ins_operands *ops) |
---|
| 218 | + struct ins_operands *ops, int max_ins_name) |
---|
187 | 219 | { |
---|
188 | | - return scnprintf(bf, size, "%-6s %s", ins->name, ops->raw); |
---|
| 220 | + return scnprintf(bf, size, "%-*s %s", max_ins_name, ins->name, ops->raw); |
---|
189 | 221 | } |
---|
190 | 222 | |
---|
191 | 223 | int ins__scnprintf(struct ins *ins, char *bf, size_t size, |
---|
192 | | - struct ins_operands *ops) |
---|
| 224 | + struct ins_operands *ops, int max_ins_name) |
---|
193 | 225 | { |
---|
194 | 226 | if (ins->ops->scnprintf) |
---|
195 | | - return ins->ops->scnprintf(ins, bf, size, ops); |
---|
| 227 | + return ins->ops->scnprintf(ins, bf, size, ops, max_ins_name); |
---|
196 | 228 | |
---|
197 | | - return ins__raw_scnprintf(ins, bf, size, ops); |
---|
| 229 | + return ins__raw_scnprintf(ins, bf, size, ops, max_ins_name); |
---|
198 | 230 | } |
---|
199 | 231 | |
---|
200 | 232 | bool ins__is_fused(struct arch *arch, const char *ins1, const char *ins2) |
---|
.. | .. |
---|
210 | 242 | char *endptr, *tok, *name; |
---|
211 | 243 | struct map *map = ms->map; |
---|
212 | 244 | struct addr_map_symbol target = { |
---|
213 | | - .map = map, |
---|
| 245 | + .ms = { .map = map, }, |
---|
214 | 246 | }; |
---|
215 | 247 | |
---|
216 | 248 | ops->target.addr = strtoull(ops->raw, &endptr, 16); |
---|
.. | .. |
---|
238 | 270 | find_target: |
---|
239 | 271 | target.addr = map__objdump_2mem(map, ops->target.addr); |
---|
240 | 272 | |
---|
241 | | - if (map_groups__find_ams(&target) == 0 && |
---|
242 | | - map__rip_2objdump(target.map, map->map_ip(target.map, target.addr)) == ops->target.addr) |
---|
243 | | - ops->target.sym = target.sym; |
---|
| 273 | + if (maps__find_ams(ms->maps, &target) == 0 && |
---|
| 274 | + map__rip_2objdump(target.ms.map, map->map_ip(target.ms.map, target.addr)) == ops->target.addr) |
---|
| 275 | + ops->target.sym = target.ms.sym; |
---|
244 | 276 | |
---|
245 | 277 | return 0; |
---|
246 | 278 | |
---|
.. | .. |
---|
258 | 290 | } |
---|
259 | 291 | |
---|
260 | 292 | static int call__scnprintf(struct ins *ins, char *bf, size_t size, |
---|
261 | | - struct ins_operands *ops) |
---|
| 293 | + struct ins_operands *ops, int max_ins_name) |
---|
262 | 294 | { |
---|
263 | 295 | if (ops->target.sym) |
---|
264 | | - return scnprintf(bf, size, "%-6s %s", ins->name, ops->target.sym->name); |
---|
| 296 | + return scnprintf(bf, size, "%-*s %s", max_ins_name, ins->name, ops->target.sym->name); |
---|
265 | 297 | |
---|
266 | 298 | if (ops->target.addr == 0) |
---|
267 | | - return ins__raw_scnprintf(ins, bf, size, ops); |
---|
| 299 | + return ins__raw_scnprintf(ins, bf, size, ops, max_ins_name); |
---|
268 | 300 | |
---|
269 | 301 | if (ops->target.name) |
---|
270 | | - return scnprintf(bf, size, "%-6s %s", ins->name, ops->target.name); |
---|
| 302 | + return scnprintf(bf, size, "%-*s %s", max_ins_name, ins->name, ops->target.name); |
---|
271 | 303 | |
---|
272 | | - return scnprintf(bf, size, "%-6s *%" PRIx64, ins->name, ops->target.addr); |
---|
| 304 | + return scnprintf(bf, size, "%-*s *%" PRIx64, max_ins_name, ins->name, ops->target.addr); |
---|
273 | 305 | } |
---|
274 | 306 | |
---|
275 | 307 | static struct ins_ops call_ops = { |
---|
.. | .. |
---|
285 | 317 | /* |
---|
286 | 318 | * Prevents from matching commas in the comment section, e.g.: |
---|
287 | 319 | * ffff200008446e70: b.cs ffff2000084470f4 <generic_exec_single+0x314> // b.hs, b.nlast |
---|
| 320 | + * |
---|
| 321 | + * and skip comma as part of function arguments, e.g.: |
---|
| 322 | + * 1d8b4ac <linemap_lookup(line_maps const*, unsigned int)+0xcc> |
---|
288 | 323 | */ |
---|
289 | 324 | static inline const char *validate_comma(const char *c, struct ins_operands *ops) |
---|
290 | 325 | { |
---|
291 | 326 | if (ops->raw_comment && c > ops->raw_comment) |
---|
| 327 | + return NULL; |
---|
| 328 | + |
---|
| 329 | + if (ops->raw_func_start && c > ops->raw_func_start) |
---|
292 | 330 | return NULL; |
---|
293 | 331 | |
---|
294 | 332 | return c; |
---|
.. | .. |
---|
299 | 337 | struct map *map = ms->map; |
---|
300 | 338 | struct symbol *sym = ms->sym; |
---|
301 | 339 | struct addr_map_symbol target = { |
---|
302 | | - .map = map, |
---|
| 340 | + .ms = { .map = map, }, |
---|
303 | 341 | }; |
---|
304 | 342 | const char *c = strchr(ops->raw, ','); |
---|
305 | 343 | u64 start, end; |
---|
306 | 344 | |
---|
307 | 345 | ops->raw_comment = strchr(ops->raw, arch->objdump.comment_char); |
---|
| 346 | + ops->raw_func_start = strchr(ops->raw, '<'); |
---|
| 347 | + |
---|
308 | 348 | c = validate_comma(c, ops); |
---|
309 | 349 | |
---|
310 | 350 | /* |
---|
.. | .. |
---|
358 | 398 | * Actual navigation will come next, with further understanding of how |
---|
359 | 399 | * the symbol searching and disassembly should be done. |
---|
360 | 400 | */ |
---|
361 | | - if (map_groups__find_ams(&target) == 0 && |
---|
362 | | - map__rip_2objdump(target.map, map->map_ip(target.map, target.addr)) == ops->target.addr) |
---|
363 | | - ops->target.sym = target.sym; |
---|
| 401 | + if (maps__find_ams(ms->maps, &target) == 0 && |
---|
| 402 | + map__rip_2objdump(target.ms.map, map->map_ip(target.ms.map, target.addr)) == ops->target.addr) |
---|
| 403 | + ops->target.sym = target.ms.sym; |
---|
364 | 404 | |
---|
365 | 405 | if (!ops->target.outside) { |
---|
366 | 406 | ops->target.offset = target.addr - start; |
---|
.. | .. |
---|
373 | 413 | } |
---|
374 | 414 | |
---|
375 | 415 | static int jump__scnprintf(struct ins *ins, char *bf, size_t size, |
---|
376 | | - struct ins_operands *ops) |
---|
| 416 | + struct ins_operands *ops, int max_ins_name) |
---|
377 | 417 | { |
---|
378 | 418 | const char *c; |
---|
379 | 419 | |
---|
380 | 420 | if (!ops->target.addr || ops->target.offset < 0) |
---|
381 | | - return ins__raw_scnprintf(ins, bf, size, ops); |
---|
| 421 | + return ins__raw_scnprintf(ins, bf, size, ops, max_ins_name); |
---|
382 | 422 | |
---|
383 | 423 | if (ops->target.outside && ops->target.sym != NULL) |
---|
384 | | - return scnprintf(bf, size, "%-6s %s", ins->name, ops->target.sym->name); |
---|
| 424 | + return scnprintf(bf, size, "%-*s %s", max_ins_name, ins->name, ops->target.sym->name); |
---|
385 | 425 | |
---|
386 | 426 | c = strchr(ops->raw, ','); |
---|
387 | 427 | c = validate_comma(c, ops); |
---|
.. | .. |
---|
400 | 440 | c++; |
---|
401 | 441 | } |
---|
402 | 442 | |
---|
403 | | - return scnprintf(bf, size, "%-6s %.*s%" PRIx64, |
---|
| 443 | + return scnprintf(bf, size, "%-*s %.*s%" PRIx64, max_ins_name, |
---|
404 | 444 | ins->name, c ? c - ops->raw : 0, ops->raw, |
---|
405 | 445 | ops->target.offset); |
---|
406 | 446 | } |
---|
.. | .. |
---|
468 | 508 | } |
---|
469 | 509 | |
---|
470 | 510 | static int lock__scnprintf(struct ins *ins, char *bf, size_t size, |
---|
471 | | - struct ins_operands *ops) |
---|
| 511 | + struct ins_operands *ops, int max_ins_name) |
---|
472 | 512 | { |
---|
473 | 513 | int printed; |
---|
474 | 514 | |
---|
475 | 515 | if (ops->locked.ins.ops == NULL) |
---|
476 | | - return ins__raw_scnprintf(ins, bf, size, ops); |
---|
| 516 | + return ins__raw_scnprintf(ins, bf, size, ops, max_ins_name); |
---|
477 | 517 | |
---|
478 | | - printed = scnprintf(bf, size, "%-6s ", ins->name); |
---|
| 518 | + printed = scnprintf(bf, size, "%-*s ", max_ins_name, ins->name); |
---|
479 | 519 | return printed + ins__scnprintf(&ops->locked.ins, bf + printed, |
---|
480 | | - size - printed, ops->locked.ops); |
---|
| 520 | + size - printed, ops->locked.ops, max_ins_name); |
---|
481 | 521 | } |
---|
482 | 522 | |
---|
483 | 523 | static void lock__delete(struct ins_operands *ops) |
---|
.. | .. |
---|
537 | 577 | if (comment == NULL) |
---|
538 | 578 | return 0; |
---|
539 | 579 | |
---|
540 | | - comment = ltrim(comment); |
---|
| 580 | + comment = skip_spaces(comment); |
---|
541 | 581 | comment__symbol(ops->source.raw, comment + 1, &ops->source.addr, &ops->source.name); |
---|
542 | 582 | comment__symbol(ops->target.raw, comment + 1, &ops->target.addr, &ops->target.name); |
---|
543 | 583 | |
---|
.. | .. |
---|
549 | 589 | } |
---|
550 | 590 | |
---|
551 | 591 | static int mov__scnprintf(struct ins *ins, char *bf, size_t size, |
---|
552 | | - struct ins_operands *ops) |
---|
| 592 | + struct ins_operands *ops, int max_ins_name) |
---|
553 | 593 | { |
---|
554 | | - return scnprintf(bf, size, "%-6s %s,%s", ins->name, |
---|
| 594 | + return scnprintf(bf, size, "%-*s %s,%s", max_ins_name, ins->name, |
---|
555 | 595 | ops->source.name ?: ops->source.raw, |
---|
556 | 596 | ops->target.name ?: ops->target.raw); |
---|
557 | 597 | } |
---|
.. | .. |
---|
582 | 622 | if (comment == NULL) |
---|
583 | 623 | return 0; |
---|
584 | 624 | |
---|
585 | | - comment = ltrim(comment); |
---|
| 625 | + comment = skip_spaces(comment); |
---|
586 | 626 | comment__symbol(ops->target.raw, comment + 1, &ops->target.addr, &ops->target.name); |
---|
587 | 627 | |
---|
588 | 628 | return 0; |
---|
589 | 629 | } |
---|
590 | 630 | |
---|
591 | 631 | static int dec__scnprintf(struct ins *ins, char *bf, size_t size, |
---|
592 | | - struct ins_operands *ops) |
---|
| 632 | + struct ins_operands *ops, int max_ins_name) |
---|
593 | 633 | { |
---|
594 | | - return scnprintf(bf, size, "%-6s %s", ins->name, |
---|
| 634 | + return scnprintf(bf, size, "%-*s %s", max_ins_name, ins->name, |
---|
595 | 635 | ops->target.name ?: ops->target.raw); |
---|
596 | 636 | } |
---|
597 | 637 | |
---|
.. | .. |
---|
601 | 641 | }; |
---|
602 | 642 | |
---|
603 | 643 | static int nop__scnprintf(struct ins *ins __maybe_unused, char *bf, size_t size, |
---|
604 | | - struct ins_operands *ops __maybe_unused) |
---|
| 644 | + struct ins_operands *ops __maybe_unused, int max_ins_name) |
---|
605 | 645 | { |
---|
606 | | - return scnprintf(bf, size, "%-6s", "nop"); |
---|
| 646 | + return scnprintf(bf, size, "%-*s", max_ins_name, "nop"); |
---|
607 | 647 | } |
---|
608 | 648 | |
---|
609 | 649 | static struct ins_ops nop_ops = { |
---|
.. | .. |
---|
821 | 861 | ch[offset].start < start) |
---|
822 | 862 | return 0; |
---|
823 | 863 | } |
---|
| 864 | + |
---|
| 865 | + if (ch[offset].num < NUM_SPARKS) |
---|
| 866 | + ch[offset].cycles_spark[ch[offset].num] = cycles; |
---|
| 867 | + |
---|
824 | 868 | ch[offset].have_start = have_start; |
---|
825 | 869 | ch[offset].start = start; |
---|
826 | 870 | ch[offset].cycles += cycles; |
---|
.. | .. |
---|
828 | 872 | return 0; |
---|
829 | 873 | } |
---|
830 | 874 | |
---|
831 | | -static int __symbol__inc_addr_samples(struct symbol *sym, struct map *map, |
---|
| 875 | +static int __symbol__inc_addr_samples(struct map_symbol *ms, |
---|
832 | 876 | struct annotated_source *src, int evidx, u64 addr, |
---|
833 | 877 | struct perf_sample *sample) |
---|
834 | 878 | { |
---|
| 879 | + struct symbol *sym = ms->sym; |
---|
835 | 880 | unsigned offset; |
---|
836 | 881 | struct sym_hist *h; |
---|
837 | 882 | |
---|
838 | | - pr_debug3("%s: addr=%#" PRIx64 "\n", __func__, map->unmap_ip(map, addr)); |
---|
| 883 | + pr_debug3("%s: addr=%#" PRIx64 "\n", __func__, ms->map->unmap_ip(ms->map, addr)); |
---|
839 | 884 | |
---|
840 | 885 | if ((addr < sym->start || addr >= sym->end) && |
---|
841 | 886 | (addr != sym->end || sym->start != sym->end)) { |
---|
.. | .. |
---|
902 | 947 | return notes->src; |
---|
903 | 948 | } |
---|
904 | 949 | |
---|
905 | | -static int symbol__inc_addr_samples(struct symbol *sym, struct map *map, |
---|
906 | | - struct perf_evsel *evsel, u64 addr, |
---|
| 950 | +static int symbol__inc_addr_samples(struct map_symbol *ms, |
---|
| 951 | + struct evsel *evsel, u64 addr, |
---|
907 | 952 | struct perf_sample *sample) |
---|
908 | 953 | { |
---|
| 954 | + struct symbol *sym = ms->sym; |
---|
909 | 955 | struct annotated_source *src; |
---|
910 | 956 | |
---|
911 | 957 | if (sym == NULL) |
---|
912 | 958 | return 0; |
---|
913 | | - src = symbol__hists(sym, evsel->evlist->nr_entries); |
---|
914 | | - return (src) ? __symbol__inc_addr_samples(sym, map, src, evsel->idx, |
---|
915 | | - addr, sample) : 0; |
---|
| 959 | + src = symbol__hists(sym, evsel->evlist->core.nr_entries); |
---|
| 960 | + return src ? __symbol__inc_addr_samples(ms, src, evsel->idx, addr, sample) : 0; |
---|
916 | 961 | } |
---|
917 | 962 | |
---|
918 | 963 | static int symbol__account_cycles(u64 addr, u64 start, |
---|
.. | .. |
---|
960 | 1005 | * it starts on the function start. |
---|
961 | 1006 | */ |
---|
962 | 1007 | if (start && |
---|
963 | | - (start->sym == ams->sym || |
---|
964 | | - (ams->sym && |
---|
965 | | - start->addr == ams->sym->start + ams->map->start))) |
---|
| 1008 | + (start->ms.sym == ams->ms.sym || |
---|
| 1009 | + (ams->ms.sym && |
---|
| 1010 | + start->addr == ams->ms.sym->start + ams->ms.map->start))) |
---|
966 | 1011 | saddr = start->al_addr; |
---|
967 | 1012 | if (saddr == 0) |
---|
968 | 1013 | pr_debug2("BB with bad start: addr %"PRIx64" start %"PRIx64" sym %"PRIx64" saddr %"PRIx64"\n", |
---|
969 | 1014 | ams->addr, |
---|
970 | 1015 | start ? start->addr : 0, |
---|
971 | | - ams->sym ? ams->sym->start + ams->map->start : 0, |
---|
| 1016 | + ams->ms.sym ? ams->ms.sym->start + ams->ms.map->start : 0, |
---|
972 | 1017 | saddr); |
---|
973 | | - err = symbol__account_cycles(ams->al_addr, saddr, ams->sym, cycles); |
---|
| 1018 | + err = symbol__account_cycles(ams->al_addr, saddr, ams->ms.sym, cycles); |
---|
974 | 1019 | if (err) |
---|
975 | 1020 | pr_debug2("account_cycles failed %d\n", err); |
---|
976 | 1021 | return err; |
---|
.. | .. |
---|
991 | 1036 | static void annotation__count_and_fill(struct annotation *notes, u64 start, u64 end, struct cyc_hist *ch) |
---|
992 | 1037 | { |
---|
993 | 1038 | unsigned n_insn; |
---|
| 1039 | + unsigned int cover_insn = 0; |
---|
994 | 1040 | u64 offset; |
---|
995 | 1041 | |
---|
996 | 1042 | n_insn = annotation__count_insn(notes, start, end); |
---|
.. | .. |
---|
998 | 1044 | float ipc = n_insn / ((double)ch->cycles / (double)ch->num); |
---|
999 | 1045 | |
---|
1000 | 1046 | /* Hide data when there are too many overlaps. */ |
---|
1001 | | - if (ch->reset >= 0x7fff || ch->reset >= ch->num / 2) |
---|
| 1047 | + if (ch->reset >= 0x7fff) |
---|
1002 | 1048 | return; |
---|
1003 | 1049 | |
---|
1004 | 1050 | for (offset = start; offset <= end; offset++) { |
---|
1005 | 1051 | struct annotation_line *al = notes->offsets[offset]; |
---|
1006 | 1052 | |
---|
1007 | | - if (al) |
---|
| 1053 | + if (al && al->ipc == 0.0) { |
---|
1008 | 1054 | al->ipc = ipc; |
---|
| 1055 | + cover_insn++; |
---|
| 1056 | + } |
---|
| 1057 | + } |
---|
| 1058 | + |
---|
| 1059 | + if (cover_insn) { |
---|
| 1060 | + notes->hit_cycles += ch->cycles; |
---|
| 1061 | + notes->hit_insn += n_insn * ch->num; |
---|
| 1062 | + notes->cover_insn += cover_insn; |
---|
1009 | 1063 | } |
---|
1010 | 1064 | } |
---|
1011 | 1065 | } |
---|
1012 | 1066 | |
---|
1013 | 1067 | void annotation__compute_ipc(struct annotation *notes, size_t size) |
---|
1014 | 1068 | { |
---|
1015 | | - u64 offset; |
---|
| 1069 | + s64 offset; |
---|
1016 | 1070 | |
---|
1017 | 1071 | if (!notes->src || !notes->src->cycles_hist) |
---|
1018 | 1072 | return; |
---|
1019 | 1073 | |
---|
| 1074 | + notes->total_insn = annotation__count_insn(notes, 0, size - 1); |
---|
| 1075 | + notes->hit_cycles = 0; |
---|
| 1076 | + notes->hit_insn = 0; |
---|
| 1077 | + notes->cover_insn = 0; |
---|
| 1078 | + |
---|
1020 | 1079 | pthread_mutex_lock(¬es->lock); |
---|
1021 | | - for (offset = 0; offset < size; ++offset) { |
---|
| 1080 | + for (offset = size - 1; offset >= 0; --offset) { |
---|
1022 | 1081 | struct cyc_hist *ch; |
---|
1023 | 1082 | |
---|
1024 | 1083 | ch = ¬es->src->cycles_hist[offset]; |
---|
.. | .. |
---|
1040 | 1099 | } |
---|
1041 | 1100 | |
---|
1042 | 1101 | int addr_map_symbol__inc_samples(struct addr_map_symbol *ams, struct perf_sample *sample, |
---|
1043 | | - struct perf_evsel *evsel) |
---|
| 1102 | + struct evsel *evsel) |
---|
1044 | 1103 | { |
---|
1045 | | - return symbol__inc_addr_samples(ams->sym, ams->map, evsel, ams->al_addr, sample); |
---|
| 1104 | + return symbol__inc_addr_samples(&ams->ms, evsel, ams->al_addr, sample); |
---|
1046 | 1105 | } |
---|
1047 | 1106 | |
---|
1048 | 1107 | int hist_entry__inc_addr_samples(struct hist_entry *he, struct perf_sample *sample, |
---|
1049 | | - struct perf_evsel *evsel, u64 ip) |
---|
| 1108 | + struct evsel *evsel, u64 ip) |
---|
1050 | 1109 | { |
---|
1051 | | - return symbol__inc_addr_samples(he->ms.sym, he->ms.map, evsel, ip, sample); |
---|
| 1110 | + return symbol__inc_addr_samples(&he->ms, evsel, ip, sample); |
---|
1052 | 1111 | } |
---|
1053 | 1112 | |
---|
1054 | 1113 | static void disasm_line__init_ins(struct disasm_line *dl, struct arch *arch, struct map_symbol *ms) |
---|
.. | .. |
---|
1064 | 1123 | |
---|
1065 | 1124 | static int disasm_line__parse(char *line, const char **namep, char **rawp) |
---|
1066 | 1125 | { |
---|
1067 | | - char tmp, *name = ltrim(line); |
---|
| 1126 | + char tmp, *name = skip_spaces(line); |
---|
1068 | 1127 | |
---|
1069 | 1128 | if (name[0] == '\0') |
---|
1070 | 1129 | return -1; |
---|
.. | .. |
---|
1082 | 1141 | goto out; |
---|
1083 | 1142 | |
---|
1084 | 1143 | (*rawp)[0] = tmp; |
---|
1085 | | - *rawp = ltrim(*rawp); |
---|
| 1144 | + *rawp = strim(*rawp); |
---|
1086 | 1145 | |
---|
1087 | 1146 | return 0; |
---|
1088 | 1147 | |
---|
.. | .. |
---|
1091 | 1150 | } |
---|
1092 | 1151 | |
---|
1093 | 1152 | struct annotate_args { |
---|
1094 | | - size_t privsize; |
---|
1095 | | - struct arch *arch; |
---|
1096 | | - struct map_symbol ms; |
---|
1097 | | - struct perf_evsel *evsel; |
---|
| 1153 | + struct arch *arch; |
---|
| 1154 | + struct map_symbol ms; |
---|
| 1155 | + struct evsel *evsel; |
---|
1098 | 1156 | struct annotation_options *options; |
---|
1099 | | - s64 offset; |
---|
1100 | | - char *line; |
---|
1101 | | - int line_nr; |
---|
| 1157 | + s64 offset; |
---|
| 1158 | + char *line; |
---|
| 1159 | + int line_nr; |
---|
1102 | 1160 | }; |
---|
1103 | 1161 | |
---|
1104 | | -static void annotation_line__delete(struct annotation_line *al) |
---|
| 1162 | +static void annotation_line__init(struct annotation_line *al, |
---|
| 1163 | + struct annotate_args *args, |
---|
| 1164 | + int nr) |
---|
1105 | 1165 | { |
---|
1106 | | - void *ptr = (void *) al - al->privsize; |
---|
1107 | | - |
---|
1108 | | - free_srcline(al->path); |
---|
1109 | | - zfree(&al->line); |
---|
1110 | | - free(ptr); |
---|
| 1166 | + al->offset = args->offset; |
---|
| 1167 | + al->line = strdup(args->line); |
---|
| 1168 | + al->line_nr = args->line_nr; |
---|
| 1169 | + al->data_nr = nr; |
---|
1111 | 1170 | } |
---|
1112 | 1171 | |
---|
1113 | | -/* |
---|
1114 | | - * Allocating the annotation line data with following |
---|
1115 | | - * structure: |
---|
1116 | | - * |
---|
1117 | | - * -------------------------------------- |
---|
1118 | | - * private space | struct annotation_line |
---|
1119 | | - * -------------------------------------- |
---|
1120 | | - * |
---|
1121 | | - * Size of the private space is stored in 'struct annotation_line'. |
---|
1122 | | - * |
---|
1123 | | - */ |
---|
1124 | | -static struct annotation_line * |
---|
1125 | | -annotation_line__new(struct annotate_args *args, size_t privsize) |
---|
| 1172 | +static void annotation_line__exit(struct annotation_line *al) |
---|
| 1173 | +{ |
---|
| 1174 | + free_srcline(al->path); |
---|
| 1175 | + zfree(&al->line); |
---|
| 1176 | +} |
---|
| 1177 | + |
---|
| 1178 | +static size_t disasm_line_size(int nr) |
---|
1126 | 1179 | { |
---|
1127 | 1180 | struct annotation_line *al; |
---|
1128 | | - struct perf_evsel *evsel = args->evsel; |
---|
1129 | | - size_t size = privsize + sizeof(*al); |
---|
1130 | | - int nr = 1; |
---|
1131 | 1181 | |
---|
1132 | | - if (perf_evsel__is_group_event(evsel)) |
---|
1133 | | - nr = evsel->nr_members; |
---|
1134 | | - |
---|
1135 | | - size += sizeof(al->data[0]) * nr; |
---|
1136 | | - |
---|
1137 | | - al = zalloc(size); |
---|
1138 | | - if (al) { |
---|
1139 | | - al = (void *) al + privsize; |
---|
1140 | | - al->privsize = privsize; |
---|
1141 | | - al->offset = args->offset; |
---|
1142 | | - al->line = strdup(args->line); |
---|
1143 | | - al->line_nr = args->line_nr; |
---|
1144 | | - al->data_nr = nr; |
---|
1145 | | - } |
---|
1146 | | - |
---|
1147 | | - return al; |
---|
| 1182 | + return (sizeof(struct disasm_line) + (sizeof(al->data[0]) * nr)); |
---|
1148 | 1183 | } |
---|
1149 | 1184 | |
---|
1150 | 1185 | /* |
---|
1151 | 1186 | * Allocating the disasm annotation line data with |
---|
1152 | 1187 | * following structure: |
---|
1153 | 1188 | * |
---|
1154 | | - * ------------------------------------------------------------ |
---|
1155 | | - * privsize space | struct disasm_line | struct annotation_line |
---|
1156 | | - * ------------------------------------------------------------ |
---|
| 1189 | + * ------------------------------------------- |
---|
| 1190 | + * struct disasm_line | struct annotation_line |
---|
| 1191 | + * ------------------------------------------- |
---|
1157 | 1192 | * |
---|
1158 | 1193 | * We have 'struct annotation_line' member as last member |
---|
1159 | 1194 | * of 'struct disasm_line' to have an easy access. |
---|
1160 | | - * |
---|
1161 | 1195 | */ |
---|
1162 | 1196 | static struct disasm_line *disasm_line__new(struct annotate_args *args) |
---|
1163 | 1197 | { |
---|
1164 | 1198 | struct disasm_line *dl = NULL; |
---|
1165 | | - struct annotation_line *al; |
---|
1166 | | - size_t privsize = args->privsize + offsetof(struct disasm_line, al); |
---|
| 1199 | + int nr = 1; |
---|
1167 | 1200 | |
---|
1168 | | - al = annotation_line__new(args, privsize); |
---|
1169 | | - if (al != NULL) { |
---|
1170 | | - dl = disasm_line(al); |
---|
| 1201 | + if (evsel__is_group_event(args->evsel)) |
---|
| 1202 | + nr = args->evsel->core.nr_members; |
---|
1171 | 1203 | |
---|
1172 | | - if (dl->al.line == NULL) |
---|
1173 | | - goto out_delete; |
---|
| 1204 | + dl = zalloc(disasm_line_size(nr)); |
---|
| 1205 | + if (!dl) |
---|
| 1206 | + return NULL; |
---|
1174 | 1207 | |
---|
1175 | | - if (args->offset != -1) { |
---|
1176 | | - if (disasm_line__parse(dl->al.line, &dl->ins.name, &dl->ops.raw) < 0) |
---|
1177 | | - goto out_free_line; |
---|
| 1208 | + annotation_line__init(&dl->al, args, nr); |
---|
| 1209 | + if (dl->al.line == NULL) |
---|
| 1210 | + goto out_delete; |
---|
1178 | 1211 | |
---|
1179 | | - disasm_line__init_ins(dl, args->arch, &args->ms); |
---|
1180 | | - } |
---|
| 1212 | + if (args->offset != -1) { |
---|
| 1213 | + if (disasm_line__parse(dl->al.line, &dl->ins.name, &dl->ops.raw) < 0) |
---|
| 1214 | + goto out_free_line; |
---|
| 1215 | + |
---|
| 1216 | + disasm_line__init_ins(dl, args->arch, &args->ms); |
---|
1181 | 1217 | } |
---|
1182 | 1218 | |
---|
1183 | 1219 | return dl; |
---|
.. | .. |
---|
1195 | 1231 | dl->ins.ops->free(&dl->ops); |
---|
1196 | 1232 | else |
---|
1197 | 1233 | ins__delete(&dl->ops); |
---|
1198 | | - free((void *)dl->ins.name); |
---|
1199 | | - dl->ins.name = NULL; |
---|
1200 | | - annotation_line__delete(&dl->al); |
---|
| 1234 | + zfree(&dl->ins.name); |
---|
| 1235 | + annotation_line__exit(&dl->al); |
---|
| 1236 | + free(dl); |
---|
1201 | 1237 | } |
---|
1202 | 1238 | |
---|
1203 | | -int disasm_line__scnprintf(struct disasm_line *dl, char *bf, size_t size, bool raw) |
---|
| 1239 | +int disasm_line__scnprintf(struct disasm_line *dl, char *bf, size_t size, bool raw, int max_ins_name) |
---|
1204 | 1240 | { |
---|
1205 | 1241 | if (raw || !dl->ins.ops) |
---|
1206 | | - return scnprintf(bf, size, "%-6s %s", dl->ins.name, dl->ops.raw); |
---|
| 1242 | + return scnprintf(bf, size, "%-*s %s", max_ins_name, dl->ins.name, dl->ops.raw); |
---|
1207 | 1243 | |
---|
1208 | | - return ins__scnprintf(&dl->ins, bf, size, &dl->ops); |
---|
| 1244 | + return ins__scnprintf(&dl->ins, bf, size, &dl->ops, max_ins_name); |
---|
1209 | 1245 | } |
---|
1210 | 1246 | |
---|
1211 | 1247 | static void annotation_line__add(struct annotation_line *al, struct list_head *head) |
---|
.. | .. |
---|
1320 | 1356 | |
---|
1321 | 1357 | static int |
---|
1322 | 1358 | annotation_line__print(struct annotation_line *al, struct symbol *sym, u64 start, |
---|
1323 | | - struct perf_evsel *evsel, u64 len, int min_pcnt, int printed, |
---|
| 1359 | + struct evsel *evsel, u64 len, int min_pcnt, int printed, |
---|
1324 | 1360 | int max_lines, struct annotation_line *queue, int addr_fmt_width, |
---|
1325 | 1361 | int percent_type) |
---|
1326 | 1362 | { |
---|
.. | .. |
---|
1408 | 1444 | if (queue) |
---|
1409 | 1445 | return -1; |
---|
1410 | 1446 | |
---|
1411 | | - if (perf_evsel__is_group_event(evsel)) |
---|
1412 | | - width *= evsel->nr_members; |
---|
| 1447 | + if (evsel__is_group_event(evsel)) |
---|
| 1448 | + width *= evsel->core.nr_members; |
---|
1413 | 1449 | |
---|
1414 | 1450 | if (!*al->line) |
---|
1415 | 1451 | printf(" %*s:\n", width, " "); |
---|
.. | .. |
---|
1440 | 1476 | * means that it's not a disassembly line so should be treated differently. |
---|
1441 | 1477 | * The ops.raw part will be parsed further according to type of the instruction. |
---|
1442 | 1478 | */ |
---|
1443 | | -static int symbol__parse_objdump_line(struct symbol *sym, FILE *file, |
---|
| 1479 | +static int symbol__parse_objdump_line(struct symbol *sym, |
---|
1444 | 1480 | struct annotate_args *args, |
---|
1445 | | - int *line_nr) |
---|
| 1481 | + char *parsed_line, int *line_nr) |
---|
1446 | 1482 | { |
---|
1447 | 1483 | struct map *map = args->ms.map; |
---|
1448 | 1484 | struct annotation *notes = symbol__annotation(sym); |
---|
1449 | 1485 | struct disasm_line *dl; |
---|
1450 | | - char *line = NULL, *parsed_line, *tmp, *tmp2; |
---|
1451 | | - size_t line_len; |
---|
| 1486 | + char *tmp; |
---|
1452 | 1487 | s64 line_ip, offset = -1; |
---|
1453 | 1488 | regmatch_t match[2]; |
---|
1454 | | - |
---|
1455 | | - if (getline(&line, &line_len, file) < 0) |
---|
1456 | | - return -1; |
---|
1457 | | - |
---|
1458 | | - if (!line) |
---|
1459 | | - return -1; |
---|
1460 | | - |
---|
1461 | | - line_ip = -1; |
---|
1462 | | - parsed_line = rtrim(line); |
---|
1463 | 1489 | |
---|
1464 | 1490 | /* /filename:linenr ? Save line number and ignore. */ |
---|
1465 | 1491 | if (regexec(&file_lineno, parsed_line, 2, match, 0) == 0) { |
---|
.. | .. |
---|
1467 | 1493 | return 0; |
---|
1468 | 1494 | } |
---|
1469 | 1495 | |
---|
1470 | | - tmp = ltrim(parsed_line); |
---|
1471 | | - if (*tmp) { |
---|
1472 | | - /* |
---|
1473 | | - * Parse hexa addresses followed by ':' |
---|
1474 | | - */ |
---|
1475 | | - line_ip = strtoull(tmp, &tmp2, 16); |
---|
1476 | | - if (*tmp2 != ':' || tmp == tmp2 || tmp2[1] == '\0') |
---|
1477 | | - line_ip = -1; |
---|
1478 | | - } |
---|
1479 | | - |
---|
1480 | | - if (line_ip != -1) { |
---|
| 1496 | + /* Process hex address followed by ':'. */ |
---|
| 1497 | + line_ip = strtoull(parsed_line, &tmp, 16); |
---|
| 1498 | + if (parsed_line != tmp && tmp[0] == ':' && tmp[1] != '\0') { |
---|
1481 | 1499 | u64 start = map__rip_2objdump(map, sym->start), |
---|
1482 | 1500 | end = map__rip_2objdump(map, sym->end); |
---|
1483 | 1501 | |
---|
.. | .. |
---|
1485 | 1503 | if ((u64)line_ip < start || (u64)line_ip >= end) |
---|
1486 | 1504 | offset = -1; |
---|
1487 | 1505 | else |
---|
1488 | | - parsed_line = tmp2 + 1; |
---|
| 1506 | + parsed_line = tmp + 1; |
---|
1489 | 1507 | } |
---|
1490 | 1508 | |
---|
1491 | 1509 | args->offset = offset; |
---|
.. | .. |
---|
1494 | 1512 | args->ms.sym = sym; |
---|
1495 | 1513 | |
---|
1496 | 1514 | dl = disasm_line__new(args); |
---|
1497 | | - free(line); |
---|
1498 | 1515 | (*line_nr)++; |
---|
1499 | 1516 | |
---|
1500 | 1517 | if (dl == NULL) |
---|
.. | .. |
---|
1509 | 1526 | /* kcore has no symbols, so add the call target symbol */ |
---|
1510 | 1527 | if (dl->ins.ops && ins__is_call(&dl->ins) && !dl->ops.target.sym) { |
---|
1511 | 1528 | struct addr_map_symbol target = { |
---|
1512 | | - .map = map, |
---|
1513 | 1529 | .addr = dl->ops.target.addr, |
---|
| 1530 | + .ms = { .map = map, }, |
---|
1514 | 1531 | }; |
---|
1515 | 1532 | |
---|
1516 | | - if (!map_groups__find_ams(&target) && |
---|
1517 | | - target.sym->start == target.al_addr) |
---|
1518 | | - dl->ops.target.sym = target.sym; |
---|
| 1533 | + if (!maps__find_ams(args->ms.maps, &target) && |
---|
| 1534 | + target.ms.sym->start == target.al_addr) |
---|
| 1535 | + dl->ops.target.sym = target.ms.sym; |
---|
1519 | 1536 | } |
---|
1520 | 1537 | |
---|
1521 | 1538 | annotation_line__add(&dl->al, ¬es->src->source); |
---|
.. | .. |
---|
1547 | 1564 | return; |
---|
1548 | 1565 | } |
---|
1549 | 1566 | |
---|
1550 | | - list_del(&dl->al.node); |
---|
| 1567 | + list_del_init(&dl->al.node); |
---|
1551 | 1568 | disasm_line__free(dl); |
---|
1552 | 1569 | } |
---|
1553 | 1570 | } |
---|
1554 | 1571 | |
---|
1555 | | -int symbol__strerror_disassemble(struct symbol *sym __maybe_unused, struct map *map, |
---|
1556 | | - int errnum, char *buf, size_t buflen) |
---|
| 1572 | +int symbol__strerror_disassemble(struct map_symbol *ms, int errnum, char *buf, size_t buflen) |
---|
1557 | 1573 | { |
---|
1558 | | - struct dso *dso = map->dso; |
---|
| 1574 | + struct dso *dso = ms->map->dso; |
---|
1559 | 1575 | |
---|
1560 | 1576 | BUG_ON(buflen == 0); |
---|
1561 | 1577 | |
---|
.. | .. |
---|
1570 | 1586 | char *build_id_msg = NULL; |
---|
1571 | 1587 | |
---|
1572 | 1588 | if (dso->has_build_id) { |
---|
1573 | | - build_id__sprintf(dso->build_id, |
---|
1574 | | - sizeof(dso->build_id), bf + 15); |
---|
| 1589 | + build_id__sprintf(&dso->bid, bf + 15); |
---|
1575 | 1590 | build_id_msg = bf; |
---|
1576 | 1591 | } |
---|
1577 | 1592 | scnprintf(buf, buflen, |
---|
.. | .. |
---|
1582 | 1597 | "or:\n\n" |
---|
1583 | 1598 | " --vmlinux vmlinux\n", build_id_msg ?: ""); |
---|
1584 | 1599 | } |
---|
| 1600 | + break; |
---|
| 1601 | + case SYMBOL_ANNOTATE_ERRNO__NO_LIBOPCODES_FOR_BPF: |
---|
| 1602 | + scnprintf(buf, buflen, "Please link with binutils's libopcode to enable BPF annotation"); |
---|
| 1603 | + break; |
---|
| 1604 | + case SYMBOL_ANNOTATE_ERRNO__ARCH_INIT_REGEXP: |
---|
| 1605 | + scnprintf(buf, buflen, "Problems with arch specific instruction name regular expressions."); |
---|
| 1606 | + break; |
---|
| 1607 | + case SYMBOL_ANNOTATE_ERRNO__ARCH_INIT_CPUID_PARSING: |
---|
| 1608 | + scnprintf(buf, buflen, "Problems while parsing the CPUID in the arch specific initialization."); |
---|
| 1609 | + break; |
---|
| 1610 | + case SYMBOL_ANNOTATE_ERRNO__BPF_INVALID_FILE: |
---|
| 1611 | + scnprintf(buf, buflen, "Invalid BPF file: %s.", dso->long_name); |
---|
| 1612 | + break; |
---|
| 1613 | + case SYMBOL_ANNOTATE_ERRNO__BPF_MISSING_BTF: |
---|
| 1614 | + scnprintf(buf, buflen, "The %s BPF file has no BTF section, compile with -g or use pahole -J.", |
---|
| 1615 | + dso->long_name); |
---|
1585 | 1616 | break; |
---|
1586 | 1617 | default: |
---|
1587 | 1618 | scnprintf(buf, buflen, "Internal error: Invalid %d error code\n", errnum); |
---|
.. | .. |
---|
1597 | 1628 | char *build_id_filename; |
---|
1598 | 1629 | char *build_id_path = NULL; |
---|
1599 | 1630 | char *pos; |
---|
| 1631 | + int len; |
---|
1600 | 1632 | |
---|
1601 | 1633 | if (dso->symtab_type == DSO_BINARY_TYPE__KALLSYMS && |
---|
1602 | 1634 | !dso__is_kcore(dso)) |
---|
.. | .. |
---|
1625 | 1657 | if (pos && strlen(pos) < SBUILD_ID_SIZE - 2) |
---|
1626 | 1658 | dirname(build_id_path); |
---|
1627 | 1659 | |
---|
1628 | | - if (dso__is_kcore(dso) || |
---|
1629 | | - readlink(build_id_path, linkname, sizeof(linkname)) < 0 || |
---|
1630 | | - strstr(linkname, DSO__NAME_KALLSYMS) || |
---|
1631 | | - access(filename, R_OK)) { |
---|
| 1660 | + if (dso__is_kcore(dso)) |
---|
| 1661 | + goto fallback; |
---|
| 1662 | + |
---|
| 1663 | + len = readlink(build_id_path, linkname, sizeof(linkname) - 1); |
---|
| 1664 | + if (len < 0) |
---|
| 1665 | + goto fallback; |
---|
| 1666 | + |
---|
| 1667 | + linkname[len] = '\0'; |
---|
| 1668 | + if (strstr(linkname, DSO__NAME_KALLSYMS) || |
---|
| 1669 | + access(filename, R_OK)) { |
---|
1632 | 1670 | fallback: |
---|
1633 | 1671 | /* |
---|
1634 | 1672 | * If we don't have build-ids or the build-id file isn't in the |
---|
.. | .. |
---|
1642 | 1680 | return 0; |
---|
1643 | 1681 | } |
---|
1644 | 1682 | |
---|
| 1683 | +#if defined(HAVE_LIBBFD_SUPPORT) && defined(HAVE_LIBBPF_SUPPORT) |
---|
| 1684 | +#define PACKAGE "perf" |
---|
| 1685 | +#include <bfd.h> |
---|
| 1686 | +#include <dis-asm.h> |
---|
| 1687 | + |
---|
| 1688 | +static int symbol__disassemble_bpf(struct symbol *sym, |
---|
| 1689 | + struct annotate_args *args) |
---|
| 1690 | +{ |
---|
| 1691 | + struct annotation *notes = symbol__annotation(sym); |
---|
| 1692 | + struct annotation_options *opts = args->options; |
---|
| 1693 | + struct bpf_prog_info_linear *info_linear; |
---|
| 1694 | + struct bpf_prog_linfo *prog_linfo = NULL; |
---|
| 1695 | + struct bpf_prog_info_node *info_node; |
---|
| 1696 | + int len = sym->end - sym->start; |
---|
| 1697 | + disassembler_ftype disassemble; |
---|
| 1698 | + struct map *map = args->ms.map; |
---|
| 1699 | + struct disassemble_info info; |
---|
| 1700 | + struct dso *dso = map->dso; |
---|
| 1701 | + int pc = 0, count, sub_id; |
---|
| 1702 | + struct btf *btf = NULL; |
---|
| 1703 | + char tpath[PATH_MAX]; |
---|
| 1704 | + size_t buf_size; |
---|
| 1705 | + int nr_skip = 0; |
---|
| 1706 | + char *buf; |
---|
| 1707 | + bfd *bfdf; |
---|
| 1708 | + int ret; |
---|
| 1709 | + FILE *s; |
---|
| 1710 | + |
---|
| 1711 | + if (dso->binary_type != DSO_BINARY_TYPE__BPF_PROG_INFO) |
---|
| 1712 | + return SYMBOL_ANNOTATE_ERRNO__BPF_INVALID_FILE; |
---|
| 1713 | + |
---|
| 1714 | + pr_debug("%s: handling sym %s addr %" PRIx64 " len %" PRIx64 "\n", __func__, |
---|
| 1715 | + sym->name, sym->start, sym->end - sym->start); |
---|
| 1716 | + |
---|
| 1717 | + memset(tpath, 0, sizeof(tpath)); |
---|
| 1718 | + perf_exe(tpath, sizeof(tpath)); |
---|
| 1719 | + |
---|
| 1720 | + bfdf = bfd_openr(tpath, NULL); |
---|
| 1721 | + if (bfdf == NULL) |
---|
| 1722 | + abort(); |
---|
| 1723 | + |
---|
| 1724 | + if (!bfd_check_format(bfdf, bfd_object)) |
---|
| 1725 | + abort(); |
---|
| 1726 | + |
---|
| 1727 | + s = open_memstream(&buf, &buf_size); |
---|
| 1728 | + if (!s) { |
---|
| 1729 | + ret = errno; |
---|
| 1730 | + goto out; |
---|
| 1731 | + } |
---|
| 1732 | + init_disassemble_info(&info, s, |
---|
| 1733 | + (fprintf_ftype) fprintf); |
---|
| 1734 | + |
---|
| 1735 | + info.arch = bfd_get_arch(bfdf); |
---|
| 1736 | + info.mach = bfd_get_mach(bfdf); |
---|
| 1737 | + |
---|
| 1738 | + info_node = perf_env__find_bpf_prog_info(dso->bpf_prog.env, |
---|
| 1739 | + dso->bpf_prog.id); |
---|
| 1740 | + if (!info_node) { |
---|
| 1741 | + ret = SYMBOL_ANNOTATE_ERRNO__BPF_MISSING_BTF; |
---|
| 1742 | + goto out; |
---|
| 1743 | + } |
---|
| 1744 | + info_linear = info_node->info_linear; |
---|
| 1745 | + sub_id = dso->bpf_prog.sub_id; |
---|
| 1746 | + |
---|
| 1747 | + info.buffer = (void *)(uintptr_t)(info_linear->info.jited_prog_insns); |
---|
| 1748 | + info.buffer_length = info_linear->info.jited_prog_len; |
---|
| 1749 | + |
---|
| 1750 | + if (info_linear->info.nr_line_info) |
---|
| 1751 | + prog_linfo = bpf_prog_linfo__new(&info_linear->info); |
---|
| 1752 | + |
---|
| 1753 | + if (info_linear->info.btf_id) { |
---|
| 1754 | + struct btf_node *node; |
---|
| 1755 | + |
---|
| 1756 | + node = perf_env__find_btf(dso->bpf_prog.env, |
---|
| 1757 | + info_linear->info.btf_id); |
---|
| 1758 | + if (node) |
---|
| 1759 | + btf = btf__new((__u8 *)(node->data), |
---|
| 1760 | + node->data_size); |
---|
| 1761 | + } |
---|
| 1762 | + |
---|
| 1763 | + disassemble_init_for_target(&info); |
---|
| 1764 | + |
---|
| 1765 | +#ifdef DISASM_FOUR_ARGS_SIGNATURE |
---|
| 1766 | + disassemble = disassembler(info.arch, |
---|
| 1767 | + bfd_big_endian(bfdf), |
---|
| 1768 | + info.mach, |
---|
| 1769 | + bfdf); |
---|
| 1770 | +#else |
---|
| 1771 | + disassemble = disassembler(bfdf); |
---|
| 1772 | +#endif |
---|
| 1773 | + if (disassemble == NULL) |
---|
| 1774 | + abort(); |
---|
| 1775 | + |
---|
| 1776 | + fflush(s); |
---|
| 1777 | + do { |
---|
| 1778 | + const struct bpf_line_info *linfo = NULL; |
---|
| 1779 | + struct disasm_line *dl; |
---|
| 1780 | + size_t prev_buf_size; |
---|
| 1781 | + const char *srcline; |
---|
| 1782 | + u64 addr; |
---|
| 1783 | + |
---|
| 1784 | + addr = pc + ((u64 *)(uintptr_t)(info_linear->info.jited_ksyms))[sub_id]; |
---|
| 1785 | + count = disassemble(pc, &info); |
---|
| 1786 | + |
---|
| 1787 | + if (prog_linfo) |
---|
| 1788 | + linfo = bpf_prog_linfo__lfind_addr_func(prog_linfo, |
---|
| 1789 | + addr, sub_id, |
---|
| 1790 | + nr_skip); |
---|
| 1791 | + |
---|
| 1792 | + if (linfo && btf) { |
---|
| 1793 | + srcline = btf__name_by_offset(btf, linfo->line_off); |
---|
| 1794 | + nr_skip++; |
---|
| 1795 | + } else |
---|
| 1796 | + srcline = NULL; |
---|
| 1797 | + |
---|
| 1798 | + fprintf(s, "\n"); |
---|
| 1799 | + prev_buf_size = buf_size; |
---|
| 1800 | + fflush(s); |
---|
| 1801 | + |
---|
| 1802 | + if (!opts->hide_src_code && srcline) { |
---|
| 1803 | + args->offset = -1; |
---|
| 1804 | + args->line = strdup(srcline); |
---|
| 1805 | + args->line_nr = 0; |
---|
| 1806 | + args->ms.sym = sym; |
---|
| 1807 | + dl = disasm_line__new(args); |
---|
| 1808 | + if (dl) { |
---|
| 1809 | + annotation_line__add(&dl->al, |
---|
| 1810 | + ¬es->src->source); |
---|
| 1811 | + } |
---|
| 1812 | + } |
---|
| 1813 | + |
---|
| 1814 | + args->offset = pc; |
---|
| 1815 | + args->line = buf + prev_buf_size; |
---|
| 1816 | + args->line_nr = 0; |
---|
| 1817 | + args->ms.sym = sym; |
---|
| 1818 | + dl = disasm_line__new(args); |
---|
| 1819 | + if (dl) |
---|
| 1820 | + annotation_line__add(&dl->al, ¬es->src->source); |
---|
| 1821 | + |
---|
| 1822 | + pc += count; |
---|
| 1823 | + } while (count > 0 && pc < len); |
---|
| 1824 | + |
---|
| 1825 | + ret = 0; |
---|
| 1826 | +out: |
---|
| 1827 | + free(prog_linfo); |
---|
| 1828 | + free(btf); |
---|
| 1829 | + fclose(s); |
---|
| 1830 | + bfd_close(bfdf); |
---|
| 1831 | + return ret; |
---|
| 1832 | +} |
---|
| 1833 | +#else // defined(HAVE_LIBBFD_SUPPORT) && defined(HAVE_LIBBPF_SUPPORT) |
---|
| 1834 | +static int symbol__disassemble_bpf(struct symbol *sym __maybe_unused, |
---|
| 1835 | + struct annotate_args *args __maybe_unused) |
---|
| 1836 | +{ |
---|
| 1837 | + return SYMBOL_ANNOTATE_ERRNO__NO_LIBOPCODES_FOR_BPF; |
---|
| 1838 | +} |
---|
| 1839 | +#endif // defined(HAVE_LIBBFD_SUPPORT) && defined(HAVE_LIBBPF_SUPPORT) |
---|
| 1840 | + |
---|
| 1841 | +static int |
---|
| 1842 | +symbol__disassemble_bpf_image(struct symbol *sym, |
---|
| 1843 | + struct annotate_args *args) |
---|
| 1844 | +{ |
---|
| 1845 | + struct annotation *notes = symbol__annotation(sym); |
---|
| 1846 | + struct disasm_line *dl; |
---|
| 1847 | + |
---|
| 1848 | + args->offset = -1; |
---|
| 1849 | + args->line = strdup("to be implemented"); |
---|
| 1850 | + args->line_nr = 0; |
---|
| 1851 | + dl = disasm_line__new(args); |
---|
| 1852 | + if (dl) |
---|
| 1853 | + annotation_line__add(&dl->al, ¬es->src->source); |
---|
| 1854 | + |
---|
| 1855 | + free(args->line); |
---|
| 1856 | + return 0; |
---|
| 1857 | +} |
---|
| 1858 | + |
---|
| 1859 | +/* |
---|
| 1860 | + * Possibly create a new version of line with tabs expanded. Returns the |
---|
| 1861 | + * existing or new line, storage is updated if a new line is allocated. If |
---|
| 1862 | + * allocation fails then NULL is returned. |
---|
| 1863 | + */ |
---|
| 1864 | +static char *expand_tabs(char *line, char **storage, size_t *storage_len) |
---|
| 1865 | +{ |
---|
| 1866 | + size_t i, src, dst, len, new_storage_len, num_tabs; |
---|
| 1867 | + char *new_line; |
---|
| 1868 | + size_t line_len = strlen(line); |
---|
| 1869 | + |
---|
| 1870 | + for (num_tabs = 0, i = 0; i < line_len; i++) |
---|
| 1871 | + if (line[i] == '\t') |
---|
| 1872 | + num_tabs++; |
---|
| 1873 | + |
---|
| 1874 | + if (num_tabs == 0) |
---|
| 1875 | + return line; |
---|
| 1876 | + |
---|
| 1877 | + /* |
---|
| 1878 | + * Space for the line and '\0', less the leading and trailing |
---|
| 1879 | + * spaces. Each tab may introduce 7 additional spaces. |
---|
| 1880 | + */ |
---|
| 1881 | + new_storage_len = line_len + 1 + (num_tabs * 7); |
---|
| 1882 | + |
---|
| 1883 | + new_line = malloc(new_storage_len); |
---|
| 1884 | + if (new_line == NULL) { |
---|
| 1885 | + pr_err("Failure allocating memory for tab expansion\n"); |
---|
| 1886 | + return NULL; |
---|
| 1887 | + } |
---|
| 1888 | + |
---|
| 1889 | + /* |
---|
| 1890 | + * Copy regions starting at src and expand tabs. If there are two |
---|
| 1891 | + * adjacent tabs then 'src == i', the memcpy is of size 0 and the spaces |
---|
| 1892 | + * are inserted. |
---|
| 1893 | + */ |
---|
| 1894 | + for (i = 0, src = 0, dst = 0; i < line_len && num_tabs; i++) { |
---|
| 1895 | + if (line[i] == '\t') { |
---|
| 1896 | + len = i - src; |
---|
| 1897 | + memcpy(&new_line[dst], &line[src], len); |
---|
| 1898 | + dst += len; |
---|
| 1899 | + new_line[dst++] = ' '; |
---|
| 1900 | + while (dst % 8 != 0) |
---|
| 1901 | + new_line[dst++] = ' '; |
---|
| 1902 | + src = i + 1; |
---|
| 1903 | + num_tabs--; |
---|
| 1904 | + } |
---|
| 1905 | + } |
---|
| 1906 | + |
---|
| 1907 | + /* Expand the last region. */ |
---|
| 1908 | + len = line_len - src; |
---|
| 1909 | + memcpy(&new_line[dst], &line[src], len); |
---|
| 1910 | + dst += len; |
---|
| 1911 | + new_line[dst] = '\0'; |
---|
| 1912 | + |
---|
| 1913 | + free(*storage); |
---|
| 1914 | + *storage = new_line; |
---|
| 1915 | + *storage_len = new_storage_len; |
---|
| 1916 | + return new_line; |
---|
| 1917 | + |
---|
| 1918 | +} |
---|
| 1919 | + |
---|
1645 | 1920 | static int symbol__disassemble(struct symbol *sym, struct annotate_args *args) |
---|
1646 | 1921 | { |
---|
1647 | 1922 | struct annotation_options *opts = args->options; |
---|
.. | .. |
---|
1653 | 1928 | struct kcore_extract kce; |
---|
1654 | 1929 | bool delete_extract = false; |
---|
1655 | 1930 | bool decomp = false; |
---|
1656 | | - int stdout_fd[2]; |
---|
1657 | 1931 | int lineno = 0; |
---|
1658 | 1932 | int nline; |
---|
1659 | | - pid_t pid; |
---|
| 1933 | + char *line; |
---|
| 1934 | + size_t line_len; |
---|
| 1935 | + const char *objdump_argv[] = { |
---|
| 1936 | + "/bin/sh", |
---|
| 1937 | + "-c", |
---|
| 1938 | + NULL, /* Will be the objdump command to run. */ |
---|
| 1939 | + "--", |
---|
| 1940 | + NULL, /* Will be the symfs path. */ |
---|
| 1941 | + NULL, |
---|
| 1942 | + }; |
---|
| 1943 | + struct child_process objdump_process; |
---|
1660 | 1944 | int err = dso__disassemble_filename(dso, symfs_filename, sizeof(symfs_filename)); |
---|
1661 | 1945 | |
---|
1662 | 1946 | if (err) |
---|
.. | .. |
---|
1669 | 1953 | pr_debug("annotating [%p] %30s : [%p] %30s\n", |
---|
1670 | 1954 | dso, dso->long_name, sym, sym->name); |
---|
1671 | 1955 | |
---|
1672 | | - if (dso__is_kcore(dso)) { |
---|
| 1956 | + if (dso->binary_type == DSO_BINARY_TYPE__BPF_PROG_INFO) { |
---|
| 1957 | + return symbol__disassemble_bpf(sym, args); |
---|
| 1958 | + } else if (dso->binary_type == DSO_BINARY_TYPE__BPF_IMAGE) { |
---|
| 1959 | + return symbol__disassemble_bpf_image(sym, args); |
---|
| 1960 | + } else if (dso__is_kcore(dso)) { |
---|
1673 | 1961 | kce.kcore_filename = symfs_filename; |
---|
1674 | 1962 | kce.addr = map__rip_2objdump(map, sym->start); |
---|
1675 | 1963 | kce.offs = sym->start; |
---|
.. | .. |
---|
1684 | 1972 | |
---|
1685 | 1973 | if (dso__decompress_kmodule_path(dso, symfs_filename, |
---|
1686 | 1974 | tmp, sizeof(tmp)) < 0) |
---|
1687 | | - goto out; |
---|
| 1975 | + return -1; |
---|
1688 | 1976 | |
---|
1689 | 1977 | decomp = true; |
---|
1690 | 1978 | strcpy(symfs_filename, tmp); |
---|
.. | .. |
---|
1693 | 1981 | err = asprintf(&command, |
---|
1694 | 1982 | "%s %s%s --start-address=0x%016" PRIx64 |
---|
1695 | 1983 | " --stop-address=0x%016" PRIx64 |
---|
1696 | | - " -l -d %s %s -C \"%s\" 2>/dev/null|grep -v \"%s:\"|expand", |
---|
| 1984 | + " -l -d %s %s %s %c%s%c %s%s -C \"$1\"", |
---|
1697 | 1985 | opts->objdump_path ?: "objdump", |
---|
1698 | 1986 | opts->disassembler_style ? "-M " : "", |
---|
1699 | 1987 | opts->disassembler_style ?: "", |
---|
1700 | 1988 | map__rip_2objdump(map, sym->start), |
---|
1701 | 1989 | map__rip_2objdump(map, sym->end), |
---|
1702 | | - opts->show_asm_raw ? "" : "--no-show-raw", |
---|
| 1990 | + opts->show_asm_raw ? "" : "--no-show-raw-insn", |
---|
1703 | 1991 | opts->annotate_src ? "-S" : "", |
---|
1704 | | - symfs_filename, symfs_filename); |
---|
| 1992 | + opts->prefix ? "--prefix " : "", |
---|
| 1993 | + opts->prefix ? '"' : ' ', |
---|
| 1994 | + opts->prefix ?: "", |
---|
| 1995 | + opts->prefix ? '"' : ' ', |
---|
| 1996 | + opts->prefix_strip ? "--prefix-strip=" : "", |
---|
| 1997 | + opts->prefix_strip ?: ""); |
---|
1705 | 1998 | |
---|
1706 | 1999 | if (err < 0) { |
---|
1707 | 2000 | pr_err("Failure allocating memory for the command to run\n"); |
---|
.. | .. |
---|
1710 | 2003 | |
---|
1711 | 2004 | pr_debug("Executing: %s\n", command); |
---|
1712 | 2005 | |
---|
1713 | | - err = -1; |
---|
1714 | | - if (pipe(stdout_fd) < 0) { |
---|
1715 | | - pr_err("Failure creating the pipe to run %s\n", command); |
---|
| 2006 | + objdump_argv[2] = command; |
---|
| 2007 | + objdump_argv[4] = symfs_filename; |
---|
| 2008 | + |
---|
| 2009 | + /* Create a pipe to read from for stdout */ |
---|
| 2010 | + memset(&objdump_process, 0, sizeof(objdump_process)); |
---|
| 2011 | + objdump_process.argv = objdump_argv; |
---|
| 2012 | + objdump_process.out = -1; |
---|
| 2013 | + if (start_command(&objdump_process)) { |
---|
| 2014 | + pr_err("Failure starting to run %s\n", command); |
---|
| 2015 | + err = -1; |
---|
1716 | 2016 | goto out_free_command; |
---|
1717 | 2017 | } |
---|
1718 | 2018 | |
---|
1719 | | - pid = fork(); |
---|
1720 | | - if (pid < 0) { |
---|
1721 | | - pr_err("Failure forking to run %s\n", command); |
---|
1722 | | - goto out_close_stdout; |
---|
1723 | | - } |
---|
1724 | | - |
---|
1725 | | - if (pid == 0) { |
---|
1726 | | - close(stdout_fd[0]); |
---|
1727 | | - dup2(stdout_fd[1], 1); |
---|
1728 | | - close(stdout_fd[1]); |
---|
1729 | | - execl("/bin/sh", "sh", "-c", command, NULL); |
---|
1730 | | - perror(command); |
---|
1731 | | - exit(-1); |
---|
1732 | | - } |
---|
1733 | | - |
---|
1734 | | - close(stdout_fd[1]); |
---|
1735 | | - |
---|
1736 | | - file = fdopen(stdout_fd[0], "r"); |
---|
| 2019 | + file = fdopen(objdump_process.out, "r"); |
---|
1737 | 2020 | if (!file) { |
---|
1738 | 2021 | pr_err("Failure creating FILE stream for %s\n", command); |
---|
1739 | 2022 | /* |
---|
1740 | 2023 | * If we were using debug info should retry with |
---|
1741 | 2024 | * original binary. |
---|
1742 | 2025 | */ |
---|
1743 | | - goto out_free_command; |
---|
| 2026 | + err = -1; |
---|
| 2027 | + goto out_close_stdout; |
---|
1744 | 2028 | } |
---|
| 2029 | + |
---|
| 2030 | + /* Storage for getline. */ |
---|
| 2031 | + line = NULL; |
---|
| 2032 | + line_len = 0; |
---|
1745 | 2033 | |
---|
1746 | 2034 | nline = 0; |
---|
1747 | 2035 | while (!feof(file)) { |
---|
| 2036 | + const char *match; |
---|
| 2037 | + char *expanded_line; |
---|
| 2038 | + |
---|
| 2039 | + if (getline(&line, &line_len, file) < 0 || !line) |
---|
| 2040 | + break; |
---|
| 2041 | + |
---|
| 2042 | + /* Skip lines containing "filename:" */ |
---|
| 2043 | + match = strstr(line, symfs_filename); |
---|
| 2044 | + if (match && match[strlen(symfs_filename)] == ':') |
---|
| 2045 | + continue; |
---|
| 2046 | + |
---|
| 2047 | + expanded_line = strim(line); |
---|
| 2048 | + expanded_line = expand_tabs(expanded_line, &line, &line_len); |
---|
| 2049 | + if (!expanded_line) |
---|
| 2050 | + break; |
---|
| 2051 | + |
---|
1748 | 2052 | /* |
---|
1749 | 2053 | * The source code line number (lineno) needs to be kept in |
---|
1750 | | - * accross calls to symbol__parse_objdump_line(), so that it |
---|
| 2054 | + * across calls to symbol__parse_objdump_line(), so that it |
---|
1751 | 2055 | * can associate it with the instructions till the next one. |
---|
1752 | 2056 | * See disasm_line__new() and struct disasm_line::line_nr. |
---|
1753 | 2057 | */ |
---|
1754 | | - if (symbol__parse_objdump_line(sym, file, args, &lineno) < 0) |
---|
| 2058 | + if (symbol__parse_objdump_line(sym, args, expanded_line, |
---|
| 2059 | + &lineno) < 0) |
---|
1755 | 2060 | break; |
---|
1756 | 2061 | nline++; |
---|
1757 | 2062 | } |
---|
| 2063 | + free(line); |
---|
1758 | 2064 | |
---|
1759 | | - if (nline == 0) |
---|
| 2065 | + err = finish_command(&objdump_process); |
---|
| 2066 | + if (err) |
---|
| 2067 | + pr_err("Error running %s\n", command); |
---|
| 2068 | + |
---|
| 2069 | + if (nline == 0) { |
---|
| 2070 | + err = -1; |
---|
1760 | 2071 | pr_err("No output from %s\n", command); |
---|
| 2072 | + } |
---|
1761 | 2073 | |
---|
1762 | 2074 | /* |
---|
1763 | 2075 | * kallsyms does not have symbol sizes so there may a nop at the end. |
---|
.. | .. |
---|
1767 | 2079 | delete_last_nop(sym); |
---|
1768 | 2080 | |
---|
1769 | 2081 | fclose(file); |
---|
1770 | | - err = 0; |
---|
| 2082 | + |
---|
| 2083 | +out_close_stdout: |
---|
| 2084 | + close(objdump_process.out); |
---|
| 2085 | + |
---|
1771 | 2086 | out_free_command: |
---|
1772 | 2087 | free(command); |
---|
1773 | | -out_remove_tmp: |
---|
1774 | | - close(stdout_fd[0]); |
---|
1775 | 2088 | |
---|
| 2089 | +out_remove_tmp: |
---|
1776 | 2090 | if (decomp) |
---|
1777 | 2091 | unlink(symfs_filename); |
---|
1778 | 2092 | |
---|
1779 | 2093 | if (delete_extract) |
---|
1780 | 2094 | kcore_extract__delete(&kce); |
---|
1781 | | -out: |
---|
1782 | | - return err; |
---|
1783 | 2095 | |
---|
1784 | | -out_close_stdout: |
---|
1785 | | - close(stdout_fd[1]); |
---|
1786 | | - goto out_free_command; |
---|
| 2096 | + return err; |
---|
1787 | 2097 | } |
---|
1788 | 2098 | |
---|
1789 | 2099 | static void calc_percent(struct sym_hist *sym_hist, |
---|
.. | .. |
---|
1817 | 2127 | } |
---|
1818 | 2128 | |
---|
1819 | 2129 | static void annotation__calc_percent(struct annotation *notes, |
---|
1820 | | - struct perf_evsel *leader, s64 len) |
---|
| 2130 | + struct evsel *leader, s64 len) |
---|
1821 | 2131 | { |
---|
1822 | 2132 | struct annotation_line *al, *next; |
---|
1823 | | - struct perf_evsel *evsel; |
---|
| 2133 | + struct evsel *evsel; |
---|
1824 | 2134 | |
---|
1825 | 2135 | list_for_each_entry(al, ¬es->src->source, node) { |
---|
1826 | 2136 | s64 end; |
---|
.. | .. |
---|
1847 | 2157 | } |
---|
1848 | 2158 | } |
---|
1849 | 2159 | |
---|
1850 | | -void symbol__calc_percent(struct symbol *sym, struct perf_evsel *evsel) |
---|
| 2160 | +void symbol__calc_percent(struct symbol *sym, struct evsel *evsel) |
---|
1851 | 2161 | { |
---|
1852 | 2162 | struct annotation *notes = symbol__annotation(sym); |
---|
1853 | 2163 | |
---|
1854 | 2164 | annotation__calc_percent(notes, evsel, symbol__size(sym)); |
---|
1855 | 2165 | } |
---|
1856 | 2166 | |
---|
1857 | | -int symbol__annotate(struct symbol *sym, struct map *map, |
---|
1858 | | - struct perf_evsel *evsel, size_t privsize, |
---|
1859 | | - struct annotation_options *options, |
---|
1860 | | - struct arch **parch) |
---|
| 2167 | +int symbol__annotate(struct map_symbol *ms, struct evsel *evsel, |
---|
| 2168 | + struct annotation_options *options, struct arch **parch) |
---|
1861 | 2169 | { |
---|
| 2170 | + struct symbol *sym = ms->sym; |
---|
1862 | 2171 | struct annotation *notes = symbol__annotation(sym); |
---|
1863 | 2172 | struct annotate_args args = { |
---|
1864 | | - .privsize = privsize, |
---|
1865 | 2173 | .evsel = evsel, |
---|
1866 | 2174 | .options = options, |
---|
1867 | 2175 | }; |
---|
1868 | | - struct perf_env *env = perf_evsel__env(evsel); |
---|
| 2176 | + struct perf_env *env = evsel__env(evsel); |
---|
1869 | 2177 | const char *arch_name = perf_env__arch(env); |
---|
1870 | 2178 | struct arch *arch; |
---|
1871 | 2179 | int err; |
---|
.. | .. |
---|
1888 | 2196 | } |
---|
1889 | 2197 | } |
---|
1890 | 2198 | |
---|
1891 | | - args.ms.map = map; |
---|
1892 | | - args.ms.sym = sym; |
---|
1893 | | - notes->start = map__rip_2objdump(map, sym->start); |
---|
| 2199 | + args.ms = *ms; |
---|
| 2200 | + notes->start = map__rip_2objdump(ms->map, sym->start); |
---|
1894 | 2201 | |
---|
1895 | 2202 | return symbol__disassemble(sym, &args); |
---|
1896 | 2203 | } |
---|
.. | .. |
---|
2020 | 2327 | } |
---|
2021 | 2328 | } |
---|
2022 | 2329 | |
---|
2023 | | -static void symbol__annotate_hits(struct symbol *sym, struct perf_evsel *evsel) |
---|
| 2330 | +static void symbol__annotate_hits(struct symbol *sym, struct evsel *evsel) |
---|
2024 | 2331 | { |
---|
2025 | 2332 | struct annotation *notes = symbol__annotation(sym); |
---|
2026 | 2333 | struct sym_hist *h = annotation__histogram(notes, evsel->idx); |
---|
.. | .. |
---|
2046 | 2353 | return 0; |
---|
2047 | 2354 | } |
---|
2048 | 2355 | |
---|
2049 | | -int symbol__annotate_printf(struct symbol *sym, struct map *map, |
---|
2050 | | - struct perf_evsel *evsel, |
---|
| 2356 | +int symbol__annotate_printf(struct map_symbol *ms, struct evsel *evsel, |
---|
2051 | 2357 | struct annotation_options *opts) |
---|
2052 | 2358 | { |
---|
| 2359 | + struct map *map = ms->map; |
---|
| 2360 | + struct symbol *sym = ms->sym; |
---|
2053 | 2361 | struct dso *dso = map->dso; |
---|
2054 | 2362 | char *filename; |
---|
2055 | 2363 | const char *d_filename; |
---|
2056 | | - const char *evsel_name = perf_evsel__name(evsel); |
---|
| 2364 | + const char *evsel_name = evsel__name(evsel); |
---|
2057 | 2365 | struct annotation *notes = symbol__annotation(sym); |
---|
2058 | 2366 | struct sym_hist *h = annotation__histogram(notes, evsel->idx); |
---|
2059 | 2367 | struct annotation_line *pos, *queue = NULL; |
---|
.. | .. |
---|
2077 | 2385 | |
---|
2078 | 2386 | len = symbol__size(sym); |
---|
2079 | 2387 | |
---|
2080 | | - if (perf_evsel__is_group_event(evsel)) { |
---|
2081 | | - width *= evsel->nr_members; |
---|
2082 | | - perf_evsel__group_desc(evsel, buf, sizeof(buf)); |
---|
| 2388 | + if (evsel__is_group_event(evsel)) { |
---|
| 2389 | + width *= evsel->core.nr_members; |
---|
| 2390 | + evsel__group_desc(evsel, buf, sizeof(buf)); |
---|
2083 | 2391 | evsel_name = buf; |
---|
2084 | 2392 | } |
---|
2085 | 2393 | |
---|
.. | .. |
---|
2211 | 2519 | return 0; |
---|
2212 | 2520 | } |
---|
2213 | 2521 | |
---|
2214 | | -int map_symbol__annotation_dump(struct map_symbol *ms, struct perf_evsel *evsel, |
---|
| 2522 | +int map_symbol__annotation_dump(struct map_symbol *ms, struct evsel *evsel, |
---|
2215 | 2523 | struct annotation_options *opts) |
---|
2216 | 2524 | { |
---|
2217 | | - const char *ev_name = perf_evsel__name(evsel); |
---|
| 2525 | + const char *ev_name = evsel__name(evsel); |
---|
2218 | 2526 | char buf[1024]; |
---|
2219 | 2527 | char *filename; |
---|
2220 | 2528 | int err = -1; |
---|
.. | .. |
---|
2227 | 2535 | if (fp == NULL) |
---|
2228 | 2536 | goto out_free_filename; |
---|
2229 | 2537 | |
---|
2230 | | - if (perf_evsel__is_group_event(evsel)) { |
---|
2231 | | - perf_evsel__group_desc(evsel, buf, sizeof(buf)); |
---|
| 2538 | + if (evsel__is_group_event(evsel)) { |
---|
| 2539 | + evsel__group_desc(evsel, buf, sizeof(buf)); |
---|
2232 | 2540 | ev_name = buf; |
---|
2233 | 2541 | } |
---|
2234 | 2542 | |
---|
.. | .. |
---|
2269 | 2577 | struct annotation_line *al, *n; |
---|
2270 | 2578 | |
---|
2271 | 2579 | list_for_each_entry_safe(al, n, &as->source, node) { |
---|
2272 | | - list_del(&al->node); |
---|
| 2580 | + list_del_init(&al->node); |
---|
2273 | 2581 | disasm_line__free(disasm_line(al)); |
---|
2274 | 2582 | } |
---|
2275 | 2583 | } |
---|
.. | .. |
---|
2340 | 2648 | |
---|
2341 | 2649 | if (++al->jump_sources > notes->max_jump_sources) |
---|
2342 | 2650 | notes->max_jump_sources = al->jump_sources; |
---|
2343 | | - |
---|
2344 | | - ++notes->nr_jumps; |
---|
2345 | 2651 | } |
---|
2346 | 2652 | } |
---|
2347 | 2653 | |
---|
.. | .. |
---|
2350 | 2656 | struct annotation_line *al; |
---|
2351 | 2657 | |
---|
2352 | 2658 | notes->max_line_len = 0; |
---|
| 2659 | + notes->nr_entries = 0; |
---|
| 2660 | + notes->nr_asm_entries = 0; |
---|
2353 | 2661 | |
---|
2354 | 2662 | list_for_each_entry(al, ¬es->src->source, node) { |
---|
2355 | 2663 | size_t line_len = strlen(al->line); |
---|
.. | .. |
---|
2382 | 2690 | return 1; |
---|
2383 | 2691 | } |
---|
2384 | 2692 | |
---|
| 2693 | +static int annotation__max_ins_name(struct annotation *notes) |
---|
| 2694 | +{ |
---|
| 2695 | + int max_name = 0, len; |
---|
| 2696 | + struct annotation_line *al; |
---|
| 2697 | + |
---|
| 2698 | + list_for_each_entry(al, ¬es->src->source, node) { |
---|
| 2699 | + if (al->offset == -1) |
---|
| 2700 | + continue; |
---|
| 2701 | + |
---|
| 2702 | + len = strlen(disasm_line(al)->ins.name); |
---|
| 2703 | + if (max_name < len) |
---|
| 2704 | + max_name = len; |
---|
| 2705 | + } |
---|
| 2706 | + |
---|
| 2707 | + return max_name; |
---|
| 2708 | +} |
---|
| 2709 | + |
---|
2385 | 2710 | void annotation__init_column_widths(struct annotation *notes, struct symbol *sym) |
---|
2386 | 2711 | { |
---|
2387 | 2712 | notes->widths.addr = notes->widths.target = |
---|
2388 | 2713 | notes->widths.min_addr = hex_width(symbol__size(sym)); |
---|
2389 | 2714 | notes->widths.max_addr = hex_width(sym->end); |
---|
2390 | 2715 | notes->widths.jumps = width_jumps(notes->max_jump_sources); |
---|
| 2716 | + notes->widths.max_ins_name = annotation__max_ins_name(notes); |
---|
2391 | 2717 | } |
---|
2392 | 2718 | |
---|
2393 | 2719 | void annotation__update_column_widths(struct annotation *notes) |
---|
.. | .. |
---|
2435 | 2761 | resort_source_line(root, &tmp_root); |
---|
2436 | 2762 | } |
---|
2437 | 2763 | |
---|
2438 | | -static void symbol__calc_lines(struct symbol *sym, struct map *map, |
---|
2439 | | - struct rb_root *root, |
---|
| 2764 | +static void symbol__calc_lines(struct map_symbol *ms, struct rb_root *root, |
---|
2440 | 2765 | struct annotation_options *opts) |
---|
2441 | 2766 | { |
---|
2442 | | - struct annotation *notes = symbol__annotation(sym); |
---|
| 2767 | + struct annotation *notes = symbol__annotation(ms->sym); |
---|
2443 | 2768 | |
---|
2444 | | - annotation__calc_lines(notes, map, root, opts); |
---|
| 2769 | + annotation__calc_lines(notes, ms->map, root, opts); |
---|
2445 | 2770 | } |
---|
2446 | 2771 | |
---|
2447 | | -int symbol__tty_annotate2(struct symbol *sym, struct map *map, |
---|
2448 | | - struct perf_evsel *evsel, |
---|
| 2772 | +int symbol__tty_annotate2(struct map_symbol *ms, struct evsel *evsel, |
---|
2449 | 2773 | struct annotation_options *opts) |
---|
2450 | 2774 | { |
---|
2451 | | - struct dso *dso = map->dso; |
---|
| 2775 | + struct dso *dso = ms->map->dso; |
---|
| 2776 | + struct symbol *sym = ms->sym; |
---|
2452 | 2777 | struct rb_root source_line = RB_ROOT; |
---|
2453 | 2778 | struct hists *hists = evsel__hists(evsel); |
---|
2454 | 2779 | char buf[1024]; |
---|
2455 | 2780 | |
---|
2456 | | - if (symbol__annotate2(sym, map, evsel, opts, NULL) < 0) |
---|
| 2781 | + if (symbol__annotate2(ms, evsel, opts, NULL) < 0) |
---|
2457 | 2782 | return -1; |
---|
2458 | 2783 | |
---|
2459 | 2784 | if (opts->print_lines) { |
---|
2460 | 2785 | srcline_full_filename = opts->full_path; |
---|
2461 | | - symbol__calc_lines(sym, map, &source_line, opts); |
---|
| 2786 | + symbol__calc_lines(ms, &source_line, opts); |
---|
2462 | 2787 | print_summary(&source_line, dso->long_name); |
---|
2463 | 2788 | } |
---|
2464 | 2789 | |
---|
.. | .. |
---|
2472 | 2797 | return 0; |
---|
2473 | 2798 | } |
---|
2474 | 2799 | |
---|
2475 | | -int symbol__tty_annotate(struct symbol *sym, struct map *map, |
---|
2476 | | - struct perf_evsel *evsel, |
---|
| 2800 | +int symbol__tty_annotate(struct map_symbol *ms, struct evsel *evsel, |
---|
2477 | 2801 | struct annotation_options *opts) |
---|
2478 | 2802 | { |
---|
2479 | | - struct dso *dso = map->dso; |
---|
| 2803 | + struct dso *dso = ms->map->dso; |
---|
| 2804 | + struct symbol *sym = ms->sym; |
---|
2480 | 2805 | struct rb_root source_line = RB_ROOT; |
---|
2481 | 2806 | |
---|
2482 | | - if (symbol__annotate(sym, map, evsel, 0, opts, NULL) < 0) |
---|
| 2807 | + if (symbol__annotate(ms, evsel, opts, NULL) < 0) |
---|
2483 | 2808 | return -1; |
---|
2484 | 2809 | |
---|
2485 | 2810 | symbol__calc_percent(sym, evsel); |
---|
2486 | 2811 | |
---|
2487 | 2812 | if (opts->print_lines) { |
---|
2488 | 2813 | srcline_full_filename = opts->full_path; |
---|
2489 | | - symbol__calc_lines(sym, map, &source_line, opts); |
---|
| 2814 | + symbol__calc_lines(ms, &source_line, opts); |
---|
2490 | 2815 | print_summary(&source_line, dso->long_name); |
---|
2491 | 2816 | } |
---|
2492 | 2817 | |
---|
2493 | | - symbol__annotate_printf(sym, map, evsel, opts); |
---|
| 2818 | + symbol__annotate_printf(ms, evsel, opts); |
---|
2494 | 2819 | |
---|
2495 | 2820 | annotated_source__purge(symbol__annotation(sym)->src); |
---|
2496 | 2821 | |
---|
.. | .. |
---|
2551 | 2876 | obj__printf(obj, " "); |
---|
2552 | 2877 | } |
---|
2553 | 2878 | |
---|
2554 | | - disasm_line__scnprintf(dl, bf, size, !notes->options->use_offset); |
---|
| 2879 | + disasm_line__scnprintf(dl, bf, size, !notes->options->use_offset, notes->widths.max_ins_name); |
---|
| 2880 | +} |
---|
| 2881 | + |
---|
| 2882 | +static void ipc_coverage_string(char *bf, int size, struct annotation *notes) |
---|
| 2883 | +{ |
---|
| 2884 | + double ipc = 0.0, coverage = 0.0; |
---|
| 2885 | + |
---|
| 2886 | + if (notes->hit_cycles) |
---|
| 2887 | + ipc = notes->hit_insn / ((double)notes->hit_cycles); |
---|
| 2888 | + |
---|
| 2889 | + if (notes->total_insn) { |
---|
| 2890 | + coverage = notes->cover_insn * 100.0 / |
---|
| 2891 | + ((double)notes->total_insn); |
---|
| 2892 | + } |
---|
| 2893 | + |
---|
| 2894 | + scnprintf(bf, size, "(Average IPC: %.2f, IPC Coverage: %.1f%%)", |
---|
| 2895 | + ipc, coverage); |
---|
2555 | 2896 | } |
---|
2556 | 2897 | |
---|
2557 | 2898 | static void __annotation_line__write(struct annotation_line *al, struct annotation *notes, |
---|
.. | .. |
---|
2588 | 2929 | percent = annotation_data__percent(&al->data[i], percent_type); |
---|
2589 | 2930 | |
---|
2590 | 2931 | obj__set_percent_color(obj, percent, current_entry); |
---|
2591 | | - if (notes->options->show_total_period) { |
---|
| 2932 | + if (symbol_conf.show_total_period) { |
---|
2592 | 2933 | obj__printf(obj, "%11" PRIu64 " ", al->data[i].he.period); |
---|
2593 | | - } else if (notes->options->show_nr_samples) { |
---|
| 2934 | + } else if (symbol_conf.show_nr_samples) { |
---|
2594 | 2935 | obj__printf(obj, "%6" PRIu64 " ", |
---|
2595 | 2936 | al->data[i].he.nr_samples); |
---|
2596 | 2937 | } else { |
---|
.. | .. |
---|
2604 | 2945 | obj__printf(obj, "%-*s", pcnt_width, " "); |
---|
2605 | 2946 | else { |
---|
2606 | 2947 | obj__printf(obj, "%-*s", pcnt_width, |
---|
2607 | | - notes->options->show_total_period ? "Period" : |
---|
2608 | | - notes->options->show_nr_samples ? "Samples" : "Percent"); |
---|
| 2948 | + symbol_conf.show_total_period ? "Period" : |
---|
| 2949 | + symbol_conf.show_nr_samples ? "Samples" : "Percent"); |
---|
2609 | 2950 | } |
---|
2610 | 2951 | } |
---|
2611 | 2952 | |
---|
.. | .. |
---|
2648 | 2989 | obj__printf(obj, "%*s ", |
---|
2649 | 2990 | ANNOTATION__MINMAX_CYCLES_WIDTH - 1, |
---|
2650 | 2991 | "Cycle(min/max)"); |
---|
| 2992 | + } |
---|
| 2993 | + |
---|
| 2994 | + if (show_title && !*al->line) { |
---|
| 2995 | + ipc_coverage_string(bf, sizeof(bf), notes); |
---|
| 2996 | + obj__printf(obj, "%*s", ANNOTATION__AVG_IPC_WIDTH, bf); |
---|
2651 | 2997 | } |
---|
2652 | 2998 | } |
---|
2653 | 2999 | |
---|
.. | .. |
---|
2723 | 3069 | wops->write_graph); |
---|
2724 | 3070 | } |
---|
2725 | 3071 | |
---|
2726 | | -int symbol__annotate2(struct symbol *sym, struct map *map, struct perf_evsel *evsel, |
---|
| 3072 | +int symbol__annotate2(struct map_symbol *ms, struct evsel *evsel, |
---|
2727 | 3073 | struct annotation_options *options, struct arch **parch) |
---|
2728 | 3074 | { |
---|
| 3075 | + struct symbol *sym = ms->sym; |
---|
2729 | 3076 | struct annotation *notes = symbol__annotation(sym); |
---|
2730 | 3077 | size_t size = symbol__size(sym); |
---|
2731 | 3078 | int nr_pcnt = 1, err; |
---|
.. | .. |
---|
2734 | 3081 | if (notes->offsets == NULL) |
---|
2735 | 3082 | return ENOMEM; |
---|
2736 | 3083 | |
---|
2737 | | - if (perf_evsel__is_group_event(evsel)) |
---|
2738 | | - nr_pcnt = evsel->nr_members; |
---|
| 3084 | + if (evsel__is_group_event(evsel)) |
---|
| 3085 | + nr_pcnt = evsel->core.nr_members; |
---|
2739 | 3086 | |
---|
2740 | | - err = symbol__annotate(sym, map, evsel, 0, options, parch); |
---|
| 3087 | + err = symbol__annotate(ms, evsel, options, parch); |
---|
2741 | 3088 | if (err) |
---|
2742 | 3089 | goto out_free_offsets; |
---|
2743 | 3090 | |
---|
.. | .. |
---|
2752 | 3099 | notes->nr_events = nr_pcnt; |
---|
2753 | 3100 | |
---|
2754 | 3101 | annotation__update_column_widths(notes); |
---|
| 3102 | + sym->annotate2 = true; |
---|
2755 | 3103 | |
---|
2756 | 3104 | return 0; |
---|
2757 | 3105 | |
---|
.. | .. |
---|
2760 | 3108 | return err; |
---|
2761 | 3109 | } |
---|
2762 | 3110 | |
---|
2763 | | -#define ANNOTATION__CFG(n) \ |
---|
2764 | | - { .name = #n, .value = &annotation__default_options.n, } |
---|
2765 | | - |
---|
2766 | | -/* |
---|
2767 | | - * Keep the entries sorted, they are bsearch'ed |
---|
2768 | | - */ |
---|
2769 | | -static struct annotation_config { |
---|
2770 | | - const char *name; |
---|
2771 | | - void *value; |
---|
2772 | | -} annotation__configs[] = { |
---|
2773 | | - ANNOTATION__CFG(hide_src_code), |
---|
2774 | | - ANNOTATION__CFG(jump_arrows), |
---|
2775 | | - ANNOTATION__CFG(offset_level), |
---|
2776 | | - ANNOTATION__CFG(show_linenr), |
---|
2777 | | - ANNOTATION__CFG(show_nr_jumps), |
---|
2778 | | - ANNOTATION__CFG(show_nr_samples), |
---|
2779 | | - ANNOTATION__CFG(show_total_period), |
---|
2780 | | - ANNOTATION__CFG(use_offset), |
---|
2781 | | -}; |
---|
2782 | | - |
---|
2783 | | -#undef ANNOTATION__CFG |
---|
2784 | | - |
---|
2785 | | -static int annotation_config__cmp(const void *name, const void *cfgp) |
---|
| 3111 | +static int annotation__config(const char *var, const char *value, void *data) |
---|
2786 | 3112 | { |
---|
2787 | | - const struct annotation_config *cfg = cfgp; |
---|
2788 | | - |
---|
2789 | | - return strcmp(name, cfg->name); |
---|
2790 | | -} |
---|
2791 | | - |
---|
2792 | | -static int annotation__config(const char *var, const char *value, |
---|
2793 | | - void *data __maybe_unused) |
---|
2794 | | -{ |
---|
2795 | | - struct annotation_config *cfg; |
---|
2796 | | - const char *name; |
---|
| 3113 | + struct annotation_options *opt = data; |
---|
2797 | 3114 | |
---|
2798 | 3115 | if (!strstarts(var, "annotate.")) |
---|
2799 | 3116 | return 0; |
---|
2800 | 3117 | |
---|
2801 | | - name = var + 9; |
---|
2802 | | - cfg = bsearch(name, annotation__configs, ARRAY_SIZE(annotation__configs), |
---|
2803 | | - sizeof(struct annotation_config), annotation_config__cmp); |
---|
| 3118 | + if (!strcmp(var, "annotate.offset_level")) { |
---|
| 3119 | + perf_config_u8(&opt->offset_level, "offset_level", value); |
---|
2804 | 3120 | |
---|
2805 | | - if (cfg == NULL) |
---|
2806 | | - pr_debug("%s variable unknown, ignoring...", var); |
---|
2807 | | - else if (strcmp(var, "annotate.offset_level") == 0) { |
---|
2808 | | - perf_config_int(cfg->value, name, value); |
---|
2809 | | - |
---|
2810 | | - if (*(int *)cfg->value > ANNOTATION__MAX_OFFSET_LEVEL) |
---|
2811 | | - *(int *)cfg->value = ANNOTATION__MAX_OFFSET_LEVEL; |
---|
2812 | | - else if (*(int *)cfg->value < ANNOTATION__MIN_OFFSET_LEVEL) |
---|
2813 | | - *(int *)cfg->value = ANNOTATION__MIN_OFFSET_LEVEL; |
---|
| 3121 | + if (opt->offset_level > ANNOTATION__MAX_OFFSET_LEVEL) |
---|
| 3122 | + opt->offset_level = ANNOTATION__MAX_OFFSET_LEVEL; |
---|
| 3123 | + else if (opt->offset_level < ANNOTATION__MIN_OFFSET_LEVEL) |
---|
| 3124 | + opt->offset_level = ANNOTATION__MIN_OFFSET_LEVEL; |
---|
| 3125 | + } else if (!strcmp(var, "annotate.hide_src_code")) { |
---|
| 3126 | + opt->hide_src_code = perf_config_bool("hide_src_code", value); |
---|
| 3127 | + } else if (!strcmp(var, "annotate.jump_arrows")) { |
---|
| 3128 | + opt->jump_arrows = perf_config_bool("jump_arrows", value); |
---|
| 3129 | + } else if (!strcmp(var, "annotate.show_linenr")) { |
---|
| 3130 | + opt->show_linenr = perf_config_bool("show_linenr", value); |
---|
| 3131 | + } else if (!strcmp(var, "annotate.show_nr_jumps")) { |
---|
| 3132 | + opt->show_nr_jumps = perf_config_bool("show_nr_jumps", value); |
---|
| 3133 | + } else if (!strcmp(var, "annotate.show_nr_samples")) { |
---|
| 3134 | + symbol_conf.show_nr_samples = perf_config_bool("show_nr_samples", |
---|
| 3135 | + value); |
---|
| 3136 | + } else if (!strcmp(var, "annotate.show_total_period")) { |
---|
| 3137 | + symbol_conf.show_total_period = perf_config_bool("show_total_period", |
---|
| 3138 | + value); |
---|
| 3139 | + } else if (!strcmp(var, "annotate.use_offset")) { |
---|
| 3140 | + opt->use_offset = perf_config_bool("use_offset", value); |
---|
| 3141 | + } else if (!strcmp(var, "annotate.disassembler_style")) { |
---|
| 3142 | + opt->disassembler_style = value; |
---|
2814 | 3143 | } else { |
---|
2815 | | - *(bool *)cfg->value = perf_config_bool(name, value); |
---|
| 3144 | + pr_debug("%s variable unknown, ignoring...", var); |
---|
2816 | 3145 | } |
---|
| 3146 | + |
---|
2817 | 3147 | return 0; |
---|
2818 | 3148 | } |
---|
2819 | 3149 | |
---|
2820 | | -void annotation_config__init(void) |
---|
| 3150 | +void annotation_config__init(struct annotation_options *opt) |
---|
2821 | 3151 | { |
---|
2822 | | - perf_config(annotation__config, NULL); |
---|
2823 | | - |
---|
2824 | | - annotation__default_options.show_total_period = symbol_conf.show_total_period; |
---|
2825 | | - annotation__default_options.show_nr_samples = symbol_conf.show_nr_samples; |
---|
| 3152 | + perf_config(annotation__config, opt); |
---|
2826 | 3153 | } |
---|
2827 | 3154 | |
---|
2828 | 3155 | static unsigned int parse_percent_type(char *str1, char *str2) |
---|
.. | .. |
---|
2876 | 3203 | free(str1); |
---|
2877 | 3204 | return err; |
---|
2878 | 3205 | } |
---|
| 3206 | + |
---|
| 3207 | +int annotate_check_args(struct annotation_options *args) |
---|
| 3208 | +{ |
---|
| 3209 | + if (args->prefix_strip && !args->prefix) { |
---|
| 3210 | + pr_err("--prefix-strip requires --prefix\n"); |
---|
| 3211 | + return -1; |
---|
| 3212 | + } |
---|
| 3213 | + return 0; |
---|
| 3214 | +} |
---|