大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家分享的是IAR下调试信息输出机制之半主机(Semihosting)。
在嵌入式世界里,输出打印信息是一种非常常用的辅助调试手段,借助打印信息,我们可以比较容易地定位和分析程序问题。在嵌入式应用设计里实现打印信息输出的方式有很多,本系列将以 IAR 环境为例逐一介绍 ARM Cortex-M 内核 MCU 下打印信息输出方法。
上一篇文章 《IAR下调试信息输出机制之硬件UART外设》 里我们介绍了利用 MCU 芯片内的硬件 UART 外设去做打印输出的方式,这种方式很简单,还可以脱离在线调试环境去使用,但毕竟占用了芯片内部的外设资源,而且调试的时候还需要额外连接串口线路,稍微麻烦了一点。今天痞子衡介绍一种在 IDE 中结合仿真器直接做打印输出的方式,即半主机(Semihosting)技术。
- Note: 本文使用的 IAR EWARM 软件版本是 v9.10.2。
老规矩先简介下本文介绍的打印输出方法整体软硬件框图,硬件上主要是 PC 主机、MCU 目标板、一个 ARM 仿真器(DAP-Link 或者 J-Link 都可以)。
软件上 PC 这边就需要 IAR 开发环境即可,这里在编译目标板 MCU 应用程序时,除了需要包含打印输出相关代码(标准 I/O 库接口),底层接口实现必须选用 IAR 的 Semihosting 库。当 MCU 程序运行起来后(需要保持在线调试状态),仿真器实时捕获代码里的打印输出需求,将这种 I/O 访问需求转移到 PC 主机上来完成,然后我们在 IAR 的 Terminal I/O 窗口里可以看到打印信息。
上图里 MCU 应用程序中的 printf() 打印输出需求到底是如何转移到 PC 主机上 IAR 软件里去完成的,这是本文要研究的重点。
Semihosting 中文为半主机(半托管),这是一个在嵌入式系统中已经流传了几十年的技术。Semihosting 技术将应用程序中的 I/O 请求通过一定的通道传送到主机(host),由主机上的资源响应应用程序的 I/O 请求,而不是像在主机上执行本地应用程序一样,由应用程序所在的硬件系统来响应应用程序 I/O请求,简单说就是将目标板的输入/输出请求从应用程序代码传递到远程运行调试器的主机上的一种机制。
ARM 也定义了这种 Semihosting 机制实现,我们在目标 ARM 开发板代码中加入 printf,scanf 等函数调用时,这些函数并不是链接到目标开发板的库函数,而是远程主机交叉编译器中带有的库函数,并且这些库函数被编译时会额外插入软件中断指令,早期的 ARMv7 架构下软中断使用得是 SVC 指令(SWI 指令),而对于 Cortex-M 处理器(ARMv6-M 或者 ARMv7-M 架构),这个软件中断指令是 BKPT。
我们可以在 ARMv6/7/8-M Architecture RM 手册里找到 BKPT 指令定义,其中 imm8 是指定存储在指令中的 8 位值,这个值会被处理器忽略,但是调试器可以使用它来存储关于断点的附加信息。
如果 BKPT 指令是用于触发 Semihosting 请求,其 imm8 值应该被设为 0xAB,这是 ARM 的规定。因此当仿真器捕获到 BKPT #0xab 指令时便会让主机响应接下来的 I/O 请求。
现在可以触发 Semihosting 请求了,但是 I/O 请求的种类很多,主机该如何区分呢?别担心,ARM 还规定了 R0 寄存器来存放请求的类型(编译器应该在 BKPT 指令前,将请求类型值放到 R0 寄存器里),我们可以在 ARM 开发者官网找到这些请求类型定义以及详细解释。整个 Semihosting 机制至此就清楚了,各大 IDE 只需要按照这个规定去具体实现即可。
https://developer.arm.com/documentation/dui0471/g/Semihosting/Semihosting-operations?lang=en
关于更详细的 Semihosting 机制,可以参考 SEGGER 整理的一篇博客 https://wiki.segger.com/Semihosting 。
IAR 对 ARM 定义的 Semihosting 机制是有完善支持的,我们按上一篇的老办法,看工程编译链接后生成的 .map 文件,找到 IAR 实现 Semihosting 的相关源文件。
继续以上一篇文章使用的 \SDK_2.11.0_EVK-MIMXRT1060\boards\evkmimxrt1060\demo_apps\hello_world\iar 工程为例,简单改造一下工程里 hello_world.c 文件里的 main() 函数,将原来代码全部删掉(原来的打印输出涉及恩智浦 SDK 封装,本文没必要关心其实现),只要如下一句打印即可:
#include <stdio.h>
int main(void)
{
printf("hello world.\r\n");
while (1);
}
然后注意工程选项里 Library low-level interface implementation 选项,这里我们选 Semihosted 方式,并且 stdout/stderr 选择 Via semihosting。这时候底层 I/O 完全由 IAR 内置 Semihosting 库来搞定了。
我们将 MCU 目标板供上电,并连接调试器在线跑起来看看效果,在 IAR 菜单栏 View 里打开 Terminal I/O 窗口,全速运行,可以看到有 hello world. 字样打印输出,没有真实的串口线路物理连接,照样能实现打印了。
IAR 的 Terminal I/O 窗口里怎么就能看到打印输出的呢?我们在 \IAR Systems\Embedded Workbench 9.10.2\arm\doc\EWARM_DevelopmentGuide.ENU 手册里找到了玄机,其实是 IAR 里的调试组件 C-SPY 负责响应调试器捕捉到的来自 MCU 的 I/O 访问需求,并负责解释 Semihosting 库源码,然后模拟了对应 I/O 操作。
现在我们再来查看生成的 hello_world.map 文件,除了 dl7M_tln.a 部分多了 XShttio.o 目标文件外,还增加了 shb_l.a 库(里面有一系列 .o 文件),这些增加的 .o 文件均是 Semihosting 库相关源码实现。我们可以在 \IAR Systems\Embedded Workbench 9.10.2\arm\src\lib\semihosting 目录下找到 XShttio.c 源文件,这个主要是 ARM 标准 Semihosting 实现层,但是偏 IAR 这一层的 iarttio.o、iarwrite.o、iarwstd.o 并没有公开源码,这可能属于 IAR 软件商业机密了吧。
*******************************************************************************
*** MODULE SUMMARY
***
Module ro code ro data rw data
------ ------- ------- -------
dl7M_tln.a: [10]
XShttio.o 8 8 8
abort.o 6
exit.o 4
low_level_init.o 4
printf.o 40
putchar.o 32
xfail_s.o 64 4
xprintfsmall_nomb.o 1'281
xprout.o 22
-----------------------------------------------
Total: 1'461 8 12
shb_l.a: [13]
dwrite.o 30
exit.o 20
iarttio.o 124
iarwrite.o 34
iarwstd.o 32
write.o 16
-----------------------------------------------
Total: 256
最后我们再从工程反汇编文件角度看一下 Semihosting 机制是不是如第二小节原理里介绍得那样,先借助 IAR 小工具 ielfdumparm.exe 将工程可执行文件 hello_world.out 转换成反汇编文件 hello_world.dump。
ielfdumparm.exe --source --code .\hello_world.out -o .\hello_world.dump
然后使用任意文本编辑器打开这个反汇编文件 hello_world.dump,在里面搜索 BKPT 指令,确实能够看到插入了多处软中断指令用于触发 Semihosting,并且软中断指令前都装载了 R0 寄存器,痞子衡截取的片段里 R0 装载的值是 5,从 ARM 文档里查询,这对应了 SYS_WRITE 访问请求。
至此,IAR下调试信息输出机制之半主机(Semihosting)痞子衡便介绍完毕了,掌声在哪里~~~
文章会同时发布到我的 博客园主页、CSDN主页、知乎主页、微信公众号 平台上。
微信搜索"痞子衡嵌入式"或者扫描下面二维码,就可以在手机上第一时间看了哦。

大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList()Obt
我遇到了这个奇怪的错误.../Users/gideon/Documents/ca_ruby/rubytactoe/lib/player.rb:13:in`gets':Isadirectory-spec(Errno::EISDIR)player_spec.rb:require_relative'../spec_helper'#theuniverseisvastandinfinite...itcontainsagame....butnoplayersdescribe"tictactoegame"docontext"theplayerclass"doit"musthaveahumanplay
我有两个文本文件,master.txt和926.txt。如果926.txt中有一行不在master.txt中,我想写入一个新文件notinbook.txt。我写了我能想到的最好的东西,但考虑到我是一个糟糕的/新手程序员,它失败了。这是我的东西g=File.new("notinbook.txt","w")File.open("926.txt","r")do|f|while(line=f.gets)x=line.chompifFile.open("master.txt","w")do|h|endwhile(line=h.gets)ifline.chomp!=xputslineendende
我使用raise(ConfigurationError.new(msg))引发错误我试着用rspec测试一下:expect{Base.configuration.username}.toraise_error(ConfigurationError,message)但这行不通。我该如何测试呢?目标是匹配message。 最佳答案 您可以使用正则表达式匹配错误消息:it{expect{Foo.bar}.toraise_error(NoMethodError,/private/)}这将检查NoMethodError是否由privateme
我最近一直在查看一些gem的源代码。我经常看到的一个习惯用法是使用嵌套模块,其中包含连接到版本字符串中的版本常量,即围绕此类事物的变体:moduleChunkyBaconmoduleVersionMAJOR=0MINOR=6TINY=2endVERSION=[Version::MAJOR,Version::MINOR,Version::TINY].compact*'.'end以这种方式存储库版本信息有什么好处(如果有的话)?为什么不这样做:moduleChunkyBaconVERSION='0.6.2'.freezeend 最佳答案
如何使用如下两个数组构建一个数组:名称=[a,b,c]how_many_of_each[3,5,2]得到my_array=[a,a,a,b,b,b,b,b,c,c] 最佳答案 使用zip、flat_map和数组乘法:irb(main):001:0>value=[:a,:b,:c]=>[:a,:b,:c]irb(main):002:0>times=[3,5,2]=>[3,5,2]irb(main):003:0>value.zip(times).flat_map{|v,t|[v]*t}=>[:a,:a,:a,:b,:b,:b,:b,:b
如何在Ruby中获取linux系统(这必须适用于Fedora、Ubuntu等)的软件/硬件信息? 最佳答案 Chef背后的优秀人才,拥有一颗名为Ohai的优秀gemhttps://github.com/opscode/ohai以散列形式返回系统信息,例如操作系统、内核、规范、fqdn、磁盘、空间、内存、用户、接口(interface)、sshkey等。它非常完整,非常好。它还会安装命令行二进制文件(也称为ohai)。 关于ruby-如何在Ruby中获取linux系统信息,我们在Stack
我正在尝试构建一个seeds.rb文件以将初始管理员用户添加到数据库中。我有一个用户表和模型,以及一个角色表和模型。我有一个连接表,roles_users来加入用户角色和权限。这是架构:create_table"roles",:force=>truedo|t|t.string"name"t.datetime"created_at"t.datetime"updated_at"endcreate_table"roles_users",:id=>false,:force=>truedo|t|t.integer"role_id"t.integer"user_id"endcreate_table