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


加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。