Home | History | Annotate | Download | only in mdb
      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