前言
嵌入式 linux 经常要编译 linux 内核,默认情况下编译出的内核镜像是不带调试信息的,这样,当内核 crash 打印 PC 指针和堆栈信息时,我们需要反汇编来确认出错位置,不直观。
如果内核开启了调试选项,我们只需要一个 addr2line 命令,就可以将 PC 指针定位到 C 程序的哪个文件的哪一行,非常快捷高效。
下面我们就来介绍下,如何开启内核调试选项。
-g
gcc 编译应用程序时,使用 -g 选项编译出带有调试信息的可执行程序。编译内核也是同样的道理。所以,我们先在顶层 Makefile 中搜索 -g 选项,下面是 linux-4.1.15 例子
ifdef CONFIG_DEBUG_INFO
ifdef CONFIG_DEBUG_INFO_SPLIT
KBUILD_CFLAGS += $(call cc-option, -gsplit-dwarf, -g)
else
KBUILD_CFLAGS += -g
endif
KBUILD_AFLAGS += -Wa,-gdwarf-2
endif
ifdef CONFIG_DEBUG_INFO_DWARF4
KBUILD_CFLAGS += $(call cc-option, -gdwarf-4,)
endif
要想使能 -g 选项,就要使能 CONFIG_DEBUG_INFO 编译选项。
make menuconfig,搜索 CONFIG_DEBUG_INFO
.config - Linux/arm 4.1.15 Kernel Configuration
→ Search (CONFIG_DEBUG_INFO) ──────────────────────────────────────────────────────────
┌───────────────────────────────── Search Results ─────────────────────────────────┐
│ Symbol: DEBUG_INFO [=n] │
│ Type : boolean │
│ Prompt: Compile the kernel with debug info │
│ Location: │
│ -> Kernel hacking │
│ (1) -> Compile-time checks and compiler options │
│ Defined at lib/Kconfig.debug:120 │
│ Depends on: DEBUG_KERNEL [=y] && !COMPILE_TEST [=n] │
│ │
│ │
│ Symbol: DEBUG_INFO_DWARF4 [=n] │
│ Type : boolean │
│ Prompt: Generate dwarf4 debuginfo │
│ Location: │
│ -> Kernel hacking │
│ -> Compile-time checks and compiler options │
│ (2) -> Compile the kernel with debug info (DEBUG_INFO [=y]) │
│ Defined at lib/Kconfig.debug:161 │
│ Depends on: DEBUG_INFO [=y] │
│ │
│ │
│ Symbol: DEBUG_INFO_REDUCED [=n] │
│ Type : boolean │
├──────────────────────────────────────────────────────────────────────────( 51%)──┤
│ < Exit > │
└──────────────────────────────────────────────────────────────────────────────────┘
vim .config
3244 # CONFIG_DEBUG_INFO is not set
修改为
3244 CONFIG_DEBUG_INFO=y
检查
.config - Linux/arm 4.1.15 Kernel Configuration
→ Search (CONFIG_DEBUG_INFO) ──────────────────────────────────────────────────────────
┌───────────────────────────────── Search Results ─────────────────────────────────┐
│ Symbol: DEBUG_INFO [=y] │
│ Type : boolean │
│ Prompt: Compile the kernel with debug info │
│ Location: │
│ -> Kernel hacking │
│ (1) -> Compile-time checks and compiler options │
│ Defined at lib/Kconfig.debug:120 │
│ Depends on: DEBUG_KERNEL [=y] && !COMPILE_TEST [=n] │
│ │
│ │
│ Symbol: DEBUG_INFO_DWARF4 [=n] │
│ Type : boolean │
│ Prompt: Generate dwarf4 debuginfo │
│ Location: │
│ -> Kernel hacking │
│ -> Compile-time checks and compiler options │
│ (2) -> Compile the kernel with debug info (DEBUG_INFO [=y]) │
│ Defined at lib/Kconfig.debug:161 │
│ Depends on: DEBUG_INFO [=y] │
│ │
│ │
│ Symbol: DEBUG_INFO_REDUCED [=n] │
│ Type : boolean │
├──────────────────────────────────────────────────────────────────────────( 51%)──┤
│ < Exit > │
└──────────────────────────────────────────────────────────────────────────────────┘
看到 Symbol: DEBUG_INFO [=y] ,这样 -g 编译选项就开启了。
重新编译内核,然后检查镜像文件
$ file vmlinux
vmlinux: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, BuildID[sha1]=bfb6aefe3b6955df44222bfefc9ee5b4955f9c81, with debug_info, not stripped
看到 with debug_info,这样内核镜像就带有调试信息了。后面使用 openOCD 进行硬件调试,或者根据 crash 信息定位出错代码位置都是很方便的。
实例
如下图,在 do_mount_root 函数中添加一个调试 BUG。
板子运行后报错如下
ALSA device list:
No soundcards found.
------------[ cut here ]------------
Kernel BUG at 809c70a8 [verbose debug info unavailable]
Internal error: Oops - BUG: 0 [#1] PREEMPT SMP ARM
Modules linked in:
CPU: 0 PID: 1 Comm: swapper/0 Not tainted 4.1.15 #95
Hardware name: Freescale i.MX6 Ultralite (Device Tree)
task: 88048000 ti: 8804c000 task.ti: 8804c000
PC is at do_mount_root.part.3+0x38/0x44
LR is at mntput_no_expire+0x40/0x1ec
pc : [<809c70a8>] lr : [<80115138>] psr: 60000113
sp : 8804df00 ip : 00000000 fp : 8804df04
r10: 8086900c r9 : 80869154 r8 : 000003e8
r7 : 80a90024 r6 : 00000006 r5 : 80a33888 r4 : 00000005
r3 : 0000000f r2 : 80a90024 r1 : 0b10a000 r0 : 00000000
Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment kernel
Control: 10c5387d Table: 8000406a DAC: 00000015
Process swapper/0 (pid: 1, stack limit = 0x8804c210)
Stack: (0x8804df00 to 0x8804e000)
df00: 8804df3c 809c74d4 80a1bf6c 801067e0 80a1c474 80a1bf6c 88030040 80a1aac4
df20: 80a90024 80a90000 80a1aaa0 8094d744 80a90000 809c6620 8804df5c 809c76c4
df40: 80a1aaa0 8094d744 00000008 000000d2 80a90000 80a1aaa0 8804df9c 809c6ebc
df60: 00000007 00000007 809c6620 00000000 8804df7c 00000008 8804df9c 00000000
df80: 806d63c4 00000000 00000000 00000000 00000000 00000000 8804dfac 806d63d0
dfa0: 00000000 806d63c4 00000000 80010408 00000000 00000000 00000000 00000000
dfc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
dfe0: 00000000 00000000 00000000 00000000 00000013 00000000 5fedd77f e7ffbaae
[<809c70a8>] (do_mount_root.part.3) from [<809c74d4>] (mount_root+0x74/0x100)
[<809c74d4>] (mount_root) from [<809c76c4>] (prepare_namespace+0x164/0x1c8)
[<809c76c4>] (prepare_namespace) from [<809c6ebc>] (kernel_init_freeable+0x18c/0x1dc)
[<809c6ebc>] (kernel_init_freeable) from [<806d63d0>] (kernel_init+0xc/0xec)
[<806d63d0>] (kernel_init) from [<80010408>] (ret_from_fork+0x14/0x2c)
Code: e5933020 e593305c e5933008 e582300c (e7f001f2)
---[ end trace 48ec6f9402ae51bf ]---
Kernel panic - not syncing: Attempted to kill init! exitcode=0x0000000b
---[ end Kernel panic - not syncing: Attempted to kill init! exitcode=0x0000000b
random: nonblocking pool is initialized
开门见山地就提示我们 Kernel BUG at 809c70a8,那我们就用 addr2line 来看看到底是哪行代码
$ addr2line -f -e vmlinux 0x809c70a8
do_mount_root
/home/liyongjun/project/board/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/init/do_mounts.c:373
do_mounts.c 文件的第 373 行
一下就定位到了出错位置,这就是内核开启调试选项的好处。