Linux核心使用gdb除錯
這裡記錄平時使用gdb除錯核心KE的步驟和方法. 有不足的地方也請大家指出和完善.
1 必備工具和檔案
Gdb,addr2line,vmlinux以及核心coredump檔案
在64位平臺,gdb和addr2line 分別使用aarch64-linux-android-gdb
aarch64-linux-android-addr2line.
2. 除錯過程
MTK平臺coredump檔名為: SYS_MINI_RDUMP,用GAT工具解析DB檔案得到.
2.1 啟動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行.
2.2 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 :以彙編方式列出當前函式內容
2.3 異常點分析
可以從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 <dev->dl_max_pkts_per_xfer) && (length <(dev->port_usb->dl_max_transfer_len - dev->net->mtu)))
從上面的log看,是由NULL指標引起. 這裡涉及到三個指標,dev, dev->port_usb, dev->net.
那麼怎麼查詢到底是哪個指標有問題了?
2.3.1直接列印變數值
p dev
$1 = <optimized out>
可以看出已經被編譯器優化了,無法用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 <+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>}
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 */
本文永久更新連結:http://embeddedlinux.org.cn/emb-linux/kernel-driver/201901/25-8503.html