| //===-- REPL.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/Expression/REPL.h" | |
| #include "lldb/Core/Debugger.h" | |
| #include "lldb/Core/PluginManager.h" | |
| #include "lldb/Expression/ExpressionVariable.h" | |
| #include "lldb/Expression/UserExpression.h" | |
| #include "lldb/Host/HostInfo.h" | |
| #include "lldb/Host/StreamFile.h" | |
| #include "lldb/Interpreter/CommandInterpreter.h" | |
| #include "lldb/Interpreter/CommandReturnObject.h" | |
| #include "lldb/Target/Thread.h" | |
| #include "lldb/Utility/AnsiTerminal.h" | |
| #include <memory> | |
| using namespace lldb_private; | |
| char REPL::ID; | |
| REPL::REPL(Target &target) : m_target(target) { | |
| // Make sure all option values have sane defaults | |
| Debugger &debugger = m_target.GetDebugger(); | |
| debugger.SetShowProgress(false); | |
| auto exe_ctx = debugger.GetCommandInterpreter().GetExecutionContext(); | |
| m_format_options.OptionParsingStarting(&exe_ctx); | |
| m_varobj_options.OptionParsingStarting(&exe_ctx); | |
| } | |
| REPL::~REPL() = default; | |
| lldb::REPLSP REPL::Create(Status &err, lldb::LanguageType language, | |
| Debugger *debugger, Target *target, | |
| const char *repl_options) { | |
| lldb::REPLSP ret; | |
| for (auto &cbs : PluginManager::GetREPLCallbacks()) { | |
| if (!cbs.supported_languages[language]) | |
| continue; | |
| ret = (*cbs.create_callback)(err, language, debugger, target, repl_options); | |
| if (ret) | |
| break; | |
| } | |
| return ret; | |
| } | |
| std::string REPL::GetSourcePath() { | |
| llvm::StringRef file_basename = GetSourceFileBasename(); | |
| FileSpec tmpdir_file_spec = HostInfo::GetProcessTempDir(); | |
| if (tmpdir_file_spec) { | |
| tmpdir_file_spec.SetFilename(file_basename); | |
| m_repl_source_path = tmpdir_file_spec.GetPath(); | |
| } else { | |
| tmpdir_file_spec = FileSpec("/tmp"); | |
| tmpdir_file_spec.AppendPathComponent(file_basename); | |
| } | |
| return tmpdir_file_spec.GetPath(); | |
| } | |
| lldb::IOHandlerSP REPL::GetIOHandler() { | |
| if (!m_io_handler_sp) { | |
| Debugger &debugger = m_target.GetDebugger(); | |
| m_io_handler_sp = std::make_shared<IOHandlerEditline>( | |
| debugger, IOHandler::Type::REPL, | |
| "lldb-repl", // Name of input reader for history | |
| llvm::StringRef("> "), // prompt | |
| llvm::StringRef(". "), // Continuation prompt | |
| true, // Multi-line | |
| true, // The REPL prompt is always colored | |
| 1, // Line number | |
| *this); | |
| // Don't exit if CTRL+C is pressed | |
| static_cast<IOHandlerEditline *>(m_io_handler_sp.get()) | |
| ->SetInterruptExits(false); | |
| if (m_io_handler_sp->GetIsInteractive() && | |
| m_io_handler_sp->GetIsRealTerminal()) { | |
| m_indent_str.assign(debugger.GetTabSize(), ' '); | |
| m_enable_auto_indent = debugger.GetAutoIndent(); | |
| } else { | |
| m_indent_str.clear(); | |
| m_enable_auto_indent = false; | |
| } | |
| } | |
| return m_io_handler_sp; | |
| } | |
| void REPL::IOHandlerActivated(IOHandler &io_handler, bool interactive) { | |
| lldb::ProcessSP process_sp = m_target.GetProcessSP(); | |
| if (process_sp && process_sp->IsAlive()) | |
| return; | |
| LockedStreamFile locked_stream = io_handler.GetErrorStreamFileSP()->Lock(); | |
| locked_stream.PutCString("REPL requires a running target process.\n"); | |
| io_handler.SetIsDone(true); | |
| } | |
| bool REPL::IOHandlerInterrupt(IOHandler &io_handler) { return false; } | |
| void REPL::IOHandlerInputInterrupted(IOHandler &io_handler, std::string &line) { | |
| } | |
| const char *REPL::IOHandlerGetFixIndentationCharacters() { | |
| return (m_enable_auto_indent ? GetAutoIndentCharacters() : nullptr); | |
| } | |
| llvm::StringRef REPL::IOHandlerGetControlSequence(char ch) { | |
| static constexpr llvm::StringLiteral control_sequence(":quit\n"); | |
| if (ch == 'd') | |
| return control_sequence; | |
| return {}; | |
| } | |
| const char *REPL::IOHandlerGetCommandPrefix() { return ":"; } | |
| const char *REPL::IOHandlerGetHelpPrologue() { | |
| return "\nThe REPL (Read-Eval-Print-Loop) acts like an interpreter. " | |
| "Valid statements, expressions, and declarations are immediately " | |
| "compiled and executed.\n\n" | |
| "The complete set of LLDB debugging commands are also available as " | |
| "described below.\n\nCommands " | |
| "must be prefixed with a colon at the REPL prompt (:quit for " | |
| "example.) Typing just a colon " | |
| "followed by return will switch to the LLDB prompt.\n\n" | |
| "Type “< path” to read in code from a text file “path”.\n\n"; | |
| } | |
| bool REPL::IOHandlerIsInputComplete(IOHandler &io_handler, StringList &lines) { | |
| // Check for meta command | |
| const size_t num_lines = lines.GetSize(); | |
| if (num_lines == 1) { | |
| const char *first_line = lines.GetStringAtIndex(0); | |
| if (first_line[0] == ':') | |
| return true; // Meta command is a single line where that starts with ':' | |
| } | |
| // Check if REPL input is done | |
| std::string source_string(lines.CopyList()); | |
| return SourceIsComplete(source_string); | |
| } | |
| int REPL::CalculateActualIndentation(const StringList &lines) { | |
| std::string last_line = lines[lines.GetSize() - 1]; | |
| int actual_indent = 0; | |
| for (char &ch : last_line) { | |
| if (ch != ' ') | |
| break; | |
| ++actual_indent; | |
| } | |
| return actual_indent; | |
| } | |
| int REPL::IOHandlerFixIndentation(IOHandler &io_handler, | |
| const StringList &lines, | |
| int cursor_position) { | |
| if (!m_enable_auto_indent) | |
| return 0; | |
| if (!lines.GetSize()) { | |
| return 0; | |
| } | |
| int tab_size = io_handler.GetDebugger().GetTabSize(); | |
| lldb::offset_t desired_indent = | |
| GetDesiredIndentation(lines, cursor_position, tab_size); | |
| int actual_indent = REPL::CalculateActualIndentation(lines); | |
| if (desired_indent == LLDB_INVALID_OFFSET) | |
| return 0; | |
| return (int)desired_indent - actual_indent; | |
| } | |
| static bool ReadCode(const std::string &path, std::string &code, | |
| lldb::LockableStreamFileSP &error_stream_sp) { | |
| auto &fs = FileSystem::Instance(); | |
| llvm::Twine pathTwine(path); | |
| if (!fs.Exists(pathTwine)) { | |
| error_stream_sp->Lock().Printf("no such file at path '%s'\n", path.c_str()); | |
| return false; | |
| } | |
| if (!fs.Readable(pathTwine)) { | |
| error_stream_sp->Lock().Printf("could not read file at path '%s'\n", | |
| path.c_str()); | |
| return false; | |
| } | |
| const size_t file_size = fs.GetByteSize(pathTwine); | |
| const size_t max_size = code.max_size(); | |
| if (file_size > max_size) { | |
| error_stream_sp->Lock().Printf("file at path '%s' too large: " | |
| "file_size = %zu, max_size = %zu\n", | |
| path.c_str(), file_size, max_size); | |
| return false; | |
| } | |
| auto data_sp = fs.CreateDataBuffer(pathTwine); | |
| if (data_sp == nullptr) { | |
| error_stream_sp->Lock().Printf( | |
| "could not create buffer for file at path '%s'\n", path.c_str()); | |
| return false; | |
| } | |
| code.assign((const char *)data_sp->GetBytes(), data_sp->GetByteSize()); | |
| return true; | |
| } | |
| void REPL::IOHandlerInputComplete(IOHandler &io_handler, std::string &code) { | |
| lldb::LockableStreamFileSP output_stream_sp = | |
| io_handler.GetOutputStreamFileSP(); | |
| lldb::LockableStreamFileSP error_stream_sp = | |
| io_handler.GetErrorStreamFileSP(); | |
| bool extra_line = false; | |
| bool did_quit = false; | |
| if (code.empty()) { | |
| m_code.AppendString(""); | |
| static_cast<IOHandlerEditline &>(io_handler) | |
| .SetBaseLineNumber(m_code.GetSize() + 1); | |
| } else { | |
| Debugger &debugger = m_target.GetDebugger(); | |
| CommandInterpreter &ci = debugger.GetCommandInterpreter(); | |
| extra_line = ci.GetSpaceReplPrompts(); | |
| ExecutionContext exe_ctx(m_target.GetProcessSP() | |
| ->GetThreadList() | |
| .GetSelectedThread() | |
| ->GetSelectedFrame(DoNoSelectMostRelevantFrame) | |
| .get()); | |
| lldb::ProcessSP process_sp(exe_ctx.GetProcessSP()); | |
| if (code[0] == ':') { | |
| // Meta command | |
| // Strip the ':' | |
| code.erase(0, 1); | |
| if (!llvm::StringRef(code).trim().empty()) { | |
| // "lldb" was followed by arguments, so just execute the command dump | |
| // the results | |
| // Turn off prompt on quit in case the user types ":quit" | |
| const bool saved_prompt_on_quit = ci.GetPromptOnQuit(); | |
| if (saved_prompt_on_quit) | |
| ci.SetPromptOnQuit(false); | |
| // Execute the command | |
| CommandReturnObject result(debugger.GetUseColor()); | |
| result.SetImmediateOutputStream(std::make_shared<StreamFile>( | |
| output_stream_sp->GetUnlockedFileSP())); | |
| result.SetImmediateErrorStream( | |
| std::make_shared<StreamFile>(error_stream_sp->GetUnlockedFileSP())); | |
| ci.HandleCommand(code.c_str(), eLazyBoolNo, result); | |
| if (saved_prompt_on_quit) | |
| ci.SetPromptOnQuit(true); | |
| if (result.GetStatus() == lldb::eReturnStatusQuit) { | |
| did_quit = true; | |
| io_handler.SetIsDone(true); | |
| if (debugger.CheckTopIOHandlerTypes( | |
| IOHandler::Type::REPL, IOHandler::Type::CommandInterpreter)) { | |
| // We typed "quit" or an alias to quit so we need to check if the | |
| // command interpreter is above us and tell it that it is done as | |
| // well so we don't drop back into the command interpreter if we | |
| // have already quit | |
| lldb::IOHandlerSP io_handler_sp(ci.GetIOHandler()); | |
| if (io_handler_sp) | |
| io_handler_sp->SetIsDone(true); | |
| } | |
| } | |
| } else { | |
| // ":" was followed by no arguments, so push the LLDB command prompt | |
| if (debugger.CheckTopIOHandlerTypes( | |
| IOHandler::Type::REPL, IOHandler::Type::CommandInterpreter)) { | |
| // If the user wants to get back to the command interpreter and the | |
| // command interpreter is what launched the REPL, then just let the | |
| // REPL exit and fall back to the command interpreter. | |
| io_handler.SetIsDone(true); | |
| } else { | |
| // The REPL wasn't launched the by the command interpreter, it is the | |
| // base IOHandler, so we need to get the command interpreter and | |
| lldb::IOHandlerSP io_handler_sp(ci.GetIOHandler()); | |
| if (io_handler_sp) { | |
| io_handler_sp->SetIsDone(false); | |
| debugger.RunIOHandlerAsync(ci.GetIOHandler()); | |
| } | |
| } | |
| } | |
| } else { | |
| if (code[0] == '<') { | |
| // User wants to read code from a file. | |
| // Interpret rest of line as a literal path. | |
| auto path = llvm::StringRef(code.substr(1)).trim().str(); | |
| if (!ReadCode(path, code, error_stream_sp)) { | |
| return; | |
| } | |
| } | |
| // Unwind any expression we might have been running in case our REPL | |
| // expression crashed and the user was looking around | |
| if (m_dedicated_repl_mode) { | |
| Thread *thread = exe_ctx.GetThreadPtr(); | |
| if (thread && thread->UnwindInnermostExpression().Success()) { | |
| thread->SetSelectedFrameByIndex(0, false); | |
| exe_ctx.SetFrameSP( | |
| thread->GetSelectedFrame(DoNoSelectMostRelevantFrame)); | |
| } | |
| } | |
| const bool colorize_err = | |
| error_stream_sp->Lock().GetFile().GetIsTerminalWithColors(); | |
| EvaluateExpressionOptions expr_options = m_expr_options; | |
| expr_options.SetCoerceToId(m_varobj_options.use_object_desc); | |
| expr_options.SetKeepInMemory(true); | |
| expr_options.SetUseDynamic(m_varobj_options.use_dynamic); | |
| expr_options.SetGenerateDebugInfo(true); | |
| expr_options.SetREPLEnabled(true); | |
| expr_options.SetColorizeErrors(colorize_err); | |
| expr_options.SetPoundLine(m_repl_source_path.c_str(), | |
| m_code.GetSize() + 1); | |
| expr_options.SetLanguage(GetLanguage()); | |
| PersistentExpressionState *persistent_state = | |
| m_target.GetPersistentExpressionStateForLanguage(GetLanguage()); | |
| if (!persistent_state) | |
| return; | |
| const size_t var_count_before = persistent_state->GetSize(); | |
| const char *expr_prefix = nullptr; | |
| lldb::ValueObjectSP result_valobj_sp; | |
| lldb::ExpressionResults execution_results = UserExpression::Evaluate( | |
| exe_ctx, expr_options, code.c_str(), expr_prefix, result_valobj_sp); | |
| Status error; | |
| if (llvm::Error err = OnExpressionEvaluated(exe_ctx, code, expr_options, | |
| execution_results, | |
| result_valobj_sp, error)) { | |
| error_stream_sp->Lock() << llvm::toString(std::move(err)) << "\n"; | |
| } else if (process_sp && process_sp->IsAlive()) { | |
| bool add_to_code = true; | |
| bool handled = false; | |
| if (result_valobj_sp) { | |
| lldb::Format format = m_format_options.GetFormat(); | |
| if (result_valobj_sp->GetError().Success()) { | |
| handled |= | |
| PrintOneVariable(debugger, output_stream_sp, result_valobj_sp); | |
| } else if (result_valobj_sp->GetError().GetError() == | |
| UserExpression::kNoResult) { | |
| if (format != lldb::eFormatVoid && debugger.GetNotifyVoid()) { | |
| error_stream_sp->Lock().PutCString("(void)\n"); | |
| handled = true; | |
| } | |
| } | |
| } | |
| if (debugger.GetPrintDecls()) { | |
| for (size_t vi = var_count_before, ve = persistent_state->GetSize(); | |
| vi != ve; ++vi) { | |
| lldb::ExpressionVariableSP persistent_var_sp = | |
| persistent_state->GetVariableAtIndex(vi); | |
| lldb::ValueObjectSP valobj_sp = persistent_var_sp->GetValueObject(); | |
| PrintOneVariable(debugger, output_stream_sp, valobj_sp, | |
| persistent_var_sp.get()); | |
| } | |
| } | |
| bool run_command_interpreter = false; | |
| if (!handled) { | |
| LockedStreamFile locked_error_stream = error_stream_sp->Lock(); | |
| bool useColors = | |
| locked_error_stream.GetFile().GetIsTerminalWithColors(); | |
| switch (execution_results) { | |
| case lldb::eExpressionSetupError: | |
| case lldb::eExpressionParseError: | |
| add_to_code = false; | |
| [[fallthrough]]; | |
| case lldb::eExpressionDiscarded: | |
| locked_error_stream.Printf("%s\n", error.AsCString()); | |
| break; | |
| case lldb::eExpressionCompleted: | |
| break; | |
| case lldb::eExpressionInterrupted: | |
| if (useColors) { | |
| locked_error_stream.Printf(ANSI_ESCAPE1(ANSI_FG_COLOR_RED)); | |
| locked_error_stream.Printf(ANSI_ESCAPE1(ANSI_CTRL_BOLD)); | |
| } | |
| locked_error_stream.PutCString("Execution interrupted. "); | |
| if (useColors) | |
| locked_error_stream.Printf(ANSI_ESCAPE1(ANSI_CTRL_NORMAL)); | |
| locked_error_stream.Printf( | |
| "Enter code to recover and continue.\nEnter LLDB " | |
| "commands to investigate (type :help for " | |
| "assistance.)\n"); | |
| break; | |
| case lldb::eExpressionHitBreakpoint: | |
| // Breakpoint was hit, drop into LLDB command interpreter | |
| if (useColors) { | |
| locked_error_stream.Printf(ANSI_ESCAPE1(ANSI_FG_COLOR_RED)); | |
| locked_error_stream.Printf(ANSI_ESCAPE1(ANSI_CTRL_BOLD)); | |
| } | |
| output_stream_sp->Lock().Printf( | |
| "Execution stopped at breakpoint. "); | |
| if (useColors) | |
| locked_error_stream.Printf(ANSI_ESCAPE1(ANSI_CTRL_NORMAL)); | |
| locked_error_stream.Printf( | |
| "Enter LLDB commands to investigate (type help " | |
| "for assistance.)\n"); | |
| run_command_interpreter = true; | |
| break; | |
| case lldb::eExpressionTimedOut: | |
| locked_error_stream.PutCString("error: timeout\n"); | |
| if (error.AsCString()) | |
| locked_error_stream.Printf("error: %s\n", error.AsCString()); | |
| break; | |
| case lldb::eExpressionResultUnavailable: | |
| // Shoulnd't happen??? | |
| locked_error_stream.Printf("error: could not fetch result -- %s\n", | |
| error.AsCString()); | |
| break; | |
| case lldb::eExpressionStoppedForDebug: | |
| // Shoulnd't happen??? | |
| locked_error_stream.Printf("error: stopped for debug -- %s\n", | |
| error.AsCString()); | |
| break; | |
| case lldb::eExpressionThreadVanished: | |
| // Shoulnd't happen??? | |
| locked_error_stream.Printf( | |
| "error: expression thread vanished -- %s\n", error.AsCString()); | |
| break; | |
| } | |
| } | |
| // RunIOHandlerAsync takes the IOHandler stack mutex, and | |
| // Debugger::PrintAsync takes that mutex before the mutex shared by the | |
| // output and error streams. Don't call RunIOHandlerAsync while holding | |
| // the error stream lock. | |
| if (run_command_interpreter) { | |
| lldb::IOHandlerSP io_handler_sp(ci.GetIOHandler()); | |
| if (io_handler_sp) { | |
| io_handler_sp->SetIsDone(false); | |
| debugger.RunIOHandlerAsync(ci.GetIOHandler()); | |
| } | |
| } | |
| if (add_to_code) { | |
| const uint32_t new_default_line = m_code.GetSize() + 1; | |
| m_code.SplitIntoLines(code); | |
| // Update our code on disk | |
| if (!m_repl_source_path.empty()) { | |
| auto file = FileSystem::Instance().Open( | |
| FileSpec(m_repl_source_path), | |
| File::eOpenOptionWriteOnly | File::eOpenOptionTruncate | | |
| File::eOpenOptionCanCreate, | |
| lldb::eFilePermissionsFileDefault); | |
| if (file) { | |
| std::string code(m_code.CopyList()); | |
| code.append(1, '\n'); | |
| size_t bytes_written = code.size(); | |
| file.get()->Write(code.c_str(), bytes_written); | |
| file.get()->Close(); | |
| } else { | |
| std::string message = llvm::toString(file.takeError()); | |
| error_stream_sp->Lock().Printf("error: couldn't open %s: %s\n", | |
| m_repl_source_path.c_str(), | |
| message.c_str()); | |
| } | |
| // Now set the default file and line to the REPL source file | |
| m_target.GetSourceManager().SetDefaultFileAndLine( | |
| std::make_shared<SupportFile>(FileSpec(m_repl_source_path)), | |
| new_default_line); | |
| } | |
| static_cast<IOHandlerEditline &>(io_handler) | |
| .SetBaseLineNumber(m_code.GetSize() + 1); | |
| } | |
| if (extra_line) | |
| output_stream_sp->Lock().Printf("\n"); | |
| } | |
| } | |
| // Don't complain about the REPL process going away if we are in the | |
| // process of quitting. | |
| if (!did_quit && (!process_sp || !process_sp->IsAlive())) { | |
| error_stream_sp->Lock().Printf( | |
| "error: REPL process is no longer alive, exiting REPL\n"); | |
| io_handler.SetIsDone(true); | |
| } | |
| } | |
| } | |
| void REPL::IOHandlerComplete(IOHandler &io_handler, | |
| CompletionRequest &request) { | |
| // Complete an LLDB command if the first character is a colon... | |
| if (request.GetRawLine().starts_with(":")) { | |
| Debugger &debugger = m_target.GetDebugger(); | |
| // auto complete LLDB commands | |
| llvm::StringRef new_line = request.GetRawLine().drop_front(); | |
| CompletionResult sub_result; | |
| CompletionRequest sub_request(new_line, request.GetRawCursorPos() - 1, | |
| sub_result); | |
| debugger.GetCommandInterpreter().HandleCompletion(sub_request); | |
| StringList matches, descriptions; | |
| sub_result.GetMatches(matches); | |
| // Prepend command prefix that was excluded in the completion request. | |
| if (request.GetCursorIndex() == 0) | |
| for (auto &match : matches) | |
| match.insert(0, 1, ':'); | |
| sub_result.GetDescriptions(descriptions); | |
| request.AddCompletions(matches, descriptions); | |
| return; | |
| } | |
| // Strip spaces from the line and see if we had only spaces | |
| if (request.GetRawLine().trim().empty()) { | |
| // Only spaces on this line, so just indent | |
| request.AddCompletion(m_indent_str); | |
| return; | |
| } | |
| std::string current_code; | |
| current_code.append(m_code.CopyList()); | |
| IOHandlerEditline &editline = static_cast<IOHandlerEditline &>(io_handler); | |
| StringList current_lines = editline.GetCurrentLines(); | |
| const uint32_t current_line_idx = editline.GetCurrentLineIndex(); | |
| if (current_line_idx < current_lines.GetSize()) { | |
| for (uint32_t i = 0; i < current_line_idx; ++i) { | |
| const char *line_cstr = current_lines.GetStringAtIndex(i); | |
| if (line_cstr) { | |
| current_code.append("\n"); | |
| current_code.append(line_cstr); | |
| } | |
| } | |
| } | |
| current_code.append("\n"); | |
| current_code += request.GetRawLine(); | |
| CompleteCode(current_code, request); | |
| } | |
| bool QuitCommandOverrideCallback(void *baton, const char **argv) { | |
| Target *target = (Target *)baton; | |
| lldb::ProcessSP process_sp(target->GetProcessSP()); | |
| if (process_sp) { | |
| process_sp->Destroy(false); | |
| process_sp->GetTarget().GetDebugger().ClearIOHandlers(); | |
| } | |
| return false; | |
| } | |
| Status REPL::RunLoop() { | |
| Status error; | |
| error = DoInitialization(); | |
| m_repl_source_path = GetSourcePath(); | |
| if (!error.Success()) | |
| return error; | |
| Debugger &debugger = m_target.GetDebugger(); | |
| lldb::IOHandlerSP io_handler_sp(GetIOHandler()); | |
| std::optional<SourceManager::SupportFileAndLine> default_file_line; | |
| if (!m_repl_source_path.empty()) { | |
| // Save the current default file and line | |
| default_file_line = m_target.GetSourceManager().GetDefaultFileAndLine(); | |
| } | |
| debugger.RunIOHandlerAsync(io_handler_sp); | |
| // Check if we are in dedicated REPL mode where LLDB was start with the "-- | |
| // repl" option from the command line. Currently we know this by checking if | |
| // the debugger already has a IOHandler thread. | |
| if (!debugger.HasIOHandlerThread()) { | |
| // The debugger doesn't have an existing IOHandler thread, so this must be | |
| // dedicated REPL mode... | |
| m_dedicated_repl_mode = true; | |
| debugger.StartIOHandlerThread(); | |
| llvm::StringRef command_name_str("quit"); | |
| CommandObject *cmd_obj = | |
| debugger.GetCommandInterpreter().GetCommandObjectForCommand( | |
| command_name_str); | |
| if (cmd_obj) { | |
| assert(command_name_str.empty()); | |
| cmd_obj->SetOverrideCallback(QuitCommandOverrideCallback, &m_target); | |
| } | |
| } | |
| // Wait for the REPL command interpreter to get popped | |
| io_handler_sp->WaitForPop(); | |
| if (m_dedicated_repl_mode) { | |
| // If we were in dedicated REPL mode we would have started the IOHandler | |
| // thread, and we should kill our process | |
| lldb::ProcessSP process_sp = m_target.GetProcessSP(); | |
| if (process_sp && process_sp->IsAlive()) | |
| process_sp->Destroy(false); | |
| // Wait for the IO handler thread to exit (TODO: don't do this if the IO | |
| // handler thread already exists...) | |
| debugger.JoinIOHandlerThread(); | |
| } | |
| // Restore the default file and line | |
| if (default_file_line) | |
| m_target.GetSourceManager().SetDefaultFileAndLine( | |
| default_file_line->support_file_nsp, default_file_line->line); | |
| return error; | |
| } |