x86: emulate I/O port access breakpoints

Emulate the trapping on I/O port accesses when emulating IN/OUT.

Also allow 8-byte breakpoints on x86-64 (and on i686 if the hardware
supports them), and tighten the condition for loading debug registers
during context switch.

Signed-off-by: Jan Beulich <jbeulich@novell.com>
Signed-off-by: Keir Fraser <keir.fraser@eu.citrix.com>
This commit is contained in:
Keir Fraser committed 2007-11-22 19:23:40 +00:00
1 parent 8b831f4189
commit bec9e32050
7 files changed
+147 -60

No files matched your search

+4 -3
View File
@@ -42,6 +42,7 @@
#include <asm/hypercall.h>
#include <asm/hvm/hvm.h>
#include <asm/hvm/support.h>
#include <asm/debugreg.h>
#include <asm/msr.h>
#include <asm/nmi.h>
#include <asm/iommu.h>
@@ -583,7 +584,7 @@ unsigned long pv_guest_cr4_fixup(unsigned long guest_cr4)
if ( (guest_cr4 & hv_cr4_mask) != (hv_cr4 & hv_cr4_mask) )
gdprintk(XENLOG_WARNING,
"Attempt to change CR4 flags %08lx -> %08lx\n",
hv_cr4 & ~(X86_CR4_PGE|X86_CR4_PSE), guest_cr4);
hv_cr4, guest_cr4);
return (hv_cr4 & hv_cr4_mask) | (guest_cr4 & ~hv_cr4_mask);
}
@@ -1219,7 +1220,7 @@ static void paravirt_ctxt_switch_from(struct vcpu *v)
* inside Xen, before we get a chance to reload DR7, and this cannot always
* safely be handled.
*/
if ( unlikely(v->arch.guest_context.debugreg[7]) )
if ( unlikely(v->arch.guest_context.debugreg[7] & DR7_ACTIVE_MASK) )
write_debugreg(7, 0);
}
@@ -1234,7 +1235,7 @@ static void paravirt_ctxt_switch_to(struct vcpu *v)
if ( unlikely(cr4 != read_cr4()) )
write_cr4(cr4);
if ( unlikely(v->arch.guest_context.debugreg[7]) )
if ( unlikely(v->arch.guest_context.debugreg[7] & DR7_ACTIVE_MASK) )
{
write_debugreg(0, v->arch.guest_context.debugreg[0]);
write_debugreg(1, v->arch.guest_context.debugreg[1]);
+8
View File
@@ -825,12 +825,20 @@ void arch_get_info_guest(struct vcpu *v, vcpu_guest_context_u c)
c.nat->ctrlreg[1] = xen_pfn_to_cr3(
pagetable_get_pfn(v->arch.guest_table_user));
#endif
/* Merge shadow DR7 bits into real DR7. */
c.nat->debugreg[7] |= c.nat->debugreg[5];
c.nat->debugreg[5] = 0;
}
#ifdef CONFIG_COMPAT
else
{
l4_pgentry_t *l4e = __va(pagetable_get_paddr(v->arch.guest_table));
c.cmp->ctrlreg[3] = compat_pfn_to_cr3(l4e_get_pfn(*l4e));
/* Merge shadow DR7 bits into real DR7. */
c.cmp->debugreg[7] |= c.cmp->debugreg[5];
c.cmp->debugreg[5] = 0;
}
#endif
+1 -2
View File
@@ -34,6 +34,7 @@
#include <asm/cpufeature.h>
#include <asm/processor.h>
#include <asm/types.h>
#include <asm/debugreg.h>
#include <asm/msr.h>
#include <asm/spinlock.h>
#include <asm/hvm/hvm.h>
@@ -176,8 +177,6 @@ static void __restore_debug_registers(struct vcpu *v)
* if one of the breakpoints is enabled. So mask out all bits that don't
* enable some breakpoint functionality.
*/
#define DR7_ACTIVE_MASK 0xff
static void svm_restore_dr(struct vcpu *v)
{
if ( unlikely(v->arch.guest_context.debugreg[7] & DR7_ACTIVE_MASK) )
+1 -2
View File
@@ -32,6 +32,7 @@
#include <asm/cpufeature.h>
#include <asm/processor.h>
#include <asm/types.h>
#include <asm/debugreg.h>
#include <asm/msr.h>
#include <asm/spinlock.h>
#include <asm/paging.h>
@@ -435,8 +436,6 @@ static void __restore_debug_registers(struct vcpu *v)
* if one of the breakpoints is enabled. So mask out all bits that don't
* enable some breakpoint functionality.
*/
#define DR7_ACTIVE_MASK 0xff
static void vmx_restore_dr(struct vcpu *v)
{
/* NB. __vmread() is not usable here, so we cannot read from the VMCS. */
+103 -26
View File
@@ -414,17 +414,55 @@ static int do_guest_trap(
return 0;
}
static void instruction_done(struct cpu_user_regs *regs, unsigned long eip)
static void instruction_done(
struct cpu_user_regs *regs, unsigned long eip, unsigned int bpmatch)
{
regs->eip = eip;
regs->eflags &= ~X86_EFLAGS_RF;
if ( regs->eflags & X86_EFLAGS_TF )
if ( bpmatch || (regs->eflags & X86_EFLAGS_TF) )
{
current->arch.guest_context.debugreg[6] |= 0xffff4ff0;
current->arch.guest_context.debugreg[6] |= bpmatch | 0xffff0ff0;
if ( regs->eflags & X86_EFLAGS_TF )
current->arch.guest_context.debugreg[6] |= 0x4000;
do_guest_trap(TRAP_debug, regs, 0);
}
}
static unsigned int check_guest_io_breakpoint(struct vcpu *v,
unsigned int port, unsigned int len)
{
unsigned int width, i, match = 0;
unsigned long start;
if ( !(v->arch.guest_context.debugreg[5]) ||
!(v->arch.guest_context.ctrlreg[4] & X86_CR4_DE) )
return 0;
for ( i = 0; i < 4; i++ )
{
if ( !(v->arch.guest_context.debugreg[5] &
(3 << (i * DR_ENABLE_SIZE))) )
continue;
start = v->arch.guest_context.debugreg[i];
width = 0;
switch ( (v->arch.guest_context.debugreg[7] >>
(DR_CONTROL_SHIFT + i * DR_CONTROL_SIZE)) & 0xc )
{
case DR_LEN_1: width = 1; break;
case DR_LEN_2: width = 2; break;
case DR_LEN_4: width = 4; break;
case DR_LEN_8: width = 8; break;
}
if ( (start < (port + len)) && ((start + width) > port) )
match |= 1 << i;
}
return match;
}
/*
* Called from asm to set up the NMI trapbounce info.
* Returns 0 if no callback is set up, else 1.
@@ -639,7 +677,6 @@ static int emulate_forced_invalid_op(struct cpu_user_regs *regs)
{
/* Modify Feature Information. */
clear_bit(X86_FEATURE_VME, &d);
clear_bit(X86_FEATURE_DE, &d);
clear_bit(X86_FEATURE_PSE, &d);
clear_bit(X86_FEATURE_PGE, &d);
if ( !cpu_has_sep )
@@ -669,7 +706,7 @@ static int emulate_forced_invalid_op(struct cpu_user_regs *regs)
regs->ecx = c;
regs->edx = d;
instruction_done(regs, eip);
instruction_done(regs, eip, 0);
trace_trap_one_addr(TRC_PV_FORCED_INVALID_OP, regs->eip);
@@ -1329,7 +1366,7 @@ static int emulate_privileged_op(struct cpu_user_regs *regs)
unsigned long *reg, eip = regs->eip, res;
u8 opcode, modrm_reg = 0, modrm_rm = 0, rep_prefix = 0, lock = 0, rex = 0;
enum { lm_seg_none, lm_seg_fs, lm_seg_gs } lm_ovr = lm_seg_none;
unsigned int port, i, data_sel, ar, data, rc;
unsigned int port, i, data_sel, ar, data, rc, bpmatch = 0;
unsigned int op_bytes, op_default, ad_bytes, ad_default;
#define rd_ad(reg) (ad_bytes >= sizeof(regs->reg) \
? regs->reg \
@@ -1479,6 +1516,8 @@ static int emulate_privileged_op(struct cpu_user_regs *regs)
}
#endif
port = (u16)regs->edx;
continue_io_string:
switch ( opcode )
{
@@ -1487,9 +1526,8 @@ static int emulate_privileged_op(struct cpu_user_regs *regs)
case 0x6d: /* INSW/INSL */
if ( data_limit < op_bytes - 1 ||
rd_ad(edi) > data_limit - (op_bytes - 1) ||
!guest_io_okay((u16)regs->edx, op_bytes, v, regs) )
!guest_io_okay(port, op_bytes, v, regs) )
goto fail;
port = (u16)regs->edx;
switch ( op_bytes )
{
case 1:
@@ -1519,7 +1557,7 @@ static int emulate_privileged_op(struct cpu_user_regs *regs)
case 0x6f: /* OUTSW/OUTSL */
if ( data_limit < op_bytes - 1 ||
rd_ad(esi) > data_limit - (op_bytes - 1) ||
!guest_io_okay((u16)regs->edx, op_bytes, v, regs) )
!guest_io_okay(port, op_bytes, v, regs) )
goto fail;
rc = copy_from_user(&data, (void *)data_base + rd_ad(esi), op_bytes);
if ( rc != 0 )
@@ -1527,7 +1565,6 @@ static int emulate_privileged_op(struct cpu_user_regs *regs)
propagate_page_fault(data_base + rd_ad(esi) + op_bytes - rc, 0);
return EXCRET_fault_fixed;
}
port = (u16)regs->edx;
switch ( op_bytes )
{
case 1:
@@ -1553,9 +1590,11 @@ static int emulate_privileged_op(struct cpu_user_regs *regs)
break;
}
bpmatch = check_guest_io_breakpoint(v, port, op_bytes);
if ( rep_prefix && (wr_ad(ecx, regs->ecx - 1) != 0) )
{
if ( !hypercall_preempt_check() )
if ( !bpmatch && !hypercall_preempt_check() )
goto continue_io_string;
eip = regs->eip;
}
@@ -1634,6 +1673,7 @@ static int emulate_privileged_op(struct cpu_user_regs *regs)
regs->eax = (u32)~0;
break;
}
bpmatch = check_guest_io_breakpoint(v, port, op_bytes);
goto done;
case 0xec: /* IN %dx,%al */
@@ -1671,6 +1711,7 @@ static int emulate_privileged_op(struct cpu_user_regs *regs)
io_emul(regs);
break;
}
bpmatch = check_guest_io_breakpoint(v, port, op_bytes);
goto done;
case 0xee: /* OUT %al,%dx */
@@ -1964,7 +2005,7 @@ static int emulate_privileged_op(struct cpu_user_regs *regs)
#undef rd_ad
done:
instruction_done(regs, eip);
instruction_done(regs, eip, bpmatch);
return EXCRET_fault_fixed;
fail:
@@ -2295,7 +2336,7 @@ static int emulate_gate_op(struct cpu_user_regs *regs)
sel |= (regs->cs & 3);
regs->cs = sel;
instruction_done(regs, off);
instruction_done(regs, off, 0);
#endif
return 0;
@@ -2805,25 +2846,47 @@ long set_debugreg(struct vcpu *v, int reg, unsigned long value)
/*
* DR7: Bit 10 reserved (set to 1).
* Bits 11-12,14-15 reserved (set to 0).
*/
value &= ~DR_CONTROL_RESERVED_ZERO; /* reserved bits => 0 */
value |= DR_CONTROL_RESERVED_ONE; /* reserved bits => 1 */
/*
* Privileged bits:
* GD (bit 13): must be 0.
* R/Wn (bits 16-17,20-21,24-25,28-29): mustn't be 10.
* LENn (bits 18-19,22-23,26-27,30-31): mustn't be 10.
*/
/* DR7 == 0 => debugging disabled for this domain. */
if ( value != 0 )
if ( value & DR_GENERAL_DETECT )
return -EPERM;
/* DR7.{G,L}E = 0 => debugging disabled for this domain. */
if ( value & DR7_ACTIVE_MASK )
{
value &= 0xffff27ff; /* reserved bits => 0 */
value |= 0x00000400; /* reserved bits => 1 */
if ( (value & (1<<13)) != 0 ) return -EPERM;
for ( i = 0; i < 16; i += 2 )
if ( ((value >> (i+16)) & 3) == 2 ) return -EPERM;
unsigned int io_enable = 0;
for ( i = DR_CONTROL_SHIFT; i < 32; i += DR_CONTROL_SIZE )
{
if ( ((value >> i) & 3) == DR_IO )
{
if ( !(v->arch.guest_context.ctrlreg[4] & X86_CR4_DE) )
return -EPERM;
io_enable |= value & (3 << ((i - 16) >> 1));
}
#ifdef __i386__
if ( ((boot_cpu_data.x86_vendor != X86_VENDOR_INTEL) ||
!boot_cpu_has(X86_FEATURE_LM)) &&
(((value >> i) & 0xc) == DR_LEN_8) )
return -EPERM;
#endif
}
/* Guest DR5 is a handy stash for I/O intercept information. */
v->arch.guest_context.debugreg[5] = io_enable;
value &= ~io_enable;
/*
* If DR7 was previously clear then we need to load all other
* debug registers at this point as they were not restored during
* context switch.
*/
if ( (v == curr) && (v->arch.guest_context.debugreg[7] == 0) )
if ( (v == curr) &&
!(v->arch.guest_context.debugreg[7] & DR7_ACTIVE_MASK) )
{
write_debugreg(0, v->arch.guest_context.debugreg[0]);
write_debugreg(1, v->arch.guest_context.debugreg[1]);
@@ -2832,7 +2895,7 @@ long set_debugreg(struct vcpu *v, int reg, unsigned long value)
write_debugreg(6, v->arch.guest_context.debugreg[6]);
}
}
if ( v == curr )
if ( v == curr )
write_debugreg(7, value);
break;
default:
@@ -2850,8 +2913,22 @@ long do_set_debugreg(int reg, unsigned long value)
unsigned long do_get_debugreg(int reg)
{
if ( (reg < 0) || (reg > 7) ) return -EINVAL;
return current->arch.guest_context.debugreg[reg];
struct vcpu *curr = current;
switch ( reg )
{
case 0 ... 3:
case 6:
return curr->arch.guest_context.debugreg[reg];
case 7:
return (curr->arch.guest_context.debugreg[7] |
curr->arch.guest_context.debugreg[5]);
case 4 ... 5:
return ((curr->arch.guest_context.ctrlreg[4] & X86_CR4_DE) ?
curr->arch.guest_context.debugreg[reg + 2] : 0);
}
return -EINVAL;
}
/*
+29 -26
View File
@@ -4,23 +4,22 @@
/* Indicate the register numbers for a number of the specific
debug registers. Registers 0-3 contain the addresses we wish to trap on */
#define DR_FIRSTADDR 0 /* u_debugreg[DR_FIRSTADDR] */
#define DR_LASTADDR 3 /* u_debugreg[DR_LASTADDR] */
#define DR_STATUS 6 /* u_debugreg[DR_STATUS] */
#define DR_CONTROL 7 /* u_debugreg[DR_CONTROL] */
#define DR_FIRSTADDR 0
#define DR_LASTADDR 3
#define DR_STATUS 6
#define DR_CONTROL 7
/* Define a few things for the status register. We can use this to determine
which debugging register was responsible for the trap. The other bits
are either reserved or not of interest to us. */
#define DR_TRAP0 (0x1) /* db0 */
#define DR_TRAP1 (0x2) /* db1 */
#define DR_TRAP2 (0x4) /* db2 */
#define DR_TRAP3 (0x8) /* db3 */
#define DR_STEP (0x4000) /* single-step */
#define DR_SWITCH (0x8000) /* task switch */
#define DR_TRAP0 (0x1) /* db0 */
#define DR_TRAP1 (0x2) /* db1 */
#define DR_TRAP2 (0x4) /* db2 */
#define DR_TRAP3 (0x8) /* db3 */
#define DR_STEP (0x4000) /* single-step */
#define DR_SWITCH (0x8000) /* task switch */
/* Now define a bunch of things for manipulating the control register.
The top two bytes of the control register consist of 4 fields of 4
@@ -29,36 +28,40 @@
field is that we are looking at */
#define DR_CONTROL_SHIFT 16 /* Skip this many bits in ctl register */
#define DR_CONTROL_SIZE 4 /* 4 control bits per register */
#define DR_CONTROL_SIZE 4 /* 4 control bits per register */
#define DR_RW_EXECUTE (0x0) /* Settings for the access types to trap on */
#define DR_RW_WRITE (0x1)
#define DR_RW_READ (0x3)
#define DR_RW_EXECUTE (0x0) /* Settings for the access types to trap on */
#define DR_RW_WRITE (0x1)
#define DR_IO (0x2)
#define DR_RW_READ (0x3)
#define DR_LEN_1 (0x0) /* Settings for data length to trap on */
#define DR_LEN_2 (0x4)
#define DR_LEN_4 (0xC)
#define DR_LEN_1 (0x0) /* Settings for data length to trap on */
#define DR_LEN_2 (0x4)
#define DR_LEN_4 (0xC)
#define DR_LEN_8 (0x8)
/* The low byte to the control register determine which registers are
enabled. There are 4 fields of two bits. One bit is "local", meaning
that the processor will reset the bit after a task switch and the other
is global meaning that we have to explicitly reset the bit. With linux,
you can use either one, since we explicitly zero the register when we enter
kernel mode. */
is global meaning that we have to explicitly reset the bit. */
#define DR_LOCAL_ENABLE_SHIFT 0 /* Extra shift to the local enable bit */
#define DR_LOCAL_ENABLE_SHIFT 0 /* Extra shift to the local enable bit */
#define DR_GLOBAL_ENABLE_SHIFT 1 /* Extra shift to the global enable bit */
#define DR_ENABLE_SIZE 2 /* 2 enable bits per register */
#define DR_ENABLE_SIZE 2 /* 2 enable bits per register */
#define DR_LOCAL_ENABLE_MASK (0x55) /* Set local bits for all 4 regs */
#define DR_GLOBAL_ENABLE_MASK (0xAA) /* Set global bits for all 4 regs */
#define DR7_ACTIVE_MASK (DR_LOCAL_ENABLE_MASK|DR_GLOBAL_ENABLE_MASK)
/* The second byte to the control register has a few special things.
We can slow the instruction pipeline for instructions coming via the
gdt or the ldt if we want to. I am not sure why this is an advantage */
#define DR_CONTROL_RESERVED (~0xFFFF03FFUL) /* Reserved by Intel */
#define DR_LOCAL_SLOWDOWN (0x100) /* Local slow the pipeline */
#define DR_GLOBAL_SLOWDOWN (0x200) /* Global slow the pipeline */
#define DR_CONTROL_RESERVED_ZERO (0x0000d800ul) /* Reserved, read as zero */
#define DR_CONTROL_RESERVED_ONE (0x00000400ul) /* Reserved, read as one */
#define DR_LOCAL_EXACT_ENABLE (0x00000100ul) /* Local exact enable */
#define DR_GLOBAL_EXACT_ENABLE (0x00000200ul) /* Global exact enable */
#define DR_GENERAL_DETECT (0x00002000ul) /* General detect enable */
#endif /* _X86_DEBUGREG_H */
+1 -1
View File
@@ -356,7 +356,7 @@ unsigned long pv_guest_cr4_fixup(unsigned long guest_cr4);
/* Convert between guest-visible and real CR4 values. */
#define pv_guest_cr4_to_real_cr4(c) \
((c) | (mmu_cr4_features & (X86_CR4_PGE | X86_CR4_PSE)))
(((c) | (mmu_cr4_features & (X86_CR4_PGE | X86_CR4_PSE))) & ~X86_CR4_DE)
#define real_cr4_to_pv_guest_cr4(c) \
((c) & ~(X86_CR4_PGE | X86_CR4_PSE))
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.