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