所谓的半主机模式:semihosting机制

        Semihosting技术将应用程序中的IO请求通过一定的通道传送到主机(host),由主机上的资源响应应用程序的IO请求, 而不是像在主机上执行本地应用程序一样,由应用程序所在的计算机响应应用程序IO请求, 也就是将目标板的输入/输出请求从应用程序代码传递到远程运行调试器的主机的一种机制。 简单来说,目标开发板上通常不会有输入/输出这些外设,开发板运行的代码想要将结果打印出来, 或者获得用户的输入,可以通过请求远程主机IO设备来实现,如:显示器,键盘等。 目标开发板执行代码中加入对输入/输出设备进行访问函数,如:printf,scanf等, 这些函数并不是目标开发板的库函数,而是远程主机交叉编译器中带有的库函数,这些库函数被编译时,编译成一条软件中断指令。 当目标开发板上电运行之后,执行到请求访问输入/输出设备指令时,产生特定中断号的软件中断SWI, 与开发板相连的调试器会先截获目标板SWI请求,由于开发板程序中也可能存在用户自定义软件中断, 为了区分二者,调试器会根据SWI的软中断号来判断是不是semihosting模式IO请求, 如果是,则取出R0寄存器里代表的具体请求号,然后使用远程主机来响应目标板具体IO请求, 而不是开发板本身去处理setmihosting请求。 semihosting仅仅是一种调试手段,它的工作原理就是利用调试器捕捉目标环境运行过程中产生SWI中断, 然后向远程主机调试环境发送对应的调试信息。 也就是说目标开发板通过特定的软件中断指令,借用了远程主机的输入输出设备实现IO请求的访问。

        Semihosting半主机调试模式,只能使用在开发板和调试主机通过仿真器连接的情况下, 也就是说脱离了主机调试环境上述代码不能正常运行。 目标开发板上执行的IO实际上是交给了远程主机来处理实现,正是因为如此,这种方式只适合在调试模式下, 真正的嵌入式系统不可能依赖于主机实现IO处理的,嵌入式系统要想独立出来实现IO请求的处理, 这就需要将输入输出库函数的底层相关硬件实现重定向。

使用ITM机制实现调试,实现printf与scanf, ITM是ARM在推出semihosting之后推出的新一代调试机制。

ITM机制要求使用SWD方式接口,并需要连接SWO线。

What is semihosting?

Semihosting is a mechanism that enables code running on an ARM target to communicate and use the Input/Output facilities on a host computer that is running a debugger.

11.1 What is semihosting?

Examples of these facilities include keyboard input, screen output, and disk I/O. For example, you can use this mechanism to enable functions in the C library, such as printf() and scanf(), to use the screen and keyboard of the host instead of having a screen and keyboard on the target system.

This is useful because development hardware often does not have all the input and output facilities of the final system. Semihosting enables the host computer to provide these facilities.

Semihosting is implemented by a set of defined software instructions, for example SVCs, that generate exceptions from program control. The application invokes the appropriate Semihosting call and the debug agent then handles the exception. The debug agent provides the required communication with the host.

The Semihosting interface is common across all debug agents provided by ARM. Semihosted operations work when you are debugging applications on your development platform, as shown in the following figure:

IOS 半塘效果 下载半塘_arm

In many cases, semihosting is invoked by code within library functions. The application can also invoke the ssemihosting operation directly.

Note

ARM processors use the SVC instructions, formerly known as SWI instructions, to make semihosting calls. However, if you are compiling for an ARMv6-M or ARMv7-M, for example a Cortex-M1 or Cortex-M3 processor, semihosting is implemented using the BKPT instruction.

翻译:半主机是用于 ARM 目标的一种机制,可将来自应用程序代码的输入/输出请求传送至运行调试器的主机。 例如,使用此机制可以启用 C 库中的函数,如 printf() 和 scanf(),来使用主机的屏幕和键盘,而不是在目标系统上配备屏幕和键盘。

这种机制很有用,因为开发时使用的硬件通常没有最终系统的所有输入和输出设备。 半主机可让主机来提供这些设备。

         半主机是通过一组定义好的软件指令(如 SVC)来实现的,这些指令通过程序控制生成异常。 应用程序调用相应的半主机调用,然后调试代理处理该异常。 调试代理提供与主机之间的必需通信。

         半主机接口对 ARM 公司提供的所有调试代理都是通用的。 在无需移植的情况下使用 RealView ARMulator® ISS、指令集系统模型 (ISSM)、实时系统模型 (RTSM)、RealView ICE 或 RealMonitor 时,会执行半主机操作。

         标准库使用半主机模式,半主机是通过一组定义好的软件指令 (如 SVC)SVC 指令 (以前称为 SWI 指令)来实现的,这些指令通过程序控制生成异常。 应用程序调用相应的半主机调用,然后调试代理处理该异常。调试代理(这里的调试代理是仿真器)提供与主机之间的必需通信。也就是说使用半主机模式必须使用仿真器调试。

         ARMv7 之前的 ARM 处理器使用 SVC 指令 (以前称为 SWI 指令)进行半主机调用。 但是,如果要为 ARMv6-M 或 ARMv7-M (如 Cortex™-M1 或 Cortex-M3 处理器)进行编译,请使用 BKPT 指令来实现半主机。

IOS 半塘效果 下载半塘_IOS 半塘效果_02

         简单的来说,半主机模式就是通过仿真器实现开发板在电脑上的输入和输出。和半主机模式功能相同的是ITM调试机制。

 

      上面介绍的半主机和ITM功能相当,他们都是调试机制,开发板均借助仿真器与电脑连接,实现单片机利用主机的屏幕键盘的输入输出。

这两种机制的运行均需要仿真器,否则无法运行。

        开发式一般单片机需要独立运行,开发者应去掉仿真器,把printf函数通过单片机的外设来实现,例如通过开发板的串口,lcd或者sd卡。

        使用keil在半主机模式下,若是使用printf、 fopen等库函数库函数调用,会进入半主机模式,发生软件异常,若此时有半主机调试环境的支持(RealView ISS、ISSM、RealView ICE 和 RealMonitor)进而通过调试器与主机进行交互,则可以进入半主机模式。但是本人使用的是JLINK V9调试器,此调试器应该是不支持半主机的调试。所以程序会进入一个错误的BKPT 0xAB状态。得出结论:并不是keil5完全不支持半主机调试,是需要完全具备半主机调试的所有软硬件平台条件后才可以用半主机来调试代码。

        参考上一篇文章中作者遇到的坑,其实KEIL+Jlink是不能运行半主机模式调试的。而DS5+DSTREAM应该是可以,因为后者在按下debug按钮后,程序全速运行没有在BKPT处中断,从侧面验证了后者是支援的。但是由于printf函数的fputc也重新定向到了UART输出,所以主机host端的IDE应该是看不到相应的输入输出的。但是由于不会中断在BKPT处,所以我当时无法发现此问题。这也导致debug的代码烧录到flash无法运行。

        那么如何禁用半主机模式呢?arm development tools的手册有详细的介绍:

Using the libraries in a nonsemihosting environment
Some C library functions use semihosting. If you use the libraries in a nonsemihosting environment, you must ensure that semihosting function calls are dealt with appropriately.

1.6.4 Using the libraries in a nonsemihosting environment

If you do not want to use semihosting, either:

  • Remove all calls to semihosting functions.
  • Re-implement the lower-level functions, for example, fputc(). You are not required to re-implement all semihosting functions. You must, however, re-implement the functions you are using in your application.
    You must re-implement functions that the C library uses to isolate itself from target dependencies. For example, if you use printf() you must re-implement fputc(). If you do not use the higher-level input/output functions like printf(), you do not have to re-implement the lower-level functions like fputc().
  • Implement a handler for all of the semihosting calls to be handled in your own specific way. One such example is for the handler to intercept the calls, redirecting them to your own nonsemihosted, that is, target-specific, functions.

To guarantee that no functions using semihosting are included in your application, use either:

  • IMPORT __use_no_semihosting from armasm assembly language.
  • #pragma import(__use_no_semihosting) from C.

以上介绍到,有三种方式来解决:

        1. 应用程序中不调用任何半主机库函数

        2. 重新定义半主机库函数的low-level函数,举例,使用到printf,则重新定义fputc函数。如果不重新定义,则编译器会编译出半主机模式的指令,即会有BKPT or SVC等指令。脱离仿真器调试的应用端不能正常运行。

        3. 实现一个统一的handler来处理所有的半主机函数调用,其实就是模拟仿真器,截取半主机函数中断异常调用的行为,这个方法待研究...

还有最后一个方法:

        在应用程序中采用__use_no_semihosting的#prama代码告诉编译器不要生成任何半主机模式的代码。只需要在汇编或者c程序中添加一次即可。

在作者上一篇中的坑,其实作者已经重新定义了fputc函数,但是依然出现半主机命令。后来反汇编发现,__main的库函数调用后,__main有一步是专门处理lib的初始化,不仅仅只有printf,还有_sys_exit(),_sys_open,__ttywrch等等,猜测是因为调用了printf后这些库函数默认会被引用,但是作者只重新定义了printf,所以其它的编译后就出现了半主机模式的问题。

为了一劳永逸,最好就是直接添加#pragma import(__use_no_semihosting)。

而当添加了此#pragran后,linker就会报错,:__use_no_semihosting was requested, but _sys_exit/_sys_open/_ttywrch was referenced

所以其实还是需要重新定义上述的几个库函数的,不然无法顺利编译通过。

网上解决办法如下:

/* 告知连接器不从C库链接使用半主机的函数 */
#pragma import(__use_no_semihosting)

/* 定义 _sys_exit() 以避免使用半主机模式 */
void _sys_exit(int x)
{
    x = x;
}

/* 标准库需要的支持类型 */
struct __FILE
{
    int handle;
};

FILE __stdout;

/*重定义fputc的另外一种方式*/
int fputc(int ch, FILE *stream)
{
    uartwriteFunc
}

添加上述代码后,就不会再报错了。_sys_open和_ttywrch也不用重新定义。