電子產(chǎn)業(yè)一站式賦能平臺

PCB聯(lián)盟網(wǎng)

搜索
查看: 37|回復(fù): 0
收起左側(cè)

難以復(fù)現(xiàn)Bug的分析方法:堆棧分析實(shí)戰(zhàn)

[復(fù)制鏈接]

491

主題

491

帖子

3107

積分

四級會員

Rank: 4

積分
3107
跳轉(zhuǎn)到指定樓層
樓主
發(fā)表于 2024-9-6 11:38:00 | 只看該作者 |只看大圖 回帖獎勵 |倒序?yàn)g覽 |閱讀模式

引言友情提示本文接近三千字,預(yù)計(jì)閱讀時間為10-15分鐘。建議您在空閑時細(xì)細(xì)閱讀,享受閱讀的樂趣。
難以復(fù)現(xiàn)的Bug之痛你是否曾為那些難以復(fù)現(xiàn)的Bug而頭疼不已?本文將揭秘一種通過堆棧分析來定位并解決這類問題的神奇方法。
作為一名開發(fā)人員,在開發(fā)過程中會碰到各式各樣的問題,如果能通過一些操作復(fù)現(xiàn)問題的,通過對目標(biāo)板進(jìn)行調(diào)試還能夠逐步分析。
但是,如果由于某些原因不能對目標(biāo)板進(jìn)行調(diào)試,這種情況分析可就比較復(fù)雜了。
這不,前一陣子就碰到了一個問題,在調(diào)試過程中,怎樣都無法復(fù)現(xiàn)問題。直到后來才發(fā)現(xiàn)一個現(xiàn)象,只有在對目標(biāo)板上電時才有一定幾率復(fù)現(xiàn)問題,即頻繁的對目標(biāo)板進(jìn)行斷電-上電測試。
降臣:“你難道不好奇你的功力是從何而來嘛?”
李星云:“不好奇”
降臣:“你難道不好奇為何換心之后 無法壓制這突增的內(nèi)力嗎?”
李星云:“不好奇”
降臣:“你難道不好奇為何你只能活半年?”
李星云:“不好奇”
降臣:“你好奇”
李星云:“不好奇”
降臣:“你好奇”
李星云:“不好奇”
如何對這種問題進(jìn)行分析,你難道不好奇嘛?
寄存器(SP、LR)詳解這里介紹兩個寄存器。SP(Stack Pointer)寄存器和LR(Link Register)寄存器是非常重要的寄存器。
SP寄存器:堆棧的指路明燈SP寄存器用于指向當(dāng)前堆棧的棧頂位置。在函數(shù)調(diào)用時,SP寄存器會調(diào)整以反映堆棧的變化,確保數(shù)據(jù)正確地存儲和取出。
LR寄存器:函數(shù)調(diào)用與異常處理的橋梁LR寄存器有兩個作用。
  • 函數(shù)調(diào)用:當(dāng)一個函數(shù)調(diào)用另一個函數(shù)時,LR寄存器保存當(dāng)前函數(shù)的返回地址。
  • 異常處理:當(dāng)發(fā)生異常時,LR寄存器保存異常處理程序的返回地址。[/ol]問題分析與解決流程揭秘保存現(xiàn)場在處理難以復(fù)現(xiàn)的Bug時,保存現(xiàn)場數(shù)據(jù)是至關(guān)重要的一步。這就像是在犯罪現(xiàn)場拍照留證一樣,能夠?yàn)槲覀兒罄m(xù)的分析提供寶貴的線索。
    當(dāng)上電出現(xiàn)死機(jī)問題后,插上Jlink,使用J-Link commander軟件連接目標(biāo)板,然后通過命令將芯片RAM中的區(qū)域保存成二進(jìn)制文件存放到電腦本地。
    如果應(yīng)用程序的版本號不確定,也可以將ROM中的程序保存到本地,操作方法同保存RAM,后面會說到如何保存。
    接下來,我們會分析RAM中的數(shù)據(jù)。
    幸運(yùn)的情況下,可以直接找到最后一次被調(diào)用的函數(shù),然后分析某個函數(shù)的功能,即可找到問題。而有的情況下就可能還需要根據(jù)函數(shù)的前后調(diào)用關(guān)系分析出問題所在。
    分析堆棧數(shù)據(jù)我之前也不知道如何分析堆棧數(shù)據(jù),第一次分析的時候就感覺進(jìn)了一個迷宮,繞著繞著就把自己繞進(jìn)去了。
    你可以想象一下,拿著一份二進(jìn)制文件,去分析函數(shù)的調(diào)用關(guān)系,想想就腦殼疼...
    分析堆棧時需要知道下面幾個知識點(diǎn),才能正確分析,我接下來會解釋一下。
    堆棧結(jié)構(gòu)堆棧結(jié)構(gòu)主要有四種類型,分別是滿遞減堆棧、滿遞增堆棧、空遞減堆棧和空遞增堆棧。
    遞增/遞減是指棧向高地址還是低地址增長,滿是指棧指針(SP)總是指向堆棧中的最后一個元素,即最后壓入的數(shù)據(jù)?帐侵笚V羔槪⊿P)總是指向下一個將要放入數(shù)據(jù)的空位置。
    常用的可能還是滿遞減堆棧比較多一點(diǎn)。
    入棧順序因?yàn)槲覀円治鰲V械臄?shù)據(jù),所以我們通過匯編查看依次有哪些數(shù)據(jù)入棧,然后分析出當(dāng)前的LR寄存器中的值。例如下方是一個入棧的匯編指令。我們需要知道入棧的順序,是從右往左入棧的還是從左往右入棧的。
  • 0x08000644 B570      PUSH     {r4-r6,lr}案例J-Link Commande工具首先需要安裝J-Link軟件,去官網(wǎng)https://www.segger.com/downloads/jlink/下載,這里是一個套件,安裝后會有若干個獨(dú)立的小軟件。
    我們需要使用的是J-Link Commande軟件。打開軟件之后,可以輸入?來查看支持的命令,如下圖:

    感興趣的可以研究這些命令,會對自己有所幫助的。
    分析;拘畔⑦@里需要分析的是棧屬于上面說的四種結(jié)構(gòu)的哪一種,以及數(shù)據(jù)入棧的順序是如何的。
    這里我們需要將ARM仿真器連接目標(biāo)板進(jìn)行調(diào)試,通過單步調(diào)試定位到PUSH匯編指令中,如下圖所示:

    當(dāng)未執(zhí)行 0x08000E0C B510 PUSH {r4,1r} 入棧操作時,當(dāng)前的棧指針SP指向0x20000658地址,并且該地址中值不為空。從而說明當(dāng)前的SP指向的是最后一個入棧的元素,即可判定為滿堆棧。
    隨后我們單步執(zhí)行,讓其執(zhí)行入棧操作,再來看堆棧中的數(shù)據(jù),如下圖所示:

    此時我們看到SP的地址為0x20000650,執(zhí)行了入棧操作,SP的地址減小了,從而判定堆棧的生長方向是遞減的。根據(jù)上述兩個判斷從而能得出堆棧結(jié)構(gòu)為滿遞減堆棧。
    接下來我們要判斷數(shù)據(jù)入棧的順序,這個匯編是將r4以及l(fā)r寄存器中的值入棧。分析入棧后的數(shù)據(jù)得知,第一個入棧的數(shù)據(jù)為0x08000DE1,剛好是lr寄存器中的值。我在圖片中也標(biāo)注了入棧數(shù)據(jù)對應(yīng)的寄存器。從而可以得出結(jié)論,入棧的順序是從右往左的,先入棧lr后入棧r4。
    保存RAM數(shù)據(jù)到本地需要執(zhí)行如下幾個步驟,即可連接到目標(biāo)板。
    當(dāng)系統(tǒng)死機(jī)后,需要將目標(biāo)板連接到ARM仿真器。
  • 使用管理員的身份打開J-Link Commande工具(否則后面保存數(shù)據(jù)會提示寫入文件失敗)[/ol]
  • connect命令準(zhǔn)備連接目標(biāo)板選擇目標(biāo)芯片型號選擇調(diào)試接口JTAGSWDcJTAG選擇接口速度連接成功提示
  • SaveBin命令保存棧數(shù)據(jù)[/ol]SaveBin c:/ram.bin 0x20000000 0x5000我這里是將整個RAM區(qū)域的數(shù)據(jù)保存起來了這里用到了一個SaveBin的命令,命令的原型如下:
    SaveBin  SaveBin , ,   Save target memory range into binary file.
    如下圖所示,是我連接目標(biāo)板到保存RAM數(shù)據(jù)的所有操作,此時C盤根目錄就會出現(xiàn)一個ram.bin的文件。

    分析棧數(shù)據(jù)當(dāng)我們使用jlink連接目標(biāo)板后,輸入命令h可以看到一些關(guān)鍵信息,如下圖:

    可以看到SP(R13)= 20000654, PC = 08000E1E, IPSR = 000 (NoException),那我們先去看pc指向的地址是屬于哪一個函數(shù)的。我們可以直接在匯編窗口中輸入地址然后直接跳轉(zhuǎn)過去,如下圖所示:

    通過定位得知,這個地址是在InitC函數(shù)內(nèi),如下圖:

    而且這個函數(shù)剛執(zhí)行的時候,執(zhí)行了一次PUSH操作入棧了三個元素,根據(jù)之前的分析,入棧的順序是從右往左,所以第一個入棧的數(shù)據(jù)就是LR,又因?yàn)楫?dāng)前的SP指針指向的地址為20000654,然后去查ram.bin數(shù)據(jù),如下圖:

    從而可以推斷出LR的值為0x08000E13,這里是PC+1的值,所以函數(shù)返回的實(shí)際地址為0x08000E12。然后再在跳轉(zhuǎn)到這個地址,根據(jù)上面圖片,發(fā)現(xiàn)是一個出棧三個元素的指令,同樣找到LR實(shí)際地址0x08000E02然后在跳轉(zhuǎn)到這個地址,反發(fā)仍然是一個出棧三個元素的出棧指令,同樣找到LR實(shí)際地址為0x08001046,再繼續(xù)跳轉(zhuǎn)到這個地址,發(fā)現(xiàn)是一個延時函數(shù)了如下圖:

    至此,就是通過分析棧數(shù)據(jù)分析函數(shù)的調(diào)用關(guān)系,這里寫了一個簡單的測試歷程,通過按下按鍵會執(zhí)行幾層函數(shù)調(diào)用最后進(jìn)入一個死循環(huán),從而模擬死機(jī)的情況。
    怎么樣,看著是不是感覺特簡單,但是在實(shí)際的開發(fā)過程中,真實(shí)情況可能比這復(fù)雜百倍。
    結(jié)語為了寫這篇文章真的下了血本,我買了一個STM32的小開發(fā)板以及一個ARM仿真器,這對于原本就不富裕的我來說無疑是雪上加霜。
    感謝您的耐心閱讀!如果您覺得本文有幫助,請不要吝嗇您的點(diǎn)贊、分享和收藏!
    猜你喜歡:
    WiFi6+藍(lán)牙+星閃,三合一開發(fā)板,真香!
    Github上熱門 C 語言項(xiàng)目匯總!
    嵌入式,可測試性軟件設(shè)計(jì)!
    一些低功耗軟件設(shè)計(jì)的要點(diǎn)!
    嵌入式 C 保護(hù)結(jié)構(gòu)體的方式
    實(shí)用 | 10分鐘教你通過網(wǎng)頁點(diǎn)燈
    談?wù)勄度胧杰浖募嫒菪裕?/strong>
  • 回復(fù)

    使用道具 舉報

    發(fā)表回復(fù)

    您需要登錄后才可以回帖 登錄 | 立即注冊

    本版積分規(guī)則

    關(guān)閉

    站長推薦上一條 /1 下一條


    聯(lián)系客服 關(guān)注微信 下載APP 返回頂部 返回列表