Skip to content

Commit 0302786

Browse files
committed
[lldb/Plugins] Improve error reporting with reading memory in Scripted Process
This patch improves the ScriptedPythonInterface::Dispatch method to support passing lldb_private types to the python implementation. This will allow, for instance, the Scripted Process python implementation to report errors when reading memory back to lldb. To do so, the Dispatch method will transform the private types in the parameter pack into `PythonObject`s to be able to pass them down to the python methods. Then, if the call succeeded, the transformed arguments will be converted back to their original type and re-assigned in the parameter pack, to ensure pointers and references behaviours are preserved. This patch also updates various scripted process python class and tests to reflect this change. rdar://100030995 Differential Revision: https://reviews.llvm.org/D134033 Signed-off-by: Med Ismail Bennani <[email protected]>
1 parent 6291458 commit 0302786

File tree

14 files changed

+214
-78
lines changed

14 files changed

+214
-78
lines changed

lldb/bindings/python/python-swigsafecast.swig

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,6 @@ PythonObject ToSWIGHelper(void *obj, swig_type_info *info) {
55
return {PyRefType::Owned, SWIG_NewPointerObj(obj, info, SWIG_POINTER_OWN)};
66
}
77

8-
/// A class that automatically clears an SB object when it goes out of scope.
9-
/// Use for cases where the SB object points to a temporary/unowned entity.
10-
template <typename T> class ScopedPythonObject : PythonObject {
11-
public:
12-
ScopedPythonObject(T *sb, swig_type_info *info)
13-
: PythonObject(ToSWIGHelper(sb, info)), m_sb(sb) {}
14-
~ScopedPythonObject() {
15-
if (m_sb)
16-
*m_sb = T();
17-
}
18-
ScopedPythonObject(ScopedPythonObject &&rhs)
19-
: PythonObject(std::move(rhs)), m_sb(std::exchange(rhs.m_sb, nullptr)) {}
20-
ScopedPythonObject(const ScopedPythonObject &) = delete;
21-
ScopedPythonObject &operator=(const ScopedPythonObject &) = delete;
22-
ScopedPythonObject &operator=(ScopedPythonObject &&) = delete;
23-
24-
const PythonObject &obj() const { return *this; }
25-
26-
private:
27-
T *m_sb;
28-
};
29-
308
PythonObject ToSWIGWrapper(std::unique_ptr<lldb::SBValue> value_sb) {
319
return ToSWIGHelper(value_sb.release(), SWIGTYPE_p_lldb__SBValue);
3210
}
@@ -55,6 +33,10 @@ PythonObject ToSWIGWrapper(lldb::BreakpointSP breakpoint_sp) {
5533
SWIGTYPE_p_lldb__SBBreakpoint);
5634
}
5735

36+
PythonObject ToSWIGWrapper(const Status& status) {
37+
return ToSWIGHelper(new lldb::SBError(status), SWIGTYPE_p_lldb__SBError);
38+
}
39+
5840
PythonObject ToSWIGWrapper(std::unique_ptr<lldb::SBStream> stream_sb) {
5941
return ToSWIGHelper(stream_sb.release(), SWIGTYPE_p_lldb__SBStream);
6042
}

lldb/examples/python/scripted_process/crashlog_scripted_process.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def get_thread_with_id(self, tid: int):
103103
def get_registers_for_thread(self, tid: int):
104104
return {}
105105

106-
def read_memory_at_address(self, addr: int, size: int) -> lldb.SBData:
106+
def read_memory_at_address(self, addr: int, size: int, error: lldb.SBError) -> lldb.SBData:
107107
# NOTE: CrashLogs don't contain any memory.
108108
return lldb.SBData()
109109

lldb/examples/python/scripted_process/scripted_process.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,14 @@ def get_registers_for_thread(self, tid):
9898
pass
9999

100100
@abstractmethod
101-
def read_memory_at_address(self, addr, size):
101+
def read_memory_at_address(self, addr, size, error):
102102
""" Get a memory buffer from the scripted process at a certain address,
103103
of a certain size.
104104
105105
Args:
106106
addr (int): Address from which we should start reading.
107107
size (int): Size of the memory to read.
108+
error (lldb.SBError): Error object.
108109
109110
Returns:
110111
lldb.SBData: An `lldb.SBData` buffer with the target byte size and

lldb/include/lldb/API/SBError.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ class LLDB_API SBError {
2323

2424
SBError(const lldb::SBError &rhs);
2525

26+
SBError(const lldb_private::Status &error);
27+
2628
~SBError();
2729

2830
const SBError &operator=(const lldb::SBError &rhs);

lldb/source/API/SBError.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ SBError::SBError(const SBError &rhs) {
2525
m_opaque_up = clone(rhs.m_opaque_up);
2626
}
2727

28+
SBError::SBError(const lldb_private::Status &status)
29+
: m_opaque_up(new Status(status)) {
30+
LLDB_INSTRUMENT_VA(this, status);
31+
}
32+
2833
SBError::~SBError() = default;
2934

3035
const SBError &SBError::operator=(const SBError &rhs) {

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

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,68 @@
2323
#include "lldb/lldb-types.h"
2424
#include "llvm/Support/Error.h"
2525

26+
namespace lldb {
27+
class SBEvent;
28+
class SBCommandReturnObject;
29+
class SBValue;
30+
class SBStream;
31+
class SBStructuredData;
32+
} // namespace lldb
33+
2634
namespace lldb_private {
35+
namespace python {
36+
37+
typedef struct swig_type_info swig_type_info;
38+
39+
python::PythonObject ToSWIGHelper(void *obj, swig_type_info *info);
40+
41+
/// A class that automatically clears an SB object when it goes out of scope.
42+
/// Use for cases where the SB object points to a temporary/unowned entity.
43+
template <typename T> class ScopedPythonObject : PythonObject {
44+
public:
45+
ScopedPythonObject(T *sb, swig_type_info *info)
46+
: PythonObject(ToSWIGHelper(sb, info)), m_sb(sb) {}
47+
~ScopedPythonObject() {
48+
if (m_sb)
49+
*m_sb = T();
50+
}
51+
ScopedPythonObject(ScopedPythonObject &&rhs)
52+
: PythonObject(std::move(rhs)), m_sb(std::exchange(rhs.m_sb, nullptr)) {}
53+
ScopedPythonObject(const ScopedPythonObject &) = delete;
54+
ScopedPythonObject &operator=(const ScopedPythonObject &) = delete;
55+
ScopedPythonObject &operator=(ScopedPythonObject &&) = delete;
56+
57+
const PythonObject &obj() const { return *this; }
58+
59+
private:
60+
T *m_sb;
61+
};
62+
63+
PythonObject ToSWIGWrapper(lldb::ValueObjectSP value_sp);
64+
PythonObject ToSWIGWrapper(lldb::TargetSP target_sp);
65+
PythonObject ToSWIGWrapper(lldb::ProcessSP process_sp);
66+
PythonObject ToSWIGWrapper(lldb::ThreadPlanSP thread_plan_sp);
67+
PythonObject ToSWIGWrapper(lldb::BreakpointSP breakpoint_sp);
68+
PythonObject ToSWIGWrapper(const Status &status);
69+
PythonObject ToSWIGWrapper(const StructuredDataImpl &data_impl);
70+
PythonObject ToSWIGWrapper(lldb::ThreadSP thread_sp);
71+
PythonObject ToSWIGWrapper(lldb::StackFrameSP frame_sp);
72+
PythonObject ToSWIGWrapper(lldb::DebuggerSP debugger_sp);
73+
PythonObject ToSWIGWrapper(lldb::WatchpointSP watchpoint_sp);
74+
PythonObject ToSWIGWrapper(lldb::BreakpointLocationSP bp_loc_sp);
75+
PythonObject ToSWIGWrapper(lldb::ExecutionContextRefSP ctx_sp);
76+
PythonObject ToSWIGWrapper(const TypeSummaryOptions &summary_options);
77+
PythonObject ToSWIGWrapper(const SymbolContext &sym_ctx);
78+
79+
PythonObject ToSWIGWrapper(std::unique_ptr<lldb::SBValue> value_sb);
80+
PythonObject ToSWIGWrapper(std::unique_ptr<lldb::SBStream> stream_sb);
81+
PythonObject ToSWIGWrapper(std::unique_ptr<lldb::SBStructuredData> data_sb);
82+
83+
python::ScopedPythonObject<lldb::SBCommandReturnObject>
84+
ToSWIGWrapper(CommandReturnObject &cmd_retobj);
85+
python::ScopedPythonObject<lldb::SBEvent> ToSWIGWrapper(Event *event);
86+
87+
} // namespace python
2788

2889
// GetPythonValueFormatString provides a system independent type safe way to
2990
// convert a variable's type into a python value format. Python value formats

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "lldb/Host/Config.h"
1010
#include "lldb/Utility/Log.h"
11+
#include "lldb/Utility/Status.h"
1112
#include "lldb/lldb-enumerations.h"
1213

1314
#if LLDB_ENABLE_PYTHON
@@ -120,8 +121,15 @@ ScriptedProcessPythonInterface::GetRegistersForThread(lldb::tid_t tid) {
120121

121122
lldb::DataExtractorSP ScriptedProcessPythonInterface::ReadMemoryAtAddress(
122123
lldb::addr_t address, size_t size, Status &error) {
123-
return Dispatch<lldb::DataExtractorSP>("read_memory_at_address", error,
124-
address, size);
124+
Status py_error;
125+
lldb::DataExtractorSP data_sp = Dispatch<lldb::DataExtractorSP>(
126+
"read_memory_at_address", py_error, address, size, error);
127+
128+
// If there was an error on the python call, surface it to the user.
129+
if (py_error.Fail())
130+
error = py_error;
131+
132+
return data_sp;
125133
}
126134

127135
StructuredData::ArraySP ScriptedProcessPythonInterface::GetLoadedImages() {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,11 @@ Status ScriptedPythonInterface::ExtractValueFromPythonObject<Status>(
5555
python::PythonObject &p, Status &error) {
5656
if (lldb::SBError *sb_error = reinterpret_cast<lldb::SBError *>(
5757
LLDBSWIGPython_CastPyObjectToSBError(p.get())))
58-
error = m_interpreter.GetStatusFromSBError(*sb_error);
58+
return m_interpreter.GetStatusFromSBError(*sb_error);
5959
else
6060
error.SetErrorString("Couldn't cast lldb::SBError to lldb::Status.");
6161

62-
return error;
62+
return {};
6363
}
6464

6565
template <>

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

Lines changed: 105 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,14 @@
99
#ifndef LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_SCRIPTEDPYTHONINTERFACE_H
1010
#define LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_SCRIPTEDPYTHONINTERFACE_H
1111

12-
#include "lldb/Host/Config.h"
13-
1412
#if LLDB_ENABLE_PYTHON
1513

14+
#include <sstream>
15+
#include <tuple>
16+
#include <type_traits>
17+
#include <utility>
18+
19+
#include "lldb/Host/Config.h"
1620
#include "lldb/Interpreter/ScriptedInterface.h"
1721
#include "lldb/Utility/DataBufferHeap.h"
1822

@@ -34,7 +38,7 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
3438
}
3539

3640
template <typename T = StructuredData::ObjectSP, typename... Args>
37-
T Dispatch(llvm::StringRef method_name, Status &error, Args... args) {
41+
T Dispatch(llvm::StringRef method_name, Status &error, Args &&...args) {
3842
using namespace python;
3943
using Locker = ScriptInterpreterPythonImpl::Locker;
4044

@@ -56,59 +60,116 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
5660
return ErrorWithMessage<T>(caller_signature,
5761
"Python implementor not allocated.", error);
5862

59-
PythonObject pmeth(
60-
PyRefType::Owned,
61-
PyObject_GetAttrString(implementor.get(), method_name.str().c_str()));
63+
std::tuple<Args...> original_args = std::forward_as_tuple(args...);
64+
auto transformed_args = TransformArgs(original_args);
65+
66+
llvm::Expected<PythonObject> expected_return_object =
67+
llvm::make_error<llvm::StringError>("Not initialized.",
68+
llvm::inconvertibleErrorCode());
69+
std::apply(
70+
[&implementor, &method_name, &expected_return_object](auto &&...args) {
71+
llvm::consumeError(expected_return_object.takeError());
72+
expected_return_object =
73+
implementor.CallMethod(method_name.data(), args...);
74+
},
75+
transformed_args);
76+
77+
if (llvm::Error e = expected_return_object.takeError()) {
78+
error.SetErrorString(llvm::toString(std::move(e)).c_str());
79+
return ErrorWithMessage<T>(caller_signature,
80+
"Python method could not be called.", error);
81+
}
6282

63-
if (PyErr_Occurred())
64-
PyErr_Clear();
83+
PythonObject py_return = std::move(expected_return_object.get());
6584

66-
if (!pmeth.IsAllocated())
67-
return ErrorWithMessage<T>(caller_signature,
68-
"Python method not allocated.", error);
85+
if (!py_return.IsAllocated())
86+
return ErrorWithMessage<T>(caller_signature, "Returned object is null.",
87+
error);
6988

70-
if (PyCallable_Check(pmeth.get()) == 0) {
71-
if (PyErr_Occurred())
72-
PyErr_Clear();
73-
return ErrorWithMessage<T>(caller_signature,
74-
"Python method not callable.", error);
75-
}
89+
// Now that we called the python method with the transformed arguments,
90+
// we need to interate again over both the original and transformed
91+
// parameter pack, and transform back the parameter that were passed in
92+
// the original parameter pack as references or pointers.
93+
if (sizeof...(Args) > 0)
94+
if (!ReassignPtrsOrRefsArgs(original_args, transformed_args))
95+
return ErrorWithMessage<T>(
96+
caller_signature,
97+
"Couldn't re-assign reference and pointer arguments.", error);
7698

77-
if (PyErr_Occurred())
78-
PyErr_Clear();
99+
return ExtractValueFromPythonObject<T>(py_return, error);
100+
}
79101

80-
// TODO: make `const char *` when removing support for Python 2.
81-
char *format = nullptr;
82-
std::string format_buffer;
102+
Status GetStatusFromMethod(llvm::StringRef method_name);
83103

84-
if (sizeof...(Args) > 0) {
85-
FormatArgs(format_buffer, args...);
86-
// TODO: make `const char *` when removing support for Python 2.
87-
format = const_cast<char *>(format_buffer.c_str());
104+
template <typename T> struct transformation { using type = T; };
105+
template <typename T, typename U> struct reverse_transformation {
106+
static void Apply(ScriptedPythonInterface &obj, T &original_arg,
107+
U transformed_arg, Status &error) {
108+
// If U is not a PythonObject, don't touch it!
109+
return;
110+
}
111+
};
112+
113+
template <> struct transformation<Status> {
114+
using type = python::PythonObject;
115+
};
116+
template <typename T> struct reverse_transformation<T, python::PythonObject> {
117+
static void Apply(ScriptedPythonInterface &obj, T &original_arg,
118+
python::PythonObject transformed_arg, Status &error) {
119+
original_arg =
120+
obj.ExtractValueFromPythonObject<T>(transformed_arg, error);
88121
}
122+
};
89123

90-
// TODO: make `const char *` when removing support for Python 2.
91-
PythonObject py_return(
92-
PyRefType::Owned,
93-
PyObject_CallMethod(implementor.get(),
94-
const_cast<char *>(method_name.data()), format,
95-
args...));
124+
template <typename T> typename transformation<T>::type Transform(T object) {
125+
// No Transformation for generic usage
126+
return {object};
127+
}
96128

97-
if (PyErr_Occurred()) {
98-
PyErr_Print();
99-
PyErr_Clear();
100-
return ErrorWithMessage<T>(caller_signature,
101-
"Python method could not be called.", error);
102-
}
129+
template <> typename transformation<Status>::type Transform(Status arg) {
130+
// Call SWIG Wrapper function
131+
return python::ToSWIGWrapper(arg);
132+
}
103133

104-
if (!py_return.IsAllocated())
105-
return ErrorWithMessage<T>(caller_signature, "Returned object is null.",
106-
error);
134+
template <std::size_t... I, typename... Args>
135+
auto TransformTuple(const std::tuple<Args...> &args,
136+
std::index_sequence<I...>) {
137+
return std::make_tuple(Transform(std::get<I>(args))...);
138+
}
107139

108-
return ExtractValueFromPythonObject<T>(py_return, error);
140+
// This will iterate over the Dispatch parameter pack and replace in-place
141+
// every `lldb_private` argument that has a SB counterpart.
142+
template <typename... Args>
143+
auto TransformArgs(const std::tuple<Args...> &args) {
144+
return TransformTuple(args, std::make_index_sequence<sizeof...(Args)>());
109145
}
110146

111-
Status GetStatusFromMethod(llvm::StringRef method_name);
147+
template <typename T, typename U>
148+
void TransformBack(T &original_arg, U transformed_arg, Status &error) {
149+
reverse_transformation<T, U>::Apply(*this, original_arg, transformed_arg,
150+
error);
151+
}
152+
153+
template <std::size_t... I, typename... Ts, typename... Us>
154+
bool ReassignPtrsOrRefsArgs(std::tuple<Ts...> &original_args,
155+
std::tuple<Us...> &transformed_args,
156+
std::index_sequence<I...>) {
157+
Status error;
158+
(TransformBack(std::get<I>(original_args), std::get<I>(transformed_args),
159+
error),
160+
...);
161+
return error.Success();
162+
}
163+
164+
template <typename... Ts, typename... Us>
165+
bool ReassignPtrsOrRefsArgs(std::tuple<Ts...> &original_args,
166+
std::tuple<Us...> &transformed_args) {
167+
if (sizeof...(Ts) != sizeof...(Us))
168+
return false;
169+
170+
return ReassignPtrsOrRefsArgs(original_args, transformed_args,
171+
std::make_index_sequence<sizeof...(Ts)>());
172+
}
112173

113174
template <typename T, typename... Args>
114175
void FormatArgs(std::string &fmt, T arg, Args... args) const {
@@ -117,7 +178,7 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
117178
}
118179

119180
template <typename T> void FormatArgs(std::string &fmt, T arg) const {
120-
fmt += GetPythonValueFormatString(arg);
181+
fmt += python::PythonFormat<T>::format;
121182
}
122183

123184
void FormatArgs(std::string &fmt) const {}

0 commit comments

Comments
 (0)