brintos

brintos / llvm-project-archived public Read only

0
0
Text · 13.9 KiB · a45b420 Raw
354 lines · python
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 of5threads) can be adjusted, runs the inferior to that point, and modifies the6locals that control the event thread counts. This test also sets a breakpoint in7breakpoint_func (the function executed by each 'breakpoint' thread) and a8watchpoint on a global modified in watchpoint_func. The inferior is continued9until exit or a crash takes place, and the number of events seen by LLDB is10verified to match the expected number of events.11"""12 13 14import lldb15from lldbsuite.test.decorators import *16from lldbsuite.test.lldbtest import *17from lldbsuite.test import lldbutil18 19 20class ConcurrentEventsBase(TestBase):21    # Concurrency is the primary test factor here, not debug info variants.22    NO_DEBUG_INFO_TESTCASE = True23 24    def setUp(self):25        # Call super's setUp().26        super(ConcurrentEventsBase, self).setUp()27        # Find the line number for our breakpoint.28        self.filename = "main.cpp"29        self.thread_breakpoint_line = line_number(30            self.filename, "// Set breakpoint here"31        )32        self.setup_breakpoint_line = line_number(33            self.filename, "// Break here and adjust num"34        )35        self.finish_breakpoint_line = line_number(36            self.filename, "// Break here and verify one thread is active"37        )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),51                    bp.GetHitCount(),52                )53            elif reason == lldb.eStopReasonWatchpoint:54                watchid = x.GetStopReasonDataAtIndex(0)55                watch = self.inferior_target.FindWatchpointByID(watchid)56                reason_str = "%s hit %d times" % (57                    lldbutil.get_description(watch),58                    watch.GetHitCount(),59                )60            elif reason == lldb.eStopReasonSignal:61                signals = self.inferior_process.GetUnixSignals()62                signal_name = signals.GetSignalAsCString(x.GetStopReasonDataAtIndex(0))63                reason_str = "signal %s" % signal_name64 65            location = "\t".join(66                [67                    lldbutil.get_description(x.GetFrameAtIndex(i))68                    for i in range(x.GetNumFrames())69                ]70            )71            ret.append(72                "thread %d %s due to %s at\n\t%s" % (id, status, reason_str, location)73            )74        return ret75 76    def add_breakpoint(self, line, descriptions):77        """Adds a breakpoint at self.filename:line and appends its description to descriptions, and78        returns the LLDB SBBreakpoint object.79        """80 81        bpno = lldbutil.run_break_set_by_file_and_line(82            self, self.filename, line, num_expected_locations=-183        )84        bp = self.inferior_target.FindBreakpointByID(bpno)85        descriptions.append(": file = 'main.cpp', line = %d" % line)86        return bp87 88    def inferior_done(self):89        """Returns true if the inferior is done executing all the event threads (and is stopped at self.finish_breakpoint,90        or has terminated execution.91        """92        return (93            self.finish_breakpoint.GetHitCount() > 094            or self.crash_count > 095            or self.inferior_process.GetState() == lldb.eStateExited96        )97 98    def count_signaled_threads(self):99        count = 0100        for thread in self.inferior_process:101            if (102                thread.GetStopReason() == lldb.eStopReasonSignal103                and thread.GetStopReasonDataAtIndex(0)104                == self.inferior_process.GetUnixSignals().GetSignalNumberFromName(105                    "SIGUSR1"106                )107            ):108                count += 1109        return count110 111    def do_thread_actions(112        self,113        num_breakpoint_threads=0,114        num_signal_threads=0,115        num_watchpoint_threads=0,116        num_crash_threads=0,117        num_delay_breakpoint_threads=0,118        num_delay_signal_threads=0,119        num_delay_watchpoint_threads=0,120        num_delay_crash_threads=0,121    ):122        """Sets a breakpoint in the main thread where test parameters (numbers of threads) can be adjusted, runs the inferior123        to that point, and modifies the locals that control the event thread counts. Also sets a breakpoint in124        breakpoint_func (the function executed by each 'breakpoint' thread) and a watchpoint on a global modified in125        watchpoint_func. The inferior is continued until exit or a crash takes place, and the number of events seen by LLDB126        is verified to match the expected number of events.127        """128        exe = self.getBuildArtifact("a.out")129        self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)130 131        # Get the target132        self.inferior_target = self.dbg.GetSelectedTarget()133 134        expected_bps = []135 136        # Initialize all the breakpoints (main thread/aux thread)137        self.setup_breakpoint = self.add_breakpoint(138            self.setup_breakpoint_line, expected_bps139        )140        self.finish_breakpoint = self.add_breakpoint(141            self.finish_breakpoint_line, expected_bps142        )143 144        # Set the thread breakpoint145        if num_breakpoint_threads + num_delay_breakpoint_threads > 0:146            self.thread_breakpoint = self.add_breakpoint(147                self.thread_breakpoint_line, expected_bps148            )149 150        # Verify breakpoints151        self.expect(152            "breakpoint list -f",153            "Breakpoint locations shown correctly",154            substrs=expected_bps,155        )156 157        # Run the program.158        self.runCmd("run", RUN_SUCCEEDED)159 160        # Check we are at line self.setup_breakpoint161        self.expect(162            "thread backtrace",163            STOPPED_DUE_TO_BREAKPOINT,164            substrs=["stop reason = breakpoint 1."],165        )166 167        # Initialize the (single) watchpoint on the global variable (g_watchme)168        if num_watchpoint_threads + num_delay_watchpoint_threads > 0:169            # The concurrent tests have multiple threads modifying a variable170            # with the same value.  The default "modify" style watchpoint will171            # only report this as 1 hit for all threads, because they all wrote172            # the same value.  The testsuite needs "write" style watchpoints to173            # get the correct number of hits reported.174            self.runCmd("watchpoint set variable -w write g_watchme")175            for w in self.inferior_target.watchpoint_iter():176                self.thread_watchpoint = w177                self.assertTrue(178                    "g_watchme" in str(self.thread_watchpoint),179                    "Watchpoint location not shown correctly",180                )181 182        # Get the process183        self.inferior_process = self.inferior_target.GetProcess()184 185        # We should be stopped at the setup site where we can set the number of186        # threads doing each action (break/crash/signal/watch)187        self.assertEqual(188            self.inferior_process.GetNumThreads(),189            1,190            "Expected to stop before any additional threads are spawned.",191        )192 193        self.runCmd("expr num_breakpoint_threads=%d" % num_breakpoint_threads)194        self.runCmd("expr num_crash_threads=%d" % num_crash_threads)195        self.runCmd("expr num_signal_threads=%d" % num_signal_threads)196        self.runCmd("expr num_watchpoint_threads=%d" % num_watchpoint_threads)197 198        self.runCmd(199            "expr num_delay_breakpoint_threads=%d" % num_delay_breakpoint_threads200        )201        self.runCmd("expr num_delay_crash_threads=%d" % num_delay_crash_threads)202        self.runCmd("expr num_delay_signal_threads=%d" % num_delay_signal_threads)203        self.runCmd(204            "expr num_delay_watchpoint_threads=%d" % num_delay_watchpoint_threads205        )206 207        # Continue the inferior so threads are spawned208        self.runCmd("continue")209 210        # Make sure we see all the threads. The inferior program's threads all synchronize with a pseudo-barrier; that is,211        # the inferior program ensures all threads are started and running212        # before any thread triggers its 'event'.213        num_threads = self.inferior_process.GetNumThreads()214        expected_num_threads = (215            num_breakpoint_threads216            + num_delay_breakpoint_threads217            + num_signal_threads218            + num_delay_signal_threads219            + num_watchpoint_threads220            + num_delay_watchpoint_threads221            + num_crash_threads222            + num_delay_crash_threads223            + 1224        )225        self.assertEqual(226            num_threads,227            expected_num_threads,228            "Expected to see %d threads, but seeing %d. Details:\n%s"229            % (expected_num_threads, num_threads, "\n\t".join(self.describe_threads())),230        )231 232        self.signal_count = self.count_signaled_threads()233        self.crash_count = len(234            lldbutil.get_crashed_threads(self, self.inferior_process)235        )236 237        # Run to completion (or crash)238        while not self.inferior_done():239            if self.TraceOn():240                self.runCmd("thread backtrace all")241            self.runCmd("continue")242            self.signal_count += self.count_signaled_threads()243            self.crash_count += len(244                lldbutil.get_crashed_threads(self, self.inferior_process)245            )246 247        if num_crash_threads > 0 or num_delay_crash_threads > 0:248            # Expecting a crash249            self.assertTrue(250                self.crash_count > 0,251                "Expecting at least one thread to crash. Details: %s"252                % "\t\n".join(self.describe_threads()),253            )254 255            # Ensure the zombie process is reaped256            self.runCmd("process kill")257 258        elif num_crash_threads == 0 and num_delay_crash_threads == 0:259            # There should be a single active thread (the main one) which hit260            # the breakpoint after joining261            self.assertEqual(262                1,263                self.finish_breakpoint.GetHitCount(),264                "Expected main thread (finish) breakpoint to be hit once",265            )266 267            # There should be a single active thread (the main one) which hit268            # the breakpoint after joining.  Depending on the pthread269            # implementation we may have a worker thread finishing the pthread_join()270            # after it has returned.  Filter the threads to only count those271            # with user functions on them from our test case file,272            # lldb/test/API/functionalities/thread/concurrent_events/main.cpp273            user_code_funcnames = [274                "breakpoint_func",275                "crash_func",276                "do_action_args",277                "dotest",278                "main",279                "register_signal_handler",280                "signal_func",281                "sigusr1_handler",282                "start_threads",283                "watchpoint_func",284            ]285            num_threads_with_usercode = 0286            for t in self.inferior_process.threads:287                thread_has_user_code = False288                for f in t.frames:289                    for funcname in user_code_funcnames:290                        if funcname in f.GetDisplayFunctionName():291                            thread_has_user_code = True292                            break293 294                    if thread_has_user_code:295                        break296 297                if thread_has_user_code:298                    num_threads_with_usercode += 1299 300            self.assertEqual(301                1,302                num_threads_with_usercode,303                "Expecting 1 thread but seeing %d. Details:%s"304                % (num_threads_with_usercode, "\n\t".join(self.describe_threads())),305            )306            self.runCmd("continue")307 308            # The inferior process should have exited without crashing309            self.assertEqual(310                0, self.crash_count, "Unexpected thread(s) in crashed state"311            )312            self.assertEqual(313                self.inferior_process.GetState(), lldb.eStateExited, PROCESS_EXITED314            )315 316            # Verify the number of actions took place matches expected numbers317            expected_breakpoint_threads = (318                num_delay_breakpoint_threads + num_breakpoint_threads319            )320            breakpoint_hit_count = (321                self.thread_breakpoint.GetHitCount()322                if expected_breakpoint_threads > 0323                else 0324            )325            self.assertEqual(326                expected_breakpoint_threads,327                breakpoint_hit_count,328                "Expected %d breakpoint hits, but got %d"329                % (expected_breakpoint_threads, breakpoint_hit_count),330            )331 332            expected_signal_threads = num_delay_signal_threads + num_signal_threads333            self.assertEqual(334                expected_signal_threads,335                self.signal_count,336                "Expected %d stops due to signal delivery, but got %d"337                % (expected_signal_threads, self.signal_count),338            )339 340            expected_watchpoint_threads = (341                num_delay_watchpoint_threads + num_watchpoint_threads342            )343            watchpoint_hit_count = (344                self.thread_watchpoint.GetHitCount()345                if expected_watchpoint_threads > 0346                else 0347            )348            self.assertEqual(349                expected_watchpoint_threads,350                watchpoint_hit_count,351                "Expected %d watchpoint hits, got %d"352                % (expected_watchpoint_threads, watchpoint_hit_count),353            )354