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