日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

教你如何找到导致程序跑飞的指令

發(fā)布時(shí)間:2025/4/5 编程问答 12 豆豆
生活随笔 收集整理的這篇文章主要介紹了 教你如何找到导致程序跑飞的指令 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

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

?

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

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

?

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

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

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

?

接下來我們?cè)傧攘私庖幌孪嚓P(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的中斷向量表比較簡(jiǎn)單,只有7種中斷,它把所有正常的中斷都放到了SWI、IRQ和FIQ中了,那么本文所介紹的異常情況將會(huì)觸發(fā)Undef、PAbt或者DAbt異常中斷,至于是哪種就需要看具體的原因了。

指令A(yù)????//觸發(fā)異常

指令B

比如說當(dāng)指令A(yù)無法執(zhí)行時(shí),它就會(huì)觸發(fā)異常中斷,硬件就會(huì)自動(dòng)將這條指令后面的指令的所在地址,也就是指令B的地址保存到LR寄存器中,然后就跳轉(zhuǎn)到與這種異常相關(guān)的中斷向量表中,假如指令A(yù)觸發(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??}

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

然后,為了方便,我們?cè)谥袛嘞蛄勘砝锇焉厦娴?個(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í)際情況也許不允許我們這么 做,這樣的話就需要你有比較高的匯編語(yǔ)言水平了,這不在本文討論之內(nèi),先不管了。我們?cè)谶@個(gè)例子里將編譯選項(xiàng)設(shè)置為不優(yōu)化。

?

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



1

從圖1可以看到程序停在了42行,這與我們的設(shè)計(jì)是一致的。在圖1的左側(cè)顯示了此時(shí)各個(gè)寄存器內(nèi)的數(shù)值,注意到LR寄存器了吧,這里保存的就是返回地址,出錯(cuò)的指令就在這附近。但,還有一點(diǎn)需要注意,FaultIsr函數(shù)是C語(yǔ)言函數(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í)的值了。

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

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



圖2

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



圖3

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

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

圖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è)跑飛的問題也就找到原因了,是不是很簡(jiǎn)單?

?

當(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)備在“底層工作者手冊(cè)之嵌入式操作系統(tǒng)內(nèi)核”6.1節(jié)實(shí)例講解一個(gè)這種情況(現(xiàn)在是2012.02.28,手冊(cè)暫時(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ù)開始的代碼一定要用匯編語(yǔ)言來寫,以防止調(diào)用FaultIsr函數(shù)時(shí)的寄存器、堆棧信息被C語(yǔ)言破壞。

如果我們的單板有這樣的功能,那么當(dāng)單板跑死時(shí),一般情況都會(huì)向外打印信息,比如上面的例子,就會(huì)打印出LR的值為0x805ec。但我們似乎又遇到了一個(gè)問題,我們?nèi)绾沃?x805ec這個(gè)地址是哪個(gè)函數(shù)的?別忘了,我們?cè)谝粋€(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地址,如下圖所示:

圖5

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

?

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

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

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

6

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

7

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

8

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

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

這里面有2點(diǎn)說明一下,一是cortex內(nèi)核支持雙堆棧,如果使用雙堆棧的話會(huì)復(fù)雜一點(diǎn),這里為了簡(jiǎn)單的說明問題,我們只使用了其中的一個(gè)MSP,另外一個(gè)PSP沒有使用,在這個(gè)例子里你只需要認(rèn)為只有一個(gè)SP就可以了。另外一點(diǎn)是0x1669這個(gè)地址其實(shí)就是0x1668,因?yàn)閏ortex內(nèi)核采用的是Thumb2指令集,該指令集要求指令的最后一個(gè)bit為1,因此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í)打印的一小段信息,從這段信息里我們可以看到SP(R13)的數(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:?0x00000536

0x2000126C:?0x00001669

0x20001268:?0x00000000

0x20001264:?0x0000008C

0x20001260:?0x00000000

0x2000125C:?0xE000ED04

0x20001258:?0x00000834

總結(jié)

以上是生活随笔為你收集整理的教你如何找到导致程序跑飞的指令的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。