Skip to content

Commit ab745c2

Browse files
author
Greg Clayton
committed
Fix stepping a virtual thread when the python operating system was enabled.
The OperatingSystem plug-ins allow code to detect threads in memory and then say "memory thread 0x11111" is backed by the actual thread 1. You can then single step these virtual threads. A problem arose when thread specific breakpoints were used during thread plans where we would say "set a breakpoint on thread 0x11111" and we would hit the breakpoint on the real thread 1 and the thread IDs wouldn't match and we would get rid of the "stopped at breakpoint" stop info due to this mismatch. Code was added to ensure these events get forwarded and thus allow single stepping a memory thread to work correctly. Added a test case for this as well. <rdar://problem/19211770> llvm-svn: 234364
1 parent 61a5bbf commit ab745c2

File tree

6 files changed

+105
-7
lines changed

6 files changed

+105
-7
lines changed

lldb/include/lldb/Target/StopInfo.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,12 @@ class StopInfo
116116
else
117117
m_description.clear();
118118
}
119+
120+
virtual bool
121+
IsValidForOperatingSystemThread (Thread &thread)
122+
{
123+
return true;
124+
}
119125

120126
// Sometimes the thread plan logic will know that it wants a given stop to stop or not,
121127
// regardless of what the ordinary logic for that StopInfo would dictate. The main example

lldb/source/Plugins/OperatingSystem/Python/OperatingSystemPython.cpp

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -178,11 +178,18 @@ OperatingSystemPython::UpdateThreadList (ThreadList &old_thread_list,
178178

179179
Log *log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_OS));
180180

181-
// First thing we have to do is get the API lock, and the run lock. We're going to change the thread
182-
// content of the process, and we're going to use python, which requires the API lock to do it.
183-
// So get & hold that. This is a recursive lock so we can grant it to any Python code called on the stack below us.
181+
// First thing we have to do is to try to get the API lock, and the run lock.
182+
// We're going to change the thread content of the process, and we're going
183+
// to use python, which requires the API lock to do it.
184+
//
185+
// If someone already has the API lock, that is ok, we just want to avoid
186+
// external code from making new API calls while this call is happening.
187+
//
188+
// This is a recursive lock so we can grant it to any Python code called on
189+
// the stack below us.
184190
Target &target = m_process->GetTarget();
185-
Mutex::Locker api_locker (target.GetAPIMutex());
191+
Mutex::Locker api_locker;
192+
api_locker.TryLock(target.GetAPIMutex());
186193

187194
if (log)
188195
log->Printf ("OperatingSystemPython::UpdateThreadList() fetching thread data from python for pid %" PRIu64, m_process->GetID());

lldb/source/Plugins/Process/Utility/StopInfoMachException.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -498,12 +498,15 @@ StopInfoMachException::CreateStopReasonWithMachException
498498
// If the breakpoint is for this thread, then we'll report the hit, but if it is for another thread,
499499
// we can just report no reason. We don't need to worry about stepping over the breakpoint here, that
500500
// will be taken care of when the thread resumes and notices that there's a breakpoint under the pc.
501-
if (bp_site_sp->ValidForThisThread (&thread))
501+
// If we have an operating system plug-in, we might have set a thread specific breakpoint using the
502+
// operating system thread ID, so we can't make any assumptions about the thread ID so we must always
503+
// report the breakpoint regardless of the thread.
504+
if (bp_site_sp->ValidForThisThread (&thread) || thread.GetProcess()->GetOperatingSystem () != NULL)
502505
return StopInfo::CreateStopReasonWithBreakpointSiteID (thread, bp_site_sp->GetID());
503506
else
504507
return StopInfoSP();
505508
}
506-
509+
507510
// Don't call this a trace if we weren't single stepping this thread.
508511
if (is_trace_if_actual_breakpoint_missing && thread.GetTemporaryResumeState() == eStateStepping)
509512
{

lldb/source/Plugins/Process/Utility/ThreadMemory.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ ThreadMemory::CalculateStopInfo ()
105105
if (m_backing_thread_sp)
106106
{
107107
lldb::StopInfoSP backing_stop_info_sp (m_backing_thread_sp->GetPrivateStopInfo());
108-
if (backing_stop_info_sp)
108+
if (backing_stop_info_sp && backing_stop_info_sp->IsValidForOperatingSystemThread(*this))
109109
{
110110
backing_stop_info_sp->SetThread (shared_from_this());
111111
SetStopInfo (backing_stop_info_sp);

lldb/source/Target/StopInfo.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,19 @@ class StopInfoBreakpoint : public StopInfo
163163
{
164164
}
165165

166+
virtual bool
167+
IsValidForOperatingSystemThread (Thread &thread)
168+
{
169+
ProcessSP process_sp (thread.GetProcess());
170+
if (process_sp)
171+
{
172+
BreakpointSiteSP bp_site_sp (process_sp->GetBreakpointSiteList().FindByID (m_value));
173+
if (bp_site_sp)
174+
return bp_site_sp->ValidForThisThread (&thread);
175+
}
176+
return false;
177+
}
178+
166179
virtual StopReason
167180
GetStopReason () const
168181
{

lldb/test/functionalities/plugins/python_os_plugin/TestPythonOSPlugin.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,19 @@ def test_python_os_plugin_dwarf(self):
2626
self.buildDwarf()
2727
self.run_python_os_funcionality()
2828

29+
@skipUnlessDarwin
30+
@dsym_test
31+
def test_python_os_step_dsym(self):
32+
"""Test that the Python operating system plugin works correctly when single stepping a virtual thread"""
33+
self.buildDsym()
34+
self.run_python_os_step()
35+
36+
@dwarf_test
37+
def run_python_os_step_dwarf(self):
38+
"""Test that the Python operating system plugin works correctly when single stepping a virtual thread"""
39+
self.buildDwarf()
40+
self.run_python_os_step()
41+
2942
def verify_os_thread_registers(self, thread):
3043
frame = thread.GetFrameAtIndex(0)
3144
registers = frame.GetRegisters().GetValueAtIndex(0)
@@ -94,6 +107,62 @@ def run_python_os_funcionality(self):
94107
thread = process.GetThreadByID(0x333333333);
95108
self.assertFalse (thread.IsValid(), "Make sure there is no thread 0x333333333 after we unload the python OS plug-in");
96109

110+
def run_python_os_step(self):
111+
"""Test that the Python operating system plugin works correctly and allows single stepping of a virtual thread that is backed by a real thread"""
112+
113+
# Set debugger into synchronous mode
114+
self.dbg.SetAsync(False)
115+
116+
# Create a target by the debugger.
117+
cwd = os.getcwd()
118+
exe = os.path.join(cwd, "a.out")
119+
python_os_plugin_path = os.path.join(cwd, "operating_system2.py")
120+
target = self.dbg.CreateTarget(exe)
121+
self.assertTrue(target, VALID_TARGET)
122+
123+
# Set breakpoints inside and outside methods that take pointers to the containing struct.
124+
lldbutil.run_break_set_by_source_regexp (self, "// Set breakpoint here")
125+
126+
# Register our shared libraries for remote targets so they get automatically uploaded
127+
arguments = None
128+
environment = None
129+
130+
# Now launch the process, and do not stop at entry point.
131+
process = target.LaunchSimple (arguments, environment, self.get_process_working_directory())
132+
self.assertTrue(process, PROCESS_IS_VALID)
133+
134+
# Make sure there are no OS plug-in created thread when we first stop at our breakpoint in main
135+
thread = process.GetThreadByID(0x111111111);
136+
self.assertFalse (thread.IsValid(), "Make sure there is no thread 0x111111111 before we load the python OS plug-in");
137+
138+
139+
# Now load the python OS plug-in which should update the thread list and we should have
140+
# OS plug-in created threads with the IDs: 0x111111111, 0x222222222, 0x333333333
141+
command = "settings set target.process.python-os-plugin-path '%s'" % python_os_plugin_path
142+
self.dbg.HandleCommand(command)
143+
144+
# Verify our OS plug-in threads showed up
145+
thread = process.GetThreadByID(0x111111111);
146+
self.assertTrue (thread.IsValid(), "Make sure there is a thread 0x111111111 after we load the python OS plug-in");
147+
148+
frame = thread.GetFrameAtIndex(0)
149+
self.assertTrue(frame.IsValid(), "Make sure we get a frame from thread 0x111111111")
150+
line_entry = frame.GetLineEntry()
151+
152+
self.assertTrue(line_entry.GetFileSpec().GetFilename() == 'main.c', "Make sure we stopped on line 5 in main.c")
153+
self.assertTrue(line_entry.GetLine() == 5, "Make sure we stopped on line 5 in main.c")
154+
155+
# Now single step thread 0x111111111 and make sure it does what we need it to
156+
thread.StepOver()
157+
158+
frame = thread.GetFrameAtIndex(0)
159+
self.assertTrue(frame.IsValid(), "Make sure we get a frame from thread 0x111111111")
160+
line_entry = frame.GetLineEntry()
161+
162+
self.assertTrue(line_entry.GetFileSpec().GetFilename() == 'main.c', "Make sure we stepped from line 5 to line 6 in main.c")
163+
self.assertTrue(line_entry.GetLine() == 6, "Make sure we stepped from line 5 to line 6 in main.c")
164+
165+
97166
if __name__ == '__main__':
98167
import atexit
99168
lldb.SBDebugger.Initialize()

0 commit comments

Comments
 (0)