1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * Copyright (c) 2025, Oracle and/or its affiliates. 4 */ 5 6 #include <objtool/trace.h> 7 8 bool trace; 9 int trace_depth; 10 11 /* 12 * Macros to trace CFI state attributes changes. 13 */ 14 15 #define TRACE_CFI_ATTR(attr, prev, next, fmt, ...) \ 16 ({ \ 17 if ((prev)->attr != (next)->attr) \ 18 TRACE("%s=" fmt " ", #attr, __VA_ARGS__); \ 19 }) 20 21 #define TRACE_CFI_ATTR_BOOL(attr, prev, next) \ 22 TRACE_CFI_ATTR(attr, prev, next, \ 23 "%s", (next)->attr ? "true" : "false") 24 25 #define TRACE_CFI_ATTR_NUM(attr, prev, next, fmt) \ 26 TRACE_CFI_ATTR(attr, prev, next, fmt, (next)->attr) 27 28 #define CFI_REG_NAME_MAXLEN 16 29 30 /* 31 * Return the name of a register. Note that the same static buffer 32 * is returned if the name is dynamically generated. 33 */ 34 static const char *cfi_reg_name(unsigned int reg) 35 { 36 static char rname_buffer[CFI_REG_NAME_MAXLEN]; 37 const char *rname; 38 39 switch (reg) { 40 case CFI_UNDEFINED: 41 return "<undefined>"; 42 case CFI_CFA: 43 return "cfa"; 44 case CFI_SP_INDIRECT: 45 return "(sp)"; 46 case CFI_BP_INDIRECT: 47 return "(bp)"; 48 } 49 50 if (reg < CFI_NUM_REGS) { 51 rname = arch_reg_name[reg]; 52 if (rname) 53 return rname; 54 } 55 56 if (snprintf(rname_buffer, CFI_REG_NAME_MAXLEN, "r%d", reg) == -1) 57 return "<error>"; 58 59 return (const char *)rname_buffer; 60 } 61 62 /* 63 * Functions and macros to trace CFI registers changes. 64 */ 65 66 static void trace_cfi_reg(const char *prefix, int reg, const char *fmt, 67 int base_prev, int offset_prev, 68 int base_next, int offset_next) 69 { 70 char *rname; 71 72 if (base_prev == base_next && offset_prev == offset_next) 73 return; 74 75 if (prefix) 76 TRACE("%s:", prefix); 77 78 if (base_next == CFI_UNDEFINED) { 79 TRACE("%1$s=<undef> ", cfi_reg_name(reg)); 80 } else { 81 rname = strdup(cfi_reg_name(reg)); 82 TRACE(fmt, rname, cfi_reg_name(base_next), offset_next); 83 free(rname); 84 } 85 } 86 87 static void trace_cfi_reg_val(const char *prefix, int reg, 88 int base_prev, int offset_prev, 89 int base_next, int offset_next) 90 { 91 trace_cfi_reg(prefix, reg, "%1$s=%2$s%3$+d ", 92 base_prev, offset_prev, base_next, offset_next); 93 } 94 95 static void trace_cfi_reg_ref(const char *prefix, int reg, 96 int base_prev, int offset_prev, 97 int base_next, int offset_next) 98 { 99 trace_cfi_reg(prefix, reg, "%1$s=(%2$s%3$+d) ", 100 base_prev, offset_prev, base_next, offset_next); 101 } 102 103 #define TRACE_CFI_REG_VAL(reg, prev, next) \ 104 trace_cfi_reg_val(NULL, reg, prev.base, prev.offset, \ 105 next.base, next.offset) 106 107 #define TRACE_CFI_REG_REF(reg, prev, next) \ 108 trace_cfi_reg_ref(NULL, reg, prev.base, prev.offset, \ 109 next.base, next.offset) 110 111 void trace_insn_state(struct instruction *insn, struct insn_state *sprev, 112 struct insn_state *snext) 113 { 114 struct cfi_state *cprev, *cnext; 115 int i; 116 117 if (!memcmp(sprev, snext, sizeof(struct insn_state))) 118 return; 119 120 cprev = &sprev->cfi; 121 cnext = &snext->cfi; 122 123 disas_print_insn(stderr, objtool_disas_ctx, insn, 124 trace_depth - 1, "state: "); 125 126 /* print registers changes */ 127 TRACE_CFI_REG_VAL(CFI_CFA, cprev->cfa, cnext->cfa); 128 for (i = 0; i < CFI_NUM_REGS; i++) { 129 TRACE_CFI_REG_VAL(i, cprev->vals[i], cnext->vals[i]); 130 TRACE_CFI_REG_REF(i, cprev->regs[i], cnext->regs[i]); 131 } 132 133 /* print attributes changes */ 134 TRACE_CFI_ATTR_NUM(stack_size, cprev, cnext, "%d"); 135 TRACE_CFI_ATTR_BOOL(drap, cprev, cnext); 136 if (cnext->drap) { 137 trace_cfi_reg_val("drap", cnext->drap_reg, 138 cprev->drap_reg, cprev->drap_offset, 139 cnext->drap_reg, cnext->drap_offset); 140 } 141 TRACE_CFI_ATTR_BOOL(bp_scratch, cprev, cnext); 142 TRACE_CFI_ATTR_NUM(instr, sprev, snext, "%d"); 143 TRACE_CFI_ATTR_NUM(uaccess_stack, sprev, snext, "%u"); 144 145 TRACE("\n"); 146 147 insn->trace = 1; 148 } 149 150 void trace_alt_begin(struct instruction *orig_insn, struct alternative *alt, 151 char *alt_name) 152 { 153 struct instruction *alt_insn; 154 char suffix[2]; 155 156 alt_insn = alt->insn; 157 158 if (alt->type == ALT_TYPE_EX_TABLE) { 159 /* 160 * When there is an exception table then the instruction 161 * at the original location is executed but it can cause 162 * an exception. In that case, the execution will be 163 * redirected to the alternative instruction. 164 * 165 * The instruction at the original location can have 166 * instruction alternatives, so we just print the location 167 * of the instruction that can cause the exception and 168 * not the instruction itself. 169 */ 170 TRACE_ALT_INFO_NOADDR(orig_insn, "/ ", "%s for instruction at 0x%lx <%s+0x%lx>", 171 alt_name, 172 orig_insn->offset, orig_insn->sym->name, 173 orig_insn->offset - orig_insn->sym->offset); 174 } else { 175 TRACE_ALT_INFO_NOADDR(orig_insn, "/ ", "%s", alt_name); 176 } 177 178 if (alt->type == ALT_TYPE_JUMP_TABLE) { 179 /* 180 * For a jump alternative, if the default instruction is 181 * a NOP then it is replaced with the jmp instruction, 182 * otherwise it is replaced with a NOP instruction. 183 */ 184 trace_depth++; 185 if (orig_insn->type == INSN_NOP) { 186 suffix[0] = (orig_insn->len == 5) ? 'q' : '\0'; 187 TRACE_ADDR(orig_insn, "jmp%-3s %lx <%s+0x%lx>", suffix, 188 alt_insn->offset, alt_insn->sym->name, 189 alt_insn->offset - alt_insn->sym->offset); 190 } else { 191 TRACE_ADDR(orig_insn, "nop%d", orig_insn->len); 192 trace_depth--; 193 } 194 } 195 } 196 197 void trace_alt_end(struct instruction *orig_insn, struct alternative *alt, 198 char *alt_name) 199 { 200 if (alt->type == ALT_TYPE_JUMP_TABLE && orig_insn->type == INSN_NOP) 201 trace_depth--; 202 TRACE_ALT_INFO_NOADDR(orig_insn, "\\ ", "%s", alt_name); 203 } 204