Skip to content

Commit 9bdb7e5

Browse files
committed
[lldb] Add a log dump command
Add a log dump command to dump logs to a file. This only works for channels that have a log handler associated that supports dumping. For now that's limited to the circular log handler, but more could be added in the future. Differential revision: https://reviews.llvm.org/D128557
1 parent becbbb7 commit 9bdb7e5

File tree

7 files changed

+237
-0
lines changed

7 files changed

+237
-0
lines changed

lldb/include/lldb/Host/Host.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,12 @@ class SystemLogHandler : public LogHandler {
261261
public:
262262
SystemLogHandler();
263263
void Emit(llvm::StringRef message) override;
264+
265+
bool isA(const void *ClassID) const override { return ClassID == &ID; }
266+
static bool classof(const LogHandler *obj) { return obj->isA(&ID); }
267+
268+
private:
269+
static char ID;
264270
};
265271

266272
} // namespace lldb_private

lldb/include/lldb/Utility/Log.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ class LogHandler {
4949
public:
5050
virtual ~LogHandler() = default;
5151
virtual void Emit(llvm::StringRef message) = 0;
52+
53+
virtual bool isA(const void *ClassID) const { return ClassID == &ID; }
54+
static bool classof(const LogHandler *obj) { return obj->isA(&ID); }
55+
56+
private:
57+
static char ID;
5258
};
5359

5460
class StreamLogHandler : public LogHandler {
@@ -59,9 +65,13 @@ class StreamLogHandler : public LogHandler {
5965
void Emit(llvm::StringRef message) override;
6066
void Flush();
6167

68+
bool isA(const void *ClassID) const override { return ClassID == &ID; }
69+
static bool classof(const LogHandler *obj) { return obj->isA(&ID); }
70+
6271
private:
6372
std::mutex m_mutex;
6473
llvm::raw_fd_ostream m_stream;
74+
static char ID;
6575
};
6676

6777
class CallbackLogHandler : public LogHandler {
@@ -70,9 +80,13 @@ class CallbackLogHandler : public LogHandler {
7080

7181
void Emit(llvm::StringRef message) override;
7282

83+
bool isA(const void *ClassID) const override { return ClassID == &ID; }
84+
static bool classof(const LogHandler *obj) { return obj->isA(&ID); }
85+
7386
private:
7487
lldb::LogOutputCallback m_callback;
7588
void *m_baton;
89+
static char ID;
7690
};
7791

7892
class RotatingLogHandler : public LogHandler {
@@ -82,6 +96,9 @@ class RotatingLogHandler : public LogHandler {
8296
void Emit(llvm::StringRef message) override;
8397
void Dump(llvm::raw_ostream &stream) const;
8498

99+
bool isA(const void *ClassID) const override { return ClassID == &ID; }
100+
static bool classof(const LogHandler *obj) { return obj->isA(&ID); }
101+
85102
private:
86103
size_t NormalizeIndex(size_t i) const;
87104
size_t GetNumMessages() const;
@@ -92,6 +109,7 @@ class RotatingLogHandler : public LogHandler {
92109
const size_t m_size = 0;
93110
size_t m_next_index = 0;
94111
size_t m_total_count = 0;
112+
static char ID;
95113
};
96114

97115
class Log final {
@@ -169,6 +187,10 @@ class Log final {
169187
llvm::ArrayRef<const char *> categories,
170188
llvm::raw_ostream &error_stream);
171189

190+
static bool DumpLogChannel(llvm::StringRef channel,
191+
llvm::raw_ostream &output_stream,
192+
llvm::raw_ostream &error_stream);
193+
172194
static bool ListChannelCategories(llvm::StringRef channel,
173195
llvm::raw_ostream &stream);
174196

@@ -258,6 +280,8 @@ class Log final {
258280

259281
void Disable(uint32_t flags);
260282

283+
bool Dump(llvm::raw_ostream &stream);
284+
261285
typedef llvm::StringMap<Log> ChannelMap;
262286
static llvm::ManagedStatic<ChannelMap> g_channel_map;
263287

lldb/source/Commands/CommandObjectLog.cpp

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ static constexpr OptionEnumValues LogHandlerType() {
5656
#define LLDB_OPTIONS_log_enable
5757
#include "CommandOptions.inc"
5858

59+
#define LLDB_OPTIONS_log_dump
60+
#include "CommandOptions.inc"
61+
5962
/// Common completion logic for log enable/disable.
6063
static void CompleteEnableDisable(CompletionRequest &request) {
6164
size_t arg_index = request.GetCursorIndex();
@@ -345,6 +348,114 @@ class CommandObjectLogList : public CommandObjectParsed {
345348
return result.Succeeded();
346349
}
347350
};
351+
class CommandObjectLogDump : public CommandObjectParsed {
352+
public:
353+
CommandObjectLogDump(CommandInterpreter &interpreter)
354+
: CommandObjectParsed(interpreter, "log dump",
355+
"dump circular buffer logs", nullptr) {
356+
CommandArgumentEntry arg1;
357+
CommandArgumentData channel_arg;
358+
359+
// Define the first (and only) variant of this arg.
360+
channel_arg.arg_type = eArgTypeLogChannel;
361+
channel_arg.arg_repetition = eArgRepeatPlain;
362+
363+
// There is only one variant this argument could be; put it into the
364+
// argument entry.
365+
arg1.push_back(channel_arg);
366+
367+
// Push the data for the first argument into the m_arguments vector.
368+
m_arguments.push_back(arg1);
369+
}
370+
371+
~CommandObjectLogDump() override = default;
372+
373+
Options *GetOptions() override { return &m_options; }
374+
375+
class CommandOptions : public Options {
376+
public:
377+
CommandOptions() = default;
378+
379+
~CommandOptions() override = default;
380+
381+
Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
382+
ExecutionContext *execution_context) override {
383+
Status error;
384+
const int short_option = m_getopt_table[option_idx].val;
385+
386+
switch (short_option) {
387+
case 'f':
388+
log_file.SetFile(option_arg, FileSpec::Style::native);
389+
FileSystem::Instance().Resolve(log_file);
390+
break;
391+
default:
392+
llvm_unreachable("Unimplemented option");
393+
}
394+
395+
return error;
396+
}
397+
398+
void OptionParsingStarting(ExecutionContext *execution_context) override {
399+
log_file.Clear();
400+
}
401+
402+
llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
403+
return llvm::makeArrayRef(g_log_dump_options);
404+
}
405+
406+
FileSpec log_file;
407+
};
408+
409+
void
410+
HandleArgumentCompletion(CompletionRequest &request,
411+
OptionElementVector &opt_element_vector) override {
412+
CompleteEnableDisable(request);
413+
}
414+
415+
protected:
416+
bool DoExecute(Args &args, CommandReturnObject &result) override {
417+
if (args.empty()) {
418+
result.AppendErrorWithFormat(
419+
"%s takes a log channel and one or more log types.\n",
420+
m_cmd_name.c_str());
421+
return false;
422+
}
423+
424+
std::unique_ptr<llvm::raw_ostream> stream_up;
425+
if (m_options.log_file) {
426+
const File::OpenOptions flags = File::eOpenOptionWriteOnly |
427+
File::eOpenOptionCanCreate |
428+
File::eOpenOptionTruncate;
429+
llvm::Expected<FileUP> file = FileSystem::Instance().Open(
430+
m_options.log_file, flags, lldb::eFilePermissionsFileDefault, false);
431+
if (!file) {
432+
result.AppendErrorWithFormat("Unable to open log file '%s': %s",
433+
m_options.log_file.GetCString(),
434+
llvm::toString(file.takeError()).c_str());
435+
return false;
436+
}
437+
stream_up = std::make_unique<llvm::raw_fd_ostream>(
438+
(*file)->GetDescriptor(), /*shouldClose=*/true);
439+
} else {
440+
stream_up = std::make_unique<llvm::raw_fd_ostream>(
441+
GetDebugger().GetOutputFile().GetDescriptor(), /*shouldClose=*/false);
442+
}
443+
444+
const std::string channel = std::string(args[0].ref());
445+
std::string error;
446+
llvm::raw_string_ostream error_stream(error);
447+
if (Log::DumpLogChannel(channel, *stream_up, error_stream)) {
448+
result.SetStatus(eReturnStatusSuccessFinishNoResult);
449+
} else {
450+
result.SetStatus(eReturnStatusFailed);
451+
result.GetErrorStream() << error_stream.str();
452+
}
453+
454+
return result.Succeeded();
455+
}
456+
457+
CommandOptions m_options;
458+
};
348459

349460
class CommandObjectLogTimerEnable : public CommandObjectParsed {
350461
public:
@@ -554,6 +665,8 @@ CommandObjectLog::CommandObjectLog(CommandInterpreter &interpreter)
554665
CommandObjectSP(new CommandObjectLogDisable(interpreter)));
555666
LoadSubCommand("list",
556667
CommandObjectSP(new CommandObjectLogList(interpreter)));
668+
LoadSubCommand("dump",
669+
CommandObjectSP(new CommandObjectLogDump(interpreter)));
557670
LoadSubCommand("timers",
558671
CommandObjectSP(new CommandObjectLogTimer(interpreter)));
559672
}

lldb/source/Commands/Options.td

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,11 @@ let Command = "log enable" in {
456456
Desc<"Prepend the names of files and function that generate the logs.">;
457457
}
458458

459+
let Command = "log dump" in {
460+
def log_dump_file : Option<"file", "f">, Group<1>, Arg<"Filename">,
461+
Desc<"Set the destination file to dump to.">;
462+
}
463+
459464
let Command = "reproducer dump" in {
460465
def reproducer_provider : Option<"provider", "p">, Group<1>,
461466
EnumArg<"None", "ReproducerProviderType()">,

lldb/source/Host/common/Host.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,8 @@ uint32_t Host::FindProcesses(const ProcessInstanceInfoMatch &match_info,
632632
return result;
633633
}
634634

635+
char SystemLogHandler::ID;
636+
635637
SystemLogHandler::SystemLogHandler() {}
636638

637639
void SystemLogHandler::Emit(llvm::StringRef message) {

lldb/source/Utility/Log.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "llvm/ADT/Twine.h"
1414
#include "llvm/ADT/iterator.h"
1515

16+
#include "llvm/Support/Casting.h"
1617
#include "llvm/Support/Chrono.h"
1718
#include "llvm/Support/ManagedStatic.h"
1819
#include "llvm/Support/Path.h"
@@ -34,6 +35,11 @@
3435

3536
using namespace lldb_private;
3637

38+
char LogHandler::ID;
39+
char StreamLogHandler::ID;
40+
char CallbackLogHandler::ID;
41+
char RotatingLogHandler::ID;
42+
3743
llvm::ManagedStatic<Log::ChannelMap> Log::g_channel_map;
3844

3945
void Log::ForEachCategory(
@@ -106,6 +112,16 @@ void Log::Disable(uint32_t flags) {
106112
}
107113
}
108114

115+
bool Log::Dump(llvm::raw_ostream &output_stream) {
116+
llvm::sys::ScopedReader lock(m_mutex);
117+
if (RotatingLogHandler *handler =
118+
llvm::dyn_cast_or_null<RotatingLogHandler>(m_handler.get())) {
119+
handler->Dump(output_stream);
120+
return true;
121+
}
122+
return false;
123+
}
124+
109125
const Flags Log::GetOptions() const {
110126
return m_options.load(std::memory_order_relaxed);
111127
}
@@ -222,6 +238,22 @@ bool Log::DisableLogChannel(llvm::StringRef channel,
222238
return true;
223239
}
224240

241+
bool Log::DumpLogChannel(llvm::StringRef channel,
242+
llvm::raw_ostream &output_stream,
243+
llvm::raw_ostream &error_stream) {
244+
auto iter = g_channel_map->find(channel);
245+
if (iter == g_channel_map->end()) {
246+
error_stream << llvm::formatv("Invalid log channel '{0}'.\n", channel);
247+
return false;
248+
}
249+
if (!iter->second.Dump(output_stream)) {
250+
error_stream << llvm::formatv(
251+
"log channel '{0}' does not support dumping.\n", channel);
252+
return false;
253+
}
254+
return true;
255+
}
256+
225257
bool Log::ListChannelCategories(llvm::StringRef channel,
226258
llvm::raw_ostream &stream) {
227259
auto ch = g_channel_map->find(channel);
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""
2+
Test lldb log handlers.
3+
"""
4+
5+
import os
6+
import lldb
7+
from lldbsuite.test.decorators import *
8+
from lldbsuite.test.lldbtest import *
9+
from lldbsuite.test import lldbutil
10+
11+
12+
class LogHandlerTestCase(TestBase):
13+
NO_DEBUG_INFO_TESTCASE = True
14+
15+
def setUp(self):
16+
TestBase.setUp(self)
17+
self.log_file = self.getBuildArtifact("log-file.txt")
18+
if (os.path.exists(self.log_file)):
19+
os.remove(self.log_file)
20+
21+
def test_circular(self):
22+
self.runCmd("log enable -b 5 -h circular lldb commands")
23+
self.runCmd("bogus", check=False)
24+
self.runCmd("log dump lldb -f {}".format(self.log_file))
25+
26+
with open(self.log_file, 'r') as f:
27+
log_lines = f.readlines()
28+
29+
self.assertEqual(len(log_lines), 5)
30+
31+
found_command_log_dump = False
32+
found_command_bogus = False
33+
34+
for line in log_lines:
35+
if 'Processing command: log dump' in line:
36+
found_command_log_dump = True
37+
if 'Processing command: bogus' in line:
38+
found_command_bogus = True
39+
40+
self.assertTrue(found_command_log_dump)
41+
self.assertFalse(found_command_bogus)
42+
43+
def test_circular_no_buffer_size(self):
44+
self.expect(
45+
"log enable -h circular lldb commands",
46+
error=True,
47+
substrs=[
48+
'the circular buffer handler requires a non-zero buffer size'
49+
])
50+
51+
def test_dump_unsupported(self):
52+
self.runCmd("log enable lldb commands -f {}".format(self.log_file))
53+
self.expect("log dump lldb",
54+
error=True,
55+
substrs=["log channel 'lldb' does not support dumping"])

0 commit comments

Comments
 (0)