1 | //===-- LLDBUtils.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 "LLDBUtils.h" |
10 | #include "JSONUtils.h" |
11 | #include "lldb/API/SBCommandInterpreter.h" |
12 | #include "lldb/API/SBCommandReturnObject.h" |
13 | #include "lldb/API/SBDebugger.h" |
14 | #include "lldb/API/SBFrame.h" |
15 | #include "lldb/API/SBStringList.h" |
16 | #include "lldb/API/SBStructuredData.h" |
17 | #include "lldb/API/SBThread.h" |
18 | #include "lldb/lldb-enumerations.h" |
19 | #include "llvm/ADT/ArrayRef.h" |
20 | #include "llvm/Support/JSON.h" |
21 | #include "llvm/Support/raw_ostream.h" |
22 | |
23 | #include <cstring> |
24 | #include <mutex> |
25 | #include <system_error> |
26 | |
27 | namespace lldb_dap { |
28 | |
29 | bool RunLLDBCommands(lldb::SBDebugger &debugger, llvm::StringRef prefix, |
30 | const llvm::ArrayRef<std::string> &commands, |
31 | llvm::raw_ostream &strm, bool parse_command_directives, |
32 | bool echo_commands) { |
33 | if (commands.empty()) |
34 | return true; |
35 | |
36 | bool did_print_prefix = false; |
37 | |
38 | // We only need the prompt when echoing commands. |
39 | std::string prompt_string; |
40 | if (echo_commands) { |
41 | prompt_string = "(lldb) " ; |
42 | |
43 | // Get the current prompt from settings. |
44 | if (const lldb::SBStructuredData prompt = debugger.GetSetting(setting: "prompt" )) { |
45 | const size_t prompt_length = prompt.GetStringValue(dst: nullptr, dst_len: 0); |
46 | |
47 | if (prompt_length != 0) { |
48 | prompt_string.resize(n: prompt_length + 1); |
49 | prompt.GetStringValue(dst: prompt_string.data(), dst_len: prompt_string.length()); |
50 | } |
51 | } |
52 | } |
53 | |
54 | lldb::SBCommandInterpreter interp = debugger.GetCommandInterpreter(); |
55 | for (llvm::StringRef command : commands) { |
56 | lldb::SBCommandReturnObject result; |
57 | bool quiet_on_success = false; |
58 | bool check_error = false; |
59 | |
60 | while (parse_command_directives) { |
61 | if (command.starts_with(Prefix: "?" )) { |
62 | command = command.drop_front(); |
63 | quiet_on_success = true; |
64 | } else if (command.starts_with(Prefix: "!" )) { |
65 | command = command.drop_front(); |
66 | check_error = true; |
67 | } else { |
68 | break; |
69 | } |
70 | } |
71 | |
72 | { |
73 | // Prevent simultaneous calls to HandleCommand, e.g. EventThreadFunction |
74 | // may asynchronously call RunExitCommands when we are already calling |
75 | // RunTerminateCommands. |
76 | static std::mutex handle_command_mutex; |
77 | std::lock_guard<std::mutex> locker(handle_command_mutex); |
78 | interp.HandleCommand(command_line: command.str().c_str(), result, |
79 | /*add_to_history=*/true); |
80 | } |
81 | |
82 | const bool got_error = !result.Succeeded(); |
83 | // The if statement below is assuming we always print out `!` prefixed |
84 | // lines. The only time we don't print is when we have `quiet_on_success == |
85 | // true` and we don't have an error. |
86 | if (quiet_on_success ? got_error : true) { |
87 | if (!did_print_prefix && !prefix.empty()) { |
88 | strm << prefix << "\n" ; |
89 | did_print_prefix = true; |
90 | } |
91 | |
92 | if (echo_commands) |
93 | strm << prompt_string.c_str() << command << '\n'; |
94 | |
95 | auto output_len = result.GetOutputSize(); |
96 | if (output_len) { |
97 | const char *output = result.GetOutput(); |
98 | strm << output; |
99 | } |
100 | auto error_len = result.GetErrorSize(); |
101 | if (error_len) { |
102 | const char *error = result.GetError(); |
103 | strm << error; |
104 | } |
105 | } |
106 | if (check_error && got_error) |
107 | return false; // Stop running commands. |
108 | } |
109 | return true; |
110 | } |
111 | |
112 | std::string RunLLDBCommands(lldb::SBDebugger &debugger, llvm::StringRef prefix, |
113 | const llvm::ArrayRef<std::string> &commands, |
114 | bool &required_command_failed, |
115 | bool parse_command_directives, bool echo_commands) { |
116 | required_command_failed = false; |
117 | std::string s; |
118 | llvm::raw_string_ostream strm(s); |
119 | required_command_failed = |
120 | !RunLLDBCommands(debugger, prefix, commands, strm, |
121 | parse_command_directives, echo_commands); |
122 | return s; |
123 | } |
124 | |
125 | bool ThreadHasStopReason(lldb::SBThread &thread) { |
126 | switch (thread.GetStopReason()) { |
127 | case lldb::eStopReasonTrace: |
128 | case lldb::eStopReasonPlanComplete: |
129 | case lldb::eStopReasonBreakpoint: |
130 | case lldb::eStopReasonWatchpoint: |
131 | case lldb::eStopReasonInstrumentation: |
132 | case lldb::eStopReasonSignal: |
133 | case lldb::eStopReasonException: |
134 | case lldb::eStopReasonExec: |
135 | case lldb::eStopReasonProcessorTrace: |
136 | case lldb::eStopReasonFork: |
137 | case lldb::eStopReasonVFork: |
138 | case lldb::eStopReasonVForkDone: |
139 | case lldb::eStopReasonInterrupt: |
140 | case lldb::eStopReasonHistoryBoundary: |
141 | return true; |
142 | case lldb::eStopReasonThreadExiting: |
143 | case lldb::eStopReasonInvalid: |
144 | case lldb::eStopReasonNone: |
145 | break; |
146 | } |
147 | return false; |
148 | } |
149 | |
150 | static uint32_t constexpr THREAD_INDEX_SHIFT = 19; |
151 | |
152 | uint32_t GetLLDBThreadIndexID(uint64_t dap_frame_id) { |
153 | return dap_frame_id >> THREAD_INDEX_SHIFT; |
154 | } |
155 | |
156 | uint32_t GetLLDBFrameID(uint64_t dap_frame_id) { |
157 | return dap_frame_id & ((1u << THREAD_INDEX_SHIFT) - 1); |
158 | } |
159 | |
160 | int64_t MakeDAPFrameID(lldb::SBFrame &frame) { |
161 | return ((int64_t)frame.GetThread().GetIndexID() << THREAD_INDEX_SHIFT) | |
162 | frame.GetFrameID(); |
163 | } |
164 | |
165 | lldb::SBEnvironment |
166 | GetEnvironmentFromArguments(const llvm::json::Object &arguments) { |
167 | lldb::SBEnvironment envs{}; |
168 | constexpr llvm::StringRef env_key = "env" ; |
169 | const llvm::json::Value *raw_json_env = arguments.get(K: env_key); |
170 | |
171 | if (!raw_json_env) |
172 | return envs; |
173 | |
174 | if (raw_json_env->kind() == llvm::json::Value::Object) { |
175 | auto env_map = GetStringMap(obj: arguments, key: env_key); |
176 | for (const auto &[key, value] : env_map) |
177 | envs.Set(name: key.c_str(), value: value.c_str(), overwrite: true); |
178 | |
179 | } else if (raw_json_env->kind() == llvm::json::Value::Array) { |
180 | const auto envs_strings = GetStrings(obj: &arguments, key: env_key); |
181 | lldb::SBStringList entries{}; |
182 | for (const auto &env : envs_strings) |
183 | entries.AppendString(str: env.c_str()); |
184 | |
185 | envs.SetEntries(entries, append: true); |
186 | } |
187 | return envs; |
188 | } |
189 | |
190 | lldb::StopDisassemblyType |
191 | GetStopDisassemblyDisplay(lldb::SBDebugger &debugger) { |
192 | lldb::StopDisassemblyType result = |
193 | lldb::StopDisassemblyType::eStopDisassemblyTypeNoDebugInfo; |
194 | lldb::SBStructuredData string_result = |
195 | debugger.GetSetting(setting: "stop-disassembly-display" ); |
196 | const size_t result_length = string_result.GetStringValue(dst: nullptr, dst_len: 0); |
197 | if (result_length > 0) { |
198 | std::string result_string(result_length, '\0'); |
199 | string_result.GetStringValue(dst: result_string.data(), dst_len: result_length + 1); |
200 | |
201 | result = |
202 | llvm::StringSwitch<lldb::StopDisassemblyType>(result_string) |
203 | .Case(S: "never" , Value: lldb::StopDisassemblyType::eStopDisassemblyTypeNever) |
204 | .Case(S: "always" , |
205 | Value: lldb::StopDisassemblyType::eStopDisassemblyTypeAlways) |
206 | .Case(S: "no-source" , |
207 | Value: lldb::StopDisassemblyType::eStopDisassemblyTypeNoSource) |
208 | .Case(S: "no-debuginfo" , |
209 | Value: lldb::StopDisassemblyType::eStopDisassemblyTypeNoDebugInfo) |
210 | .Default( |
211 | Value: lldb::StopDisassemblyType::eStopDisassemblyTypeNoDebugInfo); |
212 | } |
213 | |
214 | return result; |
215 | } |
216 | |
217 | llvm::Error ToError(const lldb::SBError &error) { |
218 | if (error.Success()) |
219 | return llvm::Error::success(); |
220 | |
221 | return llvm::createStringError( |
222 | EC: std::error_code(error.GetError(), std::generic_category()), |
223 | S: error.GetCString()); |
224 | } |
225 | |
226 | std::string GetStringValue(const lldb::SBStructuredData &data) { |
227 | if (!data.IsValid()) |
228 | return "" ; |
229 | |
230 | const size_t str_length = data.GetStringValue(dst: nullptr, dst_len: 0); |
231 | if (!str_length) |
232 | return "" ; |
233 | |
234 | std::string str(str_length, 0); |
235 | data.GetStringValue(dst: str.data(), dst_len: str_length + 1); |
236 | return str; |
237 | } |
238 | |
239 | ScopeSyncMode::ScopeSyncMode(lldb::SBDebugger &debugger) |
240 | : m_debugger(debugger), m_async(m_debugger.GetAsync()) { |
241 | m_debugger.SetAsync(false); |
242 | } |
243 | |
244 | ScopeSyncMode::~ScopeSyncMode() { m_debugger.SetAsync(m_async); } |
245 | |
246 | std::string GetSBFileSpecPath(const lldb::SBFileSpec &file_spec) { |
247 | const auto directory_length = ::strlen(s: file_spec.GetDirectory()); |
248 | const auto file_name_length = ::strlen(s: file_spec.GetFilename()); |
249 | |
250 | std::string path(directory_length + file_name_length + 1, '\0'); |
251 | file_spec.GetPath(dst_path: path.data(), dst_len: path.length() + 1); |
252 | return path; |
253 | } |
254 | |
255 | lldb::SBLineEntry GetLineEntryForAddress(lldb::SBTarget &target, |
256 | const lldb::SBAddress &address) { |
257 | lldb::SBSymbolContext sc = target.ResolveSymbolContextForAddress( |
258 | addr: address, resolve_scope: lldb::eSymbolContextLineEntry); |
259 | return sc.GetLineEntry(); |
260 | } |
261 | |
262 | } // namespace lldb_dap |
263 | |