1""" 2A stress-test of sorts for LLDB's handling of threads in the inferior. 3 4This test sets a breakpoint in the main thread where test parameters (numbers of 5threads) can be adjusted, runs the inferior to that point, and modifies the 6locals that control the event thread counts. This test also sets a breakpoint in 7breakpoint_func (the function executed by each 'breakpoint' thread) and a 8watchpoint on a global modified in watchpoint_func. The inferior is continued 9until exit or a crash takes place, and the number of events seen by LLDB is 10verified to match the expected number of events. 11""" 12 13 14import lldb 15from lldbsuite.test.decorators import * 16from lldbsuite.test.lldbtest import * 17from lldbsuite.test import lldbutil 18 19 20class ConcurrentEventsBase(TestBase): 21 # Concurrency is the primary test factor here, not debug info variants. 22 NO_DEBUG_INFO_TESTCASE = True 23 24 def setUp(self): 25 # Call super's setUp(). 26 super(ConcurrentEventsBase, self).setUp() 27 # Find the line number for our breakpoint. 28 self.filename = "main.cpp" 29 self.thread_breakpoint_line = line_number( 30 self.filename, "// Set breakpoint here" 31 ) 32 self.setup_breakpoint_line = line_number( 33 self.filename, "// Break here and adjust num" 34 ) 35 self.finish_breakpoint_line = line_number( 36 self.filename, "// Break here and verify one thread is active" 37 ) 38 39 def describe_threads(self): 40 ret = [] 41 for x in self.inferior_process: 42 id = x.GetIndexID() 43 reason = x.GetStopReason() 44 status = "stopped" if x.IsStopped() else "running" 45 reason_str = lldbutil.stop_reason_to_str(reason) 46 if reason == lldb.eStopReasonBreakpoint: 47 bpid = x.GetStopReasonDataAtIndex(0) 48 bp = self.inferior_target.FindBreakpointByID(bpid) 49 reason_str = "%s hit %d times" % ( 50 lldbutil.get_description(bp), 51 bp.GetHitCount(), 52 ) 53 elif reason == lldb.eStopReasonWatchpoint: 54 watchid = x.GetStopReasonDataAtIndex(0) 55 watch = self.inferior_target.FindWatchpointByID(watchid) 56 reason_str = "%s hit %d times" % ( 57 lldbutil.get_description(watch), 58 watch.GetHitCount(), 59 ) 60 elif reason == lldb.eStopReasonSignal: 61 signals = self.inferior_process.GetUnixSignals() 62 signal_name = signals.GetSignalAsCString(x.GetStopReasonDataAtIndex(0)) 63 reason_str = "signal %s" % signal_name 64 65 location = "\t".join( 66 [ 67 lldbutil.get_description(x.GetFrameAtIndex(i)) 68 for i in range(x.GetNumFrames()) 69 ] 70 ) 71 ret.append( 72 "thread %d %s due to %s at\n\t%s" % (id, status, reason_str, location) 73 ) 74 return ret 75 76 def add_breakpoint(self, line, descriptions): 77 """Adds a breakpoint at self.filename:line and appends its description to descriptions, and 78 returns the LLDB SBBreakpoint object. 79 """ 80 81 bpno = lldbutil.run_break_set_by_file_and_line( 82 self, self.filename, line, num_expected_locations=-1 83 ) 84 bp = self.inferior_target.FindBreakpointByID(bpno) 85 descriptions.append(": file = 'main.cpp', line = %d" % line) 86 return bp 87 88 def inferior_done(self): 89 """Returns true if the inferior is done executing all the event threads (and is stopped at self.finish_breakpoint, 90 or has terminated execution. 91 """ 92 return ( 93 self.finish_breakpoint.GetHitCount() > 0 94 or self.crash_count > 0 95 or self.inferior_process.GetState() == lldb.eStateExited 96 ) 97 98 def count_signaled_threads(self): 99 count = 0 100 for thread in self.inferior_process: 101 if ( 102 thread.GetStopReason() == lldb.eStopReasonSignal 103 and thread.GetStopReasonDataAtIndex(0) 104 == self.inferior_process.GetUnixSignals().GetSignalNumberFromName( 105 "SIGUSR1" 106 ) 107 ): 108 count += 1 109 return count 110 111 def do_thread_actions( 112 self, 113 num_breakpoint_threads=0, 114 num_signal_threads=0, 115 num_watchpoint_threads=0, 116 num_crash_threads=0, 117 num_delay_breakpoint_threads=0, 118 num_delay_signal_threads=0, 119 num_delay_watchpoint_threads=0, 120 num_delay_crash_threads=0, 121 ): 122 """Sets a breakpoint in the main thread where test parameters (numbers of threads) can be adjusted, runs the inferior 123 to that point, and modifies the locals that control the event thread counts. Also sets a breakpoint in 124 breakpoint_func (the function executed by each 'breakpoint' thread) and a watchpoint on a global modified in 125 watchpoint_func. The inferior is continued until exit or a crash takes place, and the number of events seen by LLDB 126 is verified to match the expected number of events. 127 """ 128 exe = self.getBuildArtifact("a.out") 129 self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) 130 131 # Get the target 132 self.inferior_target = self.dbg.GetSelectedTarget() 133 134 expected_bps = [] 135 136 # Initialize all the breakpoints (main thread/aux thread) 137 self.setup_breakpoint = self.add_breakpoint( 138 self.setup_breakpoint_line, expected_bps 139 ) 140 self.finish_breakpoint = self.add_breakpoint( 141 self.finish_breakpoint_line, expected_bps 142 ) 143 144 # Set the thread breakpoint 145 if num_breakpoint_threads + num_delay_breakpoint_threads > 0: 146 self.thread_breakpoint = self.add_breakpoint( 147 self.thread_breakpoint_line, expected_bps 148 ) 149 150 # Verify breakpoints 151 self.expect( 152 "breakpoint list -f", 153 "Breakpoint locations shown correctly", 154 substrs=expected_bps, 155 ) 156 157 # Run the program. 158 self.runCmd("run", RUN_SUCCEEDED) 159 160 # Check we are at line self.setup_breakpoint 161 self.expect( 162 "thread backtrace", 163 STOPPED_DUE_TO_BREAKPOINT, 164 substrs=["stop reason = breakpoint 1."], 165 ) 166 167 # Initialize the (single) watchpoint on the global variable (g_watchme) 168 if num_watchpoint_threads + num_delay_watchpoint_threads > 0: 169 self.runCmd("watchpoint set variable g_watchme") 170 for w in self.inferior_target.watchpoint_iter(): 171 self.thread_watchpoint = w 172 self.assertTrue( 173 "g_watchme" in str(self.thread_watchpoint), 174 "Watchpoint location not shown correctly", 175 ) 176 177 # Get the process 178 self.inferior_process = self.inferior_target.GetProcess() 179 180 # We should be stopped at the setup site where we can set the number of 181 # threads doing each action (break/crash/signal/watch) 182 self.assertEqual( 183 self.inferior_process.GetNumThreads(), 184 1, 185 "Expected to stop before any additional threads are spawned.", 186 ) 187 188 self.runCmd("expr num_breakpoint_threads=%d" % num_breakpoint_threads) 189 self.runCmd("expr num_crash_threads=%d" % num_crash_threads) 190 self.runCmd("expr num_signal_threads=%d" % num_signal_threads) 191 self.runCmd("expr num_watchpoint_threads=%d" % num_watchpoint_threads) 192 193 self.runCmd( 194 "expr num_delay_breakpoint_threads=%d" % num_delay_breakpoint_threads 195 ) 196 self.runCmd("expr num_delay_crash_threads=%d" % num_delay_crash_threads) 197 self.runCmd("expr num_delay_signal_threads=%d" % num_delay_signal_threads) 198 self.runCmd( 199 "expr num_delay_watchpoint_threads=%d" % num_delay_watchpoint_threads 200 ) 201 202 # Continue the inferior so threads are spawned 203 self.runCmd("continue") 204 205 # Make sure we see all the threads. The inferior program's threads all synchronize with a pseudo-barrier; that is, 206 # the inferior program ensures all threads are started and running 207 # before any thread triggers its 'event'. 208 num_threads = self.inferior_process.GetNumThreads() 209 expected_num_threads = ( 210 num_breakpoint_threads 211 + num_delay_breakpoint_threads 212 + num_signal_threads 213 + num_delay_signal_threads 214 + num_watchpoint_threads 215 + num_delay_watchpoint_threads 216 + num_crash_threads 217 + num_delay_crash_threads 218 + 1 219 ) 220 self.assertEqual( 221 num_threads, 222 expected_num_threads, 223 "Expected to see %d threads, but seeing %d. Details:\n%s" 224 % (expected_num_threads, num_threads, "\n\t".join(self.describe_threads())), 225 ) 226 227 self.signal_count = self.count_signaled_threads() 228 self.crash_count = len( 229 lldbutil.get_crashed_threads(self, self.inferior_process) 230 ) 231 232 # Run to completion (or crash) 233 while not self.inferior_done(): 234 if self.TraceOn(): 235 self.runCmd("thread backtrace all") 236 self.runCmd("continue") 237 self.signal_count += self.count_signaled_threads() 238 self.crash_count += len( 239 lldbutil.get_crashed_threads(self, self.inferior_process) 240 ) 241 242 if num_crash_threads > 0 or num_delay_crash_threads > 0: 243 # Expecting a crash 244 self.assertTrue( 245 self.crash_count > 0, 246 "Expecting at least one thread to crash. Details: %s" 247 % "\t\n".join(self.describe_threads()), 248 ) 249 250 # Ensure the zombie process is reaped 251 self.runCmd("process kill") 252 253 elif num_crash_threads == 0 and num_delay_crash_threads == 0: 254 # There should be a single active thread (the main one) which hit 255 # the breakpoint after joining 256 self.assertEqual( 257 1, 258 self.finish_breakpoint.GetHitCount(), 259 "Expected main thread (finish) breakpoint to be hit once", 260 ) 261 262 num_threads = self.inferior_process.GetNumThreads() 263 self.assertEqual( 264 1, 265 num_threads, 266 "Expecting 1 thread but seeing %d. Details:%s" 267 % (num_threads, "\n\t".join(self.describe_threads())), 268 ) 269 self.runCmd("continue") 270 271 # The inferior process should have exited without crashing 272 self.assertEqual( 273 0, self.crash_count, "Unexpected thread(s) in crashed state" 274 ) 275 self.assertEqual( 276 self.inferior_process.GetState(), lldb.eStateExited, PROCESS_EXITED 277 ) 278 279 # Verify the number of actions took place matches expected numbers 280 expected_breakpoint_threads = ( 281 num_delay_breakpoint_threads + num_breakpoint_threads 282 ) 283 breakpoint_hit_count = ( 284 self.thread_breakpoint.GetHitCount() 285 if expected_breakpoint_threads > 0 286 else 0 287 ) 288 self.assertEqual( 289 expected_breakpoint_threads, 290 breakpoint_hit_count, 291 "Expected %d breakpoint hits, but got %d" 292 % (expected_breakpoint_threads, breakpoint_hit_count), 293 ) 294 295 expected_signal_threads = num_delay_signal_threads + num_signal_threads 296 self.assertEqual( 297 expected_signal_threads, 298 self.signal_count, 299 "Expected %d stops due to signal delivery, but got %d" 300 % (expected_signal_threads, self.signal_count), 301 ) 302 303 expected_watchpoint_threads = ( 304 num_delay_watchpoint_threads + num_watchpoint_threads 305 ) 306 watchpoint_hit_count = ( 307 self.thread_watchpoint.GetHitCount() 308 if expected_watchpoint_threads > 0 309 else 0 310 ) 311 self.assertEqual( 312 expected_watchpoint_threads, 313 watchpoint_hit_count, 314 "Expected %d watchpoint hits, got %d" 315 % (expected_watchpoint_threads, watchpoint_hit_count), 316 ) 317