286 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			286 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
"""
 | 
						|
A stress-test of sorts for LLDB's handling of threads in the inferior.
 | 
						|
 | 
						|
This test sets a breakpoint in the main thread where test parameters (numbers of
 | 
						|
threads) can be adjusted, runs the inferior to that point, and modifies the
 | 
						|
locals that control the event thread counts. This test also sets a breakpoint in
 | 
						|
breakpoint_func (the function executed by each 'breakpoint' thread) and a
 | 
						|
watchpoint on a global modified in watchpoint_func. The inferior is continued
 | 
						|
until exit or a crash takes place, and the number of events seen by LLDB is
 | 
						|
verified to match the expected number of events.
 | 
						|
"""
 | 
						|
 | 
						|
from __future__ import print_function
 | 
						|
 | 
						|
 | 
						|
import unittest2
 | 
						|
import lldb
 | 
						|
from lldbsuite.test.decorators import *
 | 
						|
from lldbsuite.test.lldbtest import *
 | 
						|
from lldbsuite.test import lldbutil
 | 
						|
 | 
						|
 | 
						|
class ConcurrentEventsBase(TestBase):
 | 
						|
 | 
						|
    # Concurrency is the primary test factor here, not debug info variants.
 | 
						|
    NO_DEBUG_INFO_TESTCASE = True
 | 
						|
 | 
						|
    def setUp(self):
 | 
						|
        # Call super's setUp().
 | 
						|
        super(ConcurrentEventsBase, self).setUp()
 | 
						|
        # Find the line number for our breakpoint.
 | 
						|
        self.filename = 'main.cpp'
 | 
						|
        self.thread_breakpoint_line = line_number(
 | 
						|
            self.filename, '// Set breakpoint here')
 | 
						|
        self.setup_breakpoint_line = line_number(
 | 
						|
            self.filename, '// Break here and adjust num')
 | 
						|
        self.finish_breakpoint_line = line_number(
 | 
						|
            self.filename, '// Break here and verify one thread is active')
 | 
						|
 | 
						|
    def describe_threads(self):
 | 
						|
        ret = []
 | 
						|
        for x in self.inferior_process:
 | 
						|
            id = x.GetIndexID()
 | 
						|
            reason = x.GetStopReason()
 | 
						|
            status = "stopped" if x.IsStopped() else "running"
 | 
						|
            reason_str = lldbutil.stop_reason_to_str(reason)
 | 
						|
            if reason == lldb.eStopReasonBreakpoint:
 | 
						|
                bpid = x.GetStopReasonDataAtIndex(0)
 | 
						|
                bp = self.inferior_target.FindBreakpointByID(bpid)
 | 
						|
                reason_str = "%s hit %d times" % (
 | 
						|
                    lldbutil.get_description(bp), bp.GetHitCount())
 | 
						|
            elif reason == lldb.eStopReasonWatchpoint:
 | 
						|
                watchid = x.GetStopReasonDataAtIndex(0)
 | 
						|
                watch = self.inferior_target.FindWatchpointByID(watchid)
 | 
						|
                reason_str = "%s hit %d times" % (
 | 
						|
                    lldbutil.get_description(watch), watch.GetHitCount())
 | 
						|
            elif reason == lldb.eStopReasonSignal:
 | 
						|
                signals = self.inferior_process.GetUnixSignals()
 | 
						|
                signal_name = signals.GetSignalAsCString(
 | 
						|
                    x.GetStopReasonDataAtIndex(0))
 | 
						|
                reason_str = "signal %s" % signal_name
 | 
						|
 | 
						|
            location = "\t".join([lldbutil.get_description(
 | 
						|
                x.GetFrameAtIndex(i)) for i in range(x.GetNumFrames())])
 | 
						|
            ret.append(
 | 
						|
                "thread %d %s due to %s at\n\t%s" %
 | 
						|
                (id, status, reason_str, location))
 | 
						|
        return ret
 | 
						|
 | 
						|
    def add_breakpoint(self, line, descriptions):
 | 
						|
        """ Adds a breakpoint at self.filename:line and appends its description to descriptions, and
 | 
						|
            returns the LLDB SBBreakpoint object.
 | 
						|
        """
 | 
						|
 | 
						|
        bpno = lldbutil.run_break_set_by_file_and_line(
 | 
						|
            self, self.filename, line, num_expected_locations=-1)
 | 
						|
        bp = self.inferior_target.FindBreakpointByID(bpno)
 | 
						|
        descriptions.append(
 | 
						|
            ": file = 'main.cpp', line = %d" %
 | 
						|
            self.finish_breakpoint_line)
 | 
						|
        return bp
 | 
						|
 | 
						|
    def inferior_done(self):
 | 
						|
        """ Returns true if the inferior is done executing all the event threads (and is stopped at self.finish_breakpoint,
 | 
						|
            or has terminated execution.
 | 
						|
        """
 | 
						|
        return self.finish_breakpoint.GetHitCount() > 0 or \
 | 
						|
            self.crash_count > 0 or \
 | 
						|
            self.inferior_process.GetState() == lldb.eStateExited
 | 
						|
 | 
						|
    def count_signaled_threads(self):
 | 
						|
        count = 0
 | 
						|
        for thread in self.inferior_process:
 | 
						|
            if thread.GetStopReason() == lldb.eStopReasonSignal and thread.GetStopReasonDataAtIndex(
 | 
						|
                    0) == self.inferior_process.GetUnixSignals().GetSignalNumberFromName('SIGUSR1'):
 | 
						|
                count += 1
 | 
						|
        return count
 | 
						|
 | 
						|
    def do_thread_actions(self,
 | 
						|
                          num_breakpoint_threads=0,
 | 
						|
                          num_signal_threads=0,
 | 
						|
                          num_watchpoint_threads=0,
 | 
						|
                          num_crash_threads=0,
 | 
						|
                          num_delay_breakpoint_threads=0,
 | 
						|
                          num_delay_signal_threads=0,
 | 
						|
                          num_delay_watchpoint_threads=0,
 | 
						|
                          num_delay_crash_threads=0):
 | 
						|
        """ Sets a breakpoint in the main thread where test parameters (numbers of threads) can be adjusted, runs the inferior
 | 
						|
            to that point, and modifies the locals that control the event thread counts. Also sets a breakpoint in
 | 
						|
            breakpoint_func (the function executed by each 'breakpoint' thread) and a watchpoint on a global modified in
 | 
						|
            watchpoint_func. The inferior is continued until exit or a crash takes place, and the number of events seen by LLDB
 | 
						|
            is verified to match the expected number of events.
 | 
						|
        """
 | 
						|
        exe = self.getBuildArtifact("a.out")
 | 
						|
        self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)
 | 
						|
 | 
						|
        # Get the target
 | 
						|
        self.inferior_target = self.dbg.GetSelectedTarget()
 | 
						|
 | 
						|
        expected_bps = []
 | 
						|
 | 
						|
        # Initialize all the breakpoints (main thread/aux thread)
 | 
						|
        self.setup_breakpoint = self.add_breakpoint(
 | 
						|
            self.setup_breakpoint_line, expected_bps)
 | 
						|
        self.finish_breakpoint = self.add_breakpoint(
 | 
						|
            self.finish_breakpoint_line, expected_bps)
 | 
						|
 | 
						|
        # Set the thread breakpoint
 | 
						|
        if num_breakpoint_threads + num_delay_breakpoint_threads > 0:
 | 
						|
            self.thread_breakpoint = self.add_breakpoint(
 | 
						|
                self.thread_breakpoint_line, expected_bps)
 | 
						|
 | 
						|
        # Verify breakpoints
 | 
						|
        self.expect(
 | 
						|
            "breakpoint list -f",
 | 
						|
            "Breakpoint locations shown correctly",
 | 
						|
            substrs=expected_bps)
 | 
						|
 | 
						|
        # Run the program.
 | 
						|
        self.runCmd("run", RUN_SUCCEEDED)
 | 
						|
 | 
						|
        # Check we are at line self.setup_breakpoint
 | 
						|
        self.expect("thread backtrace", STOPPED_DUE_TO_BREAKPOINT,
 | 
						|
                    substrs=["stop reason = breakpoint 1."])
 | 
						|
 | 
						|
        # Initialize the (single) watchpoint on the global variable (g_watchme)
 | 
						|
        if num_watchpoint_threads + num_delay_watchpoint_threads > 0:
 | 
						|
            self.runCmd("watchpoint set variable g_watchme")
 | 
						|
            for w in self.inferior_target.watchpoint_iter():
 | 
						|
                self.thread_watchpoint = w
 | 
						|
                self.assertTrue(
 | 
						|
                    "g_watchme" in str(
 | 
						|
                        self.thread_watchpoint),
 | 
						|
                    "Watchpoint location not shown correctly")
 | 
						|
 | 
						|
        # Get the process
 | 
						|
        self.inferior_process = self.inferior_target.GetProcess()
 | 
						|
 | 
						|
        # We should be stopped at the setup site where we can set the number of
 | 
						|
        # threads doing each action (break/crash/signal/watch)
 | 
						|
        self.assertEqual(
 | 
						|
            self.inferior_process.GetNumThreads(),
 | 
						|
            1,
 | 
						|
            'Expected to stop before any additional threads are spawned.')
 | 
						|
 | 
						|
        self.runCmd("expr num_breakpoint_threads=%d" % num_breakpoint_threads)
 | 
						|
        self.runCmd("expr num_crash_threads=%d" % num_crash_threads)
 | 
						|
        self.runCmd("expr num_signal_threads=%d" % num_signal_threads)
 | 
						|
        self.runCmd("expr num_watchpoint_threads=%d" % num_watchpoint_threads)
 | 
						|
 | 
						|
        self.runCmd(
 | 
						|
            "expr num_delay_breakpoint_threads=%d" %
 | 
						|
            num_delay_breakpoint_threads)
 | 
						|
        self.runCmd(
 | 
						|
            "expr num_delay_crash_threads=%d" %
 | 
						|
            num_delay_crash_threads)
 | 
						|
        self.runCmd(
 | 
						|
            "expr num_delay_signal_threads=%d" %
 | 
						|
            num_delay_signal_threads)
 | 
						|
        self.runCmd(
 | 
						|
            "expr num_delay_watchpoint_threads=%d" %
 | 
						|
            num_delay_watchpoint_threads)
 | 
						|
 | 
						|
        # Continue the inferior so threads are spawned
 | 
						|
        self.runCmd("continue")
 | 
						|
 | 
						|
        # Make sure we see all the threads. The inferior program's threads all synchronize with a pseudo-barrier; that is,
 | 
						|
        # the inferior program ensures all threads are started and running
 | 
						|
        # before any thread triggers its 'event'.
 | 
						|
        num_threads = self.inferior_process.GetNumThreads()
 | 
						|
        expected_num_threads = num_breakpoint_threads + num_delay_breakpoint_threads \
 | 
						|
            + num_signal_threads + num_delay_signal_threads \
 | 
						|
            + num_watchpoint_threads + num_delay_watchpoint_threads \
 | 
						|
            + num_crash_threads + num_delay_crash_threads + 1
 | 
						|
        self.assertEqual(
 | 
						|
            num_threads,
 | 
						|
            expected_num_threads,
 | 
						|
            'Expected to see %d threads, but seeing %d. Details:\n%s' %
 | 
						|
            (expected_num_threads,
 | 
						|
             num_threads,
 | 
						|
             "\n\t".join(
 | 
						|
                 self.describe_threads())))
 | 
						|
 | 
						|
        self.signal_count = self.count_signaled_threads()
 | 
						|
        self.crash_count = len(
 | 
						|
            lldbutil.get_crashed_threads(
 | 
						|
                self, self.inferior_process))
 | 
						|
 | 
						|
        # Run to completion (or crash)
 | 
						|
        while not self.inferior_done():
 | 
						|
            if self.TraceOn():
 | 
						|
                self.runCmd("thread backtrace all")
 | 
						|
            self.runCmd("continue")
 | 
						|
            self.signal_count += self.count_signaled_threads()
 | 
						|
            self.crash_count += len(
 | 
						|
                lldbutil.get_crashed_threads(
 | 
						|
                    self, self.inferior_process))
 | 
						|
 | 
						|
        if num_crash_threads > 0 or num_delay_crash_threads > 0:
 | 
						|
            # Expecting a crash
 | 
						|
            self.assertTrue(
 | 
						|
                self.crash_count > 0,
 | 
						|
                "Expecting at least one thread to crash. Details: %s" %
 | 
						|
                "\t\n".join(
 | 
						|
                    self.describe_threads()))
 | 
						|
 | 
						|
            # Ensure the zombie process is reaped
 | 
						|
            self.runCmd("process kill")
 | 
						|
 | 
						|
        elif num_crash_threads == 0 and num_delay_crash_threads == 0:
 | 
						|
            # There should be a single active thread (the main one) which hit
 | 
						|
            # the breakpoint after joining
 | 
						|
            self.assertEqual(
 | 
						|
                1,
 | 
						|
                self.finish_breakpoint.GetHitCount(),
 | 
						|
                "Expected main thread (finish) breakpoint to be hit once")
 | 
						|
 | 
						|
            num_threads = self.inferior_process.GetNumThreads()
 | 
						|
            self.assertEqual(
 | 
						|
                1,
 | 
						|
                num_threads,
 | 
						|
                "Expecting 1 thread but seeing %d. Details:%s" %
 | 
						|
                (num_threads,
 | 
						|
                 "\n\t".join(
 | 
						|
                     self.describe_threads())))
 | 
						|
            self.runCmd("continue")
 | 
						|
 | 
						|
            # The inferior process should have exited without crashing
 | 
						|
            self.assertEqual(
 | 
						|
                0,
 | 
						|
                self.crash_count,
 | 
						|
                "Unexpected thread(s) in crashed state")
 | 
						|
            self.assertEqual(
 | 
						|
                self.inferior_process.GetState(),
 | 
						|
                lldb.eStateExited,
 | 
						|
                PROCESS_EXITED)
 | 
						|
 | 
						|
            # Verify the number of actions took place matches expected numbers
 | 
						|
            expected_breakpoint_threads = num_delay_breakpoint_threads + num_breakpoint_threads
 | 
						|
            breakpoint_hit_count = self.thread_breakpoint.GetHitCount(
 | 
						|
            ) if expected_breakpoint_threads > 0 else 0
 | 
						|
            self.assertEqual(
 | 
						|
                expected_breakpoint_threads,
 | 
						|
                breakpoint_hit_count,
 | 
						|
                "Expected %d breakpoint hits, but got %d" %
 | 
						|
                (expected_breakpoint_threads,
 | 
						|
                 breakpoint_hit_count))
 | 
						|
 | 
						|
            expected_signal_threads = num_delay_signal_threads + num_signal_threads
 | 
						|
            self.assertEqual(
 | 
						|
                expected_signal_threads,
 | 
						|
                self.signal_count,
 | 
						|
                "Expected %d stops due to signal delivery, but got %d" %
 | 
						|
                (expected_signal_threads,
 | 
						|
                 self.signal_count))
 | 
						|
 | 
						|
            expected_watchpoint_threads = num_delay_watchpoint_threads + num_watchpoint_threads
 | 
						|
            watchpoint_hit_count = self.thread_watchpoint.GetHitCount(
 | 
						|
            ) if expected_watchpoint_threads > 0 else 0
 | 
						|
            self.assertEqual(
 | 
						|
                expected_watchpoint_threads,
 | 
						|
                watchpoint_hit_count,
 | 
						|
                "Expected %d watchpoint hits, got %d" %
 | 
						|
                (expected_watchpoint_threads,
 | 
						|
                 watchpoint_hit_count))
 |