Ftrace Dump On Kernel Oops-Free Linux Device Drivers Training Online

PREV_LEC | NEXT_LEC

Ftrace Dump On Kernel Oops
Capture the exact sequence of kernel events that led to a crash — free linux kernel development course
Chapter 14 · Kernel Debugging
Lecture 6
Hands-on demo included

When a Linux kernel crashes, the panic or oops message you see on the console tells you
where it crashed, but rarely tells you why. The instruction that faulted is only the
last frame of a much longer story — the sequence of function calls, interrupts, and scheduling
decisions that put the CPU in that broken state in the first place. This lecture is part of our
free linux kernel development course and focuses on one of the most underused
debugging weapons for exactly this problem: making Ftrace automatically dump its trace buffer the
moment an oops or panic happens.

ftrace_dump_on_oops
tracefs
ring buffer
kernel oops
free linux device drivers course
crash triage

What You Will Learn

  • Why post-mortem tools like kdump only show you a snapshot, not a timeline
  • How Ftrace’s automatic oops-dump feature captures the events leading up to a crash
  • How to enable it via tracefs and via a kernel boot parameter
  • How to size the per-CPU trace ring buffer sensibly
  • A complete, original demo module that triggers a fault and shows the resulting trace dump
  • Common mistakes, performance trade-offs, and security considerations

Prerequisites

  • Comfortable building and loading out-of-tree kernel modules
  • Basic familiarity with Ftrace (function tracer, tracers, tracefs mount point)
  • A test VM or board where you’re allowed to crash the kernel on purpose — never do this on
    production hardware

Why Post-Mortem Snapshots Aren’t Enough

Tools such as kdump/kexec combined with the crash utility are
excellent for inspecting the state of memory at the instant of a crash — register values, the
stack, active processes. What they cannot show you is the path the kernel took to get there.
If a driver corrupts a data structure ten function calls before the actual fault, a memory snapshot
alone won’t reveal that chain of events. You need a timeline, and that’s exactly what Ftrace’s
ring buffer already is: a rolling, timestamped log of function entries/exits, scheduling events, and
any custom trace points you’ve enabled.

Timeline vs Snapshot Debugging
Snapshot debugging (kdump/crash)
Crash instant –> Register + stack dump only
No visibility into the events before the faultTimeline debugging (Ftrace dump-on-oops)
Function A entry –> Function B entry –> Function C entry (faults here)
Every step before the fault is preserved in the ring buffer
Buffer is flushed to the console automatically when the oops fires

How ftrace_dump_on_oops Works

Ftrace maintains a per-CPU ring buffer of trace events. Normally you’d read that buffer manually
through trace or trace_pipe. The ftrace_dump_on_oops option changes
that: the moment the kernel detects an oops or panic, it automatically dumps the entire buffer to the
console in plain ASCII, before the rest of the panic handling proceeds. If your console is wired to a
serial line (which is standard practice on embedded boards), that dump survives even if the system
never comes back up — you simply capture the serial log.

There are two independent ways to turn this on:

1. At runtime via sysctl / tracefs

# echo 1 > /proc/sys/kernel/ftrace_dump_on_oops

2. At boot, via the kernel command line

ftrace_dump_on_oops

Adding this to your bootloader’s kernel command line (for example in the U-Boot bootargs or
GRUB config) guarantees the feature is active from the very first boot, which matters if the crash you’re
chasing happens early — before you’d have a chance to enable it manually.

Sizing the Trace Ring Buffer

By default the Ftrace ring buffer is comfortably larger than 1 MB per CPU, and dumping
that much text to a slow serial console can take a very long time — sometimes long enough that a
watchdog resets the board before the dump finishes. In practice you almost always want to shrink the
buffer before you arm dump-on-oops, trading history depth for a dump that actually completes.

# mount -t tracefs nodev /sys/kernel/tracing   # if not already mounted
# echo 4 > /sys/kernel/tracing/buffer_size_kb

Note two things modern kernels handle slightly differently from older references you may have seen:
tracefs is its own filesystem type nowadays and is commonly mounted directly at
/sys/kernel/tracing, independent of debugfs. If your system still mounts debugfs at
/sys/kernel/debug, the same file is also reachable at
/sys/kernel/debug/tracing/buffer_size_kb — both paths point at the same tracefs instance.
The value you write is always per CPU, not a total, so a 4 CPU board with
buffer_size_kb set to 4 is actually holding roughly 16 KB of trace history in total.

File (relative to tracing root) Purpose
buffer_size_kb Per-CPU ring buffer size in KB
tracing_on Master on/off switch for tracing
current_tracer Which tracer is active (function, function_graph, nop, …)
trace Static snapshot read of the current buffer contents
trace_pipe Live streaming read of trace events

Hands-On: Triggering and Capturing a Dump

Let’s build a small, original demo module — ep_oops_demo — that deliberately
dereferences a NULL pointer inside a function a few calls deep, so you can see the trace dump actually
capture that call chain.

// ep_oops_demo.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

static noinline void ep_level_three(void)
{
    int *bad = NULL;

    pr_info("ep_oops_demo: about to fault in %s\n", __func__);
    *bad = 42;   /* deliberate NULL pointer write */
}

static noinline void ep_level_two(void)
{
    pr_info("ep_oops_demo: entering %s\n", __func__);
    ep_level_three();
}

static noinline void ep_level_one(void)
{
    pr_info("ep_oops_demo: entering %s\n", __func__);
    ep_level_two();
}

static int __init ep_oops_demo_init(void)
{
    pr_info("ep_oops_demo: loaded, triggering fault chain\n");
    ep_level_one();
    return 0; /* unreachable in practice */
}

static void __exit ep_oops_demo_exit(void)
{
    pr_info("ep_oops_demo: unloaded\n");
}

module_init(ep_oops_demo_init);
module_exit(ep_oops_demo_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("EmbeddedPathashala Ftrace dump-on-oops demo");
# Makefile
obj-m += ep_oops_demo.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Build it, then arm Ftrace before loading:

$ make
# mount -t tracefs nodev /sys/kernel/tracing
# echo function > /sys/kernel/tracing/current_tracer
# echo 4 > /sys/kernel/tracing/buffer_size_kb
# echo 1 > /sys/kernel/tracing/tracing_on
# echo 1 > /proc/sys/kernel/ftrace_dump_on_oops

# insmod ep_oops_demo.ko

The expected dmesg output shows the module’s own log lines, followed by the oops itself, followed
by the automatically flushed trace buffer showing the exact call sequence
ep_oops_demo_init → ep_level_one → ep_level_two → ep_level_three right before the fault:

$ dmesg | tail -n 30
ep_oops_demo: loaded, triggering fault chain
ep_oops_demo: entering ep_level_one
ep_oops_demo: entering ep_level_two
ep_oops_demo: about to fault in ep_level_three
BUG: kernel NULL pointer dereference, address: 0000000000000000
...
RIP: 0010:ep_level_three+0x1c/0x30 [ep_oops_demo]
...
---[ end trace ]---
Dumping ftrace buffer:
---------------------------------
 -0     [000] ....   123.456: ep_oops_demo_init <-do_one_initcall
 -0     [000] ....   123.457: ep_level_one <-ep_oops_demo_init
 -0     [000] ....   123.457: ep_level_two <-ep_level_one
 -0     [000] ....   123.458: ep_level_three <-ep_level_two
---------------------------------

That flushed buffer is the whole point: even though the crash happened inside
ep_level_three, you have the complete call chain that led there, with timestamps, without
needing a debugger attached at the moment of the crash.

kdump vs Ftrace Dump-on-Oops

Aspect kdump / crash utility ftrace_dump_on_oops
Shows state at crash instant Yes, in full detail No, only whatever trace events were captured
Shows events before the crash No Yes — the whole point of the feature
Setup complexity Higher — needs a reserved memory region, a second kernel Low — a couple of sysctl writes
Best used Alongside a debugger for deep memory inspection To recover the call sequence leading to intermittent bugs

In real projects these two techniques complement each other rather than compete: dump-on-oops tells
you the story, kdump lets you examine the final scene in detail.

Common Mistakes and Troubleshooting

  • Buffer too small — if you shrink buffer_size_kb too aggressively,
    the relevant history may have already scrolled out of the ring buffer before the crash happens.
    Start conservative, then shrink once you know how far back you need to see.
  • No tracer selected — dump-on-oops only dumps whatever the currently active
    tracer has been recording. If current_tracer is still nop, the buffer will
    be essentially empty. Enable function or a relevant event tracer first.
  • tracing_on left at 0 — a common oversight; the master switch has to be on for
    anything to accumulate in the buffer at all.
  • Console too slow — on a heavily loaded serial console, the dump itself can take
    long enough to trip a hardware watchdog. Either shrink the buffer further or raise the watchdog
    timeout during debugging sessions.

Best Practices

  • Route your console to a serial line (or a persistent log target) whenever you’re chasing an
    intermittent crash — a dump that scrolls off an unlogged terminal is wasted effort.
  • Pair ftrace_dump_on_oops with a narrow, purpose-built tracer (function filtering,
    specific event tracers) rather than tracing the entire kernel — it keeps the dump readable and the
    overhead low.
  • Set the boot-time parameter on boards where crashes happen during early init, since you won’t get
    a chance to enable it manually in time.

Performance Considerations

Ftrace tracing is not free — the function tracer adds measurable overhead per traced call. For
long debugging sessions on production-adjacent hardware, prefer targeted tracers (specific functions
or tracepoints) over the blanket function tracer, and keep the ring buffer only as large as you need.

Security Considerations

Trace dumps can include function names, addresses, and sometimes argument values that reveal kernel
layout — useful information for an attacker trying to defeat KASLR. Treat trace dumps the same way you
treat other kernel debug output: restrict console/log access on production or externally reachable
systems, and disable dump-on-oops outside of controlled debugging environments.

Summary and Key Takeaways

  • kdump/crash gives you a snapshot; Ftrace’s dump-on-oops gives you the timeline leading to it
  • ftrace_dump_on_oops can be toggled at runtime or set as a boot parameter
  • The ring buffer size is per CPU — size it deliberately before you need it
  • Combine a targeted tracer with dump-on-oops for a readable, low-overhead crash trail

Conclusion

Automatic ftrace dumps turn an oops from a dead end into a lead. Instead of staring at a single
faulting instruction, you get the sequence of calls that walked the kernel into that state — often
enough, on its own, to spot the real bug. It’s a small amount of setup for a disproportionately useful
payoff, which is exactly why it belongs in every kernel developer’s debugging toolkit, and in this
free linux kernel development course.

FAQ

Does ftrace_dump_on_oops work on panics as well as oopses?

Yes. It fires on both kernel oopses (recoverable faults) and full panics, dumping the buffer before
the rest of panic handling runs.

Do I need debugfs mounted to use tracefs?

No. Modern kernels expose tracefs as its own filesystem, typically at
/sys/kernel/tracing, independent of whether debugfs is mounted.

Is buffer_size_kb a total or per-CPU value?

It’s per CPU. A system with several cores holds that many kilobytes of trace history on each core,
not shared in total.

Can I use this technique on a production system?

You can, but weigh the console-time and security trade-offs first — a large dump on a slow console
can delay recovery, and trace output can leak kernel layout information.

What’s the difference between trace and trace_pipe?

trace is a static read of the current buffer contents; trace_pipe streams
events live and consumes them as they’re read.

Why did my dump show almost nothing useful?

Most commonly because no tracer was active, or the buffer was sized too small and the relevant
history had already scrolled out before the crash occurred.

Want the next debugging lecture?

Continue this free linux device drivers course with the next lecture on using objdump and
faddr2line to pinpoint the exact faulting source line.

 

PREV_LEC | NEXT_LEC

Leave a Reply

Your email address will not be published. Required fields are marked *