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