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