Android Emulator Development, Anbox, & Waydroid

Debugging Your Custom AOSP Kernel: Advanced Techniques for QEMU-Based Android Development

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction

Developing custom kernels for Android is a cornerstone of advanced system development, enabling optimizations, new hardware support, and security enhancements. When working with the Android Open Source Project (AOSP) and QEMU-based virtual devices, debugging your kernel becomes a critical yet often complex task. This guide delves into advanced techniques for effectively debugging a custom AOSP kernel running within QEMU, providing a clear path from compilation to interactive debugging sessions.

Understanding kernel behavior is paramount. Whether you’re chasing elusive race conditions, pinpointing memory corruption, or verifying new driver functionality, a robust debugging setup is indispensable. We will cover the essential steps, from configuring your kernel for debug symbols and KGDB to launching QEMU with the necessary hooks and connecting a GDB debugger for a powerful, interactive session.

Setting Up Your AOSP Environment for Kernel Compilation

Prerequisites

Before diving into kernel compilation, ensure your AOSP build environment is correctly set up. This typically involves having a Linux distribution (Ubuntu LTS recommended), sufficient disk space (200GB+), and essential build tools. You should have already synced the AOSP source code. The kernel source code for AOSP emulator targets is usually located within the AOSP tree, typically under prebuilts/qemu-kernel/ or directly within specific architecture folders like kernel/msm, kernel/common, or kernel/goldfish.

Identifying the Correct Kernel Source

For QEMU, AOSP often utilizes specific kernel branches or forks. To identify the exact kernel source used by your AOSP branch, examine the device/generic/goldfish/emulator-kernel.mk or similar makefiles that define the TARGET_PREBUILT_KERNEL variable. This will point to the prebuilt kernel or indicate where the source is expected.

# Example from device/generic/goldfish/emulator-kernel.mk
TARGET_PREBUILT_KERNEL := $(DEVICE_KERNEL_PATH)/goldfish/kernel

In many modern AOSP branches, you’ll find the kernel sources under common/kernel or a symlink pointing to prebuilts/qemu-kernel/. For our purposes, we’ll assume a typical kernel/goldfish or kernel/common structure.

Kernel Configuration for Debugging

The first step in making your kernel debuggable is to enable the necessary options in its configuration. Navigate to your kernel source directory:

cd kernel/goldfish # or kernel/common/

Then, configure your kernel. It’s often best to start with a known good configuration and modify it.

make ARCH=x86_64 goldfish_defconfig # For 64-bit QEMU goldfish kernel
make ARCH=x86_64 menuconfig

Inside menuconfig, enable the following crucial options:

  • Kernel hacking -> Kernel debugging (CONFIG_DEBUG_KERNEL=y)
  • Kernel hacking -> Compile-time checks and compiler options -> Compile the kernel with debug info (CONFIG_DEBUG_INFO=y)
  • Kernel hacking -> Compile-time checks and compiler options -> Provide GDB scripts for kernel debugging (CONFIG_GDB_SCRIPTS=y)
  • Kernel hacking -> KGDB: kernel debugger (CONFIG_KGDB=y)
  • Kernel hacking -> KGDB: use kgdb over the serial console (CONFIG_KGDB_SERIAL_CONSOLE=y)
  • Kernel hacking -> Stack unwinding support -> Call stack unwinding via frame pointers (CONFIG_FRAME_POINTER=y) – this greatly improves GDB backtraces.

Save your configuration and exit menuconfig.

Compiling Your Custom Kernel

With the debug options enabled, compile your kernel. Use the appropriate architecture and cross-compiler toolchain for your target QEMU Android device. AOSP provides these toolchains.

export ARCH=x86_64 # Or arm64
export CROSS_COMPILE=$(AOSP_ROOT)/prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.9/bin/x86_64-linux-android-
# Adjust CROSS_COMPILE path to your specific AOSP toolchain

make -j$(nproc)

Upon successful compilation, you will find the kernel image (e.g., arch/x86_64/boot/bzImage or arch/x86_64/boot/Image) and the debug symbols file (vmlinux) in the root of your kernel build directory.

Integrating the Custom Kernel with AOSP QEMU

Now, replace the AOSP prebuilt kernel with your newly compiled debug kernel. The easiest way is to modify your AOSP build configuration. After sourcing your AOSP build environment (`source build/envsetup.sh` and `lunch aosp_x86_64-userdebug`), you can override the kernel.

cp arch/x86_64/boot/Image $(AOSP_ROOT)/prebuilts/qemu-kernel/x86_64/kernel-qemu # Or the path TARGET_PREBUILT_KERNEL points to
cp vmlinux $(AOSP_ROOT)/vmlinux-custom-debug # Copy vmlinux for GDB

Alternatively, you can specify the kernel path directly when launching the emulator.

Launching QEMU with KGDB Support

To enable GDB to connect to your QEMU instance, you need to launch QEMU with specific arguments. The key flags are -s (which is shorthand for -gdb tcp::1234, starting a GDB server on port 1234) and -S (which freezes the CPU at startup, waiting for a GDB connection).

emulator -avd avd_name -kernel /path/to/your/custom/Image -qemu -s -S -serial mon:stdio -serial telnet:localhost:5554,server,nowait

Explanation of QEMU flags:

  • -avd avd_name: Specifies the Android Virtual Device to launch.
  • -kernel /path/to/your/custom/Image: Points to your custom kernel image. This overrides the default.
  • -qemu: Passes subsequent arguments directly to QEMU.
  • -s: Starts the GDB server on TCP port 1234 (default).
  • -S: Freezes the CPU at startup, waiting for GDB to connect and issue a ‘continue’ command.
  • -serial mon:stdio: Redirects the kernel’s serial output (including KGDB messages) to the QEMU console.
  • -serial telnet:localhost:5554,server,nowait: Sets up another serial port, useful for interacting with the Android console via telnet.

Once QEMU launches, it will pause. You’ll see messages indicating it’s waiting for a GDB connection.

Connecting GDB to QEMU for Kernel Debugging

Now, open a new terminal and launch GDB. It’s crucial to load the uncompressed vmlinux file, which contains all the debug symbols.

gdb $(AOSP_ROOT)/vmlinux-custom-debug # Path to your copied vmlinux

Inside GDB, connect to the QEMU server:

(gdb) target remote localhost:1234

GDB will connect and show the current CPU state. Now, you can start your debugging session.

Basic GDB Kernel Debugging Commands

Here are essential GDB commands for kernel debugging:

  • (gdb) b : Set a breakpoint at a kernel function (e.g., b sys_read).
  • (gdb) b :: Set a breakpoint at a specific line in a source file.
  • (gdb) c: Continue execution. QEMU will resume.
  • (gdb) s: Step one instruction.
  • (gdb) n: Step over the current instruction/function call.
  • (gdb) p : Print the value of a variable.
  • (gdb) l: List source code around the current execution point.
  • (gdb) bt: Print a backtrace of the call stack.
  • (gdb) info registers: Display current register values.
  • (gdb) x/Nx
    : Examine N bytes of memory at address (e.g., x/10i $pc to disassemble 10 instructions at program counter).

Example: Setting a breakpoint in the sys_read system call and inspecting arguments.

(gdb) b sys_read
Breakpoint 1 at 0xffffffff812e9b10: file fs/read_write.c, line 431.
(gdb) c
Continuing.

Breakpoint 1, sys_read (fd=4, buf=0x7fe32b9040, count=16) at fs/read_write.c:431
431	{
(gdb) p fd
$1 = 4
(gdb) p/x buf
$2 = 0x7fe32b9040
(gdb) p count
$3 = 16

Advanced Debugging & Troubleshooting

Symbol Resolution and Source Paths

If GDB can’t find source files or symbols, ensure:

  • You loaded the correct vmlinux file.
  • Your kernel source path is known to GDB. You might need (gdb) dir /path/to/kernel/source.

Using `crash` Utility for Post-mortem Analysis

For more complex issues, especially after a kernel panic, the `crash` utility is invaluable. It analyzes kernel crash dumps (vmcore files). While setting up vmcore dumping in QEMU is beyond this guide’s scope, be aware of its existence for in-depth post-mortem debugging.

Dynamic Analysis with `ftrace` and `perf`

While GDB offers static breakpoints, `ftrace` (Function Tracer) and `perf` are dynamic analysis tools built into the Linux kernel. `ftrace` allows tracing specific function calls and events without stopping the kernel, while `perf` provides detailed performance profiling. These are excellent for understanding complex interactions or performance bottlenecks. You can enable relevant CONFIG_FTRACE and CONFIG_PERF_EVENTS options in menuconfig and then use the tools from within the Android shell.

Common Troubleshooting Scenarios

  • QEMU freezes before GDB connection: Ensure -s and -S are correctly passed. Check the QEMU console for errors.
  • GDB cannot connect: Verify the port (default 1234) and that nothing else is using it. Ensure QEMU is running and waiting.
  • Breakpoints not hit: Ensure the function/line exists in the `vmlinux` loaded by GDB. Confirm the kernel is indeed your custom-built version, not a prebuilt.
  • Garbled backtraces: Double-check that CONFIG_FRAME_POINTER=y is enabled and your vmlinux contains full debug info.

Conclusion

Debugging a custom AOSP kernel in QEMU is a sophisticated process that empowers developers with deep insights into the Android system’s core. By meticulously configuring your kernel for debugging, integrating it correctly into your AOSP build, and leveraging the power of GDB, you can efficiently identify and resolve complex kernel-level issues. Mastering these advanced techniques is crucial for anyone involved in serious Android platform development, enabling you to deliver more robust, performant, and secure custom Android experiences.

Android Mobile Specs & Compare Directory

Are you researching mobile hardware properties, processor SoCs, GPU chipsets, or RAM configurations? Access our complete specs catalog to compare up to 5 devices side-by-side!

Compare Devices Specs →
Google AdSense Inline Placement - Content Footer banner