| //===-- LLDBUtils.cpp -------------------------------------------*- 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 |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "LLDBUtils.h" |
| #include "JSONUtils.h" |
| #include "lldb/API/SBCommandInterpreter.h" |
| #include "lldb/API/SBCommandReturnObject.h" |
| #include "lldb/API/SBDebugger.h" |
| #include "lldb/API/SBFrame.h" |
| #include "lldb/API/SBStringList.h" |
| #include "lldb/API/SBStructuredData.h" |
| #include "lldb/API/SBThread.h" |
| #include "lldb/lldb-enumerations.h" |
| #include "llvm/ADT/ArrayRef.h" |
| #include "llvm/Support/JSON.h" |
| #include "llvm/Support/raw_ostream.h" |
| |
| #include <mutex> |
| #include <system_error> |
| |
| namespace lldb_dap { |
| |
| bool RunLLDBCommands(lldb::SBDebugger &debugger, llvm::StringRef prefix, |
| const llvm::ArrayRef<std::string> &commands, |
| llvm::raw_ostream &strm, bool parse_command_directives, |
| bool echo_commands) { |
| if (commands.empty()) |
| return true; |
| |
| bool did_print_prefix = false; |
| |
| // We only need the prompt when echoing commands. |
| std::string prompt_string; |
| if (echo_commands) { |
| prompt_string = "(lldb) "; |
| |
| // Get the current prompt from settings. |
| if (const lldb::SBStructuredData prompt = debugger.GetSetting("prompt")) { |
| const size_t prompt_length = prompt.GetStringValue(nullptr, 0); |
| |
| if (prompt_length != 0) { |
| prompt_string.resize(prompt_length + 1); |
| prompt.GetStringValue(prompt_string.data(), prompt_string.length()); |
| } |
| } |
| } |
| |
| lldb::SBCommandInterpreter interp = debugger.GetCommandInterpreter(); |
| for (llvm::StringRef command : commands) { |
| lldb::SBCommandReturnObject result; |
| bool quiet_on_success = false; |
| bool check_error = false; |
| |
| while (parse_command_directives) { |
| if (command.starts_with("?")) { |
| command = command.drop_front(); |
| quiet_on_success = true; |
| } else if (command.starts_with("!")) { |
| command = command.drop_front(); |
| check_error = true; |
| } else { |
| break; |
| } |
| } |
| |
| { |
| // Prevent simultaneous calls to HandleCommand, e.g. EventThreadFunction |
| // may asynchronously call RunExitCommands when we are already calling |
| // RunTerminateCommands. |
| static std::mutex handle_command_mutex; |
| std::lock_guard<std::mutex> locker(handle_command_mutex); |
| interp.HandleCommand(command.str().c_str(), result, |
| /*add_to_history=*/true); |
| } |
| |
| const bool got_error = !result.Succeeded(); |
| // The if statement below is assuming we always print out `!` prefixed |
| // lines. The only time we don't print is when we have `quiet_on_success == |
| // true` and we don't have an error. |
| if (quiet_on_success ? got_error : true) { |
| if (!did_print_prefix && !prefix.empty()) { |
| strm << prefix << "\n"; |
| did_print_prefix = true; |
| } |
| |
| if (echo_commands) |
| strm << prompt_string.c_str() << command << '\n'; |
| |
| auto output_len = result.GetOutputSize(); |
| if (output_len) { |
| const char *output = result.GetOutput(); |
| strm << output; |
| } |
| auto error_len = result.GetErrorSize(); |
| if (error_len) { |
| const char *error = result.GetError(); |
| strm << error; |
| } |
| } |
| if (check_error && got_error) |
| return false; // Stop running commands. |
| } |
| return true; |
| } |
| |
| std::string RunLLDBCommands(lldb::SBDebugger &debugger, llvm::StringRef prefix, |
| const llvm::ArrayRef<std::string> &commands, |
| bool &required_command_failed, |
| bool parse_command_directives, bool echo_commands) { |
| required_command_failed = false; |
| std::string s; |
| llvm::raw_string_ostream strm(s); |
| required_command_failed = |
| !RunLLDBCommands(debugger, prefix, commands, strm, |
| parse_command_directives, echo_commands); |
| return s; |
| } |
| |
| bool ThreadHasStopReason(lldb::SBThread &thread) { |
| switch (thread.GetStopReason()) { |
| case lldb::eStopReasonTrace: |
| case lldb::eStopReasonPlanComplete: |
| case lldb::eStopReasonBreakpoint: |
| case lldb::eStopReasonWatchpoint: |
| case lldb::eStopReasonInstrumentation: |
| case lldb::eStopReasonSignal: |
| case lldb::eStopReasonException: |
| case lldb::eStopReasonExec: |
| case lldb::eStopReasonProcessorTrace: |
| case lldb::eStopReasonFork: |
| case lldb::eStopReasonVFork: |
| case lldb::eStopReasonVForkDone: |
| case lldb::eStopReasonInterrupt: |
| case lldb::eStopReasonHistoryBoundary: |
| return true; |
| case lldb::eStopReasonThreadExiting: |
| case lldb::eStopReasonInvalid: |
| case lldb::eStopReasonNone: |
| break; |
| } |
| return false; |
| } |
| |
| static uint32_t constexpr THREAD_INDEX_SHIFT = 19; |
| |
| uint32_t GetLLDBThreadIndexID(uint64_t dap_frame_id) { |
| return dap_frame_id >> THREAD_INDEX_SHIFT; |
| } |
| |
| uint32_t GetLLDBFrameID(uint64_t dap_frame_id) { |
| return dap_frame_id & ((1u << THREAD_INDEX_SHIFT) - 1); |
| } |
| |
| int64_t MakeDAPFrameID(lldb::SBFrame &frame) { |
| return ((int64_t)frame.GetThread().GetIndexID() << THREAD_INDEX_SHIFT) | |
| frame.GetFrameID(); |
| } |
| |
| lldb::SBEnvironment |
| GetEnvironmentFromArguments(const llvm::json::Object &arguments) { |
| lldb::SBEnvironment envs{}; |
| constexpr llvm::StringRef env_key = "env"; |
| const llvm::json::Value *raw_json_env = arguments.get(env_key); |
| |
| if (!raw_json_env) |
| return envs; |
| |
| if (raw_json_env->kind() == llvm::json::Value::Object) { |
| auto env_map = GetStringMap(arguments, env_key); |
| for (const auto &[key, value] : env_map) |
| envs.Set(key.c_str(), value.c_str(), true); |
| |
| } else if (raw_json_env->kind() == llvm::json::Value::Array) { |
| const auto envs_strings = GetStrings(&arguments, env_key); |
| lldb::SBStringList entries{}; |
| for (const auto &env : envs_strings) |
| entries.AppendString(env.c_str()); |
| |
| envs.SetEntries(entries, true); |
| } |
| return envs; |
| } |
| |
| lldb::StopDisassemblyType |
| GetStopDisassemblyDisplay(lldb::SBDebugger &debugger) { |
| lldb::StopDisassemblyType result = |
| lldb::StopDisassemblyType::eStopDisassemblyTypeNoDebugInfo; |
| lldb::SBStructuredData string_result = |
| debugger.GetSetting("stop-disassembly-display"); |
| const size_t result_length = string_result.GetStringValue(nullptr, 0); |
| if (result_length > 0) { |
| std::string result_string(result_length, '\0'); |
| string_result.GetStringValue(result_string.data(), result_length + 1); |
| |
| result = |
| llvm::StringSwitch<lldb::StopDisassemblyType>(result_string) |
| .Case("never", lldb::StopDisassemblyType::eStopDisassemblyTypeNever) |
| .Case("always", |
| lldb::StopDisassemblyType::eStopDisassemblyTypeAlways) |
| .Case("no-source", |
| lldb::StopDisassemblyType::eStopDisassemblyTypeNoSource) |
| .Case("no-debuginfo", |
| lldb::StopDisassemblyType::eStopDisassemblyTypeNoDebugInfo) |
| .Default( |
| lldb::StopDisassemblyType::eStopDisassemblyTypeNoDebugInfo); |
| } |
| |
| return result; |
| } |
| |
| llvm::Error ToError(const lldb::SBError &error) { |
| if (error.Success()) |
| return llvm::Error::success(); |
| |
| return llvm::createStringError( |
| std::error_code(error.GetError(), std::generic_category()), |
| error.GetCString()); |
| } |
| |
| std::string GetStringValue(const lldb::SBStructuredData &data) { |
| if (!data.IsValid()) |
| return ""; |
| |
| const size_t str_length = data.GetStringValue(nullptr, 0); |
| if (!str_length) |
| return ""; |
| |
| std::string str(str_length, 0); |
| data.GetStringValue(str.data(), str_length + 1); |
| return str; |
| } |
| |
| ScopeSyncMode::ScopeSyncMode(lldb::SBDebugger &debugger) |
| : m_debugger(debugger), m_async(m_debugger.GetAsync()) { |
| m_debugger.SetAsync(false); |
| } |
| |
| ScopeSyncMode::~ScopeSyncMode() { m_debugger.SetAsync(m_async); } |
| |
| } // namespace lldb_dap |