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"
20#include "lldb/Target/Process.h"
22#include "lldb/Target/Target.h"
23#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 static DiagnosticsProperties g_settings;
73 return g_settings;
74}
75
76static constexpr size_t g_num_log_messages = 100;
77
79 lldbassert(!InstanceImpl() && "Already initialized.");
80 InstanceImpl().emplace();
81}
82
84 lldbassert(InstanceImpl() && "Already terminated.");
85 InstanceImpl().reset();
86}
87
88bool Diagnostics::Enabled() { return InstanceImpl().operator bool(); }
89
90std::optional<Diagnostics> &Diagnostics::InstanceImpl() {
91 static std::optional<Diagnostics> g_diagnostics;
92 return g_diagnostics;
93}
94
96
98
100
101bool Diagnostics::Dump(raw_ostream &stream) {
102 Expected<FileSpec> diagnostics_dir = CreateUniqueDirectory();
103 if (!diagnostics_dir) {
104 stream << "unable to create diagnostic dir: "
105 << toString(diagnostics_dir.takeError()) << '\n';
106 return false;
107 }
108
109 return Dump(stream, *diagnostics_dir);
110}
111
112bool Diagnostics::Dump(raw_ostream &stream, const FileSpec &dir) {
113 stream << "LLDB diagnostics will be written to " << dir.GetPath() << "\n";
114 stream << "Please include the directory content when filing a bug report\n";
115
116 if (Error error = Create(dir)) {
117 stream << toString(std::move(error)) << '\n';
118 return false;
119 }
120
121 return true;
122}
123
124llvm::Expected<FileSpec> Diagnostics::CreateUniqueDirectory() {
125 SmallString<128> diagnostics_dir;
126 std::error_code ec =
127 sys::fs::createUniqueDirectory("diagnostics", diagnostics_dir);
128 if (ec)
129 return errorCodeToError(ec);
130 return FileSpec(diagnostics_dir.str());
131}
132
133Error Diagnostics::Create(const FileSpec &dir) {
134 if (Error err = DumpDiangosticsLog(dir))
135 return err;
136
137 return Error::success();
138}
139
140llvm::Error Diagnostics::DumpDiangosticsLog(const FileSpec &dir) const {
141 FileSpec log_file = dir.CopyByAppendingPathComponent("diagnostics.log");
142 std::error_code ec;
143 llvm::raw_fd_ostream stream(log_file.GetPath(), ec, llvm::sys::fs::OF_None);
144 if (ec)
145 return errorCodeToError(ec);
146 m_log_handler.Dump(stream);
147 return Error::success();
148}
149
150void Diagnostics::Record(llvm::StringRef message) {
151 m_log_handler.Emit(message);
152}
153
156 std::lock_guard<std::mutex> guard(m_artifact_providers_mutex);
158 m_artifact_providers.push_back({id, std::move(name), std::move(provider)});
159 return id;
160}
161
163 std::lock_guard<std::mutex> guard(m_artifact_providers_mutex);
164 llvm::erase_if(m_artifact_providers,
165 [id](const ArtifactProviderEntry &e) { return e.id == id; });
166}
167
168// Write a single artifact into the bundle and, on success, record its name in
169// \p files. Best-effort: a write failure leaves the file out of the list, so a
170// missing artifact stays visible. The file is made owner-only because the
171// bundle can contain paths, argv, and command history.
172static void WriteArtifact(const FileSpec &dir, llvm::StringRef name,
173 llvm::StringRef content,
174 std::vector<std::string> &files) {
175 FileSpec file = dir.CopyByAppendingPathComponent(name);
176 std::error_code ec;
177 llvm::raw_fd_ostream os(file.GetPath(), ec, llvm::sys::fs::OF_Text);
178 if (ec)
179 return;
180 os << content;
181 os.flush();
182 llvm::sys::fs::setPermissions(file.GetPath(), llvm::sys::fs::owner_read |
183 llvm::sys::fs::owner_write);
184 files.push_back(name.str());
185}
186
187// Copy a file into the bundle, best-effort like WriteArtifact. The basename is
188// disambiguated because an executable and its symbol file can share one (a
189// Mach-O and the DWARF binary inside its .dSYM), which would otherwise clobber.
190static void CopyBinary(const FileSpec &src, const FileSpec &dir,
191 std::vector<std::string> &files) {
192 if (!src || !FileSystem::Instance().Exists(src))
193 return;
194
195 std::string name = src.GetFilename().str();
197 for (unsigned i = 1; FileSystem::Instance().Exists(dst); ++i) {
198 name = formatv("{0}.{1}", src.GetFilename(), i).str();
199 dst = dir.CopyByAppendingPathComponent(name);
200 }
201
202 if (llvm::sys::fs::copy_file(src.GetPath(), dst.GetPath()))
203 return;
204 llvm::sys::fs::setPermissions(dst.GetPath(), llvm::sys::fs::owner_read |
205 llvm::sys::fs::owner_write);
206 files.push_back(std::move(name));
207}
208
209// Run a command through the interpreter and return its combined output and
210// error text, for inclusion as a snapshot in the bundle.
211static std::string CaptureCommand(Debugger &debugger, llvm::StringRef command) {
212 CommandReturnObject result(/*colors=*/false);
213 debugger.GetCommandInterpreter().HandleCommand(command.str().c_str(),
214 eLazyBoolNo, result);
215 return (result.GetOutputString() + result.GetErrorString()).str();
216}
217
218namespace {
219/// The execution context a triage command needs before it is worth running.
220enum class Requires { Always, Target, Process, Frame };
221
222struct TriageCommand {
223 llvm::StringRef command;
224 Requires requirement;
225};
226} // namespace
227
228// The commands a triager runs first, captured into the bundle. Add a row to
229// extend the snapshot. The requirement keeps a command from emitting a spurious
230// "no process" error when its context is absent.
231static constexpr TriageCommand g_triage_commands[] = {
232 {"target list", Requires::Always},
233 {"image list", Requires::Target},
234 {"process status", Requires::Process},
235 {"thread list", Requires::Process},
236 {"thread backtrace all", Requires::Process},
237 {"image lookup -va $pc", Requires::Frame},
238 {"register read", Requires::Frame},
239 {"frame variable", Requires::Frame},
240};
241
242static bool Available(Requires requirement, const ExecutionContext &exe_ctx) {
243 switch (requirement) {
244 case Requires::Always:
245 return true;
246 case Requires::Target:
247 return exe_ctx.GetTargetPtr() != nullptr;
248 case Requires::Process:
249 return exe_ctx.GetProcessPtr() != nullptr;
250 case Requires::Frame:
251 return exe_ctx.GetFramePtr() != nullptr;
252 }
253 llvm_unreachable("unhandled Requires");
254}
255
256llvm::Expected<Diagnostics::Report>
258 const FileSpec &dir) {
259 // The bundle holds potentially sensitive data (paths, argv, command history),
260 // so restrict the directory to the owner before writing anything into it.
261 llvm::sys::fs::setPermissions(dir.GetPath(), llvm::sys::fs::owner_read |
262 llvm::sys::fs::owner_write |
263 llvm::sys::fs::owner_exe);
264
265 Report report;
266 report.attachments.directory = dir.GetPath();
267 CollectLogs(debugger, dir, report.attachments.files);
268 CollectStatistics(debugger, exe_ctx, dir, report.attachments.files);
269 CollectCommands(debugger, exe_ctx, dir, report.attachments.files);
270 if (GetGlobalProperties().GetCollectBinaries())
271 CollectBinaries(exe_ctx, dir, report.attachments.files);
273
275 report.os = GetHostDescription(exe_ctx);
276 report.invocation = GetInvocation();
277 return report;
278}
279
280void Diagnostics::CollectLogs(Debugger &debugger, const FileSpec &dir,
281 std::vector<std::string> &files) {
282 // The always-on diagnostic log.
283 if (Error error = Create(dir))
284 consumeError(std::move(error));
285 else
286 files.push_back("diagnostics.log");
287
288 // This debugger's file-backed logs.
289 for (std::string &name : debugger.CopyLogFilesToDirectory(dir))
290 files.push_back(std::move(name));
291}
292
294 const ExecutionContext &exe_ctx,
295 const FileSpec &dir,
296 std::vector<std::string> &files) {
297 StatisticsOptions options;
298 json::Value stats = DebuggerStats::ReportStatistics(
299 debugger, exe_ctx.GetTargetPtr(), options);
300 std::string str;
301 raw_string_ostream os(str);
302 os << formatv("{0:2}", stats);
303 WriteArtifact(dir, "statistics.json", str, files);
304}
305
307 const ExecutionContext &exe_ctx,
308 const FileSpec &dir,
309 std::vector<std::string> &files) {
310 std::string snapshot;
311 for (const TriageCommand &tc : g_triage_commands) {
312 if (!Available(tc.requirement, exe_ctx))
313 continue;
314 snapshot += formatv("=== {0} ===\n", tc.command).str();
315 snapshot += CaptureCommand(debugger, tc.command);
316 snapshot += "\n\n";
317 }
318 WriteArtifact(dir, "commands.txt", snapshot, files);
319}
320
322 const FileSpec &dir,
323 std::vector<std::string> &files) {
324 if (Target *target = exe_ctx.GetTargetPtr()) {
325 if (Module *exe = target->GetExecutableModulePointer()) {
326 CopyBinary(exe->GetFileSpec(), dir, files);
327 // Skip when symbols are inline: the symbol file is then the executable.
328 if (exe->GetSymbolFileFileSpec() != exe->GetFileSpec())
329 CopyBinary(exe->GetSymbolFileFileSpec(), dir, files);
330 }
331 }
332
333 if (Process *process = exe_ctx.GetProcessPtr())
334 CopyBinary(process->GetCoreFile(), dir, files);
335}
336
338 std::vector<std::string> &files) {
339 // Snapshot under the lock, then run providers without it: a provider can be
340 // slow and must not block registration or removal.
341 std::vector<ArtifactProviderEntry> providers;
342 {
343 std::lock_guard<std::mutex> guard(m_artifact_providers_mutex);
344 providers = m_artifact_providers;
345 }
346 for (const ArtifactProviderEntry &entry : providers)
347 WriteArtifact(dir, entry.name, entry.provider(), files);
348}
349
351 std::string os = HostInfo::GetTargetTriple().str();
352 Target *target = exe_ctx.GetTargetPtr();
353 if (!target)
354 return os;
355 PlatformSP platform_sp = target->GetPlatform();
356 if (!platform_sp)
357 return os;
358
359 os += formatv(" platform={0}", platform_sp->GetName()).str();
360 VersionTuple version = platform_sp->GetOSVersion();
361 if (!version.empty())
362 os += " os=" + version.getAsString();
363 if (std::optional<std::string> build = platform_sp->GetOSBuildString())
364 os += " build=" + *build;
365 return os;
366}
367
369 // libLLDB does not store its own argv, so read the invocation from the host
370 // process.
373 return {};
374
375 const Args &args = info.GetArguments();
376 std::string invocation;
377 for (size_t i = 0; i < args.GetArgumentCount(); ++i) {
378 if (i)
379 invocation += ' ';
380 invocation += args.GetArgumentAtIndex(i);
381 }
382 return invocation;
383}
384
385llvm::json::Value lldb_private::toJSON(const Diagnostics::Report &report) {
386 json::Object obj{
387 {"version", report.version},
388 {"os", report.os},
389 };
390 if (!report.invocation.empty())
391 obj["invocation"] = report.invocation;
392 obj["attachments"] = json::Object{
393 {"directory", report.attachments.directory},
394 {"files", json::Array(report.attachments.files)},
395 };
396 return obj;
397}
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:100
CommandInterpreter & GetCommandInterpreter()
Definition Debugger.h:182
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:97
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:57
FileSpec CopyByAppendingPathComponent(llvm::StringRef component) const
Definition FileSpec.cpp:423
llvm::StringRef GetFilename() const
Filename string const get accessor.
Definition FileSpec.h:249
size_t GetPath(char *path, size_t max_path_length, bool denormalize=true) const
Extract the full path to the file.
Definition FileSpec.cpp:376
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
A class that describes an executable image and its associated object and symbol files.
Definition Module.h:91
A plug-in interface definition class for debugging a process.
Definition Process.h:359
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:1975
A class that represents a running process on the host machine.
const char * GetVersion()
Retrieves a string representing the complete LLDB version, which includes the lldb version number,...
Definition Version.cpp:38
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:56
The state a triager needs to make sense of a bug report.
Definition Diagnostics.h:63