diff --git a/lldb/include/lldb/Host/Host.h b/lldb/include/lldb/Host/Host.h index b3e0cbaff19ed..4fc2bd128b025 100644 --- a/lldb/include/lldb/Host/Host.h +++ b/lldb/include/lldb/Host/Host.h @@ -261,6 +261,12 @@ class SystemLogHandler : public LogHandler { public: SystemLogHandler(); void Emit(llvm::StringRef message) override; + + bool isA(const void *ClassID) const override { return ClassID == &ID; } + static bool classof(const LogHandler *obj) { return obj->isA(&ID); } + +private: + static char ID; }; } // namespace lldb_private diff --git a/lldb/include/lldb/Utility/Log.h b/lldb/include/lldb/Utility/Log.h index 4772291e43079..404cbed14c18b 100644 --- a/lldb/include/lldb/Utility/Log.h +++ b/lldb/include/lldb/Utility/Log.h @@ -49,6 +49,12 @@ class LogHandler { public: virtual ~LogHandler() = default; virtual void Emit(llvm::StringRef message) = 0; + + virtual bool isA(const void *ClassID) const { return ClassID == &ID; } + static bool classof(const LogHandler *obj) { return obj->isA(&ID); } + +private: + static char ID; }; class StreamLogHandler : public LogHandler { @@ -59,9 +65,13 @@ class StreamLogHandler : public LogHandler { void Emit(llvm::StringRef message) override; void Flush(); + bool isA(const void *ClassID) const override { return ClassID == &ID; } + static bool classof(const LogHandler *obj) { return obj->isA(&ID); } + private: std::mutex m_mutex; llvm::raw_fd_ostream m_stream; + static char ID; }; class CallbackLogHandler : public LogHandler { @@ -70,9 +80,13 @@ class CallbackLogHandler : public LogHandler { void Emit(llvm::StringRef message) override; + bool isA(const void *ClassID) const override { return ClassID == &ID; } + static bool classof(const LogHandler *obj) { return obj->isA(&ID); } + private: lldb::LogOutputCallback m_callback; void *m_baton; + static char ID; }; class RotatingLogHandler : public LogHandler { @@ -82,6 +96,9 @@ class RotatingLogHandler : public LogHandler { void Emit(llvm::StringRef message) override; void Dump(llvm::raw_ostream &stream) const; + bool isA(const void *ClassID) const override { return ClassID == &ID; } + static bool classof(const LogHandler *obj) { return obj->isA(&ID); } + private: size_t NormalizeIndex(size_t i) const; size_t GetNumMessages() const; @@ -92,6 +109,7 @@ class RotatingLogHandler : public LogHandler { const size_t m_size = 0; size_t m_next_index = 0; size_t m_total_count = 0; + static char ID; }; class Log final { @@ -169,6 +187,10 @@ class Log final { llvm::ArrayRef categories, llvm::raw_ostream &error_stream); + static bool DumpLogChannel(llvm::StringRef channel, + llvm::raw_ostream &output_stream, + llvm::raw_ostream &error_stream); + static bool ListChannelCategories(llvm::StringRef channel, llvm::raw_ostream &stream); @@ -258,6 +280,8 @@ class Log final { void Disable(uint32_t flags); + bool Dump(llvm::raw_ostream &stream); + typedef llvm::StringMap ChannelMap; static llvm::ManagedStatic g_channel_map; diff --git a/lldb/source/Commands/CommandObjectLog.cpp b/lldb/source/Commands/CommandObjectLog.cpp index 349af26691de9..684cb35da1ccb 100644 --- a/lldb/source/Commands/CommandObjectLog.cpp +++ b/lldb/source/Commands/CommandObjectLog.cpp @@ -56,6 +56,9 @@ static constexpr OptionEnumValues LogHandlerType() { #define LLDB_OPTIONS_log_enable #include "CommandOptions.inc" +#define LLDB_OPTIONS_log_dump +#include "CommandOptions.inc" + /// Common completion logic for log enable/disable. static void CompleteEnableDisable(CompletionRequest &request) { size_t arg_index = request.GetCursorIndex(); @@ -345,6 +348,114 @@ class CommandObjectLogList : public CommandObjectParsed { return result.Succeeded(); } }; +class CommandObjectLogDump : public CommandObjectParsed { +public: + CommandObjectLogDump(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "log dump", + "dump circular buffer logs", nullptr) { + CommandArgumentEntry arg1; + CommandArgumentData channel_arg; + + // Define the first (and only) variant of this arg. + channel_arg.arg_type = eArgTypeLogChannel; + channel_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg1.push_back(channel_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg1); + } + + ~CommandObjectLogDump() override = default; + + Options *GetOptions() override { return &m_options; } + + class CommandOptions : public Options { + public: + CommandOptions() = default; + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'f': + log_file.SetFile(option_arg, FileSpec::Style::native); + FileSystem::Instance().Resolve(log_file); + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + log_file.Clear(); + } + + llvm::ArrayRef GetDefinitions() override { + return llvm::makeArrayRef(g_log_dump_options); + } + + FileSpec log_file; + }; + + void + HandleArgumentCompletion(CompletionRequest &request, + OptionElementVector &opt_element_vector) override { + CompleteEnableDisable(request); + } + +protected: + bool DoExecute(Args &args, CommandReturnObject &result) override { + if (args.empty()) { + result.AppendErrorWithFormat( + "%s takes a log channel and one or more log types.\n", + m_cmd_name.c_str()); + return false; + } + + std::unique_ptr stream_up; + if (m_options.log_file) { + const File::OpenOptions flags = File::eOpenOptionWriteOnly | + File::eOpenOptionCanCreate | + File::eOpenOptionTruncate; + llvm::Expected file = FileSystem::Instance().Open( + m_options.log_file, flags, lldb::eFilePermissionsFileDefault, false); + if (!file) { + result.AppendErrorWithFormat("Unable to open log file '%s': %s", + m_options.log_file.GetCString(), + llvm::toString(file.takeError()).c_str()); + return false; + } + stream_up = std::make_unique( + (*file)->GetDescriptor(), /*shouldClose=*/true); + } else { + stream_up = std::make_unique( + GetDebugger().GetOutputFile().GetDescriptor(), /*shouldClose=*/false); + } + + const std::string channel = std::string(args[0].ref()); + std::string error; + llvm::raw_string_ostream error_stream(error); + if (Log::DumpLogChannel(channel, *stream_up, error_stream)) { + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } else { + result.SetStatus(eReturnStatusFailed); + result.GetErrorStream() << error_stream.str(); + } + + return result.Succeeded(); + } + + CommandOptions m_options; +}; class CommandObjectLogTimerEnable : public CommandObjectParsed { public: @@ -554,6 +665,8 @@ CommandObjectLog::CommandObjectLog(CommandInterpreter &interpreter) CommandObjectSP(new CommandObjectLogDisable(interpreter))); LoadSubCommand("list", CommandObjectSP(new CommandObjectLogList(interpreter))); + LoadSubCommand("dump", + CommandObjectSP(new CommandObjectLogDump(interpreter))); LoadSubCommand("timers", CommandObjectSP(new CommandObjectLogTimer(interpreter))); } diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td index 53e09cfce54bf..6e5f1ef4e02e5 100644 --- a/lldb/source/Commands/Options.td +++ b/lldb/source/Commands/Options.td @@ -456,6 +456,11 @@ let Command = "log enable" in { Desc<"Prepend the names of files and function that generate the logs.">; } +let Command = "log dump" in { + def log_dump_file : Option<"file", "f">, Group<1>, Arg<"Filename">, + Desc<"Set the destination file to dump to.">; +} + let Command = "reproducer dump" in { def reproducer_provider : Option<"provider", "p">, Group<1>, EnumArg<"None", "ReproducerProviderType()">, diff --git a/lldb/source/Host/common/Host.cpp b/lldb/source/Host/common/Host.cpp index a0834ca15274d..f35eb47ff6830 100644 --- a/lldb/source/Host/common/Host.cpp +++ b/lldb/source/Host/common/Host.cpp @@ -632,6 +632,8 @@ uint32_t Host::FindProcesses(const ProcessInstanceInfoMatch &match_info, return result; } +char SystemLogHandler::ID; + SystemLogHandler::SystemLogHandler() {} void SystemLogHandler::Emit(llvm::StringRef message) { diff --git a/lldb/source/Utility/Log.cpp b/lldb/source/Utility/Log.cpp index 4c3e05acd8995..67edb15ba684e 100644 --- a/lldb/source/Utility/Log.cpp +++ b/lldb/source/Utility/Log.cpp @@ -13,6 +13,7 @@ #include "llvm/ADT/Twine.h" #include "llvm/ADT/iterator.h" +#include "llvm/Support/Casting.h" #include "llvm/Support/Chrono.h" #include "llvm/Support/ManagedStatic.h" #include "llvm/Support/Path.h" @@ -34,6 +35,11 @@ using namespace lldb_private; +char LogHandler::ID; +char StreamLogHandler::ID; +char CallbackLogHandler::ID; +char RotatingLogHandler::ID; + llvm::ManagedStatic Log::g_channel_map; void Log::ForEachCategory( @@ -106,6 +112,16 @@ void Log::Disable(uint32_t flags) { } } +bool Log::Dump(llvm::raw_ostream &output_stream) { + llvm::sys::ScopedReader lock(m_mutex); + if (RotatingLogHandler *handler = + llvm::dyn_cast_or_null(m_handler.get())) { + handler->Dump(output_stream); + return true; + } + return false; +} + const Flags Log::GetOptions() const { return m_options.load(std::memory_order_relaxed); } @@ -222,6 +238,22 @@ bool Log::DisableLogChannel(llvm::StringRef channel, return true; } +bool Log::DumpLogChannel(llvm::StringRef channel, + llvm::raw_ostream &output_stream, + llvm::raw_ostream &error_stream) { + auto iter = g_channel_map->find(channel); + if (iter == g_channel_map->end()) { + error_stream << llvm::formatv("Invalid log channel '{0}'.\n", channel); + return false; + } + if (!iter->second.Dump(output_stream)) { + error_stream << llvm::formatv( + "log channel '{0}' does not support dumping.\n", channel); + return false; + } + return true; +} + bool Log::ListChannelCategories(llvm::StringRef channel, llvm::raw_ostream &stream) { auto ch = g_channel_map->find(channel); diff --git a/lldb/test/API/commands/log/basic/TestLogHandlers.py b/lldb/test/API/commands/log/basic/TestLogHandlers.py new file mode 100644 index 0000000000000..367813b879b43 --- /dev/null +++ b/lldb/test/API/commands/log/basic/TestLogHandlers.py @@ -0,0 +1,55 @@ +""" +Test lldb log handlers. +""" + +import os +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class LogHandlerTestCase(TestBase): + NO_DEBUG_INFO_TESTCASE = True + + def setUp(self): + TestBase.setUp(self) + self.log_file = self.getBuildArtifact("log-file.txt") + if (os.path.exists(self.log_file)): + os.remove(self.log_file) + + def test_circular(self): + self.runCmd("log enable -b 5 -h circular lldb commands") + self.runCmd("bogus", check=False) + self.runCmd("log dump lldb -f {}".format(self.log_file)) + + with open(self.log_file, 'r') as f: + log_lines = f.readlines() + + self.assertEqual(len(log_lines), 5) + + found_command_log_dump = False + found_command_bogus = False + + for line in log_lines: + if 'Processing command: log dump' in line: + found_command_log_dump = True + if 'Processing command: bogus' in line: + found_command_bogus = True + + self.assertTrue(found_command_log_dump) + self.assertFalse(found_command_bogus) + + def test_circular_no_buffer_size(self): + self.expect( + "log enable -h circular lldb commands", + error=True, + substrs=[ + 'the circular buffer handler requires a non-zero buffer size' + ]) + + def test_dump_unsupported(self): + self.runCmd("log enable lldb commands -f {}".format(self.log_file)) + self.expect("log dump lldb", + error=True, + substrs=["log channel 'lldb' does not support dumping"])