• Paolo Bonzini's avatar
    KVM: x86: fix missed hardware breakpoints · fc90441e
    Paolo Bonzini authored
    commit 172b2386 upstream.
    
    Sometimes when setting a breakpoint a process doesn't stop on it.
    This is because the debug registers are not loaded correctly on
    VCPU load.
    
    The following simple reproducer from Oleg Nesterov tries using debug
    registers in two threads.  To see the bug, run a 2-VCPU guest with
    "taskset -c 0" and run "./bp 0 1" inside the guest.
    
        #include <unistd.h>
        #include <signal.h>
        #include <stdlib.h>
        #include <stdio.h>
        #include <sys/wait.h>
        #include <sys/ptrace.h>
        #include <sys/user.h>
        #include <asm/debugreg.h>
        #include <assert.h>
    
        #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
    
        unsigned long encode_dr7(int drnum, int enable, unsigned int type, unsigned int len)
        {
            unsigned long dr7;
    
            dr7 = ((len | type) & 0xf)
                << (DR_CONTROL_SHIFT + drnum * DR_CONTROL_SIZE);
            if (enable)
                dr7 |= (DR_GLOBAL_ENABLE << (drnum * DR_ENABLE_SIZE));
    
            return dr7;
        }
    
        int write_dr(int pid, int dr, unsigned long val)
        {
            return ptrace(PTRACE_POKEUSER, pid,
                    offsetof (struct user, u_debugreg[dr]),
                    val);
        }
    
        void set_bp(pid_t pid, void *addr)
        {
            unsigned long dr7;
            assert(write_dr(pid, 0, (long)addr) == 0);
            dr7 = encode_dr7(0, 1, DR_RW_EXECUTE, DR_LEN_1);
            assert(write_dr(pid, 7, dr7) == 0);
        }
    
        void *get_rip(int pid)
        {
            return (void*)ptrace(PTRACE_PEEKUSER, pid,
                    offsetof(struct user, regs.rip), 0);
        }
    
        void test(int nr)
        {
            void *bp_addr = &&label + nr, *bp_hit;
            int pid;
    
            printf("test bp %d\n", nr);
            assert(nr < 16); // see 16 asm nops below
    
            pid = fork();
            if (!pid) {
                assert(ptrace(PTRACE_TRACEME, 0,0,0) == 0);
                kill(getpid(), SIGSTOP);
                for (;;) {
                    label: asm (
                        "nop; nop; nop; nop;"
                        "nop; nop; nop; nop;"
                        "nop; nop; nop; nop;"
                        "nop; nop; nop; nop;"
                    );
                }
            }
    
            assert(pid == wait(NULL));
            set_bp(pid, bp_addr);
    
            for (;;) {
                assert(ptrace(PTRACE_CONT, pid, 0, 0) == 0);
                assert(pid == wait(NULL));
    
                bp_hit = get_rip(pid);
                if (bp_hit != bp_addr)
                    fprintf(stderr, "ERR!! hit wrong bp %ld != %d\n",
                        bp_hit - &&label, nr);
            }
        }
    
        int main(int argc, const char *argv[])
        {
            while (--argc) {
                int nr = atoi(*++argv);
                if (!fork())
                    test(nr);
            }
    
            while (wait(NULL) > 0)
                ;
            return 0;
        }
    Suggested-by: default avatarNadav Amit <namit@cs.technion.ac.il>
    Reported-by: default avatarAndrey Wagin <avagin@gmail.com>
    Signed-off-by: default avatarPaolo Bonzini <pbonzini@redhat.com>
    Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
    fc90441e
x86.c 212 KB