Case Study 40-2: From Assembly to Linux Kernel Contribution — One Path
Introduction
This case study follows a realistic path from "I just finished this book" to "my patch was accepted into the Linux kernel." It is not a guarantee — kernel contribution requires persistence. But it is a concrete, step-by-step account of how assembly knowledge translates into one of the most impactful things a systems programmer can do: improving the software that runs the world's servers, phones, and infrastructure.
The path described here is modeled on the actual experience of first-time kernel contributors, synthesized from public mailing list archives, documentation, and contributor retrospectives.
The Starting Point
You have just finished this book. You understand:
- x86-64 assembly at the instruction level
- Stack layout, calling conventions, and ABI
- Interrupt handling and hardware interface
- Memory management: page tables, TLB, allocators
- Security mitigations and their assembly implementations
- How a complete OS boots (MinOS)
What you do not have yet:
- Familiarity with the Linux kernel codebase (millions of lines)
- Knowledge of kernel development workflow (patches, mailing lists)
- A specific contribution target
The path from here to a merged patch takes most people 3-6 months of part-time work. The bottleneck is almost never technical skill — it is process familiarity and finding the right entry point.
Phase 1: Environment Setup (Week 1)
Build the Kernel Locally
The first step is verifiable: build and run the mainline kernel.
# Get the source (large download: ~4GB with history)
git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
cd linux
# Use virtme for fast iteration — no rebooting required
pip3 install virtme
virtme-configkernel --defconfig
make -j$(nproc)
# Run your kernel in a VM
virtme-run --kdir . --mods=auto
virtme runs the kernel you built inside a lightweight VM, using your host filesystem. When the boot message appears and you get a shell, you have just run a kernel you compiled from source. This is not a toy — it is the same kernel that runs Linux servers worldwide.
Explore the Architecture Directories
Your assembly knowledge makes the architecture-specific code legible immediately.
ls arch/x86/kernel/
# You will recognize: entry_64.S, process_64.c, signal.c, traps.c
# These are the files you wrote simplified versions of in MinOS
ls arch/x86/kernel/entry_64.S | head -50
# The ENTRY/END macros, the interrupt entry stubs — familiar from Chapter 38
Compare arch/x86/kernel/entry_64.S to your MinOS interrupt handler. The Linux version handles far more cases (system calls, exceptions, NMIs, MCEs, virtualization) but the core pattern — save registers, call C handler, restore registers, iret/sysret — is exactly what you implemented.
Look at arch/x86/include/asm/calling.h:
/*
* 64-bit system call stack frame layout defines and helpers,
* for assembly code:
*/
#define R15 0
#define R14 8
#define R13 16
#define R12 24
#define RBP 32
#define RBX 40
/* ...*/
#define RIP 128
#define CS 136
#define EFLAGS 144
#define RSP 152
#define SS 160
You understand what every one of these offsets means. Most kernel developers do not have this direct intuition — they work at a higher abstraction level and consult this header when needed.
Phase 2: Reading the Code (Weeks 2-4)
Find Your Domain
The Linux kernel is too large to understand all at once. Your assembly background makes certain subsystems more accessible than others:
Immediately accessible (assembly skills directly apply):
- arch/x86/kernel/ — entry stubs, signal handling, context switch
- arch/x86/mm/ — page table manipulation
- arch/x86/crypto/ — AES-NI, SHA-NI assembly implementations
- drivers/char/ — simple character device drivers
- kernel/ — core scheduler, timer code
Accessible with some study:
- fs/ — filesystem layer (C-heavy, but system calls you understand)
- net/ — networking (C-heavy but logical)
- mm/ — memory management (your page table knowledge helps here)
Harder starting points (specialized knowledge required):
- drivers/gpu/ — GPU drivers, heavily hardware-specific
- security/ — LSM framework, capabilities
- fs/btrfs/, fs/ext4/ — complex filesystems
Start in drivers/char/. The null driver (drivers/char/mem.c) is instructive: it implements /dev/null, /dev/zero, /dev/mem, and /dev/kmem. Every operation is simple, the data flow is clear, and the driver-kernel interface is visible.
Read a Bug Report
The kernel bug tracker is at bugzilla.kernel.org. Filter for issues marked CONFIRMED and LOW priority. Read the bug description, the backtrace, and any discussion.
A backtrace from a kernel crash looks like:
BUG: kernel NULL pointer dereference, address: 0000000000000020
RIP: 0010:some_driver_function+0x48/0x90
Call Trace:
other_function+0x22/0x60
driver_probe+0x15c/0x300
...
You can read this. RIP: 0010:some_driver_function+0x48/0x90 means the crash happened 0x48 bytes into some_driver_function, which is 0x90 bytes long. The +0x48 is a code offset — the same kind of offset you computed in Chapter 35 when calculating buffer overflow offsets.
Use addr2line or gdb vmlinux to find the exact source line:
gdb vmlinux
(gdb) list *(some_driver_function+0x48)
This is reverse engineering applied to kernel debugging. Your skills apply directly.
Phase 3: Finding a Contribution Target (Weeks 3-6)
The kernel-janitors and Coccinelle Approach
The kernel-janitors project maintains a list of straightforward cleanup tasks. These include:
- Replacing deprecated APIs (
kzalloc+ check →devm_kzalloc) - Fixing
checkpatch.plwarnings (code style, comment formatting) - Converting old-style
typedef-heavy code to modern style - Removing
#ifdefforests that are no longer necessary
These are not glamorous, but they are: 1. Explicitly wanted by the kernel community 2. Small enough to understand completely 3. A way to learn the patch submission process without a complex technical problem
Find targets using scripts/checkpatch.pl:
# Find style issues in a specific subsystem
perl scripts/checkpatch.pl --strict drivers/char/pcmem.c
Or use Coccinelle semantic patches to find patterns:
# Find places using deprecated interface
spatch --sp-file scripts/coccinelle/api/kzalloc-simple.cocci \
--dir drivers/char/ --include-headers
The Firmware Loader and Assembly Knowledge
A more technically interesting entry point for someone with assembly skills is the firmware signature verification code in drivers/base/firmware_loader/. This code:
- Loads firmware blobs from the filesystem
- Optionally verifies their cryptographic signature
- Delivers them to hardware drivers
The signature verification path calls into lib/crypto/, which in turn uses the architecture-specific AES and SHA implementations in arch/x86/crypto/. You wrote XOR-based encryption in this book; you understand what AES-NI assembly does. Reading arch/x86/crypto/aesni-intel_glue.c is immediately comprehensible.
A realistic first contribution here: find a function with a FIXME or TODO comment, understand the issue, fix it, and submit a patch. In this subsystem, there are periodic cleanup needs as the crypto API evolves.
Phase 4: Writing the Patch (Week 6-8)
The Mechanics
Once you have identified a specific change to make:
# Create a branch for your work
git checkout -b fix-driver-null-check
# Make your change
vim drivers/char/target_driver.c
# Run checkpatch before committing
perl scripts/checkpatch.pl --strict drivers/char/target_driver.c
# Build and test with virtme
make -j$(nproc)
virtme-run --kdir . --mods=auto
# Commit with proper format
git add drivers/char/target_driver.c
git commit
The commit message format matters. The kernel has strict conventions:
driver/char: fix null pointer dereference in target_device_probe
When target_device_probe() is called before the device is initialized,
dev->private may be NULL. Add a null check before dereferencing.
Fixes: a1b2c3d4e5f6 ("driver/char: add target device support")
Reported-by: Reporter Name <reporter@example.com>
Signed-off-by: Your Name <your@email.com>
The Signed-off-by line is mandatory — it certifies (per the Developer Certificate of Origin) that you have the right to submit this code. The Fixes: tag links to the commit that introduced the bug.
Generate and Send the Patch
# Generate a patch file
git format-patch -1 HEAD
# This produces: 0001-driver-char-fix-null-pointer-dereference-in-target_d.patch
# Find the right maintainer and mailing list
perl scripts/get_maintainer.pl 0001-*.patch
# Output: list of email addresses to CC
# Send via email (git send-email requires configuration)
git send-email --to=maintainer@kernel.org \
--cc=linux-kernel@vger.kernel.org \
0001-*.patch
The kernel does not use GitHub pull requests. It uses email. This surprises many contributors. The tooling around it (git send-email, b4 am for applying patches, lore.kernel.org for archives) is reliable and well-documented once you learn it.
Phase 5: The Review Process (Weeks 8-12)
What Happens Next
After sending the patch, one of three things happens:
-
No response (common for first patches): Wait one week, then send a gentle
PINGreply to your own patch. If still no response after another week, check that you sent to the right list and maintainer. -
Review feedback: A maintainer or experienced contributor reviews your patch. The feedback might be: code style issues, a better way to fix the problem, missing test cases, or a request for more context. This is normal and expected — even experienced contributors receive detailed review feedback.
-
Acceptance: The maintainer replies
Acked-by:orReviewed-by:and queues the patch. It will appear in their tree and eventually in Linus's tree via a pull request during the next merge window.
Responding to Review
If you receive feedback, respond to each point directly in the email thread:
On Mon, Jan 01, 2026, Maintainer Name wrote:
> The null check should use IS_ERR_OR_NULL() here, not a bare comparison.
> Also, please add a comment explaining why this case occurs.
Good point. IS_ERR_OR_NULL() is more appropriate here since the pointer
could also be an ERR_PTR value in the error path. Updated v2 attached.
Then send a revised patch:
git format-patch -1 HEAD --subject-prefix="PATCH v2"
git send-email --to=maintainer@kernel.org \
--in-reply-to=<message-id-of-review> \
0001-*.patch
The --in-reply-to flag ensures your v2 appears as a reply in the email thread, keeping the discussion context intact.
Phase 6: The Merged Patch
After the review cycle (typically 1-4 rounds), the maintainer merges your patch into their subsystem tree. You will see something like:
Applied to char-misc-next.
This means the patch is in the maintainer's -next tree. During the next Linux merge window (two weeks after each kernel release), the subsystem tree is pulled into linux-next, and eventually into Linus's mainline tree.
When the kernel version containing your patch is released, you are in the CREDITS file (if you add yourself) and in the git log of one of the most widely deployed software projects in history:
git log --oneline --all | grep "Your Name"
# a1b2c3d your fix commit message
Every Linux server, Android phone, and network router running a kernel version after your patch is running your code. The assembly skills that got you through this book are the same skills that let you understand what the kernel's assembly entry stubs do, what the page table manipulation code means, and why the crypto implementations use AES-NI.
What Assembly Skills Contributed
Looking back at the path, assembly knowledge contributed at every phase:
| Phase | How Assembly Knowledge Helped |
|---|---|
| Environment setup | Understanding what entry_64.S does; recognizing MinOS patterns in kernel code |
| Reading code | Immediately readable crash backtraces; understanding +0x48/0x90 notation |
| Crypto subsystem | AES-NI assembly legible from Chapter 32 coverage |
| Bug analysis | Page fault analysis, null pointer dereference at offset 0x20 (pointer + 8 = struct field) |
| Patch writing | Understanding what the code does at the machine level, not just syntactically |
| Review discussion | Ability to discuss IS_ERR_OR_NULL vs. null check with mechanical understanding of why |
The assembly knowledge did not write the patch — you wrote the patch. But it removed the layer of mystery that makes the kernel feel impenetrable to developers who have only worked at higher abstraction levels.
The Realistic Timeline
| Timeframe | Milestone |
|---|---|
| Week 1 | Kernel built and running in virtme |
| Week 2-3 | Read architecture-specific code; recognize patterns from this book |
| Week 3-4 | Read a bug report; understand the backtrace |
| Week 5-6 | Identify a contribution target; understand the specific code |
| Week 6-8 | Write, test, and send the patch |
| Week 8-10 | Receive and respond to review feedback |
| Week 10-16 | Patch merged into subsystem tree |
| +1-2 kernel releases | Patch appears in mainline Linux |
This is a realistic, part-time schedule. Many first-time contributors take longer because they spend time understanding the mailing list culture and submission mechanics. The technical barriers are lower than expected; the process barriers are higher.
The second patch is always faster than the first.