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