xref: /linux/tools/objtool/disas.c (revision 0bb080ba6469a573bc85122153d931334d10a173)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Copyright (C) 2015-2017 Josh Poimboeuf <jpoimboe@redhat.com>
4  */
5 
6 #include <objtool/arch.h>
7 #include <objtool/check.h>
8 #include <objtool/disas.h>
9 #include <objtool/warn.h>
10 
11 #include <bfd.h>
12 #include <linux/string.h>
13 #include <tools/dis-asm-compat.h>
14 
15 /*
16  * Size of the buffer for storing the result of disassembling
17  * a single instruction.
18  */
19 #define DISAS_RESULT_SIZE	1024
20 
21 struct disas_context {
22 	struct objtool_file *file;
23 	struct instruction *insn;
24 	char result[DISAS_RESULT_SIZE];
25 	disassembler_ftype disassembler;
26 	struct disassemble_info info;
27 };
28 
29 static int sprint_name(char *str, const char *name, unsigned long offset)
30 {
31 	int len;
32 
33 	if (offset)
34 		len = sprintf(str, "%s+0x%lx", name, offset);
35 	else
36 		len = sprintf(str, "%s", name);
37 
38 	return len;
39 }
40 
41 #define DINFO_FPRINTF(dinfo, ...)	\
42 	((*(dinfo)->fprintf_func)((dinfo)->stream, __VA_ARGS__))
43 
44 static int disas_result_fprintf(struct disas_context *dctx,
45 				const char *fmt, va_list ap)
46 {
47 	char *buf = dctx->result;
48 	int avail, len;
49 
50 	len = strlen(buf);
51 	if (len >= DISAS_RESULT_SIZE - 1) {
52 		WARN_FUNC(dctx->insn->sec, dctx->insn->offset,
53 			  "disassembly buffer is full");
54 		return -1;
55 	}
56 	avail = DISAS_RESULT_SIZE - len;
57 
58 	len = vsnprintf(buf + len, avail, fmt, ap);
59 	if (len < 0 || len >= avail) {
60 		WARN_FUNC(dctx->insn->sec, dctx->insn->offset,
61 			  "disassembly buffer is truncated");
62 		return -1;
63 	}
64 
65 	return 0;
66 }
67 
68 static int disas_fprintf(void *stream, const char *fmt, ...)
69 {
70 	va_list arg;
71 	int rv;
72 
73 	va_start(arg, fmt);
74 	rv = disas_result_fprintf(stream, fmt, arg);
75 	va_end(arg);
76 
77 	return rv;
78 }
79 
80 /*
81  * For init_disassemble_info_compat().
82  */
83 static int disas_fprintf_styled(void *stream,
84 				enum disassembler_style style,
85 				const char *fmt, ...)
86 {
87 	va_list arg;
88 	int rv;
89 
90 	va_start(arg, fmt);
91 	rv = disas_result_fprintf(stream, fmt, arg);
92 	va_end(arg);
93 
94 	return rv;
95 }
96 
97 static void disas_print_addr_sym(struct section *sec, struct symbol *sym,
98 				 bfd_vma addr, struct disassemble_info *dinfo)
99 {
100 	char symstr[1024];
101 	char *str;
102 
103 	if (sym) {
104 		sprint_name(symstr, sym->name, addr - sym->offset);
105 		DINFO_FPRINTF(dinfo, "0x%lx <%s>", addr, symstr);
106 	} else {
107 		str = offstr(sec, addr);
108 		DINFO_FPRINTF(dinfo, "0x%lx <%s>", addr, str);
109 		free(str);
110 	}
111 }
112 
113 static void disas_print_addr_noreloc(bfd_vma addr,
114 				     struct disassemble_info *dinfo)
115 {
116 	struct disas_context *dctx = dinfo->application_data;
117 	struct instruction *insn = dctx->insn;
118 	struct symbol *sym = NULL;
119 
120 	if (insn->sym && addr >= insn->sym->offset &&
121 	    addr < insn->sym->offset + insn->sym->len) {
122 		sym = insn->sym;
123 	}
124 
125 	disas_print_addr_sym(insn->sec, sym, addr, dinfo);
126 }
127 
128 static void disas_print_addr_reloc(bfd_vma addr, struct disassemble_info *dinfo)
129 {
130 	struct disas_context *dctx = dinfo->application_data;
131 	struct instruction *insn = dctx->insn;
132 	unsigned long offset;
133 	struct reloc *reloc;
134 	char symstr[1024];
135 	char *str;
136 
137 	reloc = find_reloc_by_dest_range(dctx->file->elf, insn->sec,
138 					 insn->offset, insn->len);
139 	if (!reloc) {
140 		/*
141 		 * There is no relocation for this instruction although
142 		 * the address to resolve points to the next instruction.
143 		 * So this is an effective reference to the next IP, for
144 		 * example: "lea 0x0(%rip),%rdi". The kernel can reference
145 		 * the next IP with _THIS_IP_ macro.
146 		 */
147 		DINFO_FPRINTF(dinfo, "0x%lx <_THIS_IP_>", addr);
148 		return;
149 	}
150 
151 	offset = arch_insn_adjusted_addend(insn, reloc);
152 
153 	/*
154 	 * If the relocation symbol is a section name (for example ".bss")
155 	 * then we try to further resolve the name.
156 	 */
157 	if (reloc->sym->type == STT_SECTION) {
158 		str = offstr(reloc->sym->sec, reloc->sym->offset + offset);
159 		DINFO_FPRINTF(dinfo, "0x%lx <%s>", addr, str);
160 		free(str);
161 	} else {
162 		sprint_name(symstr, reloc->sym->name, offset);
163 		DINFO_FPRINTF(dinfo, "0x%lx <%s>", addr, symstr);
164 	}
165 }
166 
167 /*
168  * Resolve an address into a "<symbol>+<offset>" string.
169  */
170 static void disas_print_address(bfd_vma addr, struct disassemble_info *dinfo)
171 {
172 	struct disas_context *dctx = dinfo->application_data;
173 	struct instruction *insn = dctx->insn;
174 	struct instruction *jump_dest;
175 	struct symbol *sym;
176 	bool is_reloc;
177 
178 	/*
179 	 * If the instruction is a call/jump and it references a
180 	 * destination then this is likely the address we are looking
181 	 * up. So check it first.
182 	 */
183 	jump_dest = insn->jump_dest;
184 	if (jump_dest && jump_dest->sym && jump_dest->offset == addr) {
185 		disas_print_addr_sym(jump_dest->sec, jump_dest->sym,
186 				     addr, dinfo);
187 		return;
188 	}
189 
190 	/*
191 	 * If the address points to the next instruction then there is
192 	 * probably a relocation. It can be a false positive when the
193 	 * current instruction is referencing the address of the next
194 	 * instruction. This particular case will be handled in
195 	 * disas_print_addr_reloc().
196 	 */
197 	is_reloc = (addr == insn->offset + insn->len);
198 
199 	/*
200 	 * The call destination offset can be the address we are looking
201 	 * up, or 0 if there is a relocation.
202 	 */
203 	sym = insn_call_dest(insn);
204 	if (sym && (sym->offset == addr || (sym->offset == 0 && is_reloc))) {
205 		DINFO_FPRINTF(dinfo, "0x%lx <%s>", addr, sym->name);
206 		return;
207 	}
208 
209 	if (!is_reloc)
210 		disas_print_addr_noreloc(addr, dinfo);
211 	else
212 		disas_print_addr_reloc(addr, dinfo);
213 }
214 
215 /*
216  * Initialize disassemble info arch, mach (32 or 64-bit) and options.
217  */
218 int disas_info_init(struct disassemble_info *dinfo,
219 		    int arch, int mach32, int mach64,
220 		    const char *options)
221 {
222 	struct disas_context *dctx = dinfo->application_data;
223 	struct objtool_file *file = dctx->file;
224 
225 	dinfo->arch = arch;
226 
227 	switch (file->elf->ehdr.e_ident[EI_CLASS]) {
228 	case ELFCLASS32:
229 		dinfo->mach = mach32;
230 		break;
231 	case ELFCLASS64:
232 		dinfo->mach = mach64;
233 		break;
234 	default:
235 		return -1;
236 	}
237 
238 	dinfo->disassembler_options = options;
239 
240 	return 0;
241 }
242 
243 struct disas_context *disas_context_create(struct objtool_file *file)
244 {
245 	struct disas_context *dctx;
246 	struct disassemble_info *dinfo;
247 	int err;
248 
249 	dctx = malloc(sizeof(*dctx));
250 	if (!dctx) {
251 		WARN("failed to allocate disassembly context");
252 		return NULL;
253 	}
254 
255 	dctx->file = file;
256 	dinfo = &dctx->info;
257 
258 	init_disassemble_info_compat(dinfo, dctx,
259 				     disas_fprintf, disas_fprintf_styled);
260 
261 	dinfo->read_memory_func = buffer_read_memory;
262 	dinfo->print_address_func = disas_print_address;
263 	dinfo->application_data = dctx;
264 
265 	/*
266 	 * bfd_openr() is not used to avoid doing ELF data processing
267 	 * and caching that has already being done. Here, we just need
268 	 * to identify the target file so we call an arch specific
269 	 * function to fill some disassemble info (arch, mach).
270 	 */
271 
272 	dinfo->arch = bfd_arch_unknown;
273 	dinfo->mach = 0;
274 
275 	err = arch_disas_info_init(dinfo);
276 	if (err || dinfo->arch == bfd_arch_unknown || dinfo->mach == 0) {
277 		WARN("failed to init disassembly arch");
278 		goto error;
279 	}
280 
281 	dinfo->endian = (file->elf->ehdr.e_ident[EI_DATA] == ELFDATA2MSB) ?
282 		BFD_ENDIAN_BIG : BFD_ENDIAN_LITTLE;
283 
284 	disassemble_init_for_target(dinfo);
285 
286 	dctx->disassembler = disassembler(dinfo->arch,
287 					  dinfo->endian == BFD_ENDIAN_BIG,
288 					  dinfo->mach, NULL);
289 	if (!dctx->disassembler) {
290 		WARN("failed to create disassembler function");
291 		goto error;
292 	}
293 
294 	return dctx;
295 
296 error:
297 	free(dctx);
298 	return NULL;
299 }
300 
301 void disas_context_destroy(struct disas_context *dctx)
302 {
303 	free(dctx);
304 }
305 
306 char *disas_result(struct disas_context *dctx)
307 {
308 	return dctx->result;
309 }
310 
311 /*
312  * Disassemble a single instruction. Return the size of the instruction.
313  */
314 size_t disas_insn(struct disas_context *dctx, struct instruction *insn)
315 {
316 	disassembler_ftype disasm = dctx->disassembler;
317 	struct disassemble_info *dinfo = &dctx->info;
318 
319 	dctx->insn = insn;
320 	dctx->result[0] = '\0';
321 
322 	if (insn->type == INSN_NOP) {
323 		DINFO_FPRINTF(dinfo, "nop%d", insn->len);
324 		return insn->len;
325 	}
326 
327 	/*
328 	 * Set the disassembler buffer to read data from the section
329 	 * containing the instruction to disassemble.
330 	 */
331 	dinfo->buffer = insn->sec->data->d_buf;
332 	dinfo->buffer_vma = 0;
333 	dinfo->buffer_length = insn->sec->sh.sh_size;
334 
335 	return disasm(insn->offset, &dctx->info);
336 }
337 
338 /*
339  * Disassemble a function.
340  */
341 static void disas_func(struct disas_context *dctx, struct symbol *func)
342 {
343 	struct instruction *insn;
344 	size_t addr;
345 
346 	printf("%s:\n", func->name);
347 	sym_for_each_insn(dctx->file, func, insn) {
348 		addr = insn->offset;
349 		disas_insn(dctx, insn);
350 		printf(" %6lx:  %s+0x%-6lx      %s\n",
351 		       addr, func->name, addr - func->offset,
352 		       disas_result(dctx));
353 	}
354 	printf("\n");
355 }
356 
357 /*
358  * Disassemble all warned functions.
359  */
360 void disas_warned_funcs(struct disas_context *dctx)
361 {
362 	struct symbol *sym;
363 
364 	if (!dctx)
365 		return;
366 
367 	for_each_sym(dctx->file->elf, sym) {
368 		if (sym->warned)
369 			disas_func(dctx, sym);
370 	}
371 }
372