x86: A process doesn't stop on hw breakpoints sometimes

From: Andrei Vagin
Date: Mon May 23 2016 - 19:05:55 EST


Hi,

We use breakpoints on CRIU to stop a processes before calling
rt_sigreturn and we found that sometimes a process runs through a
break-point without stopping on it.

https://github.com/xemul/criu/issues/162


A small reproducer is attached. It forks a child, stops it, sets a
breakpoint, executes a child, waits when it stops on the breakpoint. I
execute it a few times concurrently and wait a few minutes.

https://asciinema.org/a/006l3u5v82ubbkfy9fto07agd

I know that it can be reproduced on:
AMD A10 Micro-6700T
Intel(R) Core(TM) i5-5200U CPU @ 2.20GHz
Intel(R) Core(TM) i7-4600U CPU @ 2.10GHz

so It doesn't look like a bug in a processor.

Thanks,
Andrew
#include <sys/ptrace.h>
#include <stdio.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <asm/debugreg.h>
#include <stdlib.h>
#include <unistd.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);
}


# define noinline __attribute__((noinline))
static noinline void bp1(void) {}
static noinline void bp2(void) {}

static void child1()
{
int nr = 0;

for (;;) {
bp1();
printf("fail1 %d %d\n", getpid(), ++nr);
}
}
static void child2()
{
int nr = 0;

for (;;) {
bp2();
printf("fail2 %d %d\n", getpid(), ++nr);
}
}

# define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
int main(int argc, char *argv[])
{
pid_t pid;
int status, i;

for (i = 0; ; i++ ) {
pid = fork();
assert(pid != -1);
if (pid == 0) {
assert(ptrace(PTRACE_TRACEME, 0, 0, 0) == 0);
kill(getpid(), SIGSTOP);
if (i % 2)
child1();
else
child2();
return 1;
}

assert(waitpid(pid, NULL, 0) == pid);

set_bp(pid, i % 2 ? bp1 : bp2);

assert(ptrace(PTRACE_CONT, pid, NULL, NULL) == 0);
assert(waitpid(pid, &status, 0) == pid);
if (WIFEXITED(status))
return 1;
assert(ptrace(PTRACE_KILL, pid, 0, 0) == 0);
assert(waitpid(pid, &status, 0) == pid);
}

return 0;
}