Zephyr Fatal Handlers: Decide When Aborting One Thread Cannot Restore System Health

Returning from a Zephyr fatal-policy handler does not resume the faulting instruction. In the normal multithreaded path, it leads to aborting the current thread. That can contain a fault only if the rest of the system still has a valid state. A reason code alone cannot prove that mutexes, shared data, peripheral operations or kernel structures remain usable.

This guide pins upstream Zephyr 3.7.0 with CONFIG_MULTITHREADING=y and CONFIG_TEST=n, focusing on a single-core product policy. Architecture exception handling, assertion settings, userspace protection and stack-protection options must be recorded separately. No target fault injection was performed. The procedure below is a policy and qualification framework, not a ready-made recovery handler.

Start from the documented return boundary

The versioned fatal-error guide distinguishes kernel oops, kernel panic, CPU exceptions, spurious interrupts and stack-related failures. Its default policy halts. A custom policy may return in appropriate cases, but kernel panic represents an unrecoverable kernel condition and must not be treated as a recoverable thread failure.

The tagged fatal API also warns that a non-userspace fault can leave the kernel inconsistent even when the reason is not panic. A subsequent kernel API call may fail again. Therefore, “not panic” is not a sufficient condition for returning.

Fatal-policy decision checks panic or interrupt and unknown corruption first, then requires demonstrated thread containment and a resource recovery contract before allowing return to the kernel abort path.
Returning is a containment decision, not a repair operation. Unknown corruption belongs on the non-returning policy path.

Classify context as carefully as reason

Record the fault origin, whether the current thread is meaningful for that origin, the execution privilege level and available exception-frame evidence. The frame pointer can be null. Do not dereference it unconditionally or assume one architecture’s register layout applies to another.

The 3.7.0 fatal implementation contains architecture- and configuration-dependent nested-exception handling, with a special stack-sentinel case detected around interrupt exit. It also has test-mode exceptions. Do not generalize a test application’s ability to continue after a deliberately induced fault into a production recovery guarantee.

For a production policy, treat a genuine interrupt-context fault as a system-level failure unless a specifically supported, independently qualified architecture mechanism establishes otherwise. Do not return merely because a “current thread” pointer was printed; an interrupt may have interrupted an unrelated thread. Confirming origin is part of deciding whether thread abort has any useful meaning.

Use a containment matrix

Observation Default review conclusion Evidence needed to narrow it
Kernel panic Non-returning system policy Do not downgrade it to ordinary thread failure
Supervisor fault with unknown writes Possible system corruption Demonstrated integrity boundary, not only a reason code
Stack sentinel or supervisor stack failure Corruption may predate detection Protection configuration and affected-memory analysis
Isolated userspace application fault Candidate for contained abort Permissions, shared-resource state and recovery ownership
Fault origin uncertain No proof of thread-local containment Reliable architecture-specific context evidence

Userspace is relevant because memory permissions can constrain what a thread can damage. It does not automatically repair application-level shared state that the thread was legitimately allowed to modify. For example, an authorized shared record may contain half of a multi-step update when a fault occurs. Other threads need a protocol that recognizes or rejects that incomplete state.

Inventory the resources thread abort leaves behind

A hypothetical parser thread may hold a mutex, own a slab block, have submitted a device transaction and be responsible for completing a request. Aborting it removes execution but does not satisfy those four obligations. A system that keeps scheduling while another task waits forever on the parser’s mutex is not successfully recovered.

Before permitting a contained-abort policy, identify every resource the candidate thread can own at a fault boundary. For each, name an independent recovery owner and a safe way to determine the resource’s state. If that determination depends on running more code in the corrupted thread or trusting damaged metadata, the containment argument is incomplete.

Keep normal cleanup outside the fatal handler whenever it depends on functioning kernel services. The handler may run with interrupt restrictions or compromised state. Do not attempt a broad destructor sequence that allocates, waits on locks or drives complex peripherals from that context. A later healthy supervisor can perform only the recovery operations justified by the containment contract.

Define a bounded fatal-policy path

  1. Capture a small, validated set of reason and context fields using the platform’s qualified fatal path. Handle missing exception-frame information.
  2. Classify against an explicit allowlist of contained cases. Unknown conditions use the non-returning system policy.
  3. For panic or suspected kernel corruption, follow the product’s qualified halt/reset behavior without returning to ordinary execution.
  4. For an allowed contained case, return only with an established understanding that the kernel abort path follows, not the interrupted application instruction.
  5. After containment, require the independent supervisor to verify resource and service health before accepting new work for the failed component.

The default implementation uses panic-mode logging before halting. A custom policy should not depend on an ordinary logging task or an unbounded output operation completing after arbitrary corruption. Evidence capture itself needs a failure budget and a tested fallback.

Qualify the policy on an isolated target

Use a controlled bench configuration with hazardous outputs disabled. Exercise each allowed fault category with known resource ownership, then verify more than continued execution: check mutex progress, outstanding-buffer accounting, request completion and peripheral state. Repeat with assertions configured as in the intended product, because removing an assertion does not make an invalid recovery safe.

Separately test missing context evidence, faults while holding resources, delayed stack detection and a failure inside the reporting path. These tests are proposed, not executed here. Acceptance requires both an explained fault boundary and verified post-fault service invariants. If those cannot be demonstrated, aborting one thread is insufficient, and the system-level fatal policy remains the defensible choice.

Similar Posts