diff --git a/tools/ctrace/src/decode/DwtPacketDecoder.cpp b/tools/ctrace/src/decode/DwtPacketDecoder.cpp index c61b2a6c0..ce3d12ffe 100644 --- a/tools/ctrace/src/decode/DwtPacketDecoder.cpp +++ b/tools/ctrace/src/decode/DwtPacketDecoder.cpp @@ -75,7 +75,7 @@ std::vector DwtPacketDecoder::decode(const DwtPayloadPacket& payload if (source == DwtPacketSource::ExceptionTrace) { output = flush(payload.quality, payload.tcyc); - const auto exceptionNumber = payload.value & kExceptionNumberMask; + const auto exceptionNumber = static_cast(payload.value & kExceptionNumberMask); const auto action = exceptionAction((payload.value >> kExceptionActionShift) & kExceptionActionMask); if (action == ExceptionAction::Unknown) { TraceEvent error{TraceIssueEvent{ diff --git a/tools/ctrace/src/model/TraceEvent.h b/tools/ctrace/src/model/TraceEvent.h index 49c414d3a..de3867174 100644 --- a/tools/ctrace/src/model/TraceEvent.h +++ b/tools/ctrace/src/model/TraceEvent.h @@ -115,9 +115,12 @@ inline std::optional dwtAddressOffset(const DwtAddressTraceEvent& return std::nullopt; } +/** @brief Stores a decoded Cortex-M exception number (9 bits on the wire). */ +using ExceptionNumber = std::uint16_t; + /** @brief Contains a decoded exception transition. */ struct ExceptionTraceEvent { - std::uint32_t number = 0; + ExceptionNumber number = 0; ExceptionAction action = ExceptionAction::Unknown; }; diff --git a/tools/ctrace/src/output/ctf/CtfEncoder.cpp b/tools/ctrace/src/output/ctf/CtfEncoder.cpp index cd6427f58..9809e14a3 100644 --- a/tools/ctrace/src/output/ctf/CtfEncoder.cpp +++ b/tools/ctrace/src/output/ctf/CtfEncoder.cpp @@ -345,6 +345,18 @@ void CtfEncoder::writeGlobalTimestampEvent(const TraceEvent& event, const Global void CtfEncoder::writeTraceStatusEvent(std::uint8_t reason, std::uint8_t traceBusId, bool emitEvent) { + if (reason == CtfSchema::value(CtfSchema::TraceStatusReason::Overflow) || + reason == CtfSchema::value(CtfSchema::TraceStatusReason::DataLoss)) { + const auto lane = m_exceptionLanes.find(traceBusId); + if (lane != m_exceptionLanes.end()) { + lane->second.resetForDiscontinuity( + [this, traceBusId](ExceptionNumber number, CtfExceptionLaneTracker::RecordAction action, + CtfExceptionLaneTracker::RecordOrigin origin) { + emitExceptionRecord(traceBusId, number, action, origin); + }); + } + } + if (emitEvent) { constexpr auto payloadSize = 1U + 4U; const auto eventTimestamp = allocateEventTimestamp(traceBusId); @@ -354,48 +366,49 @@ void CtfEncoder::writeTraceStatusEvent(std::uint8_t reason, std::uint8_t traceBu record.writeU32(ctfOverflowCount(m_streamStates[traceBusId].overflowCount)); }); } - - if (reason == CtfSchema::value(CtfSchema::TraceStatusReason::Overflow) || - reason == CtfSchema::value(CtfSchema::TraceStatusReason::DataLoss)) { - const auto lane = m_exceptionLanes.find(traceBusId); - if (lane != m_exceptionLanes.end()) { - lane->second.resetForDiscontinuity( - [this, traceBusId](std::uint32_t number, CtfExceptionLaneTracker::RecordAction action) { - emitExceptionRecord(traceBusId, number, action); - }); - } - } } void CtfEncoder::writeExceptionEvent(std::uint8_t traceBusId, const ExceptionTraceEvent& exception) { exceptionLane(traceBusId) - .consume(exception, [this, traceBusId](std::uint32_t number, CtfExceptionLaneTracker::RecordAction action) { - emitExceptionRecord(traceBusId, number, action); + .consume(exception, [this, traceBusId](ExceptionNumber number, CtfExceptionLaneTracker::RecordAction action, + CtfExceptionLaneTracker::RecordOrigin origin) { + emitExceptionRecord(traceBusId, number, action, origin); }); } -void CtfEncoder::emitExceptionRecord(std::uint8_t traceBusId, std::uint32_t number, - CtfExceptionLaneTracker::RecordAction action) +void CtfEncoder::emitExceptionRecord(std::uint8_t traceBusId, ExceptionNumber number, + CtfExceptionLaneTracker::RecordAction action, + CtfExceptionLaneTracker::RecordOrigin origin) { + const auto semanticAction = + action == CtfExceptionLaneTracker::RecordAction::Enter + ? ExceptionAction::Entered + : action == CtfExceptionLaneTracker::RecordAction::Exit ? ExceptionAction::Exited : ExceptionAction::Returned; TraceEvent selectionEvent{ExceptionTraceEvent{ number, - action == CtfExceptionLaneTracker::RecordAction::Enter ? ExceptionAction::Entered : ExceptionAction::Exited, + semanticAction, }}; selectionEvent.traceBusId = traceBusId; if (!traceEventSelectedForOutput(selectionEvent, m_config.selection)) { return; } - constexpr auto payloadSize = 2U + 1U + 2U; + constexpr auto payloadSize = 2U + 1U + 2U + 1U; const auto eventTimestamp = allocateEventTimestamp(traceBusId); - const auto encodedAction = - CtfSchema::value(action == CtfExceptionLaneTracker::RecordAction::Enter ? CtfSchema::ExceptionAction::Entered - : CtfSchema::ExceptionAction::Exited); + const auto encodedAction = CtfSchema::value( + action == CtfExceptionLaneTracker::RecordAction::Enter + ? CtfSchema::ExceptionAction::Entered + : action == CtfExceptionLaneTracker::RecordAction::Exit ? CtfSchema::ExceptionAction::Exited + : CtfSchema::ExceptionAction::Returned); + const auto encodedOrigin = CtfSchema::value(origin == CtfExceptionLaneTracker::RecordOrigin::Trace + ? CtfSchema::ExceptionOrigin::Trace + : CtfSchema::ExceptionOrigin::Synthetic); m_stream.writeRecord(CtfSchema::value(CtfSchema::EventId::Exception), eventTimestamp, traceBusId, payloadSize, [&](CtfStreamWriter::Record& record) { - record.writeU16(static_cast(number & 0xffffU)); + record.writeU16(number); record.writeU8(encodedAction); - record.writeU16(static_cast(number & 0xffffU)); + record.writeU16(number); + record.writeU8(encodedOrigin); }); } @@ -404,8 +417,9 @@ CtfExceptionLaneTracker& CtfEncoder::exceptionLane(std::uint8_t traceBusId) const auto [lane, inserted] = m_exceptionLanes.try_emplace(traceBusId); if (inserted) { lane->second.startThreadMode( - [this, traceBusId](std::uint32_t number, CtfExceptionLaneTracker::RecordAction action) { - emitExceptionRecord(traceBusId, number, action); + [this, traceBusId](ExceptionNumber number, CtfExceptionLaneTracker::RecordAction action, + CtfExceptionLaneTracker::RecordOrigin origin) { + emitExceptionRecord(traceBusId, number, action, origin); }); } return lane->second; @@ -428,7 +442,7 @@ std::pair CtfEncoder::computeSampleQuality(const Tr void CtfEncoder::writeMetadataFile() { - std::set observedExceptionNumbers; + std::set observedExceptionNumbers; for (const auto& [traceBusId, lane] : m_exceptionLanes) { (void)traceBusId; observedExceptionNumbers.insert(lane.observedExceptionNumbers().begin(), lane.observedExceptionNumbers().end()); diff --git a/tools/ctrace/src/output/ctf/CtfEncoder.h b/tools/ctrace/src/output/ctf/CtfEncoder.h index d7659eb39..bb5c65954 100644 --- a/tools/ctrace/src/output/ctf/CtfEncoder.h +++ b/tools/ctrace/src/output/ctf/CtfEncoder.h @@ -80,7 +80,9 @@ class CtfEncoder final { /** @brief Applies one exception transition to its CTF lane state. */ void writeExceptionEvent(std::uint8_t traceBusId, const ExceptionTraceEvent& exception); /** @brief Emits one concrete exception lane record. */ - void emitExceptionRecord(std::uint8_t traceBusId, std::uint32_t number, CtfExceptionLaneTracker::RecordAction action); + void emitExceptionRecord(std::uint8_t traceBusId, ExceptionNumber number, + CtfExceptionLaneTracker::RecordAction action, + CtfExceptionLaneTracker::RecordOrigin origin); /** @brief Returns the exception tracker for one stream. */ CtfExceptionLaneTracker& exceptionLane(std::uint8_t traceBusId); /** @brief Computes CTF sample flags and saturated overflow count. */ diff --git a/tools/ctrace/src/output/ctf/CtfExceptionLaneTracker.cpp b/tools/ctrace/src/output/ctf/CtfExceptionLaneTracker.cpp index 4b9ee03ef..51d21d343 100644 --- a/tools/ctrace/src/output/ctf/CtfExceptionLaneTracker.cpp +++ b/tools/ctrace/src/output/ctf/CtfExceptionLaneTracker.cpp @@ -15,75 +15,83 @@ void CtfExceptionLaneTracker::startThreadMode(const RecordEmitter& emit) { - setActiveContext(kThreadModeNumber, emit); + setActiveContext(kThreadModeNumber, RecordAction::Enter, RecordOrigin::Synthetic, emit); } void CtfExceptionLaneTracker::resetForDiscontinuity(const RecordEmitter& emit) { m_contextStack.clear(); - closeActiveContext(emit); + closeActiveContext(RecordOrigin::Synthetic, emit); } void CtfExceptionLaneTracker::consume(const ExceptionTraceEvent& event, const RecordEmitter& emit) { - const auto number = event.number & 0x1ffU; + const auto number = event.number; switch (event.action) { case ExceptionAction::Entered: enterContext(number); - break; + updateActiveContext(RecordAction::Enter, RecordOrigin::Trace, emit); + return; case ExceptionAction::Exited: - exitContext(number); - break; + if (exitContext(number)) { + closeActiveContext(RecordOrigin::Trace, emit); + } + return; case ExceptionAction::Returned: returnToContext(number); - break; + updateActiveContext(RecordAction::Return, RecordOrigin::Trace, emit); + return; case ExceptionAction::Unknown: return; } - updateActiveContext(emit); } -const std::vector& CtfExceptionLaneTracker::observedExceptionNumbers() const +const std::vector& CtfExceptionLaneTracker::observedExceptionNumbers() const { return m_observedExceptionNumbers; } -void CtfExceptionLaneTracker::setActiveContext(std::uint32_t number, const RecordEmitter& emit) +void CtfExceptionLaneTracker::setActiveContext(ExceptionNumber number, RecordAction action, RecordOrigin origin, + const RecordEmitter& emit) { if (m_activeContextNumber.has_value() && *m_activeContextNumber == number) { + if (action == RecordAction::Return) { + emitRecord(number, action, origin, emit); + } return; } if (m_activeContextNumber.has_value()) { - emitRecord(*m_activeContextNumber, RecordAction::Exit, emit); + emitRecord(*m_activeContextNumber, RecordAction::Exit, RecordOrigin::Synthetic, emit); } - emitRecord(number, RecordAction::Enter, emit); + emitRecord(number, action, origin, emit); m_activeContextNumber = number; } -void CtfExceptionLaneTracker::closeActiveContext(const RecordEmitter& emit) +void CtfExceptionLaneTracker::closeActiveContext(RecordOrigin origin, const RecordEmitter& emit) { if (!m_activeContextNumber.has_value()) { return; } - emitRecord(*m_activeContextNumber, RecordAction::Exit, emit); + emitRecord(*m_activeContextNumber, RecordAction::Exit, origin, emit); m_activeContextNumber.reset(); } -void CtfExceptionLaneTracker::updateActiveContext(const RecordEmitter& emit) +void CtfExceptionLaneTracker::updateActiveContext(RecordAction action, RecordOrigin origin, const RecordEmitter& emit) { - setActiveContext(m_contextStack.empty() ? kThreadModeNumber : m_contextStack.back().number, emit); + setActiveContext(m_contextStack.empty() ? kThreadModeNumber : m_contextStack.back().number, action, origin, emit); } -void CtfExceptionLaneTracker::emitRecord(std::uint32_t number, RecordAction action, const RecordEmitter& emit) +void CtfExceptionLaneTracker::emitRecord(ExceptionNumber number, RecordAction action, RecordOrigin origin, + const RecordEmitter& emit) { if (std::find(m_observedExceptionNumbers.begin(), m_observedExceptionNumbers.end(), number) == m_observedExceptionNumbers.end()) { m_observedExceptionNumbers.push_back(number); } - emit(number, action); + emit(number, action, origin); } -void CtfExceptionLaneTracker::enterContext(std::uint32_t number) +void CtfExceptionLaneTracker::enterContext(ExceptionNumber number) { if (!m_contextStack.empty() && m_contextStack.back().state == ContextState::Running) { m_contextStack.back().state = ContextState::Preempted; @@ -91,16 +99,17 @@ void CtfExceptionLaneTracker::enterContext(std::uint32_t number) m_contextStack.push_back({number, ContextState::Running}); } -void CtfExceptionLaneTracker::exitContext(std::uint32_t number) +bool CtfExceptionLaneTracker::exitContext(ExceptionNumber number) { if (m_contextStack.empty() || m_contextStack.back().number != number || m_contextStack.back().state != ContextState::Running) { - return; + return false; } m_contextStack.pop_back(); + return true; } -void CtfExceptionLaneTracker::returnToContext(std::uint32_t number) +void CtfExceptionLaneTracker::returnToContext(ExceptionNumber number) { while (!m_contextStack.empty() && m_contextStack.back().number != number) { m_contextStack.pop_back(); diff --git a/tools/ctrace/src/output/ctf/CtfExceptionLaneTracker.h b/tools/ctrace/src/output/ctf/CtfExceptionLaneTracker.h index 67aa813d6..d50e89076 100644 --- a/tools/ctrace/src/output/ctf/CtfExceptionLaneTracker.h +++ b/tools/ctrace/src/output/ctf/CtfExceptionLaneTracker.h @@ -18,14 +18,21 @@ /** @brief Tracks active Cortex-M exception contexts for Trace Compass lanes. */ class CtfExceptionLaneTracker { public: - /** @brief Selects whether an emitted lane record enters or exits a context. */ + /** @brief Selects whether an emitted lane record enters, exits, or returns to a context. */ enum class RecordAction { Enter, Exit, + Return, + }; + + /** @brief Identifies whether a record came from trace input or lane reconstruction. */ + enum class RecordOrigin { + Trace, + Synthetic, }; /** @brief Emits one exception-lane record. */ - using RecordEmitter = std::function; + using RecordEmitter = std::function; /** @brief Starts the initial thread-mode context. */ void startThreadMode(const RecordEmitter& emit); @@ -34,10 +41,10 @@ class CtfExceptionLaneTracker { /** @brief Applies one exception transition and emits resulting lane records. */ void consume(const ExceptionTraceEvent& event, const RecordEmitter& emit); /** @brief Returns exception numbers observed by this tracker. */ - const std::vector& observedExceptionNumbers() const; + const std::vector& observedExceptionNumbers() const; private: - static constexpr std::uint32_t kThreadModeNumber = 0; + static constexpr ExceptionNumber kThreadModeNumber = 0; /** @brief Identifies whether an exception context is active or preempted. */ enum class ContextState : std::uint8_t { @@ -47,28 +54,28 @@ class CtfExceptionLaneTracker { /** @brief Stores one exception context on the nesting stack. */ struct ContextFrame { - std::uint32_t number = 0; + ExceptionNumber number = 0; ContextState state = ContextState::Running; }; - /** @brief Switches the emitted active lane to one context. */ - void setActiveContext(std::uint32_t number, const RecordEmitter& emit); + /** @brief Switches the emitted active lane to one context with the requested activation action. */ + void setActiveContext(ExceptionNumber number, RecordAction action, RecordOrigin origin, const RecordEmitter& emit); /** @brief Closes the currently emitted active lane. */ - void closeActiveContext(const RecordEmitter& emit); - /** @brief Reconciles emitted lane state with the context stack. */ - void updateActiveContext(const RecordEmitter& emit); + void closeActiveContext(RecordOrigin origin, const RecordEmitter& emit); + /** @brief Activates the context selected by the stack after an enter or return. */ + void updateActiveContext(RecordAction action, RecordOrigin origin, const RecordEmitter& emit); /** @brief Emits and records one lane transition. */ - void emitRecord(std::uint32_t number, RecordAction action, const RecordEmitter& emit); + void emitRecord(ExceptionNumber number, RecordAction action, RecordOrigin origin, const RecordEmitter& emit); /** @brief Pushes or reactivates an entered exception context. */ - void enterContext(std::uint32_t number); - /** @brief Removes an exited exception context. */ - void exitContext(std::uint32_t number); + void enterContext(ExceptionNumber number); + /** @brief Removes an exited running exception context. */ + bool exitContext(ExceptionNumber number); /** @brief Returns the stack to a previously active context. */ - void returnToContext(std::uint32_t number); + void returnToContext(ExceptionNumber number); std::vector m_contextStack; - std::optional m_activeContextNumber; - std::vector m_observedExceptionNumbers; + std::optional m_activeContextNumber; + std::vector m_observedExceptionNumbers; }; #endif // CTRACE_SRC_OUTPUT_CTF_CTFEXCEPTIONLANETRACKER_H diff --git a/tools/ctrace/src/output/ctf/CtfMetadataWriter.cpp b/tools/ctrace/src/output/ctf/CtfMetadataWriter.cpp index 9632103e1..b3bd845b3 100644 --- a/tools/ctrace/src/output/ctf/CtfMetadataWriter.cpp +++ b/tools/ctrace/src/output/ctf/CtfMetadataWriter.cpp @@ -88,10 +88,10 @@ static std::string mapValueOrEmpty(const std::map& v return found == values.end() ? "" : found->second; } -static std::vector -exceptionNumbersWithDefaults(const std::vector& observedExceptionNumbers) +static std::vector +exceptionNumbersWithDefaults(const std::vector& observedExceptionNumbers) { - std::vector exceptions = {0, 1, 2, 3, 4, 5, 6, 7, 11, 12, 14, 15}; + std::vector exceptions = {0, 1, 2, 3, 4, 5, 6, 7, 11, 12, 14, 15}; for (const auto number : observedExceptionNumbers) { if (std::find(exceptions.begin(), exceptions.end(), number) == exceptions.end()) { exceptions.push_back(number); @@ -102,11 +102,11 @@ exceptionNumbersWithDefaults(const std::vector& observedException } /** @brief Returns the architectural name for a known Cortex-M exception. */ -static std::string exceptionName(std::uint32_t number) +static std::string exceptionName(ExceptionNumber number) { switch (number) { case 0: - return "ThreadMode"; + return "Thread Mode"; case 1: return "Reset"; case 2: @@ -256,7 +256,7 @@ clock { /** @brief Writes reusable TSDL type and enumeration declarations. */ static void writeTypeDefinitions(std::ostream& out, const MetadataSymbols& symbols, - const std::vector& observedExceptionNumbers) + const std::vector& observedExceptionNumbers) { out << R"( typealias integer { size = 8; align = 8; signed = false; } := uint8_t; @@ -291,8 +291,16 @@ typealias enum : uint8_t { "entered" = )" << static_cast(CtfSchema::value(CtfSchema::ExceptionAction::Entered)) << R"(, "exited" = )" - << static_cast(CtfSchema::value(CtfSchema::ExceptionAction::Exited)) << R"( + << static_cast(CtfSchema::value(CtfSchema::ExceptionAction::Exited)) << R"(, + "returned" = )" + << static_cast(CtfSchema::value(CtfSchema::ExceptionAction::Returned)) << R"( } := cmsis_exception_action_t; +typealias enum : uint8_t { + "trace" = )" + << static_cast(CtfSchema::value(CtfSchema::ExceptionOrigin::Trace)) << R"(, + "synthetic" = )" + << static_cast(CtfSchema::value(CtfSchema::ExceptionOrigin::Synthetic)) << R"( +} := cmsis_exception_origin_t; typealias enum : uint8_t { )"; std::set itmLabels; @@ -446,6 +454,7 @@ event { cmsis_exception_number_t cmsis_exception_number; cmsis_exception_action_t cmsis_exception_action; uint16_t cmsis_exception_number_value; + cmsis_exception_origin_t cmsis_exception_origin; }; }; @@ -466,7 +475,7 @@ event { void CtfMetadataWriter::write(const std::filesystem::path& outputDir, const std::string& uuidString, std::uint64_t coreClockHz, const std::vector& sources, - const std::vector& observedExceptionNumbers) + const std::vector& observedExceptionNumbers) { const auto metadataPath = outputDir / "metadata"; std::ofstream out(metadataPath, std::ios::out | std::ios::trunc); diff --git a/tools/ctrace/src/output/ctf/CtfMetadataWriter.h b/tools/ctrace/src/output/ctf/CtfMetadataWriter.h index a6232f1de..e385117ee 100644 --- a/tools/ctrace/src/output/ctf/CtfMetadataWriter.h +++ b/tools/ctrace/src/output/ctf/CtfMetadataWriter.h @@ -8,6 +8,7 @@ #ifndef CTRACE_SRC_OUTPUT_CTF_CTFMETADATAWRITER_H #define CTRACE_SRC_OUTPUT_CTF_CTFMETADATAWRITER_H +#include "TraceEvent.h" #include "TraceOutputConfig.h" #include @@ -21,7 +22,7 @@ class CtfMetadataWriter final { /** @brief Writes metadata for clock, event schemas, sources, and exception lanes. */ static void write(const std::filesystem::path& outputDir, const std::string& uuidString, std::uint64_t coreClockHz, const std::vector& sources, - const std::vector& observedExceptionNumbers); + const std::vector& observedExceptionNumbers); private: /** @brief Prevents construction of this stateless metadata utility. */ diff --git a/tools/ctrace/src/output/ctf/CtfSchema.h b/tools/ctrace/src/output/ctf/CtfSchema.h index dee4dd6f9..26f943ba0 100644 --- a/tools/ctrace/src/output/ctf/CtfSchema.h +++ b/tools/ctrace/src/output/ctf/CtfSchema.h @@ -43,10 +43,17 @@ enum class DwtAccess : std::uint8_t { Write = 1U, }; -/** @brief Encodes exception entry and exit actions in CTF records. */ +/** @brief Encodes exception entry, exit, and return actions in CTF records. */ enum class ExceptionAction : std::uint8_t { Entered = 0U, Exited = 1U, + Returned = 2U, +}; + +/** @brief Identifies whether an exception record came from trace input or lane reconstruction. */ +enum class ExceptionOrigin : std::uint8_t { + Trace = 0U, + Synthetic = 1U, }; /** @brief Identifies the supported CTF sample value encodings. */ @@ -147,6 +154,12 @@ constexpr std::uint8_t value(ExceptionAction action) return static_cast(action); } +/** @brief Returns the integer representation of an exception record origin. */ +constexpr std::uint8_t value(ExceptionOrigin origin) +{ + return static_cast(origin); +} + /** @brief Returns the stable schema name of a CTF event ID. */ constexpr std::string_view eventName(EventId id) { diff --git a/tools/ctrace/src/output/ctf/CtfStreamWriter.h b/tools/ctrace/src/output/ctf/CtfStreamWriter.h index 8cae8baea..e48ea6fc5 100644 --- a/tools/ctrace/src/output/ctf/CtfStreamWriter.h +++ b/tools/ctrace/src/output/ctf/CtfStreamWriter.h @@ -67,7 +67,7 @@ class CtfStreamWriter final { /** @brief Closes and removes an incomplete stream without throwing. */ void abort() noexcept; - /** @brief Appends one timestamped CTF event record. */ + /** @brief Appends one timestamped CTF event record; its size is needed up front for packet rollover and bounds. */ void writeRecord(std::uint32_t eventId, std::uint64_t timestamp, std::uint8_t traceBusId, std::size_t payloadSize, const RecordCallback& writePayload); diff --git a/tools/ctrace/src/output/ctf/TraceCompassXmlWriter.cpp b/tools/ctrace/src/output/ctf/TraceCompassXmlWriter.cpp index 2de1a1ce3..bad1d145c 100644 --- a/tools/ctrace/src/output/ctf/TraceCompassXmlWriter.cpp +++ b/tools/ctrace/src/output/ctf/TraceCompassXmlWriter.cpp @@ -118,6 +118,20 @@ static std::string stateProviderXml() + + + + + + + + + + + + + + @@ -133,6 +147,55 @@ static std::string stateProviderXml() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -142,6 +205,34 @@ static std::string stateProviderXml() + + + + + + + + + + + + + + + + + + + + + + + + + + + + )"; @@ -157,35 +248,40 @@ static std::string viewsXml() << CtfSchema::eventName(CtfSchema::EventId::DwtValue) << R"(" /> + << '/' << R"(*"> )"; return xml.str(); diff --git a/tools/ctrace/test/unit/src/output/ctf/CtfBundleOutputTests.cpp b/tools/ctrace/test/unit/src/output/ctf/CtfBundleOutputTests.cpp index 2bb8692e5..237ef6d66 100644 --- a/tools/ctrace/test/unit/src/output/ctf/CtfBundleOutputTests.cpp +++ b/tools/ctrace/test/unit/src/output/ctf/CtfBundleOutputTests.cpp @@ -32,9 +32,10 @@ #include #include +using CtfTestSupport::CtfExceptionRecord; using CtfTestSupport::parseCtfRecords; +using CtfTestSupport::readCtfExceptionRecords; using CtfTestSupport::readCtfRecords; -using CtfTestSupport::readLe16; using CtfTestSupport::readLe64; using CtfTestSupport::requireFirstCtfRecord; using CtfTestSupport::requireSingleItmEvent; @@ -100,20 +101,6 @@ static ResolvedTraceSource resolvedSource(const CtraceRunSourceMeta& source) }; } -/** @brief Reads compact exception records from a test CTF stream. */ -static std::vector readCtfExceptionRecords(const std::filesystem::path& streamPath) -{ - std::vector records; - for (const auto& record : readCtfRecords(streamPath)) { - if (record.id == CtfSchema::value(CtfSchema::EventId::Exception)) { - const auto number = readLe16(record.payload, 0U); - const auto action = record.payload[2U]; - records.push_back(std::to_string(number) + ":" + std::to_string(static_cast(action))); - } - } - return records; -} - /** @brief Reads trace-status reasons while requiring no other event types. */ static std::vector readOnlyCtfTraceStatusReasons(const std::filesystem::path& streamPath) { @@ -148,6 +135,7 @@ TEST(CtraceUnitTests, testCtfBundleOutputExceptionContext) output.writeEvent(exceptionPacket(54, ExceptionAction::Exited, 300)); output.writeEvent(exceptionPacket(15, ExceptionAction::Returned, 400)); output.writeEvent(exceptionPacket(15, ExceptionAction::Exited, 500)); + output.writeEvent(exceptionPacket(0, ExceptionAction::Returned, 600)); output.stop(); const auto records = readCtfExceptionRecords(outputDir / "stream_0"); @@ -155,21 +143,42 @@ TEST(CtraceUnitTests, testCtfBundleOutputExceptionContext) ASSERT_TRUE(metadata.find("\"entered\" = 0") != std::string::npos) << "CTF exception entered label mismatch"; ASSERT_TRUE(metadata.find("\"exited\" = 1") != std::string::npos) << "CTF exception exited label mismatch"; - - ASSERT_TRUE(records == std::vector({ - "0:0", - "0:1", - "15:0", - "15:1", - "54:0", - "54:1", - "15:0", - "15:1", - "0:0", + ASSERT_TRUE(metadata.find("\"returned\" = 2") != std::string::npos) << "CTF exception returned label mismatch"; + ASSERT_TRUE(metadata.find("\"trace\" = 0") != std::string::npos) << "CTF exception trace origin mismatch"; + ASSERT_TRUE(metadata.find("\"synthetic\" = 1") != std::string::npos) << "CTF exception synthetic origin mismatch"; + + ASSERT_TRUE(records == std::vector({ + {0U, 0U, 1U}, + {0U, 1U, 1U}, + {15U, 0U, 0U}, + {15U, 1U, 1U}, + {54U, 0U, 0U}, + {54U, 1U, 0U}, + {15U, 2U, 0U}, + {15U, 1U, 0U}, + {0U, 2U, 0U}, })) << "CtfBundleOutput exception active-context records mismatch"; } +TEST(CtraceUnitTests, testCtfBundleOutputOverflowClosesExceptionUntilReturn) +{ + const TemporaryCtfOutput temporaryOutput("ctrace-ctf-exception-overflow-test"); + const auto& outputDir = temporaryOutput.outputDirectory(); + + CtfBundleOutput output(makeCtfBundleConfig(outputDir, 1000000U)); + output.start(); + output.writeEvent(exceptionPacket(15U, ExceptionAction::Entered, 100U)); + output.writeEvent(overflowPacket(200U)); + output.writeEvent(exceptionPacket(0U, ExceptionAction::Returned, 300U)); + output.stop(); + + ASSERT_TRUE(readCtfExceptionRecords(outputDir / "stream_0") == + std::vector({{0U, 0U, 1U}, {0U, 1U, 1U}, {15U, 0U, 0U}, {15U, 1U, 1U}, + {0U, 2U, 0U}})) + << "CTF overflow must close the active exception without inventing Thread Mode before its return"; +} + TEST(CtraceUnitTests, testCtfBundleOutputUsesCtraceRunMeta) { const TemporaryCtfOutput temporaryOutput("ctrace-ctf-ctrace-run-meta-test"); @@ -325,7 +334,8 @@ TEST(CtraceUnitTests, testCtfWarningsRemainVisibleWithoutResettingContext) context.writeEvent(exceptionPacket(54U, ExceptionAction::Entered, 20U)); context.stop(); ASSERT_TRUE(readCtfExceptionRecords(contextDir / "stream_0") == - std::vector({"0:0", "0:1", "15:0", "15:1", "54:0"})) + std::vector({{0U, 0U, 1U}, {0U, 1U, 1U}, {15U, 0U, 0U}, {15U, 1U, 1U}, + {54U, 0U, 0U}})) << "a decoder warning must not reset the active CTF exception context"; auto dataLossOptions = makeCtfBundleConfig(dataLossDir, 1000000U); @@ -337,7 +347,8 @@ TEST(CtraceUnitTests, testCtfWarningsRemainVisibleWithoutResettingContext) dataLoss.writeEvent(exceptionPacket(15U, ExceptionAction::Returned, 20U)); dataLoss.stop(); ASSERT_TRUE(readCtfExceptionRecords(dataLossDir / "stream_0") == - std::vector({"0:0", "0:1", "15:0", "15:1", "15:0"})) + std::vector({{0U, 0U, 1U}, {0U, 1U, 1U}, {15U, 0U, 0U}, {15U, 1U, 1U}, + {15U, 2U, 0U}})) << "filtered data-loss must still reset the CTF exception context"; } diff --git a/tools/ctrace/test/unit/src/output/ctf/CtfEncoderTests.cpp b/tools/ctrace/test/unit/src/output/ctf/CtfEncoderTests.cpp index 2334208c5..54d1dc08c 100644 --- a/tools/ctrace/test/unit/src/output/ctf/CtfEncoderTests.cpp +++ b/tools/ctrace/test/unit/src/output/ctf/CtfEncoderTests.cpp @@ -28,6 +28,7 @@ #include using CtfTestSupport::CtfRecord; +using CtfTestSupport::TimestampedCtfExceptionRecord; using CtfTestSupport::kCtfEventOffset; using CtfTestSupport::kCtfPacketContextSize; using CtfTestSupport::kCtfPacketHeaderSize; @@ -37,6 +38,7 @@ using CtfTestSupport::readLe16; using CtfTestSupport::readLe32; using CtfTestSupport::requireFirstCtfRecord; using CtfTestSupport::requireSingleItmEvent; +using CtfTestSupport::timestampedCtfExceptionRecords; /** @brief Formats an encoded CTF UUID for comparison. */ static std::string formatCtfUuid(const std::vector& bytes, std::size_t offset) @@ -321,26 +323,23 @@ TEST(CtraceUnitTests, testCtfEncoderStreamSelectionKeepsStartAndResyncContext) const auto records = readCtfRecords(temporaryPath.path() / "stream_0"); std::vector statusReasons; - std::vector> exceptionRecords; for (const auto& record : records) { EXPECT_EQ(record.traceBusId, 3U); if (record.id == CtfSchema::value(CtfSchema::EventId::TraceStatus)) { statusReasons.push_back(record.payload[0U]); - } else if (record.id == CtfSchema::value(CtfSchema::EventId::Exception)) { - exceptionRecords.emplace_back(record.timestamp, std::to_string(readLe16(record.payload, 0U)) + ":" + - std::to_string(record.payload[2U])); } } + const auto exceptionRecords = timestampedCtfExceptionRecords(records); EXPECT_EQ(statusReasons, std::vector({ CtfSchema::value(CtfSchema::TraceStatusReason::TraceStart), CtfSchema::value(CtfSchema::TraceStatusReason::Resync), })); - EXPECT_EQ(exceptionRecords, (std::vector>({ - {0U, "0:0"}, - {10U, "0:1"}, - {10U, "15:0"}, - {20U, "15:1"}, - {20U, "54:0"}, + EXPECT_EQ(exceptionRecords, (std::vector({ + {0U, {0U, 0U, 1U}}, + {10U, {0U, 1U, 1U}}, + {10U, {15U, 0U, 0U}}, + {20U, {15U, 1U, 1U}}, + {20U, {54U, 0U, 0U}}, }))); } diff --git a/tools/ctrace/test/unit/src/output/ctf/CtfMetadataWriterTests.cpp b/tools/ctrace/test/unit/src/output/ctf/CtfMetadataWriterTests.cpp index 8f1aec669..5ab1ca6e6 100644 --- a/tools/ctrace/test/unit/src/output/ctf/CtfMetadataWriterTests.cpp +++ b/tools/ctrace/test/unit/src/output/ctf/CtfMetadataWriterTests.cpp @@ -52,6 +52,8 @@ TEST(CtraceUnitTests, testCtfMetadataWriterEscapesAndDeduplicatesSourceLabels) EXPECT_NE(metadata.find("\"Reserved 13\" = 13"), std::string::npos); EXPECT_NE(metadata.find("\"External IRQ 0\" = 16"), std::string::npos); EXPECT_NE(metadata.find("\"External IRQ 38\" = 54"), std::string::npos); + EXPECT_NE(metadata.find("\"Thread Mode\" = 0"), std::string::npos); + EXPECT_NE(metadata.find("cmsis_exception_origin_t cmsis_exception_origin;"), std::string::npos); } TEST(CtraceUnitTests, testCtfMetadataWriterRejectsMissingOutputDirectory) @@ -90,6 +92,19 @@ TEST(CtraceUnitTests, testTraceCompassXmlUsesCurrentCtfEvents) for (const auto eventId : visualizedEvents) { EXPECT_NE(xml.find("eventName=\"" + std::string(CtfSchema::eventName(eventId)) + "\""), std::string::npos); } + EXPECT_NE(xml.find("value=\"returned\""), std::string::npos); + EXPECT_NE(xml.find("value=\"cmsis_exception_origin\""), std::string::npos); + EXPECT_NE(xml.find("value=\"trace\""), std::string::npos); + EXPECT_NE(xml.find("value=\"EXCEPTION_RETURN\""), std::string::npos); + EXPECT_NE(xml.find("value=\"Exception Return\""), std::string::npos); + const auto threadModeEntry = xml.find("path=\"EXCEPTION/Thread Mode\""); + const auto returnEntry = xml.find("path=\"EXCEPTION_RETURN/*\" displayText=\"true\""); + const auto interruptEntries = xml.find("path=\"EXCEPTION/(?!Thread Mode).+\""); + ASSERT_NE(threadModeEntry, std::string::npos); + ASSERT_NE(returnEntry, std::string::npos); + ASSERT_NE(interruptEntries, std::string::npos); + EXPECT_LT(threadModeEntry, returnEntry); + EXPECT_LT(returnEntry, interruptEntries); } TEST(CtraceUnitTests, testCtfTextWritersReportDeviceWriteFailures) diff --git a/tools/ctrace/test/unit/src/output/ctf/CtfSchemaTests.cpp b/tools/ctrace/test/unit/src/output/ctf/CtfSchemaTests.cpp index 73cc451cc..6a8a72dde 100644 --- a/tools/ctrace/test/unit/src/output/ctf/CtfSchemaTests.cpp +++ b/tools/ctrace/test/unit/src/output/ctf/CtfSchemaTests.cpp @@ -33,9 +33,14 @@ TEST(CtraceUnitTests, testCtfSchemaUsesDenseIdentifiers) CtfSchema::value(CtfSchema::TraceStatusReason::DecodeError), CtfSchema::value(CtfSchema::TraceStatusReason::DataLoss), }; - constexpr std::array exceptionActions{ + constexpr std::array exceptionActions{ CtfSchema::value(CtfSchema::ExceptionAction::Entered), CtfSchema::value(CtfSchema::ExceptionAction::Exited), + CtfSchema::value(CtfSchema::ExceptionAction::Returned), + }; + constexpr std::array exceptionOrigins{ + CtfSchema::value(CtfSchema::ExceptionOrigin::Trace), + CtfSchema::value(CtfSchema::ExceptionOrigin::Synthetic), }; constexpr std::array valueTags{ CtfSchema::value(CtfSchema::ValueTag::Signed8), CtfSchema::value(CtfSchema::ValueTag::Unsigned8), @@ -53,6 +58,9 @@ TEST(CtraceUnitTests, testCtfSchemaUsesDenseIdentifiers) for (std::size_t index = 0U; index < exceptionActions.size(); ++index) { EXPECT_EQ(exceptionActions[index], static_cast(index)); } + for (std::size_t index = 0U; index < exceptionOrigins.size(); ++index) { + EXPECT_EQ(exceptionOrigins[index], static_cast(index)); + } for (std::size_t index = 0U; index < valueTags.size(); ++index) { EXPECT_EQ(valueTags[index], static_cast(index)); } @@ -97,9 +105,13 @@ TEST(CtraceUnitTests, testCtfExceptionLaneTracker) { CtfExceptionLaneTracker tracker; std::vector records; - const auto emit = [&records](std::uint32_t number, CtfExceptionLaneTracker::RecordAction action) { - records.push_back(std::to_string(number) + - (action == CtfExceptionLaneTracker::RecordAction::Enter ? ":enter" : ":exit")); + const auto emit = [&records](ExceptionNumber number, CtfExceptionLaneTracker::RecordAction action, + CtfExceptionLaneTracker::RecordOrigin origin) { + const auto suffix = action == CtfExceptionLaneTracker::RecordAction::Enter + ? ":enter" + : action == CtfExceptionLaneTracker::RecordAction::Exit ? ":exit" : ":return"; + const auto originSuffix = origin == CtfExceptionLaneTracker::RecordOrigin::Trace ? ":trace" : ":synthetic"; + records.push_back(std::to_string(number) + suffix + originSuffix); }; tracker.startThreadMode(emit); @@ -111,35 +123,35 @@ TEST(CtraceUnitTests, testCtfExceptionLaneTracker) tracker.consume(ExceptionTraceEvent{3, ExceptionAction::Returned}, emit); tracker.consume(ExceptionTraceEvent{15, ExceptionAction::Exited}, emit); tracker.consume(ExceptionTraceEvent{3, ExceptionAction::Exited}, emit); + tracker.consume(ExceptionTraceEvent{0, ExceptionAction::Returned}, emit); ASSERT_TRUE(records == std::vector({ - "0:enter", - "0:exit", - "3:enter", - "3:exit", - "15:enter", - "15:exit", - "3:enter", - "3:exit", - "54:enter", - "54:exit", - "3:enter", - "3:exit", - "0:enter", + "0:enter:synthetic", + "0:exit:synthetic", + "3:enter:trace", + "3:exit:synthetic", + "15:enter:trace", + "15:exit:trace", + "54:enter:trace", + "54:exit:trace", + "3:return:trace", + "3:exit:trace", + "0:return:trace", })) << "CtfExceptionLaneTracker nested and tail-chain records mismatch"; - ASSERT_TRUE(tracker.observedExceptionNumbers() == std::vector({0U, 3U, 15U, 54U})) + ASSERT_TRUE(tracker.observedExceptionNumbers() == std::vector({0U, 3U, 15U, 54U})) << "CtfExceptionLaneTracker observed lanes mismatch"; tracker.resetForDiscontinuity(emit); - ASSERT_TRUE(records.back() == "0:exit") << "CtfExceptionLaneTracker discontinuity must close the active lane"; + ASSERT_TRUE(records.back() == "0:exit:synthetic") + << "CtfExceptionLaneTracker discontinuity must close the active lane synthetically"; const auto recordCount = records.size(); tracker.consume(ExceptionTraceEvent{0, ExceptionAction::Unknown}, emit); ASSERT_TRUE(records.size() == recordCount) << "CtfExceptionLaneTracker must ignore unknown exception actions"; CtfExceptionLaneTracker resumedTracker; resumedTracker.startThreadMode(emit); - ASSERT_TRUE(resumedTracker.observedExceptionNumbers() == std::vector({0U})) + ASSERT_TRUE(resumedTracker.observedExceptionNumbers() == std::vector({0U})) << "a new CtfExceptionLaneTracker must start with an empty lane history"; records.clear(); @@ -152,14 +164,33 @@ TEST(CtraceUnitTests, testCtfExceptionLaneTracker) << "CtfExceptionLaneTracker must not exit a preempted context before its return packet"; resumedTracker.consume(ExceptionTraceEvent{3, ExceptionAction::Returned}, emit); resumedTracker.consume(ExceptionTraceEvent{3, ExceptionAction::Exited}, emit); - ASSERT_TRUE(records.size() == preemptedRecordCount + 2U && records[records.size() - 2U] == "3:exit" && - records.back() == "0:enter") - << "CtfExceptionLaneTracker must exit a resumed context"; + resumedTracker.consume(ExceptionTraceEvent{0, ExceptionAction::Returned}, emit); + ASSERT_TRUE(records.size() == preemptedRecordCount + 3U && records[records.size() - 3U] == "3:return:trace" && + records[records.size() - 2U] == "3:exit:trace" && records.back() == "0:return:trace") + << "CtfExceptionLaneTracker must preserve return and exit the resumed context"; CtfExceptionLaneTracker returnTracker; records.clear(); returnTracker.consume(ExceptionTraceEvent{3, ExceptionAction::Entered}, emit); returnTracker.consume(ExceptionTraceEvent{15, ExceptionAction::Entered}, emit); returnTracker.consume(ExceptionTraceEvent{3, ExceptionAction::Returned}, emit); - EXPECT_EQ(records.back(), "3:enter"); + EXPECT_EQ(records.back(), "3:return:trace"); +} + +TEST(CtraceUnitTests, testCtfExceptionLaneTrackerPreservesReturnForActiveContext) +{ + CtfExceptionLaneTracker tracker; + std::vector records; + const auto emit = [&records](ExceptionNumber number, CtfExceptionLaneTracker::RecordAction action, + CtfExceptionLaneTracker::RecordOrigin origin) { + const auto actionLabel = action == CtfExceptionLaneTracker::RecordAction::Return ? "return" : "unexpected"; + const auto originLabel = origin == CtfExceptionLaneTracker::RecordOrigin::Trace ? "trace" : "synthetic"; + records.push_back(std::to_string(number) + ":" + actionLabel + ":" + originLabel); + }; + + tracker.startThreadMode(emit); + records.clear(); + tracker.consume(ExceptionTraceEvent{0U, ExceptionAction::Returned}, emit); + + EXPECT_EQ(records, std::vector({"0:return:trace"})); } diff --git a/tools/ctrace/test/unit/support/CtfTestSupport.h b/tools/ctrace/test/unit/support/CtfTestSupport.h index 2598d531a..a12250fc1 100644 --- a/tools/ctrace/test/unit/support/CtfTestSupport.h +++ b/tools/ctrace/test/unit/support/CtfTestSupport.h @@ -9,6 +9,7 @@ #define CTRACE_TEST_UNIT_SUPPORT_CTFTESTSUPPORT_H #include "TestSupport.h" +#include "TraceEvent.h" #include "ctf/CtfSchema.h" #include @@ -36,6 +37,31 @@ struct CtfRecord { std::vector payload; }; +/** @brief Stores the decoded fields of one CTF exception event. */ +struct CtfExceptionRecord { + ExceptionNumber number; + std::uint8_t action; + std::uint8_t origin; +}; + +/** @brief Compares decoded CTF exception records. */ +inline bool operator==(const CtfExceptionRecord& lhs, const CtfExceptionRecord& rhs) +{ + return lhs.number == rhs.number && lhs.action == rhs.action && lhs.origin == rhs.origin; +} + +/** @brief Stores one decoded CTF exception event with its event timestamp. */ +struct TimestampedCtfExceptionRecord { + std::uint64_t timestamp; + CtfExceptionRecord exception; +}; + +/** @brief Compares timestamped CTF exception records. */ +inline bool operator==(const TimestampedCtfExceptionRecord& lhs, const TimestampedCtfExceptionRecord& rhs) +{ + return lhs.timestamp == rhs.timestamp && lhs.exception == rhs.exception; +} + /** @brief Reads an unsigned little-endian integer from test stream bytes. */ template inline Integer readLittleEndian(const std::vector& bytes, std::size_t offset) { @@ -100,10 +126,12 @@ inline std::size_t ctfPayloadSize(const std::vector& bytes, std:: size += 1U + (hasAddress != 0U ? 2U : 0U); return size + 5U; } - if (eventId == CtfSchema::value(CtfSchema::EventId::TraceStatus) || - eventId == CtfSchema::value(CtfSchema::EventId::Exception)) { + if (eventId == CtfSchema::value(CtfSchema::EventId::TraceStatus)) { return 5U; } + if (eventId == CtfSchema::value(CtfSchema::EventId::Exception)) { + return 6U; + } if (eventId == CtfSchema::value(CtfSchema::EventId::DwtAddress)) { return 14U; } @@ -154,6 +182,47 @@ inline std::vector readCtfRecords(const std::filesystem::path& stream return parseCtfRecords(readTestBinaryFile(streamPath)); } +/** @brief Decodes and validates one CTF exception record payload. */ +inline CtfExceptionRecord decodeCtfExceptionRecord(const CtfRecord& record) +{ + require(record.id == CtfSchema::value(CtfSchema::EventId::Exception), "expected a CTF exception record"); + require(record.payload.size() == 6U, "CTF exception record has an invalid payload size"); + const auto number = readLe16(record.payload, 0U); + require(number == readLe16(record.payload, 3U), "CTF exception record contains inconsistent exception numbers"); + return {number, record.payload[2U], record.payload[5U]}; +} + +/** @brief Decodes all exception events in parsed CTF records. */ +inline std::vector ctfExceptionRecords(const std::vector& records) +{ + std::vector exceptions; + for (const auto& record : records) { + if (record.id == CtfSchema::value(CtfSchema::EventId::Exception)) { + exceptions.push_back(decodeCtfExceptionRecord(record)); + } + } + return exceptions; +} + +/** @brief Decodes all exception events and timestamps in parsed CTF records. */ +inline std::vector +timestampedCtfExceptionRecords(const std::vector& records) +{ + std::vector exceptions; + for (const auto& record : records) { + if (record.id == CtfSchema::value(CtfSchema::EventId::Exception)) { + exceptions.push_back({record.timestamp, decodeCtfExceptionRecord(record)}); + } + } + return exceptions; +} + +/** @brief Reads and decodes all exception events from a test CTF stream file. */ +inline std::vector readCtfExceptionRecords(const std::filesystem::path& streamPath) +{ + return ctfExceptionRecords(readCtfRecords(streamPath)); +} + /** @brief Returns the first record with a required CTF event ID. */ inline const CtfRecord& requireFirstCtfRecord(const std::vector& records, CtfSchema::EventId id, const std::string& message) diff --git a/tools/ctrace/test/unit/support/TestSupport.h b/tools/ctrace/test/unit/support/TestSupport.h index 287dffda6..c9786b8c5 100644 --- a/tools/ctrace/test/unit/support/TestSupport.h +++ b/tools/ctrace/test/unit/support/TestSupport.h @@ -179,7 +179,7 @@ inline std::vector readTestBinaryFile(const std::filesystem::path } /** @brief Creates a timestamped exception event for a test. */ -inline TraceEvent exceptionPacket(std::uint32_t number, ExceptionAction action, std::uint64_t tcyc = 0) +inline TraceEvent exceptionPacket(ExceptionNumber number, ExceptionAction action, std::uint64_t tcyc = 0) { TraceEvent packet{ExceptionTraceEvent{number, action}}; packet.tcyc = tcyc;