/* * 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. */ /* * Copyright 2011 Joyent, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include 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 = { .dis_destroy = libdisasm_destroy, .dis_ins2str = libdisasm_ins2str, .dis_previns = libdisasm_previns, .dis_nextins = 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 ia16_create(mdb_disasm_t *dp) { return (libdisasm_create(dp, "ia16", "Intel 16-bit disassembler", DIS_X86_SIZE16)); } 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 = { .dis_destroy = defdis_destroy, .dis_ins2str = defdis_ins2str, .dis_previns = defdis_previns, .dis_nextins = 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) ia16_create, ia32_create, amd64_create, #elif defined(__i386) ia16_create, ia32_create, #elif defined(__sparc) sparc1_create, sparc2_create, sparc4_create, sparcv8_create, sparcv9_create, sparcv9plus_create, #endif NULL };