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