xref: /netbsd-src/external/gpl3/gdb/dist/gdb/testsuite/gdb.threads/current-lwp-dead.c (revision d16b7486a53dcb8072b60ec6fcb4373a2d0c27b7)
1 /* This testcase is part of GDB, the GNU debugger.
2 
3    Copyright 2009-2023 Free Software Foundation, Inc.
4 
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 3 of the License, or
8    (at your option) any later version.
9 
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 
18 
19    The original issue we're trying to test is described in this
20    thread:
21 
22      https://sourceware.org/legacy-ml/gdb-patches/2009-06/msg00802.html
23 
24    The NEW_THREAD_EVENT code the comments below refer to no longer
25    exists in GDB, so the following comments are kept for historical
26    reasons, and to guide future updates to the testcase.
27 
28    ---
29 
30    Do not use threads as we need to exploit a bug in LWP code masked by the
31    threads code otherwise.
32 
33    INFERIOR_PTID must point to exited LWP.  Here we use the initial LWP as it
34    is automatically INFERIOR_PTID for GDB.
35 
36    Finally we need to call target_resume (RESUME_ALL, ...) which we invoke by
37    NEW_THREAD_EVENT (called from the new LWP as initial LWP is exited now).  */
38 
39 #define _GNU_SOURCE
40 #include <sched.h>
41 #include <assert.h>
42 #include <unistd.h>
43 #include <stdlib.h>
44 #include <sys/types.h>
45 #include <sys/wait.h>
46 
47 #define STACK_SIZE 0x1000
48 
49 /* True if the 'fn_return' thread has been reached at the point after
50    its parent is already gone.  */
51 volatile int fn_return_reached = 0;
52 
53 /* True if the 'fn' thread has exited.  */
54 volatile int fn_exited = 0;
55 
56 /* Wrapper around clone.  */
57 
58 static int
59 do_clone (int (*fn)(void *))
60 {
61   unsigned char *stack;
62   int new_pid;
63 
64   stack = malloc (STACK_SIZE);
65   assert (stack != NULL);
66 
67   new_pid = clone (fn, stack + STACK_SIZE, CLONE_FILES | CLONE_VM,
68 		   NULL, NULL, NULL, NULL);
69   assert (new_pid > 0);
70 
71   return new_pid;
72 }
73 
74 static int
75 fn_return (void *unused)
76 {
77   /* Wait until our direct parent exits.  We want the breakpoint set a
78      couple lines below to hit with the previously-selected thread
79      gone.  */
80   while (!fn_exited)
81     usleep (1);
82 
83   fn_return_reached = 1; /* at-fn_return */
84   return 0;
85 }
86 
87 static int
88 fn (void *unused)
89 {
90   do_clone (fn_return);
91   return 0;
92 }
93 
94 int
95 main (int argc, char **argv)
96 {
97   int new_pid, status, ret;
98 
99   new_pid = do_clone (fn);
100 
101   /* Note the clone call above didn't use CLONE_THREAD, so it actually
102      put the new child in a new thread group.  However, the new clone
103      is still reported with PTRACE_EVENT_CLONE to GDB, since we didn't
104      use CLONE_VFORK (results in PTRACE_EVENT_VFORK) nor set the
105      termination signal to SIGCHLD (results in PTRACE_EVENT_FORK), so
106      GDB thinks of it as a new thread of the same inferior.  It's a
107      bit of an odd setup, but it's not important for what we're
108      testing, and, it let's us conveniently use waitpid to wait for
109      the child, which you can't with CLONE_THREAD.  */
110   ret = waitpid (new_pid, &status, __WALL);
111   assert (ret == new_pid);
112   assert (WIFEXITED (status) && WEXITSTATUS (status) == 0);
113 
114   fn_exited = 1;
115 
116   /* Don't exit before the breakpoint at fn_return triggers.  */
117   while (!fn_return_reached)
118     usleep (1);
119 
120   return 0;
121 }
122