Skip to content

Commit f8f1d70

Browse files
committed
[lldb] Handle an empty SBMemoryRegionInfo from scripted process
A scripted process implementation might return an SBMemoryRegionInfo object in its implementation of `get_memory_region_containing_address` which will have an address 0 and size 0, without realizing the problems this can cause. One problem happens when an expression is run, and lldb needs to find part of the target's virtual address space to store the result (actually stored in lldb's host memory), it uses IRMemoryMap::FindSpace() to do that. If the process supports GetMemoryRegionInfo, FindSpace will step through the allocated memory ranges looking for an unused one. But if every request is a memory region with address 0 + size 0, this loop will never terminate. Detect this kind of response from a scripted process plugin and return an error, so callers iterating over the address space can exit. Added a test where I copied dummy_scripted_process.py from the TestScriptedProcess.py existing test, and added a bad `get_memory_region_containing_address` implementation. rdar://139678032
1 parent b0a4e95 commit f8f1d70

File tree

5 files changed

+182
-1
lines changed

5 files changed

+182
-1
lines changed

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,8 +288,15 @@ Status ScriptedProcess::DoGetMemoryRegionInfo(lldb::addr_t load_addr,
288288
MemoryRegionInfo &region) {
289289
Status error;
290290
if (auto region_or_err =
291-
GetInterface().GetMemoryRegionContainingAddress(load_addr, error))
291+
GetInterface().GetMemoryRegionContainingAddress(load_addr, error)) {
292292
region = *region_or_err;
293+
if (region.GetRange().GetRangeBase() == 0 &&
294+
(region.GetRange().GetByteSize() == 0 ||
295+
region.GetRange().GetByteSize() == LLDB_INVALID_ADDRESS)) {
296+
error = Status::FromErrorString(
297+
"Invalid memory region from scripted process");
298+
}
299+
}
293300

294301
return error;
295302
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
C_SOURCES := main.c
2+
3+
include Makefile.rules
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""
2+
Test python scripted process which returns an empty SBMemoryRegionInfo
3+
"""
4+
5+
import os, shutil
6+
7+
import lldb
8+
from lldbsuite.test.decorators import *
9+
from lldbsuite.test.lldbtest import *
10+
from lldbsuite.test import lldbutil
11+
from lldbsuite.test import lldbtest
12+
13+
14+
class ScriptedProcessEmptyMemoryRegion(TestBase):
15+
NO_DEBUG_INFO_TESTCASE = True
16+
17+
def test_scripted_process_empty_memory_region(self):
18+
"""Test that lldb handles an empty SBMemoryRegionInfo object from
19+
a scripted process plugin."""
20+
self.build()
21+
22+
target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
23+
self.assertTrue(target, VALID_TARGET)
24+
25+
scripted_process_example_relpath = "dummy_scripted_process.py"
26+
self.runCmd(
27+
"command script import "
28+
+ os.path.join(self.getSourceDir(), scripted_process_example_relpath)
29+
)
30+
31+
self.expect("memory region 0", error=True, substrs=["Invalid memory region"])
32+
33+
self.expect("expr -- 5", substrs=["5"])
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import os, struct, signal
2+
3+
from typing import Any, Dict
4+
5+
import lldb
6+
from lldb.plugins.scripted_process import ScriptedProcess
7+
from lldb.plugins.scripted_process import ScriptedThread
8+
9+
10+
class DummyStopHook:
11+
def __init__(self, target, args):
12+
self.target = target
13+
self.args = args
14+
15+
def handle_stop(self, exe_ctx, stream):
16+
print("My DummyStopHook triggered. Printing args: \n%s" % self.args)
17+
sp = exe_ctx.process.GetScriptedImplementation()
18+
sp.handled_stop = True
19+
20+
21+
class DummyScriptedProcess(ScriptedProcess):
22+
memory = None
23+
24+
def __init__(self, exe_ctx: lldb.SBExecutionContext, args: lldb.SBStructuredData):
25+
super().__init__(exe_ctx, args)
26+
self.threads[0] = DummyScriptedThread(self, None)
27+
self.memory = {}
28+
addr = 0x500000000
29+
debugger = self.target.GetDebugger()
30+
index = debugger.GetIndexOfTarget(self.target)
31+
self.memory[addr] = "Hello, target " + str(index)
32+
self.handled_stop = False
33+
34+
def read_memory_at_address(
35+
self, addr: int, size: int, error: lldb.SBError
36+
) -> lldb.SBData:
37+
data = lldb.SBData().CreateDataFromCString(
38+
self.target.GetByteOrder(), self.target.GetCodeByteSize(), self.memory[addr]
39+
)
40+
41+
return data
42+
43+
def get_memory_region_containing_address(
44+
self, addr: int
45+
) -> lldb.SBMemoryRegionInfo:
46+
return lldb.SBMemoryRegionInfo()
47+
48+
def write_memory_at_address(self, addr, data, error):
49+
self.memory[addr] = data.GetString(error, 0)
50+
return len(self.memory[addr]) + 1
51+
52+
def get_loaded_images(self):
53+
return self.loaded_images
54+
55+
def get_process_id(self) -> int:
56+
return 42
57+
58+
def should_stop(self) -> bool:
59+
return True
60+
61+
def is_alive(self) -> bool:
62+
return True
63+
64+
def get_scripted_thread_plugin(self):
65+
return DummyScriptedThread.__module__ + "." + DummyScriptedThread.__name__
66+
67+
def my_super_secret_method(self):
68+
if hasattr(self, "my_super_secret_member"):
69+
return self.my_super_secret_member
70+
else:
71+
return None
72+
73+
74+
class DummyScriptedThread(ScriptedThread):
75+
def __init__(self, process, args):
76+
super().__init__(process, args)
77+
self.frames.append({"pc": 0x0100001B00})
78+
79+
def get_thread_id(self) -> int:
80+
return 0x19
81+
82+
def get_name(self) -> str:
83+
return DummyScriptedThread.__name__ + ".thread-1"
84+
85+
def get_state(self) -> int:
86+
return lldb.eStateStopped
87+
88+
def get_stop_reason(self) -> Dict[str, Any]:
89+
return {"type": lldb.eStopReasonTrace, "data": {}}
90+
91+
def get_register_context(self) -> str:
92+
return struct.pack(
93+
"21Q",
94+
1,
95+
2,
96+
3,
97+
4,
98+
5,
99+
6,
100+
7,
101+
8,
102+
9,
103+
10,
104+
11,
105+
12,
106+
13,
107+
14,
108+
15,
109+
16,
110+
17,
111+
18,
112+
19,
113+
20,
114+
21,
115+
)
116+
117+
118+
def __lldb_init_module(debugger, dict):
119+
# This is used when loading the script in an interactive debug session to
120+
# automatically, register the stop-hook and launch the scripted process.
121+
if not "SKIP_SCRIPTED_PROCESS_LAUNCH" in os.environ:
122+
debugger.HandleCommand(
123+
"target stop-hook add -k first -v 1 -k second -v 2 -P %s.%s"
124+
% (__name__, DummyStopHook.__name__)
125+
)
126+
debugger.HandleCommand(
127+
"process launch -C %s.%s" % (__name__, DummyScriptedProcess.__name__)
128+
)
129+
else:
130+
print(
131+
"Name of the class that will manage the scripted process: '%s.%s'"
132+
% (__name__, DummyScriptedProcess.__name__)
133+
)
134+
print(
135+
"Name of the class that will manage the stop-hook: '%s.%s'"
136+
% (__name__, DummyStopHook.__name__)
137+
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
int main() {}

0 commit comments

Comments
 (0)