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