summaryrefslogtreecommitdiff
path: root/tools/objtool/trace.c
blob: 5dec44dab781cea95dbc7cab8d4b1e3a746920f5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
// 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, orig_insn->sym->name,
				      orig_insn->offset - orig_insn->sym->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, alt_insn->sym->name,
				   alt_insn->offset - alt_insn->sym->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);
}