[Lldb-commits] [lldb] 2aa1dd1 - [trace] Add a TraceCursor class

Walter Erquinigo via lldb-commits lldb-commits at lists.llvm.org
Wed Jun 23 22:35:37 PDT 2021


Author: Walter Erquinigo
Date: 2021-06-23T22:28:01-07:00
New Revision: 2aa1dd1c66dc3b1f6253eec9fc68c081a945b74d

URL: https://github.com/llvm/llvm-project/commit/2aa1dd1c66dc3b1f6253eec9fc68c081a945b74d
DIFF: https://github.com/llvm/llvm-project/commit/2aa1dd1c66dc3b1f6253eec9fc68c081a945b74d.diff

LOG: [trace] Add a TraceCursor class

As a follow up of D103588, I'm reinitiating the discussion with a new proposal for traversing instructions in a trace which uses the feedback gotten in that diff.

See the embedded documentation in TraceCursor for more information. The idea is to offer an OOP way to traverse instructions exposing a minimal interface that makes no assumptions on:

- the number of instructions in the trace (i.e. having indices for instructions might be impractical for gigantic intel-pt traces, as it would require to decode the entire trace). This renders the use of indices to point to instructions impractical. Traces are big and expensive, and the consumer should try to do look linear lookups (forwards and/or backwards) and avoid random accesses (the API could be extended though, but for now I want to dicard that funcionality and leave the API extensible if needed).
- the way the instructions are represented internally by each Trace plug-in. They could be mmap'ed from a file, exist in plain vector or generated on the fly as the user requests the data.
- the actual data structure used internally for each plug-in. Ideas like having a struct TraceInstruction have been discarded because that would make the plug-in follow a certain data type, which might be costly. Instead, the user can ask the cursor for each independent property of the instruction it's pointing at.

The way to get a cursor is to ask Trace.h for the end or being cursor or a thread's trace.

There are some benefits of this approach:
- there's little cost to create a cursor, and this allows for lazily decoding a trace as the user requests data.
- each trace plug-in could decide how to cache the instructions it generates. For example, if a trace is small, it might decide to keep everything in memory, or if the trace is massive, it might decide to keep around the last thousands of instructions to speed up local searches.
- a cursor can outlive a stop point, which makes trace comparison for live processes feasible. An application of this is to compare profiling data of two runs of the same function, which should be doable with intel pt.

Differential Revision: https://reviews.llvm.org/D104422

Added: 
    lldb/include/lldb/Target/TraceCursor.h
    lldb/source/Target/TraceCursor.cpp

Modified: 
    lldb/include/lldb/Target/Trace.h
    lldb/include/lldb/lldb-defines.h
    lldb/include/lldb/lldb-enumerations.h
    lldb/include/lldb/lldb-forward.h
    lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp
    lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp
    lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h
    lldb/source/Target/CMakeLists.txt
    lldb/source/Target/Trace.cpp

Removed: 
    


################################################################################
diff  --git a/lldb/include/lldb/Target/Trace.h b/lldb/include/lldb/Target/Trace.h
index 5ff7a149b01ee..bb2b93514994c 100644
--- a/lldb/include/lldb/Target/Trace.h
+++ b/lldb/include/lldb/Target/Trace.h
@@ -15,6 +15,7 @@
 
 #include "lldb/Core/PluginInterface.h"
 #include "lldb/Target/Thread.h"
+#include "lldb/Target/TraceCursor.h"
 #include "lldb/Utility/ArchSpec.h"
 #include "lldb/Utility/TraceGDBRemotePackets.h"
 #include "lldb/Utility/UnimplementedError.h"
@@ -204,6 +205,14 @@ class Trace : public PluginInterface,
       std::function<bool(size_t index, llvm::Expected<lldb::addr_t> load_addr)>
           callback) = 0;
 
+  /// Get a \a TraceCursor for the given thread's trace.
+  ///
+  /// \return
+  ///     A \a TraceCursorUP. If the thread is not traced or its trace
+  ///     information failed to load, the corresponding error is embedded in the
+  ///     trace.
+  virtual lldb::TraceCursorUP GetCursor(Thread &thread) = 0;
+
   /// Get the number of available instructions in the trace of the given thread.
   ///
   /// \param[in] thread
@@ -279,6 +288,11 @@ class Trace : public PluginInterface,
   /// Get the trace file of the given post mortem thread.
   llvm::Expected<const FileSpec &> GetPostMortemTraceFile(lldb::tid_t tid);
 
+  /// \return
+  ///     The stop ID of the live process being traced, or an invalid stop ID
+  ///     if the trace is in an error or invalid state.
+  uint32_t GetStopID();
+
 protected:
   /// Get binary data of a live thread given a data identifier.
   ///
@@ -350,8 +364,8 @@ class Trace : public PluginInterface,
   /// The result is cached through the same process stop.
   void RefreshLiveProcessState();
 
+  uint32_t m_stop_id = LLDB_INVALID_STOP_ID;
   /// Process traced by this object if doing live tracing. Otherwise it's null.
-  int64_t m_stop_id = -1;
   Process *m_live_process = nullptr;
   /// tid -> data kind -> size
   std::map<lldb::tid_t, std::unordered_map<std::string, size_t>>

diff  --git a/lldb/include/lldb/Target/TraceCursor.h b/lldb/include/lldb/Target/TraceCursor.h
new file mode 100644
index 0000000000000..c27bba3abf4cb
--- /dev/null
+++ b/lldb/include/lldb/Target/TraceCursor.h
@@ -0,0 +1,139 @@
+//===-- TraceCursor.h -------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_TARGET_TRACE_CURSOR_H
+#define LLDB_TARGET_TRACE_CURSOR_H
+
+#include "lldb/lldb-private.h"
+
+namespace lldb_private {
+
+/// Class used for iterating over the instructions of a thread's trace.
+///
+/// This class attempts to be a generic interface for accessing the instructions
+/// of the trace so that each Trace plug-in can reconstruct, represent and store
+/// the instruction data in an flexible way that is efficient for the given
+/// technology.
+///
+/// Live processes:
+///  In the case of a live process trace, an instance of a \a TraceCursor should
+///  point to the trace at the moment it was collected. If the process is later
+///  resumed and new trace data is collected, that should leave that old cursor
+///  unaffected.
+///
+/// Errors in the trace:
+///  As there could be errors when reconstructing the instructions of a trace,
+///  these errors are represented as failed instructions, and the cursor can
+///  point at them. The consumer should invoke \a TraceCursor::GetError() to
+///  check if the cursor is pointing to either a valid instruction or an error.
+///
+/// Instructions:
+///  A \a TraceCursor always points to a specific instruction or error in the
+///  trace.
+///
+///  The Trace initially points to the last item in the trace.
+///
+/// Sample usage:
+///
+///  TraceCursorUP cursor = trace.GetTrace(thread);
+///
+///  auto granularity = eTraceInstructionControlFlowTypeCall |
+///  eTraceInstructionControlFlowTypeReturn;
+///
+///  do {
+///     if (llvm::Error error = cursor->GetError())
+///       cout << "error found at: " << llvm::toString(error) << endl;
+///     else if (cursor->GetInstructionControlFlowType() &
+///     eTraceInstructionControlFlowTypeCall)
+///       std::cout << "call found at " << cursor->GetLoadAddress() <<
+///       std::endl;
+///     else if (cursor->GetInstructionControlFlowType() &
+///     eTraceInstructionControlFlowTypeReturn)
+///       std::cout << "return found at " << cursor->GetLoadAddress() <<
+///       std::endl;
+///  } while(cursor->Prev(granularity));
+class TraceCursor {
+public:
+  virtual ~TraceCursor() = default;
+
+  /// Move the cursor to the next instruction more recent chronologically in the
+  /// trace given the provided granularity. If such instruction is not found,
+  /// the cursor doesn't move.
+  ///
+  /// \param[in] granularity
+  ///     Bitmask granularity filter. The cursor stops at the next
+  ///     instruction that matches the specified granularity.
+  ///
+  /// \param[in] ignore_errors
+  ///     If \b false, the cursor stops as soon as it finds a failure in the
+  ///     trace and points at it.
+  ///
+  /// \return
+  ///     \b true if the cursor effectively moved and now points to a 
diff erent
+  ///     item in the trace, including errors when \b ignore_errors is \b false.
+  ///     In other words, if \b false is returned, then the trace is pointing at
+  ///     the same item in the trace as before.
+  virtual bool Next(lldb::TraceInstructionControlFlowType granularity =
+                        lldb::eTraceInstructionControlFlowTypeInstruction,
+                    bool ignore_errors = false) = 0;
+
+  /// Similar to \a TraceCursor::Next(), but moves backwards chronologically.
+  virtual bool Prev(lldb::TraceInstructionControlFlowType granularity =
+                        lldb::eTraceInstructionControlFlowTypeInstruction,
+                    bool ignore_errors = false) = 0;
+
+  /// Force the cursor to point to the end of the trace, i.e. the most recent
+  /// item.
+  virtual void SeekToEnd() = 0;
+
+  /// Force the cursor to point to the beginning of the trace, i.e. the oldest
+  /// item.
+  virtual void SeekToBegin() = 0;
+
+  /// \return
+  ///   \b true if the trace corresponds to a live process who has resumed after
+  ///   the trace cursor was created. Otherwise, including the case in which the
+  ///   process is a post-mortem one, return \b false.
+  bool IsStale();
+
+  /// Instruction or error information
+  /// \{
+
+  /// Get the corresponding error message if the cursor points to an error in
+  /// the trace.
+  ///
+  /// \return
+  ///     \b llvm::Error::success if the cursor is not pointing to an error in
+  ///     the trace. Otherwise return an \a llvm::Error describing the issue.
+  virtual llvm::Error GetError() = 0;
+
+  /// \return
+  ///     The load address of the instruction the cursor is pointing at. If the
+  ///     cursor points to an error in the trace, return \b
+  ///     LLDB_INVALID_ADDRESS.
+  virtual lldb::addr_t GetLoadAddress() = 0;
+
+  /// \return
+  ///     The \a lldb::TraceInstructionControlFlowType categories the
+  ///     instruction the cursor is pointing at falls into. If the cursor points
+  ///     to an error in the trace, return \b 0.
+  virtual lldb::TraceInstructionControlFlowType
+  GetInstructionControlFlowType() = 0;
+
+  /// \}
+
+private:
+  /// The stop ID when the cursor was created.
+  uint32_t m_stop_id = 0;
+  /// The trace that owns this cursor.
+  lldb::TraceSP m_trace_sp;
+};
+
+} // namespace lldb_private
+
+#endif // LLDB_TARGET_TRACE_CURSOR_H

diff  --git a/lldb/include/lldb/lldb-defines.h b/lldb/include/lldb/lldb-defines.h
index 487cd0b01d5c5..4bf01c3f86c61 100644
--- a/lldb/include/lldb/lldb-defines.h
+++ b/lldb/include/lldb/lldb-defines.h
@@ -82,6 +82,7 @@
 #define LLDB_REGNUM_GENERIC_ARG8                                               \
   12 // The register that would contain pointer size or less argument 8 (if any)
 /// Invalid value definitions
+#define LLDB_INVALID_STOP_ID 0
 #define LLDB_INVALID_ADDRESS UINT64_MAX
 #define LLDB_INVALID_INDEX32 UINT32_MAX
 #define LLDB_INVALID_IVAR_OFFSET UINT32_MAX

diff  --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h
index 0e22ad517802e..81f6be3eec7d8 100644
--- a/lldb/include/lldb/lldb-enumerations.h
+++ b/lldb/include/lldb/lldb-enumerations.h
@@ -959,6 +959,25 @@ enum ExpressionEvaluationPhase {
   eExpressionEvaluationComplete
 };
 
+/// Architecture-agnostic categorization of instructions for traversing the
+/// control flow of a trace.
+///
+/// A single instruction can match one or more of these categories.
+FLAGS_ENUM(TraceInstructionControlFlowType){
+    /// Any instruction.
+    eTraceInstructionControlFlowTypeInstruction = (1u << 1),
+    /// A conditional or unconditional branch/jump.
+    eTraceInstructionControlFlowTypeBranch = (1u << 2),
+    /// A conditional or unconditional branch/jump that changed
+    /// the control flow of the program.
+    eTraceInstructionControlFlowTypeTakenBranch = (1u << 3),
+    /// A call to a function.
+    eTraceInstructionControlFlowTypeCall = (1u << 4),
+    /// A return from a function.
+    eTraceInstructionControlFlowTypeReturn = (1u << 5)};
+
+LLDB_MARK_AS_BITMASK_ENUM(TraceInstructionControlFlowType)
+
 /// Watchpoint Kind.
 ///
 /// Indicates what types of events cause the watchpoint to fire. Used by Native

diff  --git a/lldb/include/lldb/lldb-forward.h b/lldb/include/lldb/lldb-forward.h
index 27458f0e91548..452dbff029b12 100644
--- a/lldb/include/lldb/lldb-forward.h
+++ b/lldb/include/lldb/lldb-forward.h
@@ -229,6 +229,7 @@ class ThreadPlanTracer;
 class ThreadSpec;
 class ThreadPostMortemTrace;
 class Trace;
+class TraceCursor;
 class TraceSessionFileParser;
 class Type;
 class TypeAndOrName;
@@ -441,6 +442,7 @@ typedef std::shared_ptr<lldb_private::ThreadPostMortemTrace>
 typedef std::weak_ptr<lldb_private::ThreadPlan> ThreadPlanWP;
 typedef std::shared_ptr<lldb_private::ThreadPlanTracer> ThreadPlanTracerSP;
 typedef std::shared_ptr<lldb_private::Trace> TraceSP;
+typedef std::unique_ptr<lldb_private::TraceCursor> TraceCursorUP;
 typedef std::shared_ptr<lldb_private::Type> TypeSP;
 typedef std::weak_ptr<lldb_private::Type> TypeWP;
 typedef std::shared_ptr<lldb_private::TypeCategoryImpl> TypeCategoryImplSP;

diff  --git a/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp b/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp
index fcfa1b01b1d2e..e615b2051f977 100644
--- a/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp
+++ b/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp
@@ -61,7 +61,7 @@ bool CommandObjectThreadTraceStartIntelPT::DoExecuteOnThreads(
     Args &command, CommandReturnObject &result,
     llvm::ArrayRef<lldb::tid_t> tids) {
   if (Error err = m_trace.Start(tids, m_options.m_thread_buffer_size))
-    result.SetError(toString(std::move(err)));
+    result.SetError(Status(std::move(err)));
   else
     result.SetStatus(eReturnStatusSuccessFinishResult);
 
@@ -122,7 +122,7 @@ bool CommandObjectProcessTraceStartIntelPT::DoExecute(
     Args &command, CommandReturnObject &result) {
   if (Error err = m_trace.Start(m_options.m_thread_buffer_size,
                                 m_options.m_process_buffer_size_limit))
-    result.SetError(toString(std::move(err)));
+    result.SetError(Status(std::move(err)));
   else
     result.SetStatus(eReturnStatusSuccessFinishResult);
 

diff  --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp
index ce7db5447f1ae..5546e2e96788a 100644
--- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp
+++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp
@@ -106,6 +106,11 @@ size_t TraceIntelPT::GetCursorPosition(Thread &thread) {
   return decoded_thread->GetCursorPosition();
 }
 
+lldb::TraceCursorUP TraceIntelPT::GetCursor(Thread &thread) {
+  // TODO: to implement
+  return nullptr;
+}
+
 void TraceIntelPT::TraverseInstructions(
     Thread &thread, size_t position, TraceDirection direction,
     std::function<bool(size_t index, Expected<lldb::addr_t> load_addr)>

diff  --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h
index 6bee4b8273574..d25316e0c658d 100644
--- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h
+++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h
@@ -74,6 +74,8 @@ class TraceIntelPT : public Trace {
 
   size_t GetCursorPosition(Thread &thread) override;
 
+  lldb::TraceCursorUP GetCursor(Thread &thread) override;
+
   void DoRefreshLiveProcessState(
       llvm::Expected<TraceGetStateResponse> state) override;
 

diff  --git a/lldb/source/Target/CMakeLists.txt b/lldb/source/Target/CMakeLists.txt
index a94d32a463520..13e9568d52f12 100644
--- a/lldb/source/Target/CMakeLists.txt
+++ b/lldb/source/Target/CMakeLists.txt
@@ -68,6 +68,7 @@ add_lldb_library(lldbTarget
   ThreadSpec.cpp
   ThreadPostMortemTrace.cpp
   Trace.cpp
+  TraceCursor.cpp
   TraceSessionFileParser.cpp
   UnixSignals.cpp
   UnwindAssembly.cpp

diff  --git a/lldb/source/Target/Trace.cpp b/lldb/source/Target/Trace.cpp
index ba8826331314b..c703ba7b93633 100644
--- a/lldb/source/Target/Trace.cpp
+++ b/lldb/source/Target/Trace.cpp
@@ -473,3 +473,8 @@ void Trace::RefreshLiveProcessState() {
 
   DoRefreshLiveProcessState(std::move(live_process_state));
 }
+
+uint32_t Trace::GetStopID() {
+  RefreshLiveProcessState();
+  return m_stop_id;
+}

diff  --git a/lldb/source/Target/TraceCursor.cpp b/lldb/source/Target/TraceCursor.cpp
new file mode 100644
index 0000000000000..80a7dadf3df5c
--- /dev/null
+++ b/lldb/source/Target/TraceCursor.cpp
@@ -0,0 +1,15 @@
+//===-- TraceCursor.cpp -----------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Target/TraceCursor.h"
+
+#include "lldb/Target/Trace.h"
+
+using namespace lldb_private;
+
+bool TraceCursor::IsStale() { return m_stop_id != m_trace_sp->GetStopID(); }


        


More information about the lldb-commits mailing list