結論#
結論寫在前面
首先不使用 microLib 是為了源碼行為可控,不會由於廠商的胡作非為改源 lib ,同時也使得源碼具備了跨平台的能力,不會因 C 標準庫不同產生行為差異。
由此推演,如果不使用 microLib ,那麼會引入原版 libc ,那麼 C 源庫裡的諸多交互函數,會產生 Semihosting 代碼,導致 debug 下會卡卡地運行,脫離 debug 根本無法運行。
總之,如果不使用 microLib ,其中的 C 標準庫函數的使用要非常謹慎,避免是系統交互函數產生 Semihosting 代碼。
祖宗之法不可變#
自我初入行起,我的老大就堅決禁止使用 printf 系列函數,或者乾脆說禁止所有的 libc 函數,包括 malloc 、 memset 、 strcpy 等,又或者說標準 C 庫只引入 stdint.h 、 stdlib.h 、 stdbool.h 。導致了海量自己實現的字符串操作、 buffer 操作、 trace 日誌等。如果 malloc 還可以解釋為避免線程暴栈,靜態內存可以使 bug 規模可控,其他的實現究竟為何不可用呢?
總之,祖宗之法不可變,直到現在的工程依然是這樣。
線索#
看這個家伙的 bootloader 漏洞介紹時(其實也是一個棧操作問題),看到下面這篇文章。或許可以解答這個祖宗之法的來源。
Semihosting 真的是嵌入式阑尾么?- 腾讯云开发者社区 - 腾讯云 (tencent.com)
驗證#
其中一些代碼如下。 clock (); 就是一個典型的產生系統交互的代碼。在這個工程中取消了 microLib 。
#include <stdio.h>
#include <time.h>
int main(void)
{
// 省略一些初始化代碼
while (1)
{
clock_t tTime = clock();
HAL_GPIO_TogglePin(GPIOH, GPIO_PIN_5);
HAL_Delay(500);
}
}
在 debug 調試下運行
首先是 clock () 函數相關的反匯編,將跳轉到 0x0800037C 地址運行 clock () 函數
109: clock_t tTime = clock();
0x08002386 F7FDFFF9 BL.W 0x0800037C clock
0x0800238A 9001 STR r0,[sp,#0x04]
0x0800238C F6414000 MOVW r0,#0x1C00
0x08002390 F6C50002 MOVT r0,#0x5802
0x08002394 2120 MOVS r1,#0x20
clock () 函數所在,當真出現了 BKPT 軟斷點。
其中 BKPT 是 Cortex-M 的 Break Point (軟件斷點)指令,而常數 0xAB 則是 Semihosting 專用暗號。如果使用的調試工具恰好支持 Semihosting ,那麼甚至軟件不會停下並正常運行。但斷電再起就傻了,” BKPT 指令在非調試模式下執行,會直接讓 Cortex-M 處理器進入 Hardfault “。
0x0800037C 2100 MOVS r1,#0x00
0x0800037E 2010 MOVS r0,#0x10
0x08000380 BEAB BKPT 0xAB ;將會停在這句,這就是 Semihosting 代碼的軟斷點
0x08000382 4905 LDR r1,[pc,#20] ; @0x08000398
0x08000384 6809 LDR r1,[r1,#0x00]
0x08000386 1A40 SUBS r0,r0,r1
開優化能否避免?#
事實上,現代的編譯器已經非常智能了,開一點優化會修復許多你自己都沒發現的 bug。
在開到 O3 的情況下進行編譯,仍然不可避免得出現 BKPT,這個 libc 應當是以二進制形式鏈接進燒錄文件的。
避免哪些函數?#
在參考文章裡有寫,摘到這裡。
1. 標準輸入 / 輸出(Standard I/O)#
- printf 系列函數:例如 printf、fprintf、sprintf 等,用於格式化輸出到標準輸出設備(通常是主機的控制台)。
- scanf 系列函數:例如 scanf、fscanf、sscanf 等,用於格式化輸入從標準輸入設備(通常是主機的鍵盤輸入)。
2. 文件操作(File Operations)#
- fopen:打開文件。
- fclose:關閉文件。
- fread:從文件讀取數據。
- fwrite:向文件寫入數據。
- fseek:移動文件指針到指定位置。
- ftell:獲取文件指針當前位置。
- fflush:刷新文件輸出緩衝區。
3. 時間和日期(Time and Date)#
- time:獲取當前時間。
- clock:獲取處理器時間。
- difftime:計算兩個時間點的時間差。
- strftime:格式化時間和日期為字符串。
4. 錯誤處理(Error Handling)#
- perror:輸出錯誤信息到標準錯誤設備。
- strerror:返回與錯誤碼對應的錯誤信息字符串。
5. 系統調用(System Calls)#
- exit:終止程序並返回狀態碼。
- system:執行系統命令(在嵌入式系統中很少使用,但在主機上調試時可能有用)。
6. 其他輔助功能(Other Auxiliary Functions)#
- getenv:獲取環境變量的值。
- putenv:設置環境變量(不常見)。
- remove:刪除文件。
- rename:重命名文件。
原文章太有趣了,強烈建議觀看#
顯然我們符合其歸納的特徵 4,並且更加偏激。
【“嵌入式阑尾炎” 的潛伏與誘因】
五星上將麥克阿瑟曾評論道:某度看病,癌症起步。你這偽專家,把 Semihosting 說的這麼可怕,“還編譯器默認植入”,我怎麼還活的好好的?我怎麼從來沒碰到過?
恕我直言,你可能符合以下特徵:
- 大多數情況下使用的是 Arm Compiler 5;
- 大多數情況下會默認使用 MicroLib;
- 在 Arm Compiler 6 下不選 MicroLib 的時候遇到 “調試狀態下一切正常,但下載程序直接跑就會死機” 的現象 —— 因此在小本本上默默記下了只能使用 MicroLib 的筆記;
- 從不使用 malloc 以外的 libc 函數,甚至包括 printf
- 用的程序模板是大佬做好的;
- 應用開發基於芯片廠商給的例子工程
- 使用類似 RT-Thread 這類 “提供一站式服務” 的軟件平台。
別看我列舉了很多,其實只分兩種情況:
- 瞎貓碰死耗子 —— 運氣好
- 有人替你負重前行
此文由 Mix Space 同步更新至 xLog 原始鏈接為 https://www.yono233.cn/posts/shoot/24_8_13_Semihosting