GDB调试内核总结

必备工具和文件

Gdb,addr2line,vmlinux以及内核coredump文件
在64位平台,gdb和addr2line 分别使用aarch64-linux-android-gdb
aarch64-linux-android-addr2line.

调试过程

MTK平台coredump文件名为: SYS_MINI_RDUMP,用GAT工具解析DB文件得到.

启动GDB

aarch64-linux-android-gdb vmlinux  coredump
aarch64-linux-android-gdb ./vmlinux ./aee_exp_backup/db.fatal.00.KE/20151107_170222_178/db.fatal.00.KE.dbg.DEC/SYS_MINI_RDUMP

控制台输出内容:

#0 0xffffffc00088d2c8 in eth_start_xmit(skb=0xffffffc023ba8300, net=0xffffffc06d3d2000)
at kernel-3.10/drivers/usb/gadget/u_ether.c:893
(gdb)

可以看出异常点在u__ether.c文件893行.

gdb常用指令

bt : 打印堆栈调用信息.

down : 跳转到下一级FP指针

up : 回到上一级FP指针

P : 打印变量值

x : 打印内存内容

x / (n,f,u为可选参数)
n: 需要显示的内存单元个数,也就是从当前地址向后显示几个内存单元的内容,一个内存单元的大小由后面的u定义
f:显示格式
x(hex) 按十六进制格式显示变量。
d(decimal) 按十进制格式显示变量。
u(unsigned decimal) 按十进制格式显示无符号整型。
o(octal) 按八进制格式显示变量。
t(binary) 按二进制格式显示变量。
a(address) 按十六进制格式显示变量。
c(char) 按字符格式显示变量。
f(float) 按浮点数格式显示变量
u:每个单元的大小,按字节数来计算。默认是4 bytes。GDB会从指定内存地址开始读取指定字节,并把其当作一个值取出来,并使用格式f来显示
b:1 byte h:2 bytes w:4 bytes g:8 bytes
比如x/3uh 0x54320表示从内存地址0x54320读取内容,h表示以双字节为单位,3表示输出3个单位,u表示按照十六进制显示。

list : 以c语言列出当前函数内容(c语言)

disassemble :以汇编方式列出当前函数内容

异常点分析

可以从last_kmsg或者db文件解析出的SYS_KERNEL_LOG中得知异常类型.重要信息为PC和寄存器值.

Unable to handle kernel NULL pointerdereference at virtual address 000000e4
[6464.203080]<0>-(0)[3:ksoftirqd/0]PC is at eth_start_xmit+0x1fc/0x748
[6464.203112]<0>-(0)[3:ksoftirqd/0]LR is at eth_start_xmit+0x1d8/0x748
[6464.203143]<0>-(0)[3:ksoftirqd/0]pc : [<ffffffc00088d2c8>] lr :[<ffffffc00088d2a4>] pstate: 800001c5
[6464.203168]<0>-(0)[3:ksoftirqd/0]sp : ffffffc071877b40
[ 6464.203192]<0>-(0)[3:ksoftirqd/0]x29:ffffffc071877b40 x28: 00000000000005bc
[6464.203231]<0>-(0)[3:ksoftirqd/0]x27: 00000000000005bc x26:ffffffc01ee14c40
[6464.203270]<0>-(0)[3:ksoftirqd/0]x25: ffffffc06ee12510 x24:ffffffc06d3d2730
[ 6464.203308]<0>-(0)[3:ksoftirqd/0]x23:ffffffc023ba8300 x22: ffffffc06d3d2720
[6464.203347]<0>-(0)[3:ksoftirqd/0]x21: ffffffc06d3d2000 x20:ffffffc06d3d2700
[6464.203385]<0>-(0)[3:ksoftirqd/0]x19: ffffffc00141e000 x18:0000000000000000
[ 6464.203422]<0>-(0)[3:ksoftirqd/0]x17:0000007f7ed6fcf8 x16: ffffffc000278828
[6464.203459]<0>-(0)[3:ksoftirqd/0]x15: 0000007f7eda9a24 x14:228f252b6f65a378
[6464.203498]<0>-(0)[3:ksoftirqd/0]x13: 9939719eb9fc9521 x12:0260832913e230f2
[6464.203535]<0>-(0)[3:ksoftirqd/0]x11: 63530fe2e6e696f3 x10:399aa79385bb3861
[6464.203573]<0>-(0)[3:ksoftirqd/0]x9 : 01a6b3c12e057068 x8 :2421eada8933ba1d
[6464.203610]<0>-(0)[3:ksoftirqd/0]x7 : e4324d79f1892abb x6 :ffffffc0393165bc
[6464.203646]<0>-(0)[3:ksoftirqd/0]x5 : 0000000000000000 x4 :0000000000000003
[6464.203682]<0>-(0)[3:ksoftirqd/0]x3 : 0000000000000002 x2 :0000000000000000
[6464.203718]<0>-(0)[3:ksoftirqd/0]x1 : 0000000000000140 x0 :ffffffc06d3d2000

启动gdb时会显示最后出现点, 以上面的异常来分析,u_ether.c:893

C语言代码为:

if ((dev->tx_skb_hold_count dl_max_pkts_per_xfer) && (length <(dev->port_usb->dl_max_transfer_len - dev->net->mtu)))

从上面的log看,是由NULL指针引起. 这里涉及到三个指针,dev, dev->port_usb, dev->net.

那么怎么查找到底是哪个指针有问题了?

直接打印变量值

p dev
$1 = <optimized out>

可以看出已经被编译器优化了,无法用p直接打印

PC+偏移量法

首先确定偏移量:

p&(((struct eth_dev *)0)->net)
$1 = (struct net_device **) 0x10
(gdb) p &(((struct eth_dev*)0)->port_usb)
$2 = (struct gether **) 0x8
p (((struct gether*)0)->dl_max_transfer_len)
Cannot access memory at address 0xe4
p (((struct net_device *)0)->mtu)
Cannot access memory at address 0x1b8

可以看出dev->net和port_usb的偏移量为16和8,

dl_max_transfer_len和mtu的偏移量为:0xe4 ,0x1b8

在log中提示无法处理虚拟地址为0x000000e4

Unable to handle kernel NULL pointerdereference at virtual address 000000e4

而dl_max_transfer_len的偏移量刚好为0xe4,则可以证明 port_usb 为空指针.

汇编+偏移量+寄存器

用disassemble 打印出当前函数的汇编语言(这里只列举部分)

再查找16,8, 228(0xe4),440(0x1b8)

0xffffffc00088d27c <+432>:         bl      0xffffffc0004803c0 <memcpy>
0xffffffc00088d280 <+436>:       ldr    w28, [x23,#104]
0xffffffc00088d284 <+440>:       ldr    w1, [x26,#-56]
0xffffffc00088d288 <+444>:       mov x0, x23
0xffffffc00088d28c <+448>:       add  w28, w28, w1
0xffffffc00088d290 <+452>:       str    w28, [x26,#-56]
0xffffffc00088d294 <+456>:       mov w27, w28
0xffffffc00088d298 <+460>:       bl      0xffffffc0009ce020<dev_kfree_skb_any>
0xffffffc00088d29c <+464>:       mov x0, x22
0xffffffc00088d2a0 <+468>:       bl      0xffffffc000b52434<_raw_spin_lock_irqsave>
0xffffffc00088d2a4 <+472>:       mov x1, x0
0xffffffc00088d2a8 <+476>:       ldr    w2, [x20,#88]  /*dev->tx_skb_hold_count */
0xffffffc00088d2ac <+480>:       ldr    w4, [x20,#136]
0xffffffc00088d2b0 <+484>:       add  w2, w2, #0x1
0xffffffc00088d2b4 <+488>:       str    w2, [x20,#88]
0xffffffc00088d2b8 <+492>:       cmp w2, w4
0xffffffc00088d2bc <+496>:       b.cs  0xffffffc00088d2dc <eth_start_xmit+528>
---Type <return> to continue, or q<return> to quit---
0xffffffc00088d2c0 <+500>:       ldr    x2, [x20,#8] /*dev->port_usb*/
0xffffffc00088d2c4 <+504>:       ldr    x0, [x20,#16]/*dev->net*/
=> 0xffffffc00088d2c8<+508>:        ldr    w2, [x2,#228]/*dev->port_usb->dl_max_transfer_len*/
0xffffffc00088d2cc <+512>:       ldr    w0, [x0,#440]/*dev->net->mtu*/

PC在0xffffffc00088d2c8出现异常,说明x2寄存器为NULL,可以证明dev->port_usb为NULL 。

另外这里寄存器x20保存有dev的指针,x20的值为ffffffc06d3d2700 ,也可尝试用p打印这个地址,port_usb的确为NULL.

p *(struct eth_dev*)0xffffffc06d3d2700
$10 = {lock = {{rlock = {raw_lock = {lock =0}, break_lock = 0}}}, port_usb = 0x0, net =0xffffffc06d3d2000, gadget = 0xffffffc06ee132a0, req_lock = {{rlock = {
raw_lock = {lock = 1}, break_lock= 0}}}, reqrx_lock = {{rlock = {raw_lock = {lock = 0}, break_lock = 0}}},tx_reqs = {next = 0xffffffc06d3d2730,
prev = 0xffffffc06d3d2730}, rx_reqs = {next = 0xffffffc06d3d2740, prev =0xffffffc06d3d2740}, tx_qlen = 1, no_tx_req_used = 0, tx_skb_hold_count = 1,
tx_req_bufsize = 4740, rx_frames = {next = 0xffffffc06d3d2760, prev =0xffffffc06d3d2760, qlen = 0, lock = {{rlock = {raw_lock = {lock = 0},break_lock = 0}}}},
header_len = 0, ul_max_pkts_per_xfer = 1, dl_max_pkts_per_xfer = 3, wrap= 0x0, unwrap = 0x0, work = {data = {counter = 68719476704}, entry = {
next = 0xffffffc06d3d27a8, prev = 0xffffffc06d3d27a8}, func =0xffffffc00089dda8 <eth_work>}, rx_work = {data = {counter = 512}, entry= {
next = 0xffffffc06d3d27c8, prev = 0xffffffc06d3d27c8}, func =0xffffffc00088db5c <process_rx_w>}, rx_work1 = {data = {counter = 512},entry = {
next = 0xffffffc06d3d27e8, prev = 0xffffffc06d3d27e8}, func = 0xffffffc00089dd74<process_rx_w1>}, todo = 0, zlp = false,
host_mac = "\246\030\003", <incomplete sequence \310>}

调试总结

  1. 调试时需要确定vmlinux与DB文件对应.不然无法精准定位, 打开vmlinux ,搜索SMP关键字,可以确认vmlinu的编译时间.

  2. 内存标示

有时用p打印出变量的值全部为0x6b6b6b6b,这说明内存已经被其他地方释放

内核有定义.

#define POISON_INUSE 0x5a
/* for use-uninitialised poisoning */
#define POISON_FREE 0x6b
/* for use-after-free poisoning */
#define POISON_END 0xa5
/* end-byte of poisoning */
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: gdb是一个强大的调试工具,可以用于调试Linux内核。使用gdb调试Linux内核需要以下步骤: 1. 编译内核时添加调试信息 在编译内核时需要添加调试信息,可以通过在Makefile中添加以下选项来实现: CONFIG_DEBUG_INFO=y CONFIG_DEBUG_KERNEL=y 2. 启动内核调试 在启动内核时需要添加调试选项,可以通过在grub或者lilo中添加以下选项来实现: debug debug earlyprintk=serial,ttyS0,115200 3. 连接gdb 使用gdb连接内核需要使用kgdb插件,可以通过以下命令加载kgdb插件: modprobe kgdb 然后使用以下命令连接gdbgdb vmlinux (gdb) target remote /dev/ttyS0 4. 使用gdb调试 连接成功后,可以使用gdb进行调试,例如: (gdb) b start_kernel (gdb) c 这将在内核启动时设置断点,并继续执行内核。当内核执行到断点时,gdb将停止执行并等待命令。 以上是使用gdb调试Linux内核的基本步骤,具体调试方法和命令可以参考gdb文档和Linux内核调试文档。 ### 回答2: gdb是一款功能强大的调试器,在日常的编程开发中得到了广泛应用。然而,gdb调试Linux内核时与调试用户态应用程序时有所不同。调试内核需要使用gdb的特殊功能来处理调试内核的问题。在下面的几个方面中,我将解释如何使用gdb来调试Linux内核。 1. 准备gdb环境 首先需要将gdb环境设置为可以使用内核符号。在编译内核时,需要在Makefile中添加CONFIG_DEBUG_INFO和CONFIG_DEBUG_KERNEL选项,以支持调试信息。此外,还需要安装所需的内核符号,然后通过"sudo sysctl -w kernel.yama.ptrace_scope=0"以解决防止调试器附加的安全机制问题。 2. 加载内核映像 通过gdb来加载内核映像。使用gdb命令"file vmlinux"来加载内核映像,其中vmlinux是含有调试符号的内核镜像文件。 3. 内核断点调试 可以使用gdb设置内核断点,以调试内核时确定内核程序执行过程中的问题。使用gdb命令"b <function>"设置函数断点,而使用"b * <address>"设置指定地址的断点。 4. 调试内核panic 当内核执行时发生错误时,系统会进入panic状态。如果需要调试内核panic,可以使用gdb命令"handle SIGTRAP noprint pass"来设置中断处理。使用"monitor halt"或直接ctrl+c可以停止内核,查看是什么出问题了,并且使用"cont"命令让内核继续运行。 5. 查看内核堆栈 可以使用gdb命令"bt"来查看内核的堆栈,以确定调试内核时的问题。在通过gdb调试内核处理内核问题时,内核堆栈非常有用。 总的来说,使用gdb调试Linux内核需要更多的操作方式和技巧,但是如果熟练掌握gdb的某些功能和命令,并且了解内核基本结构和运行机制,就可以高效地调试内核出现的问题。 ### 回答3: GDB(GNU调试器)是一个强大的调试工具,也可以用来调试Linux内核。但是,与调试应用程序或用户空间程序相比,内核调试可能会更加复杂。下面是关于如何使用GDB调试Linux内核的一些指南和步骤。 1.编译内核 为了调试内核,首先需要编译内核并安装它。在编译内核时,需要启用符号表。使用如下命令: $ make menuconfig 在Kernel hacking中设置Debug kernel,这将启用符号表的编译。然后使用命令“make”编译内核并安装它。 2.配置调试环境 在内核启动时,启用调试合适的调试器非常重要。例如,可以使用串线调试器而不是控制台输出,因为调试信息可能无法立即打印到控制台。可以通过UART控制台或JTAG调试器进行调试,这通常比控制台输出更可靠。 3.使用GDB连接到内核 使用GDB的第一步是启动GDB,同时指定内核映像文件作为第一个参数。例如,如果内核映像文件为”vmlinuz”,可以使用如下命令连接GDB内核: $ gdb vmlinuz 然后,需要设置GDB的默认连接地址: add-symbol-file /path/to/kernel/vmlinux 0xADDRESS 这里的ADDRESS 应该是编译内核时已知的地址。可以在内核映像文件的/System.map文件中找到地址信息。 4.使用GDB调试 在连接GDB内核后,可以使用GDB来单步执行内核代码或设置断点。可以使用GDB的“step”命令来进入下一个函数调用,并使用“next”命令来执行下一行代码。还可以使用“break”命令设置断点,以捕获特定的行动或事件。 也可以在调试内核时使用一些GDB的调试命令,例如:“watch”命令用于设置监视点,以监视变量的值,而不必停止调试器。可以使用“info” 命令,以获取关于调试目标状态的信息。 总之,调试Linux内核需要仔细的计划和准备。在内核源代码中随时插入代码并编译并不是一个高效的方法。使用GDB可以更轻松地捕获和解决内核的问题。通过使用GDB,可以提高软件开发的效率,并确保Linux内核的稳定性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值