mdb_disasm.c revision 80148899834a4078a2bd348504aa2d6de9752837
/*
* 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 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#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);
(void) 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 };
const char *p;
/*
* 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 ((p = mdb_tgt_name(t)) != NULL && strcmp(p, "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
};