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