Debugging The Linux Kernel Using Gdb

Revision as of 12:26, 25 November 2008 by Keesj (talk | contribs) (Getting the kernel log buffer)
Jump to: navigation, search

Debugging the linux kernels using gdb

The majority of day to day kernel debugging is done by adding print statements to code by using the famous printk function. Using printk it as it is relatively simple and effective and cheap technique to use. There are many other linux grown techniques that take the debugging and profiling approach to a higher level. On this page we will discuss using the gnu debugger to do kernel debugging. Overall starting using gdb to do kernel debugging is relatively easy.

Most of the examples here will work in two (open source) situations. when using JTAG and when using qemu system emulation. As the second option does not require any hardware you could go on and try it right away!

The open source jtag debugging world is not that big. One project stands out in terms of debugging capabilities is OpenOCD and this is the tool used in this documentation.

vmlinuz v.s zImage

When you want to debug the kernel you need a little understanding of how the kernel is composed. Most important is the difference between your vmlinux and the zImage. What you need to understand at this point is that the zImage is a container. This container gets loaded by a bootloader and that execution is handed over to the zImage. This zImage unpacks the kernel to the same memory location and starts executing the kernel.(explain that vmlinux does not have to be the real kernel as it is possible to debug a "stripped" kernel using a non stripped vmlinux). overall if we look at a compiled kernel we will see that vmlinux is located at the root of the kernel tree whiles the zImage is located under arch/arm/boot

`-- zImage

vmlinux is what we will be using during debugging of the linux kernel.

Loading a kernel in memory

Once you are used to using gdb to debug kernels you will want to use gdb to directly load kernels onto your target. The most practical way of doing this is to set a hardware breakpoint at the start of the kernel and reset your board using the jtag reset signal. Your bootloader will initialize your board and the execution will stop at the start of the kernel. after that you can load a kernel into memory and run it.

execute the following:

(gdb) file vmlinux
(gdb) target remote :3333
(gdb) break __init_begin
(gdb) cont
(gdb) mon reset #perhaps this needs to be done from the openocd telnet session..
Breakpoint 1, 0xc0008000 in stext ()
(gdb) load vmlinux
Loading section .text.head, size 0x240 lma 0xc0008000
Loading section .init, size 0xe4dc0 lma 0xc0008240
Loading section .text, size 0x219558 lma 0xc00ed000
Loading section .text.init, size 0x7c lma 0xc0306558
Loading section __ksymtab, size 0x4138 lma 0xc0307000
Loading section __ksymtab_gpl, size 0x1150 lma 0xc030b138
Loading section __kcrctab, size 0x209c lma 0xc030c288
Loading section __kcrctab_gpl, size 0x8a8 lma 0xc030e324
Loading section __ksymtab_strings, size 0xc040 lma 0xc030ebcc
Loading section __param, size 0x2e4 lma 0xc031ac0c
Loading section .data, size 0x1e76c lma 0xc031c000
Start address 0xc0008000, load size 3345456
Transfer rate: 64 KB/sec, 15632 bytes/write.
(gdb) cont

This will boot your kernel that was loaded into memory via jtag

Getting the kernel log buffer

Sometimes the kernel will panic before the serial is up and running. in such situations is it *VERY* handy to be able to dump the kernel log buffer. this can be done by looking at the content of the __log_buf in the kernel. in gdb this can be done by issuing

p (char*) &__log_buf[log_start]

There must be a simple way of printing the memory area between log_start and log_end.

The problem is that gdb stops after the first line. currently we use this routine that copied from wchar.gdb until something "normal" came out. we defined dmesg it like this:

define dmesg
        set $__log_buf = $arg0
        set $log_start = $arg1
        set $log_end = $arg2
        set $x = $log_start
        echo "
        while ($x < $log_end)
                set $c = (char)(($__log_buf)[$x++])
                printf "%c" , $c
        echo "\n
document dmesg
dmesg __log_buf log_start log_end
Print the content of the kernel message buffer

and call it like this:

dmesg __log_buf log_start log_end

Debugging a kernel module (.o and .ko )