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