| // 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; | |
| } |