首页
论坛
课程
招聘
[分享] 初探 ARM 半主机(Semihosting)及 QEMU 调试
2021-3-6 16:33 7483

[分享] 初探 ARM 半主机(Semihosting)及 QEMU 调试

2021-3-6 16:33
7483

我很想深入研究 ARM 的 TrustZone,想要搭建一个可以模拟和调试 Trusted Application 的平台环境。我了解到 Open-TEE (ATF) 项目提供有一个 QEMU 模拟调试环境。虽然是针对于 Open-TEE 的 TrustZone 实现,但是也可以作为一个参考来学习。在阅读这个项目的代码时,我留意到了一个 QEMU 的启动参数:

-semihosting-config enable,target=native

https://github.com/OP-TEE/build/blob/637c7863d7e428d673b18bae21ac1db52b2a9b8d/qemu.mk#L179

经过一番查找后发现这个 Semihosting 功能颇有用处:他能够让 bare-metal 的 ARM 设备通过拦截指定的 SVC 指令,在连操作系统都没有的环境中实现 POSIX 中的许多标准函数,比如 printf、scanf、open、read、write 等等。这些 IO 操作将被 Semihosting 协议转发到 Host 主机上,然后由主机代为执行,所以在 ARM 模拟器中执行一个 printf 可以直接打印到 Host 主机上的终端窗口中;在 ARM 模拟器中写一个文件可以直接写到 Host 主机的当前目录下,等等。


如果要写一个 TrustZone Kernel 的 QEMU 模拟环境,如果使用了 Semihosting,则不需要去模拟 FLASH 存储设备就能直接从 Host 主机上加载文件;不需要模拟串口设备就能直接打印 Log 输出,将会大大减少开发工作量。Open-TEE 项目也正是利用了 Semihosting 非常简单地就加载了 Trusted Application。


为了今后能够在我自己的项目中使用 Semihosting 这一方便的功能,我想要写点 Hello World 程序看看如何使用这个功能。


Semihosting 其实是 ARM 官方定义的功能,所有 ARM 芯片都支持这个协议,只要连上硬件调试器就能使用。当然了,QEMU 模拟 ARM 也把 Semihosting 模拟了一遍,只不过不需要什么特殊的连接就能直接使用。这个是 ARM 的官方文档:


https://developer.arm.com/documentation/dui0471/g/Semihosting/The-semihosting-interface


可以看到 SVC 0x123456 是官方给 Semihosting 预留的指定命令。当这条 SVC 被执行时,R0 寄存器的值是一个 Operation ID,R1 寄存器的值是指向附加参数结构体的指针。这个页面列举了 Semihosting 支持的 Operations:


https://developer.arm.com/documentation/dui0471/g/Semihosting/Semihosting-operations


比方说最简单的 puts 函数,对应的 Operation ID 是 SYS_WRITE0 (0x04),那我们就可以很简单地用 C 和汇编写一个 Hello World:


https://github.com/iNvEr7/qemu-learn/blob/master/semihosting


这个例子直接调用 SVC 指令把字符串打印出来。执行 make run 编译执行,需要安装 qemu, gcc, gcc-arm-none-eabi, tmux。


但是光有最基本 SVC 指令太弱了,如何才能使用更为强大的 POSIX/libc 功能,比如 printf 呢?答案是编译的时候使用 --specs=rdimon.specs 参数。这个会告诉编译器去链接 newlib 的 Semihosting 版本。这里解释一下,newlib 是专门给嵌入式设备实现的 libc 库,当然也包括了以 Semihosting 作为底层支持的模式。还有一个可选的参数 --specs=nano.specs 会链接 newlib 的精简版,去掉很多少用到的功能,减少编译出来的体积。


另外,newlib 因为要初始化 libc 环境,需要知道你的栈和堆在哪,所以需要你至少先将 SP 寄存器初始化到栈顶,newlib 会通过 Semihosting 的 HEAPINFO 指令获得内存大小,自动选择堆底地址。另外,newlib 虽然会初始化(清空).bss 内存区域,但是不会帮你初始化(拷贝).data 内存区域,所以需要你自己进行初始化。这是我用 newlib+Semihosting 实现的 Hello World:


https://github.com/iNvEr7/qemu-learn/blob/master/semihosting-newlib


在实现这个 newlib 版本的时候,我遇到了一个让我头疼万分的 bug,在经过重重调试后发现原来是 QEMU 的一个 Regression Bug:


https://bugs.launchpad.net/qemu/+bug/1915925


已经提交给官方了,什么时候能修复还不知道,但是各位要运行这个例子的话需要给 QEMU 打一个补丁才行:


https://gist.github.com/iNvEr7/f99fe1219a30480ba692762c98fde829


如此一来你就可以使用任何 libc 的功能了,而且直接就能在 ARM bare-metal 系统上跑,完全不用理会 FLASH、UART 等硬件的实现。


【公告】【iPhone 13、ipad、iWatch】11月15日中午12:00,看雪·众安 2021 KCTF秋季赛 正式开赛【攻击篇】!!!文末有惊喜~

收藏
点赞3
打赏
分享
最新回复 (1)
雪    币: 1956
活跃值: 活跃值 (1941)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
Invert 活跃值 1 2021-4-8 15:56
2
0
更新一下,QEMU那個bug已經有fix了,會在6.0版推出修復,在那之前可以直接使用最新的git版本
游客
登录 | 注册 方可回帖
返回