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

PCB聯盟網

搜索
查看: 46|回復: 0
收起左側

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

[復制鏈接]

488

主題

488

帖子

2976

積分

三級會員

Rank: 3Rank: 3

積分
2976
跳轉到指定樓層
樓主
發(fā)表于 2024-9-6 11:38:00 | 只看該作者 |只看大圖 回帖獎勵 |正序瀏覽 |閱讀模式

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

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

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

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

    分析棧數據當我們使用jlink連接目標板后,輸入命令h可以看到一些關鍵信息,如下圖:

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

    通過定位得知,這個地址是在InitC函數內,如下圖:

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

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

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

    使用道具 舉報

    發(fā)表回復

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

    本版積分規(guī)則

    關閉

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


    聯系客服 關注微信 下載APP 返回頂部 返回列表