Allow the ThreadPlanStackMap to hold the thread plans for threads
that were not reported by the OS plugin. To facilitate this, move adding/updating the ThreadPlans for a Thread to the ThreadPlanStackMap. Also move dumping thread plans there as well. Added some tests for "thread plan list" and "thread plan discard" since I didn't seem to have written any originally. Differential Revision: https://reviews.llvm.org/D76814
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
CXX_SOURCES := main.cpp
|
||||
ENABLE_THREADS := YES
|
||||
|
||||
include Makefile.rules
|
||||
@@ -0,0 +1,113 @@
|
||||
"""
|
||||
Test that stepping works even when the OS Plugin doesn't report
|
||||
all threads at every stop.
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
|
||||
import os
|
||||
import lldb
|
||||
from lldbsuite.test.lldbtest import *
|
||||
import lldbsuite.test.lldbutil as lldbutil
|
||||
|
||||
|
||||
class TestOSPluginStepping(TestBase):
|
||||
|
||||
mydir = TestBase.compute_mydir(__file__)
|
||||
NO_DEBUG_INFO_TESTCASE = True
|
||||
|
||||
def test_python_os_plugin(self):
|
||||
"""Test that stepping works when the OS Plugin doesn't report all
|
||||
threads at every stop"""
|
||||
self.build()
|
||||
self.main_file = lldb.SBFileSpec('main.cpp')
|
||||
self.run_python_os_step_missing_thread(False)
|
||||
|
||||
def test_python_os_plugin_prune(self):
|
||||
"""Test that pruning the unreported PlanStacks works"""
|
||||
self.build()
|
||||
self.main_file = lldb.SBFileSpec('main.cpp')
|
||||
self.run_python_os_step_missing_thread(True)
|
||||
|
||||
def get_os_thread(self):
|
||||
return self.process.GetThreadByID(0x111111111)
|
||||
|
||||
def is_os_thread(self, thread):
|
||||
id = thread.GetID()
|
||||
return id == 0x111111111
|
||||
|
||||
def run_python_os_step_missing_thread(self, do_prune):
|
||||
"""Test that the Python operating system plugin works correctly"""
|
||||
|
||||
# Our OS plugin does NOT report all threads:
|
||||
result = self.dbg.HandleCommand("settings set target.experimental.os-plugin-reports-all-threads false")
|
||||
|
||||
python_os_plugin_path = os.path.join(self.getSourceDir(),
|
||||
"operating_system.py")
|
||||
(target, self.process, thread, thread_bkpt) = lldbutil.run_to_source_breakpoint(
|
||||
self, "first stop in thread - do a step out", self.main_file)
|
||||
|
||||
main_bkpt = target.BreakpointCreateBySourceRegex('Stop here and do not make a memory thread for thread_1',
|
||||
self.main_file)
|
||||
self.assertEqual(main_bkpt.GetNumLocations(), 1, "Main breakpoint has one location")
|
||||
|
||||
# There should not be an os thread before we load the plugin:
|
||||
self.assertFalse(self.get_os_thread().IsValid(), "No OS thread before loading plugin")
|
||||
|
||||
# Now load the python OS plug-in which should update the thread list and we should have
|
||||
# an OS plug-in thread overlaying thread_1 with id 0x111111111
|
||||
command = "settings set target.process.python-os-plugin-path '%s'" % python_os_plugin_path
|
||||
self.dbg.HandleCommand(command)
|
||||
|
||||
# Verify our OS plug-in threads showed up
|
||||
os_thread = self.get_os_thread()
|
||||
self.assertTrue(
|
||||
os_thread.IsValid(),
|
||||
"Make sure we added the thread 0x111111111 after we load the python OS plug-in")
|
||||
|
||||
# Now we are going to step-out. This should get interrupted by main_bkpt. We've
|
||||
# set up the OS plugin so at this stop, we have lost the OS thread 0x111111111.
|
||||
# Make sure both of these are true:
|
||||
os_thread.StepOut()
|
||||
|
||||
stopped_threads = lldbutil.get_threads_stopped_at_breakpoint(self.process, main_bkpt)
|
||||
self.assertEqual(len(stopped_threads), 1, "Stopped at main_bkpt")
|
||||
thread = self.process.GetThreadByID(0x111111111)
|
||||
self.assertFalse(thread.IsValid(), "No thread 0x111111111 on second stop.")
|
||||
|
||||
# Make sure we still have the thread plans for this thread:
|
||||
# First, don't show unreported threads, that should fail:
|
||||
command = "thread plan list -t 0x111111111"
|
||||
result = lldb.SBCommandReturnObject()
|
||||
interp = self.dbg.GetCommandInterpreter()
|
||||
interp.HandleCommand(command, result)
|
||||
self.assertFalse(result.Succeeded(), "We found no plans for the unreported thread.")
|
||||
# Now do it again but with the -u flag:
|
||||
command = "thread plan list -u -t 0x111111111"
|
||||
result = lldb.SBCommandReturnObject()
|
||||
interp.HandleCommand(command, result)
|
||||
self.assertTrue(result.Succeeded(), "We found plans for the unreported thread.")
|
||||
|
||||
if do_prune:
|
||||
# Prune the thread plan and continue, and we will run to exit.
|
||||
interp.HandleCommand("thread plan prune 0x111111111", result)
|
||||
self.assertTrue(result.Succeeded(), "Found the plan for 0x111111111 and pruned it")
|
||||
|
||||
# List again, make sure it doesn't work:
|
||||
command = "thread plan list -u -t 0x111111111"
|
||||
interp.HandleCommand(command, result)
|
||||
self.assertFalse(result.Succeeded(), "We still found plans for the unreported thread.")
|
||||
|
||||
self.process.Continue()
|
||||
self.assertEqual(self.process.GetState(), lldb.eStateExited, "We exited.")
|
||||
else:
|
||||
# Now we are going to continue, and when we hit the step-out breakpoint, we will
|
||||
# put the OS plugin thread back, lldb will recover its ThreadPlanStack, and
|
||||
# we will stop with a "step-out" reason.
|
||||
self.process.Continue()
|
||||
os_thread = self.get_os_thread()
|
||||
self.assertTrue(os_thread.IsValid(), "The OS thread is back after continue")
|
||||
self.assertTrue("step out" in os_thread.GetStopDescription(100), "Completed step out plan")
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
// This test will present lldb with two threads one of which the test will
|
||||
// overlay with an OSPlugin thread. Then we'll do a step out on the thread_1,
|
||||
// but arrange to hit a breakpoint in main before the step out completes. At
|
||||
// that point we will not report an OS plugin thread for thread_1. Then we'll
|
||||
// run again and hit the step out breakpoint. Make sure we haven't deleted
|
||||
// that, and recognize it.
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <stdio.h>
|
||||
#include <thread>
|
||||
|
||||
static int g_value = 0; // I don't have access to the real threads in the
|
||||
// OS Plugin, and I don't want to have to count
|
||||
// StopID's. So I'm using this value to tell me which
|
||||
// stop point the program has reached.
|
||||
std::mutex g_mutex;
|
||||
std::condition_variable g_cv;
|
||||
static int g_condition = 0; // Using this as the variable backing g_cv
|
||||
// to prevent spurious wakeups.
|
||||
|
||||
void step_out_of_here() {
|
||||
std::unique_lock<std::mutex> func_lock(g_mutex);
|
||||
// Set a breakpoint:first stop in thread - do a step out.
|
||||
g_condition = 1;
|
||||
g_cv.notify_one();
|
||||
g_cv.wait(func_lock, [&] { return g_condition == 2; });
|
||||
}
|
||||
|
||||
void *thread_func() {
|
||||
// Do something
|
||||
step_out_of_here();
|
||||
|
||||
// Return
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main() {
|
||||
// Lock the mutex so we can block the thread:
|
||||
std::unique_lock<std::mutex> main_lock(g_mutex);
|
||||
// Create the thread
|
||||
std::thread thread_1(thread_func);
|
||||
g_cv.wait(main_lock, [&] { return g_condition == 1; });
|
||||
g_value = 1;
|
||||
g_condition = 2;
|
||||
// Stop here and do not make a memory thread for thread_1.
|
||||
g_cv.notify_one();
|
||||
g_value = 2;
|
||||
main_lock.unlock();
|
||||
|
||||
// Wait for the threads to finish
|
||||
thread_1.join();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
import lldb
|
||||
import struct
|
||||
|
||||
|
||||
class OperatingSystemPlugIn(object):
|
||||
"""Class that provides a OS plugin that along with the particular code in main.cpp
|
||||
emulates the following scenario:
|
||||
a) We stop in an OS Plugin created thread - which should be thread index 1
|
||||
b) We step-out from that thread
|
||||
c) We hit a breakpoint in another thread, and DON'T produce the OS Plugin thread.
|
||||
d) We continue, and when we hit the step out breakpoint, we again produce the same
|
||||
OS Plugin thread.
|
||||
main.cpp sets values into the global variable g_value, which we use to tell the OS
|
||||
plugin whether to produce the OS plugin thread or not.
|
||||
Since we are always producing an OS plugin thread with a backing thread, we don't
|
||||
need to implement get_register_info or get_register_data.
|
||||
"""
|
||||
|
||||
def __init__(self, process):
|
||||
'''Initialization needs a valid.SBProcess object.
|
||||
|
||||
This plug-in will get created after a live process is valid and has stopped for the
|
||||
first time.'''
|
||||
print("Plugin initialized.")
|
||||
self.process = None
|
||||
self.start_stop_id = 0
|
||||
self.g_value = lldb.SBValue()
|
||||
|
||||
if isinstance(process, lldb.SBProcess) and process.IsValid():
|
||||
self.process = process
|
||||
self.g_value = process.GetTarget().FindFirstGlobalVariable("g_value")
|
||||
if not self.g_value.IsValid():
|
||||
print("Could not find g_value")
|
||||
|
||||
def create_thread(self, tid, context):
|
||||
print("Called create thread with tid: ", tid)
|
||||
return None
|
||||
|
||||
def get_thread_info(self):
|
||||
g_value = self.g_value.GetValueAsUnsigned()
|
||||
print("Called get_thread_info: g_value: %d"%(g_value))
|
||||
if g_value == 0 or g_value == 2:
|
||||
return [{'tid': 0x111111111,
|
||||
'name': 'one',
|
||||
'queue': 'queue1',
|
||||
'state': 'stopped',
|
||||
'stop_reason': 'breakpoint',
|
||||
'core' : 1 }]
|
||||
else:
|
||||
return []
|
||||
|
||||
def get_register_info(self):
|
||||
print ("called get_register_info")
|
||||
return None
|
||||
|
||||
|
||||
def get_register_data(self, tid):
|
||||
print("Get register data called for tid: %d"%(tid))
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user