diff options
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 000000000000..ecee8cc674f8 --- /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 000000000000..f2984a925dfe --- /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 000000000000..701d68d1ec08 --- /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 000000000000..4e922151385b --- /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 70d93424fdec..e5a67653e334 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 373d3212697d..b75045b239a8 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 000000000000..5f6429f5cd0e --- /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 000000000000..c75219eb1a4f --- /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 3eee52184142..06e0d5bfa63f 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 33ae308041b2..e59fedbd0971 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 929567e579d8..1fa198b07e54 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 884514da9924..48f27b09b95c 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 | 
