導讀:程序運行過程中,有些數據被莫名修改了,在哪里修改的?又是怎么修改的?這個代碼我只想知道是否運行過,或者運行了多少次,但是不想讓程序停下來,或者僅打印調試信息,怎么辦?當這個變量設置成某個數據后,我想讓程序自動暫停下來進行分析,怎么辦?
以上問題的所有答案就在本節內容:斷點窗口(KEIL)。
本節內容將顛覆你之前對斷點調試的認知。這個調試技巧魚鷹也用了半年多了,當時知道這個調試方法的時候特別興奮,感覺發現了新大陸。而這個調試技巧也在魚鷹接手公司項目代碼的時候快速解決了不少疑難雜癥,而前些天又擴展學習了這個技巧的功能,更是讓魚鷹在學會之后輕松解決了好幾個一般調試方法很難解決的 BUG,相信這個技巧也將為魚鷹之后的開發調試之旅發揮更大的作用。
我們知道常規的斷點調試是在想觀察哪里的問題時就在對應的代碼地址設置斷點,并且一旦運行到斷點位置會讓程序自動暫停運行,這種斷點調試功能確實為開發者解決 bug 立下了汗馬功勞,但是這種方式有很大的局限性,因為很多時候我們并不需要讓程序停下來,而只想知道是否在這段代碼運行過,或者說發生問題的位置根本不能停下來,否則就會讓整個系統功能出現問題,比如中斷處理函數的調試,程序一旦停下了也就失去了所有中斷的后續響應;比如兩個設備通信,一方采用常規斷點的方式調試,肯定會打斷正常的通信過程,而這可不是我們想要的,我們只想知道在收到或發送數據后得到環境快照,而并不想讓程序停下來。以上這些問題可以采用打印方式解決,但是打印調試也有很多弊端:
以串口為例:
1、你必須添加必要的打印和串口驅動代碼,如果你使用 printf 函數,你還得重定向(如果對空間要求高的話,你得知道使用 printf 差不多要占用 1K 大小代碼空間)。
2、如果打印效率比較低,常規波特率 9600 和 115200 打印一個字符串耗時可能比較久,那么對于中斷頻率較高的函數就可能就不適用了。如果你使用 printf 函數,你還得考慮函數是否可重入問題。
3、在代碼中引入調試代碼有風險,本來程序運行沒有問題的,一旦引入調試代碼之后可能就出現了問題,這種情況對于擁有豐富開發經驗的人來說應該見怪不怪了。原因就在于打印輸出時間太久,打亂了程序運行的節奏(而這也是我推薦使用 ITM 調試的一個原因,因為它的輸出效率比串口要高得多),或者打印函數本身有問題,也會導致程序運行出現問題。
4、調試完畢之后,你必須把對應的調試代碼刪除(不管是刪除代碼還是使用宏,都要進行這一步),不然會影響運行效率。而人是健忘的(也不能說健忘,可能只是因為專注于 BUG 本身,容易忘記其它細枝末節,而解決 bug 之后的欣喜更可能忘記后續處理工作了)這個時候你可以嘗試用 #warnning。但是這一步還是必不可少。
而以上問題的解決方案就是 KEIL 的斷點調試窗口!
首先打開數據觀察點的窗口:
快捷鍵是 Ctrl + B。
可以看到如下窗口:
當然你也可以通過下面這種方式打開并設置:
從這里你會發現,其實這個窗口就是用來管理你設置的斷點的。平常使用的設置斷點方法只是其中的一種特例罷了。
首先要知道的就是,調試器支持的斷點數量是有限的,具體有多少視情況而定,一旦 KEIL 警告你設置斷點太多,那么就要刪除一些斷點了:
常規用法
1、代碼位置運行次數
有些時候我們想知道某些代碼的運行次數,比如進入中斷處理函數的次數,尋常的斷點設置方式必然會讓程序停止在中斷程序中,但有些時候我們并不希望它停下來。這個時候,你只需要打開該窗口,找到已有的對應斷點位置,雙擊之后就可以看到類似下面的窗口:
此時,你將 Count 的值設置的盡可能大一些,那么就可以讓程序運行多次之后才停止。
比如我們設置 Count 的值為 100 次,那么必須在該代碼位置運行 100 次才會讓程序暫停。當你設置完后點擊【Define】后,就會詢問你是否需要重新定義,你選擇“是”即可。
這樣你的斷點變成了這樣:
后面的 count=100 表示剩余運行次數為 100,運行 100 次后將停止程序。前面的 00 代表斷點號,E 代表這是一個執行斷點,0x080016B0 代表代碼地址,后面的是源碼位置。
當這個斷點位置運行了 2 次,重新打開該窗口(刷新數據),發現這個數變成了 98,從而可以推算出,已經運行了多少了。如果說你想讓這段代碼運行 2 次后停止,那么你只需要一開始設置 Count 的值為 2 即可。
2、數據訪問
有些時候我們需要知道一些變量會在哪里被訪問,那么你可以設置該變量的訪問條件。比如魚鷹想知道 emOspery 變量會在哪里被讀取?那么你只需設置如下:
定義之后就是這樣:
因為 Count 值設置為 1,所以每一次讀取 emOspery 的操作都將使程序停止。比如這段代碼:
還有后面的打印函數也使用 emOsprey 變量,所以也會導致程序運行停止。可能你會感到奇怪,為什么 emOsprey++這樣的操作也會涉及到讀取?事實上你理解了 CPU 寄存器存在的意義也就明白了。
而當你設置為寫(Write)訪問時,你會發現從復位程序開始運行后,程序會停止在某個地方,這是為什么?當你知道全局變量會在進入 main 函數之前被初始化時,你也就明白為什么了。
在這里我們選擇使用 Objects 訪問,即按整個變量對象進行訪問,上面的 emOsprey 變量實際上是 uint16_t,所以 len 為 2,即字節大小。也就說,如果你設置為 Objects 訪問,那么它會根據實際的情況設置訪問范圍。
為了更好的說明這一點,我構造一個結構體。
這個結構體大小可以看出是 6 個字節。
然后設置訪問該結構體的條件:
如果我們按 Objects 訪問的話,那么下面的每一條語句都會導致程序運行的停止。
這是因為這些數據都在 Osprey 結構體的范圍內(從這里也可以了解到,只要在 len 的范圍內的訪問都會導致程序停止運行,所以你可以試試將 Size 設置得更大)。
而如果設置為 Byte 訪問的話,那么就只有第一條語句才會導致程序停止運行:
實際上如果你希望只在某個結構體成員變量被訪問時才停止,那么直接這么設置就可以:
你會發現設置是如此之簡單。
實際上還有一種更為通用的訪問方式,即按地址訪問。
上面可以看出 Ospery.Ospery1 成員變量的地址為 0x20000016(由此我們知道也可以通過這個來看出一個結構體變量的地址是多少)。所以我們可以這樣設置:
而代碼位置的斷點設置亦是如此。
斷點太多,怎么知道程序因何停止?看你的命令窗口就知道了:
3、數據匹配
有些時候,我們并不關注地址訪問情況,而對變量的數據內容感興趣。比如說魚鷹想讓變量emOspery 等于 1 時停下來,怎么設置?
只要簡單的設置 emOspery == 1 即可(注意必須設置訪問條件,并且 Size 設置正確)。
事實上你也可以設置兩個變量相等作為條件:
設置為不等也是可以的:
當然還有其它支持的運算就靠你們自己去發現了(可支持運算:&,&&,<,<=,>,>= ,==,!=)。
注意:以上內容可以組合使用,比如讀、寫條件,計數器計數等可以同時設置。滿足條件時就會讓程序運行停止。
高級用法
以上為比較常規的調試功能,現在說說魚鷹剛學習的技能,這個技能的使用靈活性更大,而且對于解決疑難雜癥更是不二之選。
首先設置一個你需要的斷點:
打開斷點窗口,并雙擊你之前設置的斷點:
設置 Command 為【printf(“USRAT_Init()\n”)】(注意\n,否則可能不能輸出,這個應該是 KEIL 的一個 bug)。最后【Define】
清空你之前的命令(如果你不嫌亂的話,也可以不清空):
那么你的程序每次運行到這個代碼位置都會在Command 窗口輸出一條信息:
但是你的程序并不會停止。
如果說你想讓斷點代碼位置運行多次之后才輸出一條信息也是可以的,只要設置Count 即可。
這里可能你會問,這 printf 不就是我們寫的打印函數嗎?事實上,是,也不是。
這個函數是打印函數沒錯,但是這是 KEIL 調用的打印函數,輸出位置是 Command 窗口,和你自己寫的代碼沒一點關系,每次觸發條件時KEIL 都會調用該函數進行打印,而不會讓你的程序暫停運行。事實上這個 Command 絕不僅僅只是設置 printf這么簡單,如果真是這樣我也不會如此推崇它了,感興趣的可以去官網查找關于調試命令的使用方法。
因為是利用 KEIL 去執行打印任務,所以對你的程序幾乎沒有任何影響,并且在你設置斷點后也不用擔心刪除代碼問題,可以放心飲用。還有一個額外的好處就是,對于所有能設置調試斷點的單片機都適用,因此對于調試器也就沒有過多的要求了,比如說,不管你是用JLINK、ST-LINK 還是CMSIS-DAP(CMSIS-DAP 不能使用ITM,所以魚鷹才會想著用別的方式替代。總算是找到了,而且它在某些方面更出色),都可以這么用。
現在摘錄官網一些關于斷點窗口的知識:
表達式定義斷點類型:
§當設置標志 **Read **或 **Write **或兩者時,訪問中斷(A)被定義 。發生指定的內存訪問時會觸發斷點。以字節為單位指定內存訪問窗口的大小,或者以表達式的對象大小指定。對于此斷點類型,**Expression **必須解析為內存地址和內存類型。允許的運算符(&,&&,<。<=。>,> =,= =和!=)在程序執行暫停或執行**命令**之前比較變量值 。
§當Expression解析為代碼地址時,將執行執行中斷(E)。到達指定的代碼地址時觸發斷點。代碼地址必須引用 CPU 指令的第一個字節。
§當Expression不能簡化為地址時,定義條件中斷(C)。當條件表達式變為 TRUE 時,斷點將觸發。在每條 CPU 指令之后重新計算條件表達式,并且會大大減慢程序執行速度。
該計數值指定的次數的斷點表達式必須計算為 TRUE 斷點觸發之前的數目。
當命令被指定的μVision 執行語句,然后恢復執行程序。此處指定的命令可以是μVision 調試或信號功能。要從這些函數中暫停程序執行,請設置系統變量break。
注意
當在模擬器中將訪問斷點(讀或寫)設置為外設寄存器(SFR)時,即使應用程序未訪問外設寄存器,斷點也可能觸發。發生這種情況是因為μVision 模擬器在應用程序驅動和模擬器內部訪問之間沒有區別。
里面有一個比較關鍵的就是關于條件中斷(C),如果你設置的表達式不是一個代碼地址,也沒有設置讀寫訪問條件,那么就會被設置為條件中斷,一旦設置為條件中斷,那么會在每條匯編指令后計算表達式,這會影響程序正常運行速度,所以沒有必要的話,不要設置為條件中斷。
設置斷點的一般錯誤總結:
當彈出以下窗口時,說明斷點設置錯誤,需要查看命令窗口才能知道具體錯誤信息。
**a) **斷點太多
刪除一些斷點即可
**b) **重復定義斷點
這是因為之前你已經定義了這個斷點,而現在你又定義了這個斷點,這個時候你可以選擇覆蓋之前的斷點或者保留之前的斷點
**c) **不允許對同一個資源設置不同類型斷點
這個是由于對同一個資源準備設置不同斷點導致的,需要刪除之前的設置的斷點才行。
**d) **表達式錯誤
檢查你的表達式是否正確,注意如果你使用了運算符,那么對于浮點變量的支持好像并不正常,不管你怎么設置,都說表達式錯誤。
到此,斷點窗口(前期我叫它數據觀察點,我也不知道從哪看到的這個詞,后來覺得還是斷點窗口比較準確)的內容就結束了。這個小節內容對于調試而言絕對是一大利器,也是魚鷹決定寫這個KEIL 調試系列文章的主要原因。但是以上所有的調試內容都有一個很大的局限性,就是它只能定格在某一刻(如果你使用Command 命令就不一樣了),而這一刻前面的所有信息都無法知曉。這個時候就要了解另一個調試技能,ITM,它能將程序從出生(復位程序開始)到死亡(死循環或者斷電)的大部分信息記錄下來。這個章節內容早已發布,感興趣的就去前面看一看咯。
審核編輯:劉清
-
寄存器
+關注
關注
31文章
5336瀏覽量
120232 -
計數器
+關注
關注
32文章
2256瀏覽量
94478 -
CMSIS
+關注
關注
0文章
40瀏覽量
11892 -
調試器
+關注
關注
1文章
303瀏覽量
23716 -
串口驅動
+關注
關注
2文章
82瀏覽量
18647
原文標題:數據被篡改了,無法在線調試該怎么定位?
文章出處:【微信號:eOsprey,微信公眾號:魚鷹談Linux】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論