Skip to content

Commit 3e4b7fd

Browse files
committed
[lldb/Plugins] Add support for ScriptedThread in ScriptedProcess
This patch introduces the `ScriptedThread` class with its python interface. When used with `ScriptedProcess`, `ScriptedThreaad` can provide various information such as the thread state, stop reason or even its register context. This can be used to reconstruct the program stack frames using lldb's unwinder. rdar://74503836 Differential Revision: https://reviews.llvm.org/D107585 Signed-off-by: Med Ismail Bennani <[email protected]>
1 parent 93bcb83 commit 3e4b7fd

21 files changed

+932
-74
lines changed

lldb/bindings/python/python-wrapper.swig

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,63 @@ LLDBSwigPythonCreateScriptedProcess
340340
Py_RETURN_NONE;
341341
}
342342

343+
SWIGEXPORT void*
344+
LLDBSwigPythonCreateScriptedThread
345+
(
346+
const char *python_class_name,
347+
const char *session_dictionary_name,
348+
const lldb::TargetSP& target_sp,
349+
std::string &error_string
350+
)
351+
{
352+
if (python_class_name == NULL || python_class_name[0] == '\0' || !session_dictionary_name)
353+
Py_RETURN_NONE;
354+
355+
PyErr_Cleaner py_err_cleaner(true);
356+
357+
auto dict = PythonModule::MainModule().ResolveName<PythonDictionary>(session_dictionary_name);
358+
auto pfunc = PythonObject::ResolveNameWithDictionary<PythonCallable>(python_class_name, dict);
359+
360+
if (!pfunc.IsAllocated()) {
361+
error_string.append("could not find script class: ");
362+
error_string.append(python_class_name);
363+
return nullptr;
364+
}
365+
366+
// I do not want the SBTarget to be deallocated when going out of scope
367+
// because python has ownership of it and will manage memory for this
368+
// object by itself
369+
PythonObject target_arg(PyRefType::Owned, SBTypeToSWIGWrapper(new lldb::SBTarget(target_sp)));
370+
371+
if (!target_arg.IsAllocated())
372+
Py_RETURN_NONE;
373+
374+
llvm::Expected<PythonCallable::ArgInfo> arg_info = pfunc.GetArgInfo();
375+
if (!arg_info) {
376+
llvm::handleAllErrors(
377+
arg_info.takeError(),
378+
[&](PythonException &E) {
379+
error_string.append(E.ReadBacktrace());
380+
},
381+
[&](const llvm::ErrorInfoBase &E) {
382+
error_string.append(E.message());
383+
});
384+
Py_RETURN_NONE;
385+
}
386+
387+
PythonObject result = {};
388+
if (arg_info.get().max_positional_args == 1) {
389+
result = pfunc(target_arg);
390+
} else {
391+
error_string.assign("wrong number of arguments in __init__, should be 2 or 3 (not including self)");
392+
Py_RETURN_NONE;
393+
}
394+
395+
if (result.IsAllocated())
396+
return result.release();
397+
Py_RETURN_NONE;
398+
}
399+
343400
SWIGEXPORT void*
344401
LLDBSwigPythonCreateScriptedThreadPlan
345402
(

lldb/examples/python/scripted_process/my_scripted_process.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
import os
1+
import os,struct,signal
2+
3+
from typing import Any, Dict
24

35
import lldb
46
from lldb.plugins.scripted_process import ScriptedProcess
7+
from lldb.plugins.scripted_process import ScriptedThread
58

69
class MyScriptedProcess(ScriptedProcess):
710
def __init__(self, target: lldb.SBTarget, args : lldb.SBStructuredData):
@@ -35,6 +38,45 @@ def should_stop(self) -> bool:
3538
def is_alive(self) -> bool:
3639
return True
3740

41+
def get_scripted_thread_plugin(self):
42+
return MyScriptedThread.__module__ + "." + MyScriptedThread.__name__
43+
44+
45+
class MyScriptedThread(ScriptedThread):
46+
def __init__(self, target):
47+
super().__init__(target)
48+
49+
def get_thread_id(self) -> int:
50+
return 0x19
51+
52+
def get_name(self) -> str:
53+
return MyScriptedThread.__name__ + ".thread-1"
54+
55+
def get_stop_reason(self) -> Dict[str, Any]:
56+
return { "type": lldb.eStopReasonSignal, "data": {
57+
"signal": signal.SIGINT
58+
} }
59+
60+
def get_stackframes(self):
61+
class ScriptedStackFrame:
62+
def __init__(idx, cfa, pc, symbol_ctx):
63+
self.idx = idx
64+
self.cfa = cfa
65+
self.pc = pc
66+
self.symbol_ctx = symbol_ctx
67+
68+
69+
symbol_ctx = lldb.SBSymbolContext()
70+
frame_zero = ScriptedStackFrame(0, 0x42424242, 0x5000000, symbol_ctx)
71+
self.frames.append(frame_zero)
72+
73+
return self.frame_zero[0:0]
74+
75+
def get_register_context(self) -> str:
76+
return struct.pack(
77+
'21Q', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21)
78+
79+
3880
def __lldb_init_module(debugger, dict):
3981
if not 'SKIP_SCRIPTED_PROCESS_LAUNCH' in os.environ:
4082
debugger.HandleCommand(

lldb/examples/python/scripted_process/scripted_process.py

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,3 +163,163 @@ def is_alive(self):
163163
bool: True if scripted process is alive. False otherwise.
164164
"""
165165
pass
166+
167+
@abstractmethod
168+
def get_scripted_thread_plugin(self):
169+
""" Get scripted thread plugin name.
170+
171+
Returns:
172+
str: Name of the scripted thread plugin.
173+
"""
174+
return None
175+
176+
@six.add_metaclass(ABCMeta)
177+
class ScriptedThread:
178+
179+
"""
180+
The base class for a scripted thread.
181+
182+
Most of the base class methods are `@abstractmethod` that need to be
183+
overwritten by the inheriting class.
184+
185+
DISCLAIMER: THIS INTERFACE IS STILL UNDER DEVELOPMENT AND NOT STABLE.
186+
THE METHODS EXPOSED MIGHT CHANGE IN THE FUTURE.
187+
"""
188+
189+
@abstractmethod
190+
def __init__(self, target):
191+
""" Construct a scripted thread.
192+
193+
Args:
194+
target (lldb.SBTarget): The target launching the scripted process.
195+
args (lldb.SBStructuredData): A Dictionary holding arbitrary
196+
key/value pairs used by the scripted process.
197+
"""
198+
self.target = None
199+
self.args = None
200+
if isinstance(target, lldb.SBTarget) and target.IsValid():
201+
self.target = target
202+
203+
self.id = None
204+
self.name = None
205+
self.queue = None
206+
self.state = None
207+
self.stop_reason = None
208+
self.register_info = None
209+
self.register_ctx = []
210+
self.frames = []
211+
212+
@abstractmethod
213+
def get_thread_id(self):
214+
""" Get the scripted thread identifier.
215+
216+
Returns:
217+
int: The identifier of the scripted thread.
218+
"""
219+
pass
220+
221+
@abstractmethod
222+
def get_name(self):
223+
""" Get the scripted thread name.
224+
225+
Returns:
226+
str: The name of the scripted thread.
227+
"""
228+
pass
229+
230+
def get_state(self):
231+
""" Get the scripted thread state type.
232+
233+
eStateStopped, ///< Process or thread is stopped and can be examined.
234+
eStateRunning, ///< Process or thread is running and can't be examined.
235+
eStateStepping, ///< Process or thread is in the process of stepping and can
236+
/// not be examined.
237+
238+
Returns:
239+
int: The state type of the scripted thread.
240+
Returns lldb.eStateStopped by default.
241+
"""
242+
return lldb.eStateStopped
243+
244+
def get_queue(self):
245+
""" Get the scripted thread associated queue name.
246+
This method is optional.
247+
248+
Returns:
249+
str: The queue name associated with the scripted thread.
250+
"""
251+
pass
252+
253+
@abstractmethod
254+
def get_stop_reason(self):
255+
""" Get the dictionary describing the stop reason type with some data.
256+
This method is optional.
257+
258+
Returns:
259+
Dict: The dictionary holding the stop reason type and the possibly
260+
the stop reason data.
261+
"""
262+
pass
263+
264+
def get_stackframes(self):
265+
""" Get the list of stack frames for the scripted thread.
266+
267+
```
268+
class ScriptedStackFrame:
269+
def __init__(idx, cfa, pc, symbol_ctx):
270+
self.idx = idx
271+
self.cfa = cfa
272+
self.pc = pc
273+
self.symbol_ctx = symbol_ctx
274+
```
275+
276+
Returns:
277+
List[ScriptedFrame]: A list of `ScriptedStackFrame`
278+
containing for each entry, the frame index, the canonical
279+
frame address, the program counter value for that frame
280+
and a symbol context.
281+
None if the list is empty.
282+
"""
283+
return 0
284+
285+
def get_register_info(self):
286+
if self.register_info is None:
287+
self.register_info = dict()
288+
triple = self.target.triple
289+
if triple:
290+
arch = triple.split('-')[0]
291+
if arch == 'x86_64':
292+
self.register_info['sets'] = ['GPR', 'FPU', 'EXC']
293+
self.register_info['registers'] = [
294+
{'name': 'rax', 'bitsize': 64, 'offset': 0, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 0, 'dwarf': 0},
295+
{'name': 'rbx', 'bitsize': 64, 'offset': 8, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 3, 'dwarf': 3},
296+
{'name': 'rcx', 'bitsize': 64, 'offset': 16, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 2, 'dwarf': 2, 'generic': 'arg4', 'alt-name': 'arg4', },
297+
{'name': 'rdx', 'bitsize': 64, 'offset': 24, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 1, 'dwarf': 1, 'generic': 'arg3', 'alt-name': 'arg3', },
298+
{'name': 'rdi', 'bitsize': 64, 'offset': 32, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 5, 'dwarf': 5, 'generic': 'arg1', 'alt-name': 'arg1', },
299+
{'name': 'rsi', 'bitsize': 64, 'offset': 40, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 4, 'dwarf': 4, 'generic': 'arg2', 'alt-name': 'arg2', },
300+
{'name': 'rbp', 'bitsize': 64, 'offset': 48, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 6, 'dwarf': 6, 'generic': 'fp', 'alt-name': 'fp', },
301+
{'name': 'rsp', 'bitsize': 64, 'offset': 56, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 7, 'dwarf': 7, 'generic': 'sp', 'alt-name': 'sp', },
302+
{'name': 'r8', 'bitsize': 64, 'offset': 64, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 8, 'dwarf': 8, 'generic': 'arg5', 'alt-name': 'arg5', },
303+
{'name': 'r9', 'bitsize': 64, 'offset': 72, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 9, 'dwarf': 9, 'generic': 'arg6', 'alt-name': 'arg6', },
304+
{'name': 'r10', 'bitsize': 64, 'offset': 80, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 10, 'dwarf': 10},
305+
{'name': 'r11', 'bitsize': 64, 'offset': 88, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 11, 'dwarf': 11},
306+
{'name': 'r12', 'bitsize': 64, 'offset': 96, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 12, 'dwarf': 12},
307+
{'name': 'r13', 'bitsize': 64, 'offset': 104, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 13, 'dwarf': 13},
308+
{'name': 'r14', 'bitsize': 64, 'offset': 112, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 14, 'dwarf': 14},
309+
{'name': 'r15', 'bitsize': 64, 'offset': 120, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 15, 'dwarf': 15},
310+
{'name': 'rip', 'bitsize': 64, 'offset': 128, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'gcc': 16, 'dwarf': 16, 'generic': 'pc', 'alt-name': 'pc'},
311+
{'name': 'rflags', 'bitsize': 64, 'offset': 136, 'encoding': 'uint', 'format': 'hex', 'set': 0, 'generic': 'flags', 'alt-name': 'flags'},
312+
{'name': 'cs', 'bitsize': 64, 'offset': 144, 'encoding': 'uint', 'format': 'hex', 'set': 0},
313+
{'name': 'fs', 'bitsize': 64, 'offset': 152, 'encoding': 'uint', 'format': 'hex', 'set': 0},
314+
{'name': 'gs', 'bitsize': 64, 'offset': 160, 'encoding': 'uint', 'format': 'hex', 'set': 0},
315+
]
316+
return self.register_info
317+
318+
@abstractmethod
319+
def get_register_context(self):
320+
""" Get the scripted thread register context
321+
322+
Returns:
323+
str: A byte representing all register's value.
324+
"""
325+
pass

lldb/include/lldb/Interpreter/ScriptedInterface.h

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
#include "lldb/Core/StructuredDataImpl.h"
1313
#include "lldb/Target/ExecutionContext.h"
14+
#include "lldb/Utility/Log.h"
15+
#include "lldb/Utility/Logging.h"
1416
#include "lldb/lldb-private.h"
1517

1618
#include <string>
@@ -25,6 +27,44 @@ class ScriptedInterface {
2527
CreatePluginObject(llvm::StringRef class_name, ExecutionContext &exe_ctx,
2628
StructuredData::DictionarySP args_sp) = 0;
2729

30+
template <typename Ret>
31+
Ret ErrorWithMessage(llvm::StringRef caller_name, llvm::StringRef error_msg,
32+
Status &error,
33+
uint32_t log_caterogy = LIBLLDB_LOG_PROCESS) {
34+
LLDB_LOGF(GetLogIfAllCategoriesSet(log_caterogy), "%s ERROR = %s",
35+
caller_name.data(), error_msg.data());
36+
error.SetErrorString(llvm::Twine(caller_name + llvm::Twine(" ERROR = ") +
37+
llvm::Twine(error_msg))
38+
.str());
39+
return {};
40+
}
41+
42+
template <typename T = StructuredData::ObjectSP>
43+
bool CheckStructuredDataObject(llvm::StringRef caller, T obj, Status &error) {
44+
if (!obj) {
45+
return ErrorWithMessage<bool>(caller,
46+
llvm::Twine("Null StructuredData object (" +
47+
llvm::Twine(error.AsCString()) +
48+
llvm::Twine(")."))
49+
.str(),
50+
error);
51+
}
52+
53+
if (!obj->IsValid()) {
54+
return ErrorWithMessage<bool>(
55+
caller,
56+
llvm::Twine("Invalid StructuredData object (" +
57+
llvm::Twine(error.AsCString()) + llvm::Twine(")."))
58+
.str(),
59+
error);
60+
}
61+
62+
if (error.Fail())
63+
return ErrorWithMessage<bool>(caller, error.AsCString(), error);
64+
65+
return true;
66+
}
67+
2868
protected:
2969
StructuredData::GenericSP m_object_instance_sp;
3070
};

lldb/include/lldb/Interpreter/ScriptedProcessInterface.h

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,45 @@ class ScriptedProcessInterface : virtual public ScriptedInterface {
5757
virtual lldb::pid_t GetProcessID() { return LLDB_INVALID_PROCESS_ID; }
5858

5959
virtual bool IsAlive() { return true; }
60+
61+
virtual llvm::Optional<std::string> GetScriptedThreadPluginName() {
62+
return llvm::None;
63+
}
64+
65+
protected:
66+
friend class ScriptedThread;
67+
virtual lldb::ScriptedThreadInterfaceSP GetScriptedThreadInterface() {
68+
return nullptr;
69+
}
70+
71+
lldb::ScriptedThreadInterfaceSP m_scripted_thread_interface_sp = nullptr;
72+
};
73+
74+
class ScriptedThreadInterface : virtual public ScriptedInterface {
75+
public:
76+
StructuredData::GenericSP
77+
CreatePluginObject(llvm::StringRef class_name, ExecutionContext &exe_ctx,
78+
StructuredData::DictionarySP args_sp) override {
79+
return nullptr;
80+
}
81+
82+
virtual lldb::tid_t GetThreadID() { return LLDB_INVALID_THREAD_ID; }
83+
84+
virtual llvm::Optional<std::string> GetName() { return llvm::None; }
85+
86+
virtual lldb::StateType GetState() { return lldb::eStateInvalid; }
87+
88+
virtual llvm::Optional<std::string> GetQueue() { return llvm::None; }
89+
90+
virtual StructuredData::DictionarySP GetStopReason() { return nullptr; }
91+
92+
virtual StructuredData::ArraySP GetStackFrames() { return nullptr; }
93+
94+
virtual StructuredData::DictionarySP GetRegisterInfo() { return nullptr; }
95+
96+
virtual llvm::Optional<std::string> GetRegisterContext() {
97+
return llvm::None;
98+
}
6099
};
61100
} // namespace lldb_private
62101

0 commit comments

Comments
 (0)