Prior to this patch, SBFrame/SBThread methods exhibit racy behavior if
called while the process is running, because they do not lock the
`Process::RetRunLock` mutex. If they did, they would fail, correctly
identifying that the process is not running.
Some methods _attempt_ to protect against this with the pattern:
```
ExecutionContext exe_ctx(m_opaque_sp.get(), lock); // this is a different lock
Process *process = exe_ctx.GetProcessPtr();
if (process) {
Process::StopLocker stop_locker;
if (stop_locker.TryLock(&process->GetRunLock()))
.... do work ...
```
However, this is also racy: the constructor of `ExecutionContext` will
access the frame list, which is something that can only be done once the
process is stopped.
With this patch:
1. The constructor of `ExecutionContext` now expects a `ProcessRunLock`
as an argument. It attempts to lock the run lock, and only fills in
information about frames and threads if the lock can be acquired.
Callers of the constructor are expected to check the lock.
2. All uses of ExecutionContext are adjusted to conform to the above.
3. The SBThread.cpp-defined helper function ResumeNewPlan now expects a
locked ProcessRunLock as _proof_ that the execution is stopped. It will
unlock the mutex prior to resuming the process.
This commit exposes many opportunities for early-returns, but these
would increase the diff of this patch and distract from the important
changes, so we opt not to do it here.
113 lines
4.3 KiB
Python
113 lines
4.3 KiB
Python
"""
|
|
Test that the run locker really does work to keep
|
|
us from running SB API that should only be run
|
|
while stopped. This test is mostly concerned with
|
|
what happens between launch and first stop.
|
|
"""
|
|
|
|
import lldb
|
|
import lldbsuite.test.lldbutil as lldbutil
|
|
from lldbsuite.test.decorators import *
|
|
from lldbsuite.test.lldbtest import *
|
|
|
|
|
|
class TestRunLocker(TestBase):
|
|
NO_DEBUG_INFO_TESTCASE = True
|
|
|
|
@expectedFailureAll(oslist=["windows"])
|
|
# Is flaky on Linux AArch64 buildbot.
|
|
@skipIf(oslist=["linux"], archs=["aarch64"])
|
|
def test_run_locker(self):
|
|
"""Test that the run locker is set correctly when we launch"""
|
|
self.build()
|
|
self.runlocker_test(False)
|
|
|
|
@expectedFailureAll(oslist=["windows"])
|
|
# Is flaky on Linux AArch64 buildbot.
|
|
@skipIf(oslist=["linux"], archs=["aarch64"])
|
|
def test_run_locker_stop_at_entry(self):
|
|
"""Test that the run locker is set correctly when we launch"""
|
|
self.build()
|
|
self.runlocker_test(False)
|
|
|
|
def setUp(self):
|
|
# Call super's setUp().
|
|
TestBase.setUp(self)
|
|
self.main_source_file = lldb.SBFileSpec("main.c")
|
|
|
|
def runlocker_test(self, stop_at_entry):
|
|
"""The code to stop at entry handles events slightly differently, so
|
|
we test both versions of process launch."""
|
|
|
|
target = lldbutil.run_to_breakpoint_make_target(self)
|
|
|
|
launch_info = target.GetLaunchInfo()
|
|
if stop_at_entry:
|
|
flags = launch_info.GetFlags()
|
|
launch_info.SetFlags(flags | lldb.eLaunchFlagStopAtEntry)
|
|
|
|
error = lldb.SBError()
|
|
# We are trying to do things when the process is running, so
|
|
# we have to run the debugger asynchronously.
|
|
self.dbg.SetAsync(True)
|
|
|
|
listener = lldb.SBListener("test-run-lock-listener")
|
|
launch_info.SetListener(listener)
|
|
process = target.Launch(launch_info, error)
|
|
self.assertSuccess(error, "Launched the process")
|
|
|
|
event = lldb.SBEvent()
|
|
|
|
event_result = listener.WaitForEvent(10, event)
|
|
self.assertTrue(event_result, "timed out waiting for launch")
|
|
state_type = lldb.SBProcess.GetStateFromEvent(event)
|
|
# We don't always see a launching...
|
|
if state_type == lldb.eStateLaunching:
|
|
event_result = listener.WaitForEvent(10, event)
|
|
self.assertTrue(
|
|
event_result, "Timed out waiting for running after launching"
|
|
)
|
|
state_type = lldb.SBProcess.GetStateFromEvent(event)
|
|
|
|
self.assertState(state_type, lldb.eStateRunning, "Didn't get a running event")
|
|
|
|
# We aren't checking the entry state, but just making sure
|
|
# the running state is set properly if we continue in this state.
|
|
|
|
if stop_at_entry:
|
|
event_result = listener.WaitForEvent(10, event)
|
|
self.assertTrue(event_result, "Timed out waiting for stop at entry stop")
|
|
state_type = lldb.SBProcess.GetStateFromEvent(event)
|
|
self.assertState(state_type, eStateStopped, "Stop at entry stopped")
|
|
process.Continue()
|
|
|
|
# Okay, now the process is running, make sure we can't do things
|
|
# you aren't supposed to do while running, and that we get some
|
|
# actual error:
|
|
val = target.EvaluateExpression("SomethingToCall()")
|
|
# There was a bug [#93313] in the printing that would cause repr to crash, so I'm
|
|
# testing that separately.
|
|
self.assertIn(
|
|
"can't evaluate expressions when the process is running",
|
|
repr(val),
|
|
"repr works"
|
|
)
|
|
error = val.GetError()
|
|
self.assertTrue(error.Fail(), "Failed to run expression")
|
|
self.assertIn(
|
|
"can't evaluate expressions when the process is running",
|
|
error.GetCString(),
|
|
"Stopped by stop locker",
|
|
)
|
|
|
|
# This should also fail if we try to use the script interpreter directly:
|
|
interp = self.dbg.GetCommandInterpreter()
|
|
result = lldb.SBCommandReturnObject()
|
|
ret = interp.HandleCommand(
|
|
"script var = lldb.frame.EvaluateExpression('SomethingToCall()'); var.GetError().GetCString()",
|
|
result,
|
|
)
|
|
self.assertIn(
|
|
"can't evaluate expressions when the process is running", result.GetOutput()
|
|
)
|