| //===-- ScriptInterpreter.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 "lldb/Interpreter/ScriptInterpreter.h" | |
| #include "lldb/Core/Debugger.h" | |
| #include "lldb/Host/ConnectionFileDescriptor.h" | |
| #include "lldb/Host/Pipe.h" | |
| #include "lldb/Host/PseudoTerminal.h" | |
| #include "lldb/Interpreter/CommandReturnObject.h" | |
| #include "lldb/Utility/Status.h" | |
| #include "lldb/Utility/Stream.h" | |
| #include "lldb/Utility/StringList.h" | |
| #include "lldb/Utility/UnimplementedError.h" | |
| #include "llvm/ADT/StringSwitch.h" | |
| #if defined(_WIN32) | |
| #include "lldb/Host/windows/ConnectionGenericFileWindows.h" | |
| #endif | |
| #include <cstdio> | |
| #include <cstdlib> | |
| #include <memory> | |
| #include <optional> | |
| #include <string> | |
| using namespace lldb; | |
| using namespace lldb_private; | |
| ScriptInterpreter::ScriptInterpreter(Debugger &debugger, | |
| lldb::ScriptLanguage script_lang) | |
| : m_debugger(debugger), m_script_lang(script_lang) {} | |
| void ScriptInterpreter::CollectDataForBreakpointCommandCallback( | |
| std::vector<std::reference_wrapper<BreakpointOptions>> &bp_options_vec, | |
| CommandReturnObject &result) { | |
| result.AppendError( | |
| "This script interpreter does not support breakpoint callbacks."); | |
| } | |
| void ScriptInterpreter::CollectDataForWatchpointCommandCallback( | |
| WatchpointOptions *bp_options, CommandReturnObject &result) { | |
| result.AppendError( | |
| "This script interpreter does not support watchpoint callbacks."); | |
| } | |
| StructuredData::DictionarySP ScriptInterpreter::GetInterpreterInfo() { | |
| return nullptr; | |
| } | |
| llvm::Expected<FileSpec> ScriptInterpreter::GenerateExtensionTemplate( | |
| const std::string &name, std::vector<ExtensionTemplateRequest> &extensions, | |
| bool generate_non_abstract_methods, std::string output_file) { | |
| return llvm::make_error<UnimplementedError>(); | |
| } | |
| bool ScriptInterpreter::LoadScriptingModule( | |
| const char *filename, const LoadScriptOptions &options, | |
| lldb_private::Status &error, StructuredData::ObjectSP *module_sp, | |
| FileSpec extra_search_dir, lldb::TargetSP loaded_into_target_sp) { | |
| error = Status::FromErrorString( | |
| "This script interpreter does not support importing modules."); | |
| return false; | |
| } | |
| std::string ScriptInterpreter::LanguageToString(lldb::ScriptLanguage language) { | |
| switch (language) { | |
| case eScriptLanguageNone: | |
| return "None"; | |
| case eScriptLanguagePython: | |
| return "Python"; | |
| case eScriptLanguageLua: | |
| return "Lua"; | |
| case eScriptLanguageUnknown: | |
| return "Unknown"; | |
| } | |
| llvm_unreachable("Unhandled ScriptInterpreter!"); | |
| } | |
| lldb::ScriptLanguage | |
| ScriptInterpreter::StringToLanguage(const llvm::StringRef &language) { | |
| if (language.equals_insensitive(LanguageToString(eScriptLanguageNone))) | |
| return eScriptLanguageNone; | |
| if (language.equals_insensitive(LanguageToString(eScriptLanguagePython))) | |
| return eScriptLanguagePython; | |
| if (language.equals_insensitive(LanguageToString(eScriptLanguageLua))) | |
| return eScriptLanguageLua; | |
| return eScriptLanguageUnknown; | |
| } | |
| llvm::StringLiteral | |
| ScriptInterpreter::ExtensionToString(lldb::ScriptedExtension extension) { | |
| switch (extension) { | |
| case eScriptedExtensionInvalid: | |
| return "Invalid"; | |
| case eScriptedExtensionOperatingSystem: | |
| return "OperatingSystem"; | |
| case eScriptedExtensionScriptedPlatform: | |
| return "ScriptedPlatform"; | |
| case eScriptedExtensionScriptedProcess: | |
| return "ScriptedProcess"; | |
| case eScriptedExtensionScriptedBreakpointResolver: | |
| return "ScriptedBreakpointResolver"; | |
| case eScriptedExtensionScriptedThreadPlan: | |
| return "ScriptedThreadPlan"; | |
| case eScriptedExtensionScriptedFrameProvider: | |
| return "ScriptedFrameProvider"; | |
| case eScriptedExtensionScriptedHook: | |
| return "ScriptedHook"; | |
| case eScriptedExtensionScriptedThread: | |
| return "ScriptedThread"; | |
| case eScriptedExtensionScriptedFrame: | |
| return "ScriptedFrame"; | |
| case eScriptedExtensionScriptedStackFrameRecognizer: | |
| return "ScriptedStackFrameRecognizer"; | |
| case eScriptedExtensionScriptedCommand: | |
| return "ScriptedCommand"; | |
| case eScriptedExtensionParsedCommand: | |
| return "ParsedCommand"; | |
| case eScriptedExtensionScriptedStringSummary: | |
| return "ScriptedStringSummary"; | |
| case eScriptedExtensionScriptedSyntheticChildren: | |
| return "ScriptedSyntheticChildren"; | |
| } | |
| llvm_unreachable("unhandled ScriptedExtension"); | |
| } | |
| lldb::ScriptedExtension | |
| ScriptInterpreter::StringToExtension(llvm::StringRef string) { | |
| return llvm::StringSwitch<lldb::ScriptedExtension>(string) | |
| .CaseLower("OperatingSystem", eScriptedExtensionOperatingSystem) | |
| .CaseLower("ScriptedPlatform", eScriptedExtensionScriptedPlatform) | |
| .CaseLower("ScriptedProcess", eScriptedExtensionScriptedProcess) | |
| .CaseLower("ScriptedBreakpointResolver", | |
| eScriptedExtensionScriptedBreakpointResolver) | |
| .CaseLower("ScriptedThreadPlan", eScriptedExtensionScriptedThreadPlan) | |
| .CaseLower("ScriptedFrameProvider", | |
| eScriptedExtensionScriptedFrameProvider) | |
| .CaseLower("ScriptedHook", eScriptedExtensionScriptedHook) | |
| .CaseLower("ScriptedThread", eScriptedExtensionScriptedThread) | |
| .CaseLower("ScriptedFrame", eScriptedExtensionScriptedFrame) | |
| .CaseLower("ScriptedStackFrameRecognizer", | |
| eScriptedExtensionScriptedStackFrameRecognizer) | |
| .CaseLower("ScriptedCommand", eScriptedExtensionScriptedCommand) | |
| .CaseLower("ParsedCommand", eScriptedExtensionParsedCommand) | |
| .CaseLower("ScriptedStringSummary", | |
| eScriptedExtensionScriptedStringSummary) | |
| .CaseLower("ScriptedSyntheticChildren", | |
| eScriptedExtensionScriptedSyntheticChildren) | |
| .Default(eScriptedExtensionInvalid); | |
| } | |
| Status ScriptInterpreter::SetBreakpointCommandCallback( | |
| std::vector<std::reference_wrapper<BreakpointOptions>> &bp_options_vec, | |
| const char *callback_text) { | |
| Status error; | |
| for (BreakpointOptions &bp_options : bp_options_vec) { | |
| error = SetBreakpointCommandCallback(bp_options, callback_text, | |
| /*is_callback=*/false); | |
| if (!error.Success()) | |
| break; | |
| } | |
| return error; | |
| } | |
| Status ScriptInterpreter::SetBreakpointCommandCallbackFunction( | |
| std::vector<std::reference_wrapper<BreakpointOptions>> &bp_options_vec, | |
| const char *function_name, StructuredData::ObjectSP extra_args_sp) { | |
| Status error; | |
| for (BreakpointOptions &bp_options : bp_options_vec) { | |
| error = SetBreakpointCommandCallbackFunction(bp_options, function_name, | |
| extra_args_sp); | |
| if (!error.Success()) | |
| return error; | |
| } | |
| return error; | |
| } | |
| std::unique_ptr<ScriptInterpreterLocker> | |
| ScriptInterpreter::AcquireInterpreterLock() { | |
| return std::make_unique<ScriptInterpreterLocker>(); | |
| } | |
| ScriptInterpreter::SanitizedScriptingModuleName | |
| ScriptInterpreter::GetSanitizedScriptingModuleName(llvm::StringRef name) { | |
| std::string sanitized_name(name); | |
| std::string conflicting_keyword; | |
| // FIXME: for Python, don't allow certain characters in imported module | |
| // filenames. Theoretically, different scripting languages may have | |
| // different sets of forbidden tokens in filenames, and that should | |
| // be dealt with by each ScriptInterpreter. For now, just replace dots | |
| // with underscores. In order to support anything other than Python | |
| // this will need to be reworked. | |
| llvm::replace(sanitized_name, '.', '_'); | |
| llvm::replace(sanitized_name, ' ', '_'); | |
| llvm::replace(sanitized_name, '-', '_'); | |
| llvm::replace(sanitized_name, '+', 'x'); | |
| if (IsReservedWord(sanitized_name.c_str())) { | |
| conflicting_keyword = sanitized_name; | |
| sanitized_name.insert(sanitized_name.begin(), '_'); | |
| } | |
| return ScriptInterpreter::SanitizedScriptingModuleName( | |
| name.str(), std::move(sanitized_name), std::move(conflicting_keyword)); | |
| } | |
| static void ReadThreadBytesReceived(void *baton, const void *src, | |
| size_t src_len) { | |
| if (src && src_len) { | |
| Stream *strm = (Stream *)baton; | |
| strm->Write(src, src_len); | |
| strm->Flush(); | |
| } | |
| } | |
| llvm::Expected<std::unique_ptr<ScriptInterpreterIORedirect>> | |
| ScriptInterpreterIORedirect::Create(bool enable_io, Debugger &debugger, | |
| CommandReturnObject *result) { | |
| if (enable_io) | |
| return std::unique_ptr<ScriptInterpreterIORedirect>( | |
| new ScriptInterpreterIORedirect(debugger, result)); | |
| auto nullin = FileSystem::Instance().Open(FileSpec(FileSystem::DEV_NULL), | |
| File::eOpenOptionReadOnly); | |
| if (!nullin) | |
| return nullin.takeError(); | |
| auto nullout = FileSystem::Instance().Open(FileSpec(FileSystem::DEV_NULL), | |
| File::eOpenOptionWriteOnly); | |
| if (!nullout) | |
| return nullout.takeError(); | |
| return std::unique_ptr<ScriptInterpreterIORedirect>( | |
| new ScriptInterpreterIORedirect(std::move(*nullin), std::move(*nullout))); | |
| } | |
| ScriptInterpreterIORedirect::ScriptInterpreterIORedirect( | |
| std::unique_ptr<File> input, std::unique_ptr<File> output) | |
| : m_input_file_sp(std::move(input)), | |
| m_output_file_sp(std::make_shared<LockableStreamFile>(std::move(output), | |
| m_output_mutex)), | |
| m_error_file_sp(m_output_file_sp), | |
| m_communication("lldb.ScriptInterpreterIORedirect.comm"), | |
| m_disconnect(false) {} | |
| ScriptInterpreterIORedirect::ScriptInterpreterIORedirect( | |
| Debugger &debugger, CommandReturnObject *result) | |
| : m_communication("lldb.ScriptInterpreterIORedirect.comm"), | |
| m_disconnect(false) { | |
| if (result) { | |
| m_input_file_sp = debugger.GetInputFileSP(); | |
| Pipe pipe; | |
| Status pipe_result = pipe.CreateNew(); | |
| #if defined(_WIN32) | |
| lldb::file_t read_file = pipe.GetReadNativeHandle(); | |
| pipe.ReleaseReadFileDescriptor(); | |
| std::unique_ptr<ConnectionGenericFile> conn_up = | |
| std::make_unique<ConnectionGenericFile>(read_file, true); | |
| #else | |
| std::unique_ptr<ConnectionFileDescriptor> conn_up = | |
| std::make_unique<ConnectionFileDescriptor>( | |
| pipe.ReleaseReadFileDescriptor(), true); | |
| #endif | |
| if (conn_up->IsConnected()) { | |
| m_communication.SetConnection(std::move(conn_up)); | |
| m_communication.SetReadThreadBytesReceivedCallback( | |
| ReadThreadBytesReceived, &result->GetOutputStream()); | |
| m_communication.StartReadThread(); | |
| m_disconnect = true; | |
| FILE *outfile_handle = fdopen(pipe.ReleaseWriteFileDescriptor(), "w"); | |
| m_output_file_sp = std::make_shared<LockableStreamFile>( | |
| std::make_shared<StreamFile>(outfile_handle, NativeFile::Owned), | |
| m_output_mutex); | |
| m_error_file_sp = m_output_file_sp; | |
| if (outfile_handle) | |
| ::setbuf(outfile_handle, nullptr); | |
| result->SetImmediateOutputFile(debugger.GetOutputFileSP()); | |
| result->SetImmediateErrorFile(debugger.GetErrorFileSP()); | |
| } | |
| } | |
| if (!m_input_file_sp || !m_output_file_sp || !m_error_file_sp) | |
| debugger.AdoptTopIOHandlerFilesIfInvalid(m_input_file_sp, m_output_file_sp, | |
| m_error_file_sp); | |
| } | |
| void ScriptInterpreterIORedirect::Flush() { | |
| if (m_output_file_sp) | |
| m_output_file_sp->Lock().Flush(); | |
| if (m_error_file_sp) | |
| m_error_file_sp->Lock().Flush(); | |
| } | |
| ScriptInterpreterIORedirect::~ScriptInterpreterIORedirect() { | |
| if (!m_disconnect) | |
| return; | |
| assert(m_output_file_sp); | |
| assert(m_error_file_sp); | |
| assert(m_output_file_sp == m_error_file_sp); | |
| // Close the write end of the pipe since we are done with our one line | |
| // script. This should cause the read thread that output_comm is using to | |
| // exit. | |
| m_output_file_sp->GetUnlockedFile().Close(); | |
| // The close above should cause this thread to exit when it gets to the end | |
| // of file, so let it get all its data. | |
| m_communication.JoinReadThread(); | |
| // Now we can close the read end of the pipe. | |
| m_communication.Disconnect(); | |
| } |