diff options
| -rw-r--r-- | tools/perf/util/annotate-arch/Build | 1 | ||||
| -rw-r--r-- | tools/perf/util/annotate-arch/annotate-alpha.c | 185 | ||||
| -rw-r--r-- | tools/perf/util/disasm.c | 15 | ||||
| -rw-r--r-- | tools/perf/util/disasm.h | 1 |
4 files changed, 200 insertions, 2 deletions
diff --git a/tools/perf/util/annotate-arch/Build b/tools/perf/util/annotate-arch/Build index 23316743fdc5..0a74e1a9f17b 100644 --- a/tools/perf/util/annotate-arch/Build +++ b/tools/perf/util/annotate-arch/Build @@ -1,3 +1,4 @@ +perf-util-y += annotate-alpha.o perf-util-y += annotate-arc.o perf-util-y += annotate-arm.o perf-util-y += annotate-arm64.o diff --git a/tools/perf/util/annotate-arch/annotate-alpha.c b/tools/perf/util/annotate-arch/annotate-alpha.c new file mode 100644 index 000000000000..ccb13e2338a2 --- /dev/null +++ b/tools/perf/util/annotate-arch/annotate-alpha.c @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <stdlib.h> +#include <string.h> +#include <linux/compiler.h> +#include <linux/kernel.h> +#include <linux/zalloc.h> +#include "../../util/disasm.h" +#include "../../util/map.h" +#include "../../util/maps.h" +#include "../../util/symbol.h" +#include "../../util/thread.h" + +/* + * Alpha control-transfer instructions, as printed by objdump: + * + * PC-relative (opcode group 0x30-0x3f), 21-bit displacement: + * br, bsr unconditional / to-subroutine + * beq bne blt ble bgt bge blbc blbs integer conditional + * fbeq fbne fblt fble fbgt fbge floating conditional + * + * Register-indirect (JSR group, opcode 0x1a): + * jmp, jsr, ret, jcr + * + * bsr/jsr (and jcr, the coroutine form, which binutils prints in preference to + * the jsr_coroutine spelling) save a return address, so they are calls; ret + * returns; everything else that transfers control is a jump. + * + * Alpha has no machine "mov"; objdump prints "mov"/"fmov" as pseudos for + * bis/cpys, so map them to mov_ops when present. The no-ops are deliberately + * left alone: nop_ops would let delete_last_nop() trim the padding gcc leaves + * at the end of a function, but its scnprintf() prints the literal "nop", and + * Alpha pads with unop (ldq_u $31) rather than nop. + */ + +/* + * The generic call__parse() expects the target address to be the first thing + * in the operand string, but a bsr prints its return-address register first: + * + * bsr t0,fffffc0001031dc0 <cserve_ena> + * + * so take the address from after the comma. Without this the address comes + * out as 0, and neither the callee symbol nor the annotation browser's + * "go to target" work. + */ +static int alpha_call__parse(const struct arch *arch, struct ins_operands *ops, + struct map_symbol *ms, + struct disasm_line *dl __maybe_unused) +{ + char *endptr, *tok, *name; + struct map *map = ms->map; + struct addr_map_symbol target; + + tok = strchr(ops->raw, ','); + if (tok == NULL) + return -1; + + ops->target.addr = strtoull(tok + 1, &endptr, 16); + if (endptr == tok + 1) + return -1; + + /* A stripped object has no "<symbol>" to name the target with. */ + name = strchr(endptr, '<'); + if (name == NULL) + goto find_target; + + name++; + + if (arch->objdump.skip_functions_char && + strchr(name, arch->objdump.skip_functions_char)) + return -1; + + tok = strchr(name, '>'); + if (tok == NULL) + return -1; + + *tok = '\0'; + ops->target.name = strdup(name); + *tok = '>'; + + if (ops->target.name == NULL) + return -1; + +find_target: + target = (struct addr_map_symbol) { + .ms = { .map = map__get(map), }, + .addr = map__objdump_2mem(map, ops->target.addr), + }; + + if (maps__find_ams(thread__maps(ms->thread), &target) == 0 && + map__rip_2objdump(target.ms.map, + map__map_ip(target.ms.map, target.addr)) == ops->target.addr) + ops->target.sym = target.ms.sym; + + addr_map_symbol__exit(&target); + return 0; +} + +static const struct ins_ops alpha_call_ops = { + .parse = alpha_call__parse, + .scnprintf = call__scnprintf, + .is_call = true, +}; + +/* + * jsr and jmp transfer control to a register, and their trailing operand is + * only a branch prediction hint: + * + * jsr ra,(t12),fffffc0001014ee8 <_printk> + * + * binutils extracts that hint as a 14-bit signed field scaled by four and + * prints it relative to the next instruction (extract_jhint() in alpha-opc.c, + * print_insn_alpha() in alpha-dis.c), so it can name the callee only when the + * callee lies within the resulting +-32KB. It also defaults to zero, which + * prints as the next instruction. Most hints are therefore not the callee at + * all, and parsing one would invent a call target, so these resolve no target + * and keep their operands, as an indirect call does elsewhere. The hint on + * jcr is not even an address. + */ +static const struct ins_ops alpha_indirect_call_ops = { + .scnprintf = ins__raw_scnprintf, + .is_call = true, +}; + +static const struct ins_ops alpha_indirect_jump_ops = { + .scnprintf = ins__raw_scnprintf, + .is_jump = true, +}; + +static int is_alpha_cond_branch(const char *name) +{ + static const char *const branches[] = { + "beq", "bne", "blt", "ble", "bgt", "bge", "blbc", "blbs", + "fbeq", "fbne", "fblt", "fble", "fbgt", "fbge", + }; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(branches); i++) { + if (!strcmp(name, branches[i])) + return 1; + } + return 0; +} + +static const struct ins_ops *alpha__associate_instruction_ops(struct arch *arch, const char *name) +{ + const struct ins_ops *ops = NULL; + + if (!strcmp(name, "bsr")) { + ops = &alpha_call_ops; + } else if (!strcmp(name, "jsr") || + !strcmp(name, "jcr")) { + ops = &alpha_indirect_call_ops; + } else if (!strcmp(name, "ret")) { + ops = &ret_ops; + } else if (!strcmp(name, "jmp")) { + ops = &alpha_indirect_jump_ops; + } else if (!strcmp(name, "br") || + is_alpha_cond_branch(name)) { + ops = &jump_ops; + } else if (!strcmp(name, "mov") || + !strcmp(name, "fmov")) { + ops = &mov_ops; + } + + if (ops) + arch__associate_ins_ops(arch, name, ops); + + return ops; +} + +const struct arch *arch__new_alpha(const struct e_machine_and_e_flags *id, + const char *cpuid __maybe_unused) +{ + struct arch *arch = zalloc(sizeof(*arch)); + + if (!arch) + return NULL; + + arch->name = "alpha"; + arch->id = *id; + arch->associate_instruction_ops = alpha__associate_instruction_ops; + /* objdump emits no comments for Alpha; '#' is what the assembler uses. */ + arch->objdump.comment_char = '#'; + return arch; +} diff --git a/tools/perf/util/disasm.c b/tools/perf/util/disasm.c index 6cfdbabbb8c7..49c206a88eec 100644 --- a/tools/perf/util/disasm.c +++ b/tools/perf/util/disasm.c @@ -161,6 +161,8 @@ const struct arch *arch__find(uint16_t e_machine, uint32_t e_flags, const char * .e_flags = e_flags, }; const struct arch *result = NULL, **tmp; + const struct arch *(*new_fn)(const struct e_machine_and_e_flags *id, + const char *cpuid) = NULL; if (num_archs > 0) { tmp = bsearch(&key, archs, num_archs, sizeof(*archs), arch__key_cmp); @@ -171,7 +173,16 @@ const struct arch *arch__find(uint16_t e_machine, uint32_t e_flags, const char * if (result) return result; - if (e_machine >= ARRAY_SIZE(arch_new_fn) || arch_new_fn[e_machine] == NULL) { + /* + * EM_ALPHA (0x9026) is far too large to index arch_new_fn[], so it is + * selected explicitly; everything else uses the e_machine-indexed table. + */ + if (e_machine == EM_ALPHA) + new_fn = arch__new_alpha; + else if (e_machine < ARRAY_SIZE(arch_new_fn)) + new_fn = arch_new_fn[e_machine]; + + if (new_fn == NULL) { errno = ENOTSUP; return NULL; } @@ -182,7 +193,7 @@ const struct arch *arch__find(uint16_t e_machine, uint32_t e_flags, const char * archs = tmp; - result = arch_new_fn[e_machine](&key, cpuid); + result = new_fn(&key, cpuid); if (!result) { pr_err("%s: failed to initialize %u arch priv area\n", __func__, e_machine); diff --git a/tools/perf/util/disasm.h b/tools/perf/util/disasm.h index 25756e3f47e4..adbdbf8f1f35 100644 --- a/tools/perf/util/disasm.h +++ b/tools/perf/util/disasm.h @@ -122,6 +122,7 @@ extern const struct ins_ops ret_ops; int arch__associate_ins_ops(struct arch *arch, const char *name, const struct ins_ops *ops); +const struct arch *arch__new_alpha(const struct e_machine_and_e_flags *id, const char *cpuid); const struct arch *arch__new_arc(const struct e_machine_and_e_flags *id, const char *cpuid); const struct arch *arch__new_arm(const struct e_machine_and_e_flags *id, const char *cpuid); const struct arch *arch__new_arm64(const struct e_machine_and_e_flags *id, const char *cpuid); |
