banner
yono

yono

哈喽~欢迎光临
follow
github

為什麼不用 MicroLib 和 printf —— Arm 的 Semihosting

結論#

結論寫在前面

  1. 首先不使用 microLib 是為了源碼行為可控,不會由於廠商的胡作非為改源 lib ,同時也使得源碼具備了跨平台的能力,不會因 C 標準庫不同產生行為差異。

  2. 由此推演,如果不使用 microLib ,那麼會引入原版 libc ,那麼 C 源庫裡的諸多交互函數,會產生 Semihosting 代碼,導致 debug 下會卡卡地運行,脫離 debug 根本無法運行。

  3. 總之,如果不使用 microLib ,其中的 C 標準庫函數的使用要非常謹慎,避免是系統交互函數產生 Semihosting 代碼。

祖宗之法不可變#

自我初入行起,我的老大就堅決禁止使用 printf 系列函數,或者乾脆說禁止所有的 libc 函數,包括 mallocmemsetstrcpy 等,又或者說標準 C 庫只引入 stdint.hstdlib.hstdbool.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 軟斷點。

其中 BKPTCortex-MBreak 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 說的這麼可怕,“還編譯器默認植入”,我怎麼還活的好好的?我怎麼從來沒碰到過?

恕我直言,你可能符合以下特徵:

  1. 大多數情況下使用的是 Arm Compiler 5
  2. 大多數情況下會默認使用 MicroLib
  3. Arm Compiler 6 下不選 MicroLib 的時候遇到 “調試狀態下一切正常,但下載程序直接跑就會死機” 的現象 —— 因此在小本本上默默記下了只能使用 MicroLib 的筆記;
  4. 從不使用 malloc 以外的 libc 函數,甚至包括 printf
  5. 用的程序模板是大佬做好的;
  6. 應用開發基於芯片廠商給的例子工程
  7. 使用類似 RT-Thread 這類 “提供一站式服務” 的軟件平台。

別看我列舉了很多,其實只分兩種情況:

  1. 瞎貓碰死耗子 —— 運氣好
  2. 有人替你負重前行

此文由 Mix Space 同步更新至 xLog 原始鏈接為 https://www.yono233.cn/posts/shoot/24_8_13_Semihosting

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。