在嵌入式系统开发中,我们通常会将标准输入输出作为一个控制台功能添加到我的嵌入式应用程序中。这样我就有了一个命令行接口,可以检查和修改目标系统。在 ARM 架构中,Semihosting 和 SWO 是经常会遇到的两个概念,在调试输出方面也是用的非常多的,今天就来学习一下!
Semihosting
Semihosting 是嵌入式芯片中提供一种机制,它使在嵌入式芯片上运行的固件代码可以使用主机计算机的 I/O(与主机计算机的 I/O 进行通信)。在此技术之下,目标芯片上固件程序中的 printf
就可以直接输出到 PC 显示器,目标芯片上固件程序中的 scanf
就可以直接接收从 PC 键盘上的输入。
工作原理
Semihosting 的基本实现方法是通过在固件代码的某个点使用某种类型的断点指令或者模式切换(legacy ARM 或 Cortex A/R 的 supervisor 模式)来停止目标程序的运行然后与调试器进行通信来实现的。
-
目标 CPU 通过进入断点指令或其他某些操作来停止程序执行并将目标 CPU 控制在调试仿真器(例如 J-Link)或调试器(例如 GDB、集成在 IDE 中的调试器)的控制之下。
通常由编译器对应的 C 库中相关函数实现,发送 Semihosting 请求
-
调试仿真器或调试器读取一个或多个寄存器以确定主机代表目标执行的操作类型,然后执行操作,然后重新启动目标芯片上的程序运行。
调试仿真器或调试器需要实现对各种 Semihosting 请求的响应
需要注意的是,Semihosting 仅在调试状态下才可以正常使用,如果是发布程序,则通常需要显示将 Semihosting 禁用,否则,可能会由于无法正确处理 Semihosting 的请求进而导致程序运行异常!
历史
术语 Semihosting 最初是由 ARM 在 1990 年代初期提出的,该技术实现部分功能由主机(运行调试工具的 PC)执行,部分由目标芯片(开发板)执行。最初的设计目的是在根本没有真正基于外设的 I/O 的目标环境中提供 I/O。
Semihosting 这种技术并不是什么新鲜事物,它已经在嵌入式系统中使用了几十年。开发工具领域的大多数公司都开发了自己的 Semihosting,基本上都遵循停止目标处理器的相同想法(通常是让它运行到一个断点)。然而,各家的实现没有互操作性,因为没有标准,直到 ARM 为其处理器定义了相关标准,并采用了 Semihosting 这个术语。
ARM 规范
Semihosting 是通过一组定义的软件指令来实现的,这些指令会从程序控制中产生异常。使用 A64、 A32 和 T32 指令集的 A 和 R profiles 以及使用 T32 指令集的 M profiles 均支持 Semihosting 技术。Semihosting 操作是使用 Trap Instructions 来发送请求,Semihosting 的 Trap Instructions 和编码表如下所示:
Semihosting 的实现必须支持 ARM 架构中该指令的所有版本。对于使用 A32 和 T32 指令集的 A 系列以及 R 系列而言,可以使用 SVC 指令和 HLT 指令。尽管 HLT 指令在 ARMv8 中被定义,但是在 ARMv7 以及更早的版本中也可以使用,当然,这可能需要 Semihosting agent 把它当作 UNDEF 异常来处理。
-
在 AArch32 架构下,应用程序通过使用 SVC(SWI)指令并携带一个特定的 SVC number,来触发一个异常,从而实现一个 Semihosting 请求。
- 在 ARMv7 中(如 ARM7、 ARM9、 ARM11),优先使用 SVC 指令,早期称为 SWI。
- 在 ARMv6-M 或者 ARMv7-M,比如 Cortex-M1 或 Cortex-M3 ,则使用 BKPT 指令来产生 semihosting 请求。
-
在 AArch64 架构下,应用程序使用 HLT 指令,并携带一个常数 0xF000 来创建一个 Semihosting 请求,但是它不会产生异常。
最新版 ARM 的 Semihosting 标准下载 Semihosting for AArch32 and AArch64.pdf
ARM 鼓励在目标芯片测程序中实现 A32 和 T32 指令集 中的 HLT 指令作为 Semihosting 的可配置选项。ARM 强烈反对目标芯片测程序中混合使用 HLT 和 SVC 机制。
各种 Semihosting 操作的参数或者返回值的传递由特定的寄存器来实现,一些 Semihosting 操作在 32 位和 64 位版本之间还有其他不同之处。如下所示:
-
OPERATION NUMBER REGISTER:包含要执行的 Semihosting 操作的数目
W0 寄存器仅使用低 32 位
-
PARAMETER REGISTER:包含 Semihosting 操作使用的参数。对于大多数操作,PARAMETER REGISTER 必须包含一个指向保存参数的数据块的指针。在某些情况下,直接在 PARAMETER REGISTER 中传递单个参数,或者根本不传递任何参数。
-
RETURN REGISTER:结果以显式返回值或指向数据块的指针的形式在 RETURN REGISTER 中返回。某些操作也通过 PARAMETER REGISTER 返回信息
现在,Semihosting 操作码的定义如下:0x00-0x31 由 ARM 使用;0x32-0xFF 由 ARM 保留给未来扩展;0x100-0x1FF 可用用户自定义;0x200-0xFFFFFFFF 未定义,当前未使用。当前 ARM 定义的操作码如下所示:
旧规范中的操作码
0x17
(angel_SWIreason_EnterSVC)和0x19
(angelSWI_Reason_SyncCacheRange)现在被保留
使用
大多数情况下,Semihosting 请求是由 C 库函数中的代码调用的,同时各大 IDE 都对 Semihosting 提供了支持(响应 Semihosting 请求),我们只需要在 IDE 中启用即可。当然我们自己的应用程序也可以直接调用/实现 Semihosting 操作(ARM 建议自己实现时自定义所有操作码,不要标准 + 自定义混用)。
IAR(EWARM)
IAR 的编译器使用的 C 库官方称为 DLIB,DLIB 运行时库被设计成通过一小组简单的功能来执行所有的 I/O 操作,这些功能被称为 DLIB 低级 I/O 接口。在默认情况下,很多 DLIB 低级 I/O 接口是没有被实现的,而标准输入输出则默认被实现为 Semihosting 模式。
IAR(EWARM)的调试器(官方名称为 C-SPY)支持由 C-SPY 模拟 I/O。这样当 DLIB 库中的相应函数发出 Semihosting 请求时,C-SPY 就会读取到相关信息,进而传递到 C-SPY 模拟 I/O,而 C-SPY 模拟 I/O 实际就是和 Terminal I/O 这个界面交互的,这也就实现了和用户进行交互。
要启用 C-SPY 模拟 I/O,只需要打开 Project ➔ Options ➔ General Options ➔ Library Configuration 页面,然后在 Library low-level interface implementation 中选择 Semihosted 或 IAR breakpoint 即可,然后就可以在 IAR 的 Terminal I/O 中进行输出输出了。
MDK-ARM
MDK-ARM 不支持 Semihosting,我们只能自己进行重定义来实现调试跟踪输出。但是,ARM 编译器的 C 标准库(StandardLIB)则默认是支持 Semihosting(其中的标准输入输出默认就实现为 Semihosting 模式)。
ARM 编译器的 MicroLIB 本身不支持 Semihosting,如果使用 MicroLIB ,只能选择自己重定向 I/O
我这里先使用 MDK-ARM 编译出可执行程序(.axf
),然后使用 SEGGER 的 Ozone 进行调试来进行简单验证(如下配置中选择 User input via terminal window
,不过貌似 Ozone 的处理有些问题,输入一次就会一直有效):
Eclipse 系
Eclipse 系大都使用的是 GCC 作为编译工具链,而调试则使用的是 GDB,而对应的 C 标准库则由 GCC 的供应商决定。但是,大多数情况下,目标芯片裸机编程就是使用 Newlib 这个 C 库,目标芯片 Linux 系统编程则使用 glibc 这个 C 库。
我使用编译工具链是 Arm GNU Toolchain(arm-none-eabi-gcc
) 的 11.3.1 版本,Arm GNU Toolchain 使用的 C 库就是 Newlib。根据官方文档,如果需要支持 Semihosting 则必须添加 --specs=rdimon.specs
这个链接器选项;如果不需要 Semihosting 或者需要自己进行重定向,则必须使用 --specs=nosys.specs
这个链接器选项。
- 添加
--specs=rdimon.specs
这个链接器选项。Eclipse 的 UI 配置界面并没有这个选项,只能自己添加
实际上,该操作会链接libc.a
(Newlib 主库) 和librdimon.a
(libgloss 库的一部分) 中相应的实现 - 在自己的代码中显示启用 Semihosting 处理函数
initialise_monitor_handles
,否则将收到WARNING: Semihosting command SYS_FLEN failed. Handle is 0.
这个错误提示!
- 调试配置中开启 Semihosting 支持。我这里使用的是 J-Link,其他调试仿真器的设置类似。
- 启动调试,就可以在 Console 界面中接收或者输入数据了。注意需要切换合适的 Console 通道!
SEGGER Embedded Studio
SEGGER 定义了一套自己的名为 SEGGER Semihosting 的 Semihosting 标准,并与 ARM 定义的旧标准兼容,相关源码可以从官网下载。SEGGER Embedded Studio 调试器完全支持 SEGGER Semihosting 标准,而且其 C 标准库(官方名为 emRun)中默认情况下就启用了 Semihosting 支持。
SEGGER Embedded Studio 的 Semihosting 实现还包括主机格式化输出功能。对于目标芯片上的格式化的输出,比如通过 printf ()
,目标芯片上的程序不必在 RAM 中构造格式化的字符串。相反,格式字符串通过 Semihosting 传递给 Embedded Studio,Embedded Studio 解析它并从目标中读取参数,从而节省了目标芯片资源。
- https://wiki.segger.com/SEGGER_Semihosting
- https://wiki.segger.com/Semihosting
SWO
SWO(Serial Wire Output)是 ARM 制定的 Serial Wire Debug(SWD)的一个扩展,但是并不是所有支持 SWD 的 ARM 核心都支持 SWO。而 SWD 又是 ARM 公司 Cortex-M CoreSight 组件的一部分。CoreSight 是 ARM 设计的用于芯片内部的调试及跟踪的组件。
- Arm Debug Interface(ADI):定义了嵌入式 SoC 中调试组件的接口。
- Serial Wire Debug (SWD):SWD(Serial Wire Debug)是由 ARM 公司设计的用于编程和调试 Cortex 系列微控制器的协议。SWD 协议定义了 2 个引脚,在体系结构方面支持星型拓扑。
- ARM 处理器实时跟踪宏单元(ETM,PTM,STM)体系结构
- High Speed Serial Trace Port (HSSTP) 体系结构将串行传输端口(STP)定义为替代现有的并行数据输出端口。它适合于在芯片外传输高带宽数据,例如来自 CoreSight 解决方案的数据。
- 调试访问端口(Debug Access Port,DAP):这是 ARM Debug Interface version 5.1(ADIv5.1)的一个实现,该版本包含在单个配置中提供的许多组件。所有提供的组件都适用于 Debug Ports (DP) 的各种体系结构组件,这些组件用于从外部调试器和 Access Ports (AP)访问 DAP,以访问片上系统资源。
Arm Cortex-M 的 DWT 和 ITM 核心外围设备生成的数据包可以在发生可配置的事件集时通过 SWO 输出。通过 SWO 传输的 DWT/ITM 数据包的组合称为串行有线查看器(Serial Wire Viewer,SWV)。SWV 的一个常见用例是 printf 样式的日志输出,以至于 SWV 或多或少就是这个意思。
SWO 支持两种有线协议,异步 UART 和曼彻斯特编码,尽管在实践中几乎只使用 UART。(SWO 也可以作为独立的 CoreSight 组件使用,但这种情况相对较少)
参考
- https://blog.csdn.net/luolaihua2018/article/details/127344891
- https://interrupt.memfault.com/blog/arm-semihosting
- https://wiki.segger.com/Semihosting
- https://mklimenko.github.io/english/2018/11/02/disable-semihosting/
- https://docs.parasoft.com/display/CPPDESKE1033/Runtime+Testing+with+Keil+MDK-ARM+Support
- https://www.keil.com/pack/doc/compiler/RetargetIO/html/_retarget__overview.html
- https://mcuoneclipse.com/2014/09/11/semihosting-with-gnu-arm-embedded-launchpad-and-gnu-arm-eclipse-debug-plugins/
- https://www.cnblogs.com/foxclever/p/7787083.html