x86: Debug register emulation (Jan Kiszka)
Built on top of previously enhanced breakpoint/watchpoint support, this patch adds full debug register emulation for the x86 architecture. Many corner cases were considered, and the result was successfully tested inside a Linux guest with gdb, but I won't be surprised if one or two scenarios still behave differently in reality. Signed-off-by: Jan Kiszka <jan.kiszka@siemens.com> Signed-off-by: Anthony Liguori <aliguori@us.ibm.com> git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@5747 c046a42c-6fe2-441c-8c8c-71466251a162
This commit is contained in:
parent
2dc9f4117c
commit
01df040b52
@ -403,7 +403,7 @@ void cpu_loop(CPUX86State *env)
|
||||
queue_signal(env, info.si_signo, &info);
|
||||
}
|
||||
break;
|
||||
case EXCP01_SSTP:
|
||||
case EXCP01_DB:
|
||||
case EXCP03_INT3:
|
||||
#ifndef TARGET_X86_64
|
||||
if (env->eflags & VM_MASK) {
|
||||
@ -413,7 +413,7 @@ void cpu_loop(CPUX86State *env)
|
||||
{
|
||||
info.si_signo = SIGTRAP;
|
||||
info.si_errno = 0;
|
||||
if (trapnr == EXCP01_SSTP) {
|
||||
if (trapnr == EXCP01_DB) {
|
||||
info.si_code = TARGET_TRAP_BRKPT;
|
||||
info._sifields._sigfault._addr = env->eip;
|
||||
} else {
|
||||
|
@ -205,6 +205,16 @@
|
||||
#define CR4_OSFXSR_MASK (1 << CR4_OSFXSR_SHIFT)
|
||||
#define CR4_OSXMMEXCPT_MASK (1 << 10)
|
||||
|
||||
#define DR6_BD (1 << 13)
|
||||
#define DR6_BS (1 << 14)
|
||||
#define DR6_BT (1 << 15)
|
||||
#define DR6_FIXED_1 0xffff0ff0
|
||||
|
||||
#define DR7_GD (1 << 13)
|
||||
#define DR7_TYPE_SHIFT 16
|
||||
#define DR7_LEN_SHIFT 18
|
||||
#define DR7_FIXED_1 0x00000400
|
||||
|
||||
#define PG_PRESENT_BIT 0
|
||||
#define PG_RW_BIT 1
|
||||
#define PG_USER_BIT 2
|
||||
@ -362,7 +372,7 @@
|
||||
#define CPUID_MWAIT_EMX (1 << 0) /* enumeration supported */
|
||||
|
||||
#define EXCP00_DIVZ 0
|
||||
#define EXCP01_SSTP 1
|
||||
#define EXCP01_DB 1
|
||||
#define EXCP02_NMI 2
|
||||
#define EXCP03_INT3 3
|
||||
#define EXCP04_INTO 4
|
||||
@ -596,6 +606,10 @@ typedef struct CPUX86State {
|
||||
int exception_is_int;
|
||||
target_ulong exception_next_eip;
|
||||
target_ulong dr[8]; /* debug registers */
|
||||
union {
|
||||
CPUBreakpoint *cpu_breakpoint[4];
|
||||
CPUWatchpoint *cpu_watchpoint[4];
|
||||
}; /* break/watchpoints for dr[0..3] */
|
||||
uint32_t smbase;
|
||||
int old_exception; /* exception in flight */
|
||||
|
||||
@ -789,6 +803,26 @@ static inline void cpu_clone_regs(CPUState *env, target_ulong newsp)
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline int hw_breakpoint_enabled(unsigned long dr7, int index)
|
||||
{
|
||||
return (dr7 >> (index * 2)) & 3;
|
||||
}
|
||||
|
||||
static inline int hw_breakpoint_type(unsigned long dr7, int index)
|
||||
{
|
||||
return (dr7 >> (DR7_TYPE_SHIFT + (index * 2))) & 3;
|
||||
}
|
||||
|
||||
static inline int hw_breakpoint_len(unsigned long dr7, int index)
|
||||
{
|
||||
int len = ((dr7 >> (DR7_LEN_SHIFT + (index * 2))) & 3);
|
||||
return (len == 2) ? 8 : len + 1;
|
||||
}
|
||||
|
||||
void hw_breakpoint_insert(CPUState *env, int index);
|
||||
void hw_breakpoint_remove(CPUState *env, int index);
|
||||
int check_hw_breakpoints(CPUState *env, int force_dr6_update);
|
||||
|
||||
#include "cpu-all.h"
|
||||
#include "exec-all.h"
|
||||
|
||||
|
@ -34,8 +34,6 @@
|
||||
|
||||
//#define DEBUG_MMU
|
||||
|
||||
static int cpu_x86_register (CPUX86State *env, const char *cpu_model);
|
||||
|
||||
static void add_flagname_to_bitmaps(char *flagname, uint32_t *features,
|
||||
uint32_t *ext_features,
|
||||
uint32_t *ext2_features,
|
||||
@ -93,35 +91,6 @@ static void add_flagname_to_bitmaps(char *flagname, uint32_t *features,
|
||||
fprintf(stderr, "CPU feature %s not found\n", flagname);
|
||||
}
|
||||
|
||||
CPUX86State *cpu_x86_init(const char *cpu_model)
|
||||
{
|
||||
CPUX86State *env;
|
||||
static int inited;
|
||||
|
||||
env = qemu_mallocz(sizeof(CPUX86State));
|
||||
if (!env)
|
||||
return NULL;
|
||||
cpu_exec_init(env);
|
||||
env->cpu_model_str = cpu_model;
|
||||
|
||||
/* init various static tables */
|
||||
if (!inited) {
|
||||
inited = 1;
|
||||
optimize_flags_init();
|
||||
}
|
||||
if (cpu_x86_register(env, cpu_model) < 0) {
|
||||
cpu_x86_close(env);
|
||||
return NULL;
|
||||
}
|
||||
cpu_reset(env);
|
||||
#ifdef USE_KQEMU
|
||||
kqemu_init(env);
|
||||
#endif
|
||||
if (kvm_enabled())
|
||||
kvm_init_vcpu(env);
|
||||
return env;
|
||||
}
|
||||
|
||||
typedef struct x86_def_t {
|
||||
const char *name;
|
||||
uint32_t level;
|
||||
@ -499,6 +468,12 @@ void cpu_reset(CPUX86State *env)
|
||||
env->fpuc = 0x37f;
|
||||
|
||||
env->mxcsr = 0x1f80;
|
||||
|
||||
memset(env->dr, 0, sizeof(env->dr));
|
||||
env->dr[6] = DR6_FIXED_1;
|
||||
env->dr[7] = DR7_FIXED_1;
|
||||
cpu_breakpoint_remove_all(env, BP_CPU);
|
||||
cpu_watchpoint_remove_all(env, BP_CPU);
|
||||
}
|
||||
|
||||
void cpu_x86_close(CPUX86State *env)
|
||||
@ -1295,6 +1270,105 @@ target_phys_addr_t cpu_get_phys_page_debug(CPUState *env, target_ulong addr)
|
||||
paddr = (pte & TARGET_PAGE_MASK) + page_offset;
|
||||
return paddr;
|
||||
}
|
||||
|
||||
void hw_breakpoint_insert(CPUState *env, int index)
|
||||
{
|
||||
int type, err = 0;
|
||||
|
||||
switch (hw_breakpoint_type(env->dr[7], index)) {
|
||||
case 0:
|
||||
if (hw_breakpoint_enabled(env->dr[7], index))
|
||||
err = cpu_breakpoint_insert(env, env->dr[index], BP_CPU,
|
||||
&env->cpu_breakpoint[index]);
|
||||
break;
|
||||
case 1:
|
||||
type = BP_CPU | BP_MEM_WRITE;
|
||||
goto insert_wp;
|
||||
case 2:
|
||||
/* No support for I/O watchpoints yet */
|
||||
break;
|
||||
case 3:
|
||||
type = BP_CPU | BP_MEM_ACCESS;
|
||||
insert_wp:
|
||||
err = cpu_watchpoint_insert(env, env->dr[index],
|
||||
hw_breakpoint_len(env->dr[7], index),
|
||||
type, &env->cpu_watchpoint[index]);
|
||||
break;
|
||||
}
|
||||
if (err)
|
||||
env->cpu_breakpoint[index] = NULL;
|
||||
}
|
||||
|
||||
void hw_breakpoint_remove(CPUState *env, int index)
|
||||
{
|
||||
if (!env->cpu_breakpoint[index])
|
||||
return;
|
||||
switch (hw_breakpoint_type(env->dr[7], index)) {
|
||||
case 0:
|
||||
if (hw_breakpoint_enabled(env->dr[7], index))
|
||||
cpu_breakpoint_remove_by_ref(env, env->cpu_breakpoint[index]);
|
||||
break;
|
||||
case 1:
|
||||
case 3:
|
||||
cpu_watchpoint_remove_by_ref(env, env->cpu_watchpoint[index]);
|
||||
break;
|
||||
case 2:
|
||||
/* No support for I/O watchpoints yet */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int check_hw_breakpoints(CPUState *env, int force_dr6_update)
|
||||
{
|
||||
target_ulong dr6;
|
||||
int reg, type;
|
||||
int hit_enabled = 0;
|
||||
|
||||
dr6 = env->dr[6] & ~0xf;
|
||||
for (reg = 0; reg < 4; reg++) {
|
||||
type = hw_breakpoint_type(env->dr[7], reg);
|
||||
if ((type == 0 && env->dr[reg] == env->eip) ||
|
||||
((type & 1) && env->cpu_watchpoint[reg] &&
|
||||
(env->cpu_watchpoint[reg]->flags & BP_WATCHPOINT_HIT))) {
|
||||
dr6 |= 1 << reg;
|
||||
if (hw_breakpoint_enabled(env->dr[7], reg))
|
||||
hit_enabled = 1;
|
||||
}
|
||||
}
|
||||
if (hit_enabled || force_dr6_update)
|
||||
env->dr[6] = dr6;
|
||||
return hit_enabled;
|
||||
}
|
||||
|
||||
static CPUDebugExcpHandler *prev_debug_excp_handler;
|
||||
|
||||
void raise_exception(int exception_index);
|
||||
|
||||
static void breakpoint_handler(CPUState *env)
|
||||
{
|
||||
CPUBreakpoint *bp;
|
||||
|
||||
if (env->watchpoint_hit) {
|
||||
if (env->watchpoint_hit->flags & BP_CPU) {
|
||||
env->watchpoint_hit = NULL;
|
||||
if (check_hw_breakpoints(env, 0))
|
||||
raise_exception(EXCP01_DB);
|
||||
else
|
||||
cpu_resume_from_signal(env, NULL);
|
||||
}
|
||||
} else {
|
||||
for (bp = env->breakpoints; bp != NULL; bp = bp->next)
|
||||
if (bp->pc == env->eip) {
|
||||
if (bp->flags & BP_CPU) {
|
||||
check_hw_breakpoints(env, 1);
|
||||
raise_exception(EXCP01_DB);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (prev_debug_excp_handler)
|
||||
prev_debug_excp_handler(env);
|
||||
}
|
||||
#endif /* !CONFIG_USER_ONLY */
|
||||
|
||||
static void host_cpuid(uint32_t function, uint32_t *eax, uint32_t *ebx,
|
||||
@ -1532,3 +1606,36 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index,
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
CPUX86State *cpu_x86_init(const char *cpu_model)
|
||||
{
|
||||
CPUX86State *env;
|
||||
static int inited;
|
||||
|
||||
env = qemu_mallocz(sizeof(CPUX86State));
|
||||
if (!env)
|
||||
return NULL;
|
||||
cpu_exec_init(env);
|
||||
env->cpu_model_str = cpu_model;
|
||||
|
||||
/* init various static tables */
|
||||
if (!inited) {
|
||||
inited = 1;
|
||||
optimize_flags_init();
|
||||
#ifndef CONFIG_USER_ONLY
|
||||
prev_debug_excp_handler =
|
||||
cpu_set_debug_excp_handler(breakpoint_handler);
|
||||
#endif
|
||||
}
|
||||
if (cpu_x86_register(env, cpu_model) < 0) {
|
||||
cpu_x86_close(env);
|
||||
return NULL;
|
||||
}
|
||||
cpu_reset(env);
|
||||
#ifdef USE_KQEMU
|
||||
kqemu_init(env);
|
||||
#endif
|
||||
if (kvm_enabled())
|
||||
kvm_init_vcpu(env);
|
||||
return env;
|
||||
}
|
||||
|
@ -259,6 +259,10 @@ int cpu_load(QEMUFile *f, void *opaque, int version_id)
|
||||
|
||||
for(i = 0; i < 8; i++)
|
||||
qemu_get_betls(f, &env->dr[i]);
|
||||
cpu_breakpoint_remove_all(env, BP_CPU);
|
||||
cpu_watchpoint_remove_all(env, BP_CPU);
|
||||
for (i = 0; i < 4; i++)
|
||||
hw_breakpoint_insert(env, i);
|
||||
|
||||
/* MMU */
|
||||
qemu_get_sbe32s(f, &a20_mask);
|
||||
|
@ -496,6 +496,17 @@ static void switch_tss(int tss_selector,
|
||||
/* XXX: different exception if CALL ? */
|
||||
raise_exception_err(EXCP0D_GPF, 0);
|
||||
}
|
||||
|
||||
#ifndef CONFIG_USER_ONLY
|
||||
/* reset local breakpoints */
|
||||
if (env->dr[7] & 0x55) {
|
||||
for (i = 0; i < 4; i++) {
|
||||
if (hw_breakpoint_enabled(env->dr[7], i) == 0x1)
|
||||
hw_breakpoint_remove(env, i);
|
||||
}
|
||||
env->dr[7] &= ~0x55;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* check if Port I/O is allowed in TSS */
|
||||
@ -1879,8 +1890,11 @@ void helper_cmpxchg16b(target_ulong a0)
|
||||
|
||||
void helper_single_step(void)
|
||||
{
|
||||
env->dr[6] |= 0x4000;
|
||||
raise_exception(EXCP01_SSTP);
|
||||
#ifndef CONFIG_USER_ONLY
|
||||
check_hw_breakpoints(env, 1);
|
||||
env->dr[6] |= DR6_BS;
|
||||
#endif
|
||||
raise_exception(EXCP01_DB);
|
||||
}
|
||||
|
||||
void helper_cpuid(void)
|
||||
@ -2868,6 +2882,10 @@ target_ulong helper_read_crN(int reg)
|
||||
void helper_write_crN(int reg, target_ulong t0)
|
||||
{
|
||||
}
|
||||
|
||||
void helper_movl_drN_T0(int reg, target_ulong t0)
|
||||
{
|
||||
}
|
||||
#else
|
||||
target_ulong helper_read_crN(int reg)
|
||||
{
|
||||
@ -2913,6 +2931,24 @@ void helper_write_crN(int reg, target_ulong t0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void helper_movl_drN_T0(int reg, target_ulong t0)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (reg < 4) {
|
||||
hw_breakpoint_remove(env, reg);
|
||||
env->dr[reg] = t0;
|
||||
hw_breakpoint_insert(env, reg);
|
||||
} else if (reg == 7) {
|
||||
for (i = 0; i < 4; i++)
|
||||
hw_breakpoint_remove(env, i);
|
||||
env->dr[7] = t0;
|
||||
for (i = 0; i < 4; i++)
|
||||
hw_breakpoint_insert(env, i);
|
||||
} else
|
||||
env->dr[reg] = t0;
|
||||
}
|
||||
#endif
|
||||
|
||||
void helper_lmsw(target_ulong t0)
|
||||
@ -2929,12 +2965,6 @@ void helper_clts(void)
|
||||
env->hflags &= ~HF_TS_MASK;
|
||||
}
|
||||
|
||||
/* XXX: do more */
|
||||
void helper_movl_drN_T0(int reg, target_ulong t0)
|
||||
{
|
||||
env->dr[reg] = t0;
|
||||
}
|
||||
|
||||
void helper_invlpg(target_ulong addr)
|
||||
{
|
||||
helper_svm_check_intercept_param(SVM_EXIT_INVLPG, 0);
|
||||
|
Loading…
Reference in New Issue
Block a user