1 | |
2 | // This program creates NUMBER_OF_SIMULTANEOUS_DEBUG_SESSIONS of pthreads, |
3 | // creates an lldb Debugger on each thread, creates targets, inserts two |
4 | // breakpoints, runs to the first breakpoint, backtraces, runs to the second |
5 | // breakpoint, backtraces, kills the inferior process, closes down the |
6 | // debugger. |
7 | |
8 | // The main thread keeps track of which pthreads have completed and which |
9 | // pthreads have completed successfully, and exits when all pthreads have |
10 | // completed successfully, or our time limit has been exceeded. |
11 | |
12 | // This test file helps to uncover race conditions and locking mistakes |
13 | // that are hit when lldb is being used to debug multiple processes |
14 | // simultaneously. |
15 | |
16 | #include <stdio.h> |
17 | #include <stdlib.h> |
18 | #include <string.h> |
19 | |
20 | #include "lldb/API/LLDB.h" |
21 | #include "lldb/API/SBCommandInterpreter.h" |
22 | #include "lldb/API/SBCommandReturnObject.h" |
23 | #include "lldb/API/SBDebugger.h" |
24 | |
25 | #include <chrono> |
26 | #include <csignal> |
27 | #include <thread> |
28 | |
29 | #define NUMBER_OF_SIMULTANEOUS_DEBUG_SESSIONS 10 |
30 | |
31 | #define DEBUG 0 |
32 | |
33 | using namespace lldb; |
34 | |
35 | bool *completed_threads_array = 0; |
36 | bool *successful_threads_array = 0; |
37 | |
38 | const char *inferior_process_name = "testprog" ; |
39 | |
40 | bool |
41 | wait_for_stop_event (SBProcess process, SBListener listener) |
42 | { |
43 | bool stopped = false; |
44 | while (!stopped) |
45 | { |
46 | SBEvent event; |
47 | bool waitfor_ret = listener.WaitForEvent (num_seconds: 2, event); |
48 | if (event.GetType() == SBProcess::eBroadcastBitStateChanged) |
49 | { |
50 | if (process.GetState() == StateType::eStateStopped |
51 | || process.GetState() == StateType::eStateCrashed |
52 | || process.GetState() == StateType::eStateDetached |
53 | || process.GetState() == StateType::eStateExited) |
54 | { |
55 | stopped = true; |
56 | } |
57 | } |
58 | } |
59 | return stopped; |
60 | } |
61 | |
62 | bool |
63 | walk_stack_to_main (SBThread thread) |
64 | { |
65 | if (thread.IsValid() == 0) |
66 | { |
67 | return false; |
68 | } |
69 | |
70 | bool found_main = false; |
71 | uint32_t curr_frame = 0; |
72 | const uint32_t framecount = thread.GetNumFrames(); |
73 | while (!found_main && curr_frame < framecount) |
74 | { |
75 | SBFrame frame = thread.GetFrameAtIndex (idx: curr_frame); |
76 | if (strcmp (s1: frame.GetFunctionName(), s2: "main" ) == 0) |
77 | { |
78 | found_main = true; |
79 | break; |
80 | } |
81 | curr_frame += 1; |
82 | } |
83 | return found_main; |
84 | } |
85 | |
86 | void *do_one_debugger (void *in) |
87 | { |
88 | uint64_t threadnum = (uint64_t) in; |
89 | |
90 | #if defined (__APPLE__) |
91 | char *threadname; |
92 | asprintf (&threadname, "thread #%lld" , threadnum); |
93 | pthread_setname_np (threadname); |
94 | free (threadname); |
95 | #endif |
96 | |
97 | #if DEBUG == 1 |
98 | printf ("#%lld: Starting debug session\n" , threadnum); |
99 | #endif |
100 | |
101 | SBDebugger debugger = lldb::SBDebugger::Create (source_init_files: false); |
102 | if (debugger.IsValid ()) |
103 | { |
104 | debugger.SetAsync (true); |
105 | SBTarget target = debugger.CreateTargetWithFileAndArch(filename: inferior_process_name, archname: "x86_64" ); |
106 | SBCommandInterpreter command_interp = debugger.GetCommandInterpreter(); |
107 | if (target.IsValid()) |
108 | { |
109 | SBBreakpoint bar_br = target.BreakpointCreateByName (symbol_name: "bar" , module_name: "testprog" ); |
110 | if (!bar_br.IsValid()) |
111 | { |
112 | printf (format: "#%lld: failed to set breakpoint on bar, exiting.\n" , threadnum); |
113 | exit (status: 1); |
114 | } |
115 | SBBreakpoint foo_br = target.BreakpointCreateByName (symbol_name: "foo" , module_name: "testprog" ); |
116 | if (!foo_br.IsValid()) |
117 | { |
118 | printf (format: "#%lld: Failed to set breakpoint on foo()\n" , threadnum); |
119 | } |
120 | |
121 | SBLaunchInfo launch_info (NULL); |
122 | SBError error; |
123 | SBProcess process = target.Launch (launch_info, error); |
124 | if (process.IsValid()) |
125 | { |
126 | SBListener listener = debugger.GetListener(); |
127 | SBBroadcaster broadcaster = process.GetBroadcaster(); |
128 | uint32_t rc = broadcaster.AddListener (listener, event_mask: SBProcess::eBroadcastBitStateChanged); |
129 | if (rc == 0) |
130 | { |
131 | printf (format: "adding listener failed\n" ); |
132 | exit (status: 1); |
133 | } |
134 | |
135 | wait_for_stop_event (process, listener); |
136 | |
137 | if (!walk_stack_to_main (thread: process.GetThreadAtIndex(index: 0))) |
138 | { |
139 | printf (format: "#%lld: backtrace while @ foo() failed\n" , threadnum); |
140 | completed_threads_array[threadnum] = true; |
141 | return (void *) 1; |
142 | } |
143 | |
144 | if (strcmp (s1: process.GetThreadAtIndex(index: 0).GetFrameAtIndex(idx: 0).GetFunctionName(), s2: "foo" ) != 0) |
145 | { |
146 | #if DEBUG == 1 |
147 | printf ("#%lld: First breakpoint did not stop at foo(), instead stopped at '%s'\n" , threadnum, process.GetThreadAtIndex(0).GetFrameAtIndex(0).GetFunctionName()); |
148 | #endif |
149 | completed_threads_array[threadnum] = true; |
150 | return (void*) 1; |
151 | } |
152 | |
153 | process.Continue(); |
154 | |
155 | wait_for_stop_event (process, listener); |
156 | |
157 | if (process.GetState() == StateType::eStateExited) |
158 | { |
159 | printf (format: "#%lld: Process exited\n" , threadnum); |
160 | completed_threads_array[threadnum] = true; |
161 | return (void *) 1; |
162 | } |
163 | |
164 | |
165 | if (!walk_stack_to_main (thread: process.GetThreadAtIndex(index: 0))) |
166 | { |
167 | printf (format: "#%lld: backtrace while @ bar() failed\n" , threadnum); |
168 | completed_threads_array[threadnum] = true; |
169 | return (void *) 1; |
170 | } |
171 | |
172 | if (strcmp (s1: process.GetThreadAtIndex(index: 0).GetFrameAtIndex(idx: 0).GetFunctionName(), s2: "bar" ) != 0) |
173 | { |
174 | printf (format: "#%lld: First breakpoint did not stop at bar()\n" , threadnum); |
175 | completed_threads_array[threadnum] = true; |
176 | return (void*) 1; |
177 | } |
178 | |
179 | process.Kill(); |
180 | |
181 | wait_for_stop_event (process, listener); |
182 | |
183 | SBDebugger::Destroy(debugger); |
184 | |
185 | #if DEBUG == 1 |
186 | printf ("#%lld: All good!\n" , threadnum); |
187 | #endif |
188 | successful_threads_array[threadnum] = true; |
189 | completed_threads_array[threadnum] = true; |
190 | return (void*) 0; |
191 | } |
192 | else |
193 | { |
194 | printf(format: "#%lld: process failed to launch\n" , threadnum); |
195 | successful_threads_array[threadnum] = false; |
196 | completed_threads_array[threadnum] = true; |
197 | return (void*) 0; |
198 | } |
199 | } |
200 | else |
201 | { |
202 | printf (format: "#%lld: did not get valid target\n" , threadnum); |
203 | successful_threads_array[threadnum] = false; |
204 | completed_threads_array[threadnum] = true; |
205 | return (void*) 0; |
206 | } |
207 | } |
208 | else |
209 | { |
210 | printf (format: "#%lld: did not get debugger\n" , threadnum); |
211 | successful_threads_array[threadnum] = false; |
212 | completed_threads_array[threadnum] = true; |
213 | return (void*) 0; |
214 | } |
215 | completed_threads_array[threadnum] = true; |
216 | return (void*) 1; |
217 | } |
218 | |
219 | int main (int argc, char **argv) |
220 | { |
221 | #if !defined(_MSC_VER) |
222 | signal(SIGPIPE, SIG_IGN); |
223 | #endif |
224 | |
225 | SBDebugger::Initialize(); |
226 | |
227 | completed_threads_array = (bool *) malloc (size: sizeof (bool) * NUMBER_OF_SIMULTANEOUS_DEBUG_SESSIONS); |
228 | memset (s: completed_threads_array, c: 0, n: sizeof (bool) * NUMBER_OF_SIMULTANEOUS_DEBUG_SESSIONS); |
229 | successful_threads_array = (bool *) malloc (size: sizeof (bool) * NUMBER_OF_SIMULTANEOUS_DEBUG_SESSIONS); |
230 | memset (s: successful_threads_array, c: 0, n: sizeof (bool) * NUMBER_OF_SIMULTANEOUS_DEBUG_SESSIONS); |
231 | |
232 | if (argc > 1 && argv[1] != NULL) |
233 | { |
234 | inferior_process_name = argv[1]; |
235 | } |
236 | |
237 | std::thread threads[NUMBER_OF_SIMULTANEOUS_DEBUG_SESSIONS]; |
238 | for (uint64_t i = 0; i< NUMBER_OF_SIMULTANEOUS_DEBUG_SESSIONS; i++) |
239 | { |
240 | threads[i] = std::move(std::thread(do_one_debugger, (void*)i)); |
241 | } |
242 | |
243 | |
244 | int max_time_to_wait = 20; // 20 iterations, or 60 seconds |
245 | int iter = 0; |
246 | while (1) |
247 | { |
248 | std::this_thread::sleep_for(rtime: std::chrono::seconds(3)); |
249 | bool all_done = true; |
250 | int successful_threads = 0; |
251 | int total_completed_threads = 0; |
252 | for (uint64_t i = 0; i < NUMBER_OF_SIMULTANEOUS_DEBUG_SESSIONS; i++) |
253 | { |
254 | if (successful_threads_array[i] == true) |
255 | successful_threads++; |
256 | if (completed_threads_array[i] == true) |
257 | total_completed_threads++; |
258 | if (completed_threads_array[i] == false) |
259 | { |
260 | all_done = false; |
261 | } |
262 | } |
263 | if (all_done) |
264 | { |
265 | #if DEBUG == 1 |
266 | printf ("All threads completed.\n" ); |
267 | printf ("%d threads completed successfully out of %d\n" , successful_threads, NUMBER_OF_SIMULTANEOUS_DEBUG_SESSIONS); |
268 | #endif |
269 | SBDebugger::Terminate(); |
270 | exit(status: 0); |
271 | } |
272 | else |
273 | { |
274 | #if DEBUG == 1 |
275 | printf ("%d threads completed so far (%d successfully), out of %d\n" , total_completed_threads, successful_threads, NUMBER_OF_SIMULTANEOUS_DEBUG_SESSIONS); |
276 | #endif |
277 | } |
278 | if (iter++ == max_time_to_wait) |
279 | { |
280 | printf (format: "reached maximum timeout but only %d threads have completed so far (%d successfully), out of %d. Exiting.\n" , total_completed_threads, successful_threads, NUMBER_OF_SIMULTANEOUS_DEBUG_SESSIONS); |
281 | break; |
282 | } |
283 | } |
284 | |
285 | |
286 | SBDebugger::Terminate(); |
287 | exit (status: 1); |
288 | } |
289 | |