Skip to content

Commit 680ca7f

Browse files
committed
[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 <[email protected]>
1 parent 6eddd98 commit 680ca7f

File tree

12 files changed

+189
-29
lines changed

12 files changed

+189
-29
lines changed

lldb/examples/python/scripted_process/scripted_process.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class ScriptedProcess:
1919
memory_regions = None
2020
stack_memory_dump = None
2121
loaded_images = None
22-
threads = {}
22+
threads = None
2323

2424
@abstractmethod
2525
def __init__(self, target, args):
@@ -41,6 +41,8 @@ def __init__(self, target, args):
4141
self.dbg = target.GetDebugger()
4242
if isinstance(args, lldb.SBStructuredData) and args.IsValid():
4343
self.args = args
44+
self.threads = {}
45+
self.loaded_images = []
4446

4547
@abstractmethod
4648
def get_memory_region_containing_address(self, addr):
@@ -116,8 +118,7 @@ def get_loaded_images(self):
116118
117119
```
118120
class ScriptedProcessImage:
119-
def __init__(name, file_spec, uuid, load_address):
120-
self.name = name
121+
def __init__(file_spec, uuid, load_address):
121122
self.file_spec = file_spec
122123
self.uuid = uuid
123124
self.load_address = load_address

lldb/include/lldb/Interpreter/ScriptedProcessInterface.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ class ScriptedProcessInterface : virtual public ScriptedInterface {
5757
return nullptr;
5858
}
5959

60-
virtual StructuredData::DictionarySP GetLoadedImages() { return nullptr; }
60+
virtual StructuredData::ArraySP GetLoadedImages() { return nullptr; }
6161

6262
virtual lldb::pid_t GetProcessID() { return LLDB_INVALID_PROCESS_ID; }
6363

lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ Status ScriptedProcess::DoLaunch(Module *exe_module,
170170
void ScriptedProcess::DidLaunch() {
171171
CheckInterpreterAndScriptObject();
172172
m_pid = GetInterface().GetProcessID();
173+
GetLoadedDynamicLibrariesInfos();
173174
}
174175

175176
Status ScriptedProcess::DoResume() {
@@ -378,6 +379,93 @@ bool ScriptedProcess::GetProcessInfo(ProcessInstanceInfo &info) {
378379
return true;
379380
}
380381

382+
lldb_private::StructuredData::ObjectSP
383+
ScriptedProcess::GetLoadedDynamicLibrariesInfos() {
384+
CheckInterpreterAndScriptObject();
385+
386+
Status error;
387+
auto error_with_message = [&error](llvm::StringRef message) {
388+
return ScriptedInterface::ErrorWithMessage<bool>(LLVM_PRETTY_FUNCTION,
389+
message.data(), error);
390+
};
391+
392+
StructuredData::ArraySP loaded_images_sp = GetInterface().GetLoadedImages();
393+
394+
if (!loaded_images_sp || !loaded_images_sp->GetSize())
395+
return GetInterface().ErrorWithMessage<StructuredData::ObjectSP>(
396+
LLVM_PRETTY_FUNCTION, "No loaded images.", error);
397+
398+
ModuleList module_list;
399+
Target &target = GetTarget();
400+
401+
auto reload_image = [&target, &module_list, &error_with_message](
402+
StructuredData::Object *obj) -> bool {
403+
StructuredData::Dictionary *dict = obj->GetAsDictionary();
404+
405+
if (!dict)
406+
return error_with_message("Couldn't cast image object into dictionary.");
407+
408+
ModuleSpec module_spec;
409+
llvm::StringRef value;
410+
411+
bool has_path = dict->HasKey("path");
412+
bool has_uuid = dict->HasKey("uuid");
413+
if (!has_path && !has_uuid)
414+
return error_with_message("Dictionary should have key 'path' or 'uuid'");
415+
if (!dict->HasKey("load_addr"))
416+
return error_with_message("Dictionary is missing key 'load_addr'");
417+
418+
if (has_path) {
419+
dict->GetValueForKeyAsString("path", value);
420+
module_spec.GetFileSpec().SetPath(value);
421+
}
422+
423+
if (has_uuid) {
424+
dict->GetValueForKeyAsString("uuid", value);
425+
module_spec.GetUUID().SetFromStringRef(value);
426+
}
427+
module_spec.GetArchitecture() = target.GetArchitecture();
428+
429+
ModuleSP module_sp =
430+
target.GetOrCreateModule(module_spec, true /* notify */);
431+
432+
if (!module_sp)
433+
return error_with_message("Couldn't create or get module.");
434+
435+
lldb::addr_t load_addr = LLDB_INVALID_ADDRESS;
436+
lldb::addr_t slide = LLDB_INVALID_OFFSET;
437+
dict->GetValueForKeyAsInteger("load_addr", load_addr);
438+
dict->GetValueForKeyAsInteger("slide", slide);
439+
if (load_addr == LLDB_INVALID_ADDRESS)
440+
return error_with_message(
441+
"Couldn't get valid load address or slide offset.");
442+
443+
if (slide != LLDB_INVALID_OFFSET)
444+
load_addr += slide;
445+
446+
bool changed = false;
447+
module_sp->SetLoadAddress(target, load_addr, false /*=value_is_offset*/,
448+
changed);
449+
450+
if (!changed && !module_sp->GetObjectFile())
451+
return error_with_message("Couldn't set the load address for module.");
452+
453+
dict->GetValueForKeyAsString("path", value);
454+
FileSpec objfile(value);
455+
module_sp->SetFileSpecAndObjectName(objfile, objfile.GetFilename());
456+
457+
return module_list.AppendIfNeeded(module_sp);
458+
};
459+
460+
if (!loaded_images_sp->ForEach(reload_image))
461+
return GetInterface().ErrorWithMessage<StructuredData::ObjectSP>(
462+
LLVM_PRETTY_FUNCTION, "Couldn't reload all images.", error);
463+
464+
target.ModulesDidLoad(module_list);
465+
466+
return loaded_images_sp;
467+
}
468+
381469
ScriptedProcessInterface &ScriptedProcess::GetInterface() const {
382470
return m_interpreter->GetScriptedProcessInterface();
383471
}

lldb/source/Plugins/Process/scripted/ScriptedProcess.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ class ScriptedProcess : public Process {
8989

9090
bool GetProcessInfo(ProcessInstanceInfo &info) override;
9191

92+
lldb_private::StructuredData::ObjectSP
93+
GetLoadedDynamicLibrariesInfos() override;
94+
9295
protected:
9396
Status DoStop();
9497

lldb/source/Plugins/ScriptInterpreter/Python/ScriptedProcessPythonInterface.cpp

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,21 @@ lldb::DataExtractorSP ScriptedProcessPythonInterface::ReadMemoryAtAddress(
124124
address, size);
125125
}
126126

127-
StructuredData::DictionarySP ScriptedProcessPythonInterface::GetLoadedImages() {
128-
// TODO: Implement
129-
return {};
127+
StructuredData::ArraySP ScriptedProcessPythonInterface::GetLoadedImages() {
128+
Status error;
129+
StructuredData::ArraySP array =
130+
Dispatch<StructuredData::ArraySP>("get_loaded_images", error);
131+
132+
if (!array || !array->IsValid() || error.Fail()) {
133+
return ScriptedInterface::ErrorWithMessage<StructuredData::ArraySP>(
134+
LLVM_PRETTY_FUNCTION,
135+
llvm::Twine("Null or invalid object (" +
136+
llvm::Twine(error.AsCString()) + llvm::Twine(")."))
137+
.str(),
138+
error);
139+
}
140+
141+
return array;
130142
}
131143

132144
lldb::pid_t ScriptedProcessPythonInterface::GetProcessID() {

lldb/source/Plugins/ScriptInterpreter/Python/ScriptedProcessPythonInterface.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class ScriptedProcessPythonInterface : public ScriptedProcessInterface,
4949
lldb::DataExtractorSP ReadMemoryAtAddress(lldb::addr_t address, size_t size,
5050
Status &error) override;
5151

52-
StructuredData::DictionarySP GetLoadedImages() override;
52+
StructuredData::ArraySP GetLoadedImages() override;
5353

5454
lldb::pid_t GetProcessID() override;
5555

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
11
CXX_SOURCES := main.cpp
22
ENABLE_THREADS := YES
3-
include Makefile.rules
3+
LD_EXTRAS := -L. -lbaz -I.
4+
5+
override ARCH := $(shell uname -m)
6+
7+
all: libbaz.dylib a.out
48

9+
libbaz.dylib: baz.c
10+
$(MAKE) -f $(MAKEFILE_RULES) ARCH=$(ARCH) \
11+
DYLIB_ONLY=YES DYLIB_NAME=baz DYLIB_C_SOURCES=baz.c
12+
13+
include Makefile.rules

lldb/test/API/functionalities/scripted_process/TestStackCoreScriptedProcess.py

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,19 @@ def tearDown(self):
2525
def create_stack_skinny_corefile(self, file):
2626
self.build()
2727
target, process, thread, _ = lldbutil.run_to_source_breakpoint(self, "// break here",
28-
lldb.SBFileSpec("main.cpp"))
28+
lldb.SBFileSpec("baz.c"))
2929
self.assertTrue(process.IsValid(), "Process is invalid.")
3030
# FIXME: Use SBAPI to save the process corefile.
3131
self.runCmd("process save-core -s stack " + file)
3232
self.assertTrue(os.path.exists(file), "No stack-only corefile found.")
3333
self.assertTrue(self.dbg.DeleteTarget(target), "Couldn't delete target")
3434

35+
def get_module_with_name(self, target, name):
36+
for module in target.modules:
37+
if name in module.GetFileSpec().GetFilename():
38+
return module
39+
return None
40+
3541
@skipUnlessDarwin
3642
@skipIfOutOfTreeDebugserver
3743
def test_launch_scripted_process_stack_frames(self):
@@ -41,15 +47,15 @@ def test_launch_scripted_process_stack_frames(self):
4147
target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
4248
self.assertTrue(target, VALID_TARGET)
4349

44-
for module in target.modules:
45-
if 'a.out' in module.GetFileSpec().GetFilename():
46-
main_module = module
47-
break
48-
50+
main_module = self.get_module_with_name(target, 'a.out')
4951
self.assertTrue(main_module, "Invalid main module.")
5052
error = target.SetModuleLoadAddress(main_module, 0)
5153
self.assertSuccess(error, "Reloading main module at offset 0 failed.")
5254

55+
scripted_dylib = self.get_module_with_name(target, 'libbaz.dylib')
56+
self.assertTrue(scripted_dylib, "Dynamic library libbaz.dylib not found.")
57+
self.assertEqual(scripted_dylib.GetObjectFileHeaderAddress().GetLoadAddress(target), 0xffffffffffffffff)
58+
5359
os.environ['SKIP_SCRIPTED_PROCESS_LAUNCH'] = '1'
5460
def cleanup():
5561
del os.environ["SKIP_SCRIPTED_PROCESS_LAUNCH"]
@@ -68,7 +74,8 @@ def cleanup():
6874

6975
structured_data = lldb.SBStructuredData()
7076
structured_data.SetFromJSON(json.dumps({
71-
"backing_target_idx" : self.dbg.GetIndexOfTarget(corefile_process.GetTarget())
77+
"backing_target_idx" : self.dbg.GetIndexOfTarget(corefile_process.GetTarget()),
78+
"libbaz_path" : self.getBuildArtifact("libbaz.dylib")
7279
}))
7380
launch_info = lldb.SBLaunchInfo(None)
7481
launch_info.SetProcessPluginName("ScriptedProcess")
@@ -81,10 +88,10 @@ def cleanup():
8188
self.assertTrue(process, PROCESS_IS_VALID)
8289
self.assertEqual(process.GetProcessID(), 42)
8390

84-
self.assertEqual(process.GetNumThreads(), 3)
91+
self.assertEqual(process.GetNumThreads(), 2)
8592
thread = process.GetSelectedThread()
8693
self.assertTrue(thread, "Invalid thread.")
87-
self.assertEqual(thread.GetName(), "StackCoreScriptedThread.thread-2")
94+
self.assertEqual(thread.GetName(), "StackCoreScriptedThread.thread-1")
8895

8996
self.assertTrue(target.triple, "Invalid target triple")
9097
arch = target.triple.split('-')[0]
@@ -102,9 +109,17 @@ def cleanup():
102109
else:
103110
self.assertTrue(thread.GetStopReason(), lldb.eStopReasonSignal)
104111

105-
self.assertEqual(thread.GetNumFrames(), 6)
112+
self.assertEqual(thread.GetNumFrames(), 5)
106113
frame = thread.GetSelectedFrame()
107114
self.assertTrue(frame, "Invalid frame.")
108-
self.assertIn("bar", frame.GetFunctionName())
109-
self.assertEqual(int(frame.FindValue("i", lldb.eValueTypeVariableArgument).GetValue()), 42)
110-
self.assertEqual(int(frame.FindValue("j", lldb.eValueTypeVariableLocal).GetValue()), 42 * 42)
115+
func = frame.GetFunction()
116+
self.assertTrue(func, "Invalid function.")
117+
118+
self.assertIn("baz", frame.GetFunctionName())
119+
self.assertEqual(frame.vars.GetSize(), 2)
120+
self.assertEqual(int(frame.vars.GetFirstValueByName('j').GetValue()), 42 * 42)
121+
self.assertEqual(int(frame.vars.GetFirstValueByName('k').GetValue()), 42)
122+
123+
scripted_dylib = self.get_module_with_name(target, 'libbaz.dylib')
124+
self.assertTrue(scripted_dylib, "Dynamic library libbaz.dylib not found.")
125+
self.assertEqual(scripted_dylib.GetObjectFileHeaderAddress().GetLoadAddress(target), 0x1001e0000)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#include "baz.h"
2+
3+
#include <math.h>
4+
5+
int baz(int j) {
6+
int k = sqrt(j);
7+
return k; // break here
8+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#pragma once
2+
3+
int baz(int j);

lldb/test/API/functionalities/scripted_process/main.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,20 @@
22
#include <mutex>
33
#include <thread>
44

5+
extern "C" {
6+
int baz(int);
7+
}
8+
59
int bar(int i) {
610
int j = i * i;
7-
return j; // break here
11+
return j;
812
}
913

1014
int foo(int i) { return bar(i); }
1115

1216
void call_and_wait(int &n) {
1317
std::cout << "waiting for computation!" << std::endl;
14-
while (n != 42 * 42)
18+
while (baz(n) != 42)
1519
;
1620
std::cout << "finished computation!" << std::endl;
1721
}

lldb/test/API/functionalities/scripted_process/stack_core_scripted_process.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,19 @@
77
from lldb.plugins.scripted_process import ScriptedThread
88

99
class StackCoreScriptedProcess(ScriptedProcess):
10+
def get_module_with_name(self, target, name):
11+
for module in target.modules:
12+
if name in module.GetFileSpec().GetFilename():
13+
return module
14+
return None
15+
1016
def __init__(self, target: lldb.SBTarget, args : lldb.SBStructuredData):
1117
super().__init__(target, args)
1218

13-
self.backing_target_idx = args.GetValueForKey("backing_target_idx")
14-
1519
self.corefile_target = None
1620
self.corefile_process = None
21+
22+
self.backing_target_idx = args.GetValueForKey("backing_target_idx")
1723
if (self.backing_target_idx and self.backing_target_idx.IsValid()):
1824
if self.backing_target_idx.GetType() == lldb.eStructuredDataTypeInteger:
1925
idx = self.backing_target_idx.GetIntegerValue(42)
@@ -30,9 +36,22 @@ def __init__(self, target: lldb.SBTarget, args : lldb.SBStructuredData):
3036

3137
self.threads[corefile_thread.GetThreadID()] = StackCoreScriptedThread(self, structured_data)
3238

33-
if len(self.threads) == 3:
39+
if len(self.threads) == 2:
3440
self.threads[len(self.threads) - 1].is_stopped = True
3541

42+
corefile_module = self.get_module_with_name(self.corefile_target,
43+
"libbaz.dylib")
44+
if not corefile_module or not corefile_module.IsValid():
45+
return
46+
module_path = os.path.join(corefile_module.GetFileSpec().GetDirectory(),
47+
corefile_module.GetFileSpec().GetFilename())
48+
if not os.path.exists(module_path):
49+
return
50+
module_load_addr = corefile_module.GetObjectFileHeaderAddress().GetLoadAddress(self.corefile_target)
51+
52+
self.loaded_images.append({"path": module_path,
53+
"load_addr": module_load_addr})
54+
3655
def get_memory_region_containing_address(self, addr: int) -> lldb.SBMemoryRegionInfo:
3756
mem_region = lldb.SBMemoryRegionInfo()
3857
error = self.corefile_process.GetMemoryRegionInfo(addr, mem_region)
@@ -61,8 +80,6 @@ def read_memory_at_address(self, addr: int, size: int) -> lldb.SBData:
6180
return data
6281

6382
def get_loaded_images(self):
64-
# TODO: Iterate over corefile_target modules and build a data structure
65-
# from it.
6683
return self.loaded_images
6784

6885
def get_process_id(self) -> int:

0 commit comments

Comments
 (0)