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