| 1 | |
| 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 |
|
| 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 |
|
| 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, insn_sym(orig_insn)->name,
|
| 173 | orig_insn->offset - insn_sym(orig_insn)->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, insn_sym(alt_insn)->name,
|
| 189 | alt_insn->offset - insn_sym(alt_insn)->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 | }
|