view usr/src/cmd/mdb/common/mdb/mdb_disasm.c @ 3892:55e05ad4374a

6448268 dis: 6427698 introduced a bunch of meaningless spew when disassembling .o files
author dmick
date Fri, 23 Mar 2007 19:23:04 -0700
parents 7a50156b9dc0
children dabad3a01e08
line wrap: on
line source

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <mdb/mdb_disasm_impl.h>
#include <mdb/mdb_modapi.h>
#include <mdb/mdb_string.h>
#include <mdb/mdb_debug.h>
#include <mdb/mdb_err.h>
#include <mdb/mdb_nv.h>
#include <mdb/mdb.h>

#include <libdisasm.h>

int
mdb_dis_select(const char *name)
{
	mdb_var_t *v = mdb_nv_lookup(&mdb.m_disasms, name);

	if (v != NULL) {
		mdb.m_disasm = mdb_nv_get_cookie(v);
		return (0);
	}

	if (mdb.m_target == NULL) {
		if (mdb.m_defdisasm != NULL)
			strfree(mdb.m_defdisasm);
		mdb.m_defdisasm = strdup(name);
		return (0);
	}

	return (set_errno(EMDB_NODIS));
}

mdb_disasm_t *
mdb_dis_create(mdb_dis_ctor_f *ctor)
{
	mdb_disasm_t *dp = mdb_zalloc(sizeof (mdb_disasm_t), UM_SLEEP);

	if ((dp->dis_module = mdb.m_lmod) == NULL)
		dp->dis_module = &mdb.m_rmod;

	if (ctor(dp) == 0) {
		mdb_var_t *v = mdb_nv_lookup(&mdb.m_disasms, dp->dis_name);

		if (v != NULL) {
			dp->dis_ops->dis_destroy(dp);
			mdb_free(dp, sizeof (mdb_disasm_t));
			(void) set_errno(EMDB_DISEXISTS);
			return (NULL);
		}

		(void) mdb_nv_insert(&mdb.m_disasms, dp->dis_name, NULL,
		    (uintptr_t)dp, MDB_NV_RDONLY | MDB_NV_SILENT);

		if (mdb.m_disasm == NULL) {
			mdb.m_disasm = dp;
		} else if (mdb.m_defdisasm != NULL &&
		    strcmp(mdb.m_defdisasm, dp->dis_name) == 0) {
			mdb.m_disasm = dp;
			strfree(mdb.m_defdisasm);
			mdb.m_defdisasm = NULL;
		}

		return (dp);
	}

	mdb_free(dp, sizeof (mdb_disasm_t));
	return (NULL);
}

void
mdb_dis_destroy(mdb_disasm_t *dp)
{
	mdb_var_t *v = mdb_nv_lookup(&mdb.m_disasms, dp->dis_name);

	ASSERT(v != NULL);
	mdb_nv_remove(&mdb.m_disasms, v);
	dp->dis_ops->dis_destroy(dp);
	mdb_free(dp, sizeof (mdb_disasm_t));

	if (mdb.m_disasm == dp)
		(void) mdb_dis_select("default");
}

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)
{
	return (dp->dis_ops->dis_ins2str(dp, t, as, buf, len, addr));
}

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)
{
	return (dp->dis_ops->dis_previns(dp, t, as, addr, n));
}

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)
{
	return (dp->dis_ops->dis_nextins(dp, t, as, addr));
}

/*ARGSUSED*/
int
cmd_dismode(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
	if ((flags & DCMD_ADDRSPEC) || argc > 1)
		return (DCMD_USAGE);

	if (argc != 0) {
		const char *name;

		if (argv->a_type == MDB_TYPE_STRING)
			name = argv->a_un.a_str;
		else
			name = numtostr(argv->a_un.a_val, 10, NTOS_UNSIGNED);

		if (mdb_dis_select(name) == -1) {
			warn("failed to set disassembly mode");
			return (DCMD_ERR);
		}
	}

	mdb_printf("disassembly mode is %s (%s)\n",
	    mdb.m_disasm->dis_name, mdb.m_disasm->dis_desc);

	return (DCMD_OK);
}

/*ARGSUSED*/
static int
print_dis(mdb_var_t *v, void *ignore)
{
	mdb_disasm_t *dp = mdb_nv_get_cookie(v);

	mdb_printf("%-24s - %s\n", dp->dis_name, dp->dis_desc);
	return (0);
}

/*ARGSUSED*/
int
cmd_disasms(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
	if ((flags & DCMD_ADDRSPEC) || argc != 0)
		return (DCMD_USAGE);

	mdb_nv_sort_iter(&mdb.m_disasms, print_dis, NULL, UM_SLEEP | UM_GC);
	return (DCMD_OK);
}

/*
 * Generic libdisasm disassembler interfaces.
 */

#define	DISBUFSZ	64

/*
 * Internal structure used by the read and lookup routines.
 */
typedef struct dis_buf {
	mdb_tgt_t	*db_tgt;
	mdb_tgt_as_t	db_as;
	mdb_tgt_addr_t	db_addr;
	mdb_tgt_addr_t	db_nextaddr;
	uchar_t		db_buf[DISBUFSZ];
	ssize_t		db_bufsize;
	boolean_t	db_readerr;
} dis_buf_t;

/*
 * Disassembler support routine for lookup up an address.  Rely on mdb's "%a"
 * qualifier to convert the address to a symbol.
 */
/*ARGSUSED*/
static int
libdisasm_lookup(void *data, uint64_t addr, char *buf, size_t buflen,
    uint64_t *start, size_t *len)
{
	char c;
	GElf_Sym sym;

	if (buf != NULL) {
#ifdef __sparc
		uint32_t instr[3];
		uint32_t dtrace_id;

		/*
		 * On SPARC, DTrace FBT trampoline entries have a sethi/or pair
		 * that indicates the dtrace probe id; this may appear as the
		 * first two instructions or one instruction into the
		 * trampoline.
		 */
		if (mdb_vread(instr, sizeof (instr), (uintptr_t)addr) ==
		    sizeof (instr)) {
			if ((instr[0] & 0xfffc0000) == 0x11000000 &&
			    (instr[1] & 0xffffe000) == 0x90122000) {
				dtrace_id = (instr[0] << 10) |
				    (instr[1] & 0x1fff);
				(void) mdb_snprintf(buf, sizeof (buf), "dt=%#x",
				    dtrace_id);
				goto out;
			} else if ((instr[1] & 0xfffc0000) == 0x11000000 &&
			    (instr[2] & 0xffffe000) == 0x90122000) {
				dtrace_id = (instr[1] << 10) |
				    (instr[2] & 0x1fff);
				(void) mdb_snprintf(buf, sizeof (buf), "dt=%#x",
				    dtrace_id);
				goto out;
			}
		}
#endif
		(void) mdb_snprintf(buf, buflen, "%a", (uintptr_t)addr);
	}

#ifdef __sparc
out:
#endif
	if (mdb_lookup_by_addr(addr, MDB_SYM_FUZZY, &c, 1, &sym) < 0)
		return (-1);
	if (start != NULL)
		*start = sym.st_value;
	if (len != NULL)
		*len = sym.st_size;

	return (0);
}

/*
 * Disassembler support routine for reading from the target.  Rather than having
 * to read one byte at a time, we read from the address space in chunks.  If the
 * current address doesn't lie within our buffer range, we read in the chunk
 * starting from the given address.
 */
static int
libdisasm_read(void *data, uint64_t pc, void *buf, size_t buflen)
{
	dis_buf_t *db = data;
	size_t offset;
	size_t len;

	if (pc - db->db_addr >= db->db_bufsize) {
		if (mdb_tgt_aread(db->db_tgt, db->db_as, db->db_buf,
		    sizeof (db->db_buf), pc) != -1) {
			db->db_bufsize = sizeof (db->db_buf);
		} else if (mdb_tgt_aread(db->db_tgt, db->db_as, db->db_buf,
		    buflen, pc) != -1) {
			db->db_bufsize = buflen;
		} else {
			if (!db->db_readerr)
				mdb_warn("failed to read instruction at %#lr",
				    (uintptr_t)pc);
			db->db_readerr = B_TRUE;
			return (-1);
		}
		db->db_addr = pc;
	}

	offset = pc - db->db_addr;

	len = MIN(buflen, db->db_bufsize - offset);

	memcpy(buf, (char *)db->db_buf + offset, len);
	db->db_nextaddr = pc + len;

	return (len);
}

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)
{
	dis_handle_t *dhp = dp->dis_data;
	dis_buf_t db = { 0 };

	/*
	 * Set the libdisasm data to point to our buffer.  This will be
	 * passed as the first argument to the lookup and read functions.
	 */
	db.db_tgt = t;
	db.db_as = as;

	dis_set_data(dhp, &db);

	if (strcmp(mdb_tgt_name(t), "proc") == 0) {
		/* check for ELF ET_REL type; turn on NOIMMSYM if so */

		GElf_Ehdr 	leh;

		if (mdb_tgt_getxdata(t, "ehdr", &leh, sizeof (leh)) != -1 &&
		    leh.e_type == ET_REL)  {
			dis_flags_set(dhp, DIS_NOIMMSYM);
		} else {
			dis_flags_clear(dhp, DIS_NOIMMSYM);
		}
	}

	/*
	 * Attempt to disassemble the instruction.  If this fails because of an
	 * unknown opcode, drive on anyway.  If it fails because we couldn't
	 * read from the target, bail out immediately.
	 */
	if (dis_disassemble(dhp, pc, buf, len) != 0)
		(void) mdb_snprintf(buf, len,
		    "***ERROR--unknown op code***");

	if (db.db_readerr)
		return (pc);

	/*
	 * Return the updated location
	 */
	return (db.db_nextaddr);
}

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)
{
	dis_handle_t *dhp = dp->dis_data;
	dis_buf_t db = { 0 };

	/*
	 * Set the libdisasm data to point to our buffer.  This will be
	 * passed as the first argument to the lookup and read functions.
	 * We set 'readerr' to B_TRUE to turn off the mdb_warn() in
	 * libdisasm_read, because the code works by probing backwards until a
	 * valid address is found.
	 */
	db.db_tgt = t;
	db.db_as = as;
	db.db_readerr = B_TRUE;

	dis_set_data(dhp, &db);

	return (dis_previnstr(dhp, pc, n));
}

/*ARGSUSED*/
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)
{
	mdb_tgt_addr_t npc;
	char c;

	if ((npc = libdisasm_ins2str(dp, t, as, &c, 1, pc)) == pc)
		return (pc);

	/*
	 * Probe the address to make sure we can read something from it - we
	 * want the address we return to actually contain something.
	 */
	if (mdb_tgt_aread(t, as, &c, 1, npc) != 1)
		return (pc);

	return (npc);
}

static void
libdisasm_destroy(mdb_disasm_t *dp)
{
	dis_handle_t *dhp = dp->dis_data;

	dis_handle_destroy(dhp);
}

static const mdb_dis_ops_t libdisasm_ops = {
	libdisasm_destroy,
	libdisasm_ins2str,
	libdisasm_previns,
	libdisasm_nextins
};

/*
 * Generic function for creating a libdisasm-backed disassembler.  Creates an
 * MDB disassembler with the given name backed by libdis with the given flags.
 */
static int
libdisasm_create(mdb_disasm_t *dp, const char *name,
		const char *desc, int flags)
{
	if ((dp->dis_data = dis_handle_create(flags, NULL, libdisasm_lookup,
	    libdisasm_read)) == NULL)
		return (-1);

	dp->dis_name = name;
	dp->dis_ops = &libdisasm_ops;
	dp->dis_desc = desc;

	return (0);
}


#if defined(__i386) || defined(__amd64)
static int
ia32_create(mdb_disasm_t *dp)
{
	return (libdisasm_create(dp,
	    "ia32",
	    "Intel 32-bit disassembler",
	    DIS_X86_SIZE32));
}
#endif

#if defined(__amd64)
static int
amd64_create(mdb_disasm_t *dp)
{
	return (libdisasm_create(dp,
	    "amd64",
	    "AMD64 and IA32e 64-bit disassembler",
	    DIS_X86_SIZE64));
}
#endif

#if defined(__sparc)
static int
sparc1_create(mdb_disasm_t *dp)
{
	return (libdisasm_create(dp,
	    "1",
	    "SPARC-v8 disassembler",
	    DIS_SPARC_V8));
}

static int
sparc2_create(mdb_disasm_t *dp)
{
	return (libdisasm_create(dp,
	    "2",
	    "SPARC-v9 disassembler",
	    DIS_SPARC_V9));
}

static int
sparc4_create(mdb_disasm_t *dp)
{
	return (libdisasm_create(dp,
	    "4",
	    "UltraSPARC1-v9 disassembler",
	    DIS_SPARC_V9 | DIS_SPARC_V9_SGI));
}

static int
sparcv8_create(mdb_disasm_t *dp)
{
	return (libdisasm_create(dp,
	    "v8",
	    "SPARC-v8 disassembler",
	    DIS_SPARC_V8));
}

static int
sparcv9_create(mdb_disasm_t *dp)
{
	return (libdisasm_create(dp,
	    "v9",
	    "SPARC-v9 disassembler",
	    DIS_SPARC_V9));
}

static int
sparcv9plus_create(mdb_disasm_t *dp)
{
	return (libdisasm_create(dp,
	    "v9plus",
	    "UltraSPARC1-v9 disassembler",
	    DIS_SPARC_V9 | DIS_SPARC_V9_SGI));
}
#endif

/*ARGSUSED*/
static void
defdis_destroy(mdb_disasm_t *dp)
{
	/* Nothing to do here */
}

/*ARGSUSED*/
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)
{
	return (addr);
}

/*ARGSUSED*/
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)
{
	return (addr);
}

/*ARGSUSED*/
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)
{
	return (addr);
}

static const mdb_dis_ops_t defdis_ops = {
	defdis_destroy,
	defdis_ins2str,
	defdis_previns,
	defdis_nextins
};

static int
defdis_create(mdb_disasm_t *dp)
{
	dp->dis_name = "default";
	dp->dis_desc = "default no-op disassembler";
	dp->dis_ops = &defdis_ops;

	return (0);
}

mdb_dis_ctor_f *const mdb_dis_builtins[] = {
	defdis_create,
#if defined(__amd64)
	ia32_create,
	amd64_create,
#elif defined(__i386)
	ia32_create,
#elif defined(__sparc)
	sparc1_create,
	sparc2_create,
	sparc4_create,
	sparcv8_create,
	sparcv9_create,
	sparcv9plus_create,
#endif
	NULL
};