| // SPDX-License-Identifier: GPL-2.0-or-later | |
| /* | |
| * Copyright (c) 2025, Oracle and/or its affiliates. | |
| */ | |
| #include <objtool/trace.h> | |
| bool trace; | |
| int trace_depth; | |
| /* | |
| * Macros to trace CFI state attributes changes. | |
| */ | |
| #define TRACE_CFI_ATTR(attr, prev, next, fmt, ...) \ | |
| ({ \ | |
| if ((prev)->attr != (next)->attr) \ | |
| TRACE("%s=" fmt " ", #attr, __VA_ARGS__); \ | |
| }) | |
| #define TRACE_CFI_ATTR_BOOL(attr, prev, next) \ | |
| TRACE_CFI_ATTR(attr, prev, next, \ | |
| "%s", (next)->attr ? "true" : "false") | |
| #define TRACE_CFI_ATTR_NUM(attr, prev, next, fmt) \ | |
| TRACE_CFI_ATTR(attr, prev, next, fmt, (next)->attr) | |
| #define CFI_REG_NAME_MAXLEN 16 | |
| /* | |
| * Return the name of a register. Note that the same static buffer | |
| * is returned if the name is dynamically generated. | |
| */ | |
| static const char *cfi_reg_name(unsigned int reg) | |
| { | |
| static char rname_buffer[CFI_REG_NAME_MAXLEN]; | |
| const char *rname; | |
| switch (reg) { | |
| case CFI_UNDEFINED: | |
| return "<undefined>"; | |
| case CFI_CFA: | |
| return "cfa"; | |
| case CFI_SP_INDIRECT: | |
| return "(sp)"; | |
| case CFI_BP_INDIRECT: | |
| return "(bp)"; | |
| } | |
| if (reg < CFI_NUM_REGS) { | |
| rname = arch_reg_name[reg]; | |
| if (rname) | |
| return rname; | |
| } | |
| if (snprintf(rname_buffer, CFI_REG_NAME_MAXLEN, "r%d", reg) == -1) | |
| return "<error>"; | |
| return (const char *)rname_buffer; | |
| } | |
| /* | |
| * Functions and macros to trace CFI registers changes. | |
| */ | |
| static void trace_cfi_reg(const char *prefix, int reg, const char *fmt, | |
| int base_prev, int offset_prev, | |
| int base_next, int offset_next) | |
| { | |
| char *rname; | |
| if (base_prev == base_next && offset_prev == offset_next) | |
| return; | |
| if (prefix) | |
| TRACE("%s:", prefix); | |
| if (base_next == CFI_UNDEFINED) { | |
| TRACE("%1$s=<undef> ", cfi_reg_name(reg)); | |
| } else { | |
| rname = strdup(cfi_reg_name(reg)); | |
| TRACE(fmt, rname, cfi_reg_name(base_next), offset_next); | |
| free(rname); | |
| } | |
| } | |
| static void trace_cfi_reg_val(const char *prefix, int reg, | |
| int base_prev, int offset_prev, | |
| int base_next, int offset_next) | |
| { | |
| trace_cfi_reg(prefix, reg, "%1$s=%2$s%3$+d ", | |
| base_prev, offset_prev, base_next, offset_next); | |
| } | |
| static void trace_cfi_reg_ref(const char *prefix, int reg, | |
| int base_prev, int offset_prev, | |
| int base_next, int offset_next) | |
| { | |
| trace_cfi_reg(prefix, reg, "%1$s=(%2$s%3$+d) ", | |
| base_prev, offset_prev, base_next, offset_next); | |
| } | |
| #define TRACE_CFI_REG_VAL(reg, prev, next) \ | |
| trace_cfi_reg_val(NULL, reg, prev.base, prev.offset, \ | |
| next.base, next.offset) | |
| #define TRACE_CFI_REG_REF(reg, prev, next) \ | |
| trace_cfi_reg_ref(NULL, reg, prev.base, prev.offset, \ | |
| next.base, next.offset) | |
| void trace_insn_state(struct instruction *insn, struct insn_state *sprev, | |
| struct insn_state *snext) | |
| { | |
| struct cfi_state *cprev, *cnext; | |
| int i; | |
| if (!memcmp(sprev, snext, sizeof(struct insn_state))) | |
| return; | |
| cprev = &sprev->cfi; | |
| cnext = &snext->cfi; | |
| disas_print_insn(stderr, objtool_disas_ctx, insn, | |
| trace_depth - 1, "state: "); | |
| /* print registers changes */ | |
| TRACE_CFI_REG_VAL(CFI_CFA, cprev->cfa, cnext->cfa); | |
| for (i = 0; i < CFI_NUM_REGS; i++) { | |
| TRACE_CFI_REG_VAL(i, cprev->vals[i], cnext->vals[i]); | |
| TRACE_CFI_REG_REF(i, cprev->regs[i], cnext->regs[i]); | |
| } | |
| /* print attributes changes */ | |
| TRACE_CFI_ATTR_NUM(stack_size, cprev, cnext, "%d"); | |
| TRACE_CFI_ATTR_BOOL(drap, cprev, cnext); | |
| if (cnext->drap) { | |
| trace_cfi_reg_val("drap", cnext->drap_reg, | |
| cprev->drap_reg, cprev->drap_offset, | |
| cnext->drap_reg, cnext->drap_offset); | |
| } | |
| TRACE_CFI_ATTR_BOOL(bp_scratch, cprev, cnext); | |
| TRACE_CFI_ATTR_NUM(instr, sprev, snext, "%d"); | |
| TRACE_CFI_ATTR_NUM(uaccess_stack, sprev, snext, "%u"); | |
| TRACE("\n"); | |
| insn->trace = 1; | |
| } | |
| void trace_alt_begin(struct instruction *orig_insn, struct alternative *alt, | |
| char *alt_name) | |
| { | |
| struct instruction *alt_insn; | |
| char suffix[2]; | |
| alt_insn = alt->insn; | |
| if (alt->type == ALT_TYPE_EX_TABLE) { | |
| /* | |
| * When there is an exception table then the instruction | |
| * at the original location is executed but it can cause | |
| * an exception. In that case, the execution will be | |
| * redirected to the alternative instruction. | |
| * | |
| * The instruction at the original location can have | |
| * instruction alternatives, so we just print the location | |
| * of the instruction that can cause the exception and | |
| * not the instruction itself. | |
| */ | |
| TRACE_ALT_INFO_NOADDR(orig_insn, "/ ", "%s for instruction at 0x%lx <%s+0x%lx>", | |
| alt_name, | |
| orig_insn->offset, insn_sym(orig_insn)->name, | |
| orig_insn->offset - insn_sym(orig_insn)->offset); | |
| } else { | |
| TRACE_ALT_INFO_NOADDR(orig_insn, "/ ", "%s", alt_name); | |
| } | |
| if (alt->type == ALT_TYPE_JUMP_TABLE) { | |
| /* | |
| * For a jump alternative, if the default instruction is | |
| * a NOP then it is replaced with the jmp instruction, | |
| * otherwise it is replaced with a NOP instruction. | |
| */ | |
| trace_depth++; | |
| if (orig_insn->type == INSN_NOP) { | |
| suffix[0] = (orig_insn->len == 5) ? 'q' : '\0'; | |
| TRACE_ADDR(orig_insn, "jmp%-3s %lx <%s+0x%lx>", suffix, | |
| alt_insn->offset, insn_sym(alt_insn)->name, | |
| alt_insn->offset - insn_sym(alt_insn)->offset); | |
| } else { | |
| TRACE_ADDR(orig_insn, "nop%d", orig_insn->len); | |
| trace_depth--; | |
| } | |
| } | |
| } | |
| void trace_alt_end(struct instruction *orig_insn, struct alternative *alt, | |
| char *alt_name) | |
| { | |
| if (alt->type == ALT_TYPE_JUMP_TABLE && orig_insn->type == INSN_NOP) | |
| trace_depth--; | |
| TRACE_ALT_INFO_NOADDR(orig_insn, "\\ ", "%s", alt_name); | |
| } |