Skip to content

idt: report the faulting task, ESP and caller trail on a page fault - #128

Merged
douglasmun merged 1 commit into
mainfrom
diag/page-fault-context
Aug 23, 2026
Merged

idt: report the faulting task, ESP and caller trail on a page fault#128
douglasmun merged 1 commit into
mainfrom
diag/page-fault-context

Conversation

@douglasmun

Copy link
Copy Markdown
Owner

Why

An EIP=0 kernel page fault currently cannot be diagnosed from the log. The dump gives the faulting address, error code, CR3 and the page walk — enough to say address 0 was entered, but not which task was running, and not how control got there.

#126 is exactly this. Four nightly-CI panics, all identical:

Faulting address: 0x00000000
Error code: 0x00000011   (present | instruction fetch)
EIP: 0x00000000
CS: 0x0010
Mode: KERNEL (CPL=0)

The fault point differed between runs — three faulted just after the EDR daemon's config line, one after a completed scan cycle — so the log cannot distinguish a bad indirect call from a context switch into a task with a corrupted saved EIP. Those are very different bugs, and the current output supports neither diagnosis. I could not reproduce the panic locally either (toolchain, QEMU version and GRUB target are all ruled out in #126), which leaves the next nightly run as the main source of evidence — so that run should produce something usable.

What

On the kernel path, three additions:

line value
Task: PID + name of current_task, or an explicit <none> — itself informative, meaning pre-scheduler or no current task
ESP(kernel): kernel stack pointer at fault time
Stack near ESP: eight words from there, to match against nm

useresp is deliberately not used for the kernel case: the CPU pushes it only on a privilege change, so on a CPL=0 fault that slot holds whatever followed the frame. The kernel ESP is derived from the hardware-pushed portion of the frame instead. The user path keeps useresp/ss, where they are valid.

The stack read is bounded to kernel-mapped addresses and breaks out otherwise — this runs inside the fault handler, which must not itself fault. That is the same constraint that already forced pae_dump_tables() over recursive mapping.

Verification

Not just "it compiles" — I injected a deliberate null call (reverted, not part of this PR) to fire the path. It reproduces the CI signature exactly, and the new lines do the job:

Task: PID=26865 'edr_daemon' (kernel)
ESP(kernel): 0x004c8f00
Stack near ESP: 00000000 00000010 00010246 001304ae 001586ec 000001f4 00000005 0013042f

Resolving that trail:

001304ae -> edr_daemon_main +0x9e     <- the injected call site
0013042f -> edr_daemon_main +0x1f

So the caller the old dump lost is recoverable.

  • Clean boot with the change in place, 0 panics, shell reached.
  • make -j8 kernel.elf warning-clean under -Werror.

Diagnostic only — no behaviour change on any non-faulting path. This does not fix #126; it makes the next occurrence interpretable.

An EIP=0 kernel page fault was undiagnosable from the log. The dump printed
the faulting address, error code, CR3 and the page walk -- enough to say that
address 0 was entered, but not which task was running nor how control got
there.

Issue #126 is exactly this: four nightly-CI panics, all reporting

    Faulting address: 0x00000000
    Error code: 0x00000011   (present | instruction fetch)
    EIP: 0x00000000
    CS: 0x0010

with nothing to tell them apart. The fault point differed between runs -- three
faulted just after the EDR daemon's config line, one after a completed scan --
so the log could not distinguish a bad indirect call from a context switch into
a corrupted saved EIP. Those are very different bugs and the output supported
neither.

Adds three things to the kernel-fault path:

  Task:        PID and name of current_task (or an explicit <none>, which is
               itself informative: it means pre-scheduler or no current task).
  ESP(kernel): the kernel stack pointer at fault time.
  Stack near:  eight words from there, to be matched against nm.

useresp is deliberately NOT used for the kernel case. The CPU pushes it only
on a privilege change, so on a CPL=0 fault that slot holds whatever happened to
follow the frame. The kernel ESP is the address of the hardware-pushed portion
of the frame instead. The user case keeps useresp/ss, where they are valid.

The stack read is bounded to kernel-mapped addresses and breaks out otherwise:
this runs inside the fault handler, which must not itself fault -- the same
constraint that already forced pae_dump_tables() over recursive mapping.

Verified against a deliberately injected null call (reverted, not committed),
which reproduces the CI signature exactly:

    Task: PID=26865 'edr_daemon' (kernel)
    ESP(kernel): 0x004c8f00
    Stack near ESP: 00000000 00000010 00010246 001304ae 001586ec ...

and 001304ae resolves via i686-elf-nm to edr_daemon_main+0x9e -- the injected
call site. So the trail recovers the caller that the old dump lost.

Clean boot with the change in place, 0 panics. Warning-clean under -Werror.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CEkhAhgTxbE5TgifyYf8v4
@douglasmun
douglasmun merged commit 3ed54da into main Aug 23, 2026
2 checks passed
@douglasmun
douglasmun deleted the diag/page-fault-context branch August 23, 2026 14:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Kernel panics on boot under the CI cross-toolchain (jump to address 0)

1 participant