mirror of
https://github.com/torvalds/linux.git
synced 2026-11-10 17:11:02 +01:00
perf annotate: add Alpha instruction support
Teach perf annotate about the Alpha control-transfer instructions, so that
an Alpha perf.data gets call and jump arrows and resolved call targets,
whether it is read on Alpha or on another host.
Add tools/perf/util/annotate-arch/annotate-alpha.c with arch__new_alpha()
and an associate_instruction_ops() that classifies:
call: bsr, plus jsr and jcr as indirect calls
ret: ret
jump: br, the conditional branches beq/bne/blt/ble/bgt/bge/blbc/blbs
and fbeq/fbne/fblt/fble/fbgt/fbge, plus jmp as an indirect jump
mov: mov, fmov (objdump pseudos)
That is every mnemonic binutils can print for the branch and JSR formats.
jcr rather than jsr_coroutine, because both name the same MBR(0x1a,3)
encoding and print_insn_alpha() takes the first match in the table, where
the jcr alias has come first since the sources were imported in 1999.
bsr needs an Alpha-specific parse routine. The generic call__parse()
expects the operand string to begin with the target address, but a bsr
prints its return-address register first:
bsr t0,fffffc0001031dc0 <cserve_ena>
strtoull() then stops on the leading register name, leaving the target
address as 0, which makes call__scnprintf() fall back to printing the raw
operands and leaves target.sym unresolved so the browser cannot follow the
call. alpha_call__parse() takes the address from after the comma instead,
as s390_call__parse() does for the same reason. The PC-relative branches
need no such handling, as jump__parse() already skips up to two operands.
jsr and jmp get ins_ops that resolve no target at all. They 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. Of the 213750 jsr in a vmlinux built from
this tree, only 23093 hints land on a symbol; 157204 point into the middle
of an unrelated function and 33453 are that default. Parsing the hint
would therefore invent a call target for the majority of calls, so these
keep their operands, as an indirect call does elsewhere.
EM_ALPHA is 0x9026, far too large to index the e_machine-keyed
arch_new_fn[] table in arch__find(), so select arch__new_alpha explicitly
before the table lookup. Declare it in disasm.h and add the object to the
annotate-arch Build.
Disassembly itself comes from objdump/binutils, which already supports
Alpha; this provides perf's instruction-class metadata for annotation.
Tested on an EV7 Marvel, both natively and by annotating its perf.data on
an x86_64 host, over bsr to a local function, jsr through the PLT and
kernel-mode jsr; the two hosts produce identical output.
Reviewed-by: Ian Rogers <irogers@google.com>
Signed-off-by: Matt Turner <mattst88@gmail.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
This commit is contained in:
parent
15917a3a1c
commit
c749aacf73
|
|
@ -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
|
||||
|
|
|
|||
185
tools/perf/util/annotate-arch/annotate-alpha.c
Normal file
185
tools/perf/util/annotate-arch/annotate-alpha.c
Normal file
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user