diff --git a/lldb/bindings/interface/SBTrace.i b/lldb/bindings/interface/SBTrace.i --- a/lldb/bindings/interface/SBTrace.i +++ b/lldb/bindings/interface/SBTrace.i @@ -15,6 +15,8 @@ public: SBTrace(); + SBTraceCursor CreateNewCursor(SBError &error, SBThread &thread); + const char *GetStartConfigurationHelp(); SBFileSpec SaveToDisk(SBError &error, const SBFileSpec &bundle_dir, bool compact = false); diff --git a/lldb/bindings/interface/SBTraceCursor.i b/lldb/bindings/interface/SBTraceCursor.i new file mode 100644 --- /dev/null +++ b/lldb/bindings/interface/SBTraceCursor.i @@ -0,0 +1,63 @@ +//===-- SWIG Interface for SBTraceCursor.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 +// +//===----------------------------------------------------------------------===// + +namespace lldb { + +%feature("docstring", +"Represents a trace cursor." +) SBTrace; +class LLDB_API SBTraceCursor { +public: + enum class SeekType { + /// The beginning of the trace, i.e the oldest item. + Beginning = 0, + /// The current position in the trace. + Current, + /// The end of the trace, i.e the most recent item. + End + }; + + SBTraceCursor(); + + SBTraceCursor(lldb::TraceCursorSP trace_cursor_sp); + + void SetForwards(bool forwards); + + bool IsForwards() const; + + void Next(); + + bool HasValue(); + + bool GoToId(lldb::user_id_t id); + + bool HasId(lldb::user_id_t id) const; + + lldb::user_id_t GetId() const; + + bool Seek(int64_t offset, SeekType origin); + + lldb::TraceItemKind GetItemKind() const; + + bool IsError() const; + + const char *GetError() const; + + bool IsEvent() const; + + lldb::TraceEvent GetEventType() const; + + const char *GetEventTypeAsString() const; + + bool IsInstruction() const; + + lldb::addr_t GetLoadAddress() const; + + lldb::cpu_id_t GetCPU(SBError &error) const; +}; +} // namespace lldb diff --git a/lldb/bindings/interfaces.swig b/lldb/bindings/interfaces.swig --- a/lldb/bindings/interfaces.swig +++ b/lldb/bindings/interfaces.swig @@ -69,6 +69,7 @@ %include "./interface/SBThreadCollection.i" %include "./interface/SBThreadPlan.i" %include "./interface/SBTrace.i" +%include "./interface/SBTraceCursor.i" %include "./interface/SBType.i" %include "./interface/SBTypeCategory.i" %include "./interface/SBTypeEnumMember.i" diff --git a/lldb/include/lldb/API/SBDefines.h b/lldb/include/lldb/API/SBDefines.h --- a/lldb/include/lldb/API/SBDefines.h +++ b/lldb/include/lldb/API/SBDefines.h @@ -88,6 +88,7 @@ class LLDB_API SBThreadCollection; class LLDB_API SBThreadPlan; class LLDB_API SBTrace; +class LLDB_API SBTraceCursor; class LLDB_API SBType; class LLDB_API SBTypeCategory; class LLDB_API SBTypeEnumMember; diff --git a/lldb/include/lldb/API/SBTrace.h b/lldb/include/lldb/API/SBTrace.h --- a/lldb/include/lldb/API/SBTrace.h +++ b/lldb/include/lldb/API/SBTrace.h @@ -11,6 +11,7 @@ #include "lldb/API/SBDefines.h" #include "lldb/API/SBError.h" +#include "lldb/API/SBTraceCursor.h" namespace lldb { @@ -25,6 +26,19 @@ static SBTrace LoadTraceFromFile(SBError &error, SBDebugger &debugger, const SBFileSpec &trace_description_file); + /// Get a \a TraceCursor for the given thread's trace. + /// + /// \param[out] error + /// This will be set with an error in case of failures. + // + /// \param[in] thread + /// The thread to get a \a TraceCursor for. + // + /// \return + /// A \a SBTraceCursor. If the thread is not traced or its trace + /// information failed to load, an \a llvm::Error is returned. + SBTraceCursor CreateNewCursor(SBError &error, SBThread &thread); + /// Save the trace to the specified directory, which will be created if /// needed. This will also create a a file \a /trace.json with the /// main properties of the trace session, along with others files which diff --git a/lldb/include/lldb/API/SBTraceCursor.h b/lldb/include/lldb/API/SBTraceCursor.h new file mode 100644 --- /dev/null +++ b/lldb/include/lldb/API/SBTraceCursor.h @@ -0,0 +1,190 @@ +//===-- SBTraceCursor.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_API_SBTRACECURSOR_H +#define LLDB_API_SBTRACECURSOR_H + +#include "lldb/API/SBDefines.h" +#include "lldb/API/SBError.h" +#include "lldb/API/SBExecutionContext.h" +#include "lldb/Target/TraceCursor.h" + +namespace lldb { + +class LLDB_API SBTraceCursor { +public: + /// Helper enum to indicate the reference point when invoking / \a + /// SBTraceCursor::Seek(). The following values are inspired by \a + /// std::istream::seekg. + enum class SeekType { + /// The beginning of the trace, i.e the oldest item. + Beginning = 0, + /// The current position in the trace. + Current, + /// The end of the trace, i.e the most recent item. + End + }; + + SBTraceCursor(); + + /// Create a cursor that initially points to the end of the trace, i.e. the + /// most recent item. + SBTraceCursor(lldb::TraceCursorSP trace_cursor_sp); + + /// Set the direction to use in the \a SBTraceCursor::Next() method. + /// + /// \param[in] forwards + /// If \b true, then the traversal will be forwards, otherwise backwards. + void SetForwards(bool forwards); + + /// Check if the direction to use in the \a SBTraceCursor::Next() method is + /// forwards. + /// + /// \return + /// \b true if the current direction is forwards, \b false if backwards. + bool IsForwards() const; + + /// Move the cursor to the next item (instruction or error). + /// + /// Direction: + /// The traversal is done following the current direction of the trace. If + /// it is forwards, the instructions are visited forwards + /// chronologically. Otherwise, the traversal is done in + /// the opposite direction. By default, a cursor moves backwards unless + /// changed with \a SBTraceCursor::SetForwards(). + void Next(); + + /// \return + /// \b true if the cursor is pointing to a valid item. \b false if the + /// cursor has reached the end of the trace. + bool HasValue() const; + + /// Instruction identifiers: + /// + /// When building complex higher level tools, fast random accesses in the + /// trace might be needed, for which each instruction requires a unique + /// identifier within its thread trace. For example, a tool might want to + /// repeatedly inspect random consecutive portions of a trace. This means that + /// it will need to first move quickly to the beginning of each section and + /// then start its iteration. Given that the number of instructions can be in + /// the order of hundreds of millions, fast random access is necessary. + /// + /// An example of such a tool could be an inspector of the call graph of a + /// trace, where each call is represented with its start and end instructions. + /// Inspecting all the instructions of a call requires moving to its first + /// instruction and then iterating until the last instruction, which following + /// the pattern explained above. + /// + /// Instead of using 0-based indices as identifiers, each Trace plug-in can + /// decide the nature of these identifiers and thus no assumptions can be made + /// regarding their ordering and sequentiality. The reason is that an + /// instruction might be encoded by the plug-in in a way that hides its actual + /// 0-based index in the trace, but it's still possible to efficiently find + /// it. + /// + /// Requirements: + /// - For a given thread, no two instructions have the same id. + /// - In terms of efficiency, moving the cursor to a given id should be as + /// fast as possible, but not necessarily O(1). That's why the recommended + /// way to traverse sequential instructions is to use the \a + /// SBTraceCursor::Next() method and only use \a SBTraceCursor::GoToId(id) + /// sparingly. + + /// Make the cursor point to the item whose identifier is \p id. + /// + /// \return + /// \b true if the given identifier exists and the cursor effectively + /// moved to it. Otherwise, \b false is returned and the cursor now points + /// to an invalid item, i.e. calling \a HasValue() will return \b false. + bool GoToId(lldb::user_id_t id); + + /// \return + /// \b true if and only if there's an instruction item with the given \p + /// id. + bool HasId(lldb::user_id_t id) const; + + /// \return + /// A unique identifier for the instruction or error this cursor is + /// pointing to. + lldb::user_id_t GetId() const; + /// \} + + /// Make the cursor point to an item in the trace based on an origin point and + /// an offset. + /// + /// The resulting position of the trace is + /// origin + offset + /// + /// If this resulting position would be out of bounds, the trace then points + /// to an invalid item, i.e. calling \a HasValue() returns \b false. + /// + /// \param[in] offset + /// How many items to move forwards (if positive) or backwards (if + /// negative) from the given origin point. For example, if origin is \b + /// End, then a negative offset would move backward in the trace, but a + /// positive offset would move past the trace to an invalid item. + /// + /// \param[in] origin + /// The reference point to use when moving the cursor. + /// + /// \return + /// \b true if and only if the cursor ends up pointing to a valid item. + bool Seek(int64_t offset, SeekType origin); + + /// \return + /// The \a ExecutionContextRef of the backing thread from the creation time + /// of this cursor. + SBExecutionContext &GetExecutionContextRef(); + + /// Trace item information (instructions, errors and events) + /// \{ + + /// \return + /// The kind of item the cursor is pointing at. + lldb::TraceItemKind GetItemKind() const; + + /// \return + /// Whether the cursor points to an error or not. + bool IsError() const; + + /// \return + /// The error message the cursor is pointing at. + const char *GetError() const; + + /// \return + /// Whether the cursor points to an event or not. + bool IsEvent() const; + + /// \return + /// The specific kind of event the cursor is pointing at, or \b + /// TraceEvent::eTraceEventNone if the cursor not pointing to an event. + lldb::TraceEvent GetEventType() const; + + /// \return + /// A human-readable description of the event this cursor is pointing at. + const char *GetEventTypeAsString() const; + + /// \return + /// Whether the cursor points to an instruction. + bool IsInstruction() const; + + /// \return + /// The load address of the instruction the cursor is pointing at. + lldb::addr_t GetLoadAddress() const; + + // TODO: should we define LLDB_INVALID_CPU_ID so this matches the behavior of + // `GetLoadAddress()`? + lldb::cpu_id_t GetCPU(SBError &error) const; + +protected: + lldb::TraceCursorSP m_opaque_sp; +}; +} // namespace lldb + +#endif // LLDB_API_SBTRACECURSOR_H diff --git a/lldb/source/API/CMakeLists.txt b/lldb/source/API/CMakeLists.txt --- a/lldb/source/API/CMakeLists.txt +++ b/lldb/source/API/CMakeLists.txt @@ -72,6 +72,7 @@ SBThreadCollection.cpp SBThreadPlan.cpp SBTrace.cpp + SBTraceCursor.cpp SBType.cpp SBTypeCategory.cpp SBTypeEnumMember.cpp diff --git a/lldb/source/API/SBTrace.cpp b/lldb/source/API/SBTrace.cpp --- a/lldb/source/API/SBTrace.cpp +++ b/lldb/source/API/SBTrace.cpp @@ -43,6 +43,22 @@ return SBTrace(trace_or_err.get()); } +SBTraceCursor SBTrace::CreateNewCursor(SBError &error, SBThread &thread) { + LLDB_INSTRUMENT_VA(this); + if (!m_opaque_sp || !thread.get()) { + error.SetErrorString("error: invalid trace"); + return SBTraceCursor(); + } + + if (llvm::Expected trace_cursor_sp = + m_opaque_sp->CreateNewCursor(*thread.get())) { + return SBTraceCursor(std::move(*trace_cursor_sp)); + } else { + error.SetErrorString(llvm::toString(trace_cursor_sp.takeError()).c_str()); + return SBTraceCursor(); + } +} + SBFileSpec SBTrace::SaveToDisk(SBError &error, const SBFileSpec &bundle_dir, bool compact) { LLDB_INSTRUMENT_VA(this, error, bundle_dir, compact); diff --git a/lldb/source/API/SBTraceCursor.cpp b/lldb/source/API/SBTraceCursor.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/API/SBTraceCursor.cpp @@ -0,0 +1,129 @@ +//===-- SBTraceCursor.cpp +//-------------------------------------------------------===// +// +// 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/API/SBTraceCursor.h" +#include "Utils.h" +#include "lldb/Utility/Instrumentation.h" + +using namespace lldb; +using namespace lldb_private; + +// relevant to the clone issue: +// https://stackoverflow.com/questions/16030081/copy-constructor-for-a-class-with-unique-ptr +SBTraceCursor::SBTraceCursor() { LLDB_INSTRUMENT_VA(this); } + +SBTraceCursor::SBTraceCursor(TraceCursorSP trace_cursor_sp) + : m_opaque_sp{std::move(trace_cursor_sp)} { + LLDB_INSTRUMENT_VA(this, trace_cursor_sp); +} + +void SBTraceCursor::SetForwards(bool forwards) { + LLDB_INSTRUMENT_VA(this, forwards); + m_opaque_sp->SetForwards(forwards); +} + +bool SBTraceCursor::IsForwards() const { + LLDB_INSTRUMENT_VA(this); + return m_opaque_sp->IsForwards(); +} + +void SBTraceCursor::Next() { + LLDB_INSTRUMENT_VA(this); + return m_opaque_sp->Next(); +} + +bool SBTraceCursor::HasValue() const { + LLDB_INSTRUMENT_VA(this); + return m_opaque_sp->HasValue(); +} + +bool SBTraceCursor::GoToId(lldb::user_id_t id) { + LLDB_INSTRUMENT_VA(this, id); + return m_opaque_sp->GoToId(id); +} + +bool SBTraceCursor::HasId(lldb::user_id_t id) const { + LLDB_INSTRUMENT_VA(this, id); + return m_opaque_sp->HasId(id); +} + +lldb::user_id_t SBTraceCursor::GetId() const { + LLDB_INSTRUMENT_VA(this); + return m_opaque_sp->GetId(); +} + +bool SBTraceCursor::Seek(int64_t offset, SeekType origin) { + LLDB_INSTRUMENT_VA(this, offset, origin); + // Convert from public `SBTraceCursor::SeekType` enum to private + // `TraceCursor::SeekType` enum. + auto convert_seek_type_enum = [](SeekType seek_type) { + switch (seek_type) { + case SeekType::Beginning: + return TraceCursor::SeekType::Beginning; + case SeekType::Current: + return TraceCursor::SeekType::Current; + case SeekType::End: + return TraceCursor::SeekType::End; + }; + }; + + return m_opaque_sp->Seek(offset, convert_seek_type_enum(origin)); +} + +lldb::TraceItemKind SBTraceCursor::GetItemKind() const { + LLDB_INSTRUMENT_VA(this); + return m_opaque_sp->GetItemKind(); +} + +bool SBTraceCursor::IsError() const { + LLDB_INSTRUMENT_VA(this); + return m_opaque_sp->IsError(); +} + +const char *SBTraceCursor::GetError() const { + LLDB_INSTRUMENT_VA(this); + return m_opaque_sp->GetError(); +} + +bool SBTraceCursor::IsEvent() const { + LLDB_INSTRUMENT_VA(this); + return m_opaque_sp->IsEvent(); +} + +lldb::TraceEvent SBTraceCursor::GetEventType() const { + LLDB_INSTRUMENT_VA(this); + return m_opaque_sp->GetEventType(); +} + +const char *SBTraceCursor::GetEventTypeAsString() const { + LLDB_INSTRUMENT_VA(this); + return m_opaque_sp->GetEventTypeAsString(); +} + +bool SBTraceCursor::IsInstruction() const { + LLDB_INSTRUMENT_VA(this); + return m_opaque_sp->IsInstruction(); +} + +lldb::addr_t SBTraceCursor::GetLoadAddress() const { + LLDB_INSTRUMENT_VA(this); + return m_opaque_sp->GetLoadAddress(); +} + +// TODO: should we define LLDB_INVALID_CPU_ID so this matches the behavior of +// `GetLoadAddress()`? +lldb::cpu_id_t SBTraceCursor::GetCPU(SBError &error) const { + LLDB_INSTRUMENT_VA(this, error); + if (auto cpu_opt = m_opaque_sp->GetCPU()) { + return *cpu_opt; + } else { + error.SetErrorString("CPU id unknown for current trace item"); + return 0; + } +} 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 @@ -264,3 +264,84 @@ expected_substrs = ['error: missing value at traceBundle.processes[1].pid'] self.traceLoad(traceDescriptionFilePath=trace_description_file_path, error=True, substrs=expected_substrs) self.assertEqual(self.dbg.GetNumTargets(), 0) + + def testLoadTraceCursor(self): + src_dir = self.getSourceDir() + trace_description_file_path = os.path.join(src_dir, "intelpt-multi-core-trace", "trace.json") + traceDescriptionFile = lldb.SBFileSpec(trace_description_file_path, True) + + error = lldb.SBError() + trace = self.dbg.LoadTraceFromFile(error, traceDescriptionFile) + self.assertSBError(error) + + target = self.dbg.GetSelectedTarget() + process = target.process + + # Helper function to check equality of the current item of two trace cursors. + def assertCurrentTraceCursorItemEqual(lhs, rhs): + self.assertTrue(lhs.HasValue() and rhs.HasValue()) + + self.assertEqual(lhs.GetId(), rhs.GetId()) + self.assertEqual(lhs.GetItemKind(), rhs.GetItemKind()) + if lhs.IsError(): + self.assertEqual(lhs.GetError(), rhs.GetError()) + elif lhs.IsEvent(): + self.assertEqual(lhs.GetEventType(), rhs.GetEventType()) + self.assertEqual(lhs.GetEventTypeAsString(), rhs.GetEventTypeAsString()) + elif lhs.IsInstruction(): + self.assertEqual(lhs.GetLoadAddress(), rhs.GetLoadAddress()) + else: + self.fail("Unknown trace item kind") + + for thread in process.threads: + sequentialTraversalCursor = trace.CreateNewCursor(error, thread) + self.assertSBError(error) + # Skip threads with no trace items + if not sequentialTraversalCursor.HasValue(): + continue + + + # Test "End" boundary of the trace by advancing past the trace's last item. + sequentialTraversalCursor.Seek(0, lldb.SBTraceCursor.End) + self.assertTrue(sequentialTraversalCursor.HasValue()) + sequentialTraversalCursor.SetForwards(True) + sequentialTraversalCursor.Next() + self.assertFalse(sequentialTraversalCursor.HasValue()) + + + + # Test sequential traversal using sequential access API (ie Next()) + # and random access API (ie GoToId()) simultaneously. + randomAccessCursor = trace.CreateNewCursor(error, thread) + self.assertSBError(error) + # Reset the sequential cursor + sequentialTraversalCursor.Seek(0, lldb.SBTraceCursor.Beginning) + sequentialTraversalCursor.SetForwards(True) + self.assertTrue(sequentialTraversalCursor.IsForwards()) + + while sequentialTraversalCursor.HasValue(): + itemId = sequentialTraversalCursor.GetId() + randomAccessCursor.GoToId(itemId) + assertCurrentTraceCursorItemEqual(sequentialTraversalCursor, randomAccessCursor) + sequentialTraversalCursor.Next() + + + + # Test a random access with random access API (ie Seek()) and + # sequential access API (ie consecutive calls to Next()). + TEST_SEEK_ID = 3 + randomAccessCursor.GoToId(TEST_SEEK_ID ) + # Reset the sequential cursor + sequentialTraversalCursor.Seek(0, lldb.SBTraceCursor.Beginning) + sequentialTraversalCursor.SetForwards(True) + for _ in range(TEST_SEEK_ID): sequentialTraversalCursor.Next() + assertCurrentTraceCursorItemEqual(sequentialTraversalCursor, randomAccessCursor) + + + + + + + + +