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