-
Couldn't load subscription status.
- Fork 891
Add guest debugging support #81
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1154,17 +1154,35 @@ void vcpu_save_host_state(struct vcpu_t *vcpu) | |
| save_host_msr(vcpu); | ||
|
|
||
| hstate->hcr2 = get_cr2(); | ||
| hstate->dr0 = get_dr0(); | ||
| hstate->dr1 = get_dr1(); | ||
| hstate->dr2 = get_dr2(); | ||
| hstate->dr3 = get_dr3(); | ||
| hstate->dr6 = get_dr6(); | ||
| vcpu_enter_fpu_state(vcpu); | ||
| // CR0 should be written after host fpu state is saved | ||
| vmwrite(vcpu, HOST_CR0, get_cr0()); | ||
| } | ||
|
|
||
| static void vcpu_update_exception_bitmap(struct vcpu_t *vcpu) | ||
| { | ||
| uint32 exc_bitmap; | ||
|
|
||
| exc_bitmap = (1u << VECTOR_MC); | ||
| if (vcpu->debug_control & (HAX_DEBUG_USE_HW_BP | HAX_DEBUG_STEP)) { | ||
| exc_bitmap |= (1u << VECTOR_DB); | ||
| } | ||
| if (vcpu->debug_control & HAX_DEBUG_USE_SW_BP) { | ||
| exc_bitmap |= (1u << VECTOR_BP); | ||
| } | ||
| vmwrite(vcpu, VMX_EXCEPTION_BITMAP, exc_bitmap); | ||
| } | ||
|
|
||
| static void fill_common_vmcs(struct vcpu_t *vcpu) | ||
| { | ||
| uint32 pin_ctls; | ||
| uint32 pcpu_ctls; | ||
| uint32 scpu_ctls; | ||
| uint32 exc_bitmap; | ||
| uint32 exit_ctls = 0; | ||
| uint32 entry_ctls; | ||
| uint32 vmcs_err = 0; | ||
|
|
@@ -1198,8 +1216,6 @@ static void fill_common_vmcs(struct vcpu_t *vcpu) | |
| } | ||
| } | ||
|
|
||
| exc_bitmap = 1u << VECTOR_MC; | ||
|
|
||
| #ifdef __x86_64__ | ||
| exit_ctls = EXIT_CONTROL_HOST_ADDR_SPACE_SIZE | EXIT_CONTROL_LOAD_EFER | | ||
| EXIT_CONTROL_SAVE_DEBUG_CONTROLS; | ||
|
|
@@ -1272,7 +1288,7 @@ static void fill_common_vmcs(struct vcpu_t *vcpu) | |
| WRITE_CONTROLS(vcpu, VMX_SECONDARY_PROCESSOR_CONTROLS, scpu_ctls); | ||
| } | ||
|
|
||
| vmwrite(vcpu, VMX_EXCEPTION_BITMAP, exc_bitmap); | ||
| vcpu_update_exception_bitmap(vcpu); | ||
|
|
||
| WRITE_CONTROLS(vcpu, VMX_EXIT_CONTROLS, exit_ctls); | ||
|
|
||
|
|
@@ -1368,10 +1384,39 @@ void vcpu_load_host_state(struct vcpu_t *vcpu) | |
|
|
||
| load_host_msr(vcpu); | ||
| set_cr2(hstate->hcr2); | ||
| set_dr0(hstate->dr0); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make these operations conditional, so as to minimize the performance penalty. |
||
| set_dr1(hstate->dr1); | ||
| set_dr2(hstate->dr2); | ||
| set_dr3(hstate->dr3); | ||
| set_dr6(hstate->dr6); | ||
|
|
||
| vcpu_exit_fpu_state(vcpu); | ||
| } | ||
|
|
||
| void vcpu_save_guest_state(struct vcpu_t *vcpu) | ||
| { | ||
| struct vcpu_state_t *state = vcpu->state; | ||
|
|
||
| save_guest_msr(vcpu); | ||
| state->_dr0 = get_dr0(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make these operations conditional, so as to minimize the performance penalty. |
||
| state->_dr1 = get_dr1(); | ||
| state->_dr2 = get_dr2(); | ||
| state->_dr3 = get_dr3(); | ||
| state->_dr6 = get_dr6(); | ||
| } | ||
|
|
||
| void vcpu_load_guest_state(struct vcpu_t *vcpu) | ||
| { | ||
| struct vcpu_state_t *state = vcpu->state; | ||
|
|
||
| load_guest_msr(vcpu); | ||
| set_dr0(state->_dr0); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make these operations conditional, so as to minimize the performance penalty. |
||
| set_dr1(state->_dr1); | ||
| set_dr2(state->_dr2); | ||
| set_dr3(state->_dr3); | ||
| set_dr6(state->_dr6); | ||
| } | ||
|
|
||
| /* | ||
| * Copies bits 0, 1, 2, ..., (|size| * 8 - 1) of |src| to the same positions | ||
| * in the 64-bit buffer pointed to by |pdst|, and clears bits (|size| * 8) | ||
|
|
@@ -2213,6 +2258,20 @@ static int exit_exc_nmi(struct vcpu_t *vcpu, struct hax_tunnel *htun) | |
| dump_vmcs(vcpu); | ||
| break; | ||
| } | ||
| case VECTOR_DB: { | ||
| htun->_exit_status = HAX_EXIT_DEBUG; | ||
| htun->debug.rip = vcpu->state->_rip; | ||
| htun->debug.dr6 = vmx(vcpu, exit_qualification).raw; | ||
| htun->debug.dr7 = vmread(vcpu, GUEST_DR7); | ||
| return HAX_EXIT; | ||
| } | ||
| case VECTOR_BP: { | ||
| htun->_exit_status = HAX_EXIT_DEBUG; | ||
| htun->debug.rip = vcpu->state->_rip; | ||
| htun->debug.dr6 = 0; | ||
| htun->debug.dr7 = 0; | ||
| return HAX_EXIT; | ||
| } | ||
| } | ||
|
|
||
| if (exit_intr_info.vector == VECTOR_PF) { | ||
|
|
@@ -2797,6 +2856,10 @@ static int exit_cr_access(struct vcpu_t *vcpu, struct hax_tunnel *htun) | |
|
|
||
| static int exit_dr_access(struct vcpu_t *vcpu, struct hax_tunnel *htun) | ||
| { | ||
| // TODO: DR-exiting flag is disabled so this function is never executed. | ||
| // We should conditionally enable "DR exiting" whenever | ||
| // HAX_DEBUG_USE_HW_BP is enabled, to prevent the guest from | ||
| // tampering with debug drN registers. | ||
| uint64 *dr; | ||
| struct vcpu_state_t *state = vcpu->state; | ||
|
|
||
|
|
@@ -2856,7 +2919,7 @@ static int exit_dr_access(struct vcpu_t *vcpu, struct hax_tunnel *htun) | |
| if (vmx(vcpu, exit_qualification.dr.direction)) { | ||
| // MOV DR -> GPR | ||
| state->_regs[vmx(vcpu, exit_qualification).dr.gpr] = *dr; | ||
| } else { | ||
| } else if (!(vcpu->debug_control & HAX_DEBUG_USE_HW_BP)) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. MOV DR doesn't cause an VM exit by default, and I don't see There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. True, that makes sense. I'll add a TODO comment for now. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The comment looks good. As I commented earlier, we should consider enabling |
||
| // MOV DR <- GPR | ||
| *dr = state->_regs[vmx(vcpu, exit_qualification).dr.gpr]; | ||
| } | ||
|
|
@@ -3799,6 +3862,37 @@ int vcpu_set_msr(struct vcpu_t *vcpu, uint64 entry, uint64 val) | |
| return handle_msr_write(vcpu, entry, val); | ||
| } | ||
|
|
||
| void vcpu_debug(struct vcpu_t *vcpu, struct hax_debug_t *debug) | ||
| { | ||
| if (debug->control & HAX_DEBUG_ENABLE) { | ||
| vcpu->debug_control = debug->control; | ||
| // Hardware breakpoints | ||
| if (debug->control & HAX_DEBUG_USE_HW_BP) { | ||
| vcpu->state->_dr0 = debug->dr[0]; | ||
| vcpu->state->_dr1 = debug->dr[1]; | ||
| vcpu->state->_dr2 = debug->dr[2]; | ||
| vcpu->state->_dr3 = debug->dr[3]; | ||
| vmwrite(vcpu, GUEST_DR7, debug->dr[7]); | ||
| } else { | ||
| vmwrite(vcpu, GUEST_DR7, 0); | ||
| } | ||
| // Single-stepping | ||
| if (debug->control & HAX_DEBUG_STEP) { | ||
| vmwrite(vcpu, GUEST_RFLAGS, | ||
| vmread(vcpu, GUEST_RFLAGS) | EFLAGS_TF); | ||
| } else { | ||
| vmwrite(vcpu, GUEST_RFLAGS, | ||
| vmread(vcpu, GUEST_RFLAGS) & ~EFLAGS_TF); | ||
| } | ||
| } else { | ||
| vcpu->debug_control = 0; | ||
| vmwrite(vcpu, GUEST_DR7, 0); | ||
| vmwrite(vcpu, GUEST_RFLAGS, | ||
| vmread(vcpu, GUEST_RFLAGS) & ~EFLAGS_TF); | ||
| } | ||
| vcpu_update_exception_bitmap(vcpu); | ||
| }; | ||
|
|
||
| static void vcpu_dump(struct vcpu_t *vcpu, uint32 mask, const char *caption) | ||
| { | ||
| vcpu_vmread_all(vcpu); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -143,6 +143,11 @@ struct hax_tunnel { | |
| struct { | ||
| paddr_t dummy; | ||
| } state; | ||
| struct { | ||
| uint64_t rip; | ||
| uint64_t dr6; | ||
| uint64_t dr7; | ||
| } debug; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I wonder if we should add a reserved field. Are there any debugging features we could support in the future that might require passing additional data to QEMU? Problem is we have reached the size of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I don't think so. On the SDM I've seen other nice features that could benefit debuggers (e.g. tracing), but they can have their own ioctl since they are too wildly different from breakpoints/stepping and considerably more complex. So I don't think we will ever need more than these 24 bytes. |
||
| }; | ||
| uint64_t apic_base; | ||
| } PACKED; | ||
|
|
@@ -182,6 +187,7 @@ struct hax_module_version { | |
| #define HAX_CAP_64BIT_SETRAM (1 << 4) | ||
| #define HAX_CAP_TUNNEL_PAGE (1 << 5) | ||
| #define HAX_CAP_RAM_PROTECTION (1 << 6) | ||
| #define HAX_CAP_DEBUG (1 << 7) | ||
|
|
||
| struct hax_capabilityinfo { | ||
| /* | ||
|
|
@@ -269,4 +275,15 @@ struct hax_qemu_version { | |
| uint32_t least_version; | ||
| } PACKED; | ||
|
|
||
| #define HAX_DEBUG_ENABLE (1 << 0) | ||
| #define HAX_DEBUG_STEP (1 << 1) | ||
| #define HAX_DEBUG_USE_SW_BP (1 << 2) | ||
| #define HAX_DEBUG_USE_HW_BP (1 << 3) | ||
|
|
||
| struct hax_debug_t { | ||
| uint32_t control; | ||
| uint32_t reserved; | ||
| uint64_t dr[8]; | ||
| } PACKED; | ||
|
|
||
| #endif // HAX_INTERFACE_H_ | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most of the time, the vCPU is not in debugging mode (
!(vcpu->debug_control & HAX_DEBUG_ENABLE)), so we don't need to save/load debug registers. At least, we can ignore them if no HW BP is enabled. And if we want to be more clever, we can save DR{i} (i = 0, 1, 2, 3) only if HW BP i is enabled. I think this could be an important optimization, because this function is run before every VM entry, so even a few extra instructions could end up having a non-negligible performance penalty.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious about this: What happens after a VM-exit event if the guest has modified dr{0,1,2,3,6}? If "DR exiting" is disabled, this would have side-effects on the host, right?
So saving/loading drN registers should occur if any of these events occur:
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's indeed a very good point. Should we always enable "DR exiting" though, because:
If you agree with this approach, could you implement it in this PR (maybe as a separate commit)?