1 //===-- REPL.cpp ------------------------------------------------*- C++ -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "lldb/Expression/REPL.h" 10 #include "lldb/Core/Debugger.h" 11 #include "lldb/Core/PluginManager.h" 12 #include "lldb/Core/StreamFile.h" 13 #include "lldb/Expression/ExpressionVariable.h" 14 #include "lldb/Expression/UserExpression.h" 15 #include "lldb/Host/HostInfo.h" 16 #include "lldb/Interpreter/CommandInterpreter.h" 17 #include "lldb/Interpreter/CommandReturnObject.h" 18 #include "lldb/Target/Thread.h" 19 #include "lldb/Utility/AnsiTerminal.h" 20 21 #include <memory> 22 23 using namespace lldb_private; 24 25 REPL::REPL(LLVMCastKind kind, Target &target) : m_target(target), m_kind(kind) { 26 // Make sure all option values have sane defaults 27 Debugger &debugger = m_target.GetDebugger(); 28 auto exe_ctx = debugger.GetCommandInterpreter().GetExecutionContext(); 29 m_format_options.OptionParsingStarting(&exe_ctx); 30 m_varobj_options.OptionParsingStarting(&exe_ctx); 31 } 32 33 REPL::~REPL() = default; 34 35 lldb::REPLSP REPL::Create(Status &err, lldb::LanguageType language, 36 Debugger *debugger, Target *target, 37 const char *repl_options) { 38 uint32_t idx = 0; 39 lldb::REPLSP ret; 40 41 while (REPLCreateInstance create_instance = 42 PluginManager::GetREPLCreateCallbackAtIndex(idx++)) { 43 ret = (*create_instance)(err, language, debugger, target, repl_options); 44 if (ret) { 45 break; 46 } 47 } 48 49 return ret; 50 } 51 52 std::string REPL::GetSourcePath() { 53 ConstString file_basename = GetSourceFileBasename(); 54 FileSpec tmpdir_file_spec = HostInfo::GetProcessTempDir(); 55 if (tmpdir_file_spec) { 56 tmpdir_file_spec.GetFilename().SetCString(file_basename.AsCString()); 57 m_repl_source_path = tmpdir_file_spec.GetPath(); 58 } else { 59 tmpdir_file_spec = FileSpec("/tmp"); 60 tmpdir_file_spec.AppendPathComponent(file_basename.AsCString()); 61 } 62 63 return tmpdir_file_spec.GetPath(); 64 } 65 66 lldb::IOHandlerSP REPL::GetIOHandler() { 67 if (!m_io_handler_sp) { 68 Debugger &debugger = m_target.GetDebugger(); 69 m_io_handler_sp = std::make_shared<IOHandlerEditline>( 70 debugger, IOHandler::Type::REPL, 71 "lldb-repl", // Name of input reader for history 72 llvm::StringRef("> "), // prompt 73 llvm::StringRef(". "), // Continuation prompt 74 true, // Multi-line 75 true, // The REPL prompt is always colored 76 1, // Line number 77 *this, nullptr); 78 79 // Don't exit if CTRL+C is pressed 80 static_cast<IOHandlerEditline *>(m_io_handler_sp.get()) 81 ->SetInterruptExits(false); 82 83 if (m_io_handler_sp->GetIsInteractive() && 84 m_io_handler_sp->GetIsRealTerminal()) { 85 m_indent_str.assign(debugger.GetTabSize(), ' '); 86 m_enable_auto_indent = debugger.GetAutoIndent(); 87 } else { 88 m_indent_str.clear(); 89 m_enable_auto_indent = false; 90 } 91 } 92 return m_io_handler_sp; 93 } 94 95 void REPL::IOHandlerActivated(IOHandler &io_handler, bool interactive) { 96 lldb::ProcessSP process_sp = m_target.GetProcessSP(); 97 if (process_sp && process_sp->IsAlive()) 98 return; 99 lldb::StreamFileSP error_sp(io_handler.GetErrorStreamFile()); 100 error_sp->Printf("REPL requires a running target process.\n"); 101 io_handler.SetIsDone(true); 102 } 103 104 bool REPL::IOHandlerInterrupt(IOHandler &io_handler) { return false; } 105 106 void REPL::IOHandlerInputInterrupted(IOHandler &io_handler, std::string &line) { 107 } 108 109 const char *REPL::IOHandlerGetFixIndentationCharacters() { 110 return (m_enable_auto_indent ? GetAutoIndentCharacters() : nullptr); 111 } 112 113 ConstString REPL::IOHandlerGetControlSequence(char ch) { 114 if (ch == 'd') 115 return ConstString(":quit\n"); 116 return ConstString(); 117 } 118 119 const char *REPL::IOHandlerGetCommandPrefix() { return ":"; } 120 121 const char *REPL::IOHandlerGetHelpPrologue() { 122 return "\nThe REPL (Read-Eval-Print-Loop) acts like an interpreter. " 123 "Valid statements, expressions, and declarations are immediately " 124 "compiled and executed.\n\n" 125 "The complete set of LLDB debugging commands are also available as " 126 "described below. Commands " 127 "must be prefixed with a colon at the REPL prompt (:quit for " 128 "example.) Typing just a colon " 129 "followed by return will switch to the LLDB prompt.\n\n"; 130 } 131 132 bool REPL::IOHandlerIsInputComplete(IOHandler &io_handler, StringList &lines) { 133 // Check for meta command 134 const size_t num_lines = lines.GetSize(); 135 if (num_lines == 1) { 136 const char *first_line = lines.GetStringAtIndex(0); 137 if (first_line[0] == ':') 138 return true; // Meta command is a single line where that starts with ':' 139 } 140 141 // Check if REPL input is done 142 std::string source_string(lines.CopyList()); 143 return SourceIsComplete(source_string); 144 } 145 146 int REPL::CalculateActualIndentation(const StringList &lines) { 147 std::string last_line = lines[lines.GetSize() - 1]; 148 149 int actual_indent = 0; 150 for (char &ch : last_line) { 151 if (ch != ' ') 152 break; 153 ++actual_indent; 154 } 155 156 return actual_indent; 157 } 158 159 int REPL::IOHandlerFixIndentation(IOHandler &io_handler, 160 const StringList &lines, 161 int cursor_position) { 162 if (!m_enable_auto_indent) 163 return 0; 164 165 if (!lines.GetSize()) { 166 return 0; 167 } 168 169 int tab_size = io_handler.GetDebugger().GetTabSize(); 170 171 lldb::offset_t desired_indent = 172 GetDesiredIndentation(lines, cursor_position, tab_size); 173 174 int actual_indent = REPL::CalculateActualIndentation(lines); 175 176 if (desired_indent == LLDB_INVALID_OFFSET) 177 return 0; 178 179 return (int)desired_indent - actual_indent; 180 } 181 182 void REPL::IOHandlerInputComplete(IOHandler &io_handler, std::string &code) { 183 lldb::StreamFileSP output_sp(io_handler.GetOutputStreamFile()); 184 lldb::StreamFileSP error_sp(io_handler.GetErrorStreamFile()); 185 bool extra_line = false; 186 bool did_quit = false; 187 188 if (code.empty()) { 189 m_code.AppendString(""); 190 static_cast<IOHandlerEditline &>(io_handler) 191 .SetBaseLineNumber(m_code.GetSize() + 1); 192 } else { 193 Debugger &debugger = m_target.GetDebugger(); 194 CommandInterpreter &ci = debugger.GetCommandInterpreter(); 195 extra_line = ci.GetSpaceReplPrompts(); 196 197 ExecutionContext exe_ctx(m_target.GetProcessSP() 198 ->GetThreadList() 199 .GetSelectedThread() 200 ->GetSelectedFrame() 201 .get()); 202 203 lldb::ProcessSP process_sp(exe_ctx.GetProcessSP()); 204 205 if (code[0] == ':') { 206 // Meta command 207 // Strip the ':' 208 code.erase(0, 1); 209 if (Args::StripSpaces(code)) { 210 // "lldb" was followed by arguments, so just execute the command dump 211 // the results 212 213 // Turn off prompt on quit in case the user types ":quit" 214 const bool saved_prompt_on_quit = ci.GetPromptOnQuit(); 215 if (saved_prompt_on_quit) 216 ci.SetPromptOnQuit(false); 217 218 // Execute the command 219 CommandReturnObject result; 220 result.SetImmediateOutputStream(output_sp); 221 result.SetImmediateErrorStream(error_sp); 222 ci.HandleCommand(code.c_str(), eLazyBoolNo, result); 223 224 if (saved_prompt_on_quit) 225 ci.SetPromptOnQuit(true); 226 227 if (result.GetStatus() == lldb::eReturnStatusQuit) { 228 did_quit = true; 229 io_handler.SetIsDone(true); 230 if (debugger.CheckTopIOHandlerTypes( 231 IOHandler::Type::REPL, IOHandler::Type::CommandInterpreter)) { 232 // We typed "quit" or an alias to quit so we need to check if the 233 // command interpreter is above us and tell it that it is done as 234 // well so we don't drop back into the command interpreter if we 235 // have already quit 236 lldb::IOHandlerSP io_handler_sp(ci.GetIOHandler()); 237 if (io_handler_sp) 238 io_handler_sp->SetIsDone(true); 239 } 240 } 241 } else { 242 // ":" was followed by no arguments, so push the LLDB command prompt 243 if (debugger.CheckTopIOHandlerTypes( 244 IOHandler::Type::REPL, IOHandler::Type::CommandInterpreter)) { 245 // If the user wants to get back to the command interpreter and the 246 // command interpreter is what launched the REPL, then just let the 247 // REPL exit and fall back to the command interpreter. 248 io_handler.SetIsDone(true); 249 } else { 250 // The REPL wasn't launched the by the command interpreter, it is the 251 // base IOHandler, so we need to get the command interpreter and 252 lldb::IOHandlerSP io_handler_sp(ci.GetIOHandler()); 253 if (io_handler_sp) { 254 io_handler_sp->SetIsDone(false); 255 debugger.PushIOHandler(ci.GetIOHandler()); 256 } 257 } 258 } 259 } else { 260 // Unwind any expression we might have been running in case our REPL 261 // expression crashed and the user was looking around 262 if (m_dedicated_repl_mode) { 263 Thread *thread = exe_ctx.GetThreadPtr(); 264 if (thread && thread->UnwindInnermostExpression().Success()) { 265 thread->SetSelectedFrameByIndex(0, false); 266 exe_ctx.SetFrameSP(thread->GetSelectedFrame()); 267 } 268 } 269 270 const bool colorize_err = error_sp->GetFile().GetIsTerminalWithColors(); 271 272 EvaluateExpressionOptions expr_options = m_expr_options; 273 expr_options.SetCoerceToId(m_varobj_options.use_objc); 274 expr_options.SetKeepInMemory(true); 275 expr_options.SetUseDynamic(m_varobj_options.use_dynamic); 276 expr_options.SetGenerateDebugInfo(true); 277 expr_options.SetREPLEnabled(true); 278 expr_options.SetColorizeErrors(colorize_err); 279 expr_options.SetPoundLine(m_repl_source_path.c_str(), 280 m_code.GetSize() + 1); 281 282 expr_options.SetLanguage(GetLanguage()); 283 284 PersistentExpressionState *persistent_state = 285 m_target.GetPersistentExpressionStateForLanguage(GetLanguage()); 286 287 const size_t var_count_before = persistent_state->GetSize(); 288 289 const char *expr_prefix = nullptr; 290 lldb::ValueObjectSP result_valobj_sp; 291 Status error; 292 lldb::ModuleSP jit_module_sp; 293 lldb::ExpressionResults execution_results = 294 UserExpression::Evaluate(exe_ctx, expr_options, code.c_str(), 295 expr_prefix, result_valobj_sp, error, 296 nullptr, // Fixed Expression 297 &jit_module_sp); 298 299 // CommandInterpreter &ci = debugger.GetCommandInterpreter(); 300 301 if (process_sp && process_sp->IsAlive()) { 302 bool add_to_code = true; 303 bool handled = false; 304 if (result_valobj_sp) { 305 lldb::Format format = m_format_options.GetFormat(); 306 307 if (result_valobj_sp->GetError().Success()) { 308 handled |= PrintOneVariable(debugger, output_sp, result_valobj_sp); 309 } else if (result_valobj_sp->GetError().GetError() == 310 UserExpression::kNoResult) { 311 if (format != lldb::eFormatVoid && debugger.GetNotifyVoid()) { 312 error_sp->PutCString("(void)\n"); 313 handled = true; 314 } 315 } 316 } 317 318 if (debugger.GetPrintDecls()) { 319 for (size_t vi = var_count_before, ve = persistent_state->GetSize(); 320 vi != ve; ++vi) { 321 lldb::ExpressionVariableSP persistent_var_sp = 322 persistent_state->GetVariableAtIndex(vi); 323 lldb::ValueObjectSP valobj_sp = persistent_var_sp->GetValueObject(); 324 325 PrintOneVariable(debugger, output_sp, valobj_sp, 326 persistent_var_sp.get()); 327 } 328 } 329 330 if (!handled) { 331 bool useColors = error_sp->GetFile().GetIsTerminalWithColors(); 332 switch (execution_results) { 333 case lldb::eExpressionSetupError: 334 case lldb::eExpressionParseError: 335 add_to_code = false; 336 LLVM_FALLTHROUGH; 337 case lldb::eExpressionDiscarded: 338 error_sp->Printf("%s\n", error.AsCString()); 339 break; 340 341 case lldb::eExpressionCompleted: 342 break; 343 case lldb::eExpressionInterrupted: 344 if (useColors) { 345 error_sp->Printf(ANSI_ESCAPE1(ANSI_FG_COLOR_RED)); 346 error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_BOLD)); 347 } 348 error_sp->Printf("Execution interrupted. "); 349 if (useColors) 350 error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_NORMAL)); 351 error_sp->Printf("Enter code to recover and continue.\nEnter LLDB " 352 "commands to investigate (type :help for " 353 "assistance.)\n"); 354 break; 355 356 case lldb::eExpressionHitBreakpoint: 357 // Breakpoint was hit, drop into LLDB command interpreter 358 if (useColors) { 359 error_sp->Printf(ANSI_ESCAPE1(ANSI_FG_COLOR_RED)); 360 error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_BOLD)); 361 } 362 output_sp->Printf("Execution stopped at breakpoint. "); 363 if (useColors) 364 error_sp->Printf(ANSI_ESCAPE1(ANSI_CTRL_NORMAL)); 365 output_sp->Printf("Enter LLDB commands to investigate (type help " 366 "for assistance.)\n"); 367 { 368 lldb::IOHandlerSP io_handler_sp(ci.GetIOHandler()); 369 if (io_handler_sp) { 370 io_handler_sp->SetIsDone(false); 371 debugger.PushIOHandler(ci.GetIOHandler()); 372 } 373 } 374 break; 375 376 case lldb::eExpressionTimedOut: 377 error_sp->Printf("error: timeout\n"); 378 if (error.AsCString()) 379 error_sp->Printf("error: %s\n", error.AsCString()); 380 break; 381 case lldb::eExpressionResultUnavailable: 382 // Shoulnd't happen??? 383 error_sp->Printf("error: could not fetch result -- %s\n", 384 error.AsCString()); 385 break; 386 case lldb::eExpressionStoppedForDebug: 387 // Shoulnd't happen??? 388 error_sp->Printf("error: stopped for debug -- %s\n", 389 error.AsCString()); 390 break; 391 } 392 } 393 394 if (add_to_code) { 395 const uint32_t new_default_line = m_code.GetSize() + 1; 396 397 m_code.SplitIntoLines(code); 398 399 // Update our code on disk 400 if (!m_repl_source_path.empty()) { 401 lldb_private::File file; 402 FileSystem::Instance().Open(file, FileSpec(m_repl_source_path), 403 File::eOpenOptionWrite | 404 File::eOpenOptionTruncate | 405 File::eOpenOptionCanCreate, 406 lldb::eFilePermissionsFileDefault); 407 std::string code(m_code.CopyList()); 408 code.append(1, '\n'); 409 size_t bytes_written = code.size(); 410 file.Write(code.c_str(), bytes_written); 411 file.Close(); 412 413 // Now set the default file and line to the REPL source file 414 m_target.GetSourceManager().SetDefaultFileAndLine( 415 FileSpec(m_repl_source_path), new_default_line); 416 } 417 static_cast<IOHandlerEditline &>(io_handler) 418 .SetBaseLineNumber(m_code.GetSize() + 1); 419 } 420 if (extra_line) { 421 fprintf(output_sp->GetFile().GetStream(), "\n"); 422 } 423 } 424 } 425 426 // Don't complain about the REPL process going away if we are in the 427 // process of quitting. 428 if (!did_quit && (!process_sp || !process_sp->IsAlive())) { 429 error_sp->Printf( 430 "error: REPL process is no longer alive, exiting REPL\n"); 431 io_handler.SetIsDone(true); 432 } 433 } 434 } 435 436 int REPL::IOHandlerComplete(IOHandler &io_handler, const char *current_line, 437 const char *cursor, const char *last_char, 438 int skip_first_n_matches, int max_matches, 439 StringList &matches, StringList &descriptions) { 440 matches.Clear(); 441 442 llvm::StringRef line(current_line, cursor - current_line); 443 444 // Complete an LLDB command if the first character is a colon... 445 if (!line.empty() && line[0] == ':') { 446 Debugger &debugger = m_target.GetDebugger(); 447 448 // auto complete LLDB commands 449 const char *lldb_current_line = line.substr(1).data(); 450 return debugger.GetCommandInterpreter().HandleCompletion( 451 lldb_current_line, cursor, last_char, skip_first_n_matches, max_matches, 452 matches, descriptions); 453 } 454 455 // Strip spaces from the line and see if we had only spaces 456 line = line.ltrim(); 457 if (line.empty()) { 458 // Only spaces on this line, so just indent 459 matches.AppendString(m_indent_str); 460 return 1; 461 } 462 463 std::string current_code; 464 current_code.append(m_code.CopyList()); 465 466 IOHandlerEditline &editline = static_cast<IOHandlerEditline &>(io_handler); 467 const StringList *current_lines = editline.GetCurrentLines(); 468 if (current_lines) { 469 const uint32_t current_line_idx = editline.GetCurrentLineIndex(); 470 471 if (current_line_idx < current_lines->GetSize()) { 472 for (uint32_t i = 0; i < current_line_idx; ++i) { 473 const char *line_cstr = current_lines->GetStringAtIndex(i); 474 if (line_cstr) { 475 current_code.append("\n"); 476 current_code.append(line_cstr); 477 } 478 } 479 } 480 } 481 482 if (cursor > current_line) { 483 current_code.append("\n"); 484 current_code.append(current_line, cursor - current_line); 485 } 486 487 return CompleteCode(current_code, matches); 488 } 489 490 bool QuitCommandOverrideCallback(void *baton, const char **argv) { 491 Target *target = (Target *)baton; 492 lldb::ProcessSP process_sp(target->GetProcessSP()); 493 if (process_sp) { 494 process_sp->Destroy(false); 495 process_sp->GetTarget().GetDebugger().ClearIOHandlers(); 496 } 497 return false; 498 } 499 500 Status REPL::RunLoop() { 501 Status error; 502 503 error = DoInitialization(); 504 m_repl_source_path = GetSourcePath(); 505 506 if (!error.Success()) 507 return error; 508 509 Debugger &debugger = m_target.GetDebugger(); 510 511 lldb::IOHandlerSP io_handler_sp(GetIOHandler()); 512 513 FileSpec save_default_file; 514 uint32_t save_default_line = 0; 515 516 if (!m_repl_source_path.empty()) { 517 // Save the current default file and line 518 m_target.GetSourceManager().GetDefaultFileAndLine(save_default_file, 519 save_default_line); 520 } 521 522 debugger.PushIOHandler(io_handler_sp); 523 524 // Check if we are in dedicated REPL mode where LLDB was start with the "-- 525 // repl" option from the command line. Currently we know this by checking if 526 // the debugger already has a IOHandler thread. 527 if (!debugger.HasIOHandlerThread()) { 528 // The debugger doesn't have an existing IOHandler thread, so this must be 529 // dedicated REPL mode... 530 m_dedicated_repl_mode = true; 531 debugger.StartIOHandlerThread(); 532 llvm::StringRef command_name_str("quit"); 533 CommandObject *cmd_obj = 534 debugger.GetCommandInterpreter().GetCommandObjectForCommand( 535 command_name_str); 536 if (cmd_obj) { 537 assert(command_name_str.empty()); 538 cmd_obj->SetOverrideCallback(QuitCommandOverrideCallback, &m_target); 539 } 540 } 541 542 // Wait for the REPL command interpreter to get popped 543 io_handler_sp->WaitForPop(); 544 545 if (m_dedicated_repl_mode) { 546 // If we were in dedicated REPL mode we would have started the IOHandler 547 // thread, and we should kill our process 548 lldb::ProcessSP process_sp = m_target.GetProcessSP(); 549 if (process_sp && process_sp->IsAlive()) 550 process_sp->Destroy(false); 551 552 // Wait for the IO handler thread to exit (TODO: don't do this if the IO 553 // handler thread already exists...) 554 debugger.JoinIOHandlerThread(); 555 } 556 557 // Restore the default file and line 558 if (save_default_file && save_default_line != 0) 559 m_target.GetSourceManager().SetDefaultFileAndLine(save_default_file, 560 save_default_line); 561 return error; 562 } 563