| //===-- ScriptInterpreterPythonImpl.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_SOURCE_PLUGINS_SCRIPTINTERPRETER_PYTHON_SCRIPTINTERPRETERPYTHONIMPL_H | |
| #define LLDB_SOURCE_PLUGINS_SCRIPTINTERPRETER_PYTHON_SCRIPTINTERPRETERPYTHONIMPL_H | |
| #include "lldb-python.h" | |
| #include "PythonDataObjects.h" | |
| #include "ScriptInterpreterPython.h" | |
| #include "lldb/Host/Terminal.h" | |
| #include "lldb/Utility/StreamString.h" | |
| #include "llvm/ADT/STLExtras.h" | |
| #include "llvm/ADT/StringRef.h" | |
| namespace lldb_private { | |
| class IOHandlerPythonInterpreter; | |
| class ScriptInterpreterPythonImpl : public ScriptInterpreterPython { | |
| public: | |
| friend class IOHandlerPythonInterpreter; | |
| ScriptInterpreterPythonImpl(Debugger &debugger); | |
| ~ScriptInterpreterPythonImpl() override; | |
| bool Interrupt() override; | |
| bool ExecuteOneLine( | |
| llvm::StringRef command, CommandReturnObject *result, | |
| const ExecuteScriptOptions &options = ExecuteScriptOptions()) override; | |
| void ExecuteInterpreterLoop() override; | |
| bool ExecuteOneLineWithReturn( | |
| llvm::StringRef in_string, | |
| ScriptInterpreter::ScriptReturnType return_type, void *ret_value, | |
| const ExecuteScriptOptions &options = ExecuteScriptOptions()) override; | |
| lldb_private::Status ExecuteMultipleLines( | |
| const char *in_string, | |
| const ExecuteScriptOptions &options = ExecuteScriptOptions()) override; | |
| Status | |
| ExportFunctionDefinitionToInterpreter(StringList &function_def) override; | |
| bool GenerateTypeScriptFunction(StringList &input, std::string &output, | |
| const void *name_token = nullptr) override; | |
| bool GenerateTypeSynthClass(StringList &input, std::string &output, | |
| const void *name_token = nullptr) override; | |
| bool GenerateTypeSynthClass(const char *oneliner, std::string &output, | |
| const void *name_token = nullptr) override; | |
| // use this if the function code is just a one-liner script | |
| bool GenerateTypeScriptFunction(const char *oneliner, std::string &output, | |
| const void *name_token = nullptr) override; | |
| bool GenerateScriptAliasFunction(StringList &input, | |
| std::string &output) override; | |
| StructuredData::ObjectSP | |
| CreateStructuredDataFromScriptObject(ScriptObject obj) override; | |
| lldb::ScriptedProcessInterfaceUP CreateScriptedProcessInterface() override; | |
| lldb::ScriptedHookInterfaceSP CreateScriptedHookInterface() override; | |
| lldb::ScriptedBreakpointInterfaceSP | |
| CreateScriptedBreakpointInterface() override; | |
| lldb::ScriptedStackFrameRecognizerInterfaceSP | |
| CreateScriptedStackFrameRecognizerInterface() override; | |
| lldb::ScriptedCommandInterfaceSP CreateScriptedCommandInterface() override; | |
| lldb::ScriptedStringSummaryInterfaceSP | |
| CreateScriptedStringSummaryInterface() override; | |
| lldb::ScriptedSyntheticChildrenInterfaceSP | |
| CreateScriptedSyntheticChildrenInterface() override; | |
| lldb::ScriptedThreadInterfaceSP CreateScriptedThreadInterface() override; | |
| lldb::ScriptedFrameInterfaceSP CreateScriptedFrameInterface() override; | |
| lldb::ScriptedFrameProviderInterfaceSP | |
| CreateScriptedFrameProviderInterface() override; | |
| lldb::ScriptedThreadPlanInterfaceSP | |
| CreateScriptedThreadPlanInterface() override; | |
| lldb::OperatingSystemInterfaceSP CreateOperatingSystemInterface() override; | |
| StructuredData::ObjectSP | |
| LoadPluginModule(const FileSpec &file_spec, | |
| lldb_private::Status &error) override; | |
| StructuredData::DictionarySP | |
| GetDynamicSettings(StructuredData::ObjectSP plugin_module_sp, Target *target, | |
| const char *setting_name, | |
| lldb_private::Status &error) override; | |
| bool | |
| RunScriptBasedCommand(const char *impl_function, llvm::StringRef args, | |
| ScriptedCommandSynchronicity synchronicity, | |
| lldb_private::CommandReturnObject &cmd_retobj, | |
| Status &error, | |
| const lldb_private::ExecutionContext &exe_ctx) override; | |
| Status GenerateFunction(const char *signature, const StringList &input, | |
| bool is_callback) override; | |
| Status GenerateBreakpointCommandCallbackData(StringList &input, | |
| std::string &output, | |
| bool has_extra_args, | |
| bool is_callback) override; | |
| bool GenerateWatchpointCommandCallbackData(StringList &input, | |
| std::string &output, | |
| bool is_callback) override; | |
| bool GetScriptedSummary(const char *function_name, lldb::ValueObjectSP valobj, | |
| StructuredData::ObjectSP &callee_wrapper_sp, | |
| const TypeSummaryOptions &options, | |
| std::string &retval) override; | |
| bool FormatterCallbackFunction(const char *function_name, | |
| lldb::TypeImplSP type_impl_sp) override; | |
| bool GetDocumentationForItem(const char *item, std::string &dest) override; | |
| bool CheckObjectExists(const char *name) override { | |
| if (!name || !name[0]) | |
| return false; | |
| std::string temp; | |
| return GetDocumentationForItem(name, temp); | |
| } | |
| bool RunScriptFormatKeyword(const char *impl_function, Process *process, | |
| std::string &output, Status &error) override; | |
| bool RunScriptFormatKeyword(const char *impl_function, Thread *thread, | |
| std::string &output, Status &error) override; | |
| bool RunScriptFormatKeyword(const char *impl_function, Target *target, | |
| std::string &output, Status &error) override; | |
| bool RunScriptFormatKeyword(const char *impl_function, StackFrame *frame, | |
| std::string &output, Status &error) override; | |
| bool RunScriptFormatKeyword(const char *impl_function, ValueObject *value, | |
| std::string &output, Status &error) override; | |
| bool LoadScriptingModule(const char *filename, | |
| const LoadScriptOptions &options, | |
| lldb_private::Status &error, | |
| StructuredData::ObjectSP *module_sp = nullptr, | |
| FileSpec extra_search_dir = {}, | |
| lldb::TargetSP loaded_into_target_sp = {}) override; | |
| bool IsReservedWord(const char *word) override; | |
| std::unique_ptr<ScriptInterpreterLocker> AcquireInterpreterLock() override; | |
| void CollectDataForBreakpointCommandCallback( | |
| std::vector<std::reference_wrapper<BreakpointOptions>> &bp_options_vec, | |
| CommandReturnObject &result) override; | |
| void | |
| CollectDataForWatchpointCommandCallback(WatchpointOptions *wp_options, | |
| CommandReturnObject &result) override; | |
| /// Set the callback body text into the callback for the breakpoint. | |
| Status SetBreakpointCommandCallback(BreakpointOptions &bp_options, | |
| const char *callback_body, | |
| bool is_callback) 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, bool is_callback); | |
| /// Set a one-liner as the callback for the watchpoint. | |
| void SetWatchpointCommandCallback(WatchpointOptions *wp_options, | |
| const char *user_input, | |
| bool is_callback) override; | |
| const char *GetDictionaryName() { return m_dictionary_name.c_str(); } | |
| PyThreadState *GetThreadState() { return m_command_thread_state; } | |
| void SetThreadState(PyThreadState *s) { | |
| if (s) | |
| m_command_thread_state = s; | |
| } | |
| // IOHandlerDelegate | |
| void IOHandlerActivated(IOHandler &io_handler, bool interactive) override; | |
| void IOHandlerInputComplete(IOHandler &io_handler, | |
| std::string &data) override; | |
| static lldb::ScriptInterpreterSP CreateInstance(Debugger &debugger); | |
| // PluginInterface protocol | |
| llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); } | |
| class Locker : public ScriptInterpreterLocker { | |
| public: | |
| enum OnEntry { | |
| AcquireLock = 0x0001, | |
| InitSession = 0x0002, | |
| InitGlobals = 0x0004, | |
| NoSTDIN = 0x0008, | |
| // Keep sys.stdout/stderr on the real terminal instead of routing them | |
| // through the output-lock pipe (see EnterSession). Set by the interactive | |
| // interpreter, whose input() needs both the real stdin and stdout for | |
| // readline-based line editing and echo. | |
| NoOutputRedirect = 0x0010 | |
| }; | |
| enum OnLeave { | |
| FreeLock = 0x0001, | |
| FreeAcquiredLock = 0x0002, // do not free the lock if we already held it | |
| // when calling constructor | |
| TearDownSession = 0x0004 | |
| }; | |
| Locker(ScriptInterpreterPythonImpl *py_interpreter, | |
| uint16_t on_entry = AcquireLock | InitSession, | |
| uint16_t on_leave = FreeLock | TearDownSession, | |
| lldb::FileSP in = nullptr, lldb::FileSP out = nullptr, | |
| lldb::FileSP err = nullptr); | |
| ~Locker() override; | |
| private: | |
| bool DoAcquireLock(); | |
| bool DoInitSession(uint16_t on_entry_flags, lldb::FileSP in, | |
| lldb::FileSP out, lldb::FileSP err); | |
| bool DoFreeLock(); | |
| bool DoTearDownSession(); | |
| bool m_teardown_session; | |
| ScriptInterpreterPythonImpl *m_python_interpreter; | |
| PyGILState_STATE m_GILState; | |
| }; | |
| static bool BreakpointCallbackFunction(void *baton, | |
| StoppointCallbackContext *context, | |
| lldb::user_id_t break_id, | |
| lldb::user_id_t break_loc_id); | |
| static bool WatchpointCallbackFunction(void *baton, | |
| StoppointCallbackContext *context, | |
| lldb::user_id_t watch_id); | |
| static llvm::Error Initialize(); | |
| class SynchronicityHandler { | |
| private: | |
| lldb::DebuggerSP m_debugger_sp; | |
| ScriptedCommandSynchronicity m_synch_wanted; | |
| bool m_old_asynch; | |
| public: | |
| SynchronicityHandler(lldb::DebuggerSP, ScriptedCommandSynchronicity); | |
| ~SynchronicityHandler(); | |
| }; | |
| enum class AddLocation { Beginning, End }; | |
| static void AddToSysPath(AddLocation location, std::string path); | |
| bool EnterSession(uint16_t on_entry_flags, lldb::FileSP in, lldb::FileSP out, | |
| lldb::FileSP err); | |
| void LeaveSession(); | |
| uint32_t IsExecutingPython() { | |
| std::lock_guard<std::mutex> guard(m_mutex); | |
| return m_lock_count > 0; | |
| } | |
| uint32_t IncrementLockCount() { | |
| std::lock_guard<std::mutex> guard(m_mutex); | |
| return ++m_lock_count; | |
| } | |
| uint32_t DecrementLockCount() { | |
| std::lock_guard<std::mutex> guard(m_mutex); | |
| if (m_lock_count > 0) | |
| --m_lock_count; | |
| return m_lock_count; | |
| } | |
| enum ActiveIOHandler { | |
| eIOHandlerNone, | |
| eIOHandlerBreakpoint, | |
| eIOHandlerWatchpoint | |
| }; | |
| python::PythonModule &GetMainModule(); | |
| python::PythonDictionary &GetSessionDictionary(); | |
| python::PythonDictionary &GetSysModuleDictionary(); | |
| llvm::Expected<unsigned> GetMaxPositionalArgumentsForCallable( | |
| const llvm::StringRef &callable_name) override; | |
| bool GetEmbeddedInterpreterModuleObjects(); | |
| /// Point sys.\p py_name at \p file. When \p serialize_terminal_output is true | |
| /// and \p file is the debugger's own terminal, the output is routed through a | |
| /// lock-synchronized pipe instead (see RedirectTerminalHandleThroughLock) so | |
| /// it cannot race the statusline redraw. | |
| bool SetStdHandle(lldb::FileSP file, const char *py_name, | |
| python::PythonObject &save_file, const char *mode, | |
| bool serialize_terminal_output); | |
| /// Pipe-backed sys.stdout/stderr whose writes are serialized against the | |
| /// statusline redraw. Defined and documented in the implementation file. | |
| class SessionIORedirect; | |
| /// If \p file is the debugger's own terminal, point sys.\p py_name at a | |
| /// pipe-backed file whose writes are serialized through the output lock (see | |
| /// SessionIORedirect) and return true. Return false to let SetStdHandle wrap | |
| /// \p file normally. | |
| bool RedirectTerminalHandleThroughLock(const char *py_name, | |
| python::PythonObject &save_file, | |
| const char *mode, File &file); | |
| python::PythonObject m_saved_stdin; | |
| python::PythonObject m_saved_stdout; | |
| python::PythonObject m_saved_stderr; | |
| std::unique_ptr<SessionIORedirect> m_stdout_redirect; | |
| std::unique_ptr<SessionIORedirect> m_stderr_redirect; | |
| python::PythonModule m_main_module; | |
| python::PythonDictionary m_session_dict; | |
| python::PythonDictionary m_sys_module_dict; | |
| python::PythonObject m_run_one_line_function; | |
| python::PythonObject m_run_one_line_str_global; | |
| std::string m_dictionary_name; | |
| ActiveIOHandler m_active_io_handler; | |
| bool m_session_is_active; | |
| bool m_pty_secondary_is_open; | |
| bool m_valid_session; | |
| uint32_t m_lock_count; | |
| std::mutex m_mutex; | |
| PyThreadState *m_command_thread_state; | |
| }; | |
| class IOHandlerPythonInterpreter : public IOHandler { | |
| public: | |
| IOHandlerPythonInterpreter(Debugger &debugger, | |
| ScriptInterpreterPythonImpl *python) | |
| : IOHandler(debugger, IOHandler::Type::PythonInterpreter), | |
| m_python(python) {} | |
| ~IOHandlerPythonInterpreter() override = default; | |
| llvm::StringRef GetControlSequence(char ch) override { | |
| static constexpr llvm::StringLiteral control_sequence("quit()\n"); | |
| if (ch == 'd') | |
| return control_sequence; | |
| return {}; | |
| } | |
| void Run() override { | |
| if (m_python) { | |
| int stdin_fd = GetInputFD(); | |
| if (stdin_fd >= 0) { | |
| Terminal terminal(stdin_fd); | |
| TerminalState terminal_state(terminal); | |
| if (terminal.IsATerminal()) { | |
| // FIXME: error handling? | |
| llvm::consumeError(terminal.SetCanonical(false)); | |
| llvm::consumeError(terminal.SetEcho(true)); | |
| } | |
| ScriptInterpreterPythonImpl::Locker locker( | |
| m_python, | |
| ScriptInterpreterPythonImpl::Locker::AcquireLock | | |
| ScriptInterpreterPythonImpl::Locker::InitSession | | |
| ScriptInterpreterPythonImpl::Locker::InitGlobals | | |
| ScriptInterpreterPythonImpl::Locker::NoOutputRedirect, | |
| ScriptInterpreterPythonImpl::Locker::FreeAcquiredLock | | |
| ScriptInterpreterPythonImpl::Locker::TearDownSession); | |
| // The following call drops into the embedded interpreter loop and | |
| // stays there until the user chooses to exit from the Python | |
| // interpreter. This embedded interpreter will, as any Python code that | |
| // performs I/O, unlock the GIL before a system call that can hang, and | |
| // lock it when the syscall has returned. | |
| // We need to surround the call to the embedded interpreter with calls | |
| // to PyGILState_Ensure and PyGILState_Release (using the Locker | |
| // above). This is because Python has a global lock which must be held | |
| // whenever we want to touch any Python objects. Otherwise, if the user | |
| // calls Python code, the interpreter state will be off, and things | |
| // could hang (it's happened before). | |
| StreamString run_string; | |
| run_string.Printf("run_python_interpreter (%s)", | |
| m_python->GetDictionaryName()); | |
| python::RunSimpleString(run_string.GetData()); | |
| } | |
| } | |
| SetIsDone(true); | |
| } | |
| void Cancel() override {} | |
| bool Interrupt() override { return m_python->Interrupt(); } | |
| void GotEOF() override {} | |
| protected: | |
| ScriptInterpreterPythonImpl *m_python; | |
| }; | |
| } // namespace lldb_private | |
| #endif // LLDB_SOURCE_PLUGINS_SCRIPTINTERPRETER_PYTHON_SCRIPTINTERPRETERPYTHONIMPL_H |