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