一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

教你如何找到導(dǎo)致程序跑飛的指令

 敗敗0619 2012-07-23

調(diào)試嵌入式程序時(shí),你是否遇到過程序跑飛最終導(dǎo)致硬件異常中斷的問題?遇到這種問題是否感覺比較難定位?不知道問題出在哪里,沒有辦法跟蹤?尤其是當(dāng)別人的程序踩了自己的內(nèi)存,那就只能哭了:(

 

今天在論壇上看有同學(xué)求助這種問題,正好我還算有一點(diǎn)辦法,就和大家分享一下。

解決辦法非常非常簡單,本文將以Aduc7026ARM7內(nèi)核)和LM3S8962cortex內(nèi)核,STM32也是cortex內(nèi)核,同理)為例,講講解如何定位此種問題。

 

先說ARM7內(nèi)核,cortex內(nèi)核稍微有一點(diǎn)復(fù)雜,后面再說。

ARM7內(nèi)核有多種工作模式,每種模式下有R0~R15以及CPSR17個(gè)寄存器可以使用,有關(guān)這些寄存器的細(xì)節(jié)我就不詳細(xì)介紹了,詳細(xì)的介紹請(qǐng)參考“底層工作者手冊之嵌入式操作系統(tǒng)內(nèi)核”中的2.2~2.3節(jié),這里只介紹與本文相關(guān)的寄存器。

其中R14又叫做LR寄存器,它被用來保存函數(shù)、中斷調(diào)用時(shí)的返回地址,看到了吧,它保存了“返回地址”!這不就是我們需要的么?就這么簡單,發(fā)生異常中斷時(shí),LR寄存器中保存的地址附近就會(huì)有導(dǎo)致異常的指令。

 

接下來我們再先了解一下相關(guān)的知識(shí),然后再通過一個(gè)例子構(gòu)造一個(gè)指令異常,然后再反推找到產(chǎn)生異常的這條指令,做一個(gè)實(shí)例演練!

當(dāng)程序跑飛時(shí),絕大部分情況都會(huì)觸發(fā)硬件異常中斷,硬件異常中斷的中斷服務(wù)函數(shù)在中斷向量表中有定義,我們來看看ARM7的中斷向量表,在keil開發(fā)環(huán)境里(以下例子是在keil環(huán)境下介紹的),這個(gè)文件一般叫startup.s,如下:

Vectors:        LDR     PC, Reset_Addr

                LDR     PC, Undef_Addr

                LDR     PC, SWI_Addr

                LDR     PC, PAbt_Addr

                LDR     PC, DAbt_Addr

                NOP                            

                LDR     PC, IRQ_Addr

                LDR     PC, FIQ_Addr

 

Reset_Addr:     .word   Reset_Handler

Undef_Addr:     .word   ADI_UNDEF_Interrupt_Setup

SWI_Addr:       .word   ADI_SWI_Interrupt_Setup

PAbt_Addr:      .word   ADI_PABORT_Interrupt_Setup

DAbt_Addr:      .word   ADI_DABORT_Interrupt_Setup

IRQ_Addr:       .word   ADI_IRQ_Interrupt_Setup

FIQ_Addr:       .word   ADI_FIQ_Interrupt_Setup

ARM7的中斷向量表比較簡單,只有7種中斷,它把所有正常的中斷都放到了SWI、IRQFIQ中了,那么本文所介紹的異常情況將會(huì)觸發(fā)UndefPAbt或者DAbt異常中斷,至于是哪種就需要看具體的原因了。

指令   //觸發(fā)異常

指令B

比如說當(dāng)指令A無法執(zhí)行時(shí),它就會(huì)觸發(fā)異常中斷,硬件就會(huì)自動(dòng)將這條指令后面的指令的所在地址,也就是指令B的地址保存到LR寄存器中,然后就跳轉(zhuǎn)到與這種異常相關(guān)的中斷向量表中,假如指令A觸發(fā)了Undef異常中斷,那么硬件就會(huì)跳轉(zhuǎn)到中斷向量表的第二個(gè)中斷向量Undef_Addr,從中斷向量表可知,這個(gè)中斷向量對(duì)應(yīng)的中斷服務(wù)函數(shù)就是ADI_UNDEF_Interrupt_Setup,這個(gè)函數(shù)一般是一個(gè)死循環(huán),這樣單板就死了,當(dāng)我們停下程序時(shí),就會(huì)發(fā)現(xiàn)程序停在了這個(gè)函數(shù)里面。

我們來看下面這個(gè)實(shí)例,我把定位過程的每一步都記錄下來,一起來看下:

14  S32 main(void)

15  {

16      U8* pucAddr;

17  

18      

19      DEV_HardwareInit();

20  

21      

22      WLX_TaskInit(1, TEST_TestTask1, TEST_GetTaskInitSp(1));

23      WLX_TaskInit(2, TEST_TestTask2, TEST_GetTaskInitSp(2));

24  

25      

26      pucAddr (U8*)0;

27      *pucAddr 0;

28      

29  

30  

31      

32      WLX_TaskStart();

33  

34      return 0;

35  }

上面這段測試代碼是我在我寫的一個(gè)小型嵌入式操作系統(tǒng)上改的(有興趣的話可以訪問我的博客O(_)O),只需要關(guān)注2627行即可,其余的只是陪襯,以使這段程序看起來稍微復(fù)雜一些。這兩行指令將0地址清0,0地址是中斷向量表,向這個(gè)地址寫數(shù)據(jù)會(huì)導(dǎo)致異常的,但——這正是我們所需要的。

然后,為了方便,我們在中斷向量表里把上面的3個(gè)異常中斷向量都修改一下,如下:

Vectors:        LDR     PC, Reset_Addr

                LDR     PC, FaultIsr

                LDR     PC, SWI_Addr

                LDR     PC, FaultIsr

                LDR     PC, FaultIsr

                NOP                            

                LDR     PC, IRQ_Addr

                LDR     PC, FIQ_Addr

這樣,只要發(fā)生異常中斷就都會(huì)進(jìn)入FaultIsr函數(shù),FaultIsr函數(shù)如下:

void FaultIsr()

{

    while(1)

   {

        ;

   }

}

可以看到FaultIsr函數(shù)是個(gè)死循環(huán),所以當(dāng)程序發(fā)生異常跑飛時(shí)就會(huì)死在這里了。

 

準(zhǔn)備工作完成,準(zhǔn)備實(shí)戰(zhàn)演練!在這之前還有一點(diǎn)需要注意,那就是最好將編譯選項(xiàng)設(shè)置為不優(yōu)化,這樣方便我們定位問題。當(dāng)然,實(shí)際情況也許不允許我們這么做,這樣的話就需要你有比較高的匯編語言水平了,這不在本文討論之內(nèi),先不管了。我們在這個(gè)例子里將編譯選項(xiàng)設(shè)置為不優(yōu)化。

 

我們將上面改動(dòng)后的代碼重新編譯,然后加載到單板里,進(jìn)入仿真狀態(tài),然后全速運(yùn)行,然后再停止運(yùn)行,我們就可以發(fā)現(xiàn)程序死在FaultIsr函數(shù)里了,如下圖所示:

教你如何找到導(dǎo)致程序跑飛的指令

1

從圖1可以看到程序停在了42行,這與我們的設(shè)計(jì)是一致的。在圖1的左側(cè)顯示了此時(shí)各個(gè)寄存器內(nèi)的數(shù)值,注意到LR寄存器了吧,這里保存的就是返回地址,出錯(cuò)的指令就在這附近。但,還有一點(diǎn)需要注意,FaultIsr函數(shù)是C語言函數(shù),它運(yùn)行時(shí)可能會(huì)修改LR寄存器,如果是這樣的話,那么此時(shí)LR寄存器內(nèi)的數(shù)值就不是發(fā)生異常時(shí)的值了,為解決此問題,我們可以找到FaultIsr函數(shù)的起始地址,將斷點(diǎn)打在FaultIsr函數(shù)的起始地址,這樣當(dāng)異常發(fā)生時(shí)就會(huì)停在斷點(diǎn)的地方,也就是FaultIsr函數(shù)的起始地址,這樣就可以保證LR寄存器的值就是發(fā)生異常時(shí)的值了。

如果你的匯編語言足夠好,那么你可以在圖1右上角的匯編窗口里向上找,找到FaultIsr函數(shù)的起始地址。另外,我們還可以通過一個(gè)簡單的方法找到FaultIsr函數(shù)的起始地址。我們在keil的選項(xiàng)中選擇生成map文件,代碼編譯后就會(huì)生成一個(gè)map文件,我們可以從這個(gè)文件里找到FaultIsr函數(shù)的地址。

使用一個(gè)文本編輯器打開這個(gè)map文件,然后搜索“FaultIsr”,如下圖,我們就找到了FaultIsr函數(shù)的起始地址:0x80608。

教你如何找到導(dǎo)致程序跑飛的指令

2

在匯編窗口找到0x80608的地址,打上斷點(diǎn),如下圖所示:

教你如何找到導(dǎo)致程序跑飛的指令

3

復(fù)位程序,再重新全速跑一遍,我們就會(huì)發(fā)現(xiàn)程序停在了斷點(diǎn)上,這時(shí)LR里面的數(shù)值就是程序異常時(shí)存入的返回地址,通過這個(gè)地址差不多就可以找到出錯(cuò)的指令了。

如圖3所示,LR的值為0x805ec,我們在匯編窗口里跳到這個(gè)地址,如下圖所示:

教你如何找到導(dǎo)致程序跑飛的指令

4

ARM7內(nèi)核有2級(jí)流水線,存入LR的地址一般會(huì)多+8個(gè)字節(jié),因此0x805ec-8=0x805e4,如圖4所示,0x805e4地址是一條STRB R2,[R3]指令,這條指令的意思是將R2寄存器里的數(shù)值保存到R3寄存器所指向的地址(一個(gè)字節(jié))內(nèi)。從圖3左側(cè)可以看到R2寄存器的數(shù)值為0,R3寄存器的數(shù)值也為0,那么這條指令的意思就是將0這個(gè)數(shù)值寫入0地址這個(gè)字節(jié)內(nèi),這不是正好對(duì)應(yīng)上述main函數(shù)中27行的C指令么?

看到這里我們就應(yīng)該明白了,向0地址寫0,這條C指令有問題,那么這個(gè)跑飛的問題也就找到原因了,是不是很簡單?

 

當(dāng)然,實(shí)際情況可能要比上述介紹的情況復(fù)雜的多。實(shí)際使用的程序幾乎都是經(jīng)過優(yōu)化的,這樣從匯編指令找到C指令就會(huì)比較麻煩。還有可能FaultIsr函數(shù)的指令或者堆棧被破壞了,那么FaultIsr函數(shù)運(yùn)行都會(huì)出問題。還有可能出錯(cuò)的指令不會(huì)象27行這么明顯,可能是經(jīng)過了前面很多步驟的積累才在這里觸發(fā)異常的,最典型的就是別人的程序踩了你的內(nèi)存,結(jié)果錯(cuò)誤在你的程序里表現(xiàn)出來了,如果遇到這種情況你就先哭一頓吧。對(duì)于這種踩內(nèi)存的情況也是可以通過這種方法定位的,但這相當(dāng)復(fù)雜,需要從出錯(cuò)點(diǎn)開始到觸發(fā)異常點(diǎn)為止,這之間所有的堆棧信息,然后從最后的堆棧開始,結(jié)合反匯編的代碼,從最后一條指令向前推,直到發(fā)現(xiàn)問題的根源。這種方法相當(dāng)于是我們用我們的大腦模擬CPU的反向運(yùn)行過程,如果程序是經(jīng)過優(yōu)化的,那么這個(gè)過程就更麻煩了。我準(zhǔn)備在“底層工作者手冊之嵌入式操作系統(tǒng)內(nèi)核”6.1節(jié)實(shí)例講解一個(gè)這種情況(現(xiàn)在是2012.02.28,手冊暫時(shí)只寫到了5.4節(jié))。

 

好了,先不說這么復(fù)雜的了,接著上面的繼續(xù)說。

有時(shí)候出現(xiàn)問題的單板并不在我們手邊,問題也許不能復(fù)現(xiàn),那么我們就可以預(yù)先在FaultIsr函數(shù)里做一個(gè)打印功能——將出現(xiàn)異常時(shí)的寄存器、堆棧、軟件版本號(hào)等信息打印出來,編寫這樣的FaultIsr函數(shù)需要注意,FaultIsr函數(shù)開始的代碼一定要用匯編語言來寫,以防止調(diào)用FaultIsr函數(shù)時(shí)的寄存器、堆棧信息被C語言破壞。

如果我們的單板有這樣的功能,那么當(dāng)單板跑死時(shí),一般情況都會(huì)向外打印信息,比如上面的例子,就會(huì)打印出LR的值為0x805ec。但我們似乎又遇到了一個(gè)問題,我們?nèi)绾沃?/font>0x805ec這個(gè)地址是哪個(gè)函數(shù)的?別忘了,我們在一個(gè)版本發(fā)布時(shí)會(huì)將軟件所有的信息歸檔(什么?沒歸檔!這樣的公司我勸你還是走了吧),根據(jù)軟件版本號(hào)找到出問題的軟件的歸檔文件,取出map文件,利用上面講述的方法通過map文件我們就可以找到出問題的函數(shù)了。再通過軟件版本從歸檔文件中找到這個(gè)函數(shù)最終編譯鏈接生成的目標(biāo)文件,一般為.o、.axf、.elf等文件(必須是靜態(tài)鏈接的文件,需要有各種段信息的),不能是bin、hex等文件,windows、linux等動(dòng)態(tài)鏈接的文件已經(jīng)超出了我目前的知識(shí)范圍,也不再其中。

然后使用objdump程序進(jìn)行反匯編,將目標(biāo)文件與objdump程序放到同一個(gè)目錄,在cmd窗口下進(jìn)到這個(gè)目錄,執(zhí)行下面命令:

 

objdump -d wanlix.elf >> uncode.txt

 

這行命令的意思是將wanlix.elf目標(biāo)程序進(jìn)行反匯編,反匯編的結(jié)果以文本格式存入uncode.txt文本文件。

我們用文本編輯器打開uncode.txt文件,找到0x805ec地址,如下圖所示:

教你如何找到導(dǎo)致程序跑飛的指令

5

如圖5所示,我們可以看到0x805ec這個(gè)地址位于main函數(shù)內(nèi),我們再對(duì)比一下圖5和圖4中的指令,可以發(fā)現(xiàn)它們是相同的,可能寫法上會(huì)有一些差異,但功能是相同的。

 

好了,ARM7內(nèi)核的介紹到此結(jié)束,下面介紹cortex內(nèi)核的,使用STSTM32、TILM3S系列的同學(xué)們注意了,它們都是cortex內(nèi)核的,下面的介紹你也許用得上。

Cortex內(nèi)核與ARM7內(nèi)核定位此種問題的思路完全是一樣的,cortex內(nèi)核的詳細(xì)介紹請(qǐng)參考“底層工作者手冊之嵌入式操作系統(tǒng)內(nèi)核”中的5.1節(jié)。cortex內(nèi)核有一些特殊,它在產(chǎn)生中斷時(shí)會(huì)先將R0~R3、R12LRPC以及XPSR8個(gè)寄存器壓入當(dāng)前的堆棧,然后才跳轉(zhuǎn)到中斷向量表執(zhí)行中斷服務(wù)程序,此時(shí)LR中保存的不是返回地址,而是返回時(shí)所使用的芯片模式和堆棧寄存器的標(biāo)示,只能是0xFFFFFFF1、0xFFFFFFF9或者是0xFFFFFFFD3個(gè)值中的一個(gè),如果你還認(rèn)為LR中保存的是返回地址,并且是這么奇特的地址,估計(jì)你一定會(huì)暈了。

要找cortex內(nèi)核芯片的返回地址就需要到棧中去找,前面不是說了么,進(jìn)入中斷前硬件會(huì)自動(dòng)向當(dāng)前棧壓入8個(gè)寄存器,如下圖示:

教你如何找到導(dǎo)致程序跑飛的指令

6

如果你看了2.3節(jié)和5.1節(jié)就應(yīng)該知道cortexARM7內(nèi)核都是一種遞減滿棧,意思是說壓棧時(shí)棧指針向低地址移動(dòng),棧指針指向最后壓入的數(shù)據(jù)。SPR13)寄存器就是棧寄存器,它里面保存的就是當(dāng)前的棧指針,因此當(dāng)cortex內(nèi)核發(fā)生中斷時(shí),我們就可以根據(jù)SP指針來找到壓入上述8個(gè)寄存器的地址,然后找到LR的位置,再從LR中找到返回地址,下面的這個(gè)例子是“底層工作者手冊之嵌入式操作系統(tǒng)內(nèi)核”中的6.1節(jié)的一個(gè)例子,

void TEST_TestTask1(void)

{

    while(1)

    {

        DEV_PutStrToMem((U8*)"\r\nTask1 is running! Tick is: %d",

                        MDS_SystemTickGet());

 

        DEV_DelayMs(1000);

 

        MDS_TaskDelay(250);

 

        if(MDS_SystemTickGet() >= 2000)

        {

            ADDRVAL(0xFFFFFFFF) 0;

        }

    }

}

    紅色字體部分會(huì)觸發(fā)一個(gè)異常,它會(huì)向0xFFFFFFFF這個(gè)地址寫入0,也會(huì)觸發(fā)一個(gè)異常中斷,觸發(fā)的異常會(huì)進(jìn)入MDS_FaultIsrContext異常中斷服務(wù)函數(shù),在MDS_FaultIsrContext函數(shù)的入口地址打上斷點(diǎn),運(yùn)行此程序,觸發(fā)異常后如下圖:

教你如何找到導(dǎo)致程序跑飛的指令

7

    從圖7左上側(cè)窗口可以看到SP的值為0x20001258,那么我們在右下角的窗口找到0x20001258這塊內(nèi)存的地址,從0x20001258開始,每4個(gè)字節(jié)對(duì)應(yīng)一個(gè)寄存器,依次為R0、R1、R2R3、R12、LR、PC、XPSR,其中紅框的位置就對(duì)應(yīng)著LR,從圖中可以看到LR的值為0x1669,我們找到這個(gè)版本編譯后的目標(biāo)文件,使用objdump軟件反匯編,如下圖所示:

教你如何找到導(dǎo)致程序跑飛的指令

8

可以看到0x1669這個(gè)地址位于TEST_TestTask1函數(shù)里,與我們設(shè)計(jì)的一致。

這段代碼是經(jīng)過O2優(yōu)化的,匯編指令對(duì)照到C指令上會(huì)有些費(fèi)事,這里就不再講解了,知道方法就好,剩下的自己研究。

這里面有2點(diǎn)說明一下,一是cortex內(nèi)核支持雙堆棧,如果使用雙堆棧的話會(huì)復(fù)雜一點(diǎn),這里為了簡單的說明問題,我們只使用了其中的一個(gè)MSP,另外一個(gè)PSP沒有使用,在這個(gè)例子里你只需要認(rèn)為只有一個(gè)SP就可以了。另外一點(diǎn)是0x1669這個(gè)地址其實(shí)就是0x1668,因?yàn)?/font>cortex內(nèi)核采用的是Thumb2指令集,該指令集要求指令的最后一個(gè)bit1,因此0x1668就變成了0x1669。

 

上面介紹ARM7內(nèi)核的時(shí)候我不是說過如果在FaultIsr函數(shù)里做一個(gè)打印功能就可以通過打印信息來定位這種問題么,其實(shí)在介紹cortex內(nèi)核的這個(gè)例子中我就做了這個(gè)功能,具體的實(shí)現(xiàn)就先不介紹了,有興趣的同學(xué)可以看我6.1節(jié)的介紹(2012.02.28,目前book還沒寫到6.1節(jié)),下面是出現(xiàn)異常時(shí)打印的一小段信息,從這段信息里我們可以看到SPR13)的數(shù)值為0x20001258,與圖7的情況一樣,那么在棧中從0x20001258這個(gè)地址向上找,找到棧中保存LR的位置,它的數(shù)值就是0x1669,與圖7中的分析是一致的。

注意一點(diǎn)藍(lán)色字體的R14是我這段打印程序還原過的,因此它與內(nèi)存中的數(shù)值是一樣的。

 

R15 0x00000536 R14 0x00001669 R13 0x20001258 R12 0x00000000

R11 0x00000000 R10 0x00000000 R9  0x00000000 R8  0x00000000

R7  0x00000000 R6  0x000003E8 R5  0x000007D0 R4  0x00000000

R3  0x0000008C R2  0x00000000 R1  0xE000ED04 R0  0x00000834

XPSR= 0x21000000

0x20001274: 0x21000000

0x20001270:

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    中文字幕亚洲在线一区| 国产免费观看一区二区| 在线免费看国产精品黄片| 欧美偷拍一区二区三区四区| 精品国产丝袜一区二区| 日韩欧美第一页在线观看| 91香蕉视频精品在线看| 四季av一区二区播放| 最近中文字幕高清中文字幕无| 少妇熟女亚洲色图av天堂| 色狠狠一区二区三区香蕉蜜桃| 日韩免费av一区二区三区| 日韩视频在线观看成人| av免费视屏在线观看| 两性色午夜天堂免费视频| 亚洲欧美日产综合在线网| 欧美二区视频在线观看| 国产又粗又猛又爽又黄| 日本午夜福利视频免费观看| 国产精品夜色一区二区三区不卡| 成年人视频日本大香蕉久久| 伊人欧美一区二区三区| 美国女大兵激情豪放视频播放| 97人妻人人揉人人躁人人| 国产在线一区二区三区不卡 | 欧美熟妇喷浆一区二区| 成人日韩视频中文字幕| 精品视频一区二区不卡| 日韩精品视频高清在线观看| 日韩日韩日韩日韩在线| 97人妻人人揉人人躁人人| 搡老熟女老女人一区二区| 欧美午夜性刺激在线观看| 69精品一区二区蜜桃视频| 日本成人三级在线播放| 在线中文字幕亚洲欧美一区| 黄色激情视频中文字幕| 欧美精品一区二区三区白虎| 免费特黄一级一区二区三区| 国产一区二区三区口爆在线| 欧美中文字幕一区在线|