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
mdb_dis_select(const char * name)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 *
mdb_dis_create(mdb_dis_ctor_f * ctor)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
mdb_dis_destroy(mdb_disasm_t * dp)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
mdb_dis_ins2str(mdb_disasm_t * dp,mdb_tgt_t * t,mdb_tgt_as_t as,char * buf,size_t len,mdb_tgt_addr_t addr)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
mdb_dis_previns(mdb_disasm_t * dp,mdb_tgt_t * t,mdb_tgt_as_t as,mdb_tgt_addr_t addr,uint_t n)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
mdb_dis_nextins(mdb_disasm_t * dp,mdb_tgt_t * t,mdb_tgt_as_t as,mdb_tgt_addr_t addr)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
cmd_dismode(uintptr_t addr,uint_t flags,int argc,const mdb_arg_t * argv)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
print_dis(mdb_var_t * v,void * ignore)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
cmd_disasms(uintptr_t addr,uint_t flags,int argc,const mdb_arg_t * argv)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
libdisasm_lookup(void * data,uint64_t addr,char * buf,size_t buflen,uint64_t * start,size_t * len)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
libdisasm_read(void * data,uint64_t pc,void * buf,size_t buflen)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
libdisasm_ins2str(mdb_disasm_t * dp,mdb_tgt_t * t,mdb_tgt_as_t as,char * buf,size_t len,mdb_tgt_addr_t pc)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 GElf_Ehdr leh;
316
317 if (mdb_tgt_getxdata(t, "ehdr", &leh, sizeof (leh)) != -1 &&
318 leh.e_type == ET_REL) {
319 dis_flags_set(dhp, DIS_NOIMMSYM);
320 } else {
321 dis_flags_clear(dhp, DIS_NOIMMSYM);
322 }
323 }
324
325 /*
326 * Attempt to disassemble the instruction. If this fails because of an
327 * unknown opcode, drive on anyway. If it fails because we couldn't
328 * read from the target, bail out immediately.
329 */
330 if (dis_disassemble(dhp, pc, buf, len) != 0)
331 (void) mdb_snprintf(buf, len,
332 "***ERROR--unknown op code***");
333
334 if (db.db_readerr)
335 return (pc);
336
337 /*
338 * Return the updated location
339 */
340 return (db.db_nextaddr);
341 }
342
343 static mdb_tgt_addr_t
libdisasm_previns(mdb_disasm_t * dp,mdb_tgt_t * t,mdb_tgt_as_t as,mdb_tgt_addr_t pc,uint_t n)344 libdisasm_previns(mdb_disasm_t *dp, mdb_tgt_t *t, mdb_tgt_as_t as,
345 mdb_tgt_addr_t pc, uint_t n)
346 {
347 dis_handle_t *dhp = dp->dis_data;
348 dis_buf_t db = { 0 };
349
350 /*
351 * Set the libdisasm data to point to our buffer. This will be
352 * passed as the first argument to the lookup and read functions.
353 * We set 'readerr' to B_TRUE to turn off the mdb_warn() in
354 * libdisasm_read, because the code works by probing backwards until a
355 * valid address is found.
356 */
357 db.db_tgt = t;
358 db.db_as = as;
359 db.db_readerr = B_TRUE;
360
361 dis_set_data(dhp, &db);
362
363 return (dis_previnstr(dhp, pc, n));
364 }
365
366 /*ARGSUSED*/
367 static mdb_tgt_addr_t
libdisasm_nextins(mdb_disasm_t * dp,mdb_tgt_t * t,mdb_tgt_as_t as,mdb_tgt_addr_t pc)368 libdisasm_nextins(mdb_disasm_t *dp, mdb_tgt_t *t, mdb_tgt_as_t as,
369 mdb_tgt_addr_t pc)
370 {
371 mdb_tgt_addr_t npc;
372 char c;
373
374 if ((npc = libdisasm_ins2str(dp, t, as, &c, 1, pc)) == pc)
375 return (pc);
376
377 /*
378 * Probe the address to make sure we can read something from it - we
379 * want the address we return to actually contain something.
380 */
381 if (mdb_tgt_aread(t, as, &c, 1, npc) != 1)
382 return (pc);
383
384 return (npc);
385 }
386
387 static void
libdisasm_destroy(mdb_disasm_t * dp)388 libdisasm_destroy(mdb_disasm_t *dp)
389 {
390 dis_handle_t *dhp = dp->dis_data;
391
392 dis_handle_destroy(dhp);
393 }
394
395 static const mdb_dis_ops_t libdisasm_ops = {
396 .dis_destroy = libdisasm_destroy,
397 .dis_ins2str = libdisasm_ins2str,
398 .dis_previns = libdisasm_previns,
399 .dis_nextins = libdisasm_nextins
400 };
401
402 /*
403 * Generic function for creating a libdisasm-backed disassembler. Creates an
404 * MDB disassembler with the given name backed by libdis with the given flags.
405 */
406 static int
libdisasm_create(mdb_disasm_t * dp,const char * name,const char * desc,int flags)407 libdisasm_create(mdb_disasm_t *dp, const char *name,
408 const char *desc, int flags)
409 {
410 if ((dp->dis_data = dis_handle_create(flags, NULL, libdisasm_lookup,
411 libdisasm_read)) == NULL)
412 return (-1);
413
414 dp->dis_name = name;
415 dp->dis_ops = &libdisasm_ops;
416 dp->dis_desc = desc;
417
418 return (0);
419 }
420
421 #if defined(__i386) || defined(__amd64)
422 static int
ia16_create(mdb_disasm_t * dp)423 ia16_create(mdb_disasm_t *dp)
424 {
425 return (libdisasm_create(dp,
426 "ia16",
427 "Intel 16-bit disassembler",
428 DIS_X86_SIZE16));
429 }
430
431 static int
ia32_create(mdb_disasm_t * dp)432 ia32_create(mdb_disasm_t *dp)
433 {
434 return (libdisasm_create(dp,
435 "ia32",
436 "Intel 32-bit disassembler",
437 DIS_X86_SIZE32));
438 }
439 #endif
440
441 #if defined(__amd64)
442 static int
amd64_create(mdb_disasm_t * dp)443 amd64_create(mdb_disasm_t *dp)
444 {
445 return (libdisasm_create(dp,
446 "amd64",
447 "AMD64 and IA32e 64-bit disassembler",
448 DIS_X86_SIZE64));
449 }
450 #endif
451
452 #if defined(__sparc)
453 static int
sparc1_create(mdb_disasm_t * dp)454 sparc1_create(mdb_disasm_t *dp)
455 {
456 return (libdisasm_create(dp,
457 "1",
458 "SPARC-v8 disassembler",
459 DIS_SPARC_V8));
460 }
461
462 static int
sparc2_create(mdb_disasm_t * dp)463 sparc2_create(mdb_disasm_t *dp)
464 {
465 return (libdisasm_create(dp,
466 "2",
467 "SPARC-v9 disassembler",
468 DIS_SPARC_V9));
469 }
470
471 static int
sparc4_create(mdb_disasm_t * dp)472 sparc4_create(mdb_disasm_t *dp)
473 {
474 return (libdisasm_create(dp,
475 "4",
476 "UltraSPARC1-v9 disassembler",
477 DIS_SPARC_V9 | DIS_SPARC_V9_SGI));
478 }
479
480 static int
sparcv8_create(mdb_disasm_t * dp)481 sparcv8_create(mdb_disasm_t *dp)
482 {
483 return (libdisasm_create(dp,
484 "v8",
485 "SPARC-v8 disassembler",
486 DIS_SPARC_V8));
487 }
488
489 static int
sparcv9_create(mdb_disasm_t * dp)490 sparcv9_create(mdb_disasm_t *dp)
491 {
492 return (libdisasm_create(dp,
493 "v9",
494 "SPARC-v9 disassembler",
495 DIS_SPARC_V9));
496 }
497
498 static int
sparcv9plus_create(mdb_disasm_t * dp)499 sparcv9plus_create(mdb_disasm_t *dp)
500 {
501 return (libdisasm_create(dp,
502 "v9plus",
503 "UltraSPARC1-v9 disassembler",
504 DIS_SPARC_V9 | DIS_SPARC_V9_SGI));
505 }
506 #endif
507
508 /*ARGSUSED*/
509 static void
defdis_destroy(mdb_disasm_t * dp)510 defdis_destroy(mdb_disasm_t *dp)
511 {
512 /* Nothing to do here */
513 }
514
515 /*ARGSUSED*/
516 static mdb_tgt_addr_t
defdis_ins2str(mdb_disasm_t * dp,mdb_tgt_t * t,mdb_tgt_as_t as,char * buf,size_t len,mdb_tgt_addr_t addr)517 defdis_ins2str(mdb_disasm_t *dp, mdb_tgt_t *t, mdb_tgt_as_t as,
518 char *buf, size_t len, mdb_tgt_addr_t addr)
519 {
520 return (addr);
521 }
522
523 /*ARGSUSED*/
524 static mdb_tgt_addr_t
defdis_previns(mdb_disasm_t * dp,mdb_tgt_t * t,mdb_tgt_as_t as,mdb_tgt_addr_t addr,uint_t n)525 defdis_previns(mdb_disasm_t *dp, mdb_tgt_t *t, mdb_tgt_as_t as,
526 mdb_tgt_addr_t addr, uint_t n)
527 {
528 return (addr);
529 }
530
531 /*ARGSUSED*/
532 static mdb_tgt_addr_t
defdis_nextins(mdb_disasm_t * dp,mdb_tgt_t * t,mdb_tgt_as_t as,mdb_tgt_addr_t addr)533 defdis_nextins(mdb_disasm_t *dp, mdb_tgt_t *t, mdb_tgt_as_t as,
534 mdb_tgt_addr_t addr)
535 {
536 return (addr);
537 }
538
539 static const mdb_dis_ops_t defdis_ops = {
540 .dis_destroy = defdis_destroy,
541 .dis_ins2str = defdis_ins2str,
542 .dis_previns = defdis_previns,
543 .dis_nextins = defdis_nextins,
544 };
545
546 static int
defdis_create(mdb_disasm_t * dp)547 defdis_create(mdb_disasm_t *dp)
548 {
549 dp->dis_name = "default";
550 dp->dis_desc = "default no-op disassembler";
551 dp->dis_ops = &defdis_ops;
552
553 return (0);
554 }
555
556 mdb_dis_ctor_f *const mdb_dis_builtins[] = {
557 defdis_create,
558 #if defined(__amd64)
559 ia16_create,
560 ia32_create,
561 amd64_create,
562 #elif defined(__i386)
563 ia16_create,
564 ia32_create,
565 #elif defined(__sparc)
566 sparc1_create,
567 sparc2_create,
568 sparc4_create,
569 sparcv8_create,
570 sparcv9_create,
571 sparcv9plus_create,
572 #endif
573 NULL
574 };
575