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:
Jim Ingham
2020-03-18 12:05:08 -07:00
parent 61e8e6882d
commit 1893065d7b
22 changed files with 926 additions and 109 deletions

View File

@@ -0,0 +1,4 @@
CXX_SOURCES := main.cpp
ENABLE_THREADS := YES
include Makefile.rules

View File

@@ -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")

View File

@@ -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;
}

View File

@@ -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