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;
}