#include <kmdb/kmdb_dpi_impl.h>
#include <kmdb/kmdb_kdi.h>
#include <kmdb/kmdb_umemglue.h>
#include <kmdb/kaif.h>
#include <kmdb/kmdb_io.h>
#include <kmdb/kaif_start.h>
#include <mdb/mdb_err.h>
#include <mdb/mdb_debug.h>
#include <mdb/mdb_isautil.h>
#include <mdb/mdb_io_impl.h>
#include <mdb/mdb_kreg_impl.h>
#include <mdb/mdb.h>
#include <sys/types.h>
#include <sys/bitmap.h>
#include <sys/termios.h>
#include <sys/kdi_impl.h>
#include <sys/sysmacros.h>
kdi_cpusave_t *kaif_cpusave;
int kaif_ncpusave;
kdi_drreg_t kaif_drreg;
uint32_t kaif_waptmap;
int kaif_trap_switch;
void (*kaif_modchg_cb)(struct modctl *, int);
enum {
M_SYSRET = 0x07,
M_ESC = 0x0f,
M_SYSEXIT = 0x35,
M_REX_LO = 0x40,
M_REX_HI = 0x4f,
M_PUSHF = 0x9c,
M_POPF = 0x9d,
M_INT3 = 0xcc,
M_INTX = 0xcd,
M_INTO = 0xce,
M_IRET = 0xcf,
M_CLI = 0xfa,
M_STI = 0xfb
};
#define KAIF_BREAKPOINT_INSTR M_INT3
#define KAIF_WPPRIV2ID(wp) (int)(uintptr_t)((wp)->wp_priv)
#ifdef __amd64
#define FLAGS_REG_NAME "rflags"
#else
#define FLAGS_REG_NAME "eflags"
#endif
static void
kaif_enter_mon(void)
{
char c;
for (;;) {
mdb_iob_printf(mdb.m_out,
"%s: Do you really want to reboot? (y/n) ",
mdb.m_pname);
mdb_iob_flush(mdb.m_out);
mdb_iob_clearlines(mdb.m_out);
c = kmdb_getchar();
if (c == 'n' || c == 'N' || c == CTRL('c'))
return;
else if (c == 'y' || c == 'Y') {
mdb_iob_printf(mdb.m_out, "Rebooting...\n");
kmdb_dpi_reboot();
}
}
}
static kaif_cpusave_t *
kaif_cpuid2save(int cpuid)
{
kaif_cpusave_t *save;
if (cpuid == DPI_MASTER_CPUID)
return (&kaif_cpusave[kaif_master_cpuid]);
if (cpuid < 0 || cpuid >= kaif_ncpusave) {
(void) set_errno(EINVAL);
return (NULL);
}
save = &kaif_cpusave[cpuid];
if (save->krs_cpu_state != KAIF_CPU_STATE_MASTER &&
save->krs_cpu_state != KAIF_CPU_STATE_SLAVE) {
(void) set_errno(EINVAL);
return (NULL);
}
return (save);
}
static int
kaif_get_cpu_state(int cpuid)
{
kaif_cpusave_t *save;
if ((save = kaif_cpuid2save(cpuid)) == NULL)
return (-1);
switch (save->krs_cpu_state) {
case KAIF_CPU_STATE_MASTER:
return (DPI_CPU_STATE_MASTER);
case KAIF_CPU_STATE_SLAVE:
return (DPI_CPU_STATE_SLAVE);
default:
return (set_errno(EINVAL));
}
}
static int
kaif_get_master_cpuid(void)
{
return (kaif_master_cpuid);
}
static mdb_tgt_gregset_t *
kaif_kdi_to_gregs(int cpuid)
{
kaif_cpusave_t *save;
if ((save = kaif_cpuid2save(cpuid)) == NULL)
return (NULL);
return ((mdb_tgt_gregset_t *)save->krs_gregs);
}
static const mdb_tgt_gregset_t *
kaif_get_gregs(int cpuid)
{
return (kaif_kdi_to_gregs(cpuid));
}
typedef struct kaif_reg_synonyms {
const char *rs_syn;
const char *rs_name;
} kaif_reg_synonyms_t;
static kreg_t *
kaif_find_regp(const char *regname)
{
static const kaif_reg_synonyms_t synonyms[] = {
#ifdef __amd64
{ "pc", "rip" },
{ "sp", "rsp" },
{ "fp", "rbp" },
#else
{ "pc", "eip" },
{ "sp", "esp" },
{ "fp", "ebp" },
#endif
{ "tt", "trapno" }
};
mdb_tgt_gregset_t *regs;
int i;
if ((regs = kaif_kdi_to_gregs(DPI_MASTER_CPUID)) == NULL)
return (NULL);
for (i = 0; i < sizeof (synonyms) / sizeof (synonyms[0]); i++) {
if (strcmp(synonyms[i].rs_syn, regname) == 0)
regname = synonyms[i].rs_name;
}
for (i = 0; mdb_isa_kregs[i].rd_name != NULL; i++) {
const mdb_tgt_regdesc_t *rd = &mdb_isa_kregs[i];
if (strcmp(rd->rd_name, regname) == 0)
return (®s->kregs[rd->rd_num]);
}
(void) set_errno(ENOENT);
return (NULL);
}
static int
kaif_get_register(const char *regname, kreg_t *valp)
{
kreg_t *regp;
if ((regp = kaif_find_regp(regname)) == NULL)
return (-1);
*valp = *regp;
return (0);
}
static int
kaif_set_register(const char *regname, kreg_t val)
{
kreg_t *regp;
if ((regp = kaif_find_regp(regname)) == NULL)
return (-1);
*regp = val;
return (0);
}
static boolean_t
kaif_toxic_text(uintptr_t addr)
{
static GElf_Sym toxic_syms[2] = { 0, };
size_t i;
if (toxic_syms[0].st_name == 0) {
if (mdb_tgt_lookup_by_name(mdb.m_target, MDB_TGT_OBJ_EXEC,
"tr_iret_user", &toxic_syms[0], NULL) != 0)
warn("couldn't find tr_iret_user\n");
if (mdb_tgt_lookup_by_name(mdb.m_target, MDB_TGT_OBJ_EXEC,
"tr_mmu_flush_user_range", &toxic_syms[1], NULL) != 0)
warn("couldn't find tr_mmu_flush_user_range\n");
}
for (i = 0; i < ARRAY_SIZE(toxic_syms); i++) {
if (addr >= toxic_syms[i].st_value &&
addr - toxic_syms[i].st_value < toxic_syms[i].st_size)
return (B_TRUE);
}
return (B_FALSE);
}
static int
kaif_brkpt_arm(uintptr_t addr, mdb_instr_t *instrp)
{
mdb_instr_t bkpt = KAIF_BREAKPOINT_INSTR;
if (kaif_toxic_text(addr)) {
warn("%a cannot be a breakpoint target\n", addr);
return (set_errno(EMDB_TGTNOTSUP));
}
if (mdb_tgt_aread(mdb.m_target, MDB_TGT_AS_VIRT_I, instrp,
sizeof (mdb_instr_t), addr) != sizeof (mdb_instr_t))
return (-1);
if (mdb_tgt_awrite(mdb.m_target, MDB_TGT_AS_VIRT_I, &bkpt,
sizeof (mdb_instr_t), addr) != sizeof (mdb_instr_t))
return (-1);
return (0);
}
static int
kaif_brkpt_disarm(uintptr_t addr, mdb_instr_t instrp)
{
if (mdb_tgt_awrite(mdb.m_target, MDB_TGT_AS_VIRT_I, &instrp,
sizeof (mdb_instr_t), addr) != sizeof (mdb_instr_t))
return (-1);
return (0);
}
#ifdef __amd64
#define WAPT_DATA_MAX_SIZE 8
#define WAPT_DATA_SIZES_MSG "1, 2, 4, or 8"
#else
#define WAPT_DATA_MAX_SIZE 4
#define WAPT_DATA_SIZES_MSG "1, 2, or 4"
#endif
static int
kaif_wapt_validate(kmdb_wapt_t *wp)
{
if (wp->wp_type == DPI_WAPT_TYPE_IO) {
if (wp->wp_wflags != (MDB_TGT_WA_R | MDB_TGT_WA_W)) {
warn("I/O port watchpoints must be read/write\n");
return (set_errno(EINVAL));
}
if (wp->wp_size != 1 && wp->wp_size != 2 && wp->wp_size != 4) {
warn("I/O watchpoint size must be 1, 2, or 4 bytes\n");
return (set_errno(EINVAL));
}
} else if (wp->wp_type == DPI_WAPT_TYPE_PHYS) {
warn("physical address watchpoints are not supported on this "
"platform\n");
return (set_errno(EMDB_TGTHWNOTSUP));
} else {
if (wp->wp_wflags != (MDB_TGT_WA_R | MDB_TGT_WA_W) &&
wp->wp_wflags != MDB_TGT_WA_W) {
warn("watchpoints must be read/write or write-only\n");
return (set_errno(EINVAL));
}
if ((wp->wp_size & -(wp->wp_size)) != wp->wp_size ||
wp->wp_size > WAPT_DATA_MAX_SIZE) {
warn("data watchpoint size must be " WAPT_DATA_SIZES_MSG
" bytes\n");
return (set_errno(EINVAL));
}
}
if (wp->wp_addr & (wp->wp_size - 1)) {
warn("%lu-byte watchpoints must be %lu-byte aligned\n",
(ulong_t)wp->wp_size, (ulong_t)wp->wp_size);
return (set_errno(EINVAL));
}
return (0);
}
static int
kaif_wapt_reserve(kmdb_wapt_t *wp)
{
int id;
for (id = 0; id <= KDI_MAXWPIDX; id++) {
if (!BT_TEST(&kaif_waptmap, id)) {
BT_SET(&kaif_waptmap, id);
wp->wp_priv = (void *)(uintptr_t)id;
return (0);
}
}
return (set_errno(EMDB_WPTOOMANY));
}
static void
kaif_wapt_release(kmdb_wapt_t *wp)
{
int id = KAIF_WPPRIV2ID(wp);
ASSERT(BT_TEST(&kaif_waptmap, id));
BT_CLEAR(&kaif_waptmap, id);
}
static void
kaif_wapt_arm(kmdb_wapt_t *wp)
{
uint_t rw;
int hwid = KAIF_WPPRIV2ID(wp);
ASSERT(BT_TEST(&kaif_waptmap, hwid));
if (wp->wp_type == DPI_WAPT_TYPE_IO)
rw = KREG_DRCTL_WP_IORW;
else if (wp->wp_wflags & MDB_TGT_WA_R)
rw = KREG_DRCTL_WP_RW;
else if (wp->wp_wflags & MDB_TGT_WA_X)
rw = KREG_DRCTL_WP_EXEC;
else
rw = KREG_DRCTL_WP_WONLY;
kaif_drreg.dr_addr[hwid] = wp->wp_addr;
kaif_drreg.dr_ctl &= ~KREG_DRCTL_WP_MASK(hwid);
kaif_drreg.dr_ctl |= KREG_DRCTL_WP_LENRW(hwid, wp->wp_size - 1, rw);
kaif_drreg.dr_ctl |= KREG_DRCTL_WPEN(hwid);
kmdb_kdi_update_drreg(&kaif_drreg);
}
static void
kaif_wapt_disarm(kmdb_wapt_t *wp)
{
int hwid = KAIF_WPPRIV2ID(wp);
ASSERT(BT_TEST(&kaif_waptmap, hwid));
kaif_drreg.dr_addr[hwid] = 0;
kaif_drreg.dr_ctl &= ~(KREG_DRCTL_WP_MASK(hwid) |
KREG_DRCTL_WPEN_MASK(hwid));
kmdb_kdi_update_drreg(&kaif_drreg);
}
static int
kaif_wapt_match(kmdb_wapt_t *wp)
{
int hwid = KAIF_WPPRIV2ID(wp);
uint32_t mask = KREG_DRSTAT_WP_MASK(hwid);
int n = 0;
int i;
ASSERT(BT_TEST(&kaif_waptmap, hwid));
for (i = 0; i < kaif_ncpusave; i++)
n += (kaif_cpusave[i].krs_dr.dr_stat & mask) != 0;
return (n);
}
static int
kaif_step(void)
{
kreg_t pc, fl, oldfl, newfl, sp;
mdb_tgt_addr_t npc;
mdb_instr_t instr;
int emulated = 0, rchk = 0;
size_t pcoff = 0;
(void) kmdb_dpi_get_register("pc", &pc);
if (kaif_toxic_text(pc)) {
warn("%a cannot be stepped\n", pc);
return (set_errno(EMDB_TGTNOTSUP));
}
if ((npc = mdb_dis_nextins(mdb.m_disasm, mdb.m_target,
MDB_TGT_AS_VIRT_I, pc)) == pc) {
warn("failed to decode instruction at %a for step\n", pc);
return (set_errno(EINVAL));
}
do {
if (mdb_tgt_aread(mdb.m_target, MDB_TGT_AS_VIRT_I, &instr,
sizeof (mdb_instr_t), pc + pcoff) != sizeof (mdb_instr_t)) {
warn("failed to read at %p for step",
(void *)(pc + pcoff));
return (-1);
}
} while (pcoff++, (instr >= M_REX_LO && instr <= M_REX_HI && !rchk++));
switch (instr) {
case M_IRET:
warn("iret cannot be stepped\n");
return (set_errno(EMDB_TGTNOTSUP));
case M_INT3:
case M_INTX:
case M_INTO:
warn("int cannot be stepped\n");
return (set_errno(EMDB_TGTNOTSUP));
case M_ESC:
if (mdb_tgt_aread(mdb.m_target, MDB_TGT_AS_VIRT_I, &instr,
sizeof (mdb_instr_t), pc + pcoff) != sizeof (mdb_instr_t)) {
warn("failed to read at %p for step",
(void *)(pc + pcoff));
return (-1);
}
switch (instr) {
case M_SYSRET:
warn("sysret cannot be stepped\n");
return (set_errno(EMDB_TGTNOTSUP));
case M_SYSEXIT:
warn("sysexit cannot be stepped\n");
return (set_errno(EMDB_TGTNOTSUP));
}
break;
case M_CLI:
(void) kmdb_dpi_get_register(FLAGS_REG_NAME, &fl);
fl &= ~KREG_EFLAGS_IF_MASK;
(void) kmdb_dpi_set_register(FLAGS_REG_NAME, fl);
emulated = 1;
break;
case M_STI:
(void) kmdb_dpi_get_register(FLAGS_REG_NAME, &fl);
fl |= (1 << KREG_EFLAGS_IF_SHIFT);
(void) kmdb_dpi_set_register(FLAGS_REG_NAME, fl);
emulated = 1;
break;
case M_POPF:
(void) kmdb_dpi_get_register("sp", &sp);
(void) kmdb_dpi_get_register(FLAGS_REG_NAME, &fl);
if (mdb_tgt_aread(mdb.m_target, MDB_TGT_AS_VIRT_S, &newfl,
sizeof (kreg_t), sp) != sizeof (kreg_t)) {
warn("failed to read " FLAGS_REG_NAME
" at %p for popfl step\n", (void *)sp);
return (set_errno(EMDB_TGTNOTSUP));
}
fl = (fl & ~KREG_EFLAGS_IF_MASK) | KREG_EFLAGS_TF_MASK;
if (mdb_tgt_awrite(mdb.m_target, MDB_TGT_AS_VIRT_S, &fl,
sizeof (kreg_t), sp) != sizeof (kreg_t)) {
warn("failed to update " FLAGS_REG_NAME
" at %p for popfl step\n", (void *)sp);
return (set_errno(EMDB_TGTNOTSUP));
}
break;
}
if (emulated) {
(void) kmdb_dpi_set_register("pc", npc);
return (0);
}
(void) kmdb_dpi_get_register(FLAGS_REG_NAME, &oldfl);
(void) kmdb_dpi_set_register(FLAGS_REG_NAME,
((oldfl | (1 << KREG_EFLAGS_TF_SHIFT)) & ~KREG_EFLAGS_IF_MASK));
kmdb_dpi_resume_master();
switch (instr) {
case M_POPF:
(void) kmdb_dpi_set_register(FLAGS_REG_NAME, newfl);
return (0);
case M_PUSHF:
(void) kmdb_dpi_get_register("sp", &sp);
if (mdb_tgt_awrite(mdb.m_target, MDB_TGT_AS_VIRT_S, &oldfl,
sizeof (kreg_t), sp) != sizeof (kreg_t)) {
warn("failed to update pushed " FLAGS_REG_NAME
" at %p after pushfl step\n", (void *)sp);
return (set_errno(EMDB_TGTNOTSUP));
}
(void) kmdb_dpi_set_register(FLAGS_REG_NAME, oldfl);
return (0);
default:
(void) kmdb_dpi_get_register(FLAGS_REG_NAME, &fl);
(void) kmdb_dpi_set_register(FLAGS_REG_NAME,
((fl & ~(KREG_EFLAGS_TF_MASK|KREG_EFLAGS_IF_MASK)) |
(oldfl & KREG_EFLAGS_IF_MASK)));
return (0);
}
}
static uintptr_t
kaif_call(uintptr_t funcva, uint_t argc, const uintptr_t argv[])
{
return (kaif_invoke(funcva, argc, argv));
}
static void
dump_crumb(kdi_crumb_t *krmp)
{
kdi_crumb_t krm;
if (mdb_vread(&krm, sizeof (kdi_crumb_t), (uintptr_t)krmp) !=
sizeof (kdi_crumb_t)) {
warn("failed to read crumb at %p", krmp);
return;
}
mdb_printf("state: ");
switch (krm.krm_cpu_state) {
case KAIF_CPU_STATE_MASTER:
mdb_printf("M");
break;
case KAIF_CPU_STATE_SLAVE:
mdb_printf("S");
break;
default:
mdb_printf("%d", krm.krm_cpu_state);
}
mdb_printf(" trapno %3d sp %08x flag %d pc %p %A\n",
krm.krm_trapno, krm.krm_sp, krm.krm_flag, krm.krm_pc, krm.krm_pc);
}
static void
dump_crumbs(kaif_cpusave_t *save)
{
int i;
for (i = KDI_NCRUMBS; i > 0; i--) {
uint_t idx = (save->krs_curcrumbidx + i) % KDI_NCRUMBS;
dump_crumb(&save->krs_crumbs[idx]);
}
}
static void
kaif_dump_crumbs(uintptr_t addr, int cpuid)
{
int i;
if (addr != 0) {
dump_crumb((kdi_crumb_t *)addr);
} else if (cpuid != -1) {
if (cpuid < 0 || cpuid >= kaif_ncpusave)
return;
dump_crumbs(&kaif_cpusave[cpuid]);
} else {
for (i = 0; i < kaif_ncpusave; i++) {
kaif_cpusave_t *save = &kaif_cpusave[i];
if (save->krs_cpu_state == KAIF_CPU_STATE_NONE)
continue;
mdb_printf("%sCPU %d crumbs: (curidx %d)\n",
(i == 0 ? "" : "\n"), i, save->krs_curcrumbidx);
dump_crumbs(save);
}
}
}
static void
kaif_modchg_register(void (*func)(struct modctl *, int))
{
kaif_modchg_cb = func;
}
static void
kaif_modchg_cancel(void)
{
ASSERT(kaif_modchg_cb != NULL);
kaif_modchg_cb = NULL;
}
void
kaif_trap_set_debugger(void)
{
kmdb_kdi_idt_switch(NULL);
}
void
kaif_trap_set_saved(kaif_cpusave_t *cpusave)
{
kmdb_kdi_idt_switch(cpusave);
}
static void
kaif_vmready(void)
{
}
void
kaif_memavail(caddr_t base, size_t len)
{
int ret;
kmdb_kdi_memrange_add(base, len);
ret = mdb_umem_add(base, len);
ASSERT(ret == 0);
}
void
kaif_mod_loaded(struct modctl *modp)
{
if (kaif_modchg_cb != NULL)
kaif_modchg_cb(modp, 1);
}
void
kaif_mod_unloading(struct modctl *modp)
{
if (kaif_modchg_cb != NULL)
kaif_modchg_cb(modp, 0);
}
void
kaif_handle_fault(greg_t trapno, greg_t pc, greg_t sp, int cpuid)
{
kmdb_dpi_handle_fault((kreg_t)trapno, (kreg_t)pc,
(kreg_t)sp, cpuid);
}
static kdi_debugvec_t kaif_dvec = {
NULL,
NULL,
NULL,
NULL,
kaif_vmready,
kaif_memavail,
kaif_mod_loaded,
kaif_mod_unloading,
kaif_handle_fault
};
void
kaif_kdi_entry(kdi_cpusave_t *cpusave)
{
int ret = kaif_main_loop(cpusave);
ASSERT(ret == KAIF_CPU_CMD_RESUME ||
ret == KAIF_CPU_CMD_RESUME_MASTER);
}
void
kaif_activate(kdi_debugvec_t **dvecp, uint_t flags)
{
kmdb_kdi_activate(kaif_kdi_entry, kaif_cpusave, kaif_ncpusave);
*dvecp = &kaif_dvec;
}
static int
kaif_init(kmdb_auxv_t *kav)
{
kaif_cpusave = mdb_zalloc(sizeof (kaif_cpusave_t) * kav->kav_ncpu,
UM_SLEEP);
kaif_ncpusave = kav->kav_ncpu;
kaif_modchg_cb = NULL;
kaif_waptmap = 0;
kaif_trap_switch = (kav->kav_flags & KMDB_AUXV_FL_NOTRPSWTCH) == 0;
return (0);
}
dpi_ops_t kmdb_dpi_ops = {
.dpo_init = kaif_init,
.dpo_debugger_activate = kaif_activate,
.dpo_debugger_deactivate = kmdb_kdi_deactivate,
.dpo_enter_mon = kaif_enter_mon,
.dpo_modchg_register = kaif_modchg_register,
.dpo_modchg_cancel = kaif_modchg_cancel,
.dpo_get_cpu_state = kaif_get_cpu_state,
.dpo_get_master_cpuid = kaif_get_master_cpuid,
.dpo_get_gregs = kaif_get_gregs,
.dpo_get_register = kaif_get_register,
.dpo_set_register = kaif_set_register,
.dpo_brkpt_arm = kaif_brkpt_arm,
.dpo_brkpt_disarm = kaif_brkpt_disarm,
.dpo_wapt_validate = kaif_wapt_validate,
.dpo_wapt_reserve = kaif_wapt_reserve,
.dpo_wapt_release = kaif_wapt_release,
.dpo_wapt_arm = kaif_wapt_arm,
.dpo_wapt_disarm = kaif_wapt_disarm,
.dpo_wapt_match = kaif_wapt_match,
.dpo_step = kaif_step,
.dpo_call = kaif_call,
.dpo_dump_crumbs = kaif_dump_crumbs,
};