diff --git a/lldb/include/lldb/Target/Trace.h b/lldb/include/lldb/Target/Trace.h --- a/lldb/include/lldb/Target/Trace.h +++ b/lldb/include/lldb/Target/Trace.h @@ -140,6 +140,20 @@ /// trace. virtual lldb::TraceCursorUP GetCursor(Thread &thread) = 0; + /// Dump general info about a given thread's trace. Each Trace plug-in + /// decides which data to show. + /// + /// \param[in] thread + /// The thread that owns the trace in question. + /// + /// \param[in] s + /// The stream object where the info will be printed printed. + /// + /// \param[in] verbose + /// If \b true, print detailed info + /// If \b false, print compact info + virtual void DumpTraceInfo(Thread &thread, Stream &s, bool verbose) = 0; + /// Check if a thread is currently traced by this object. /// /// \param[in] thread diff --git a/lldb/source/Commands/CommandObjectThread.cpp b/lldb/source/Commands/CommandObjectThread.cpp --- a/lldb/source/Commands/CommandObjectThread.cpp +++ b/lldb/source/Commands/CommandObjectThread.cpp @@ -2138,6 +2138,83 @@ std::map> m_dumpers; }; +// CommandObjectTraceDumpInfo +#define LLDB_OPTIONS_thread_trace_dump_info +#include "CommandOptions.inc" + +class CommandObjectTraceDumpInfo : public CommandObjectIterateOverThreads { +public: + class CommandOptions : public Options { + public: + CommandOptions() : Options() { OptionParsingStarting(nullptr); } + + ~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 'v': { + m_verbose = true; + break; + } + default: + llvm_unreachable("Unimplemented option"); + } + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_verbose = false; + } + + llvm::ArrayRef GetDefinitions() override { + return llvm::makeArrayRef(g_thread_trace_dump_info_options); + } + + // Instance variables to hold the values for command options. + bool m_verbose; + }; + + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target &target = m_exe_ctx.GetTargetRef(); + result.GetOutputStream().Printf( + "Trace technology: %s\n", + target.GetTrace()->GetPluginName().AsCString()); + return CommandObjectIterateOverThreads::DoExecute(command, result); + } + + CommandObjectTraceDumpInfo(CommandInterpreter &interpreter) + : CommandObjectIterateOverThreads( + interpreter, "thread trace dump info", + "Dump the traced information for one or more threads. If no " + "threads are specified, show the current thread. Use the " + "thread-index \"all\" to see all threads.", + nullptr, + eCommandRequiresProcess | eCommandTryTargetAPILock | + eCommandProcessMustBeLaunched | eCommandProcessMustBePaused | + eCommandProcessMustBeTraced), + m_options() {} + + ~CommandObjectTraceDumpInfo() override = default; + + Options *GetOptions() override { return &m_options; } + +protected: + bool HandleOneThread(lldb::tid_t tid, CommandReturnObject &result) override { + const TraceSP &trace_sp = m_exe_ctx.GetTargetSP()->GetTrace(); + ThreadSP thread_sp = + m_exe_ctx.GetProcessPtr()->GetThreadList().FindThreadByID(tid); + trace_sp->DumpTraceInfo(*thread_sp, result.GetOutputStream(), + m_options.m_verbose); + return true; + } + + CommandOptions m_options; +}; + // CommandObjectMultiwordTraceDump class CommandObjectMultiwordTraceDump : public CommandObjectMultiword { public: @@ -2150,6 +2227,8 @@ LoadSubCommand( "instructions", CommandObjectSP(new CommandObjectTraceDumpInstructions(interpreter))); + LoadSubCommand( + "info", CommandObjectSP(new CommandObjectTraceDumpInfo(interpreter))); } ~CommandObjectMultiwordTraceDump() override = default; }; diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td --- a/lldb/source/Commands/Options.td +++ b/lldb/source/Commands/Options.td @@ -1070,6 +1070,11 @@ Desc<"For each instruction, print the corresponding timestamp counter if available.">; } +let Command = "thread trace dump info" in { + def thread_trace_dump_info_verbose : Option<"verbose", "v">, Group<1>, + Desc<"show verbose thread trace dump info">; +} + let Command = "type summary add" in { def type_summary_add_category : Option<"category", "w">, Arg<"Name">, Desc<"Add this to the given category instead of the default one.">; diff --git a/lldb/source/Plugins/Trace/intel-pt/DecodedThread.h b/lldb/source/Plugins/Trace/intel-pt/DecodedThread.h --- a/lldb/source/Plugins/Trace/intel-pt/DecodedThread.h +++ b/lldb/source/Plugins/Trace/intel-pt/DecodedThread.h @@ -127,7 +127,8 @@ class DecodedThread : public std::enable_shared_from_this { public: DecodedThread(lldb::ThreadSP thread_sp, - std::vector &&instructions); + std::vector &&instructions, + size_t raw_trace_size); /// Constructor with a single error signaling a complete failure of the /// decoding process. @@ -143,9 +144,16 @@ /// Get a new cursor for the decoded thread. lldb::TraceCursorUP GetCursor(); + /// Get the size in bytes of the corresponding Intel PT raw trace + /// + /// \return + /// The size of the trace. + size_t GetRawTraceSize() const; + private: lldb::ThreadSP m_thread_sp; std::vector m_instructions; + size_t m_raw_trace_size; }; using DecodedThreadSP = std::shared_ptr; diff --git a/lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp b/lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp --- a/lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp @@ -61,6 +61,7 @@ return make_error(m_error->message(), m_error->convertToErrorCode()); } +size_t DecodedThread::GetRawTraceSize() const { return m_raw_trace_size; } TraceInstructionControlFlowType IntelPTInstruction::GetControlFlowType(lldb::addr_t next_load_address) const { @@ -103,8 +104,10 @@ } DecodedThread::DecodedThread(ThreadSP thread_sp, - std::vector &&instructions) - : m_thread_sp(thread_sp), m_instructions(std::move(instructions)) { + std::vector &&instructions, + size_t raw_trace_size) + : m_thread_sp(thread_sp), m_instructions(std::move(instructions)), + m_raw_trace_size(raw_trace_size) { if (m_instructions.empty()) m_instructions.emplace_back( createStringError(inconvertibleErrorCode(), "empty trace")); diff --git a/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp b/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp --- a/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp @@ -211,7 +211,7 @@ static Expected> DecodeTraceFile(Process &process, TraceIntelPT &trace_intel_pt, - const FileSpec &trace_file) { + const FileSpec &trace_file, size_t &raw_trace_size) { ErrorOr> trace_or_error = MemoryBuffer::getFile(trace_file.GetPath()); if (std::error_code err = trace_or_error.getError()) @@ -223,15 +223,17 @@ // following cast is safe. reinterpret_cast(const_cast(trace.getBufferStart())), trace.getBufferSize()); + raw_trace_size = trace_data.size(); return DecodeInMemoryTrace(process, trace_intel_pt, trace_data); } static Expected> -DecodeLiveThread(Thread &thread, TraceIntelPT &trace) { +DecodeLiveThread(Thread &thread, TraceIntelPT &trace, size_t &raw_trace_size) { Expected> buffer = trace.GetLiveThreadBuffer(thread.GetID()); if (!buffer) return buffer.takeError(); + raw_trace_size = buffer->size(); if (Expected cpu_info = trace.GetCPUInfo()) return DecodeInMemoryTrace(*thread.GetProcess(), trace, MutableArrayRef(*buffer)); @@ -250,11 +252,13 @@ : m_trace_thread(trace_thread), m_trace(trace) {} DecodedThreadSP PostMortemThreadDecoder::DoDecode() { + size_t raw_trace_size = 0; if (Expected> instructions = DecodeTraceFile(*m_trace_thread->GetProcess(), m_trace, - m_trace_thread->GetTraceFile())) + m_trace_thread->GetTraceFile(), raw_trace_size)) return std::make_shared(m_trace_thread->shared_from_this(), - std::move(*instructions)); + std::move(*instructions), + raw_trace_size); else return std::make_shared(m_trace_thread->shared_from_this(), instructions.takeError()); @@ -264,10 +268,11 @@ : m_thread_sp(thread.shared_from_this()), m_trace(trace) {} DecodedThreadSP LiveThreadDecoder::DoDecode() { + size_t raw_trace_size = 0; if (Expected> instructions = - DecodeLiveThread(*m_thread_sp, m_trace)) - return std::make_shared(m_thread_sp, - std::move(*instructions)); + DecodeLiveThread(*m_thread_sp, m_trace, raw_trace_size)) + return std::make_shared( + m_thread_sp, std::move(*instructions), raw_trace_size); else return std::make_shared(m_thread_sp, instructions.takeError()); diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h @@ -67,6 +67,10 @@ lldb::TraceCursorUP GetCursor(Thread &thread) override; + void DumpTraceInfo(Thread &thread, Stream &s, bool verbose) override; + + llvm::Optional GetRawTraceSize(Thread &thread); + void DoRefreshLiveProcessState( llvm::Expected state) override; diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp @@ -108,6 +108,24 @@ return Decode(thread)->GetCursor(); } +void TraceIntelPT::DumpTraceInfo(Thread &thread, Stream &s, bool verbose) { + Optional raw_size = GetRawTraceSize(thread); + s.Printf("\nthread #%u: tid = %" PRIu64, thread.GetIndexID(), thread.GetID()); + if (!raw_size) { + s.Printf(", not traced\n"); + return; + } + s.Printf("\n Raw trace size: %zu bytes\n", *raw_size); + return; +} + +Optional TraceIntelPT::GetRawTraceSize(Thread &thread) { + if (IsTraced(thread)) + return Decode(thread)->GetRawTraceSize(); + else + return None; +} + Expected TraceIntelPT::GetCPUInfoForLiveProcess() { Expected> cpu_info = GetLiveProcessBinaryData("cpuInfo"); if (!cpu_info) diff --git a/lldb/test/API/commands/trace/TestTraceDumpInfo.py b/lldb/test/API/commands/trace/TestTraceDumpInfo.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/trace/TestTraceDumpInfo.py @@ -0,0 +1,41 @@ +import lldb +from intelpt_testcase import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +from lldbsuite.test.decorators import * + +class TestTraceDumpInfo(TraceIntelPTTestCaseBase): + mydir = TestBase.compute_mydir(__file__) + + def testErrorMessages(self): + # We first check the output when there are no targets + self.expect("thread trace dump info", + substrs=["error: invalid target, create a target using the 'target create' command"], + error=True) + + # We now check the output when there's a non-running target + self.expect("target create " + + os.path.join(self.getSourceDir(), "intelpt-trace", "a.out")) + + self.expect("thread trace dump info", + substrs=["error: invalid process"], + error=True) + + # Now we check the output when there's a running target without a trace + self.expect("b main") + self.expect("run") + + self.expect("thread trace dump info", + substrs=["error: Process is not being traced"], + error=True) + + def testDumpRawTraceSize(self): + self.expect("trace load -v " + + os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json"), + substrs=["intel-pt"]) + + self.expect("thread trace dump info", + substrs=['''Trace technology: intel-pt + +thread #1: tid = 3842849 + Raw trace size: 4096 bytes''']) diff --git a/lldb/test/API/commands/trace/TestTraceLoad.py b/lldb/test/API/commands/trace/TestTraceLoad.py --- a/lldb/test/API/commands/trace/TestTraceLoad.py +++ b/lldb/test/API/commands/trace/TestTraceLoad.py @@ -33,7 +33,10 @@ # check that the Process and Thread objects were created correctly self.expect("thread info", substrs=["tid = 3842849"]) self.expect("thread list", substrs=["Process 1234 stopped", "tid = 3842849"]) + self.expect("thread trace dump info", substrs=['''Trace technology: intel-pt +thread #1: tid = 3842849 + Raw trace size: 4096 bytes''']) def testLoadInvalidTraces(self): src_dir = self.getSourceDir()