LLDB mainline
Diagnostics.cpp
Go to the documentation of this file.
1//===-- Diagnostics.cpp ---------------------------------------------------===//
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
10#include "lldb/Core/Debugger.h"
11#include "lldb/Core/Module.h"
13#include "lldb/Host/Host.h"
14#include "lldb/Host/HostInfo.h"
21#include "lldb/Target/Process.h"
23#include "lldb/Target/Target.h"
24#include "lldb/Utility/Args.h"
27
28#include "llvm/ADT/STLExtras.h"
29#include "llvm/Support/Error.h"
30#include "llvm/Support/FileSystem.h"
31#include "llvm/Support/FormatVariadic.h"
32#include "llvm/Support/JSON.h"
33#include "llvm/Support/raw_ostream.h"
34
35#include <mutex>
36#include <optional>
37#include <string>
38#include <vector>
39
40using namespace lldb_private;
41using namespace lldb;
42using namespace llvm;
43
44namespace {
45
46#define LLDB_PROPERTIES_diagnostics
47#include "CoreProperties.inc"
48
49enum {
50#define LLDB_PROPERTIES_diagnostics
51#include "CorePropertiesEnum.inc"
52};
53
54} // namespace
55
57 m_collection_sp = std::make_shared<OptionValueProperties>("diagnostics");
58 m_collection_sp->Initialize(g_diagnostics_properties_def);
59}
60
62 const uint32_t idx = ePropertyCollectBinaries;
64 idx, g_diagnostics_properties[idx].default_uint_value != 0);
65}
66
68 return SetPropertyAtIndex(ePropertyCollectBinaries, collect);
69}
70
72 const uint32_t idx = ePropertySuggestBugReport;
74 idx, g_diagnostics_properties[idx].default_uint_value != 0);
75}
76
78 static DiagnosticsProperties g_settings;
79 return g_settings;
80}
81
82static constexpr size_t g_num_log_messages = 100;
83
85 lldbassert(!InstanceImpl() && "Already initialized.");
86 InstanceImpl().emplace();
87}
88
90 lldbassert(InstanceImpl() && "Already terminated.");
91 InstanceImpl().reset();
92}
93
94bool Diagnostics::Enabled() { return InstanceImpl().operator bool(); }
95
96std::optional<Diagnostics> &Diagnostics::InstanceImpl() {
97 static std::optional<Diagnostics> g_diagnostics;
98 return g_diagnostics;
99}
100
102
104
106
107bool Diagnostics::Dump(raw_ostream &stream) {
108 Expected<FileSpec> diagnostics_dir = CreateUniqueDirectory();
109 if (!diagnostics_dir) {
110 stream << "unable to create diagnostic dir: "
111 << toString(diagnostics_dir.takeError()) << '\n';
112 return false;
113 }
114
115 return Dump(stream, *diagnostics_dir);
116}
117
118bool Diagnostics::Dump(raw_ostream &stream, const FileSpec &dir) {
119 stream << "LLDB diagnostics will be written to " << dir.GetPath() << "\n";
120 stream << "Please include the directory content when filing a bug report\n";
121
122 if (Error error = Create(dir)) {
123 stream << toString(std::move(error)) << '\n';
124 return false;
125 }
126
127 return true;
128}
129
130llvm::Expected<FileSpec> Diagnostics::CreateUniqueDirectory() {
131 SmallString<128> diagnostics_dir;
132 std::error_code ec =
133 sys::fs::createUniqueDirectory("diagnostics", diagnostics_dir);
134 if (ec)
135 return errorCodeToError(ec);
136 return FileSpec(diagnostics_dir.str());
137}
138
139Error Diagnostics::Create(const FileSpec &dir) {
140 if (Error err = DumpDiangosticsLog(dir))
141 return err;
142
143 return Error::success();
144}
145
146llvm::Error Diagnostics::DumpDiangosticsLog(const FileSpec &dir) const {
147 FileSpec log_file = dir.CopyByAppendingPathComponent("diagnostics.log");
148 std::error_code ec;
149 llvm::raw_fd_ostream stream(log_file.GetPath(), ec, llvm::sys::fs::OF_None);
150 if (ec)
151 return errorCodeToError(ec);
152 m_log_handler.Dump(stream);
153 return Error::success();
154}
155
156void Diagnostics::Record(llvm::StringRef message) {
157 m_log_handler.Emit(message);
158}
159
162 std::lock_guard<std::mutex> guard(m_artifact_providers_mutex);
164 m_artifact_providers.push_back({id, std::move(name), std::move(provider)});
165 return id;
166}
167
169 std::lock_guard<std::mutex> guard(m_artifact_providers_mutex);
170 llvm::erase_if(m_artifact_providers,
171 [id](const ArtifactProviderEntry &e) { return e.id == id; });
172}
173
174// Write a single artifact into the bundle and, on success, record its name in
175// \p files. Best-effort: a write failure leaves the file out of the list, so a
176// missing artifact stays visible. The file is made owner-only because the
177// bundle can contain paths, argv, and command history.
178static void WriteArtifact(const FileSpec &dir, llvm::StringRef name,
179 llvm::StringRef content,
180 std::vector<std::string> &files) {
181 FileSpec file = dir.CopyByAppendingPathComponent(name);
182 std::error_code ec;
183 llvm::raw_fd_ostream os(file.GetPath(), ec, llvm::sys::fs::OF_Text);
184 if (ec)
185 return;
186 os << content;
187 os.flush();
188 llvm::sys::fs::setPermissions(file.GetPath(), llvm::sys::fs::owner_read |
189 llvm::sys::fs::owner_write);
190 files.push_back(name.str());
191}
192
193// Copy a file into the bundle, best-effort like WriteArtifact. The basename is
194// disambiguated because an executable and its symbol file can share one (a
195// Mach-O and the DWARF binary inside its .dSYM), which would otherwise clobber.
196static void CopyBinary(const FileSpec &src, const FileSpec &dir,
197 std::vector<std::string> &files) {
198 if (!src || !FileSystem::Instance().Exists(src))
199 return;
200
201 std::string name = src.GetFilename().str();
203 for (unsigned i = 1; FileSystem::Instance().Exists(dst); ++i) {
204 name = formatv("{0}.{1}", src.GetFilename(), i).str();
205 dst = dir.CopyByAppendingPathComponent(name);
206 }
207
208 if (llvm::sys::fs::copy_file(src.GetPath(), dst.GetPath()))
209 return;
210 llvm::sys::fs::setPermissions(dst.GetPath(), llvm::sys::fs::owner_read |
211 llvm::sys::fs::owner_write);
212 files.push_back(std::move(name));
213}
214
215// Run a command through the interpreter and return its combined output and
216// error text, for inclusion as a snapshot in the bundle.
217static std::string CaptureCommand(Debugger &debugger, llvm::StringRef command) {
218 CommandReturnObject result(/*colors=*/false);
219 debugger.GetCommandInterpreter().HandleCommand(command.str().c_str(),
220 eLazyBoolNo, result);
221 return (result.GetOutputString() + result.GetErrorString()).str();
222}
223
224namespace {
225/// The execution context a triage command needs before it is worth running.
226enum class Requires { Always, Target, Process, Frame };
227
228struct TriageCommand {
229 llvm::StringRef command;
230 Requires requirement;
231};
232} // namespace
233
234// The commands a triager runs first, captured into the bundle. Add a row to
235// extend the snapshot. The requirement keeps a command from emitting a spurious
236// "no process" error when its context is absent.
237static constexpr TriageCommand g_triage_commands[] = {
238 {"target list", Requires::Always},
239 {"image list", Requires::Target},
240 {"process status", Requires::Process},
241 {"thread list", Requires::Process},
242 {"thread backtrace all", Requires::Process},
243 {"image lookup -va $pc", Requires::Frame},
244 {"register read", Requires::Frame},
245 {"frame variable", Requires::Frame},
246 {"scripting extension list --instances", Requires::Always},
247};
248
249static bool Available(Requires requirement, const ExecutionContext &exe_ctx) {
250 switch (requirement) {
251 case Requires::Always:
252 return true;
253 case Requires::Target:
254 return exe_ctx.GetTargetPtr() != nullptr;
255 case Requires::Process:
256 return exe_ctx.GetProcessPtr() != nullptr;
257 case Requires::Frame:
258 return exe_ctx.GetFramePtr() != nullptr;
259 }
260 llvm_unreachable("unhandled Requires");
261}
262
263llvm::Expected<Diagnostics::Report>
265 const FileSpec &dir) {
266 // The bundle holds potentially sensitive data (paths, argv, command history),
267 // so restrict the directory to the owner before writing anything into it.
268 llvm::sys::fs::setPermissions(dir.GetPath(), llvm::sys::fs::owner_read |
269 llvm::sys::fs::owner_write |
270 llvm::sys::fs::owner_exe);
271
272 Report report;
273 report.attachments.directory = dir.GetPath();
274 CollectLogs(debugger, dir, report.attachments.files);
275 CollectStatistics(debugger, exe_ctx, dir, report.attachments.files);
276 CollectCommands(debugger, exe_ctx, dir, report.attachments.files);
277 if (GetGlobalProperties().GetCollectBinaries())
278 CollectBinaries(exe_ctx, dir, report.attachments.files);
280
282 report.os = GetHostDescription(exe_ctx);
283 report.invocation = GetInvocation();
284 return report;
285}
286
287void Diagnostics::CollectLogs(Debugger &debugger, const FileSpec &dir,
288 std::vector<std::string> &files) {
289 // The always-on diagnostic log.
290 if (Error error = Create(dir))
291 consumeError(std::move(error));
292 else
293 files.push_back("diagnostics.log");
294
295 // This debugger's file-backed logs.
296 for (std::string &name : debugger.CopyLogFilesToDirectory(dir))
297 files.push_back(std::move(name));
298}
299
301 const ExecutionContext &exe_ctx,
302 const FileSpec &dir,
303 std::vector<std::string> &files) {
304 StatisticsOptions options;
305 json::Value stats = DebuggerStats::ReportStatistics(
306 debugger, exe_ctx.GetTargetPtr(), options);
307 std::string str;
308 raw_string_ostream os(str);
309 os << formatv("{0:2}", stats);
310 WriteArtifact(dir, "statistics.json", str, files);
311}
312
314 const ExecutionContext &exe_ctx,
315 const FileSpec &dir,
316 std::vector<std::string> &files) {
317 std::string snapshot;
318 for (const TriageCommand &tc : g_triage_commands) {
319 if (!Available(tc.requirement, exe_ctx))
320 continue;
321 snapshot += formatv("=== {0} ===\n", tc.command).str();
322 snapshot += CaptureCommand(debugger, tc.command);
323 snapshot += "\n\n";
324 }
325 WriteArtifact(dir, "commands.txt", snapshot, files);
326}
327
329 const FileSpec &dir,
330 std::vector<std::string> &files) {
331 if (Target *target = exe_ctx.GetTargetPtr()) {
332 if (Module *exe = target->GetExecutableModulePointer()) {
333 CopyBinary(exe->GetFileSpec(), dir, files);
334 // Skip when symbols are inline: the symbol file is then the executable.
335 if (exe->GetSymbolFileFileSpec() != exe->GetFileSpec())
336 CopyBinary(exe->GetSymbolFileFileSpec(), dir, files);
337 }
338 }
339
340 if (Process *process = exe_ctx.GetProcessPtr())
341 CopyBinary(process->GetCoreFile(), dir, files);
342}
343
345 std::vector<std::string> &files) {
346 // Snapshot under the lock, then run providers without it: a provider can be
347 // slow and must not block registration or removal.
348 std::vector<ArtifactProviderEntry> providers;
349 {
350 std::lock_guard<std::mutex> guard(m_artifact_providers_mutex);
351 providers = m_artifact_providers;
352 }
353 for (const ArtifactProviderEntry &entry : providers)
354 WriteArtifact(dir, entry.name, entry.provider(), files);
355}
356
358 std::string os = HostInfo::GetTargetTriple().str();
359 Target *target = exe_ctx.GetTargetPtr();
360 if (!target)
361 return os;
362 PlatformSP platform_sp = target->GetPlatform();
363 if (!platform_sp)
364 return os;
365
366 os += formatv(" platform={0}", platform_sp->GetName()).str();
367 VersionTuple version = platform_sp->GetOSVersion();
368 if (!version.empty())
369 os += " os=" + version.getAsString();
370 if (std::optional<std::string> build = platform_sp->GetOSBuildString())
371 os += " build=" + *build;
372 return os;
373}
374
376 // libLLDB does not store its own argv, so read the invocation from the host
377 // process.
380 return {};
381
382 const Args &args = info.GetArguments();
383 std::string invocation;
384 for (size_t i = 0; i < args.GetArgumentCount(); ++i) {
385 if (i)
386 invocation += ' ';
387 invocation += args.GetArgumentAtIndex(i);
388 }
389 return invocation;
390}
391
392llvm::json::Value lldb_private::toJSON(const Diagnostics::Report &report) {
393 json::Object obj{
394 {"version", report.version},
395 {"os", report.os},
396 };
397 if (!report.invocation.empty())
398 obj["invocation"] = report.invocation;
399 obj["attachments"] = json::Object{
400 {"directory", report.attachments.directory},
401 {"files", json::Array(report.attachments.files)},
402 };
403 return obj;
404}
static llvm::raw_ostream & error(Stream &strm)
static constexpr size_t g_num_log_messages
static constexpr TriageCommand g_triage_commands[]
static void CopyBinary(const FileSpec &src, const FileSpec &dir, std::vector< std::string > &files)
static bool Available(Requires requirement, const ExecutionContext &exe_ctx)
static std::string CaptureCommand(Debugger &debugger, llvm::StringRef command)
static void WriteArtifact(const FileSpec &dir, llvm::StringRef name, llvm::StringRef content, std::vector< std::string > &files)
#define lldbassert(x)
Definition LLDBAssert.h:16
A command line argument class.
Definition Args.h:33
size_t GetArgumentCount() const
Gets the number of arguments left in this command object.
Definition Args.h:120
const char * GetArgumentAtIndex(size_t idx) const
Gets the null-terminated C string argument pointer for the argument at index idx.
Definition Args.cpp:273
bool HandleCommand(const char *command_line, LazyBool add_to_history, const ExecutionContext &override_context, CommandReturnObject &result)
std::string GetErrorString(bool with_diagnostics=true) const
Return the errors as a string.
llvm::StringRef GetOutputString() const
static llvm::json::Value ReportStatistics(Debugger &debugger, Target *target, const lldb_private::StatisticsOptions &options)
Get metrics associated with one or all targets in a debugger in JSON format.
A class to manage flag bits.
Definition Debugger.h:106
CommandInterpreter & GetCommandInterpreter()
Definition Debugger.h:192
std::vector< std::string > CopyLogFilesToDirectory(const FileSpec &dir)
Copy this debugger's file-backed log files into the given directory, for inclusion in a diagnostics b...
The global diagnostics settings, exposed under diagnostics in the settings hierarchy.
Definition Diagnostics.h:36
std::mutex m_artifact_providers_mutex
RotatingLogHandler m_log_handler
std::vector< ArtifactProviderEntry > m_artifact_providers
bool Dump(llvm::raw_ostream &stream)
Write the diagnostic log into a directory and print a message to the given output stream.
static std::string GetInvocation()
static std::string GetHostDescription(const ExecutionContext &exe_ctx)
Scalars carried in the report rather than written as files.
static void CollectBinaries(const ExecutionContext &exe_ctx, const FileSpec &dir, std::vector< std::string > &files)
ArtifactProviderID AddArtifactProvider(std::string name, ArtifactProvider provider)
Register provider to contribute file name.
void CollectArtifactProviders(const FileSpec &dir, std::vector< std::string > &files)
llvm::Error DumpDiangosticsLog(const FileSpec &dir) const
void CollectLogs(Debugger &debugger, const FileSpec &dir, std::vector< std::string > &files)
Collect the individual parts of the bundle into dir, appending the name of each file to files as it i...
std::function< std::string()> ArtifactProvider
Supplies an artifact's contents on demand.
Definition Diagnostics.h:99
static DiagnosticsProperties & GetGlobalProperties()
void RemoveArtifactProvider(ArtifactProviderID id)
Unregister a provider. Thread-safe.
llvm::Error Create(const FileSpec &dir)
Write the in-memory diagnostic log into the given directory.
void Record(llvm::StringRef message)
Record a diagnostic message into the always-on, in-memory log.
static llvm::Expected< FileSpec > CreateUniqueDirectory()
Create a unique diagnostic directory.
static std::optional< Diagnostics > & InstanceImpl()
llvm::Expected< Report > Collect(Debugger &debugger, const ExecutionContext &exe_ctx, const FileSpec &dir)
Collect a full diagnostics bundle into dir and return its report.
ArtifactProviderID m_next_artifact_provider_id
Registered artifact providers, guarded by the mutex.
static void CollectCommands(Debugger &debugger, const ExecutionContext &exe_ctx, const FileSpec &dir, std::vector< std::string > &files)
static Diagnostics & Instance()
static void CollectStatistics(Debugger &debugger, const ExecutionContext &exe_ctx, const FileSpec &dir, std::vector< std::string > &files)
"lldb/Target/ExecutionContext.h" A class that contains an execution context.
StackFrame * GetFramePtr() const
Returns a pointer to the frame object.
Target * GetTargetPtr() const
Returns a pointer to the target object.
Process * GetProcessPtr() const
Returns a pointer to the process object.
A file utility class.
Definition FileSpec.h:56
FileSpec CopyByAppendingPathComponent(llvm::StringRef component) const
Definition FileSpec.cpp:425
llvm::StringRef GetFilename() const
Filename string const get accessor.
Definition FileSpec.h:248
size_t GetPath(char *path, size_t max_path_length, bool denormalize=true) const
Extract the full path to the file.
Definition FileSpec.cpp:380
bool Exists(const FileSpec &file_spec) const
Returns whether the given file exists.
static FileSystem & Instance()
static lldb::pid_t GetCurrentProcessID()
Get the process ID for the calling process.
static bool GetProcessInfo(lldb::pid_t pid, ProcessInstanceInfo &proc_info)
Definition aix/Host.cpp:211
static std::string GetVersionWithLanguageInfo()
Returns lldb_private::GetVersion(), followed by the GetVersionInfo() of each registered language plug...
Definition Language.cpp:185
A class that describes an executable image and its associated object and symbol files.
Definition Module.h:92
A plug-in interface definition class for debugging a process.
Definition Process.h:369
lldb::OptionValuePropertiesSP m_collection_sp
T GetPropertyAtIndexAs(uint32_t idx, T default_value, const ExecutionContext *exe_ctx=nullptr) const
bool SetPropertyAtIndex(uint32_t idx, T t, const ExecutionContext *exe_ctx=nullptr) const
lldb::PlatformSP GetPlatform()
Definition Target.h:2008
A class that represents a running process on the host machine.
llvm::json::Value toJSON(const Diagnostics::Report &report)
Render a diagnostics report as JSON, for diagnostics dump's terminal output.
std::string toString(FormatterBytecode::OpCodes op)
std::shared_ptr< lldb_private::Platform > PlatformSP
std::vector< std::string > files
Definition Diagnostics.h:58
The state a triager needs to make sense of a bug report.
Definition Diagnostics.h:65