Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions tools/ctrace/docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ backend-independent semantic events. Output backends consume these events to cre

The first release profile supports SWO data containing ITM and DWT packets. The command line accepts the stable type
names `itm`, `dwt`, `event`, `pmu`, `exception`, `pcsample`, `global_ts`, `overflow`, and `error`. Output semantics are
currently implemented for `itm`, `dwt`, `exception`, `global_ts`, `overflow`, and `error`. DWT event-counter and PMU
packets are retained internally but are not mapped to their selectors yet; periodic PC samples remain disabled. Trace
Bus input is discovered so that a complete trace directory can be inspected, but `*.TB.raw` files are reported and
skipped until a decoder is implemented.
currently implemented for `itm`, `dwt`, `exception`, `pcsample`, `global_ts`, `overflow`, and `error`. DWT event-counter
and PMU packets are retained internally but are not mapped to their selectors yet. Periodic PC samples reach CSV and
CTF as semantic events; the CTF event distinguishes a sampled PC from a processor-sleep indication, and Trace Compass
shows processor-sleep intervals as a timeline. Trace Bus input is discovered so that a complete trace directory can be
inspected, but `*.TB.raw` files are reported and skipped until a decoder is implemented.

The architecture separates protocol decoding, semantic interpretation, and output generation. This keeps output
formats independent of OpenCSD and allows another raw trace channel to reuse the event model and output backends.
Expand Down
1 change: 0 additions & 1 deletion tools/ctrace/docs/todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
- [ ] Add Armv8-M and Armv8.1-M DWT decoding.
- [ ] Add `event` output in its own PR.
- [ ] Add `pmu` output in its own PR.
- [ ] Add PC Sampling in its own PR after agreeing the sleep/`PC_SAMPLE` CTF contract.

## Multiple streams

Expand Down
28 changes: 25 additions & 3 deletions tools/ctrace/src/decode/DwtPacketDecoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,32 @@ std::vector<TraceEvent> DwtPacketDecoder::decode(const DwtPayloadPacket& payload
}

if (source == DwtPacketSource::PeriodicPcSample) {
// PC samples need a dedicated output event. Until that event is
// defined, flush preceding data trace but do not expose the sample as
// an address event.
output = flush(payload.quality, payload.tcyc);
const auto isPc = payload.size == 4U;
const auto isSleeping = payload.size == 1U && payload.value == 0U;
if (!isPc && !isSleeping) {
TraceEvent error{TraceIssueEvent{
TraceIssueCode::UnsupportedDwtPcSamplePayload,
TraceIssueSeverity::Error,
"unsupported DWT PC-sample payload: size " + std::to_string(payload.size) +
", value " + std::to_string(payload.value) +
"; expected a 4-byte PC or a 1-byte zero sleep indication",
std::nullopt,
std::nullopt,
}};
error.index = payload.index;
error.traceBusId = payload.traceBusId;
error.tcyc = payload.tcyc;
error.quality = payload.quality;
output.push_back(std::move(error));
return output;
}
TraceEvent packet{PcSampleTraceEvent{payload.value, isSleeping}};
packet.index = payload.index;
packet.traceBusId = payload.traceBusId;
packet.tcyc = payload.tcyc;
packet.quality = payload.quality;
output.push_back(std::move(packet));
return output;
}

Expand Down
1 change: 1 addition & 0 deletions tools/ctrace/src/diagnostics/TraceIssueReporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ static std::string displayErrorMessage(const TraceEvent& event, const TraceIssue
case TraceIssueCode::DecodeError:
case TraceIssueCode::InvalidExceptionAction:
case TraceIssueCode::UnsupportedDwtAddressPayload:
case TraceIssueCode::UnsupportedDwtPcSamplePayload:
case TraceIssueCode::OpenCsdDecodeError:
return atRawOffset("trace decode error", event);
}
Expand Down
9 changes: 8 additions & 1 deletion tools/ctrace/src/model/TraceEvent.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ enum class TraceIssueCode {
DataLoss,
InvalidExceptionAction,
UnsupportedDwtAddressPayload,
UnsupportedDwtPcSamplePayload,
OpenCsdDecodeError,
OpenCsdBadPacketSequence,
OpenCsdInvalidPacketHeader,
Expand Down Expand Up @@ -138,6 +139,12 @@ struct PmuTraceEvent {
std::uint32_t value = 0;
};

/** @brief Contains a periodic DWT PC sample or its processor-sleep indication. */
struct PcSampleTraceEvent {
std::uint32_t pc = 0;
bool sleeping = false;
};

/** @brief Marks a decoded local timestamp packet. */
struct LocalTimestampTraceEvent {};

Expand Down Expand Up @@ -166,7 +173,7 @@ struct TraceIssueEvent {

/** @brief Stores the semantic payload of a decoded trace event. */
using TraceEventPayload = std::variant<SoftwareTraceEvent, DwtDataTraceEvent, DwtAddressTraceEvent, ExceptionTraceEvent,
DwtEventTraceEvent, PmuTraceEvent, LocalTimestampTraceEvent,
DwtEventTraceEvent, PmuTraceEvent, PcSampleTraceEvent, LocalTimestampTraceEvent,
GlobalTimestampTraceEvent, OverflowTraceEvent, SyncTraceEvent, TraceIssueEvent>;

/** @brief Describes timestamp and data-loss quality at an event. */
Expand Down
6 changes: 6 additions & 0 deletions tools/ctrace/src/model/TraceSelection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ static std::optional<TraceEventType> typeFor(const PmuTraceEvent&)
return std::nullopt;
}

/** @brief Exposes periodic DWT PC samples through their public output selector. */
static std::optional<TraceEventType> typeFor(const PcSampleTraceEvent&)
{
return TraceEventType::PcSample;
}

/** @brief Excludes local timestamp control packets from type selection. */
static std::optional<TraceEventType> typeFor(const LocalTimestampTraceEvent&)
{
Expand Down
4 changes: 4 additions & 0 deletions tools/ctrace/src/output/csv/CsvRowMapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@ static CsvRow eventToCsvRow(const TraceEvent& event)
} else if (const auto* exception = traceEventPayload<ExceptionTraceEvent>(event)) {
row[column(CsvColumn::Source)] = std::to_string(exception->number);
row[column(CsvColumn::Value)] = exceptionActionCsvValue(exception->action);
} else if (const auto* sample = traceEventPayload<PcSampleTraceEvent>(event)) {
if (!sample->sleeping) {
row[column(CsvColumn::Pc)] = hexValue(sample->pc, 4);
}
} else if (const auto* timestamp = traceEventPayload<GlobalTimestampTraceEvent>(event)) {
row[column(CsvColumn::Cycles)] = std::to_string(timestamp->value);
} else if (const auto* overflow = traceEventPayload<OverflowTraceEvent>(event)) {
Expand Down
23 changes: 23 additions & 0 deletions tools/ctrace/src/output/ctf/CtfEncoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,10 @@ void CtfEncoder::writeEvent(const TraceEvent& event)
if (selected) {
writeDwtAddrEvent(event, *address);
}
} else if (const auto* sample = traceEventPayload<PcSampleTraceEvent>(event)) {
if (selected) {
writePcSampleEvent(event, *sample);
}
} else if (isTraceEvent<OverflowTraceEvent>(event)) {
auto& streamState = m_streamStates[event.traceBusId];
if (event.quality.has_value()) {
Expand Down Expand Up @@ -231,6 +235,25 @@ void CtfEncoder::writeEvent(const TraceEvent& event)
}
}

void CtfEncoder::writePcSampleEvent(const TraceEvent& event, const PcSampleTraceEvent& sample)
{
const auto pcSize = sample.sleeping ? 0U : 4U;
const auto payloadSize = 1U + pcSize + 1U + 4U;
const auto eventTimestamp = allocateEventTimestamp(event.traceBusId);
const auto quality = computeSampleQuality(event);
const auto state = CtfSchema::value(sample.sleeping ? CtfSchema::PcSampleState::Sleep
: CtfSchema::PcSampleState::Pc);
m_stream.writeRecord(CtfSchema::value(CtfSchema::EventId::PcSample), eventTimestamp, event.traceBusId, payloadSize,
[&](CtfStreamWriter::Record& record) {
record.writeU8(state);
if (!sample.sleeping) {
record.writeU32(sample.pc);
}
record.writeU8(quality.first);
record.writeU32(quality.second);
});
}

std::uint64_t CtfEncoder::allocateEventTimestamp(std::uint8_t traceBusId)
{
// CtfStreamWriter applies the final monotonic clamp across the multiplexed
Expand Down
2 changes: 2 additions & 0 deletions tools/ctrace/src/output/ctf/CtfEncoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ class CtfEncoder final {
void reportDwtSizeMismatch(const TraceEvent& event, const DwtDataTraceEvent& data, const ResolvedTraceSource* source);
/** @brief Encodes one DWT address event. */
void writeDwtAddrEvent(const TraceEvent& event, const DwtAddressTraceEvent& address);
/** @brief Encodes one periodic PC-sample or processor-sleep event. */
void writePcSampleEvent(const TraceEvent& event, const PcSampleTraceEvent& sample);
/** @brief Encodes one reconstructed global timestamp event. */
void writeGlobalTimestampEvent(const TraceEvent& event, const GlobalTimestampTraceEvent& timestamp);
/** @brief Applies one exception transition to its CTF lane state. */
Expand Down
22 changes: 22 additions & 0 deletions tools/ctrace/src/output/ctf/CtfMetadataWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,27 @@ event {
)";
}

/** @brief Writes the periodic PC-sample event declaration. */
static void writePcSampleEvent(std::ostream& out)
{
out << R"(
event {
id = )"
<< CtfSchema::value(CtfSchema::EventId::PcSample) << R"(;
name = ")"
<< CtfSchema::eventName(CtfSchema::EventId::PcSample) << R"(";
stream_id = )"
<< CtfSchema::SwoStreamId << R"(;
fields := struct {
uint8_t cmsis_pc_sample_state;
uint32_t cmsis_pc[cmsis_pc_sample_state];
uint8_t cmsis_sample_flags;
uint32_t cmsis_overflow_count;
};
};
)";
}

/** @brief Writes status, exception, and global timestamp declarations. */
static void writeStatusEvents(std::ostream& out)
{
Expand Down Expand Up @@ -491,6 +512,7 @@ void CtfMetadataWriter::write(const std::filesystem::path& outputDir, const std:
writeDwtValueEvent(out);
writeDwtAddressEvent(out);
writeStatusEvents(out);
writePcSampleEvent(out);
out.close();
if (!out) {
throw std::runtime_error("Failed to write CTF metadata " + metadataPath.string());
Expand Down
15 changes: 15 additions & 0 deletions tools/ctrace/src/output/ctf/CtfSchema.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ enum class EventId : std::uint32_t {
TraceStatus = 3U,
Exception = 4U,
GlobalTimestamp = 5U,
PcSample = 6U,
};

/** @brief Classifies CTF trace-status records. */
Expand Down Expand Up @@ -56,6 +57,12 @@ enum class ExceptionOrigin : std::uint8_t {
Synthetic = 1U,
};

/** @brief Identifies whether a periodic PC sample carries a PC or reports processor sleep. */
enum class PcSampleState : std::uint8_t {
Sleep = 0U,
Pc = 1U,
};

/** @brief Identifies the supported CTF sample value encodings. */
enum class ValueTag : std::uint8_t {
Signed8 = 0U,
Expand Down Expand Up @@ -154,6 +161,12 @@ constexpr std::uint8_t value(ExceptionAction action)
return static_cast<std::uint8_t>(action);
}

/** @brief Returns the integer representation of a PC-sample state. */
constexpr std::uint8_t value(PcSampleState state)
{
return static_cast<std::uint8_t>(state);
}

/** @brief Returns the integer representation of an exception record origin. */
constexpr std::uint8_t value(ExceptionOrigin origin)
{
Expand All @@ -176,6 +189,8 @@ constexpr std::string_view eventName(EventId id)
return "EXCEPTION";
case EventId::GlobalTimestamp:
return "GLOBAL_TIMESTAMP";
case EventId::PcSample:
return "PC_SAMPLE";
}
return "UNKNOWN";
}
Expand Down
64 changes: 64 additions & 0 deletions tools/ctrace/src/output/ctf/TraceCompassXmlWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ static std::string valueHandlers(CtfSchema::EventId eventId, const char* prefix,
/** @brief Generates the Trace Compass state-provider definition. */
static std::string stateProviderXml()
{
// Numeric time-graph states are exposed as TSP style keys; string states are
// serialized without a style and appear as gaps in compatible clients.
std::ostringstream xml;
xml << R"( <stateProvider version="__SWO_ANALYSIS_VERSION__" id="arm.cmsis.swo.analysis.v1">
<head><label value="SWO Trace Analysis" /></head>
Expand Down Expand Up @@ -198,13 +200,67 @@ static std::string stateProviderXml()
</stateChange>
</eventHandler>
<eventHandler eventName=")"
<< CtfSchema::eventName(CtfSchema::EventId::PcSample) << R"(">
<stateChange>
<if>
<condition>
<stateValue type="eventField" value="cmsis_pc_sample_state" />
<stateValue type="long" value=")"
<< static_cast<unsigned>(CtfSchema::value(CtfSchema::PcSampleState::Sleep)) << R"(" />
</condition>
</if>
<then>
<stateAttribute type="constant" value=")"
<< CtfSchema::eventName(CtfSchema::EventId::PcSample) << R"(" />
<stateAttribute type="constant" value="Sleep" />
<stateValue type="int" value=")"
<< static_cast<unsigned>(CtfSchema::value(CtfSchema::PcSampleState::Sleep)) << R"(" />
</then>
</stateChange>
<stateChange>
<if>
<condition>
<stateValue type="eventField" value="cmsis_pc_sample_state" />
<stateValue type="long" value=")"
<< static_cast<unsigned>(CtfSchema::value(CtfSchema::PcSampleState::Pc)) << R"(" />
</condition>
</if>
<then>
<stateAttribute type="constant" value=")"
<< CtfSchema::eventName(CtfSchema::EventId::PcSample) << R"(" />
<stateAttribute type="constant" value="Sleep" />
<stateValue type="null" />
</then>
</stateChange>
</eventHandler>
<eventHandler eventName=")"
<< CtfSchema::eventName(CtfSchema::EventId::TraceStatus) << R"(">
<stateChange>
<stateAttribute type="constant" value=")"
<< CtfSchema::eventName(CtfSchema::EventId::TraceStatus) << R"(" />
<stateAttribute type="eventField" value="cmsis_trace_status_reason" />
<stateValue type="eventField" value="cmsis_trace_status_reason" />
</stateChange>
<stateChange>
<if>
<or>
<condition>
<stateValue type="eventField" value="cmsis_trace_status_reason" />
<stateValue type="string" value="overflow" />
</condition>
<condition>
<stateValue type="eventField" value="cmsis_trace_status_reason" />
<stateValue type="string" value="data_loss" />
</condition>
</or>
</if>
<then>
<stateAttribute type="constant" value=")"
<< CtfSchema::eventName(CtfSchema::EventId::PcSample) << R"(" />
<stateAttribute type="constant" value="Sleep" />
<stateValue type="null" />
</then>
</stateChange>
<stateChange>
<if>
<condition>
Expand Down Expand Up @@ -283,6 +339,14 @@ static std::string viewsXml()
<< CtfSchema::eventName(CtfSchema::EventId::TraceStatus)
<< '/' << R"(*" displayText="true"><display type="self" /><name type="self" /></entry>
</timeGraphView>
<timeGraphView id="arm.cmsis.swo.tg.pc_sample.v1">
<head><analysis id="arm.cmsis.swo.analysis.v1" /><label value="PC Sampling" /></head>
<definedValue name="Sleep" value=")"
<< static_cast<unsigned>(CtfSchema::value(CtfSchema::PcSampleState::Sleep)) << R"(" color="#5B8FF9" />
<entry path=")"
<< CtfSchema::eventName(CtfSchema::EventId::PcSample)
<< '/' << R"(*" displayText="true"><display type="self" /></entry>
</timeGraphView>
)";
return xml.str();
}
Expand Down
3 changes: 2 additions & 1 deletion tools/ctrace/test/integration/src/CtraceIntegTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,13 @@ TEST_F(CtraceIntegTests, GeneratesAllOutputs)
ctrace-refs: []
)yml");

const std::string raw{"\0\0\0\0\0\x80\x09\x41", 8U};
const std::string raw{"\0\0\0\0\0\x80\x17\x34\x12\x00\x08\x09\x41", 13U};
writeFile(workDirectory() / "Minimal.SWO.raw", raw);

const auto result = run({"ctrace", workDirectory().string(), "--target", "Minimal", "--all"});
EXPECT_EQ(0, result.exitCode) << result.stderrText;
EXPECT_EQ("cycles,stream,type,source,value,pc,offset,note\n"
"0,,pcsample,,,0x08001234,,\n"
"0,,itm,1,0x41,,,\n",
readTextFile(workDirectory() / "Minimal.SWO.csv"));
expectNonEmptyFile(workDirectory() / "Minimal.ctf" / "metadata");
Expand Down
22 changes: 22 additions & 0 deletions tools/ctrace/test/unit/src/decode/DecodePipelineTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,28 @@ TEST(CtraceUnitTests, testDecodePipelinePreservesDwtEventAndPmuPackets)
ASSERT_TRUE(foundPmu) << "OpenCSD PMU-overflow packet must survive post-decoding";
}

TEST(CtraceUnitTests, testDecodePipelinePreservesPeriodicPcSamples)
{
const std::uint8_t trace[] = {
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x80U,
0x17U, 0x34U, 0x12U, 0x00U, 0x08U,
0x15U, 0x00U,
};
const auto decoded = decodeTrace({rawBytes(trace)});

std::vector<PcSampleTraceEvent> samples;
for (const auto& event : decoded.events) {
if (const auto* sample = traceEventPayload<PcSampleTraceEvent>(event)) {
samples.push_back(*sample);
}
}
ASSERT_EQ(samples.size(), 2U) << "OpenCSD periodic PC-sample packet count mismatch";
EXPECT_EQ(samples[0].pc, 0x08001234U) << "OpenCSD periodic PC sample payload mismatch";
EXPECT_FALSE(samples[0].sleeping) << "OpenCSD periodic PC sample payload mismatch";
EXPECT_EQ(samples[1].pc, 0U) << "OpenCSD periodic PC sleep indication mismatch";
EXPECT_TRUE(samples[1].sleeping) << "OpenCSD periodic PC sleep indication mismatch";
}

TEST(CtraceUnitTests, testDecodePipelineDoesNotInjectSync)
{
const std::uint8_t validWithoutAsync[] = {0x01U, static_cast<std::uint8_t>('A')};
Expand Down
Loading
Loading