Linux内核使用gdb调试

来源:
导读 大家好,我是本期栏目编辑小友,现在为大家讲解Linux内核使用gdb调试问题。 这里,记录下用gdb调试内核KE的步骤和方法。请指出并改进不足之

大家好,我是本期栏目编辑小友,现在为大家讲解Linux内核使用gdb调试问题。

这里,记录下用gdb调试内核KE的步骤和方法。请指出并改进不足之处。

1必备工具和文件。

Gdb、addr2line、vmlinux和内核coredump文件。

在64位平台上,gdb和addr2line分别使用aarch64-linux-android-gdb。

aarch 64-Linux-Android-addr 2 line。

2.调试过程。

MTK平台的coredump文件名是: SYS_MINI_RDUMP,是用GAT工具解析DB文件得到的。

2.1启动GDB。

aarch 64-Linux-Android-gdb vmlinux core dump

aarch64-linux-android-gdb。/vmlinux。/aee_exp_backup/db .致命. 00.KE/20151107_170222_178/db .致命. 00.KE.dbg.DEC/SYS_MINI_RDUMP

控制台输出:

# 00x ffffffc 00088 D2 c8 in eth _ start _ xmit(skb=0x ffffffc 023 ba 8300,net=0xffffffc06d3d2000)

at内核-3.10/驱动程序/usb/gadget/u_ether.c:893

(广发银行)

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

2.2 gdb通用指令。

Bt :打印堆栈调用信息。

向下:跳转到下一个FP指针。

向上:返回到前一个FP指针。

P :打印变量值。

X :打印内存内容。

X/(n、f和u是可选参数)。

n:中要显示的存储单元的数量意味着从当前地址向后显示几个存储单元的内容,一个存储单元的大小由下面的u定义。

f:显示格式。

x(十六进制)以十六进制格式显示变量。

d(十进制)以十进制格式显示变量。

u(无符号十进制)以十进制格式显示无符号整数。

o(八进制)以八进制格式显示变量。

t(二进制)以二进制格式显示变量。

(地址)以十六进制格式显示变量。

C(char)以字符格式显示变量。

F(float)以浮点格式显示变量。

u:每个单位的大小,以字节计算。默认值为4字节。GDB从指定的内存地址中读取指定的字节,将其作为值取出,并以F格式显示.

b:1字节h:2 by

tes     w:4 bytes g:8 bytes

比如x/3uh 0x54320表示从内存地址0x54320读取内容,h表示以双字节为单位,3表示输出3个单位,u表示按照十六进制显示。

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

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

2.3 异常点分析

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

Unable to handle kernel NULL pointerdereference at virtual address 000000e4

[6464.203080]-(0)[3:ksoftirqd/0]PC is at eth_start_xmit+0x1fc/0x748

[6464.203112]-(0)[3:ksofTIrqd/0]LR is at eth_start_xmit+0x1d8/0x748

[6464.203143]-(0)[3:ksofTIrqd/0]pc : [] lr :[] pstate: 800001c5

[6464.203168]-(0)[3:ksofTIrqd/0]sp : ffffffc071877b40

[ 6464.203192]-(0)[3:ksofTIrqd/0]x29:ffffffc071877b40 x28: 00000000000005bc

[6464.203231]-(0)[3:ksoftirqd/0]x27: 00000000000005bc x26:ffffffc01ee14c40

[6464.203270]-(0)[3:ksoftirqd/0]x25: ffffffc06ee12510 x24:ffffffc06d3d2730

[ 6464.203308]-(0)[3:ksoftirqd/0]x23:ffffffc023ba8300 x22: ffffffc06d3d2720

[6464.203347]-(0)[3:ksoftirqd/0]x21: ffffffc06d3d2000 x20:ffffffc06d3d2700

[6464.203385]-(0)[3:ksoftirqd/0]x19: ffffffc00141e000 x18:0000000000000000

[ 6464.203422]-(0)[3:ksoftirqd/0]x17:0000007f7ed6fcf8 x16: ffffffc000278828

[6464.203459]-(0)[3:ksoftirqd/0]x15: 0000007f7eda9a24 x14:228f252b6f65a378

[6464.203498]-(0)[3:ksoftirqd/0]x13: 9939719eb9fc9521 x12:0260832913e230f2

[6464.203535]-(0)[3:ksoftirqd/0]x11: 63530fe2e6e696f3 x10:399aa79385bb3861

[6464.203573]-(0)[3:ksoftirqd/0]x9 : 01a6b3c12e057068 x8 :2421eada8933ba1d

[6464.203610]-(0)[3:ksoftirqd/0]x7 : e4324d79f1892abb x6 :ffffffc0393165bc

[6464.203646]-(0)[3:ksoftirqd/0]x5 : 0000000000000000 x4 :0000000000000003

[6464.203682]-(0)[3:ksoftirqd/0]x3 : 0000000000000002 x2 :0000000000000000

[6464.203718]-(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 port_usb->dl_max_transfer_len - dev->net->mtu)))

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

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

2.3.1直接打印变量值

p dev

$1 =

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

2.3.2 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为空指针.

2.3.3 汇编+偏移量+寄存器

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

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

0xffffffc00088d27c :         bl      0xffffffc0004803c0

0xffffffc00088d280 :       ldr    w28, [x23,#104]

0xffffffc00088d284 :       ldr    w1, [x26,#-56]

0xffffffc00088d288 :       mov x0, x23

0xffffffc00088d28c :       add  w28, w28, w1

0xffffffc00088d290 :       str    w28, [x26,#-56]

0xffffffc00088d294 :       mov w27, w28

0xffffffc00088d298 :       bl      0xffffffc0009ce020

0xffffffc00088d29c :       mov x0, x22

0xffffffc00088d2a0 :       bl      0xffffffc000b52434

0xffffffc00088d2a4 :       mov x1, x0

0xffffffc00088d2a8 :       ldr    w2, [x20,#88]  /*dev->tx_skb_hold_count */

0xffffffc00088d2ac :       ldr    w4, [x20,#136]

0xffffffc00088d2b0 :       add  w2, w2, #0x1

0xffffffc00088d2b4 :       str    w2, [x20,#88]

0xffffffc00088d2b8 :       cmp w2, w4

0xffffffc00088d2bc :       b.cs  0xffffffc00088d2dc

---Type to continue, or q to quit---

0xffffffc00088d2c0 :       ldr    x2, [x20,#8] /*dev->port_usb*/

0xffffffc00088d2c4 :       ldr    x0, [x20,#16]/*dev->net*/

=> 0xffffffc00088d2c8:        ldr    w2, [x2,#228]/*dev->port_usb->dl_max_transfer_len*/

0xffffffc00088d2cc :       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 }, rx_work = {data = {counter = 512}, entry= {

next = 0xffffffc06d3d27c8, prev = 0xffffffc06d3d27c8}, func =0xffffffc00088db5c }, rx_work1 = {data = {counter = 512},entry = {

next = 0xffffffc06d3d27e8, prev = 0xffffffc06d3d27e8}, func = 0xffffffc00089dd74}, todo = 0, zlp = false,

host_mac = "\246\030\003", }

3. 调试总结

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 */

 

标签:

版权声明:转载此文是出于传递更多信息之目的。若有来源标注错误或侵犯了您的合法权益,请作者持权属证明与本网联系,我们将及时更正、删除,谢谢您的支持与理解。