| /* |
| * Test for x86 debugging facilities |
| * |
| * Copyright (c) Siemens AG, 2014 |
| * |
| * Authors: |
| * Jan Kiszka <jan.kiszka@siemens.com> |
| * |
| * This work is licensed under the terms of the GNU GPL, version 2. |
| */ |
| |
| #include "libcflat.h" |
| #include "processor.h" |
| #include "desc.h" |
| |
| static volatile unsigned long bp_addr; |
| static volatile unsigned long db_addr[10], dr6[10]; |
| static volatile unsigned int n; |
| static volatile unsigned long value; |
| |
| static unsigned long get_dr4(void) |
| { |
| unsigned long value; |
| |
| asm volatile("mov %%dr4, %0" : "=r" (value)); |
| return value; |
| } |
| |
| static unsigned long get_dr6(void) |
| { |
| unsigned long value; |
| |
| asm volatile("mov %%dr6,%0" : "=r" (value)); |
| return value; |
| } |
| |
| static void set_dr0(void *value) |
| { |
| asm volatile("mov %0,%%dr0" : : "r" (value)); |
| } |
| |
| static void set_dr1(void *value) |
| { |
| asm volatile("mov %0,%%dr1" : : "r" (value)); |
| } |
| |
| static void set_dr4(unsigned long value) |
| { |
| asm volatile("mov %0,%%dr4" : : "r" (value)); |
| } |
| |
| static void set_dr6(unsigned long value) |
| { |
| asm volatile("mov %0,%%dr6" : : "r" (value)); |
| } |
| |
| static void set_dr7(unsigned long value) |
| { |
| asm volatile("mov %0,%%dr7" : : "r" (value)); |
| } |
| |
| static void handle_db(struct ex_regs *regs) |
| { |
| db_addr[n] = regs->rip; |
| dr6[n] = get_dr6(); |
| |
| if (dr6[n] & 0x1) |
| regs->rflags |= (1 << 16); |
| |
| if (++n >= 10) { |
| regs->rflags &= ~(1 << 8); |
| set_dr7(0x00000400); |
| } |
| } |
| |
| extern unsigned char handle_db_save_rip; |
| asm("handle_db_save_rip:\n" |
| "stc\n" |
| "nop;nop;nop\n" |
| "rclq $1, n(%rip)\n" |
| "iretq\n"); |
| |
| static void handle_bp(struct ex_regs *regs) |
| { |
| bp_addr = regs->rip; |
| } |
| |
| bool got_ud; |
| static void handle_ud(struct ex_regs *regs) |
| { |
| unsigned long cr4 = read_cr4(); |
| write_cr4(cr4 & ~X86_CR4_DE); |
| got_ud = 1; |
| } |
| |
| int main(int ac, char **av) |
| { |
| unsigned long start; |
| unsigned long cr4; |
| |
| handle_exception(DB_VECTOR, handle_db); |
| handle_exception(BP_VECTOR, handle_bp); |
| handle_exception(UD_VECTOR, handle_ud); |
| |
| got_ud = 0; |
| cr4 = read_cr4(); |
| write_cr4(cr4 & ~X86_CR4_DE); |
| set_dr4(0); |
| set_dr6(0xffff4ff2); |
| report(get_dr4() == 0xffff4ff2 && !got_ud, "reading DR4 with CR4.DE == 0"); |
| |
| cr4 = read_cr4(); |
| write_cr4(cr4 | X86_CR4_DE); |
| get_dr4(); |
| report(got_ud, "reading DR4 with CR4.DE == 1"); |
| set_dr6(0); |
| |
| extern unsigned char sw_bp; |
| asm volatile("int3; sw_bp:"); |
| report(bp_addr == (unsigned long)&sw_bp, "#BP"); |
| |
| n = 0; |
| extern unsigned char hw_bp1; |
| set_dr0(&hw_bp1); |
| set_dr7(0x00000402); |
| asm volatile("hw_bp1: nop"); |
| report(n == 1 && |
| db_addr[0] == ((unsigned long)&hw_bp1) && dr6[0] == 0xffff0ff1, |
| "hw breakpoint (test that dr6.BS is not set)"); |
| |
| n = 0; |
| extern unsigned char hw_bp2; |
| set_dr0(&hw_bp2); |
| set_dr6(0x00004002); |
| asm volatile("hw_bp2: nop"); |
| report(n == 1 && |
| db_addr[0] == ((unsigned long)&hw_bp2) && dr6[0] == 0xffff4ff1, |
| "hw breakpoint (test that dr6.BS is not cleared)"); |
| |
| n = 0; |
| set_dr6(0); |
| asm volatile( |
| "pushf\n\t" |
| "pop %%rax\n\t" |
| "or $(1<<8),%%rax\n\t" |
| "push %%rax\n\t" |
| "lea (%%rip),%0\n\t" |
| "popf\n\t" |
| "and $~(1<<8),%%rax\n\t" |
| "push %%rax\n\t" |
| "popf\n\t" |
| : "=r" (start) : : "rax"); |
| report(n == 3 && |
| db_addr[0] == start + 1 + 6 && dr6[0] == 0xffff4ff0 && |
| db_addr[1] == start + 1 + 6 + 1 && dr6[1] == 0xffff4ff0 && |
| db_addr[2] == start + 1 + 6 + 1 + 1 && dr6[2] == 0xffff4ff0, |
| "single step"); |
| |
| /* |
| * cpuid and rdmsr (among others) trigger VM exits and are then |
| * emulated. Test that single stepping works on emulated instructions. |
| */ |
| n = 0; |
| set_dr6(0); |
| asm volatile( |
| "pushf\n\t" |
| "pop %%rax\n\t" |
| "or $(1<<8),%%rax\n\t" |
| "push %%rax\n\t" |
| "lea (%%rip),%0\n\t" |
| "popf\n\t" |
| "and $~(1<<8),%%rax\n\t" |
| "push %%rax\n\t" |
| "xor %%rax,%%rax\n\t" |
| "cpuid\n\t" |
| "movl $0x1a0,%%ecx\n\t" |
| "rdmsr\n\t" |
| "popf\n\t" |
| : "=r" (start) : : "rax", "ebx", "ecx", "edx"); |
| report(n == 7 && |
| db_addr[0] == start + 1 + 6 && dr6[0] == 0xffff4ff0 && |
| db_addr[1] == start + 1 + 6 + 1 && dr6[1] == 0xffff4ff0 && |
| db_addr[2] == start + 1 + 6 + 1 + 3 && dr6[2] == 0xffff4ff0 && |
| db_addr[3] == start + 1 + 6 + 1 + 3 + 2 && dr6[3] == 0xffff4ff0 && |
| db_addr[4] == start + 1 + 6 + 1 + 3 + 2 + 5 && dr6[4] == 0xffff4ff0 && |
| db_addr[5] == start + 1 + 6 + 1 + 3 + 2 + 5 + 2 && dr6[5] == 0xffff4ff0 && |
| db_addr[6] == start + 1 + 6 + 1 + 3 + 2 + 5 + 2 + 1 && dr6[6] == 0xffff4ff0, |
| "single step emulated instructions"); |
| |
| n = 0; |
| set_dr1((void *)&value); |
| set_dr7(0x00d0040a); // 4-byte write |
| |
| extern unsigned char hw_wp1; |
| asm volatile( |
| "mov $42,%%rax\n\t" |
| "mov %%rax,%0\n\t; hw_wp1:" |
| : "=m" (value) : : "rax"); |
| report(n == 1 && |
| db_addr[0] == ((unsigned long)&hw_wp1) && dr6[0] == 0xffff4ff2, |
| "hw watchpoint (test that dr6.BS is not cleared)"); |
| |
| n = 0; |
| set_dr6(0); |
| |
| extern unsigned char hw_wp2; |
| asm volatile( |
| "mov $42,%%rax\n\t" |
| "mov %%rax,%0\n\t; hw_wp2:" |
| : "=m" (value) : : "rax"); |
| report(n == 1 && |
| db_addr[0] == ((unsigned long)&hw_wp2) && dr6[0] == 0xffff0ff2, |
| "hw watchpoint (test that dr6.BS is not set)"); |
| |
| n = 0; |
| set_dr6(0); |
| extern unsigned char sw_icebp; |
| asm volatile(".byte 0xf1; sw_icebp:"); |
| report(n == 1 && |
| db_addr[0] == (unsigned long)&sw_icebp && dr6[0] == 0xffff0ff0, |
| "icebp"); |
| |
| set_dr7(0x400); |
| value = KERNEL_DS; |
| set_dr7(0x00f0040a); // 4-byte read or write |
| |
| /* |
| * Each invocation of the handler should shift n by 1 and set bit 0 to 1. |
| * We expect a single invocation, so n should become 3. If the entry |
| * RIP is wrong, or if the handler is executed more than once, the value |
| * will not match. |
| */ |
| set_idt_entry(1, &handle_db_save_rip, 0); |
| |
| n = 1; |
| asm volatile( |
| "clc\n\t" |
| "mov %0,%%ss\n\t" |
| ".byte 0x2e, 0x2e, 0xf1" |
| : "=m" (value) : : "rax"); |
| report(n == 3, "MOV SS + watchpoint + ICEBP"); |
| |
| /* |
| * Here the #DB handler is invoked twice, once as a software exception |
| * and once as a software interrupt. |
| */ |
| n = 1; |
| asm volatile( |
| "clc\n\t" |
| "mov %0,%%ss\n\t" |
| "int $1" |
| : "=m" (value) : : "rax"); |
| report(n == 7, "MOV SS + watchpoint + int $1"); |
| |
| /* |
| * Here the #DB and #BP handlers are invoked once each. |
| */ |
| n = 1; |
| bp_addr = 0; |
| asm volatile( |
| "mov %0,%%ss\n\t" |
| ".byte 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0xcc\n\t" |
| "sw_bp2:" |
| : "=m" (value) : : "rax"); |
| extern unsigned char sw_bp2; |
| report(n == 3 && bp_addr == (unsigned long)&sw_bp2, |
| "MOV SS + watchpoint + INT3"); |
| return report_summary(); |
| } |