Skip to content

Verify hard-coded TX_THREAD offsets in port assembly at compile time #577

Description

@fdesbiens

Summary

Port assembly reaches TX_THREAD members by hard-coded byte offsets, and nothing in the toolchain connects those literals to the C structure definition. A mismatch is therefore not a build error — it is silent memory corruption at run time.

This is not hypothetical. It has already happened, and the failure mode is deceptive.

The motivating defect

On cortex_r4/gnu, cortex_r5/gnu and cortex_r5/ac6, the assembly in tx_thread_schedule, tx_thread_system_return and tx_thread_context_restore reads and writes a per-thread VFP enable flag as [thread, #144], but every TX_THREAD_EXTENSION in those ports' tx_port.h is empty, so no such member exists. Offset 144 in the resulting structure is tx_thread_filex_ptr.

sizeof(TX_THREAD) is 180 on those ports, so 144 is a live member rather than padding past the end.

The corruption runs both ways:

  • tx_thread_vfp_enable() stores 1 into tx_thread_filex_ptr, after which FileX dereferences 0x1;
  • conversely, once FileX sets that pointer the context switch reads it as "floating point enabled" and begins pushing ~132 bytes of floating-point context onto a thread stack never sized for it, then restores unrelated data into the D registers.

What makes it dangerous is that it looks like it works. Reproduced on Armv8-R by reverting a port that inherited the same assembly to this state: every floating-point value was preserved exactly and no corruption was reported across interrupts, because a non-zero filex_ptr reads as "enabled" and the context switch dutifully saves and restores. The only visible damage was tx_thread_filex_ptr reading 0x00000001. The feature you enabled behaves correctly; what breaks is an unrelated pointer, and nothing notices until FileX is introduced.

A fix for those three ports is being submitted separately. This issue is about preventing the whole class of defect.

Proposal

Add a small per-port source file that asserts, at compile time, the offsets its assembly depends on. A reference implementation is available at ports/cortex_r52/gnu/src/tx_port_offset_check.c (also added for cortex_r4/gnu, cortex_r5/gnu and cortex_r5/ac6 alongside the fix above).

Properties of the reference implementation:

  • emits zero bytes of code or data;
  • compiles clean under -std=c99 -pedantic -Wall -Wextra;
  • uses a negative array dimension rather than _Static_assert, since the project targets C99;
  • verified to actually reject a wrong offset and a removed member, not merely to compile.

Scope, and why this is not a quick sweep

Roughly 40 port and toolchain combinations exist. The work is not copy-paste, and doing it as copy-paste would be actively harmful — asserting a wrong offset breaks builds that were previously fine. Each port needs:

  1. Attribution of every hard-coded literal in its assembly. Not all [rN, #imm] accesses are TX_THREAD-relative; some are relative to timer lists, ready queues or other structures. Only genuinely thread-relative offsets may be asserted.
  2. Confirmation that the offset is stable across build options. For the R-profile case above, offset 144 was checked against TX_ENABLE_STACK_CHECKING, TX_ENABLE_EVENT_TRACE, TX_ENABLE_EVENT_LOGGING, TX_THREAD_ENABLE_PERFORMANCE_INFO and TX_NOT_INTERRUPTABLE and is unchanged by all of them — because the only conditional member nearby guards tx_thread_filex_ptr, which follows the extension macros, and TX_THREAD_USER_EXTENSION appears much later in the structure. A port whose asserted offset is option-dependent needs the assertion guarded accordingly, or the assembly itself is already wrong for those configurations.
  3. A negative test per port, confirming the assertion rejects a deliberately wrong value.

Known limitation

Most ports have no CMake build of their own, so an assertion file there takes effect only in builds that compile everything under src/. It still costs nothing and emits nothing, so it is worth adding, but the protection is not universal until those ports gain a build.

Suggested acceptance criteria

  • Every port whose assembly hard-codes TX_THREAD offsets has an assertion file covering those offsets
  • Each asserted offset is attributed to the specific assembly instruction that depends on it
  • Each assertion is demonstrated to fail on a deliberately wrong value
  • Offsets shown to be stable across the relevant build options, or guarded where they are not
  • Ports with a CMake build compile the file as part of the library

Why it is worth doing

Hard-coded offsets in assembly are exactly the kind of unchecked coupling that certification evidence is expected to rule out. This converts a class of silent, hard-to-diagnose corruption into an ordinary build failure, for no code size and no runtime cost.

Good candidate for incremental contribution: the work parallelises cleanly, one port per pull request.

Metadata

Metadata

Assignees

Labels

backlogThe issue or feature request has been added to the project backlog for prioritization

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions