xref: /linux/tools/objtool/trace.c (revision 63e6995005be8ceb8a1d56a18df1a1a40c28356d)
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