diff --git a/lldb/bindings/interface/SBDebugger.i b/lldb/bindings/interface/SBDebugger.i --- a/lldb/bindings/interface/SBDebugger.i +++ b/lldb/bindings/interface/SBDebugger.i @@ -479,6 +479,8 @@ lldb::SBTypeSynthetic GetSyntheticForType (lldb::SBTypeNameSpecifier); + const char *GetScriptInterpreterInfo(ScriptLanguage); + STRING_EXTENSION(SBDebugger) %feature("docstring", diff --git a/lldb/bindings/python/CMakeLists.txt b/lldb/bindings/python/CMakeLists.txt --- a/lldb/bindings/python/CMakeLists.txt +++ b/lldb/bindings/python/CMakeLists.txt @@ -23,6 +23,18 @@ ${CMAKE_CURRENT_BINARY_DIR}/lldb.py ) +if (NOT WIN32) +add_custom_command( + OUTPUT ${LLVM_RUNTIME_OUTPUT_INTDIR}/lldb-python + VERBATIM + COMMAND ${CMAKE_COMMAND} -E copy lldb-python ${LLVM_RUNTIME_OUTPUT_INTDIR}/lldb-python + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +) +add_custom_target(lldb-python-wrapper ALL DEPENDS + ${LLVM_RUNTIME_OUTPUT_INTDIR}/lldb-python +) +endif() + function(create_python_package swig_target working_dir pkg_dir) cmake_parse_arguments(ARG "NOINIT" "" "FILES" ${ARGN}) if(ARG_FILES) @@ -149,6 +161,11 @@ create_relative_symlink(${swig_target} ${LIBLLDB_SYMLINK_DEST} ${lldb_python_target_dir} ${LIBLLDB_SYMLINK_OUTPUT_FILE}) + + if (NOT WIN32) + add_dependencies(${swig_target} lldb-python-wrapper) + endif() + if(NOT LLDB_BUILD_FRAMEWORK) set(LLDB_ARGDUMPER_FILENAME "lldb-argdumper${CMAKE_EXECUTABLE_SUFFIX}") create_relative_symlink(${swig_target} "${LLVM_RUNTIME_OUTPUT_INTDIR}/${LLDB_ARGDUMPER_FILENAME}" diff --git a/lldb/bindings/python/lldb-python b/lldb/bindings/python/lldb-python new file mode 100755 --- /dev/null +++ b/lldb/bindings/python/lldb-python @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 + +import subprocess +import os +import sys +import json + +lldb = os.path.join(os.path.dirname(__file__), 'lldb') + +info_json = subprocess.run([lldb, "-l", "python", "-print-script-interpreter-info"], + check=True, stdout=subprocess.PIPE, encoding='utf8').stdout +info = json.loads(info_json) + +os.environ["PYTHONPATH"] = ( + info["lldb-pythonpath"] + os.path.pathsep + os.environ.get("PYTHONPATH", "")) + +os.execl(info["executable"], info["executable"], *sys.argv[1:]) diff --git a/lldb/docs/man/lldb.rst b/lldb/docs/man/lldb.rst --- a/lldb/docs/man/lldb.rst +++ b/lldb/docs/man/lldb.rst @@ -234,6 +234,10 @@ Alias for --script-language +.. option:: --print-script-interpreter-info + + Prints out a json dictionary with information about the scripting language interpreter. + .. option:: --python-path Prints out the path to the lldb.py file for this version of lldb. diff --git a/lldb/include/lldb/API/SBDebugger.h b/lldb/include/lldb/API/SBDebugger.h --- a/lldb/include/lldb/API/SBDebugger.h +++ b/lldb/include/lldb/API/SBDebugger.h @@ -247,6 +247,8 @@ lldb::ScriptLanguage GetScriptingLanguage(const char *script_language_name); + const char *GetScriptInterpreterInfo(ScriptLanguage); + static const char *GetVersionString(); static const char *StateAsCString(lldb::StateType state); diff --git a/lldb/include/lldb/API/SBHostOS.h b/lldb/include/lldb/API/SBHostOS.h --- a/lldb/include/lldb/API/SBHostOS.h +++ b/lldb/include/lldb/API/SBHostOS.h @@ -20,6 +20,8 @@ static lldb::SBFileSpec GetLLDBPythonPath(); + const char *GetScriptInterpreterInfo(); + static lldb::SBFileSpec GetLLDBPath(lldb::PathType path_type); static lldb::SBFileSpec GetUserHomeDirectory(); diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h --- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h +++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h @@ -148,6 +148,8 @@ lldb::ScriptedProcessInterfaceUP scripted_process_interface_up = std::make_unique()); + virtual const char *GetInterpreterInfo(); + ~ScriptInterpreter() override = default; virtual bool Interrupt() { return false; } diff --git a/lldb/source/API/SBDebugger.cpp b/lldb/source/API/SBDebugger.cpp --- a/lldb/source/API/SBDebugger.cpp +++ b/lldb/source/API/SBDebugger.cpp @@ -680,6 +680,18 @@ llvm::StringRef(script_language_name), eScriptLanguageDefault, nullptr); } +const char * +SBDebugger::GetScriptInterpreterInfo(lldb::ScriptLanguage language) { + LLDB_RECORD_METHOD(const char *, SBDebugger, GetScriptInterpreterInfo, (lldb::ScriptLanguage), language); + if (m_opaque_sp) { + lldb_private::ScriptInterpreter *interp = m_opaque_sp->GetScriptInterpreter(language); + if (interp) { + return interp->GetInterpreterInfo(); + } + } + return nullptr; +} + const char *SBDebugger::GetVersionString() { LLDB_RECORD_STATIC_METHOD_NO_ARGS(const char *, SBDebugger, GetVersionString); @@ -1783,6 +1795,7 @@ (const char *)); LLDB_REGISTER_METHOD(lldb::ScriptLanguage, SBDebugger, GetScriptingLanguage, (const char *)); + LLDB_REGISTER_METHOD(const char *, SBDebugger, GetScriptInterpreterInfo, (lldb::ScriptLanguage)); LLDB_REGISTER_STATIC_METHOD(const char *, SBDebugger, GetVersionString, ()); LLDB_REGISTER_STATIC_METHOD(const char *, SBDebugger, StateAsCString, (lldb::StateType)); diff --git a/lldb/source/Interpreter/ScriptInterpreter.cpp b/lldb/source/Interpreter/ScriptInterpreter.cpp --- a/lldb/source/Interpreter/ScriptInterpreter.cpp +++ b/lldb/source/Interpreter/ScriptInterpreter.cpp @@ -46,6 +46,10 @@ "This script interpreter does not support watchpoint callbacks."); } +const char * ScriptInterpreter::GetInterpreterInfo() { + return nullptr; +} + bool ScriptInterpreter::LoadScriptingModule(const char *filename, const LoadScriptOptions &options, lldb_private::Status &error, diff --git a/lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.h b/lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.h --- a/lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.h +++ b/lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.h @@ -49,6 +49,8 @@ StructuredData::ObjectSP *module_sp = nullptr, FileSpec extra_search_dir = {}) override; + virtual const char *GetInterpreterInfo() override; + // Static Functions static void Initialize(); diff --git a/lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.cpp b/lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.cpp --- a/lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.cpp @@ -148,6 +148,10 @@ ScriptInterpreterLua::~ScriptInterpreterLua() = default; +const char * ScriptInterpreterLua::GetInterpreterInfo() { + return "{\"language\": \"lua\"}"; +} + bool ScriptInterpreterLua::ExecuteOneLine(llvm::StringRef command, CommandReturnObject *result, const ExecuteScriptOptions &options) { diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h b/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h --- a/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h @@ -71,6 +71,18 @@ class PythonInteger; class PythonException; +class GIL { +public: + GIL() { + m_state = PyGILState_Ensure(); + assert(!PyErr_Occurred()); + } + ~GIL() { PyGILState_Release(m_state); } + +protected: + PyGILState_STATE m_state; +}; + class StructuredPythonObject : public StructuredData::Generic { public: StructuredPythonObject() : StructuredData::Generic() {} diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp --- a/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp @@ -998,20 +998,6 @@ #endif } -namespace { -class GIL { -public: - GIL() { - m_state = PyGILState_Ensure(); - assert(!PyErr_Occurred()); - } - ~GIL() { PyGILState_Release(m_state); } - -protected: - PyGILState_STATE m_state; -}; -} // namespace - const char *PythonException::toCString() const { if (!m_repr_bytes) return "unknown exception"; diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.h b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.h --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.h @@ -46,6 +46,7 @@ : ScriptInterpreter(debugger, lldb::eScriptLanguagePython), IOHandlerDelegateMultiline("DONE") {} + virtual const char *GetInterpreterInfo() override; static void Initialize(); static void Terminate(); static llvm::StringRef GetPluginNameStatic() { return "script-python"; } @@ -56,6 +57,7 @@ protected: static void ComputePythonDirForApple(llvm::SmallVectorImpl &path); static void ComputePythonDir(llvm::SmallVectorImpl &path); + std::string m_interpreter_info; }; } // namespace lldb_private diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp @@ -410,6 +410,41 @@ return g_spec; } +const char * ScriptInterpreterPython::GetInterpreterInfo() { + GIL gil; + if (m_interpreter_info.length()) { + return m_interpreter_info.c_str(); + } + FileSpec python_dir_spec = GetPythonDir(); + if (!python_dir_spec) + return nullptr; + PythonString python_dir(python_dir_spec.GetPath()); + PythonDictionary info(PyInitialValue::Empty); + llvm::Error error = info.SetItem("lldb-pythonpath", python_dir); + if (error) + return nullptr; + static const char script[] = R"( +def main(info): + import json + import sys + import os + name = 'python' + str(sys.version_info.major) + info.update({ + "language": "python", + "prefix": sys.prefix, + "executable": os.path.join(sys.prefix, "bin", name), + }) + return json.dumps(info, indent=True) +)"; + PythonScript get_info(script); + auto info_json = unwrapIgnoringErrors(As(get_info(info))); + assert(info_json); + if (!info_json) + return nullptr; + m_interpreter_info = info_json.GetString().str(); + return m_interpreter_info.c_str(); +} + void ScriptInterpreterPython::SharedLibraryDirectoryHelper( FileSpec &this_file) { // When we're loaded from python, this_file will point to the file inside the diff --git a/lldb/test/API/functionalities/paths/TestPaths.py b/lldb/test/API/functionalities/paths/TestPaths.py --- a/lldb/test/API/functionalities/paths/TestPaths.py +++ b/lldb/test/API/functionalities/paths/TestPaths.py @@ -5,6 +5,8 @@ import lldb import os +import sys +import json from lldbsuite.test.decorators import * from lldbsuite.test.lldbtest import * from lldbsuite.test import lldbutil @@ -42,6 +44,14 @@ self.assertTrue(any([os.path.exists(os.path.join(shlib_dir, f)) for f in filenames]), "shlib_dir = " + shlib_dir) + @no_debug_info_test + def test_interpreter_info(self): + info = json.loads(self.dbg.GetScriptInterpreterInfo(self.dbg.GetScriptingLanguage("python"))) + prefix = info['prefix'] + self.assertEqual(os.path.realpath(sys.prefix), os.path.realpath(prefix)) + self.assertEqual( + os.path.realpath(os.path.join(info['lldb-pythonpath'], 'lldb')), + os.path.realpath(os.path.dirname(lldb.__file__))) @no_debug_info_test def test_directory_doesnt_end_with_slash(self): diff --git a/lldb/test/Shell/Driver/TestHelp.test b/lldb/test/Shell/Driver/TestHelp.test --- a/lldb/test/Shell/Driver/TestHelp.test +++ b/lldb/test/Shell/Driver/TestHelp.test @@ -62,6 +62,7 @@ CHECK: SCRIPTING CHECK: -l +CHECK: --print-script-interpreter-info CHECK: --python-path CHECK: -P CHECK: --script-language diff --git a/lldb/tools/driver/Driver.h b/lldb/tools/driver/Driver.h --- a/lldb/tools/driver/Driver.h +++ b/lldb/tools/driver/Driver.h @@ -79,6 +79,7 @@ bool m_source_quietly = false; bool m_print_version = false; bool m_print_python_path = false; + bool m_print_script_interpreter_info = false; bool m_wait_for = false; bool m_repl = false; bool m_batch = false; diff --git a/lldb/tools/driver/Driver.cpp b/lldb/tools/driver/Driver.cpp --- a/lldb/tools/driver/Driver.cpp +++ b/lldb/tools/driver/Driver.cpp @@ -201,6 +201,9 @@ if (args.hasArg(OPT_python_path)) { m_option_data.m_print_python_path = true; } + if (args.hasArg(OPT_print_script_interpreter_info)) { + m_option_data.m_print_script_interpreter_info = true; + } if (args.hasArg(OPT_batch)) { m_option_data.m_batch = true; @@ -398,6 +401,18 @@ return error; } + if (m_option_data.m_print_script_interpreter_info) { + const char *info = m_debugger.GetScriptInterpreterInfo(m_debugger.GetScriptLanguage()); + if (info) { + llvm::outs() << info << '\n'; + } else { + llvm::errs() << "no script interpreter.\n"; + error.SetErrorString("no script interpreter."); + } + exiting = true; + return error; + } + return error; } diff --git a/lldb/tools/driver/Options.td b/lldb/tools/driver/Options.td --- a/lldb/tools/driver/Options.td +++ b/lldb/tools/driver/Options.td @@ -48,6 +48,10 @@ HelpText<"Alias for --python-path">, Group; +def print_script_interpreter_info: F<"print-script-interpreter-info">, + HelpText<"Prints out a json dictionary with information about the scripting language interpreter.">, + Group; + def script_language: Separate<["--", "-"], "script-language">, MetaVarName<"">, HelpText<"Tells the debugger to use the specified scripting language for user-defined scripts.">,