板上CPU0錯誤跟蹤方法 (以RT3070驅動錯誤-BUG爲例)

文檔簡介

Loongson1B開發板上驅動錯誤常見CPU0錯誤,而本文檔的目的在於以禪道項目管理系統上RT3070驅動錯誤BUG爲例,講述利用反彙編文件,跟蹤CPU0錯誤的出錯位置,並解析可能原因。共享出來希望對大家能夠有所幫助。

文檔總體來說分爲四個部分:

A. RT3070驅動錯誤BUG詳情

B. 跟蹤過程

C. 原因分析

D. 方法總結

1. RT3070驅動錯誤BUG詳情

RT3070無線網卡分兩種驅動,分別是STA模式和軟AP模式,而Loongson1B板中使用的是STA模式。由於測試需要,使用網絡性能測試工具iperf測試Usb-Wifi的穩定性,連續測試22小時的時候出現CPU0錯誤,現場有保留,BUG由鄺華款提交到禪道項目管理系統,以下列出禪道上該BUG的詳情:

BUG #177::rt3070驅動錯誤

重現步驟: 

[步驟]

使用iperf測試usb-wifi 約22小時,輸出信息與錯誤見文件minicom.cap;調試文件見rt1.asm;使用的驅動模塊爲rt3070sta.ko;出錯代碼段見DoBulkln.c

RT3070驅動源碼存儲地址:\\192.168.1.48\share\測試項目\wifi

[結果]

出現CPU0錯誤

[期望]

2. BUG跟蹤過程

2.1 BUG現場分析

首先我們查看現場的輸出信息與錯誤LOG,查看文件minicom.cap:

------------------------------------------------------------

Client connecting to 192.168.50.101, TCP port 5001

TCP window size: 24.6 KByte (default)

------------------------------------------------------------

[  5] local 192.168.50.102 port 45158 connected with 192.168.50.101 port 5001

[ ID] Interval       Transfer     Bandwidth

[  5]  0.0- 1.0 sec  1.25 MBytes  10.5 Mbits/sec

[  5]  1.0- 2.0 sec  1.25 MBytes  10.5 Mbits/sec

[  5] 61578.0-61579.0 sec  1.25 MBytes  10.5 Mbits/sec

[  5] 61579.0-61580.0 sec  1.25 MBytes  10.5 Mbits/sec

CPU 0 Unable to handle kernel paging request at virtual address 0000000c, epc == c0a122a0, ra == c0a122a0

Oops[#1]:

Cpu 0

$ 0   : 00000000 1000bc00 00000004 0e9e8000

$ 4   : 00000000 00006000 00408000 ae9e8000

$ 8   : 00000000 8fc04800 8fbf8c80 ac4ce000

$12   : 52441080 00000614 37363534 00004a47

$16   : c00e3bb0 00000000 00000002 c00e7000

$20   : 8022e560 8022e9b0 80740000 00015650

$24   : 00000000 c0a121b8                  

$28   : 8e872000 8e873ac0 2aacd9b8 c0a122a0

Hi    : 000005e4

Lo    : 00000000

epc   : c0a122a0 DoBulkIn+0xe8/0x1b0 [rt3070sta]     Not tainted

ra    : c0a122a0 DoBulkIn+0xe8/0x1b0 [rt3070sta]

Status: 1000bc03    KERNEL EXL IE 

Cause : 10800008

BadVA : 0000000c

PrId  : 00004220

Modules linked in: rt3070sta

Process iperf (pid: 306, threadinfo=8e872000, task=81284ca0)

Stack : fffffffe 80740000 80740000 00015650 00000000 80740000 80740000 8071e9e0

        fffffffe 80740000 8022f044 8022f044 ffffffff 807734d0 806b0000 00015650

        00000000 80746064 00000001 80747b30 00000009 8022e7f8 8fa323c0 33323130

        00000000 c09ffc58 1000bc01 8fa323c0 00000000 8e961480 8e961494 fffffff0

        806b0000 8022e92c 8053315c 8053315c c0a130f4 39383736 80740000 8022ea60

        ...

Call Trace:

[<c0a122a0>] DoBulkIn+0xe8/0x1b0 [rt3070sta]

[<8022f044>] tasklet_hi_action+0xc0/0x18c

[<8022e7f8>] __do_softirq+0x8c/0x134

[<8022e92c>] do_softirq+0x8c/0xb8

[<8022ea60>] local_bh_enable+0xb0/0xd0

[<80521448>] dev_queue_xmit+0x1b4/0x31c

[<8054996c>] ip_output+0x1c8/0x410

[<805485d0>] ip_queue_xmit+0x4dc/0x648

[<8055ea38>] tcp_transmit_skb+0x5bc/0x9d8

[<80560a70>] tcp_push_one+0x100/0x16c

[<80552718>] tcp_sendmsg+0x6f0/0xe7c

[<805114bc>] sock_aio_write+0x100/0x128

[<8028a09c>] do_sync_write+0xd8/0x158

[<8028a280>] vfs_write+0x164/0x178

[<8028a388>] sys_write+0x54/0xa0

[<8020b420>] stack_done+0x20/0x3c

Code: 24420dd0  0040f809  02202821 <8e24000c> 3c02804d  2442b4b8  0040f809  24050020  14400012 

Kernel panic - not syncing: Fatal exception in interrupt

我們把重點放在分析CPU0錯誤的打印信息:

CPU 0無法處理的內核虛擬地址0000000c的分頁請求,其中異常程序計數寄存器EPC的值爲c0a122a0,返回地址ra爲c0a122a0。

首先看看EPC的值的作用:EPC寄存器用於指向異常發生時指令跳轉前的執行位置,一般是受害指令地址。異常發生時,程序計數器(PC)的高31位有效位被記錄在EPC寄存器的高31位字段中,所以這裏異常發生的32位指令地址是60507150,但是由於驅動指令模塊化,我們並不能根據這個地址在反彙編文件中找到對應的指令,所以在這個例子裏關鍵還是得根據其他打印信息來定位發生異常的指令位置。

接着下邊系統打印了系統的堆棧信息,然後是系統的Call Trace調試信息,在Linux環境下開啓系統的Call Trace調試,則Call Trace會在程序出現異常的時候把當前的函數的調用棧打印出來,調用順序是由下往上。

直接看第一條信息:[<c0a122a0>] DoBulkIn+0xe8/0x1b0 [rt3070sta],寄存器值爲c0a122a0,同時提示在rt3070sta模塊內的DoBulkIn函數內+偏移地址0xe8位置,前邊已經提到在這裏我們無法根據epc的值來跟蹤到異常指令位置,但是在rt3070sta.ko的反彙編文件rt1.asm可以跟蹤到DoBulkIn函數的指令段,所以先去查找rt1.asm中的DoBulkIn函數:

0007c1b8 <DoBulkIn>:

   7c1b8: 27bdffd0  addiu sp,sp,-48

   7c1bc: 3c020000  lui v0,0x0

   7c1c0: afb40020  sw s4,32(sp)

   7c360: 03e00008  jr ra

   7c364: 27bd0030  addiu sp,sp,48

其中DoBulkIn函數的起始地址是0x0007c1b8,那麼DoBulkIn+0xe8的值就是0x0007c1b8+0xe8=0x0007c2a0,查看rt1.asm文件中的0x0007c2a0位置的指令爲:

   7c2a0:8e24000c lwa0,12(s1)

這裏的指令操作是將寄存器s1作爲基地址,基地址+偏移地址12=0x0c所指的一個字的數據讀取到寄存器a0中,而我們觀察到一開始CPU 0提示無法處理的內核虛擬地址0000000c的分頁請求,也就是這裏s1+0x0c=0x0000000c,可以算出s1=0。到這裏就很明顯看出是s1的地址出現異常了。

2.2 代碼跟蹤

已經發現異常所在的指令了,那麼如何在程序代碼裏邊追蹤到對應的位置呢?關鍵在於找出程序代碼與彙編指令之間的聯繫。使用代碼查看工具,建立rt3070sta源代碼工程,並跳轉到DoBulkIn函數入口,與rt1.asm中的DoBulkIn函數指令段一起分析:

VOID DoBulkIn(IN RTMP_ADAPTER *pAd)         //函數頭

{

PRX_CONTEXT pRxContext;

PURB pUrb;

int ret = 0;

unsigned long IrqFlags;

RTMP_IRQ_LOCK(&pAd->BulkInLock, IrqFlags);//子函數

首先函數頭傳進來一個結構體指針,而在MIPS體系結構中,MIPS會通過a0 ~ a3這四個寄存器傳參,第一參數傳給寄存器a0,依次類推,那麼這裏的結構體基地址pAd便傳給寄存器a0,然後我們分析一下彙編指令:

0007c1b8 <DoBulkIn>:             //函數入口

   7c1b8: 27bdffd0  addiu sp,sp,-48

   7c1bc: 3c020000  lui v0,0x0

   7c1c0: afb40020  sw s4,32(sp)

   7c1c4: afb20018  sw s2,24(sp)

   7c1c8: afb10014  sw s1,20(sp)

   7c1cc: afb00010  sw s0,16(sp) //以上操作均爲將可能用到的寄存器值保存進堆棧

   7c1d0: 24540000  addiu s4,v0,0

   7c1d4:00808021 moves0,a0  //注意這裏用到a0,開始傳參,將a0傳給s0,也就是現在結構體基地址pAd便傳給寄存器s0了

   7c1d8: afbf0028  sw ra,40(sp)

   7c1dc: afb50024  sw s5,36(sp)

   7c1e0:0280f809 jalrs4     //注意這是跳轉指令,這裏標誌着進入新的函數 

   7c1e4: afb3001c  sw s3,28(sp)

分析到這裏,我們將代碼與指令聯繫起來,通過標記爲紅色字體的語句,能很容易看出與寄存器s0相關的操作就可能是對結構體相關成員的操作,而7c1e0處的跳轉指令,則是代碼中第一個子函數的使用。這裏說明一點,根據彙編指令中的跳轉指令,可以較容易的定位到彙編指令操作對應的程序代碼範圍,接下來分析下一個重要位置:

   7c1e8: 92020a19  lbu v0,2585(s0)

   7c1ec: 92030a18  lbu v1,2584(s0)

這裏是與寄存器s0相關的操作,即將s0+偏移地址2585(2584)所指的一個無符號字節加載到v0(v1),上邊我們知道s0存儲的是結構體的基地址pAd,也就是這兩句指令是與pAd相關的操作,而且被操作的結構體的成員偏移量相差1,再看回程序代碼段:

RTMP_IRQ_LOCK(&pAd->BulkInLock, IrqFlags);     //子函數

pRxContext = &(pAd->RxContext[pAd->NextRxBulkInIndex]);

if ((pAd->PendingRx > 0) || (pRxContext->Readable == TRUE) || (pRxContext->InUse == TRUE))

{

RTMP_IRQ_UNLOCK(&pAd->BulkInLock, IrqFlags);

return;

}

在子函數之後對pAd的操作分別爲RxContext以及PendingRx成員,查看RxContext與PendingRx定義位置:

#ifdef RTMP_MAC_USB

RX_CONTEXT RxContext[RX_RING_SIZE]; /* 1 for redundant multiple IRP bulk in. */

NDIS_SPIN_LOCK BulkInLock; /* BulkIn spinlock for 4 ACs */

UCHAR PendingRx;

發現RxContext以及PendingRx的定義並不是連續的,中間隔了一個NDIS_SPIN_LOCK定義,那與前邊的看到的偏移量相差1並不相符,是不是找錯成員了呢?其實不然,因爲NDIS_SPIN_LOCK是一個空定義,所以偏移量剛好相差1,所以可以定位到,pAd偏移2584即爲成員PendingRx,pAd偏移2585即爲操作成員RxContext,從接下來的彙編指令可以得到驗證:

   7c1e8: 92020a19  lbu v0,2585(s0)

   7c1ec:92030a18 lbuv1,2584(s0)   //v1爲pAd的成員PendingRx

   7c1f0: 24040001  li a0,1

   7c1f4: 00021140  sll v0,v0,0x5

   7c1f8: 00509021  addu s2,v0,s0

   7c1fc:14600009 bnezv1,7c224 <DoBulkIn+0x6c>//判斷v1是否爲0,如果不等於0則跳轉到7c224

觀察上邊標記爲藍色字體的彙編指令和程序代碼,可以看出其內在聯繫,可能會有疑問,爲什麼去判斷v1不等於0,但是代碼裏邊是去判斷pAd->PendingRx > 0,原因是UCHAR PendingRx,這是一個unsigned char,判斷其不等於0與判斷其大於0是等價的。

那麼彙編指令中接下來的幾個判斷指令均是程序代碼中if語句中的條件判斷:

   7c1fc: 14600009  bnez v1,7c224 <DoBulkIn+0x6c>

   7c208: 10440006  beq v0,a0,7c224 <DoBulkIn+0x6c>

   7c21c: 1444000c  bne v0,a0,7c250 <DoBulkIn+0x98>

剛好是三個條件判斷,與程序代碼中if三個條件判斷相符,直接跳轉到7c250,這時定位到這段條件語句之後,然後把到7c2a0之前的所有跳轉指令列出來如下:

   7c250: a2240017  sb a0,23(s1)

   7c254: a2240015  sb a0,21(s1)

   …

   7c26c: 02a0f809  jalr s5

   …

   7c284: 0040f809  jalr v0

   …

   7c298: 0040f809  jalr v0

   …

有三個跳轉指令,說明之間經過了三次函數調用,在條件語句之後的程序代碼中找出三次函數調用如下:

pRxContext->InUse = TRUE;

pRxContext->IRPPending = TRUE;

pAd->PendingRx++;

pAd->BulkInReq++;

RTMP_IRQ_UNLOCK(&pAd->BulkInLock, IrqFlags);

/* Init Rx context descriptor*/

NdisZeroMemory(pRxContext->TransferBuffer, pRxContext->BulkInOffset);

RTUSBInitRxDesc(pAd, pRxContext);

pUrb = pRxContext->pUrb;

這樣就定位到最後紅色字體標記的程序語句了,我們分析一下彙編指令的操作:

   7c29c: 02202821  move a1,s1

   7c2a0:8e24000c lwa0,12(s1)

按照以上指令分析,即使將寄存器s1+偏移地址0x12指向的一個字的數據加載到寄存器a0中,配合程序代碼語句分析,即寄存器a0的值對應是pUrb,而寄存器s1的值對應是pRxContext,而pUrb在pRxContext中的偏移量是0x12。

需要再加驗證一下,列出在DoBulkIn指令段裏邊與s1相關的幾句彙編指令:

0007c1b8 <DoBulkIn>:

   …

   7c1f0: 24040001  li a0,1

   …

   7c250:a2240017 sba0,23(s1)

   7c254:a2240015 sba0,21(s1)

   …

   7c2a0: 8e24000c  lw a0,12(s1)

對比程序代碼段,可以發現與以下代碼相匹配:

pRxContext->InUse = TRUE;

pRxContext->IRPPending = TRUE;

所以寄存器s1的值對應的是pRxContext,再結合7c2a0這句彙編指令,可以證明其作用是將pRxContext中的對象pUrb(偏移值爲0x0c)的值賦給pUrb。

到此,根據彙編指令跟蹤到程序代碼段裏邊的具體語句已經完成。系統在執行pUrb = pRxContext->pUrb賦值時出現異常,然後打印出CPU0錯誤信息,程序退出。

3. BUG原因分析

CPU 0無法處理的內核虛擬地址0000000c的分頁請求,而對應出錯位置的彙編指令爲:

7c2a0: 8e24000c  lw a0,12(s1)

對應出錯位置的程序語句爲:

pUrb = pRxContext->pUrb;

有以下分析:

系統在執行這條賦值語句時,需要取到結構體指針pRxContext的基地址,然後加上pUrb在該結構體中偏移地址,得到pRxContext->pUrb的地址,再進行賦值,通過彙編指令,表明pUrb在該結構體中偏移地址爲0x0c,所以此時結構體指針pRxContext的基地址爲NULL,這是非法的,所以這個BUG出現的原因總結爲:pRxContext被賦NULL導致。

那麼會有什麼原因導致pRxContext被賦NULL導致呢?

分析以下幾種可能:

第一種可能爲執行這個語句之前的RTUSBInitRxDesc(pAd, pRxContext)函數調用導致的堆棧溢出;但是由於該函數裏面只是初始化了一個URB接收描述符,這個調用過程耗用的堆棧極少,所以可能性比較低,可以通過在每次進入DoBulkIn()函數之前查看堆棧位置看有沒有接近棧底來進行驗證。

第二種可能爲內存改寫;死機時間發生在中斷下半部,如果有內存改寫只可能是中斷程序改寫或者DMA改寫。如果是DMA改寫應該是改寫一塊區域,在DoBulkIn()增加Magic看是否被改寫。

試圖重現該BUG,但是雖然有其他CPU0錯誤的出現,並沒有再出現類似同樣出現在DoBulkIn()函數裏的錯誤,所以暫時無法驗證問題所在原因。

4. 方法總結 

前邊以RT3070驅動錯誤BUG爲例,講述了根據反彙編文件來定位跟蹤到程序代碼中具體某一行代碼出現異常的方法,主要需要掌握以下幾點:

1. 及時保存現場,包括調試打印信息以及內核相關文件,最好是能夠現場調試跟蹤,因爲有些BUGS可能較難重現;

2. 根據Call Trace調試信息跟蹤到內核相關文件的反彙編文件;

3. 結合CPU0打印信息及跟蹤到異常位置的彙編指令分析出現問題的可能原因;

4. 通過反彙編文件與程序代碼對比分析,抓住主要跳轉指令、判斷指令以及某些重點參數與寄存器之間的關係,從而定位到某一行代碼語句;

本文檔的目的也僅僅在於講述一種跟蹤Loongson1B開發板上CPU0錯誤到具體程序代碼的方法,而解決BUGS的重點最終還是對於定位到異常之後的分析過程,這個往往需要具備調試經驗及對系統的深入瞭解。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章