From bfbd4e744a82a675f80911645e30bb33d8aa32f5 Mon Sep 17 00:00:00 2001 From: Thorsten de Buhr Date: Fri, 4 Sep 2026 14:49:52 +0200 Subject: [PATCH] feat(ctrace): decode compressed Armv8-M DWT packets --- tools/ctrace/src/control/FileDecodeJob.cpp | 20 +++- .../ctrace/src/decode/CortexMPostDecoder.cpp | 5 +- tools/ctrace/src/decode/CortexMPostDecoder.h | 2 +- .../src/decode/CortexMStreamDecoder.cpp | 8 +- .../ctrace/src/decode/CortexMStreamDecoder.h | 10 +- tools/ctrace/src/decode/DecodePipeline.cpp | 10 +- tools/ctrace/src/decode/DecodePipeline.h | 7 +- tools/ctrace/src/decode/DwtPacketDecoder.cpp | 64 +++++++++--- tools/ctrace/src/decode/DwtPacketDecoder.h | 15 ++- tools/ctrace/src/tracerun/CtraceRunMeta.cpp | 64 +++++++++++- tools/ctrace/src/tracerun/CtraceRunMeta.h | 5 +- tools/ctrace/src/tracerun/TraceRunConfig.h | 9 ++ .../src/tracerun/YmlTraceRunConfigReader.cpp | 41 ++++++++ tools/ctrace/test/data/README.md | 11 ++ .../trace-compressed-dwt.ctrace-run.yml | 33 ++++++ .../trace-compressed-dwt.raw | Bin 0 -> 20 bytes .../test/integration/src/CtraceIntegTests.cpp | 47 +++++++++ .../unit/src/decode/DwtPacketDecoderTests.cpp | 94 +++++++++++++++++- .../unit/src/tracerun/CtraceRunMetaTests.cpp | 48 +++++++++ .../tracerun/TraceRunConfigReaderTests.cpp | 50 ++++++++++ 20 files changed, 507 insertions(+), 36 deletions(-) create mode 100644 tools/ctrace/test/data/trace-compressed-dwt/trace-compressed-dwt.ctrace-run.yml create mode 100644 tools/ctrace/test/data/trace-compressed-dwt/trace-compressed-dwt.raw diff --git a/tools/ctrace/src/control/FileDecodeJob.cpp b/tools/ctrace/src/control/FileDecodeJob.cpp index e897eda37..1f4e285a4 100644 --- a/tools/ctrace/src/control/FileDecodeJob.cpp +++ b/tools/ctrace/src/control/FileDecodeJob.cpp @@ -110,6 +110,21 @@ static ItmTimestampPrescalers timestampPrescalers(const CtraceRunMeta& ctraceRun return {fallback, ctraceRunMeta.timestampPrescalersByTraceBusId()}; } +/** @brief Converts normalized DWT comparator metadata into decoder configuration. */ +static DwtComparatorValuesByTraceBusId dwtComparatorValues(const CtraceRunMeta& ctraceRunMeta) +{ + DwtComparatorValuesByTraceBusId result; + for (const auto& [traceBusId, configured] : ctraceRunMeta.dwtComparatorValuesByTraceBusId()) { + auto& values = result[traceBusId]; + for (const auto& [comparator, value] : configured) { + if (comparator < values.size()) { + values[comparator] = value; + } + } + } + return result; +} + /** @brief Converts command-line output selection into an output request. */ static TraceOutputRequest outputRequest(const CliOptions& options) { @@ -161,6 +176,7 @@ FileDecodeJob::FileDecodeJob(CliOptions options, std::filesystem::path rawInputP void FileDecodeJob::run() { const auto prescalers = timestampPrescalers(m_ctraceRunMeta); + const auto comparatorValues = dwtComparatorValues(m_ctraceRunMeta); auto outputPlan = planTraceOutputs(outputRequest(m_options), m_rawInputPath, m_ctraceRunMeta, m_diagnostics); if (outputPlan.hasRequestedOutputs() && !outputPlan.hasEnabledOutputs()) { return; @@ -198,9 +214,9 @@ void FileDecodeJob::run() RawFileReader input(m_rawInputPath); std::unique_ptr pipeline; if (m_sessionFactory) { - pipeline = std::make_unique(prescalers, consumers, m_sessionFactory); + pipeline = std::make_unique(prescalers, consumers, m_sessionFactory, comparatorValues); } else { - pipeline = std::make_unique(prescalers, consumers); + pipeline = std::make_unique(prescalers, consumers, comparatorValues); } while (true) { const auto read = input.read(); diff --git a/tools/ctrace/src/decode/CortexMPostDecoder.cpp b/tools/ctrace/src/decode/CortexMPostDecoder.cpp index 5902fa9d2..177054f21 100644 --- a/tools/ctrace/src/decode/CortexMPostDecoder.cpp +++ b/tools/ctrace/src/decode/CortexMPostDecoder.cpp @@ -19,8 +19,9 @@ #include #include -CortexMPostDecoder::CortexMPostDecoder(TraceEventSink& eventSink) - : m_eventSink(eventSink) +CortexMPostDecoder::CortexMPostDecoder(TraceEventSink& eventSink, DwtComparatorValues comparatorValues) + : m_eventSink(eventSink), + m_dwtDecoder(std::move(comparatorValues)) { } diff --git a/tools/ctrace/src/decode/CortexMPostDecoder.h b/tools/ctrace/src/decode/CortexMPostDecoder.h index 56cdc01e4..c37e1d119 100644 --- a/tools/ctrace/src/decode/CortexMPostDecoder.h +++ b/tools/ctrace/src/decode/CortexMPostDecoder.h @@ -21,7 +21,7 @@ class CortexMPostDecoder final : public OpenCsdTraceElementSink { public: /** @brief Creates a post-decoder that emits to the supplied event sink. */ - explicit CortexMPostDecoder(TraceEventSink& eventSink); + explicit CortexMPostDecoder(TraceEventSink& eventSink, DwtComparatorValues comparatorValues = {}); /** @brief Appends one OpenCSD trace element. */ void append(OpenCsdTraceElement element) override; diff --git a/tools/ctrace/src/decode/CortexMStreamDecoder.cpp b/tools/ctrace/src/decode/CortexMStreamDecoder.cpp index 07db9204e..85cdec5c9 100644 --- a/tools/ctrace/src/decode/CortexMStreamDecoder.cpp +++ b/tools/ctrace/src/decode/CortexMStreamDecoder.cpp @@ -18,8 +18,10 @@ #include #include -CortexMStreamDecoder::CortexMStreamDecoder(ItmTimestampPrescalers prescalers, TraceEventSink& eventSink) +CortexMStreamDecoder::CortexMStreamDecoder(ItmTimestampPrescalers prescalers, TraceEventSink& eventSink, + DwtComparatorValuesByTraceBusId comparatorValues) : m_prescalers(std::move(prescalers)), + m_comparatorValues(std::move(comparatorValues)), m_eventSink(eventSink) { } @@ -73,7 +75,9 @@ CortexMPostDecoder& CortexMStreamDecoder::decoder(std::uint8_t traceBusId) { auto& result = m_decoders[traceBusId]; if (!result) { - result = std::make_unique(m_eventSink); + const auto values = m_comparatorValues.find(traceBusId); + result = std::make_unique( + m_eventSink, values != m_comparatorValues.end() ? values->second : DwtComparatorValues{}); } return *result; } diff --git a/tools/ctrace/src/decode/CortexMStreamDecoder.h b/tools/ctrace/src/decode/CortexMStreamDecoder.h index 2a61ec5c7..d87ce5bcd 100644 --- a/tools/ctrace/src/decode/CortexMStreamDecoder.h +++ b/tools/ctrace/src/decode/CortexMStreamDecoder.h @@ -9,6 +9,7 @@ #define CTRACE_SRC_DECODE_CORTEXMSTREAMDECODER_H #include "OpenCsdTraceElement.h" +#include "DwtPacketDecoder.h" #include "TraceEvent.h" #include @@ -24,11 +25,15 @@ struct ItmTimestampPrescalers { std::map byTraceBusId; }; +/** @brief Stores DWT comparator values indexed by CoreSight Trace Bus ID. */ +using DwtComparatorValuesByTraceBusId = std::map; + /** @brief Routes OpenCSD elements to per-stream Cortex-M post-decoders. */ class CortexMStreamDecoder final : public OpenCsdTraceElementSink { public: /** @brief Creates a stream router with timestamp scaling configuration. */ - CortexMStreamDecoder(ItmTimestampPrescalers prescalers, TraceEventSink& eventSink); + CortexMStreamDecoder(ItmTimestampPrescalers prescalers, TraceEventSink& eventSink, + DwtComparatorValuesByTraceBusId comparatorValues = {}); /** @brief Destroys all per-stream post-decoders. */ ~CortexMStreamDecoder(); @@ -51,8 +56,9 @@ class CortexMStreamDecoder final : public OpenCsdTraceElementSink { CortexMPostDecoder& decoder(std::uint8_t traceBusId); ItmTimestampPrescalers m_prescalers; + DwtComparatorValuesByTraceBusId m_comparatorValues; TraceEventSink& m_eventSink; std::map> m_decoders; }; -#endif // CTRACE_SRC_DECODE_CORTEXMSTREAMDECODER_H +#endif // CTRACE_SRC_DECODE_CORTEXMSTREAMDECODER_H diff --git a/tools/ctrace/src/decode/DecodePipeline.cpp b/tools/ctrace/src/decode/DecodePipeline.cpp index 5ddb80f1e..2882879da 100644 --- a/tools/ctrace/src/decode/DecodePipeline.cpp +++ b/tools/ctrace/src/decode/DecodePipeline.cpp @@ -16,15 +16,17 @@ #include #include -DecodePipeline::DecodePipeline(ItmTimestampPrescalers timestampPrescalers, TraceEventSink& eventSink) - : m_streamDecoder(std::move(timestampPrescalers), eventSink), +DecodePipeline::DecodePipeline(ItmTimestampPrescalers timestampPrescalers, TraceEventSink& eventSink, + DwtComparatorValuesByTraceBusId comparatorValues) + : m_streamDecoder(std::move(timestampPrescalers), eventSink, std::move(comparatorValues)), m_decoder(m_streamDecoder) { } DecodePipeline::DecodePipeline(ItmTimestampPrescalers timestampPrescalers, TraceEventSink& eventSink, - const OpenCsdItmSessionFactory& sessionFactory) - : m_streamDecoder(std::move(timestampPrescalers), eventSink), + const OpenCsdItmSessionFactory& sessionFactory, + DwtComparatorValuesByTraceBusId comparatorValues) + : m_streamDecoder(std::move(timestampPrescalers), eventSink, std::move(comparatorValues)), m_decoder(m_streamDecoder, sessionFactory) { } diff --git a/tools/ctrace/src/decode/DecodePipeline.h b/tools/ctrace/src/decode/DecodePipeline.h index d0ee3680d..0f7d91707 100644 --- a/tools/ctrace/src/decode/DecodePipeline.h +++ b/tools/ctrace/src/decode/DecodePipeline.h @@ -41,7 +41,8 @@ class DecodePipeline final { * @param timestampPrescalers Default and per-stream timestamp prescalers. * @param eventSink Sink receiving decoded events synchronously. */ - DecodePipeline(ItmTimestampPrescalers timestampPrescalers, TraceEventSink& eventSink); + DecodePipeline(ItmTimestampPrescalers timestampPrescalers, TraceEventSink& eventSink, + DwtComparatorValuesByTraceBusId comparatorValues = {}); /** * @brief Creates a pipeline with an injected OpenCSD session factory. * @param timestampPrescalers Default and per-stream timestamp prescalers. @@ -49,7 +50,7 @@ class DecodePipeline final { * @param sessionFactory Factory used to create the OpenCSD session. */ DecodePipeline(ItmTimestampPrescalers timestampPrescalers, TraceEventSink& eventSink, - const OpenCsdItmSessionFactory& sessionFactory); + const OpenCsdItmSessionFactory& sessionFactory, DwtComparatorValuesByTraceBusId comparatorValues = {}); /** * @brief Pushes the next contiguous chunk of raw trace bytes. @@ -69,4 +70,4 @@ class DecodePipeline final { OpenCsdItmDecoder m_decoder; }; -#endif // CTRACE_SRC_DECODE_DECODEPIPELINE_H +#endif // CTRACE_SRC_DECODE_DECODEPIPELINE_H diff --git a/tools/ctrace/src/decode/DwtPacketDecoder.cpp b/tools/ctrace/src/decode/DwtPacketDecoder.cpp index b64dfa165..8f0051499 100644 --- a/tools/ctrace/src/decode/DwtPacketDecoder.cpp +++ b/tools/ctrace/src/decode/DwtPacketDecoder.cpp @@ -53,11 +53,17 @@ constexpr std::uint32_t kExceptionActionMask = 0x3U; constexpr std::uint32_t kExceptionActionShift = 12U; constexpr std::uint32_t kPmuOverflowMask = 0xffU; -constexpr std::uint8_t kArmv7MFullPcBytes = 4U; -constexpr std::uint8_t kArmv7MAddressOffsetBytes = 2U; +constexpr std::uint8_t kDwtShortPayloadBytes = 1U; +constexpr std::uint8_t kDwtMediumPayloadBytes = 2U; +constexpr std::uint8_t kDwtLongPayloadBytes = 4U; constexpr std::uint8_t kArmv8MMatchBytes = 1U; constexpr std::uint32_t kArmv8MMatchValue = 1U; +DwtPacketDecoder::DwtPacketDecoder(DwtComparatorValues comparatorValues) + : m_comparatorValues(std::move(comparatorValues)) +{ +} + /** @brief Describes an invalid DWT event-counter payload. */ static std::string invalidEventCounterMessage(const DwtPayloadPacket& payload) { @@ -260,17 +266,14 @@ void DwtPacketDecoder::decodeDataTrace(const DwtPayloadPacket& payload, std::vec output.push_back(std::move(match)); return; } - const auto expectedSize = secondarySubtype ? kArmv7MAddressOffsetBytes : kArmv7MFullPcBytes; - if (payload.size != expectedSize) { + + const auto reject = [&](const std::string& reason) { auto flushed = flush(payload.quality, payload.tcyc); output.insert(output.end(), std::make_move_iterator(flushed.begin()), std::make_move_iterator(flushed.end())); TraceEvent error{TraceIssueEvent{ TraceIssueCode::UnsupportedDwtAddressPayload, TraceIssueSeverity::Error, - "unsupported DWT " + std::string(secondarySubtype ? "address" : "PC or match") + " payload size " + - std::to_string(payload.size) + - "; the current ctrace-run format does not provide the " - "architecture and reconstruction data needed to decode it safely", + reason, std::nullopt, std::nullopt, }}; @@ -279,13 +282,50 @@ void DwtPacketDecoder::decodeDataTrace(const DwtPayloadPacket& payload, std::vec error.tcyc = payload.tcyc; error.quality = payload.quality; output.push_back(std::move(error)); - return; - } + }; + if (secondarySubtype) { - event.addressLo16 = payload.value; + if (payload.size == kDwtShortPayloadBytes) { + const auto& comparatorValue = m_comparatorValues[comparator]; + if (!comparatorValue.has_value()) { + reject("cannot reconstruct short DWT data-address payload for comparator " + std::to_string(comparator) + + "; ctrace-run does not provide a complete DWT_COMP" + std::to_string(comparator) + " register value"); + return; + } + event.addressLo16 = (*comparatorValue & 0xff00U) | (payload.value & 0xffU); + } else if (payload.size == kDwtMediumPayloadBytes || payload.size == kDwtLongPayloadBytes) { + // The existing semantic event carries the architecturally relevant low 16 bits. + // A medium payload supplies them completely; a long payload is truncated here. + event.addressLo16 = payload.value & 0xffffU; + } else { + reject("unsupported DWT data-address payload size " + std::to_string(payload.size) + + "; expected 1, 2, or 4 bytes"); + return; + } event.hasAddressLo16 = true; } else { - event.pc = payload.value; + if (payload.size != kDwtShortPayloadBytes && payload.size != kDwtMediumPayloadBytes && + payload.size != kDwtLongPayloadBytes) { + reject("unsupported DWT PC-value payload size " + std::to_string(payload.size) + "; expected 1, 2, or 4 bytes"); + return; + } + if ((payload.value & 1U) != 0U) { + reject("invalid DWT PC-value payload for comparator " + std::to_string(comparator) + + ": bit 0 is reserved for the match-packet discriminator"); + return; + } + if (payload.size == kDwtLongPayloadBytes) { + event.pc = payload.value; + } else { + const auto& comparatorValue = m_comparatorValues[comparator]; + if (!comparatorValue.has_value()) { + reject("cannot reconstruct compressed DWT PC-value payload for comparator " + std::to_string(comparator) + + "; ctrace-run does not provide a complete DWT_COMP" + std::to_string(comparator) + " register value"); + return; + } + const auto payloadMask = payload.size == kDwtShortPayloadBytes ? 0xffU : 0xffffU; + event.pc = (*comparatorValue & ~payloadMask) | (payload.value & payloadMask); + } event.hasPc = true; } sendDataTraceEvent(comparator, event, payload.quality, payload.tcyc, output); diff --git a/tools/ctrace/src/decode/DwtPacketDecoder.h b/tools/ctrace/src/decode/DwtPacketDecoder.h index 1ec2fb9d1..d9e31c275 100644 --- a/tools/ctrace/src/decode/DwtPacketDecoder.h +++ b/tools/ctrace/src/decode/DwtPacketDecoder.h @@ -27,9 +27,17 @@ struct DwtPayloadPacket { TraceQuality quality; }; +/** @brief Number of comparators encodable in a DWT data-trace source ID. */ +inline constexpr std::size_t kDwtDataTraceComparatorCount = 4U; + +/** @brief Stores configured DWT comparator values used for packet decompression. */ +using DwtComparatorValues = std::array, kDwtDataTraceComparatorCount>; + /** @brief Reconstructs semantic DWT events from hardware payload packets. */ class DwtPacketDecoder { public: + /** @brief Creates a decoder with the comparator values active for this stream. */ + explicit DwtPacketDecoder(DwtComparatorValues comparatorValues = {}); /** @brief Decodes one hardware payload and returns completed semantic events. */ std::vector decode(const DwtPayloadPacket& payload); /** @brief Flushes incomplete data-trace fragments at a boundary. */ @@ -38,8 +46,6 @@ class DwtPacketDecoder { void reset(); private: - static constexpr std::size_t kDataTraceComparatorCount = 4U; - /** @brief Accumulates the fragments of one pending DWT data-trace event. */ struct PendingDataTrace { std::uint64_t index = 0; @@ -68,7 +74,8 @@ class DwtPacketDecoder { /** @brief Maps an encoded DWT exception action to the semantic action. */ static ExceptionAction exceptionAction(std::uint32_t value); - std::array, kDataTraceComparatorCount> m_pendingDataTrace; + DwtComparatorValues m_comparatorValues; + std::array, kDwtDataTraceComparatorCount> m_pendingDataTrace; }; -#endif // CTRACE_SRC_DECODE_DWTPACKETDECODER_H +#endif // CTRACE_SRC_DECODE_DWTPACKETDECODER_H diff --git a/tools/ctrace/src/tracerun/CtraceRunMeta.cpp b/tools/ctrace/src/tracerun/CtraceRunMeta.cpp index 78ed47f6b..5b87a09cf 100644 --- a/tools/ctrace/src/tracerun/CtraceRunMeta.cpp +++ b/tools/ctrace/src/tracerun/CtraceRunMeta.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -303,6 +304,50 @@ static CtraceRunSourceMeta sourceMeta(const TraceRunConfig& config, const TraceR return meta; } +/** @brief Resolves a fully specified DWT comparator value from one reference. */ +static std::optional dwtComparatorValue(const TraceRunReference& reference, std::uint32_t comparator) +{ + const auto registerName = "DWT_COMP" + std::to_string(comparator); + std::uint32_t value = 0U; + std::uint32_t knownMask = 0U; + for (const auto& reg : reference.registers) { + if (reg.name != registerName) { + continue; + } + value = (value & ~reg.mask) | (reg.value & reg.mask); + knownMask |= reg.mask; + } + if (knownMask != std::numeric_limits::max()) { + return std::nullopt; + } + return value; +} + +/** @brief Adds comparator values used by compressed DWT data-trace packets. */ +static void addDwtComparatorValues(std::map>& valuesByTraceBusId, + const TraceRunConfig& config, const TraceRunReference& reference) +{ + if (reference.type != "dwt") { + return; + } + const auto traceBusId = static_cast(reference.stream.value_or(0U)); + for (const auto comparator : reference.sources) { + const auto value = dwtComparatorValue(reference, comparator); + if (!value.has_value()) { + continue; + } + auto& values = valuesByTraceBusId[traceBusId]; + const auto existing = values.find(comparator); + if (existing != values.end() && existing->second != *value) { + throw std::runtime_error(configError(config, reference.line, + "DWT comparator " + std::to_string(comparator) + + " has conflicting values for Trace Bus ID " + + std::to_string(traceBusId))); + } + values[comparator] = *value; + } +} + /** @brief Returns or creates accumulated metadata for one processor. */ static ProcessorMeta& processorMeta(std::vector& processors, const std::optional& name) { @@ -552,7 +597,18 @@ CtraceRunMeta CtraceRunMeta::fromConfig(const TraceRunConfig& config) if (isUsableStreamBinding(reference) && identity.accepts(reference)) { (void)processorMeta(processors, identity.canonicalName(reference.processorName)); } - if (!TraceRunSchema::isUsableReference(reference) || !identity.accepts(reference)) { + if (!identity.accepts(reference)) { + continue; + } + // Armv8-M can compress a DWT PC-value packet relative to either + // comparator of an instruction-address range. Such references do not + // describe a data output route, but their DWT_COMP values are still + // required to reconstruct the packet. + if (reference.type == "dwt" && !reference.sources.empty() && + TraceRunSchema::referenceProblem(reference) == ReferenceProblem::None) { + addDwtComparatorValues(ctraceRunMeta.m_dwtComparatorValuesByTraceBusId, config, reference); + } + if (!TraceRunSchema::isUsableReference(reference)) { continue; } for (const auto source : reference.sources) { @@ -610,6 +666,12 @@ const std::map& CtraceRunMeta::itmEnableMasksByTrac return m_itmEnableMasksByTraceBusId; } +const std::map>& +CtraceRunMeta::dwtComparatorValuesByTraceBusId() const +{ + return m_dwtComparatorValuesByTraceBusId; +} + const std::vector& CtraceRunMeta::timestampClockErrors() const { return m_timestampClockErrors; diff --git a/tools/ctrace/src/tracerun/CtraceRunMeta.h b/tools/ctrace/src/tracerun/CtraceRunMeta.h index d6e1992d2..af1af5a83 100644 --- a/tools/ctrace/src/tracerun/CtraceRunMeta.h +++ b/tools/ctrace/src/tracerun/CtraceRunMeta.h @@ -73,6 +73,8 @@ class CtraceRunMeta { const std::optional& itmEnableMask() const; /** @brief Returns ITM stimulus enable masks indexed by Trace Bus ID. */ const std::map& itmEnableMasksByTraceBusId() const; + /** @brief Returns complete DWT comparator values indexed by Trace Bus ID and comparator. */ + const std::map>& dwtComparatorValuesByTraceBusId() const; /** @brief Returns clock validation errors retained for output planning. */ const std::vector& timestampClockErrors() const; /** @brief Reports whether processor-specific timestamp prescalers differ. */ @@ -92,6 +94,7 @@ class CtraceRunMeta { std::map m_timestampPrescalersByTraceBusId; std::optional m_itmEnableMask; std::map m_itmEnableMasksByTraceBusId; + std::map> m_dwtComparatorValuesByTraceBusId; std::vector m_timestampClockErrors; std::size_t m_processorCount = 0; bool m_distinctProcessorPrescalers = false; @@ -99,4 +102,4 @@ class CtraceRunMeta { std::vector m_warnings; }; -#endif // CTRACE_SRC_TRACERUN_CTRACERUNMETA_H +#endif // CTRACE_SRC_TRACERUN_CTRACERUNMETA_H diff --git a/tools/ctrace/src/tracerun/TraceRunConfig.h b/tools/ctrace/src/tracerun/TraceRunConfig.h index 6117ffeb5..5c4c222f4 100644 --- a/tools/ctrace/src/tracerun/TraceRunConfig.h +++ b/tools/ctrace/src/tracerun/TraceRunConfig.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -97,6 +98,13 @@ inline bool processorNamesMayBind(const std::optional& left, const } // namespace TraceRunSchema +/** @brief Stores one register write emitted for a trace reference. */ +struct TraceRunRegisterWrite { + std::string name; + std::uint32_t value = 0U; + std::uint32_t mask = std::numeric_limits::max(); +}; + /** @brief Stores one parsed ctrace reference and its resolved routing metadata. */ struct TraceRunReference { std::string ctraceRef; @@ -116,6 +124,7 @@ struct TraceRunReference { // narrows it to the CoreSight ATB trace-ID domain. std::optional stream; std::vector sources; + std::vector registers; // Index of the referenced ctrace-setup.data entry, derived from the // specified ctrace-ref path form [/]data#. std::optional dataSetupIndex; diff --git a/tools/ctrace/src/tracerun/YmlTraceRunConfigReader.cpp b/tools/ctrace/src/tracerun/YmlTraceRunConfigReader.cpp index 7debf1826..0658bd620 100644 --- a/tools/ctrace/src/tracerun/YmlTraceRunConfigReader.cpp +++ b/tools/ctrace/src/tracerun/YmlTraceRunConfigReader.cpp @@ -286,6 +286,46 @@ static std::vector parseSources(const std::string& path, const No return sources; } +/** @brief Parses register writes needed to reconstruct compressed DWT packets. */ +static std::vector parseRegisters(const std::string& path, const Node& reference) +{ + const auto registersNode = childNode(reference, "regs"); + if (!registersNode) { + return {}; + } + requireSequence(path, registersNode, "regs"); + + std::vector registers; + for (const auto& item : registersNode) { + if (!item.IsMap()) { + fail(path, item, "each 'regs' entry must be a map"); + } + const auto nameNode = childNode(item, "name"); + if (!nameNode || !nameNode.IsScalar() || nameNode.Scalar().empty()) { + fail(path, nameNode ? nameNode : item, "each 'regs' entry requires a non-empty scalar 'name'"); + } + const auto valueNode = childNode(item, "value"); + if (!valueNode || !valueNode.IsScalar() || valueNode.Scalar().empty()) { + fail(path, valueNode ? valueNode : item, "each 'regs' entry requires a scalar unsigned 'value'"); + } + + TraceRunRegisterWrite reg; + reg.name = nameNode.Scalar(); + reg.value = static_cast( + unsignedValue(path, valueNode, "value", valueNode.Scalar(), std::numeric_limits::max())); + const auto maskNode = childNode(item, "mask"); + if (maskNode) { + if (!maskNode.IsScalar() || maskNode.Scalar().empty()) { + fail(path, maskNode, "'regs.mask' must be a scalar unsigned integer"); + } + reg.mask = static_cast( + unsignedValue(path, maskNode, "mask", maskNode.Scalar(), std::numeric_limits::max())); + } + registers.push_back(std::move(reg)); + } + return registers; +} + /** @brief Stores diagnostics copied from one parsed trace reference. */ struct ReferenceDiagnostics { std::optional info; @@ -357,6 +397,7 @@ static std::optional parseReference(const std::string& path, reference.stream = parseStream(); reference.sources = parseSources(path, element); if (reference.type == "dwt") { + reference.registers = parseRegisters(path, element); reference.address = deferredReferenceUnsignedAttribute(path, element, "address", std::numeric_limits::max(), reference.addressError); diff --git a/tools/ctrace/test/data/README.md b/tools/ctrace/test/data/README.md index 66dfb7ff1..fdd02a036 100644 --- a/tools/ctrace/test/data/README.md +++ b/tools/ctrace/test/data/README.md @@ -59,6 +59,17 @@ timeline configuration. - Generated raw trace: `5cffb5803675dc02ecd5ed4939a42c660ad7cabd3542b8ca1506230e20d14a50` - Generated trace-run YAML: `b40c10634b8ba335b14b75f0026758ad84dd68aaf68f0a1bbfd2a5745756c5e8` +The `trace-compressed-dwt` fixture is completely synthetic and was not +captured from real hardware. It contains a hardware synchronization packet, +short and medium Armv8-M Data Trace PC Value packets generated for an +instruction-address range, and short and medium Data Trace Data Address packets +generated for a data-address range. The integration test uses the corresponding +`DWT_COMP` values from the trace-run file to verify reconstruction in both +CSV and CTF output. + +- Generated raw trace: `acd1bac0bf0c248e8a2d432ccc1ac3c030787c7f16cfeb7354b08881f4cf48f8` +- Generated trace-run YAML: `0894cdf20bd8f8fca87eb4f76cea01c00b29c2e76b82fd6266fe4ade9613dc9e` + `trace-run` contains only the small current-schema inputs needed by executable tests. Reader unit tests cover only the fields consumed by ctrace. A C++ entry-point test creates a reviewable eight-byte ITM stream below the build tree diff --git a/tools/ctrace/test/data/trace-compressed-dwt/trace-compressed-dwt.ctrace-run.yml b/tools/ctrace/test/data/trace-compressed-dwt/trace-compressed-dwt.ctrace-run.yml new file mode 100644 index 000000000..c710eed80 --- /dev/null +++ b/tools/ctrace/test/data/trace-compressed-dwt/trace-compressed-dwt.ctrace-run.yml @@ -0,0 +1,33 @@ +ctrace-run: + generated-by: synthetic Armv8-M compressed DWT fixture + ctrace-setup: + - timestamps: + clock: 1000000 + data: + - location: 0x20007856 + size: 4 + output: offset + instructions: + start: + - location: 0x08001200 + size: 0x1ee00 + ctrace-refs: + - ctrace-ref: instructions:start#0 + type: dwt + source: [0, 1] + regs: + - name: DWT_COMP0 + value: 0x08001200 + - name: DWT_COMP1 + value: 0x0801ffff + - ctrace-ref: data#0 + type: dwt + address: 0x20007856 + size: 4 + data-type: unsigned + source: [2, 3] + regs: + - name: DWT_COMP2 + value: 0x20007856 + - name: DWT_COMP3 + value: 0x20007859 diff --git a/tools/ctrace/test/data/trace-compressed-dwt/trace-compressed-dwt.raw b/tools/ctrace/test/data/trace-compressed-dwt/trace-compressed-dwt.raw new file mode 100644 index 0000000000000000000000000000000000000000..6af5dcf0e8d000e3bd637949408034e4c3ddbe25 GIT binary patch literal 20 ZcmZQz0D=Zr6M?XbFooPOgSv expectedRaw{{ + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x80U, 0x45U, 0x34U, 0x10U, 0x56U, + 0x78U, 0x56U, 0x20U, 0x6dU, 0x56U, 0x30U, 0x7eU, 0x58U, 0x78U, 0x40U, + }}; + EXPECT_EQ(readBinaryFile(workDirectory() / "trace-compressed-dwt.SWO.raw"), + std::vector(expectedRaw.begin(), expectedRaw.end())); + + const auto result = run({"ctrace", workDirectory().string(), "--target", "trace-compressed-dwt", "--all"}); + EXPECT_EQ(0, result.exitCode) << result.stderrText; + EXPECT_EQ("cycles,stream,type,source,value,pc,offset,note\n" + "1,,dwt,0,,0x08001234,,\n" + "3,,dwt,1,,0x08015678,,\n" + "6,,dwt,2,,,0x7856,\n" + "10,,dwt,3,,,0x7858,\n", + readTextFile(workDirectory() / "trace-compressed-dwt.SWO.csv")); + + const auto records = CtfTestSupport::readCtfRecords(workDirectory() / "trace-compressed-dwt.ctf" / "stream_0"); + std::vector addresses; + std::copy_if(records.begin(), records.end(), std::back_inserter(addresses), + [](const auto& record) { return record.id == CtfSchema::value(CtfSchema::EventId::DwtAddress); }); + ASSERT_EQ(addresses.size(), 4U); + EXPECT_EQ(addresses[0U].timestamp, 1U); + EXPECT_EQ(addresses[0U].payload[0U], 0U); + EXPECT_EQ(addresses[0U].payload[1U], 1U); + EXPECT_EQ(CtfTestSupport::readLe32(addresses[0U].payload, 3U), 0x08001234U); + EXPECT_EQ(addresses[1U].timestamp, 3U); + EXPECT_EQ(addresses[1U].payload[0U], 1U); + EXPECT_EQ(CtfTestSupport::readLe32(addresses[1U].payload, 3U), 0x08015678U); + EXPECT_EQ(addresses[2U].timestamp, 6U); + EXPECT_EQ(addresses[2U].payload[0U], 2U); + EXPECT_EQ(addresses[2U].payload[2U], 1U); + EXPECT_EQ(CtfTestSupport::readLe16(addresses[2U].payload, 7U), 0x7856U); + EXPECT_EQ(addresses[3U].timestamp, 10U); + EXPECT_EQ(addresses[3U].payload[0U], 3U); + EXPECT_EQ(CtfTestSupport::readLe16(addresses[3U].payload, 7U), 0x7858U); +} + TEST_F(CtraceIntegTests, ConvertsCapturedDwtEventCountersAcrossOverflow) { const auto fixtureDirectory = testDataDirectory() / "trace-event"; diff --git a/tools/ctrace/test/unit/src/decode/DwtPacketDecoderTests.cpp b/tools/ctrace/test/unit/src/decode/DwtPacketDecoderTests.cpp index b3cc33290..eb285155c 100644 --- a/tools/ctrace/test/unit/src/decode/DwtPacketDecoderTests.cpp +++ b/tools/ctrace/test/unit/src/decode/DwtPacketDecoderTests.cpp @@ -319,6 +319,96 @@ TEST(CtraceUnitTests, testDwtPacketDecoderFlushesPendingComparatorBeforeMatch) EXPECT_TRUE(decoder.flush({}, 101U).empty()); } +TEST(CtraceUnitTests, testDwtPacketDecoderReconstructsCompressedPcValues) +{ + DwtComparatorValues comparatorValues{}; + comparatorValues[0U] = 0x08001200U; + comparatorValues[1U] = 0x08010000U; + + DwtPacketDecoder decoder(comparatorValues); + EXPECT_TRUE(decoder.decode(dwtPayload(8U, 1U, 0x34U, 10U)).empty()); + auto packets = decoder.flush({}, 100U); + ASSERT_EQ(packets.size(), 1U); + const auto* shortPc = traceEventPayload(packets.front()); + ASSERT_NE(shortPc, nullptr); + EXPECT_EQ(dwtAddressPc(*shortPc), std::optional(0x08001234U)); + + EXPECT_TRUE(decoder.decode(dwtPayload(10U, 2U, 0x5678U, 11U)).empty()); + packets = decoder.flush({}, 101U); + ASSERT_EQ(packets.size(), 1U); + const auto* mediumPc = traceEventPayload(packets.front()); + ASSERT_NE(mediumPc, nullptr); + EXPECT_EQ(dwtAddressPc(*mediumPc), std::optional(0x08015678U)); + + EXPECT_TRUE(decoder.decode(dwtPayload(12U, 4U, 0x08123456U, 12U)).empty()); + packets = decoder.flush({}, 102U); + ASSERT_EQ(packets.size(), 1U); + const auto* longPc = traceEventPayload(packets.front()); + ASSERT_NE(longPc, nullptr); + EXPECT_EQ(dwtAddressPc(*longPc), std::optional(0x08123456U)); +} + +TEST(CtraceUnitTests, testDwtPacketDecoderReconstructsCompressedDataAddresses) +{ + DwtComparatorValues comparatorValues{}; + comparatorValues[0U] = 0x20001200U; + DwtPacketDecoder decoder(comparatorValues); + + EXPECT_TRUE(decoder.decode(dwtPayload(9U, 1U, 0x56U, 10U)).empty()); + auto packets = decoder.flush({}, 100U); + ASSERT_EQ(packets.size(), 1U); + const auto* shortAddress = traceEventPayload(packets.front()); + ASSERT_NE(shortAddress, nullptr); + EXPECT_EQ(dwtAddressOffset(*shortAddress), std::optional(0x1256U)); + + EXPECT_TRUE(decoder.decode(dwtPayload(11U, 2U, 0x789aU, 11U)).empty()); + packets = decoder.flush({}, 101U); + ASSERT_EQ(packets.size(), 1U); + const auto* mediumAddress = traceEventPayload(packets.front()); + ASSERT_NE(mediumAddress, nullptr); + EXPECT_EQ(dwtAddressOffset(*mediumAddress), std::optional(0x789aU)); + + EXPECT_TRUE(decoder.decode(dwtPayload(13U, 4U, 0x2000abcdU, 12U)).empty()); + packets = decoder.flush({}, 102U); + ASSERT_EQ(packets.size(), 1U); + const auto* longAddress = traceEventPayload(packets.front()); + ASSERT_NE(longAddress, nullptr); + EXPECT_EQ(dwtAddressOffset(*longAddress), std::optional(0xabcdU)); +} + +TEST(CtraceUnitTests, testDwtPacketDecoderRejectsCompressedValuesWithoutComparatorMetadata) +{ + const auto verify = [](std::uint8_t discriminator, std::uint8_t size, std::uint32_t value, + std::string_view expected) { + DwtPacketDecoder decoder; + const auto packets = decoder.decode(dwtPayload(discriminator, size, value, 17U, 3U, 99U)); + ASSERT_EQ(packets.size(), 1U); + const auto* issue = traceEventPayload(packets.front()); + ASSERT_NE(issue, nullptr); + EXPECT_EQ(issue->code, TraceIssueCode::UnsupportedDwtAddressPayload); + EXPECT_NE(issue->message.find(expected), std::string::npos); + }; + + verify(8U, 1U, 0x34U, "DWT_COMP0"); + verify(10U, 2U, 0x5678U, "DWT_COMP1"); + verify(9U, 1U, 0x56U, "DWT_COMP0"); +} + +TEST(CtraceUnitTests, testDwtPacketDecoderRejectsPcValuesWithMatchBitSet) +{ + DwtComparatorValues comparatorValues{}; + comparatorValues.fill(0x08000000U); + for (const auto payload : + {dwtPayload(8U, 1U, 0x03U), dwtPayload(10U, 2U, 0x1235U), dwtPayload(12U, 4U, 0x08001235U)}) { + DwtPacketDecoder decoder(comparatorValues); + const auto packets = decoder.decode(payload); + ASSERT_EQ(packets.size(), 1U); + const auto* issue = traceEventPayload(packets.front()); + ASSERT_NE(issue, nullptr); + EXPECT_NE(issue->message.find("bit 0 is reserved for the match-packet discriminator"), std::string::npos); + } +} + TEST(CtraceUnitTests, testDwtPacketDecoderRejectsUnsupportedAddressWidths) { const auto verify = [](std::uint8_t discriminator, std::uint8_t size) { @@ -337,8 +427,8 @@ TEST(CtraceUnitTests, testDwtPacketDecoderRejectsUnsupportedAddressWidths) << "unsupported DWT address width diagnostic lost packet identity"; }; - verify(8U, 1U); - verify(9U, 4U); + verify(8U, 3U); + verify(9U, 3U); } TEST(CtraceUnitTests, testDwtPacketDecoderMapsAllExceptionActions) diff --git a/tools/ctrace/test/unit/src/tracerun/CtraceRunMetaTests.cpp b/tools/ctrace/test/unit/src/tracerun/CtraceRunMetaTests.cpp index 437db2ada..c6cb3bbb7 100644 --- a/tools/ctrace/test/unit/src/tracerun/CtraceRunMetaTests.cpp +++ b/tools/ctrace/test/unit/src/tracerun/CtraceRunMetaTests.cpp @@ -110,6 +110,54 @@ TEST(CtraceUnitTests, testCtraceRunMetaExpandsItmAndDwtSourceArrays) EXPECT_EQ(meta.sources()[3].source, 4U); } +TEST(CtraceUnitTests, testCtraceRunMetaResolvesDwtComparatorRegistersPerStream) +{ + TraceRunConfig config; + auto instructionRange = makeReference("dwt", std::nullopt, 1U, {0U, 1U}, "instructions:start#0"); + instructionRange.registers = { + {"DWT_COMP0", 0x08001234U}, + {"DWT_COMP1", 0x00005678U, 0x0000ffffU}, + {"DWT_COMP1", 0x08010000U, 0xffff0000U}, + {"DWT_FUNCTION0", 0x0000000fU}, + }; + auto second = makeReference("dwt", std::nullopt, 2U, {2U}, "data#1"); + second.dataSetupIndex = 1U; + second.registers = {{"DWT_COMP2", 0x20001200U}}; + auto incomplete = makeReference("dwt", std::nullopt, 2U, {3U}, "data#2"); + incomplete.dataSetupIndex = 2U; + incomplete.registers = {{"DWT_COMP3", 0x00003400U, 0x0000ff00U}}; + config.references = {instructionRange, second, incomplete}; + + const auto meta = CtraceRunMeta::fromConfig(config); + ASSERT_EQ(meta.sources().size(), 2U); + EXPECT_EQ(meta.sources()[0].source, 2U); + EXPECT_EQ(meta.sources()[1].source, 3U); + ASSERT_EQ(meta.dwtComparatorValuesByTraceBusId().size(), 2U); + const auto& streamOne = meta.dwtComparatorValuesByTraceBusId().at(1U); + EXPECT_EQ(streamOne.at(0U), 0x08001234U); + EXPECT_EQ(streamOne.at(1U), 0x08015678U); + const auto& streamTwo = meta.dwtComparatorValuesByTraceBusId().at(2U); + EXPECT_EQ(streamTwo.at(2U), 0x20001200U); + EXPECT_EQ(streamTwo.count(3U), 0U) << "a partially specified comparator must not be used for reconstruction"; +} + +TEST(CtraceUnitTests, testCtraceRunMetaRejectsConflictingDwtComparatorRegisters) +{ + TraceRunConfig config; + config.path = "trace.yml"; + auto first = makeReference("dwt", std::nullopt, 1U, {0U}, "data#0"); + first.dataSetupIndex = 0U; + first.registers = {{"DWT_COMP0", 0x08001200U}}; + auto second = first; + second.ctraceRef = "data#1"; + second.dataSetupIndex = 1U; + second.line = 17U; + second.registers = {{"DWT_COMP0", 0x20001200U}}; + config.references = {first, second}; + + EXPECT_TRUE(metaRejects(config, "trace.yml(17): DWT comparator 0 has conflicting values for Trace Bus ID 1")); +} + TEST(CtraceUnitTests, testCtraceRunMetaRejectsInvalidPrescalers) { TraceRunConfig config; diff --git a/tools/ctrace/test/unit/src/tracerun/TraceRunConfigReaderTests.cpp b/tools/ctrace/test/unit/src/tracerun/TraceRunConfigReaderTests.cpp index 3c1d5b6e7..1ff7660f1 100644 --- a/tools/ctrace/test/unit/src/tracerun/TraceRunConfigReaderTests.cpp +++ b/tools/ctrace/test/unit/src/tracerun/TraceRunConfigReaderTests.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -102,6 +103,12 @@ TEST(CtraceUnitTests, TraceRunReaderParsesConsumedFields) data-type: signed size: 1 label: Current + regs: + - name: DWT_COMP0 + value: 0x20000100 + - name: DWT_FUNCTION0 + value: 0x0000000f + mask: 0x0000000f )yml"); ASSERT_EQ(config.references.size(), 2U); ASSERT_EQ(config.setups.size(), 1U); @@ -127,6 +134,11 @@ TEST(CtraceUnitTests, TraceRunReaderParsesConsumedFields) EXPECT_EQ(dwt.dataSize, 1U); EXPECT_EQ(dwt.address, std::optional(0x20000100U)); EXPECT_EQ(dwt.label, std::optional("Current")); + ASSERT_EQ(config.references[1].registers.size(), 2U); + EXPECT_EQ(config.references[1].registers[0].name, "DWT_COMP0"); + EXPECT_EQ(config.references[1].registers[0].value, 0x20000100U); + EXPECT_EQ(config.references[1].registers[0].mask, std::numeric_limits::max()); + EXPECT_EQ(config.references[1].registers[1].mask, 0x0000000fU); } TEST(CtraceUnitTests, TraceRunReaderAcceptsScalarAndArraySourceNotation) @@ -306,6 +318,44 @@ TEST(CtraceUnitTests, TraceRunReaderRejectsMalformedReferenceRoutes) } } +TEST(CtraceUnitTests, TraceRunReaderRejectsMalformedDwtRegisters) +{ + TraceRunFixture file("ctrace-run-reader-register-errors-test"); + /** @brief Describes malformed register fields and their expected errors. */ + struct Case { + const char* registers; + const char* error; + }; + constexpr Case cases[] = { + {"{}", "'regs' must be an array"}, + {"[invalid]", "each 'regs' entry must be a map"}, + {"[{ value: 1 }]", "requires a non-empty scalar 'name'"}, + {"[{ name: [], value: 1 }]", "requires a non-empty scalar 'name'"}, + {"[{ name: DWT_COMP0 }]", "requires a scalar unsigned 'value'"}, + {"[{ name: DWT_COMP0, value: [] }]", "requires a scalar unsigned 'value'"}, + {"[{ name: DWT_COMP0, value: invalid }]", "'value' must be an unsigned integer"}, + {"[{ name: DWT_COMP0, value: 1, mask: [] }]", "'regs.mask' must be a scalar unsigned integer"}, + {"[{ name: DWT_COMP0, value: 1, mask: -1 }]", "'mask' must be an unsigned integer in range"}, + }; + for (const auto& testCase : cases) { + const auto yaml = std::string("ctrace-run:\n ctrace-refs:\n - type: dwt\n ctrace-ref: data#0\n") + + " source: 0\n regs: " + testCase.registers + "\n"; + expectReadError(file, yaml, testCase.error); + } + + const auto diagnosed = file.read(R"yml(ctrace-run: + ctrace-refs: + - type: dwt + ctrace-ref: data#0 + source: 0 + regs: invalid + error: producer rejected this route +)yml"); + ASSERT_EQ(diagnosed.references.size(), 1U); + EXPECT_TRUE(diagnosed.references.front().registers.empty()); + EXPECT_EQ(diagnosed.references.front().error, std::optional("producer rejected this route")); +} + TEST(CtraceUnitTests, TraceRunReaderPreservesDiagnosticReferences) { TraceRunFixture file("ctrace-run-reader-diagnostic-references-test");