diff options
author | Dimitry Andric <dim@FreeBSD.org> | 2020-01-17 20:45:01 +0000 |
---|---|---|
committer | Dimitry Andric <dim@FreeBSD.org> | 2020-01-17 20:45:01 +0000 |
commit | 706b4fc47bbc608932d3b491ae19a3b9cde9497b (patch) | |
tree | 4adf86a776049cbf7f69a1929c4babcbbef925eb /lldb/source/Plugins/ScriptInterpreter | |
parent | 7cc9cf2bf09f069cb2dd947ead05d0b54301fb71 (diff) |
Notes
Diffstat (limited to 'lldb/source/Plugins/ScriptInterpreter')
12 files changed, 690 insertions, 147 deletions
diff --git a/lldb/source/Plugins/ScriptInterpreter/Lua/Lua.cpp b/lldb/source/Plugins/ScriptInterpreter/Lua/Lua.cpp new file mode 100644 index 0000000000000..ecee8cc674f81 --- /dev/null +++ b/lldb/source/Plugins/ScriptInterpreter/Lua/Lua.cpp @@ -0,0 +1,59 @@ +//===-- Lua.cpp -----------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "Lua.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/Utility/FileSpec.h" +#include "llvm/Support/FormatVariadic.h" + +using namespace lldb_private; +using namespace lldb; + +llvm::Error Lua::Run(llvm::StringRef buffer) { + int error = + luaL_loadbuffer(m_lua_state, buffer.data(), buffer.size(), "buffer") || + lua_pcall(m_lua_state, 0, 0, 0); + if (!error) + return llvm::Error::success(); + + llvm::Error e = llvm::make_error<llvm::StringError>( + llvm::formatv("{0}\n", lua_tostring(m_lua_state, -1)), + llvm::inconvertibleErrorCode()); + // Pop error message from the stack. + lua_pop(m_lua_state, 1); + return e; +} + +llvm::Error Lua::LoadModule(llvm::StringRef filename) { + FileSpec file(filename); + if (!FileSystem::Instance().Exists(file)) { + return llvm::make_error<llvm::StringError>("invalid path", + llvm::inconvertibleErrorCode()); + } + + ConstString module_extension = file.GetFileNameExtension(); + if (module_extension != ".lua") { + return llvm::make_error<llvm::StringError>("invalid extension", + llvm::inconvertibleErrorCode()); + } + + int error = luaL_loadfile(m_lua_state, filename.data()) || + lua_pcall(m_lua_state, 0, 1, 0); + if (error) { + llvm::Error e = llvm::make_error<llvm::StringError>( + llvm::formatv("{0}\n", lua_tostring(m_lua_state, -1)), + llvm::inconvertibleErrorCode()); + // Pop error message from the stack. + lua_pop(m_lua_state, 1); + return e; + } + + ConstString module_name = file.GetFileNameStrippingExtension(); + lua_setglobal(m_lua_state, module_name.GetCString()); + return llvm::Error::success(); +} diff --git a/lldb/source/Plugins/ScriptInterpreter/Lua/Lua.h b/lldb/source/Plugins/ScriptInterpreter/Lua/Lua.h new file mode 100644 index 0000000000000..f2984a925dfe1 --- /dev/null +++ b/lldb/source/Plugins/ScriptInterpreter/Lua/Lua.h @@ -0,0 +1,48 @@ +//===-- ScriptInterpreterLua.h ----------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_Lua_h_ +#define liblldb_Lua_h_ + +#include "lldb/lldb-types.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" + +#include "lua.hpp" + +#include <mutex> + +namespace lldb_private { + +extern "C" { +int luaopen_lldb(lua_State *L); +} + +class Lua { +public: + Lua() : m_lua_state(luaL_newstate()) { + assert(m_lua_state); + luaL_openlibs(m_lua_state); + luaopen_lldb(m_lua_state); + } + + ~Lua() { + assert(m_lua_state); + luaL_openlibs(m_lua_state); + } + + llvm::Error Run(llvm::StringRef buffer); + llvm::Error LoadModule(llvm::StringRef filename); + +private: + lua_State *m_lua_state; +}; + +} // namespace lldb_private + +#endif // liblldb_Lua_h_ diff --git a/lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.cpp b/lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.cpp new file mode 100644 index 0000000000000..701d68d1ec084 --- /dev/null +++ b/lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.cpp @@ -0,0 +1,157 @@ +//===-- ScriptInterpreterLua.cpp --------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "ScriptInterpreterLua.h" +#include "Lua.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Core/StreamFile.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Utility/Stream.h" +#include "lldb/Utility/StringList.h" +#include "lldb/Utility/Timer.h" + +using namespace lldb; +using namespace lldb_private; + +class IOHandlerLuaInterpreter : public IOHandlerDelegate, + public IOHandlerEditline { +public: + IOHandlerLuaInterpreter(Debugger &debugger, + ScriptInterpreterLua &script_interpreter) + : IOHandlerEditline(debugger, IOHandler::Type::LuaInterpreter, "lua", + ">>> ", "..> ", true, debugger.GetUseColor(), 0, + *this, nullptr), + m_script_interpreter(script_interpreter) { + llvm::cantFail(m_script_interpreter.EnterSession(debugger.GetID())); + } + + ~IOHandlerLuaInterpreter() { + llvm::cantFail(m_script_interpreter.LeaveSession()); + } + + void IOHandlerInputComplete(IOHandler &io_handler, + std::string &data) override { + if (llvm::Error error = m_script_interpreter.GetLua().Run(data)) { + *GetOutputStreamFileSP() << llvm::toString(std::move(error)); + } + } + +private: + ScriptInterpreterLua &m_script_interpreter; +}; + +ScriptInterpreterLua::ScriptInterpreterLua(Debugger &debugger) + : ScriptInterpreter(debugger, eScriptLanguageLua), + m_lua(std::make_unique<Lua>()) {} + +ScriptInterpreterLua::~ScriptInterpreterLua() {} + +bool ScriptInterpreterLua::ExecuteOneLine(llvm::StringRef command, + CommandReturnObject *result, + const ExecuteScriptOptions &options) { + if (llvm::Error e = m_lua->Run(command)) { + result->AppendErrorWithFormatv( + "lua failed attempting to evaluate '{0}': {1}\n", command, + llvm::toString(std::move(e))); + return false; + } + return true; +} + +void ScriptInterpreterLua::ExecuteInterpreterLoop() { + static Timer::Category func_cat(LLVM_PRETTY_FUNCTION); + Timer scoped_timer(func_cat, LLVM_PRETTY_FUNCTION); + + Debugger &debugger = m_debugger; + + // At the moment, the only time the debugger does not have an input file + // handle is when this is called directly from lua, in which case it is + // both dangerous and unnecessary (not to mention confusing) to try to embed + // a running interpreter loop inside the already running lua interpreter + // loop, so we won't do it. + + if (!debugger.GetInputFile().IsValid()) + return; + + IOHandlerSP io_handler_sp(new IOHandlerLuaInterpreter(debugger, *this)); + debugger.PushIOHandler(io_handler_sp); +} + +bool ScriptInterpreterLua::LoadScriptingModule( + const char *filename, bool init_session, lldb_private::Status &error, + StructuredData::ObjectSP *module_sp) { + + if (llvm::Error e = m_lua->LoadModule(filename)) { + error.SetErrorStringWithFormatv("lua failed to import '{0}': {1}\n", + filename, llvm::toString(std::move(e))); + return false; + } + return true; +} + +void ScriptInterpreterLua::Initialize() { + static llvm::once_flag g_once_flag; + + llvm::call_once(g_once_flag, []() { + PluginManager::RegisterPlugin(GetPluginNameStatic(), + GetPluginDescriptionStatic(), + lldb::eScriptLanguageLua, CreateInstance); + }); +} + +void ScriptInterpreterLua::Terminate() {} + +llvm::Error ScriptInterpreterLua::EnterSession(user_id_t debugger_id) { + if (m_session_is_active) + return llvm::Error::success(); + + const char *fmt_str = + "lldb.debugger = lldb.SBDebugger.FindDebuggerWithID({0}); " + "lldb.target = lldb.debugger:GetSelectedTarget(); " + "lldb.process = lldb.target:GetProcess(); " + "lldb.thread = lldb.process:GetSelectedThread(); " + "lldb.frame = lldb.thread:GetSelectedFrame()"; + return m_lua->Run(llvm::formatv(fmt_str, debugger_id).str()); +} + +llvm::Error ScriptInterpreterLua::LeaveSession() { + if (!m_session_is_active) + return llvm::Error::success(); + + m_session_is_active = false; + + llvm::StringRef str = "lldb.debugger = nil; " + "lldb.target = nil; " + "lldb.process = nil; " + "lldb.thread = nil; " + "lldb.frame = nil"; + return m_lua->Run(str); +} + +lldb::ScriptInterpreterSP +ScriptInterpreterLua::CreateInstance(Debugger &debugger) { + return std::make_shared<ScriptInterpreterLua>(debugger); +} + +lldb_private::ConstString ScriptInterpreterLua::GetPluginNameStatic() { + static ConstString g_name("script-lua"); + return g_name; +} + +const char *ScriptInterpreterLua::GetPluginDescriptionStatic() { + return "Lua script interpreter"; +} + +lldb_private::ConstString ScriptInterpreterLua::GetPluginName() { + return GetPluginNameStatic(); +} + +uint32_t ScriptInterpreterLua::GetPluginVersion() { return 1; } + +Lua &ScriptInterpreterLua::GetLua() { return *m_lua; } diff --git a/lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.h b/lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.h new file mode 100644 index 0000000000000..4e922151385b6 --- /dev/null +++ b/lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.h @@ -0,0 +1,61 @@ +//===-- ScriptInterpreterLua.h ----------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_ScriptInterpreterLua_h_ +#define liblldb_ScriptInterpreterLua_h_ + +#include "lldb/Interpreter/ScriptInterpreter.h" + +namespace lldb_private { +class Lua; +class ScriptInterpreterLua : public ScriptInterpreter { +public: + ScriptInterpreterLua(Debugger &debugger); + + ~ScriptInterpreterLua() override; + + bool ExecuteOneLine( + llvm::StringRef command, CommandReturnObject *result, + const ExecuteScriptOptions &options = ExecuteScriptOptions()) override; + + void ExecuteInterpreterLoop() override; + + virtual bool + LoadScriptingModule(const char *filename, bool init_session, + lldb_private::Status &error, + StructuredData::ObjectSP *module_sp = nullptr) override; + + // Static Functions + static void Initialize(); + + static void Terminate(); + + static lldb::ScriptInterpreterSP CreateInstance(Debugger &debugger); + + static lldb_private::ConstString GetPluginNameStatic(); + + static const char *GetPluginDescriptionStatic(); + + // PluginInterface protocol + lldb_private::ConstString GetPluginName() override; + + uint32_t GetPluginVersion() override; + + Lua &GetLua(); + + llvm::Error EnterSession(lldb::user_id_t debugger_id); + llvm::Error LeaveSession(); + +private: + std::unique_ptr<Lua> m_lua; + bool m_session_is_active = false; +}; + +} // namespace lldb_private + +#endif // liblldb_ScriptInterpreterLua_h_ diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp index 70d93424fdecb..e5a67653e3341 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp @@ -6,11 +6,9 @@ // //===----------------------------------------------------------------------===// -#ifdef LLDB_DISABLE_PYTHON +#include "lldb/Host/Config.h" -// Python is disabled in this build - -#else +#if LLDB_ENABLE_PYTHON #include "PythonDataObjects.h" #include "ScriptInterpreterPython.h" @@ -802,29 +800,11 @@ bool PythonCallable::Check(PyObject *py_obj) { return PyCallable_Check(py_obj); } -PythonCallable::ArgInfo PythonCallable::GetNumInitArguments() const { - auto arginfo = GetInitArgInfo(); - if (!arginfo) { - llvm::consumeError(arginfo.takeError()); - return ArgInfo{}; - } - return arginfo.get(); -} - -Expected<PythonCallable::ArgInfo> PythonCallable::GetInitArgInfo() const { - if (!IsValid()) - return nullDeref(); - auto init = As<PythonCallable>(GetAttribute("__init__")); - if (!init) - return init.takeError(); - return init.get().GetArgInfo(); -} - #if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3 static const char get_arg_info_script[] = R"( from inspect import signature, Parameter, ismethod from collections import namedtuple -ArgInfo = namedtuple('ArgInfo', ['count', 'has_varargs', 'is_bound_method']) +ArgInfo = namedtuple('ArgInfo', ['count', 'has_varargs']) def main(f): count = 0 varargs = False @@ -840,7 +820,7 @@ def main(f): pass else: raise Exception(f'unknown parameter kind: {kind}') - return ArgInfo(count, varargs, ismethod(f)) + return ArgInfo(count, varargs) )"; #endif @@ -856,21 +836,27 @@ Expected<PythonCallable::ArgInfo> PythonCallable::GetArgInfo() const { Expected<PythonObject> pyarginfo = get_arg_info(*this); if (!pyarginfo) return pyarginfo.takeError(); - result.count = cantFail(As<long long>(pyarginfo.get().GetAttribute("count"))); - result.has_varargs = + long long count = + cantFail(As<long long>(pyarginfo.get().GetAttribute("count"))); + bool has_varargs = cantFail(As<bool>(pyarginfo.get().GetAttribute("has_varargs"))); - bool is_method = - cantFail(As<bool>(pyarginfo.get().GetAttribute("is_bound_method"))); - result.max_positional_args = - result.has_varargs ? ArgInfo::UNBOUNDED : result.count; - - // FIXME emulate old broken behavior - if (is_method) - result.count++; + result.max_positional_args = has_varargs ? ArgInfo::UNBOUNDED : count; #else + PyObject *py_func_obj; bool is_bound_method = false; - PyObject *py_func_obj = m_py_obj; + bool is_class = false; + + if (PyType_Check(m_py_obj) || PyClass_Check(m_py_obj)) { + auto init = GetAttribute("__init__"); + if (!init) + return init.takeError(); + py_func_obj = init.get().get(); + is_class = true; + } else { + py_func_obj = m_py_obj; + } + if (PyMethod_Check(py_func_obj)) { py_func_obj = PyMethod_GET_FUNCTION(py_func_obj); PythonObject im_self = GetAttributeValue("im_self"); @@ -899,11 +885,11 @@ Expected<PythonCallable::ArgInfo> PythonCallable::GetArgInfo() const { if (!code) return result; - result.count = code->co_argcount; - result.has_varargs = !!(code->co_flags & CO_VARARGS); - result.max_positional_args = result.has_varargs - ? ArgInfo::UNBOUNDED - : (result.count - (int)is_bound_method); + auto count = code->co_argcount; + bool has_varargs = !!(code->co_flags & CO_VARARGS); + result.max_positional_args = + has_varargs ? ArgInfo::UNBOUNDED + : (count - (int)is_bound_method) - (int)is_class; #endif @@ -913,15 +899,6 @@ Expected<PythonCallable::ArgInfo> PythonCallable::GetArgInfo() const { constexpr unsigned PythonCallable::ArgInfo::UNBOUNDED; // FIXME delete after c++17 -PythonCallable::ArgInfo PythonCallable::GetNumArguments() const { - auto arginfo = GetArgInfo(); - if (!arginfo) { - llvm::consumeError(arginfo.takeError()); - return ArgInfo{}; - } - return arginfo.get(); -} - PythonObject PythonCallable::operator()() { return PythonObject(PyRefType::Owned, PyObject_CallObject(m_py_obj, nullptr)); } @@ -1385,11 +1362,13 @@ llvm::Expected<FileSP> PythonFile::ConvertToFile(bool borrowed) { if (!options) return options.takeError(); - // LLDB and python will not share I/O buffers. We should probably - // flush the python buffers now. - auto r = CallMethod("flush"); - if (!r) - return r.takeError(); + if (options.get() & File::eOpenOptionWrite) { + // LLDB and python will not share I/O buffers. We should probably + // flush the python buffers now. + auto r = CallMethod("flush"); + if (!r) + return r.takeError(); + } FileSP file_sp; if (borrowed) { @@ -1498,14 +1477,23 @@ Expected<PythonFile> PythonFile::FromFile(File &file, const char *mode) { PyObject *file_obj; #if PY_MAJOR_VERSION >= 3 file_obj = PyFile_FromFd(file.GetDescriptor(), nullptr, mode, -1, nullptr, - "ignore", nullptr, 0); + "ignore", nullptr, /*closefd=*/0); #else - // Read through the Python source, doesn't seem to modify these strings - char *cmode = const_cast<char *>(mode); - // We pass ::flush instead of ::fclose here so we borrow the FILE* -- - // the lldb_private::File still owns it. - file_obj = - PyFile_FromFile(file.GetStream(), const_cast<char *>(""), cmode, ::fflush); + // I'd like to pass ::fflush here if the file is writable, so that + // when the python side destructs the file object it will be flushed. + // However, this would be dangerous. It can cause fflush to be called + // after fclose if the python program keeps a reference to the file after + // the original lldb_private::File has been destructed. + // + // It's all well and good to ask a python program not to use a closed file + // but asking a python program to make sure objects get released in a + // particular order is not safe. + // + // The tradeoff here is that if a python 2 program wants to make sure this + // file gets flushed, they'll have to do it explicitly or wait untill the + // original lldb File itself gets flushed. + file_obj = PyFile_FromFile(file.GetStream(), py2_const_cast(""), + py2_const_cast(mode), [](FILE *) { return 0; }); #endif if (!file_obj) diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h b/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h index 373d3212697d7..b75045b239a8c 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h @@ -48,7 +48,9 @@ #ifndef LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_PYTHONDATAOBJECTS_H #define LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_PYTHONDATAOBJECTS_H -#ifndef LLDB_DISABLE_PYTHON +#include "lldb/Host/Config.h" + +#if LLDB_ENABLE_PYTHON // LLDB Python header must be included first #include "lldb-python.h" @@ -130,7 +132,7 @@ template <typename T> T Take(PyObject *obj) { assert(!PyErr_Occurred()); T thing(PyRefType::Owned, obj); assert(thing.IsValid()); - return std::move(thing); + return thing; } // Retain a reference you have borrowed, and turn it into @@ -148,7 +150,7 @@ template <typename T> T Retain(PyObject *obj) { assert(!PyErr_Occurred()); T thing(PyRefType::Borrowed, obj); assert(thing.IsValid()); - return std::move(thing); + return thing; } // This class can be used like a utility function to convert from @@ -189,6 +191,14 @@ inline llvm::Error keyError() { "key not in dict"); } +#if PY_MAJOR_VERSION < 3 +// The python 2 API declares some arguments as char* that should +// be const char *, but it doesn't actually modify them. +inline char *py2_const_cast(const char *s) { return const_cast<char *>(s); } +#else +inline const char *py2_const_cast(const char *s) { return s; } +#endif + enum class PyInitialValue { Invalid, Empty }; template <typename T, typename Enable = void> struct PythonFormat; @@ -309,16 +319,6 @@ public: StructuredData::ObjectSP CreateStructuredObject() const; -protected: - -#if PY_MAJOR_VERSION < 3 - // The python 2 API declares some arguments as char* that should - // be const char *, but it doesn't actually modify them. - static char *py2_const_cast(const char *s) { return const_cast<char *>(s); } -#else - static const char *py2_const_cast(const char *s) { return s; } -#endif - public: template <typename... T> llvm::Expected<PythonObject> CallMethod(const char *name, @@ -621,30 +621,12 @@ public: * function and can accept an arbitrary number */ unsigned max_positional_args; static constexpr unsigned UNBOUNDED = UINT_MAX; // FIXME c++17 inline - /* the number of positional arguments, including optional ones, - * and excluding varargs. If this is a bound method, then the - * count will still include a +1 for self. - * - * FIXME. That's crazy. This should be replaced with - * an accurate min and max for positional args. - */ - int count; - /* does the callable have positional varargs? */ - bool has_varargs : 1; // FIXME delete this }; static bool Check(PyObject *py_obj); llvm::Expected<ArgInfo> GetArgInfo() const; - llvm::Expected<ArgInfo> GetInitArgInfo() const; - - ArgInfo GetNumArguments() const; // DEPRECATED - - // If the callable is a Py_Class, then find the number of arguments - // of the __init__ method. - ArgInfo GetNumInitArguments() const; // DEPRECATED - PythonObject operator()(); PythonObject operator()(std::initializer_list<PyObject *> args); diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/PythonReadline.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/PythonReadline.cpp new file mode 100644 index 0000000000000..5f6429f5cd0ec --- /dev/null +++ b/lldb/source/Plugins/ScriptInterpreter/Python/PythonReadline.cpp @@ -0,0 +1,88 @@ +#include "PythonReadline.h" + +#ifdef LLDB_USE_LIBEDIT_READLINE_COMPAT_MODULE + +#include <stdio.h> + +#include <editline/readline.h> + +// Simple implementation of the Python readline module using libedit. +// In the event that libedit is excluded from the build, this turns +// back into a null implementation that blocks the module from pulling +// in the GNU readline shared lib, which causes linkage confusion when +// both readline and libedit's readline compatibility symbols collide. +// +// Currently it only installs a PyOS_ReadlineFunctionPointer, without +// implementing any of the readline module methods. This is meant to +// work around LLVM pr18841 to avoid seg faults in the stock Python +// readline.so linked against GNU readline. +// +// Bug on the cpython side: https://bugs.python.org/issue38634 + +PyDoc_STRVAR(moduleDocumentation, + "Simple readline module implementation based on libedit."); + +#if PY_MAJOR_VERSION >= 3 +static struct PyModuleDef readline_module = { + PyModuleDef_HEAD_INIT, // m_base + "lldb_editline", // m_name + moduleDocumentation, // m_doc + -1, // m_size + nullptr, // m_methods + nullptr, // m_reload + nullptr, // m_traverse + nullptr, // m_clear + nullptr, // m_free +}; +#else +static struct PyMethodDef moduleMethods[] = {{nullptr, nullptr, 0, nullptr}}; +#endif + +static char * +#if PY_MAJOR_VERSION >= 3 +simple_readline(FILE *stdin, FILE *stdout, const char *prompt) +#else +simple_readline(FILE *stdin, FILE *stdout, char *prompt) +#endif +{ + rl_instream = stdin; + rl_outstream = stdout; + char *line = readline(prompt); + if (!line) { +#if PY_MAJOR_VERSION >= 3 + char *ret = (char *)PyMem_RawMalloc(1); +#else + char *ret = (char *)PyMem_Malloc(1); +#endif + if (ret != NULL) + *ret = '\0'; + return ret; + } + if (*line) + add_history(line); + int n = strlen(line); +#if PY_MAJOR_VERSION >= 3 + char *ret = (char *)PyMem_RawMalloc(n + 2); +#else + char *ret = (char *)PyMem_Malloc(n + 2); +#endif + if (ret) { + memcpy(ret, line, n); + free(line); + ret[n] = '\n'; + ret[n + 1] = '\0'; + } + return ret; +} + +PyMODINIT_FUNC initlldb_readline(void) { + PyOS_ReadlineFunctionPointer = simple_readline; + +#if PY_MAJOR_VERSION >= 3 + return PyModule_Create(&readline_module); +#else + Py_InitModule4("readline", moduleMethods, moduleDocumentation, + static_cast<PyObject *>(NULL), PYTHON_API_VERSION); +#endif +} +#endif diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/PythonReadline.h b/lldb/source/Plugins/ScriptInterpreter/Python/PythonReadline.h new file mode 100644 index 0000000000000..c75219eb1a4f9 --- /dev/null +++ b/lldb/source/Plugins/ScriptInterpreter/Python/PythonReadline.h @@ -0,0 +1,28 @@ +//===-- PythonReadline.h ----------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_PYTHONREADLINE_H +#define LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_PYTHONREADLINE_H + +#include "lldb/Host/Config.h" + +#if LLDB_ENABLE_LIBEDIT && defined(__linux__) +// NOTE: Since Python may define some pre-processor definitions which affect the +// standard headers on some systems, you must include Python.h before any +// standard headers are included. +#include "Python.h" + +// no need to hack into Python's readline module if libedit isn't used. +// +#define LLDB_USE_LIBEDIT_READLINE_COMPAT_MODULE 1 + +PyMODINIT_FUNC initlldb_readline(void); + +#endif + +#endif // LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_PYTHONREADLINE_H diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp index 3eee52184142d..06e0d5bfa63f4 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp @@ -6,16 +6,15 @@ // //===----------------------------------------------------------------------===// -#ifdef LLDB_DISABLE_PYTHON +#include "lldb/Host/Config.h" -// Python is disabled in this build - -#else +#if LLDB_ENABLE_PYTHON // LLDB Python header must be included first #include "lldb-python.h" #include "PythonDataObjects.h" +#include "PythonReadline.h" #include "ScriptInterpreterPythonImpl.h" #include "lldb/API/SBFrame.h" @@ -72,10 +71,28 @@ extern "C" void init_lldb(void); // These prototypes are the Pythonic implementations of the required callbacks. // Although these are scripting-language specific, their definition depends on // the public API. -extern "C" bool LLDBSwigPythonBreakpointCallbackFunction( + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreturn-type-c-linkage" + +// Disable warning C4190: 'LLDBSwigPythonBreakpointCallbackFunction' has +// C-linkage specified, but returns UDT 'llvm::Expected<bool>' which is +// incompatible with C +#if _MSC_VER +#pragma warning (push) +#pragma warning (disable : 4190) +#endif + +extern "C" llvm::Expected<bool> LLDBSwigPythonBreakpointCallbackFunction( const char *python_function_name, const char *session_dictionary_name, const lldb::StackFrameSP &sb_frame, - const lldb::BreakpointLocationSP &sb_bp_loc); + const lldb::BreakpointLocationSP &sb_bp_loc, StructuredDataImpl *args_impl); + +#if _MSC_VER +#pragma warning (pop) +#endif + +#pragma clang diagnostic pop extern "C" bool LLDBSwigPythonWatchpointCallbackFunction( const char *python_function_name, const char *session_dictionary_name, @@ -211,6 +228,22 @@ public: InitializePythonHome(); +#ifdef LLDB_USE_LIBEDIT_READLINE_COMPAT_MODULE + // Python's readline is incompatible with libedit being linked into lldb. + // Provide a patched version local to the embedded interpreter. + bool ReadlinePatched = false; + for (auto *p = PyImport_Inittab; p->name != NULL; p++) { + if (strcmp(p->name, "readline") == 0) { + p->initfunc = initlldb_readline; + break; + } + } + if (!ReadlinePatched) { + PyImport_AppendInittab("readline", initlldb_readline); + ReadlinePatched = true; + } +#endif + // Register _lldb as a built-in module. PyImport_AppendInittab("_lldb", LLDBSwigPyInit); @@ -552,8 +585,10 @@ void ScriptInterpreterPythonImpl::IOHandlerInputComplete(IOHandler &io_handler, break; data_up->user_source.SplitIntoLines(data); + StructuredData::ObjectSP empty_args_sp; if (GenerateBreakpointCommandCallbackData(data_up->user_source, - data_up->script_source) + data_up->script_source, + false) .Success()) { auto baton_sp = std::make_shared<BreakpointOptions::CommandBaton>( std::move(data_up)); @@ -779,6 +814,32 @@ PythonDictionary &ScriptInterpreterPythonImpl::GetSysModuleDictionary() { return m_sys_module_dict; } +llvm::Expected<unsigned> +ScriptInterpreterPythonImpl::GetMaxPositionalArgumentsForCallable( + const llvm::StringRef &callable_name) { + if (callable_name.empty()) { + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "called with empty callable name."); + } + Locker py_lock(this, Locker::AcquireLock | + Locker::InitSession | + Locker::NoSTDIN); + auto dict = PythonModule::MainModule() + .ResolveName<PythonDictionary>(m_dictionary_name); + auto pfunc = PythonObject::ResolveNameWithDictionary<PythonCallable>( + callable_name, dict); + if (!pfunc.IsAllocated()) { + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "can't find callable: %s", callable_name.str().c_str()); + } + llvm::Expected<PythonCallable::ArgInfo> arg_info = pfunc.GetArgInfo(); + if (!arg_info) + return arg_info.takeError(); + return arg_info.get().max_positional_args; +} + static std::string GenerateUniqueName(const char *base_name_wanted, uint32_t &functions_counter, const void *name_token = nullptr) { @@ -1139,6 +1200,7 @@ bool ScriptInterpreterPythonImpl::ExecuteOneLineWithReturn( return true; } } + llvm_unreachable("Fully covered switch!"); } Status ScriptInterpreterPythonImpl::ExecuteMultipleLines( @@ -1186,24 +1248,56 @@ void ScriptInterpreterPythonImpl::CollectDataForBreakpointCommandCallback( CommandReturnObject &result) { m_active_io_handler = eIOHandlerBreakpoint; m_debugger.GetCommandInterpreter().GetPythonCommandsFromIOHandler( - " ", *this, true, &bp_options_vec); + " ", *this, &bp_options_vec); } void ScriptInterpreterPythonImpl::CollectDataForWatchpointCommandCallback( WatchpointOptions *wp_options, CommandReturnObject &result) { m_active_io_handler = eIOHandlerWatchpoint; m_debugger.GetCommandInterpreter().GetPythonCommandsFromIOHandler( - " ", *this, true, wp_options); + " ", *this, wp_options); } -void ScriptInterpreterPythonImpl::SetBreakpointCommandCallbackFunction( - BreakpointOptions *bp_options, const char *function_name) { +Status ScriptInterpreterPythonImpl::SetBreakpointCommandCallbackFunction( + BreakpointOptions *bp_options, const char *function_name, + StructuredData::ObjectSP extra_args_sp) { + Status error; // For now just cons up a oneliner that calls the provided function. std::string oneliner("return "); oneliner += function_name; - oneliner += "(frame, bp_loc, internal_dict)"; - m_debugger.GetScriptInterpreter()->SetBreakpointCommandCallback( - bp_options, oneliner.c_str()); + + llvm::Expected<unsigned> maybe_args = + GetMaxPositionalArgumentsForCallable(function_name); + if (!maybe_args) { + error.SetErrorStringWithFormat( + "could not get num args: %s", + llvm::toString(maybe_args.takeError()).c_str()); + return error; + } + size_t max_args = *maybe_args; + + bool uses_extra_args = false; + if (max_args >= 4) { + uses_extra_args = true; + oneliner += "(frame, bp_loc, extra_args, internal_dict)"; + } else if (max_args >= 3) { + if (extra_args_sp) { + error.SetErrorString("cannot pass extra_args to a three argument callback" + ); + return error; + } + uses_extra_args = false; + oneliner += "(frame, bp_loc, internal_dict)"; + } else { + error.SetErrorStringWithFormat("expected 3 or 4 argument " + "function, %s can only take %zu", + function_name, max_args); + return error; + } + + SetBreakpointCommandCallback(bp_options, oneliner.c_str(), extra_args_sp, + uses_extra_args); + return error; } Status ScriptInterpreterPythonImpl::SetBreakpointCommandCallback( @@ -1211,7 +1305,8 @@ Status ScriptInterpreterPythonImpl::SetBreakpointCommandCallback( std::unique_ptr<BreakpointOptions::CommandData> &cmd_data_up) { Status error; error = GenerateBreakpointCommandCallbackData(cmd_data_up->user_source, - cmd_data_up->script_source); + cmd_data_up->script_source, + false); if (error.Fail()) { return error; } @@ -1222,11 +1317,17 @@ Status ScriptInterpreterPythonImpl::SetBreakpointCommandCallback( return error; } -// Set a Python one-liner as the callback for the breakpoint. Status ScriptInterpreterPythonImpl::SetBreakpointCommandCallback( BreakpointOptions *bp_options, const char *command_body_text) { - auto data_up = std::make_unique<CommandDataPython>(); + return SetBreakpointCommandCallback(bp_options, command_body_text, {},false); +} +// Set a Python one-liner as the callback for the breakpoint. +Status ScriptInterpreterPythonImpl::SetBreakpointCommandCallback( + BreakpointOptions *bp_options, const char *command_body_text, + StructuredData::ObjectSP extra_args_sp, + bool uses_extra_args) { + auto data_up = std::make_unique<CommandDataPython>(extra_args_sp); // Split the command_body_text into lines, and pass that to // GenerateBreakpointCommandCallbackData. That will wrap the body in an // auto-generated function, and return the function name in script_source. @@ -1234,7 +1335,8 @@ Status ScriptInterpreterPythonImpl::SetBreakpointCommandCallback( data_up->user_source.SplitIntoLines(command_body_text); Status error = GenerateBreakpointCommandCallbackData(data_up->user_source, - data_up->script_source); + data_up->script_source, + uses_extra_args); if (error.Success()) { auto baton_sp = std::make_shared<BreakpointOptions::CommandBaton>(std::move(data_up)); @@ -1771,8 +1873,7 @@ StructuredData::DictionarySP ScriptInterpreterPythonImpl::OSPlugin_CreateThread( StructuredData::ObjectSP ScriptInterpreterPythonImpl::CreateScriptedThreadPlan( const char *class_name, StructuredDataImpl *args_data, - std::string &error_str, - lldb::ThreadPlanSP thread_plan_sp) { + std::string &error_str, lldb::ThreadPlanSP thread_plan_sp) { if (class_name == nullptr || class_name[0] == '\0') return StructuredData::ObjectSP(); @@ -1956,8 +2057,7 @@ ScriptInterpreterPythonImpl::LoadPluginModule(const FileSpec &file_spec, StructuredData::ObjectSP module_sp; - if (LoadScriptingModule(file_spec.GetPath().c_str(), true, true, error, - &module_sp)) + if (LoadScriptingModule(file_spec.GetPath().c_str(), true, error, &module_sp)) return module_sp; return StructuredData::ObjectSP(); @@ -2063,7 +2163,8 @@ bool ScriptInterpreterPythonImpl::GenerateTypeSynthClass( } Status ScriptInterpreterPythonImpl::GenerateBreakpointCommandCallbackData( - StringList &user_input, std::string &output) { + StringList &user_input, std::string &output, + bool has_extra_args) { static uint32_t num_created_functions = 0; user_input.RemoveBlankLines(); StreamString sstr; @@ -2075,8 +2176,12 @@ Status ScriptInterpreterPythonImpl::GenerateBreakpointCommandCallbackData( std::string auto_generated_function_name(GenerateUniqueName( "lldb_autogen_python_bp_callback_func_", num_created_functions)); - sstr.Printf("def %s (frame, bp_loc, internal_dict):", - auto_generated_function_name.c_str()); + if (has_extra_args) + sstr.Printf("def %s (frame, bp_loc, extra_args, internal_dict):", + auto_generated_function_name.c_str()); + else + sstr.Printf("def %s (frame, bp_loc, internal_dict):", + auto_generated_function_name.c_str()); error = GenerateFunction(sstr.GetData(), user_input); if (!error.Success()) @@ -2193,10 +2298,26 @@ bool ScriptInterpreterPythonImpl::BreakpointCallbackFunction( Locker py_lock(python_interpreter, Locker::AcquireLock | Locker::InitSession | Locker::NoSTDIN); - ret_val = LLDBSwigPythonBreakpointCallbackFunction( - python_function_name, - python_interpreter->m_dictionary_name.c_str(), stop_frame_sp, - bp_loc_sp); + Expected<bool> maybe_ret_val = + LLDBSwigPythonBreakpointCallbackFunction( + python_function_name, + python_interpreter->m_dictionary_name.c_str(), stop_frame_sp, + bp_loc_sp, bp_option_data->m_extra_args_up.get()); + + if (!maybe_ret_val) { + + llvm::handleAllErrors( + maybe_ret_val.takeError(), + [&](PythonException &E) { + debugger.GetErrorStream() << E.ReadBacktrace(); + }, + [&](const llvm::ErrorInfoBase &E) { + debugger.GetErrorStream() << E.message(); + }); + + } else { + ret_val = maybe_ret_val.get(); + } } return ret_val; } @@ -2615,8 +2736,8 @@ uint64_t replace_all(std::string &str, const std::string &oldStr, } bool ScriptInterpreterPythonImpl::LoadScriptingModule( - const char *pathname, bool can_reload, bool init_session, - lldb_private::Status &error, StructuredData::ObjectSP *module_sp) { + const char *pathname, bool init_session, lldb_private::Status &error, + StructuredData::ObjectSP *module_sp) { if (!pathname || !pathname[0]) { error.SetErrorString("invalid pathname"); return false; @@ -2716,11 +2837,6 @@ bool ScriptInterpreterPythonImpl::LoadScriptingModule( bool was_imported = (was_imported_globally || was_imported_locally); - if (was_imported && !can_reload) { - error.SetErrorString("module already imported"); - return false; - } - // now actually do the import command_stream.Clear(); @@ -3171,4 +3287,4 @@ void ScriptInterpreterPythonImpl::AddToSysPath(AddLocation location, // // void ScriptInterpreterPythonImpl::Terminate() { Py_Finalize (); } -#endif // LLDB_DISABLE_PYTHON +#endif diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.h b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.h index 33ae308041b2e..e59fedbd0971f 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.h @@ -9,14 +9,13 @@ #ifndef LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_SCRIPTINTERPRETERPYTHON_H #define LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_SCRIPTINTERPRETERPYTHON_H -#ifdef LLDB_DISABLE_PYTHON +#include "lldb/Host/Config.h" -// Python is disabled in this build - -#else +#if LLDB_ENABLE_PYTHON #include "lldb/Breakpoint/BreakpointOptions.h" #include "lldb/Core/IOHandler.h" +#include "lldb/Core/StructuredDataImpl.h" #include "lldb/Interpreter/ScriptInterpreter.h" #include "lldb/lldb-private.h" @@ -34,6 +33,13 @@ public: CommandDataPython() : BreakpointOptions::CommandData() { interpreter = lldb::eScriptLanguagePython; } + CommandDataPython(StructuredData::ObjectSP extra_args_sp) : + BreakpointOptions::CommandData(), + m_extra_args_up(new StructuredDataImpl()) { + interpreter = lldb::eScriptLanguagePython; + m_extra_args_up->SetObjectSP(extra_args_sp); + } + lldb::StructuredDataImplUP m_extra_args_up; }; ScriptInterpreterPython(Debugger &debugger) @@ -52,5 +58,5 @@ protected: }; } // namespace lldb_private -#endif // LLDB_DISABLE_PYTHON +#endif // LLDB_ENABLE_PYTHON #endif // LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_SCRIPTINTERPRETERPYTHON_H diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h index 929567e579d8b..1fa198b07e54e 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h @@ -6,11 +6,9 @@ // //===----------------------------------------------------------------------===// -#ifdef LLDB_DISABLE_PYTHON +#include "lldb/Host/Config.h" -// Python is disabled in this build - -#else +#if LLDB_ENABLE_PYTHON #include "lldb-python.h" @@ -179,8 +177,10 @@ public: Status GenerateFunction(const char *signature, const StringList &input) override; - Status GenerateBreakpointCommandCallbackData(StringList &input, - std::string &output) override; + Status GenerateBreakpointCommandCallbackData( + StringList &input, + std::string &output, + bool has_extra_args) override; bool GenerateWatchpointCommandCallbackData(StringList &input, std::string &output) override; @@ -224,7 +224,7 @@ public: std::string &output, Status &error) override; bool - LoadScriptingModule(const char *filename, bool can_reload, bool init_session, + LoadScriptingModule(const char *filename, bool init_session, lldb_private::Status &error, StructuredData::ObjectSP *module_sp = nullptr) override; @@ -244,14 +244,21 @@ public: Status SetBreakpointCommandCallback(BreakpointOptions *bp_options, const char *callback_body) override; - void SetBreakpointCommandCallbackFunction(BreakpointOptions *bp_options, - const char *function_name) override; + Status SetBreakpointCommandCallbackFunction( + BreakpointOptions *bp_options, + const char *function_name, + StructuredData::ObjectSP extra_args_sp) override; /// This one is for deserialization: Status SetBreakpointCommandCallback( BreakpointOptions *bp_options, std::unique_ptr<BreakpointOptions::CommandData> &data_up) override; + Status SetBreakpointCommandCallback(BreakpointOptions *bp_options, + const char *command_body_text, + StructuredData::ObjectSP extra_args_sp, + bool uses_extra_args); + /// Set a one-liner as the callback for the watchpoint. void SetWatchpointCommandCallback(WatchpointOptions *wp_options, const char *oneliner) override; @@ -369,6 +376,9 @@ public: python::PythonDictionary &GetSysModuleDictionary(); + llvm::Expected<unsigned> GetMaxPositionalArgumentsForCallable( + const llvm::StringRef &callable_name) override; + bool GetEmbeddedInterpreterModuleObjects(); bool SetStdHandle(lldb::FileSP file, const char *py_name, diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/lldb-python.h b/lldb/source/Plugins/ScriptInterpreter/Python/lldb-python.h index 884514da99241..48f27b09b95c5 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/lldb-python.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/lldb-python.h @@ -9,12 +9,12 @@ #ifndef LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_LLDB_PYTHON_H #define LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_LLDB_PYTHON_H +#include "lldb/Host/Config.h" + // Python.h needs to be included before any system headers in order to avoid // redefinition of macros -#ifdef LLDB_DISABLE_PYTHON -// Python is disabled in this build -#else +#if LLDB_ENABLE_PYTHON #include "llvm/Support/Compiler.h" #if defined(_WIN32) // If anyone #includes Host/PosixApi.h later, it will try to typedef pid_t. We @@ -40,6 +40,6 @@ // Include python for non windows machines #include <Python.h> -#endif // LLDB_DISABLE_PYTHON +#endif #endif // LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_LLDB_PYTHON_H |