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