Skip to main content

3.0 Preparation and Motivation for This Chapter

In this chapter, we dive inside the kernel's "black box."

When writing code in user space, printf is your right-hand tool; but inside the kernel, things get far more subtle. You are no longer dealing with a protected process, but rather an execution environment that holds the highest privileges in the system—yet is also the most fragile. A single improper memory access, a deadlock, or even a poorly timed print statement can bring the entire system down in an instant.

This is exactly what gives kernel newcomers (and even veterans) the biggest headaches: when the system crashes, how do you debug the system itself?

This calls for a debugging approach that works without any third-party tool dependencies. It must be simple enough to require no complex UI, yet low-level enough to cut through layers of abstraction and reach the heart of the problem.

In the kernel world, that approach is printk—along with the entire logging and tracing mechanism built around it.

But this is no simple topic. Many people assume that printk is just the kernel version of printf. That understanding is correct, but only half the story. As we proceed, we will see how it builds a ring buffer under extremely constrained resources, how it filters out noise through log levels, and even how it leaves a final message via Early printk before the system completely hangs.

Get your environment ready.

We don't need to install anything new here. As mentioned in Chapter 1, your working environment—whether a virtual machine or a physical board—should already be in place. The code is there, the toolchain is ready, and your debugging journey begins with this section.


3.1 Technical Requirements

If you flip back to Chapter 1, you might think I'm repeating myself—but that's actually a good thing.

It means our battlefield hasn't changed, and our arsenal consists of the same familiar tools. There's no need to wrestle with setting up a cross-compiler again, and no need to deal with those hair-pulling dependency libraries. We can focus all our energy on the core issue: how to see exactly what is happening inside the kernel.

To ensure we can run all the experiments in this chapter together, here is a quick checklist:

  1. Development environment: You still need the Linux development environment set up in Chapter 1 (the host is typically a Linux distribution, and the target can be a QEMU virtual machine or an ARM board).

  2. Kernel source code: Although we will be reading code, you don't need to write a kernel from scratch. All the example code is packaged and available in this book's GitHub repository:

    https://github.com/PacktPublishing/Linux-Kernel-Debugging

    If your local repository isn't up to date, now is a good time to git pull.

  3. Necessary permissions: In later sections, we will frequently read the kernel log buffer (dmesg) and debug filesystems (debugfs). This means you need root privileges on the host machine, or you need to have the appropriate sudo rules configured. Don't get stuck here—there's nothing more frustrating than typing a bunch of commands only to discover a Permission denied.

Once the environment is confirmed, we officially begin.

The first question we face, and the core problem we will solve in this chapter, is: when printk is called, where exactly do those characters go?

This is a seemingly simple question, but one that touches on the underlying design philosophy of the kernel. We will break it down in the next section.