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