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/Host/Host.h"
12#include "lldb/Host/HostInfo.h"
18#include "lldb/Target/Target.h"
19#include "lldb/Utility/Args.h"
23
24#include "llvm/Support/Error.h"
25#include "llvm/Support/FileSystem.h"
26#include "llvm/Support/FormatVariadic.h"
27#include "llvm/Support/JSON.h"
28#include "llvm/Support/raw_ostream.h"
29
30#include <optional>
31#include <string>
32#include <vector>
33
34using namespace lldb_private;
35using namespace lldb;
36using namespace llvm;
37
38static constexpr size_t g_num_log_messages = 100;
39
41 lldbassert(!InstanceImpl() && "Already initialized.");
42 InstanceImpl().emplace();
43}
44
46 lldbassert(InstanceImpl() && "Already terminated.");
47 InstanceImpl().reset();
48}
49
50bool Diagnostics::Enabled() { return InstanceImpl().operator bool(); }
51
52std::optional<Diagnostics> &Diagnostics::InstanceImpl() {
53 static std::optional<Diagnostics> g_diagnostics;
54 return g_diagnostics;
55}
56
58
60
62
63bool Diagnostics::Dump(raw_ostream &stream) {
64 Expected<FileSpec> diagnostics_dir = CreateUniqueDirectory();
65 if (!diagnostics_dir) {
66 stream << "unable to create diagnostic dir: "
67 << toString(diagnostics_dir.takeError()) << '\n';
68 return false;
69 }
70
71 return Dump(stream, *diagnostics_dir);
72}
73
74bool Diagnostics::Dump(raw_ostream &stream, const FileSpec &dir) {
75 stream << "LLDB diagnostics will be written to " << dir.GetPath() << "\n";
76 stream << "Please include the directory content when filing a bug report\n";
77
78 if (Error error = Create(dir)) {
79 stream << toString(std::move(error)) << '\n';
80 return false;
81 }
82
83 return true;
84}
85
86llvm::Expected<FileSpec> Diagnostics::CreateUniqueDirectory() {
87 SmallString<128> diagnostics_dir;
88 std::error_code ec =
89 sys::fs::createUniqueDirectory("diagnostics", diagnostics_dir);
90 if (ec)
91 return errorCodeToError(ec);
92 return FileSpec(diagnostics_dir.str());
93}
94
95Error Diagnostics::Create(const FileSpec &dir) {
96 if (Error err = DumpDiangosticsLog(dir))
97 return err;
98
99 return Error::success();
100}
101
102llvm::Error Diagnostics::DumpDiangosticsLog(const FileSpec &dir) const {
103 FileSpec log_file = dir.CopyByAppendingPathComponent("diagnostics.log");
104 std::error_code ec;
105 llvm::raw_fd_ostream stream(log_file.GetPath(), ec, llvm::sys::fs::OF_None);
106 if (ec)
107 return errorCodeToError(ec);
108 m_log_handler.Dump(stream);
109 return Error::success();
110}
111
112void Diagnostics::Record(llvm::StringRef message) {
113 m_log_handler.Emit(message);
114}
115
116// Write a single artifact into the bundle and, on success, record its name in
117// \p files. Best-effort: a write failure leaves the file out of the list, so a
118// missing artifact stays visible. The file is made owner-only because the
119// bundle can contain paths, argv, and command history.
120static void WriteArtifact(const FileSpec &dir, llvm::StringRef name,
121 llvm::StringRef content,
122 std::vector<std::string> &files) {
123 FileSpec file = dir.CopyByAppendingPathComponent(name);
124 std::error_code ec;
125 llvm::raw_fd_ostream os(file.GetPath(), ec, llvm::sys::fs::OF_Text);
126 if (ec)
127 return;
128 os << content;
129 os.flush();
130 llvm::sys::fs::setPermissions(file.GetPath(), llvm::sys::fs::owner_read |
131 llvm::sys::fs::owner_write);
132 files.push_back(name.str());
133}
134
135// Run a command through the interpreter and return its combined output and
136// error text, for inclusion as a snapshot in the bundle.
137static std::string CaptureCommand(Debugger &debugger, llvm::StringRef command) {
138 CommandReturnObject result(/*colors=*/false);
139 debugger.GetCommandInterpreter().HandleCommand(command.str().c_str(),
140 eLazyBoolNo, result);
141 return (result.GetOutputString() + result.GetErrorString()).str();
142}
143
144namespace {
145/// The execution context a triage command needs before it is worth running.
146enum class Requires { Always, Target, Process, Frame };
147
148struct TriageCommand {
149 llvm::StringRef command;
150 Requires requirement;
151};
152} // namespace
153
154// The commands a triager runs first, captured into the bundle. Add a row to
155// extend the snapshot. The requirement keeps a command from emitting a spurious
156// "no process" error when its context is absent.
157static constexpr TriageCommand g_triage_commands[] = {
158 {"target list", Requires::Always},
159 {"image list", Requires::Target},
160 {"process status", Requires::Process},
161 {"thread list", Requires::Process},
162 {"thread backtrace all", Requires::Process},
163 {"image lookup -va $pc", Requires::Frame},
164 {"register read", Requires::Frame},
165 {"frame variable", Requires::Frame},
166};
167
168static bool Available(Requires requirement, const ExecutionContext &exe_ctx) {
169 switch (requirement) {
170 case Requires::Always:
171 return true;
172 case Requires::Target:
173 return exe_ctx.GetTargetPtr() != nullptr;
174 case Requires::Process:
175 return exe_ctx.GetProcessPtr() != nullptr;
176 case Requires::Frame:
177 return exe_ctx.GetFramePtr() != nullptr;
178 }
179 llvm_unreachable("unhandled Requires");
180}
181
182llvm::Expected<Diagnostics::Report>
184 const FileSpec &dir) {
185 // The bundle holds potentially sensitive data (paths, argv, command history),
186 // so restrict the directory to the owner before writing anything into it.
187 llvm::sys::fs::setPermissions(dir.GetPath(), llvm::sys::fs::owner_read |
188 llvm::sys::fs::owner_write |
189 llvm::sys::fs::owner_exe);
190
191 Report report;
192 report.attachments.directory = dir.GetPath();
193 CollectLogs(debugger, dir, report.attachments.files);
194 CollectStatistics(debugger, exe_ctx, dir, report.attachments.files);
195 CollectCommands(debugger, exe_ctx, dir, report.attachments.files);
196
198 report.os = GetHostDescription(exe_ctx);
199 report.invocation = GetInvocation();
200 return report;
201}
202
203void Diagnostics::CollectLogs(Debugger &debugger, const FileSpec &dir,
204 std::vector<std::string> &files) {
205 // The always-on diagnostic log.
206 if (Error error = Create(dir))
207 consumeError(std::move(error));
208 else
209 files.push_back("diagnostics.log");
210
211 // This debugger's file-backed logs.
212 for (std::string &name : debugger.CopyLogFilesToDirectory(dir))
213 files.push_back(std::move(name));
214}
215
217 const ExecutionContext &exe_ctx,
218 const FileSpec &dir,
219 std::vector<std::string> &files) {
220 StatisticsOptions options;
221 json::Value stats = DebuggerStats::ReportStatistics(
222 debugger, exe_ctx.GetTargetPtr(), options);
223 std::string str;
224 raw_string_ostream os(str);
225 os << formatv("{0:2}", stats);
226 WriteArtifact(dir, "statistics.json", str, files);
227}
228
230 const ExecutionContext &exe_ctx,
231 const FileSpec &dir,
232 std::vector<std::string> &files) {
233 std::string snapshot;
234 for (const TriageCommand &tc : g_triage_commands) {
235 if (!Available(tc.requirement, exe_ctx))
236 continue;
237 snapshot += formatv("=== {0} ===\n", tc.command).str();
238 snapshot += CaptureCommand(debugger, tc.command);
239 snapshot += "\n\n";
240 }
241 WriteArtifact(dir, "commands.txt", snapshot, files);
242}
243
245 std::string os = HostInfo::GetTargetTriple().str();
246 Target *target = exe_ctx.GetTargetPtr();
247 if (!target)
248 return os;
249 PlatformSP platform_sp = target->GetPlatform();
250 if (!platform_sp)
251 return os;
252
253 os += formatv(" platform={0}", platform_sp->GetName()).str();
254 VersionTuple version = platform_sp->GetOSVersion();
255 if (!version.empty())
256 os += " os=" + version.getAsString();
257 if (std::optional<std::string> build = platform_sp->GetOSBuildString())
258 os += " build=" + *build;
259 return os;
260}
261
263 // libLLDB does not store its own argv, so read the invocation from the host
264 // process.
267 return {};
268
269 const Args &args = info.GetArguments();
270 std::string invocation;
271 for (size_t i = 0; i < args.GetArgumentCount(); ++i) {
272 if (i)
273 invocation += ' ';
274 invocation += args.GetArgumentAtIndex(i);
275 }
276 return invocation;
277}
278
279llvm::json::Value lldb_private::toJSON(const Diagnostics::Report &report) {
280 json::Object obj{
281 {"version", report.version},
282 {"os", report.os},
283 };
284 if (!report.invocation.empty())
285 obj["invocation"] = report.invocation;
286 obj["attachments"] = json::Object{
287 {"directory", report.attachments.directory},
288 {"files", json::Array(report.attachments.files)},
289 };
290 return obj;
291}
static llvm::raw_ostream & error(Stream &strm)
static constexpr size_t g_num_log_messages
static constexpr TriageCommand g_triage_commands[]
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...
RotatingLogHandler m_log_handler
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.
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...
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.
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
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
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 plug-in interface definition class for debugging a process.
Definition Process.h:359
lldb::PlatformSP GetPlatform()
Definition Target.h:1971
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:43
The state a triager needs to make sense of a bug report.
Definition Diagnostics.h:50