xref: /illumos-gate/usr/src/cmd/mdb/common/mdb/mdb_disasm.c (revision 80148899834a4078a2bd348504aa2d6de9752837)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #include <mdb/mdb_disasm_impl.h>
27 #include <mdb/mdb_modapi.h>
28 #include <mdb/mdb_string.h>
29 #include <mdb/mdb_debug.h>
30 #include <mdb/mdb_err.h>
31 #include <mdb/mdb_nv.h>
32 #include <mdb/mdb.h>
33 
34 #include <libdisasm.h>
35 
36 int
37 mdb_dis_select(const char *name)
38 {
39 	mdb_var_t *v = mdb_nv_lookup(&mdb.m_disasms, name);
40 
41 	if (v != NULL) {
42 		mdb.m_disasm = mdb_nv_get_cookie(v);
43 		return (0);
44 	}
45 
46 	if (mdb.m_target == NULL) {
47 		if (mdb.m_defdisasm != NULL)
48 			strfree(mdb.m_defdisasm);
49 		mdb.m_defdisasm = strdup(name);
50 		return (0);
51 	}
52 
53 	return (set_errno(EMDB_NODIS));
54 }
55 
56 mdb_disasm_t *
57 mdb_dis_create(mdb_dis_ctor_f *ctor)
58 {
59 	mdb_disasm_t *dp = mdb_zalloc(sizeof (mdb_disasm_t), UM_SLEEP);
60 
61 	if ((dp->dis_module = mdb.m_lmod) == NULL)
62 		dp->dis_module = &mdb.m_rmod;
63 
64 	if (ctor(dp) == 0) {
65 		mdb_var_t *v = mdb_nv_lookup(&mdb.m_disasms, dp->dis_name);
66 
67 		if (v != NULL) {
68 			dp->dis_ops->dis_destroy(dp);
69 			mdb_free(dp, sizeof (mdb_disasm_t));
70 			(void) set_errno(EMDB_DISEXISTS);
71 			return (NULL);
72 		}
73 
74 		(void) mdb_nv_insert(&mdb.m_disasms, dp->dis_name, NULL,
75 		    (uintptr_t)dp, MDB_NV_RDONLY | MDB_NV_SILENT);
76 
77 		if (mdb.m_disasm == NULL) {
78 			mdb.m_disasm = dp;
79 		} else if (mdb.m_defdisasm != NULL &&
80 		    strcmp(mdb.m_defdisasm, dp->dis_name) == 0) {
81 			mdb.m_disasm = dp;
82 			strfree(mdb.m_defdisasm);
83 			mdb.m_defdisasm = NULL;
84 		}
85 
86 		return (dp);
87 	}
88 
89 	mdb_free(dp, sizeof (mdb_disasm_t));
90 	return (NULL);
91 }
92 
93 void
94 mdb_dis_destroy(mdb_disasm_t *dp)
95 {
96 	mdb_var_t *v = mdb_nv_lookup(&mdb.m_disasms, dp->dis_name);
97 
98 	ASSERT(v != NULL);
99 	mdb_nv_remove(&mdb.m_disasms, v);
100 	dp->dis_ops->dis_destroy(dp);
101 	mdb_free(dp, sizeof (mdb_disasm_t));
102 
103 	if (mdb.m_disasm == dp)
104 		(void) mdb_dis_select("default");
105 }
106 
107 mdb_tgt_addr_t
108 mdb_dis_ins2str(mdb_disasm_t *dp, mdb_tgt_t *t, mdb_tgt_as_t as,
109     char *buf, size_t len, mdb_tgt_addr_t addr)
110 {
111 	return (dp->dis_ops->dis_ins2str(dp, t, as, buf, len, addr));
112 }
113 
114 mdb_tgt_addr_t
115 mdb_dis_previns(mdb_disasm_t *dp, mdb_tgt_t *t, mdb_tgt_as_t as,
116     mdb_tgt_addr_t addr, uint_t n)
117 {
118 	return (dp->dis_ops->dis_previns(dp, t, as, addr, n));
119 }
120 
121 mdb_tgt_addr_t
122 mdb_dis_nextins(mdb_disasm_t *dp, mdb_tgt_t *t, mdb_tgt_as_t as,
123     mdb_tgt_addr_t addr)
124 {
125 	return (dp->dis_ops->dis_nextins(dp, t, as, addr));
126 }
127 
128 /*ARGSUSED*/
129 int
130 cmd_dismode(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
131 {
132 	if ((flags & DCMD_ADDRSPEC) || argc > 1)
133 		return (DCMD_USAGE);
134 
135 	if (argc != 0) {
136 		const char *name;
137 
138 		if (argv->a_type == MDB_TYPE_STRING)
139 			name = argv->a_un.a_str;
140 		else
141 			name = numtostr(argv->a_un.a_val, 10, NTOS_UNSIGNED);
142 
143 		if (mdb_dis_select(name) == -1) {
144 			warn("failed to set disassembly mode");
145 			return (DCMD_ERR);
146 		}
147 	}
148 
149 	mdb_printf("disassembly mode is %s (%s)\n",
150 	    mdb.m_disasm->dis_name, mdb.m_disasm->dis_desc);
151 
152 	return (DCMD_OK);
153 }
154 
155 /*ARGSUSED*/
156 static int
157 print_dis(mdb_var_t *v, void *ignore)
158 {
159 	mdb_disasm_t *dp = mdb_nv_get_cookie(v);
160 
161 	mdb_printf("%-24s - %s\n", dp->dis_name, dp->dis_desc);
162 	return (0);
163 }
164 
165 /*ARGSUSED*/
166 int
167 cmd_disasms(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
168 {
169 	if ((flags & DCMD_ADDRSPEC) || argc != 0)
170 		return (DCMD_USAGE);
171 
172 	mdb_nv_sort_iter(&mdb.m_disasms, print_dis, NULL, UM_SLEEP | UM_GC);
173 	return (DCMD_OK);
174 }
175 
176 /*
177  * Generic libdisasm disassembler interfaces.
178  */
179 
180 #define	DISBUFSZ	64
181 
182 /*
183  * Internal structure used by the read and lookup routines.
184  */
185 typedef struct dis_buf {
186 	mdb_tgt_t	*db_tgt;
187 	mdb_tgt_as_t	db_as;
188 	mdb_tgt_addr_t	db_addr;
189 	mdb_tgt_addr_t	db_nextaddr;
190 	uchar_t		db_buf[DISBUFSZ];
191 	ssize_t		db_bufsize;
192 	boolean_t	db_readerr;
193 } dis_buf_t;
194 
195 /*
196  * Disassembler support routine for lookup up an address.  Rely on mdb's "%a"
197  * qualifier to convert the address to a symbol.
198  */
199 /*ARGSUSED*/
200 static int
201 libdisasm_lookup(void *data, uint64_t addr, char *buf, size_t buflen,
202     uint64_t *start, size_t *len)
203 {
204 	char c;
205 	GElf_Sym sym;
206 
207 	if (buf != NULL) {
208 #ifdef __sparc
209 		uint32_t instr[3];
210 		uint32_t dtrace_id;
211 
212 		/*
213 		 * On SPARC, DTrace FBT trampoline entries have a sethi/or pair
214 		 * that indicates the dtrace probe id; this may appear as the
215 		 * first two instructions or one instruction into the
216 		 * trampoline.
217 		 */
218 		if (mdb_vread(instr, sizeof (instr), (uintptr_t)addr) ==
219 		    sizeof (instr)) {
220 			if ((instr[0] & 0xfffc0000) == 0x11000000 &&
221 			    (instr[1] & 0xffffe000) == 0x90122000) {
222 				dtrace_id = (instr[0] << 10) |
223 				    (instr[1] & 0x1fff);
224 				(void) mdb_snprintf(buf, sizeof (buf), "dt=%#x",
225 				    dtrace_id);
226 				goto out;
227 			} else if ((instr[1] & 0xfffc0000) == 0x11000000 &&
228 			    (instr[2] & 0xffffe000) == 0x90122000) {
229 				dtrace_id = (instr[1] << 10) |
230 				    (instr[2] & 0x1fff);
231 				(void) mdb_snprintf(buf, sizeof (buf), "dt=%#x",
232 				    dtrace_id);
233 				goto out;
234 			}
235 		}
236 #endif
237 		(void) mdb_snprintf(buf, buflen, "%a", (uintptr_t)addr);
238 	}
239 
240 #ifdef __sparc
241 out:
242 #endif
243 	if (mdb_lookup_by_addr(addr, MDB_SYM_FUZZY, &c, 1, &sym) < 0)
244 		return (-1);
245 	if (start != NULL)
246 		*start = sym.st_value;
247 	if (len != NULL)
248 		*len = sym.st_size;
249 
250 	return (0);
251 }
252 
253 /*
254  * Disassembler support routine for reading from the target.  Rather than having
255  * to read one byte at a time, we read from the address space in chunks.  If the
256  * current address doesn't lie within our buffer range, we read in the chunk
257  * starting from the given address.
258  */
259 static int
260 libdisasm_read(void *data, uint64_t pc, void *buf, size_t buflen)
261 {
262 	dis_buf_t *db = data;
263 	size_t offset;
264 	size_t len;
265 
266 	if (pc - db->db_addr >= db->db_bufsize) {
267 		if (mdb_tgt_aread(db->db_tgt, db->db_as, db->db_buf,
268 		    sizeof (db->db_buf), pc) != -1) {
269 			db->db_bufsize = sizeof (db->db_buf);
270 		} else if (mdb_tgt_aread(db->db_tgt, db->db_as, db->db_buf,
271 		    buflen, pc) != -1) {
272 			db->db_bufsize = buflen;
273 		} else {
274 			if (!db->db_readerr)
275 				mdb_warn("failed to read instruction at %#lr",
276 				    (uintptr_t)pc);
277 			db->db_readerr = B_TRUE;
278 			return (-1);
279 		}
280 		db->db_addr = pc;
281 	}
282 
283 	offset = pc - db->db_addr;
284 
285 	len = MIN(buflen, db->db_bufsize - offset);
286 
287 	(void) memcpy(buf, (char *)db->db_buf + offset, len);
288 	db->db_nextaddr = pc + len;
289 
290 	return (len);
291 }
292 
293 static mdb_tgt_addr_t
294 libdisasm_ins2str(mdb_disasm_t *dp, mdb_tgt_t *t, mdb_tgt_as_t as,
295     char *buf, size_t len, mdb_tgt_addr_t pc)
296 {
297 	dis_handle_t *dhp = dp->dis_data;
298 	dis_buf_t db = { 0 };
299 	const char *p;
300 
301 	/*
302 	 * Set the libdisasm data to point to our buffer.  This will be
303 	 * passed as the first argument to the lookup and read functions.
304 	 */
305 	db.db_tgt = t;
306 	db.db_as = as;
307 
308 	dis_set_data(dhp, &db);
309 
310 	if ((p = mdb_tgt_name(t)) != NULL && strcmp(p, "proc") == 0) {
311 		/* check for ELF ET_REL type; turn on NOIMMSYM if so */
312 
313 		GElf_Ehdr 	leh;
314 
315 		if (mdb_tgt_getxdata(t, "ehdr", &leh, sizeof (leh)) != -1 &&
316 		    leh.e_type == ET_REL)  {
317 			dis_flags_set(dhp, DIS_NOIMMSYM);
318 		} else {
319 			dis_flags_clear(dhp, DIS_NOIMMSYM);
320 		}
321 	}
322 
323 	/*
324 	 * Attempt to disassemble the instruction.  If this fails because of an
325 	 * unknown opcode, drive on anyway.  If it fails because we couldn't
326 	 * read from the target, bail out immediately.
327 	 */
328 	if (dis_disassemble(dhp, pc, buf, len) != 0)
329 		(void) mdb_snprintf(buf, len,
330 		    "***ERROR--unknown op code***");
331 
332 	if (db.db_readerr)
333 		return (pc);
334 
335 	/*
336 	 * Return the updated location
337 	 */
338 	return (db.db_nextaddr);
339 }
340 
341 static mdb_tgt_addr_t
342 libdisasm_previns(mdb_disasm_t *dp, mdb_tgt_t *t, mdb_tgt_as_t as,
343     mdb_tgt_addr_t pc, uint_t n)
344 {
345 	dis_handle_t *dhp = dp->dis_data;
346 	dis_buf_t db = { 0 };
347 
348 	/*
349 	 * Set the libdisasm data to point to our buffer.  This will be
350 	 * passed as the first argument to the lookup and read functions.
351 	 * We set 'readerr' to B_TRUE to turn off the mdb_warn() in
352 	 * libdisasm_read, because the code works by probing backwards until a
353 	 * valid address is found.
354 	 */
355 	db.db_tgt = t;
356 	db.db_as = as;
357 	db.db_readerr = B_TRUE;
358 
359 	dis_set_data(dhp, &db);
360 
361 	return (dis_previnstr(dhp, pc, n));
362 }
363 
364 /*ARGSUSED*/
365 static mdb_tgt_addr_t
366 libdisasm_nextins(mdb_disasm_t *dp, mdb_tgt_t *t, mdb_tgt_as_t as,
367     mdb_tgt_addr_t pc)
368 {
369 	mdb_tgt_addr_t npc;
370 	char c;
371 
372 	if ((npc = libdisasm_ins2str(dp, t, as, &c, 1, pc)) == pc)
373 		return (pc);
374 
375 	/*
376 	 * Probe the address to make sure we can read something from it - we
377 	 * want the address we return to actually contain something.
378 	 */
379 	if (mdb_tgt_aread(t, as, &c, 1, npc) != 1)
380 		return (pc);
381 
382 	return (npc);
383 }
384 
385 static void
386 libdisasm_destroy(mdb_disasm_t *dp)
387 {
388 	dis_handle_t *dhp = dp->dis_data;
389 
390 	dis_handle_destroy(dhp);
391 }
392 
393 static const mdb_dis_ops_t libdisasm_ops = {
394 	libdisasm_destroy,
395 	libdisasm_ins2str,
396 	libdisasm_previns,
397 	libdisasm_nextins
398 };
399 
400 /*
401  * Generic function for creating a libdisasm-backed disassembler.  Creates an
402  * MDB disassembler with the given name backed by libdis with the given flags.
403  */
404 static int
405 libdisasm_create(mdb_disasm_t *dp, const char *name,
406 		const char *desc, int flags)
407 {
408 	if ((dp->dis_data = dis_handle_create(flags, NULL, libdisasm_lookup,
409 	    libdisasm_read)) == NULL)
410 		return (-1);
411 
412 	dp->dis_name = name;
413 	dp->dis_ops = &libdisasm_ops;
414 	dp->dis_desc = desc;
415 
416 	return (0);
417 }
418 
419 
420 #if defined(__i386) || defined(__amd64)
421 static int
422 ia32_create(mdb_disasm_t *dp)
423 {
424 	return (libdisasm_create(dp,
425 	    "ia32",
426 	    "Intel 32-bit disassembler",
427 	    DIS_X86_SIZE32));
428 }
429 #endif
430 
431 #if defined(__amd64)
432 static int
433 amd64_create(mdb_disasm_t *dp)
434 {
435 	return (libdisasm_create(dp,
436 	    "amd64",
437 	    "AMD64 and IA32e 64-bit disassembler",
438 	    DIS_X86_SIZE64));
439 }
440 #endif
441 
442 #if defined(__sparc)
443 static int
444 sparc1_create(mdb_disasm_t *dp)
445 {
446 	return (libdisasm_create(dp,
447 	    "1",
448 	    "SPARC-v8 disassembler",
449 	    DIS_SPARC_V8));
450 }
451 
452 static int
453 sparc2_create(mdb_disasm_t *dp)
454 {
455 	return (libdisasm_create(dp,
456 	    "2",
457 	    "SPARC-v9 disassembler",
458 	    DIS_SPARC_V9));
459 }
460 
461 static int
462 sparc4_create(mdb_disasm_t *dp)
463 {
464 	return (libdisasm_create(dp,
465 	    "4",
466 	    "UltraSPARC1-v9 disassembler",
467 	    DIS_SPARC_V9 | DIS_SPARC_V9_SGI));
468 }
469 
470 static int
471 sparcv8_create(mdb_disasm_t *dp)
472 {
473 	return (libdisasm_create(dp,
474 	    "v8",
475 	    "SPARC-v8 disassembler",
476 	    DIS_SPARC_V8));
477 }
478 
479 static int
480 sparcv9_create(mdb_disasm_t *dp)
481 {
482 	return (libdisasm_create(dp,
483 	    "v9",
484 	    "SPARC-v9 disassembler",
485 	    DIS_SPARC_V9));
486 }
487 
488 static int
489 sparcv9plus_create(mdb_disasm_t *dp)
490 {
491 	return (libdisasm_create(dp,
492 	    "v9plus",
493 	    "UltraSPARC1-v9 disassembler",
494 	    DIS_SPARC_V9 | DIS_SPARC_V9_SGI));
495 }
496 #endif
497 
498 /*ARGSUSED*/
499 static void
500 defdis_destroy(mdb_disasm_t *dp)
501 {
502 	/* Nothing to do here */
503 }
504 
505 /*ARGSUSED*/
506 static mdb_tgt_addr_t
507 defdis_ins2str(mdb_disasm_t *dp, mdb_tgt_t *t, mdb_tgt_as_t as,
508     char *buf, size_t len, mdb_tgt_addr_t addr)
509 {
510 	return (addr);
511 }
512 
513 /*ARGSUSED*/
514 static mdb_tgt_addr_t
515 defdis_previns(mdb_disasm_t *dp, mdb_tgt_t *t, mdb_tgt_as_t as,
516     mdb_tgt_addr_t addr, uint_t n)
517 {
518 	return (addr);
519 }
520 
521 /*ARGSUSED*/
522 static mdb_tgt_addr_t
523 defdis_nextins(mdb_disasm_t *dp, mdb_tgt_t *t, mdb_tgt_as_t as,
524     mdb_tgt_addr_t addr)
525 {
526 	return (addr);
527 }
528 
529 static const mdb_dis_ops_t defdis_ops = {
530 	defdis_destroy,
531 	defdis_ins2str,
532 	defdis_previns,
533 	defdis_nextins
534 };
535 
536 static int
537 defdis_create(mdb_disasm_t *dp)
538 {
539 	dp->dis_name = "default";
540 	dp->dis_desc = "default no-op disassembler";
541 	dp->dis_ops = &defdis_ops;
542 
543 	return (0);
544 }
545 
546 mdb_dis_ctor_f *const mdb_dis_builtins[] = {
547 	defdis_create,
548 #if defined(__amd64)
549 	ia32_create,
550 	amd64_create,
551 #elif defined(__i386)
552 	ia32_create,
553 #elif defined(__sparc)
554 	sparc1_create,
555 	sparc2_create,
556 	sparc4_create,
557 	sparcv8_create,
558 	sparcv9_create,
559 	sparcv9plus_create,
560 #endif
561 	NULL
562 };
563