Menu

Searches titles, sections and text. ↑ ↓ to move, Enter to open, Esc to close. Opens with / or Ctrl+K.

    Contact

    Observability: what a control-flow trace can show, and where source-level coverage breaks

    A validated control-flow trace reconstructs machine execution for a declared interval; source coverage asks whether those events map to source obligations.


    For an observation interval whose trace-validity checks pass, control-flow trace can provide the machine-level program-counter sequence required by the analysis. This statement is limited to the declared trace source, protocol, configuration and interval; detected overflow, discontinuity or unsupported events invalidate or qualify the affected interval. Everything in this article follows from what that sentence includes, and from what it leaves out.

    This page is about one information basis: a passive control-flow trace of the otherwise unmodified, optimized production binary. Compiler-integrated coverage that instruments the program before the optimizer runs - Clang’s source-based coverage, for instance - works from a different basis; how the two relate is discussed below.

    In brief

    Within a validated interval, control-flow trace records the machine events used for control-flow reconstruction; source attribution remains a separate step that needs the mapping from object code back to source obligations. Optimization can merge, clone or remove exactly the structures that mapping relies on - the machine events stay observable while attribution degrades. What follows walks that chain with object-code examples; the summary concept has its own page: source attribution.

    The trust chain, stated once

    A source-level result depends on a chain: source revision → compiler and flags → generated object code → linked executable → debug and mapping information → loaded image → trace observation → reconstructed addresses → source attribution. A mismatch or ambiguity at any link limits the source-level claim even when the object-code observation is valid.

    Before mapping, the workflow verifies that the program image and debug information correspond to the executable used in the run. The check uses build identifiers or cryptographic hashes rather than filenames alone.

    What the protocol transmits

    The trace unit does not report every instruction. For a declared configuration and a valid interval, it emits compressed branch-outcome and address information - together with synchronization points - from which a decoder that holds the program image reconstructs the executed control-flow sequence, inferring the sequential instructions in between. “Discontinuity” alone would be too narrow a model: a conditional branch that is not taken produces no program-counter discontinuity, yet its outcome is still encoded, because the decoder needs it for the reconstruction.

    The following examples use different encoding models and configuration vocabularies; they are not interchangeable wire formats. The rows give the logical information carried, not a complete encoding description:

    ProtocolConditional branchIndirect branchResynchronization
    Arm® CoreSight™ ETMv3P-header elements carrying taken/not-taken outcomesbranch address packetsA-Sync byte alignment plus I-Sync instruction-address and state packets
    Arm® CoreSight™ ETMv4atom elements; interpretation requires the reconstructed instruction context - do not equate E/N mechanically with taken/not-takenaddress packetstrace-synchronization and address packets
    Intel® PTTNT packets carrying sequences of taken/not-taken outcomesTIP packetsPSB packet-stream boundaries with their synchronization context
    Nexus (IEEE-ISTO 5001)branch-history field carrying multiple outcomesunique address fieldsync message
    RISC-V® E-Tracebranch maps carrying multiple branch outcomes; optional modes change which outcomes are transmitted explicitlyaddress, differentially encodedsynchronization packets
    Infineon® MCDSdocumentation restricted, available under NDA

    One compactly coded outcome per conditional branch, an address when the target cannot be inferred - that is the minimum control-flow idea. Actual streams can also carry further trace information - context, exceptions, events, timestamps, filtering state, data trace and synchronization - according to architecture and configuration.

    The MCDS row is deliberately empty. Its message-level specification is restricted and reaches tool vendors under NDA, so the wire format does not belong on a public page, and this article makes no claim about what it carries beyond what Infineon has published itself. The observability argument that follows is made for control-flow-only trace configurations in general; whether and how it applies to a specific MCDS configuration has to be established against Infineon’s documentation for that configuration.

    Branches are observable, conditions are not

    This is the statement the rest of the article rests on:

    Within a valid, continuous and correctly configured instruction-trace interval, the decoder can reconstruct the control-flow distinctions represented by that architecture and configuration. Optional data and context channels are separate claims.

    What a decoder can establish from the stream, with certainty:

    • that a branch instruction executed;
    • which way it went;
    • the target of an indirect branch;
    • the order in which all of this happened.

    What a control-flow-only stream does not carry:

    • the value of any register, flag or memory location;
    • whether a conditional non-branch instruction selected its result - the trace shows that the instruction executed, not what it decided;
    • which source-level condition contributed to a branch outcome;
    • anything that did not become a branch in the object code.

    Nothing here is a shortcoming of a particular protocol. It is the design point: the control-flow protocols above deliberately do not carry a complete value history of registers and memory - continuous general data trace is far more bandwidth- and storage-intensive than compressed branch outcomes, which is exactly why the stream is compressed the way it is.

    The same condition, twice

    The two figures below are the whole argument in one picture each. Same source line, same metric, one optimization level apart - and the difference is not in the trace, it is in what reached the object code.

    In the first case each condition became a branch, both branches are in the stream, and the per-evaluation observations that MC/DC needs are recoverable. In the second the compiler combined both conditions into one operation: the decision is still observable, the conditions are not. Nothing was lost in transmission. The branch that would have carried the information was never emitted. MC/DC itself is a property of a set of executions - each condition additionally needs an independence pair across the test set; what the object-code structure decides is whether the per-evaluation inputs exist at all.

    One exception worth knowing. The ETMv4 architecture defines an optional conditional-instruction tracing capability that can signal conditionally executed non-branch instructions, which narrows the gap for that one class. It is an implementation option rather than a guarantee: whether a given part implements it is discoverable from the trace unit’s identification registers and its documentation, and support must be established for the specific processor, ETM version and trace configuration before evidence relies on it.

    Source attribution

    Source attribution is the step between machine observation and source-level evidence.

    It asks whether an observed instruction, branch outcome or path can be assigned to the correct source-level coverage obligation without ambiguity.

    This is not the same as trace completeness. An instruction trace may contain every required machine-level event while optimization has removed the one-to-one relationship needed to determine which source condition produced that event.

    A useful report should therefore distinguish three properties:

    1. Trace completeness: whether the relevant machine execution was observed.
    2. Source attribution: whether the observation can be assigned to the correct source obligation.
    3. Coverage achieved: whether the attributable obligation was exercised as required.

    Treating these properties as one percentage can hide important limitations in the resulting evidence.

    See source attribution measured on an optimized SQLite workload

    cmp a, #0
    bne .Lskip
    cmp b, #0
    bne .Lskip
    one branch per condition
    cmp a, #0...
    atom E
    atom N
    two atoms:
    the vector is in the stream
    atom E...
    Condition vector recoverable
    Each condition became its own branch, and every branch is in the trace.
    The per-evaluation observations the metric needs are present because the structure survived into the object code.

    Condition vector recoverable...
    Source
    Source
    Object code (-O0)
    Object code (-O0)
    Trace stream
    Trace stream
    if ((a == 0) && (b == 0))
    act();
    two conditions
    if ((a == 0) && (b == 0))...
    Each condition became a branch, and both branches are in the stream: the per-evaluation observations the metric needs are recoverable (schematic).

    The three questions a metric asks

    Now put the metrics next to that capability.

    MetricThe question it asksCan the protocol answer it?
    Object instructiondid this machine instruction execute?yes, within a valid reconstructed interval
    Source statementdid this source-level statement execute?only through a validated object-to-source attribution
    Branch / decisiondid both outcomes of this decision occur?yes, provided the decision is a branch and that branch is validly attributed to the source decision
    Conditiondid each condition take both values?only if each condition evaluation has a distinct, attributable, trace-observable representation - its own branch, a supported conditional-execution event, or a verified supplemental marker
    MC/DCdid each condition independently affect the outcome?only if the condition vector and decision outcome of each evaluation are recoverable and groupable per execution instance - and the test set contains an independence pair for each condition

    The rows mix four layers on purpose, and the mix is the point: what the protocol reports directly, what the decoder reconstructs from it, what a validated static mapping attributes, and what the coverage evaluator may conclude from grouped observations. The last two rows are answered only when the source-level decision structure is present in the object code - because that structure is the only place the information can come from.

    That is the observability problem in one sentence: the relevant decisions have to be representable in the control flow. Whether they are is decided by the compiler, not by the tool.

    How mapping results are classified

    Mapping results are classified as direct, many-to-one, one-to-many, compiler-generated, inlined, optimized away, ambiguous, or unmapped. The classes can overlap - an inlined region can at the same time be one-to-many, and compiler-generated instructions can sit inside a mapped source range - so a usable report rests on a documented classification precedence and a documented treatment of every class in the coverage denominator. The reporting policy is that every object-code block is assigned to a class and surfaces in the report rather than being silently dropped, and that source coverage is computed only according to the documented treatment of those classes.

    requirement R1 · function f()
    requirement R1 · function f()
    block 0x100..0x13F
    block 0x100..0x13F
    executed
    executed
    inlined helper g()
    inlined helper g()
    block 0x140..0x16F
    block 0x140..0x16F
    block 0x170..0x19F
    block 0x170..0x19F
    executed
    executed
    not executed in this run
    not executed in this run
    macro M in two functions
    macro M in two functions
    template instance h<T>()
    template instance h<T>()
    block 0x1A0..0x1CF
    block 0x1A0..0x1CF
    executed
    executed
    block 0x1D0..0x1EF
    compiler-generated, no source line
    block 0x1D0..0x1EF...
    Review queue
    unresolved mappings are listed, not dropped
    Review queue...
    Source constructs
    Source constructs
    Object-code blocks
    Object-code blocks
    Trace-observed execution
    Trace-observed execution
    1:1
    1:1
    Address ranges are schematic; the mapping classes are the ones described in the article.
    Address ranges are schematic; the mapping classes are...
    1:many
    1:many
    many:1
    many:1
    Reporting policy: every object-code block is assigned a mapping class - mapped one-to-one, split, merged, or handed to the review queue - rather than silently dropped (schematic).

    Example 1: the computed decision

    if ((a == 0) && (b == 0))
        act();

    At -O0 the compiler emits the short-circuit structure literally: compare a, branch; compare b, branch. Two conditions, two branches.

    The trace stream then carries two atoms. Each one is attributable to a source condition, and the pair of outcomes is the condition vector of that evaluation

    • the per-evaluation input MC/DC needs.

    At -O3 the compiler is free to evaluate the whole decision arithmetically

    • combine both comparisons into one operation and emit a single conditional branch on the result.

    Now the stream carries one atom. The decoder sees that the decision was taken or not taken. It cannot see which condition made it so, because the information never entered the control flow: the conditional branch is visible in the trace, the arguments of the arithmetic operation are not.

    Read that against the table above and the verdict is not a matter of tool quality. Branch coverage is still answerable. Condition coverage and MC/DC are not, because the branch they would need does not exist.

    if ((a == 0) && (b == 0))
    act();
    same two conditions
    if ((a == 0) && (b == 0))...
    orr r0, a, b
    cbnz r0, .Lskip
    one branch for both conditions fused
    orr r0, a, b...
    atom E
    one atom: the decision,
    not the conditions
    atom E...
    MC/DC not derivable
    The branch that would carry each condition does not exist. Nothing is lost in transmission.
    Branch coverage is still answerable. Condition level is not - the information never entered the flow.

    MC/DC not derivable...
    Source
    Source
    Object code (-O3)
    Object code (-O3)
    Trace stream
    Trace stream
    Same source, one optimization level higher. Nothing was lost in transmission - the branch that would carry each condition was never emitted (schematic).

    Example 2: the conditional move

    b = (a == 0) ? 42 : b;

    At -O0, again the literal structure: a compare and a conditional branch around the assignment.

    At -O3, the compiler emits a conditional selection instead of a branch: on x86-64 a cmovne conditional move, on AArch64 a csel conditional-select instruction; A32/T32 code can reach the same effect through predicated execution or an IT block, whose trace observability depends on the ETM architecture and configuration. There is no branch at all.

    This case is subtler than the first, and worse. Many common instruction-trace configurations report that the instruction executed; they do not report the predicate outcome - whether the destination received the new value or kept its old one. So the stream does not merely lack the condition - it contains a positive signal that can be misread as coverage. The instruction ran. What it selected is not in the trace.

    The optional ETMv4 conditional-instruction tracing capability mentioned above narrows this class of loss where a part implements it; whether a given part does has to be established from its identification registers and documentation before evidence relies on it. Where it is absent, the mitigation below is the reliable route.

    The documented mitigation is a build constraint: forbid conditional non-branch instructions. The available controls are compiler- and target-specific aids rather than a general guarantee - coding style helps, but avoiding the ternary operator does not prevent a compiler from if-converting an ordinary if/else, and flags such as GCC’s -fno-if-conversion and -fno-if-conversion2 address specific passes while back-end instruction selection can still produce branchless forms. What generalizes is the verification: prove the absence of conditional non-branch instructions in the generated object code.

    Note what just happened, though. The coverage requirement has started to dictate the code generation of the build you ship.

    if ((a==0) && (b==0)) act();
    if ((a==0) && (b==0)) act();
    Branchless conditions
    predication, bitwise combination
    Branchless conditions...
    Merged conditions
    two tests become one
    Merged conditions...
    Vector loss
    hoisting, reordering, arm selection
    Vector loss...
    Source
    Source
    Each condition visible. MC/DC inputs recoverable.
    Each condition visible. MC/DC inputs recoverable.
    Three ways the evidence is lost
    Three ways the evidence is lost
    -O0

    cmp r0, #0
    bne .Lskipcondition a
    cmp r1, #0
    bne .Lskipcondition b
    bl act
    -O0...
    -O3

    orr r0, r0, r1both conditions fused
    cbnz r0, .Lskip
    bl act

    no branch for a, none for b
    One branch. Per-condition history gone.
    -O3...
    Assembly shortened for legibility (schematic); the exact compiler output for both optimization levels is reproducible via the Compiler Explorer links at the end of this page.

    Two further mapping hazards

    Context. After optimizing transformations, the mapping from a source condition to a specific conditional branch can simply be gone - the branch exists, its provenance does not. The blunt mitigation recorded in our own coverage notes - if the compiler does not optimize the code (-O0), the described effect does not occur - is an observation scoped to the compilers and source patterns it was recorded for, not a general rule: -O0 disables most discretionary optimization passes, not every transformation; mandatory lowering, instruction selection and macro expansion remain. The preprocessor makes provenance worse: common compilers attribute every token of a multi-line macro expansion to the invocation location, in which case column information cannot separate the expanded tokens - DWARF 5 defines dedicated macro-information structures, but line tables that point at the invocation site are what tools frequently encounter.

    Direction. The compiler chooses whether a source-level true becomes a jump taken or not taken - JZ or JNZ is its decision. This is not an information loss but a polarity-mapping task: the source-to-object polarity must be established before a missing branch outcome may be reported as a missing source outcome. Once object-level coverage is complete the distinction stops mattering for the verdict; it still costs the engineer time when hunting a missing test case.

    Tree and diamond

    One distinction is not the optimizer’s doing at all - and it is easy to state incorrectly, so it deserves care.

    In a tree-shaped decision, every path to an outcome is unique, so the condition vector of an evaluation follows from the branch outcomes alone - object-level branch observations are sufficient to reconstruct which condition combinations occurred. In a diamond-shaped one - the reconvergence that short-circuit evaluation produces naturally - two different condition vectors can reach the same successor block, so from the merge point on the current control-flow node alone no longer identifies them.

    A complete, ordered trace interval still contains the branch outcomes that were emitted before the merge point; reconvergence changes the current node, it does not erase already-emitted history from the stream. What breaks is the shortcut: an analysis that keeps only aggregate edge or branch counts, or that does not delimit which observations belong to one evaluation of the decision, cannot tell the reconverging vectors apart. Distinguishing them requires per-instance condition histories - the ordered observations of one execution instance, grouped per decision. The supplementation mechanism described below provides exactly that grouping; without such a grouping model, no optimization setting and no amount of debug information turns aggregate branch counts back into per-instance vectors.

    In real code diamond-shaped decisions are a minority and not a negligible one.

    Optimization does not make source mapping uniformly impossible. It makes the relationship non-uniform: source statements can be reordered, merged, duplicated by inlining or represented without a distinct address range. The report preserves those distinctions rather than converting every executed address into a simple line hit.

    Tree: each path is unique
    Tree: each path is unique
    if (a && b)
    if (a && b)
    Condition vector derivable from the path
    Condition vector derivable from the path
    branch trace is sufficient at -O0
    branch trace is sufficient at -O0
    Diamond: two paths, one block
    Diamond: two paths, one block
    if (a || b)
    if (a || b)
    The path alone stops distinguishing them
    The path alone stops distinguishing them
    independent of optimization level
    independent of optimization level
    T
    T
    F
    F
    a
    a
    T
    T
    F
    F
    b
    b
    act()
    act()
    skip
    skip
    F
    F
    T
    T
    a
    a
    T
    T
    b
    b
    F
    F
    reached two ways
    reached two ways
    skip
    skip
    In a tree-shaped decision every path to an outcome is unique, so the condition vector follows from the branch outcomes; in a diamond-shaped one two different vectors reach the same successor block, and telling them apart requires per-instance condition histories rather than aggregate branch counts.

    Compiler-integrated coverage under optimization

    Everything above concerns a passive control-flow trace of the otherwise unmodified, optimized binary. Compiler-integrated coverage works from a different basis - and the two mechanisms in common use differ from each other as well.

    Clang’s source-based coverage generates its source-to-counter mapping from AST and preprocessor information before the optimizer runs, and the inserted counter and MC/DC-bitmap updates are exactly the state-writing instrumentation this page sets aside; its documentation states that optimizations do not affect report quality because the optimizer cannot remove the instrumentation. GCC’s condition coverage instead derives condition outcomes from control-flow-graph edges, and GCC’s documentation warns that optimization merges source constructs.

    In our reproduction of example 1, GCC 15.2 with -fcondition-coverage reports four condition outcomes at -O0 and two at -O1, -O2, -O3 and -Os - the fusion propagates into the compiler’s own MC/DC evidence - while Clang 22.1 with -fcoverage-mcdc preserves both conditions as separately instrumented tests at -O3. The result is the same whether the two conditions are written inline or first hoisted into named Boolean variables. Neither resulting coverage report exposes the optimization-induced fusion as a distinct diagnostic. In the tested GCC case, the report reflects the fused CFG; the reduction from four to two condition outcomes becomes apparent when optimization levels are compared or the generated CFG or object code is inspected. Clang’s source-based MC/DC instrumentation preserves the two source conditions in the instrumented build, so the fusion does not arise there.

    GCC 15.2.0, -fcondition-coverage, both source formscondition outcomes reported
    -O04, all covered by the three vectors
    -O1, -O2, -O3, -Os2, all covered by the three vectors

    Both mechanisms are also sensitive to source representation. Semantically equivalent rewrites - a bitwise operator, a hand-folded comparison, a conditional expression - change or eliminate the multi-condition decision that the respective coverage mechanism recognizes. In the tested cases, neither tool reports the original two-condition MC/DC obligation; the report reflects the rewritten source or control-flow graph rather than marking that original obligation as uncovered.

    Every case can be reproduced on Compiler Explorer. Each link preloads one source form with the same three-vector driver and three compiler panes: GCC 15.2 at -O3; GCC 15.2 at -O3 --coverage -fcondition-coverage; Clang 22.1 at -O3 -fprofile-instr-generate -fcoverage-mapping -fcoverage-mcdc. The runtime coverage outputs quoted above come from local runs with GCC 15.2.0 and Clang 22.1.4.

    Source formCompiler Explorer
    conditions inline, if ((a == 0) && (b == 0))godbolt.org/z/ajo4jds3P
    conditions hoisted into named Booleansgodbolt.org/z/cPqdYGsv6
    bitwise &godbolt.org/z/WefWzGGhn
    hand-folded (a | b) == 0godbolt.org/z/EK7fWT8zW
    conditional operatorgodbolt.org/z/YP6svEdjv
    && hoisted into an initializergodbolt.org/z/PhWT453x6

    Three common responses

    Every tool that derives source-level coverage from a control-flow trace plus debug information sits inside the boundary above. This is not a claim about implementation quality: it is an upper bound on the information available in the binary, and it therefore applies to every tool of the class - ours included.

    Three responses are common. Each gives something up. They are response classes, not an exhaustive taxonomy - selective data trace, source transformation, binary rewriting and compiler-metadata approaches exist, and whether any of them satisfies a given assurance case is a separate argument.

    ResponseWhat it buysWhat it costs
    Lower the optimization levelthe structure survives, the MC/DC inputs become recoverablethe measured build is no longer the shipped build - the representativeness argument returns, and it is the expensive one
    Constrain the code generation (compiler flags, coding style)removes the conditional non-branch classcoverage requirements now shape the production build; verification per build required
    Add targeted instrumentationfull MC/DCcounters in the shipped code: timing, footprint, optimizer interaction - the probe effect the trace approach existed to avoid

    Two of the three responses are not tool-independent: one lives in the compiler, the other in the coverage tool, so the constraint and the evidence about it come from the same place - a dependence a reviewer will want addressed. Each response also pays with part of what made trace-based observation attractive: response 2 with verification per build, response 3 with the probe effect, and response 1 with the shipped binary.

    Do not infer through an absent distinction. If the generated object control flow does not distinguish two source outcomes and no verified supplemental observation exists, the source-level obligation remains unresolved.

    How CEDARtools.Anchor closes it

    The gap is now precisely located, which makes the fix statable in one sentence:

    If the missing information is not in the control flow, put it there - as control flow, and without writing anything.

    At the points a coverage metric needs to tell apart, the source is supplemented with control-flow blocks that are

    • functionally neutral - they do not change what the program computes;
    • preserved through optimization - established at build time and verified rather than assumed, per compiler, version and optimization setting;
    • free of memory operations on analysis state - the application maintains no coverage counters and no software trace buffer.

    The third property separates counterless control-flow supplementation from state-writing instrumentation. Supplementing the source for observability is instrumentation in the broad sense - the program representation is modified for measurement. What this variant does not do is write coverage state from the application: conventional counter- or tag-based instrumentation performs loads and stores to analysis state, and those accesses can touch caches, timing and the memory budget depending on configuration. An anchor produces no such access; it still executes as part of the program, and its instruction-level effect is measured separately.

    And the first two are what make it work against the observability boundary of this article: the anchor is a control-flow event, which is exactly what the trace protocol transmits within a valid observation scope.

    The mechanism is applied before compilation: the supplemented source, together with its header or macro definitions, is compiled and linked into the machine code used for analysis. That is the causal step a downstream trace-path device cannot supply by itself - the optimization-resistant source distinctions exist in the binary because they were placed and protected before and during compilation, not retrofitted afterwards. The build produces the supplemented machine code together with a static observation mapping: a static correspondence between source-level points and their binary representations, used to correlate runtime trace events with source constructs. That mapping metadata can draw on debugging data, symbol-table associations, or both - and where debugging data is used, it is subject to the same chain of trust as every other mapping input, including what the debug information actually says under optimization.

    The evaluation happens outside the target: the measurement device in the trace path reconstructs the control flow, determines which supplemented points each execution instance passed, and evaluates the metric from those grouped observations. For a multi-condition decision, that grouping is exactly the per-instance condition history the reconvergence section above requires, and the independence-pair check runs across the test set, off the target. See the product it ships in and how the output reaches an existing verification tool.

    What that changes, against the responses above:

    • the optimization level stays where the product needs it;
    • the measured artifact can remain the shipped artifact: the published application states that the impact can be small enough to justify leaving the modified code in the release - artifact identity then means releasing the supplemented image that was measured, under the trust chain stated at the top of this page, or making a separate representativeness argument;
    • the metric that was unanswerable at -O3 becomes answerable, including the condition-level detail that MC/DC actually requires.

    Probe effect, stated precisely. This is probe-effect-minimal, not probe-effect-free, and we will not write that it is: the supplemented machine-code structure exists, it occupies code space, and it executes - code size, control-flow and layout changes, cache effects and localized optimization barriers are possible effects, and the timing effect is measured for the named target, compiler, placement and configuration. What the supplementation does not do is read or write application-resident analysis state - which is where much of the cost of conventional instrumentation typically lives.

    Status. The mechanism is the subject of a published international patent application. In the PCT international phase, international search and a written opinion - and optionally an international preliminary examination - take place; grant is a matter for the national and regional offices after entry into the national and regional phases. Publication details, priority date and our granted patent families are on the patents page.

    We are describing the mechanism publicly on purpose. It is disclosed in a published application, and a method that can be inspected can be argued about with an assessor.

    The full description is on the CEDARtools.Anchor page.

    If you are evaluating tools

    The questions that separate the claims quickly - optimization level of the measurement, treatment of branchless conditionals, reporting of unattributable code, and the chain of trust behind the source-level verdict - have their own page: the five questions worth asking every vendor. That page also names the three artifacts worth requesting with the answers.

    What this page establishes

    • trace completeness, source attribution and coverage achieved are three different questions;
    • the mapping classes - one-to-one, one-to-many, many-to-one, no source - and the reporting policy under which results are classified rather than silently dropped;
    • concrete object-code examples of where the source distinction is lost;
    • why reconverging decisions need per-instance condition histories rather than aggregate branch counts;
    • how Anchor* - supplementation before compilation, a static observation mapping, and off-target evaluation - supplies the cases the passive route cannot attribute.

    Sources

    The statements above about what a protocol transmits, what a debug format promises and what a standard requires are based on the primary documents:

    The compiler-coverage comparison in the section above is our own measurement (GCC 15.2.0 and Clang 22.1.4, local runs; object code on Compiler Explorer with GCC 15.2 and Clang 22.1.0); the six links reproduce every case.

    Infineon’s MCDS is covered by a non-disclosure agreement; this article therefore names only what Infineon has published itself.

    Apply this to your target

    Send the exact processor, board, trace path, compiler and metric you need. We will review feasibility at configuration level rather than answering from the core-family name alone.

    Start a target feasibility review


    Related: MC/DC worked through · what instrumentation costs

    Assembly examples in the figures are shortened schematics. Every example on this page can be reproduced on Compiler Explorer with preloaded source, compilers and flags: example 1 · example 2 · compiler-integrated coverage, six source forms.