Commit | Line | Data |
---|---|---|
5de96253 SM |
1 | #include <assert.h> |
2 | #include <pthread.h> | |
3 | #include <stdio.h> | |
4 | #include <unistd.h> | |
5 | ||
6 | // Start with: | |
7 | // | |
8 | // ./gdb -nx -q --data-directory=data-directory repro -ex "tb break_here_first" -ex r -ex "b should_break_here" | |
9 | // | |
10 | // Then do "continue". | |
11 | // | |
12 | // The main thread will likely cross should_break_here while we are handling | |
13 | // the vfork and breakpoints are removed, therefore missing the breakpoint. | |
14 | ||
15 | static volatile int release_vfork = 0; | |
16 | static volatile int release_main = 0; | |
17 | ||
18 | static void *vforker(void *arg) | |
19 | { | |
20 | while (!release_vfork); | |
21 | ||
22 | if (!vfork()) { | |
23 | /* A vfork child is not supposed to mess with the state of the program, | |
24 | but it is helpful for the purpose of this test. */ | |
25 | release_main = 1; | |
26 | _exit(0); | |
27 | } | |
28 | ||
29 | return NULL; | |
30 | } | |
31 | ||
32 | static void should_break_here(void) {} | |
33 | ||
34 | int main() | |
35 | { | |
36 | ||
37 | pthread_t thread; | |
38 | int ret = pthread_create(&thread, NULL, vforker, NULL); | |
39 | assert(ret == 0); | |
40 | ||
41 | /* We break here first, while the thread is stuck on `!release_fork`. */ | |
42 | release_vfork = 1; | |
43 | ||
44 | /* We set a breakpoint on should_break_here. | |
45 | ||
46 | We then set "release_fork" from the debugger and continue. The main | |
47 | thread hangs on `!release_main` while the non-main thread vforks. During | |
48 | the window of time where the two processes have a shared address space | |
49 | (after vfork, before _exit), GDB removes the breakpoints from the address | |
50 | space. During that window, only the vfork-ing thread (the non-main | |
51 | thread) is frozen by the kernel. The main thread is free to execute. The | |
52 | child process sets `release_main`, releasing the main thread. A buggy GDB | |
53 | would let the main thread execute during that window, leading to the | |
54 | breakpoint on should_break_here being missed. A fixed GDB does not resume | |
55 | the threads of the vforking process other than the vforking thread. When | |
56 | the vfork child exits, the fixed GDB resumes the main thread, after | |
57 | breakpoints are reinserted, so the breakpoint is not missed. */ | |
58 | ||
59 | while (!release_main); | |
60 | ||
61 | should_break_here(); | |
62 | ||
63 | pthread_join (thread, NULL); | |
64 | ||
65 | return 0; | |
66 | } |