[lldb/Plugins] Add ability to load modules to Scripted Processes

This patch introduces a new way to load modules programatically with
Scripted Processes. To do so, the scripted process blueprint holds a
list of dictionary describing the modules to load, which their path or
uuid, load address and eventually a slide offset.

LLDB will fetch that list after launching the ScriptedProcess, and
iterate over each entry to create the module that will be loaded in the
Scripted Process' target.

The patch also refactors the StackCoreScriptedProcess test to stop
inside the `libbaz` module and make sure it's loaded correctly and that
we can fetch some variables from it.

rdar://74520238

Differential Revision: https://reviews.llvm.org/D120969

Signed-off-by: Med Ismail Bennani <medismail.bennani@gmail.com>
This commit is contained in:
Med Ismail Bennani 2022-03-04 11:22:32 -08:00
parent 6eddd987c9
commit 680ca7f21a
12 changed files with 189 additions and 29 deletions

View File

@ -19,7 +19,7 @@ class ScriptedProcess:
memory_regions = None
stack_memory_dump = None
loaded_images = None
threads = {}
threads = None
@abstractmethod
def __init__(self, target, args):
@ -41,6 +41,8 @@ class ScriptedProcess:
self.dbg = target.GetDebugger()
if isinstance(args, lldb.SBStructuredData) and args.IsValid():
self.args = args
self.threads = {}
self.loaded_images = []
@abstractmethod
def get_memory_region_containing_address(self, addr):
@ -116,8 +118,7 @@ class ScriptedProcess:
```
class ScriptedProcessImage:
def __init__(name, file_spec, uuid, load_address):
self.name = name
def __init__(file_spec, uuid, load_address):
self.file_spec = file_spec
self.uuid = uuid
self.load_address = load_address

View File

@ -57,7 +57,7 @@ public:
return nullptr;
}
virtual StructuredData::DictionarySP GetLoadedImages() { return nullptr; }
virtual StructuredData::ArraySP GetLoadedImages() { return nullptr; }
virtual lldb::pid_t GetProcessID() { return LLDB_INVALID_PROCESS_ID; }

View File

@ -170,6 +170,7 @@ Status ScriptedProcess::DoLaunch(Module *exe_module,
void ScriptedProcess::DidLaunch() {
CheckInterpreterAndScriptObject();
m_pid = GetInterface().GetProcessID();
GetLoadedDynamicLibrariesInfos();
}
Status ScriptedProcess::DoResume() {
@ -378,6 +379,93 @@ bool ScriptedProcess::GetProcessInfo(ProcessInstanceInfo &info) {
return true;
}
lldb_private::StructuredData::ObjectSP
ScriptedProcess::GetLoadedDynamicLibrariesInfos() {
CheckInterpreterAndScriptObject();
Status error;
auto error_with_message = [&error](llvm::StringRef message) {
return ScriptedInterface::ErrorWithMessage<bool>(LLVM_PRETTY_FUNCTION,
message.data(), error);
};
StructuredData::ArraySP loaded_images_sp = GetInterface().GetLoadedImages();
if (!loaded_images_sp || !loaded_images_sp->GetSize())
return GetInterface().ErrorWithMessage<StructuredData::ObjectSP>(
LLVM_PRETTY_FUNCTION, "No loaded images.", error);
ModuleList module_list;
Target &target = GetTarget();
auto reload_image = [&target, &module_list, &error_with_message](
StructuredData::Object *obj) -> bool {
StructuredData::Dictionary *dict = obj->GetAsDictionary();
if (!dict)
return error_with_message("Couldn't cast image object into dictionary.");
ModuleSpec module_spec;
llvm::StringRef value;
bool has_path = dict->HasKey("path");
bool has_uuid = dict->HasKey("uuid");
if (!has_path && !has_uuid)
return error_with_message("Dictionary should have key 'path' or 'uuid'");
if (!dict->HasKey("load_addr"))
return error_with_message("Dictionary is missing key 'load_addr'");
if (has_path) {
dict->GetValueForKeyAsString("path", value);
module_spec.GetFileSpec().SetPath(value);
}
if (has_uuid) {
dict->GetValueForKeyAsString("uuid", value);
module_spec.GetUUID().SetFromStringRef(value);
}
module_spec.GetArchitecture() = target.GetArchitecture();
ModuleSP module_sp =
target.GetOrCreateModule(module_spec, true /* notify */);
if (!module_sp)
return error_with_message("Couldn't create or get module.");
lldb::addr_t load_addr = LLDB_INVALID_ADDRESS;
lldb::addr_t slide = LLDB_INVALID_OFFSET;
dict->GetValueForKeyAsInteger("load_addr", load_addr);
dict->GetValueForKeyAsInteger("slide", slide);
if (load_addr == LLDB_INVALID_ADDRESS)
return error_with_message(
"Couldn't get valid load address or slide offset.");
if (slide != LLDB_INVALID_OFFSET)
load_addr += slide;
bool changed = false;
module_sp->SetLoadAddress(target, load_addr, false /*=value_is_offset*/,
changed);
if (!changed && !module_sp->GetObjectFile())
return error_with_message("Couldn't set the load address for module.");
dict->GetValueForKeyAsString("path", value);
FileSpec objfile(value);
module_sp->SetFileSpecAndObjectName(objfile, objfile.GetFilename());
return module_list.AppendIfNeeded(module_sp);
};
if (!loaded_images_sp->ForEach(reload_image))
return GetInterface().ErrorWithMessage<StructuredData::ObjectSP>(
LLVM_PRETTY_FUNCTION, "Couldn't reload all images.", error);
target.ModulesDidLoad(module_list);
return loaded_images_sp;
}
ScriptedProcessInterface &ScriptedProcess::GetInterface() const {
return m_interpreter->GetScriptedProcessInterface();
}

View File

@ -89,6 +89,9 @@ public:
bool GetProcessInfo(ProcessInstanceInfo &info) override;
lldb_private::StructuredData::ObjectSP
GetLoadedDynamicLibrariesInfos() override;
protected:
Status DoStop();

View File

@ -124,9 +124,21 @@ lldb::DataExtractorSP ScriptedProcessPythonInterface::ReadMemoryAtAddress(
address, size);
}
StructuredData::DictionarySP ScriptedProcessPythonInterface::GetLoadedImages() {
// TODO: Implement
return {};
StructuredData::ArraySP ScriptedProcessPythonInterface::GetLoadedImages() {
Status error;
StructuredData::ArraySP array =
Dispatch<StructuredData::ArraySP>("get_loaded_images", error);
if (!array || !array->IsValid() || error.Fail()) {
return ScriptedInterface::ErrorWithMessage<StructuredData::ArraySP>(
LLVM_PRETTY_FUNCTION,
llvm::Twine("Null or invalid object (" +
llvm::Twine(error.AsCString()) + llvm::Twine(")."))
.str(),
error);
}
return array;
}
lldb::pid_t ScriptedProcessPythonInterface::GetProcessID() {

View File

@ -49,7 +49,7 @@ public:
lldb::DataExtractorSP ReadMemoryAtAddress(lldb::addr_t address, size_t size,
Status &error) override;
StructuredData::DictionarySP GetLoadedImages() override;
StructuredData::ArraySP GetLoadedImages() override;
lldb::pid_t GetProcessID() override;

View File

@ -1,4 +1,13 @@
CXX_SOURCES := main.cpp
ENABLE_THREADS := YES
include Makefile.rules
LD_EXTRAS := -L. -lbaz -I.
override ARCH := $(shell uname -m)
all: libbaz.dylib a.out
libbaz.dylib: baz.c
$(MAKE) -f $(MAKEFILE_RULES) ARCH=$(ARCH) \
DYLIB_ONLY=YES DYLIB_NAME=baz DYLIB_C_SOURCES=baz.c
include Makefile.rules

View File

@ -25,13 +25,19 @@ class StackCoreScriptedProcesTestCase(TestBase):
def create_stack_skinny_corefile(self, file):
self.build()
target, process, thread, _ = lldbutil.run_to_source_breakpoint(self, "// break here",
lldb.SBFileSpec("main.cpp"))
lldb.SBFileSpec("baz.c"))
self.assertTrue(process.IsValid(), "Process is invalid.")
# FIXME: Use SBAPI to save the process corefile.
self.runCmd("process save-core -s stack " + file)
self.assertTrue(os.path.exists(file), "No stack-only corefile found.")
self.assertTrue(self.dbg.DeleteTarget(target), "Couldn't delete target")
def get_module_with_name(self, target, name):
for module in target.modules:
if name in module.GetFileSpec().GetFilename():
return module
return None
@skipUnlessDarwin
@skipIfOutOfTreeDebugserver
def test_launch_scripted_process_stack_frames(self):
@ -41,15 +47,15 @@ class StackCoreScriptedProcesTestCase(TestBase):
target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
self.assertTrue(target, VALID_TARGET)
for module in target.modules:
if 'a.out' in module.GetFileSpec().GetFilename():
main_module = module
break
main_module = self.get_module_with_name(target, 'a.out')
self.assertTrue(main_module, "Invalid main module.")
error = target.SetModuleLoadAddress(main_module, 0)
self.assertSuccess(error, "Reloading main module at offset 0 failed.")
scripted_dylib = self.get_module_with_name(target, 'libbaz.dylib')
self.assertTrue(scripted_dylib, "Dynamic library libbaz.dylib not found.")
self.assertEqual(scripted_dylib.GetObjectFileHeaderAddress().GetLoadAddress(target), 0xffffffffffffffff)
os.environ['SKIP_SCRIPTED_PROCESS_LAUNCH'] = '1'
def cleanup():
del os.environ["SKIP_SCRIPTED_PROCESS_LAUNCH"]
@ -68,7 +74,8 @@ class StackCoreScriptedProcesTestCase(TestBase):
structured_data = lldb.SBStructuredData()
structured_data.SetFromJSON(json.dumps({
"backing_target_idx" : self.dbg.GetIndexOfTarget(corefile_process.GetTarget())
"backing_target_idx" : self.dbg.GetIndexOfTarget(corefile_process.GetTarget()),
"libbaz_path" : self.getBuildArtifact("libbaz.dylib")
}))
launch_info = lldb.SBLaunchInfo(None)
launch_info.SetProcessPluginName("ScriptedProcess")
@ -81,10 +88,10 @@ class StackCoreScriptedProcesTestCase(TestBase):
self.assertTrue(process, PROCESS_IS_VALID)
self.assertEqual(process.GetProcessID(), 42)
self.assertEqual(process.GetNumThreads(), 3)
self.assertEqual(process.GetNumThreads(), 2)
thread = process.GetSelectedThread()
self.assertTrue(thread, "Invalid thread.")
self.assertEqual(thread.GetName(), "StackCoreScriptedThread.thread-2")
self.assertEqual(thread.GetName(), "StackCoreScriptedThread.thread-1")
self.assertTrue(target.triple, "Invalid target triple")
arch = target.triple.split('-')[0]
@ -102,9 +109,17 @@ class StackCoreScriptedProcesTestCase(TestBase):
else:
self.assertTrue(thread.GetStopReason(), lldb.eStopReasonSignal)
self.assertEqual(thread.GetNumFrames(), 6)
self.assertEqual(thread.GetNumFrames(), 5)
frame = thread.GetSelectedFrame()
self.assertTrue(frame, "Invalid frame.")
self.assertIn("bar", frame.GetFunctionName())
self.assertEqual(int(frame.FindValue("i", lldb.eValueTypeVariableArgument).GetValue()), 42)
self.assertEqual(int(frame.FindValue("j", lldb.eValueTypeVariableLocal).GetValue()), 42 * 42)
func = frame.GetFunction()
self.assertTrue(func, "Invalid function.")
self.assertIn("baz", frame.GetFunctionName())
self.assertEqual(frame.vars.GetSize(), 2)
self.assertEqual(int(frame.vars.GetFirstValueByName('j').GetValue()), 42 * 42)
self.assertEqual(int(frame.vars.GetFirstValueByName('k').GetValue()), 42)
scripted_dylib = self.get_module_with_name(target, 'libbaz.dylib')
self.assertTrue(scripted_dylib, "Dynamic library libbaz.dylib not found.")
self.assertEqual(scripted_dylib.GetObjectFileHeaderAddress().GetLoadAddress(target), 0x1001e0000)

View File

@ -0,0 +1,8 @@
#include "baz.h"
#include <math.h>
int baz(int j) {
int k = sqrt(j);
return k; // break here
}

View File

@ -0,0 +1,3 @@
#pragma once
int baz(int j);

View File

@ -2,16 +2,20 @@
#include <mutex>
#include <thread>
extern "C" {
int baz(int);
}
int bar(int i) {
int j = i * i;
return j; // break here
return j;
}
int foo(int i) { return bar(i); }
void call_and_wait(int &n) {
std::cout << "waiting for computation!" << std::endl;
while (n != 42 * 42)
while (baz(n) != 42)
;
std::cout << "finished computation!" << std::endl;
}

View File

@ -7,13 +7,19 @@ from lldb.plugins.scripted_process import ScriptedProcess
from lldb.plugins.scripted_process import ScriptedThread
class StackCoreScriptedProcess(ScriptedProcess):
def get_module_with_name(self, target, name):
for module in target.modules:
if name in module.GetFileSpec().GetFilename():
return module
return None
def __init__(self, target: lldb.SBTarget, args : lldb.SBStructuredData):
super().__init__(target, args)
self.backing_target_idx = args.GetValueForKey("backing_target_idx")
self.corefile_target = None
self.corefile_process = None
self.backing_target_idx = args.GetValueForKey("backing_target_idx")
if (self.backing_target_idx and self.backing_target_idx.IsValid()):
if self.backing_target_idx.GetType() == lldb.eStructuredDataTypeInteger:
idx = self.backing_target_idx.GetIntegerValue(42)
@ -30,9 +36,22 @@ class StackCoreScriptedProcess(ScriptedProcess):
self.threads[corefile_thread.GetThreadID()] = StackCoreScriptedThread(self, structured_data)
if len(self.threads) == 3:
if len(self.threads) == 2:
self.threads[len(self.threads) - 1].is_stopped = True
corefile_module = self.get_module_with_name(self.corefile_target,
"libbaz.dylib")
if not corefile_module or not corefile_module.IsValid():
return
module_path = os.path.join(corefile_module.GetFileSpec().GetDirectory(),
corefile_module.GetFileSpec().GetFilename())
if not os.path.exists(module_path):
return
module_load_addr = corefile_module.GetObjectFileHeaderAddress().GetLoadAddress(self.corefile_target)
self.loaded_images.append({"path": module_path,
"load_addr": module_load_addr})
def get_memory_region_containing_address(self, addr: int) -> lldb.SBMemoryRegionInfo:
mem_region = lldb.SBMemoryRegionInfo()
error = self.corefile_process.GetMemoryRegionInfo(addr, mem_region)
@ -61,8 +80,6 @@ class StackCoreScriptedProcess(ScriptedProcess):
return data
def get_loaded_images(self):
# TODO: Iterate over corefile_target modules and build a data structure
# from it.
return self.loaded_images
def get_process_id(self) -> int: