Index: lldb/include/lldb/Core/Debugger.h =================================================================== --- lldb/include/lldb/Core/Debugger.h +++ lldb/include/lldb/Core/Debugger.h @@ -245,6 +245,7 @@ bool EnableLog(llvm::StringRef channel, llvm::ArrayRef categories, llvm::StringRef log_file, uint32_t log_options, + size_t buffer_size, bool circular_buffer, llvm::raw_ostream &error_stream); void SetLoggingCallback(lldb::LogOutputCallback log_callback, void *baton); Index: lldb/include/lldb/Utility/Log.h =================================================================== --- lldb/include/lldb/Utility/Log.h +++ lldb/include/lldb/Utility/Log.h @@ -52,8 +52,15 @@ virtual void Emit(llvm::StringRef message) = 0; void EmitThreadSafe(llvm::StringRef message); + /// LLVM RTTI support. + /// \{ + virtual bool isA(const void *ClassID) const { return ClassID == &ID; } + static bool classof(const LogHandler *obj) { return obj->isA(&ID); } + /// \} + private: std::mutex m_mutex; + static char ID; }; class StreamLogHandler : public LogHandler { @@ -62,8 +69,15 @@ void Emit(llvm::StringRef message) override; + /// LLVM RTTI support. + /// \{ + bool isA(const void *ClassID) const override { return ClassID == &ID; } + static bool classof(const LogHandler *obj) { return obj->isA(&ID); } + /// \} + private: llvm::raw_fd_ostream m_stream; + static char ID; }; class CallbackLogHandler : public LogHandler { @@ -72,9 +86,16 @@ void Emit(llvm::StringRef message) override; + /// LLVM RTTI support. + /// \{ + 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 BufferedLogHandler : public LogHandler { @@ -83,11 +104,18 @@ void Emit(llvm::StringRef message) override; + /// LLVM RTTI support. + /// \{ + bool isA(const void *ClassID) const override { return ClassID == &ID; } + static bool classof(const LogHandler *obj) { return obj->isA(&ID); } + /// \} + private: std::shared_ptr m_delegate; std::unique_ptr m_messages; const size_t m_size = 0; size_t m_next_index = 0; + static char ID; }; class RotatingLogHandler : public LogHandler { @@ -97,6 +125,12 @@ void Emit(llvm::StringRef message) override; void Dump(llvm::raw_ostream &stream) const; + /// LLVM RTTI support. + /// \{ + 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; @@ -106,6 +140,8 @@ const size_t m_size = 0; size_t m_next_index = 0; size_t m_total_count = 0; + + static char ID; }; class Log final { @@ -183,6 +219,11 @@ llvm::ArrayRef categories, llvm::raw_ostream &error_stream); + static bool DumpLogChannel(llvm::StringRef channel, + llvm::ArrayRef categories, + llvm::raw_ostream &output_stream, + llvm::raw_ostream &error_stream); + static bool ListChannelCategories(llvm::StringRef channel, llvm::raw_ostream &stream); @@ -272,6 +313,8 @@ void Disable(uint32_t flags); + void Dump(llvm::raw_ostream &stream); + typedef llvm::StringMap ChannelMap; static llvm::ManagedStatic g_channel_map; Index: lldb/source/API/SBDebugger.cpp =================================================================== --- lldb/source/API/SBDebugger.cpp +++ lldb/source/API/SBDebugger.cpp @@ -1620,7 +1620,8 @@ std::string error; llvm::raw_string_ostream error_stream(error); return m_opaque_sp->EnableLog(channel, GetCategoryArray(categories), "", - log_options, error_stream); + log_options, /*buffer_size=*/0, + /*circular_buffer=*/false, error_stream); } else return false; } Index: lldb/source/Commands/CommandObjectLog.cpp =================================================================== --- lldb/source/Commands/CommandObjectLog.cpp +++ lldb/source/Commands/CommandObjectLog.cpp @@ -11,6 +11,7 @@ #include "lldb/Host/OptionParser.h" #include "lldb/Interpreter/CommandReturnObject.h" #include "lldb/Interpreter/OptionArgParser.h" +#include "lldb/Interpreter/OptionValueUInt64.h" #include "lldb/Interpreter/Options.h" #include "lldb/Utility/Args.h" #include "lldb/Utility/FileSpec.h" @@ -21,7 +22,10 @@ using namespace lldb; using namespace lldb_private; -#define LLDB_OPTIONS_log +#define LLDB_OPTIONS_log_enable +#include "CommandOptions.inc" + +#define LLDB_OPTIONS_log_dump #include "CommandOptions.inc" /// Common completion logic for log enable/disable. @@ -89,6 +93,13 @@ log_file.SetFile(option_arg, FileSpec::Style::native); FileSystem::Instance().Resolve(log_file); break; + case 'b': + error = + buffer_size.SetValueFromString(option_arg, eVarSetOperationAssign); + break; + case 'c': + circular_buffer = true; + break; case 't': log_options |= LLDB_LOG_OPTION_THREADSAFE; break; @@ -126,16 +137,20 @@ void OptionParsingStarting(ExecutionContext *execution_context) override { log_file.Clear(); log_options = 0; + buffer_size.Clear(); + circular_buffer = false; } llvm::ArrayRef GetDefinitions() override { - return llvm::makeArrayRef(g_log_options); + return llvm::makeArrayRef(g_log_enable_options); } // Instance variables to hold the values for command options. FileSpec log_file; uint32_t log_options = 0; + OptionValueUInt64 buffer_size; + bool circular_buffer = false; }; void @@ -153,6 +168,18 @@ return false; } + if (m_options.log_file && m_options.circular_buffer) { + result.AppendError("cannot combine logging to a file (-f) and to a " + "circular in-memory buffer (-c)."); + return false; + } + + if (m_options.circular_buffer && !m_options.buffer_size.OptionWasSet()) { + result.AppendError("must specify a buffer size (-b) to log to a circular " + "in-memory buffer (-c)."); + return false; + } + // Store into a std::string since we're about to shift the channel off. const std::string channel = std::string(args[0].ref()); args.Shift(); // Shift off the channel @@ -164,9 +191,10 @@ std::string error; llvm::raw_string_ostream error_stream(error); - bool success = - GetDebugger().EnableLog(channel, args.GetArgumentArrayRef(), log_file, - m_options.log_options, error_stream); + bool success = GetDebugger().EnableLog( + channel, args.GetArgumentArrayRef(), log_file, m_options.log_options, + m_options.buffer_size.GetCurrentValue(), m_options.circular_buffer, + error_stream); result.GetErrorStream() << error_stream.str(); if (success) @@ -295,6 +323,122 @@ } }; +class CommandObjectLogDump : public CommandObjectParsed { +public: + CommandObjectLogDump(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "log dump", + "dump circular in-memory logs", nullptr) { + CommandArgumentEntry arg1; + CommandArgumentEntry arg2; + CommandArgumentData channel_arg; + CommandArgumentData category_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); + + category_arg.arg_type = eArgTypeLogCategory; + category_arg.arg_repetition = eArgRepeatPlus; + + arg2.push_back(category_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg1); + m_arguments.push_back(arg2); + } + + ~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()); + args.Shift(); // Shift off the channel + std::string error; + llvm::raw_string_ostream error_stream(error); + if (Log::DumpLogChannel(channel, args.GetArgumentArrayRef(), *stream_up, + error_stream)) + result.SetStatus(eReturnStatusSuccessFinishNoResult); + result.GetErrorStream() << error_stream.str(); + + return result.Succeeded(); + } + + CommandOptions m_options; +}; + class CommandObjectLogTimerEnable : public CommandObjectParsed { public: // Constructors and Destructors @@ -503,6 +647,8 @@ CommandObjectSP(new CommandObjectLogDisable(interpreter))); LoadSubCommand("list", CommandObjectSP(new CommandObjectLogList(interpreter))); + LoadSubCommand("dump", + CommandObjectSP(new CommandObjectLogDump(interpreter))); LoadSubCommand("timers", CommandObjectSP(new CommandObjectLogTimer(interpreter))); } Index: lldb/source/Commands/Options.td =================================================================== --- lldb/source/Commands/Options.td +++ lldb/source/Commands/Options.td @@ -428,9 +428,13 @@ Desc<"Clears the current command history.">; } -let Command = "log" in { +let Command = "log enable" in { def log_file : Option<"file", "f">, Group<1>, Arg<"Filename">, - Desc<"Set the destination file to log to.">; + Desc<"Set the destination file to log to. Cannot be combined with -c.">; + def log_buffer_size : Option<"buffer", "b">, Group<1>, Arg<"UnsignedInteger">, + Desc<"Set the size of the internal buffer to log to.">; + def log_circular : Option<"circular", "c">, Group<1>, + Desc<"Log to a circular in-memory buffer. Must be used in combination with -b. Cannot be combined with -f.">; def log_threadsafe : Option<"threadsafe", "t">, Group<1>, Desc<"Enable thread safe logging to avoid interweaved log lines.">; def log_verbose : Option<"verbose", "v">, Group<1>, @@ -454,6 +458,11 @@ 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()">, Index: lldb/source/Core/Debugger.cpp =================================================================== --- lldb/source/Core/Debugger.cpp +++ lldb/source/Core/Debugger.cpp @@ -1409,6 +1409,7 @@ bool Debugger::EnableLog(llvm::StringRef channel, llvm::ArrayRef categories, llvm::StringRef log_file, uint32_t log_options, + size_t buffer_size, bool circular_buffer, llvm::raw_ostream &error_stream) { const bool should_close = true; @@ -1418,9 +1419,14 @@ // For now when using the callback mode you always get thread & timestamp. log_options |= LLDB_LOG_OPTION_PREPEND_TIMESTAMP | LLDB_LOG_OPTION_PREPEND_THREAD_NAME; + } else if (buffer_size && circular_buffer) { + log_handler_sp = std::make_shared(buffer_size); } else if (log_file.empty()) { log_handler_sp = std::make_shared( GetOutputFile().GetDescriptor(), !should_close); + if (buffer_size) + log_handler_sp = + std::make_shared(buffer_size, log_handler_sp); } else { auto pos = m_stream_handlers.find(log_file); if (pos != m_stream_handlers.end()) @@ -1442,6 +1448,9 @@ log_handler_sp = std::make_shared( (*file)->GetDescriptor(), should_close); + if (buffer_size) + log_handler_sp = + std::make_shared(buffer_size, log_handler_sp); m_stream_handlers[log_file] = log_handler_sp; } } Index: lldb/source/Utility/Log.cpp =================================================================== --- lldb/source/Utility/Log.cpp +++ 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" @@ -106,6 +107,13 @@ } } +void Log::Dump(llvm::raw_ostream &output_stream) { + llvm::sys::ScopedReader lock(m_mutex); + if (RotatingLogHandler *handler = + llvm::dyn_cast(m_handler.get())) + handler->Dump(output_stream); +} + const Flags Log::GetOptions() const { return m_options.load(std::memory_order_relaxed); } @@ -222,6 +230,19 @@ return true; } +bool Log::DumpLogChannel(llvm::StringRef channel, + llvm::ArrayRef categories, + 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; + } + iter->second.Dump(output_stream); + return true; +} + bool Log::ListChannelCategories(llvm::StringRef channel, llvm::raw_ostream &stream) { auto ch = g_channel_map->find(channel); @@ -400,3 +421,9 @@ } stream.flush(); } + +char LogHandler::ID; +char StreamLogHandler::ID; +char CallbackLogHandler::ID; +char BufferedLogHandler::ID; +char RotatingLogHandler::ID; Index: lldb/tools/lldb-test/lldb-test.cpp =================================================================== --- lldb/tools/lldb-test/lldb-test.cpp +++ lldb/tools/lldb-test/lldb-test.cpp @@ -1120,7 +1120,7 @@ /*add_to_history*/ eLazyBoolNo, Result); if (!opts::Log.empty()) - Dbg->EnableLog("lldb", {"all"}, opts::Log, 0, errs()); + Dbg->EnableLog("lldb", {"all"}, opts::Log, 0, 0, errs()); if (opts::BreakpointSubcommand) return opts::breakpoint::evaluateBreakpoints(*Dbg);