semihosting
semihosting
周⼯的⼈说:(__use_no_semihosting_swi)这段语句是编译器预定义的,就是不使⽤编译器⾃带的软中断服务函数。该软中断的中断号为0x123456,⽤来实现⼀些调试信息和系统调⽤。具体参看相关的编译器⼿册。
查了下ADS调试⼿册DebugTargetGuide,第五章讲的就是Semihosting
原⽂上来第⼀句话就是
Semihosting provides code running on an ARM target use of facilities on a host computer that is running an ARM debugger. Examples of such facilities include the keyboard input, screen output, and disk I/O.
Semihosting is a mechanism for ARM targets to communicate input/output requests from application code to a host computer running a debugger. This mechanism could be used, for example, to allow functions in the C library, such as printf() and scanf(), to use the screen and keyboard of the host rather than having a screen and keyboard on the target system.
Semihosting is implemented by a set of defined software interrupt (SWI) operations. The application invokes the appropriate SWI and the debug agent then handles the SWI exception. The debug agent provides the required communication with the host.
In many cases, the semihosting SWI will be invoked by code within library functions. The application can also invoke the semihosting SWI directly. Refer to the C library descriptions in the ADS Compilers and Libraries Guide for more information on support for semihosting in the ARM C library.
Figure 5-1 Semihosting overview
看了这个明⽩点了。下⾯再来⽹友的两篇中⽂⽂章吧
blog.csdn/wenlifu71022/archive/2008/09/11/2913776.aspx
semihosting 前前后后
与传统的4/8位单⽚机相⽐,ARM的性能和处理能⼒当然是遥遥领先的,但与之相应,ARM的系统设计复杂度和难度,较之传统的设计⽅法也⼤⼤提升了。本⽂旨在通过讨论系统程序设计中的⼏个基本⽅⾯,来说明基于ARM的嵌⼊式系统程序开发的⼀些特点,并提出和解决了⼀些常见的问题。
  本⽂分成⼏个相对独⽴的专题陆续刊载。
  (⼀)嵌⼊式程序开发基本概念
  (⼆)系统的初始化过程
  (三)如何满⾜嵌⼊式系统的灵活需求
  (四)异常处理机制的设计
  (五)  ARM/Thumb的交互⼯作
  (六)开发⾼效程序的技巧
1  嵌⼊式程序开发过程
  不同于通⽤计算机和⼯作站上的软件开发⼯程,⼀个嵌⼊式程序的开发过程具有很多特点和不确定性。其中最重要的⼀点是软件跟硬件的紧密耦合特性。
  图1是两类简化的嵌⼊式系统层次结构图。由于嵌⼊式系统的灵活性和多样性,图1中各个层次之间缺乏统⼀的标准,⼏乎每⼀个独⽴的系统都不⼀样。这样就给上层的软件设计⼈员带来了极⼤地困难。第⼀,在软件设计过程中过多地考虑硬件,给开发和调试都带来了很多不便;第⼆,如果所有的软件⼯
作都需要在硬件平台就绪之后进⾏,⾃然就延长了整个的系统开发周期。这些都是应该从⽅法上加以改进和避免的问题。
图1  两类不同的嵌⼊式系统结构模型
  为了解决这个问题,⼯程和设计⼈员提出了许多对策。⾸先在应⽤与驱动(或API)这⼀层接⼝,可以设计成相对统⼀的⼀些接⼝函数,这对于具体的某⼀个开发平台或在某个公司内部,是完全做得到的。这样⼀来,就⼤⼤提⾼了应⽤层软件设计的标准化程度,⽅便了应⽤程序在跨平台之间的复⽤和移植。
  对于驱动/硬件抽象这⼀层,因为直接驱动硬件,其标准化变得⾮常困难甚⾄不太可能。但是为了简化程序的调试和缩短开发周期,我们可以在特定的EDA⼯具环境下⾯进⾏开发,通过后再移植到硬件平台上。这样既可以保证程序逻辑设计的正确性,同时使得软件开发可⾏甚⾄超前于硬件开发进程。
  我们把脱离于硬件的嵌⼊式软件开发阶段称之为“PC软件”的开发,可以⽤图2来⽰意⼀个嵌⼊式系统程序的开发过程。
图2  嵌⼊式系统程序的开发过程
  在“PC软件”开发阶段,可以⽤软件仿真,即指令集模拟的⽅法,来对⽤户程序进⾏验证。在ARM公
司的开发⼯具中,ADS内嵌的 ARMulator和RealView开发⼯具中的ISS,都提供了这项功能。在模拟环境下,⽤户可以设置ARM处理器的型号、时钟频率等,同时还可以配置存储器访问接⼝的时序参数。程序在模拟环境下运⾏,不但能够进⾏程序的运⾏流程和逻辑测试,还能够统计系统运⾏的时钟周期数、存储器访问周期数、处理器运⾏时的流⽔线状态(有效周期、等待周期、连续和⾮连续访问周期)等信息。这些宝贵的信息是在硬件调试阶段都⽆法取得的,对于程序的性能评估⾮常有价值。
  为了更加完整和真实地模拟⼀个⽬标系统,ARMulator和ISS还提供了⼀个开放的API编程环境。⽤户可以⽤标准C来描述各种各样的硬件模块,连同⼯具提供的内核模块⼀起,组成⼀个完整的“软”硬件环境。在这个环境下⾯开发的软件,可以更⼤程度地接近最终的⽬标。
  利⽤这种先进的EDA⼯具环境,极⼤地⽅便了程序开发⼈员进⾏嵌⼊式开发的⼯作。当完成⼀个“PC软件”的开发之后,只要进⾏正确的移植,⼀个真正的嵌⼊式软件就开发成功了。⽽移植过程是相对⽐较容易形成⼀套规范的流程的,其中三个最重要的⽅⾯是:
  ◆考虑硬件对库函数的⽀持
  ◆符合⽬标系统上的存储器资源分布
  ◆应⽤程序运⾏环境的初始化
2  开发⼯具环境⾥⾯的库函数
  如果⽤户程序⾥调⽤了跟⽬标相关的⼀些库函数,则在应⽤前需要裁减这些函数以适合在⽬标上允许的要求。主要需要考虑以下三类函数:
  ◆访问静态数据的函数
  ◆访问⽬标存储器的函数
  ◆使⽤semihosting(半主机)机制实现的函数
  这⾥所指的C库函数,除了ISO C标准⾥⾯定义的函数以外,还包括由编译⼯具提供的另外⼀些扩展函数和编译辅助函数。2.1  裁减访问静态数据的函数
  库函数⾥⾯的静态数据,基本上都是在头⽂件⾥⾯加以定义的。⽐如CTYPE类库函数,其返回值都是通过预定义好的CTYPE属性表来获得的。⽐如,想要改变isalpha() 函数的缺省判断,则需要修改对应CTYPE属性表⾥对字符属性的定义。
2.2  裁减访问⽬标存储器的函数
  有⼀类动态内存管理函数,如malloc() 等,其本⾝是独⽴于⽬标系统⽽运⾏的;但是它所使⽤的存储器空间需要根据⽬标来确定。所以malloc() 函数本⾝并不需要裁减或移植,但那些设置动态内存区(地址和空间)的函数则是跟⽬标系统的存储器分布直接相关的,需要进⾏移植。例如堆栈的初始化函数 __user_initial_stackheap(),是⽤来设置堆(heap)和栈(stack)地址的函数。显然,针对每⼀个具体的⽬标平台,该函数都需要根据具体的⽬标存储器资源进⾏正确移植。
  下⾯是对⽰例函数__user_initial_stackheap() 进⾏移植的⼀个例⼦:
__value_in_regs struct __initial_stackheap __user_initial_stackheap(
  unsigned R0, unsigned SP, unsigned R2, unsigned SL)
{
  struct __initial_stackheap config;
  config.heap_base = (unsigned int) 0x11110000;
  // config.stack_base = SP; // optional
  return config;
}
  请注意上⾯的函数体并不完全遵循标准C的关键字和语法规范,使⽤了ARM公司编译器(ADS或RealView Compilation tool)⾥的C语⾔扩展特性。关于编译器特定的C语⾔扩展,请参考相关的编译器说明,这⾥简单介绍函数__user_initial_stackheap() 的功能,它主要是返回堆和栈的基地址。上⾯的程序中只对堆(heap) 的基地址进⾏了设置(设成了0x11110000),也就是说⽤户把0x11110000开始的存储器地址⽤作了动态内存分配区(heap区)。具体地址的确定是要由⽤户根据⾃⼰的⽬标系统和应⽤情况来确定的,⾄少要满⾜以下条件:
  ◆ 0x11110000开始的地址空间有效且可写(是RAM)
  ◆该存储器空间不与其它功能区冲突(⽐如代码区、数据区、stack区等)
  因为__user_initial_stackheap() 函数的全部执⾏效果就是返回⼀些数值,所以只要符合接⼝的调⽤标准,直接⽤汇编来实现看起来更加直观⼀些:
  EXPORT __user_initial_stackheap
  __user_initial_stackheap
  LDR r0,0x11110000
  MOV pc,lr
  如果不对这个函数进⾏移植,编译过程中将使⽤缺省的设置,这个设置适⽤于ARM公司的Integrator系列平台。
2.3  裁减使⽤半主机机制实现的函数
  库函数⾥有⼀⼤部分函数是涉及到输⼊/输出流设备的,⽐如⽂件操作函数需要访问磁盘I/O,打印函数需要访问字符输出设备等。在嵌⼊式调试环境下,所有的标准C库函数都是有效且有其缺省⾏为的,很多⽬标系统硬件不能⽀持的操作,都通过调试⼯具来完成了。⽐如printf() 函数,缺省的输出设备是调试器⾥⾯的信息输出窗⼝。
  但是⼀个真实的系统是需要脱离调试⼯具⽽独⽴运⾏的,所以在程序的移植过程当中,需先对这些库函数的运⾏机制作⼀了解。
  图3说明了在ADS下⾯这类C库函数的结构。
图3  C库函数实现过程中的层次调⽤
  如图4中例⼦所⽰,函数printf() 最终是调⽤了底层的输⼊/输出函数_sys_write() 来实现输出操作的,⽽_sys_write() 使⽤了调试⼯具的内部机制来把信息输出到调试器。
图4  printf() 的调试过程
  显然这样的函数调⽤过程在⼀个真实的嵌⼊式系统⾥是⽆法实现的,因为独⽴运⾏的嵌⼊式系统将不会有调试器的参与。如果在最终系统中仍然要保留printf() 函数,⽽且在系统硬件中具备正确的输出设备(如LCD等),则在移植过程中,需要把printf()调⽤的输出设备进⾏重新定向。
  单纯考虑printf() 的输出重新定向,可以有三种途径实现:
  ◆改写printf() 本⾝
  ◆改写 fput()
  ◆改写 _sys_write()
  需要注意的是,越底层的函数,被其他上层函数调⽤的可能性越⼤,改变了⼀个底层函数的实现,则所有调⽤该函数的上层函数的⾏为都被改变了。
  以fputc() 的重新实现为例,下⾯是改变fputc() 输出设备到系统串⾏通信端⼝的实例:
int fputc(int ch, FILE *f)
{
  char tempch = ch;
  sendchar(&tempch); // UART driver
  return ch; }
  代码中的函数sendchar() 假定是系统的串⼝设备驱动函数。只要新建函数fput() 的接⼝符合标准,经过编译链接后,该函数实现就覆盖了原来缺省的函数体,所有对该函数的调⽤,其⾏为都被新实现的函数所重新定向了。
3  Semihosting 机制
  上⾯提到许多库函数在调试环境下的实现都调⽤了⼀种叫作semihosting(半主机)的机制。Semihosting具体来讲是指⼀种让代码在ARM ⽬标上运⾏,但使⽤运⾏了ARM 调试器的主机上I/O 设备;也就是让ARM ⽬标将输⼊/ 输出请求从应⽤程序代码传递到运⾏调试器的主机的⼀种机制。通常这些输⼊/输出设备包括键盘、屏幕和磁盘I/O。
  半主机由⼀组已定义的SWI 操作来实现,如图5所⽰。库函数调⽤相应的SWI(软件中断),然后调
试代理程序处理SWI 异常,并提供所需的与主机之间的通讯。多数情况下,半主机SWI 是由库函数内的代码调⽤的。但是应⽤程序也可以直接调⽤半主机SWI。半主机SWI 的接⼝函数是通⽤的。当半主机操作在硬件仿真器、指令集仿真器、RealMonitor或Angel下执⾏时,不需要进⾏移植处理。
图5  semihosting的实现过程
  使⽤单个SWI 编号请求半主机操作。其它的SWI 编号可供应⽤程序或操作系统使⽤。⽤于半主机的SWI号是:
  在ARM 状态下:0x123456
  在Thumb 状态下:0xAB
  SWI 编号向调试代理程序指⽰该SWI 请求是半主机请求。要辨别具体的操作类型,⽤寄存器r0 作为参数传递。r0 传递的可⽤半主机操作编号分配如下:
  ◆ 0x00~0x31:这些编号由ARM 公司使⽤,分别对应32个具体的执⾏函数。
  ◆ 0x32~0xFF:这些编号由ARM 公司保留,以备将来⽤作函数扩展。
  ◆ 0x100~0x1FF:这些编号保留给⽤户应⽤程序。但是,如果编写⾃⼰的SWI 操作,建议直接使⽤SWI指令和SWI编号,⽽不要使⽤半主机SWI 编号加这些操作类型编号的⽅法。
  ◆ 0x200~0xFFFFFFFF:这些编号未定义。当前未使⽤并且不推荐使⽤这些编号。半主机SWI使⽤的软件中断编号也可以由⽤户⾃定义,但若是改变了缺省的软中断编号,需要:
  ◆更改系统中所有代码(包括库代码)的半主机SWI 调⽤
  ◆重新配置调试器对半主机请求的捕捉与响应这样才能使⽤新的SWI 编号。
4  应⽤环境的初始化和根据⽬标系统资源进⾏的移植
blog.csdn/wenlifu71022/archive/2008/09/11/2913654.aspx
ADS 中的 semihosting 问题
最近在做⼀个移植UCOS-II的任务,遇到了semihosting的问题,现在把这个问题的解决办法总结⼀下。
Step 1 : ⽤#pragma import(__use_no_semihosting_swi)保证⽤户程序不调⽤semihostSWI;
此后,link时会有
Error :L6200E:Symbol __semihosting_swi_guard multiply defined (by use_semi.o and use_no_semi.o)
这是因为,还有compiler helper functions 和 initialization code在调⽤semihostSWI (This error is reported when functions that use semihosting SWIs are linked in from the Clibrary, in the presence of the __use_no_semihosting_swi guard)
Step 2 : armlink -verbose 结果中,查出调⽤semihostSWI的外部库函数,如:
Loading member sys_exit.o from c_a__un.l.
definition:  _sys_exit
reference :  __I_use_semihosting_swi
在报错的窗⼝中寻__I_use_semihosting_swi很⿇烦,我们可以再在连接器中修改⼀下参数如下:
Link with 'ARMlink -verbose -'
-
---------------------------------------
For example:
Loading member sys_exit.o from c_a__un.l.
reference : __I_use_semihosting_swi
definition: _sys_exit
:This shows that the SWI-using function _sys_exit is being linked-in from the C library. To
isalpha 函数prevent this, you will need to provide your own implementation of this function.
----------------------------------------
Step 3 : 在⽤户程序中重写这些函数,如:
AREA ||.text||, CODE, READONLY
__user_initial_stackheap
LDR r0, =0x20000 ; HB
LDR r1, =0x40000 ; SB
; r2 not used (HL)
; r3 not used (SL)
MOV PC,LR
EXPORT __user_initial_stackheap
END
Step 4 : compile and link OK!
hi.baidu/ap314/blog/item/fb855b3c700ab0eb3c6d9740.html
这是今天(2009-7-23)发现的⼀篇⽂章,收了
⼀.关于函数__user_initial_stackheap
_rt—stackheap_init()建⽴C库使⽤的内存模型--堆和栈。因为ARM库是建⽴在semihosted执⾏环境的,它实现的内存模型是基于这个环境的,所以必须修改这个内存模型建⽴机制。表1列出了需要重新实现的函数,实现了这些函数,应⽤程序就可以脱离宿主机环境独⽴运⾏了。其中,必须重新实现的是_user initial_stackheap(),因为默认的实现是基于semihosted执⾏环境的,该函数被_n_stackheap_init()调⽤创建内存模型,其他两个函数没有默认的实现。
-scatter file
这个选项使⽤在file中包含的分组和定位信息来创建映像内存映射。
注意,如果使⽤了该选项的话,必须要重新实现堆栈初始化函数_user_initial_stackheap().
即当⽤户使⽤分散装载功能的时候,必须重调⽤_user_initial_stackheap(),否则连接器会报错:
Error: L6218E: Undefined symbol Image$$ZI$$Limit (referred from sys_stackheap.o)
实现该函数,必须满⾜下⾯的条件:
◇使⽤不超过96字节的栈空间;
◇除了R12(ip)外不要污染其他寄存器;
◇将堆基址、栈基址、堆边界和栈边界分别存在RO~R3作为返回参数;
◇堆必须保持8个字节对齐。
ADS帮助附注:
The values returned in r0 to r3 depend on whether you are using the one or two region model:
One region
(r0,r1) is the single stack and heap region. r1 is greater than r0. r2 and r3 are ignored.
Two regions
(r0, r2) is the initial heap and (r3, r1) is the initial stack. r2 is greater than or equal to r0. r3 is less than r1.
⼆:关于    IMPORT __use_no_semihosting_swi
IMPORT __use_two_region_memory
ADS 提供了两种实时存储器模型。缺省时为one-region,应⽤程序的堆栈和heap位于同⼀个存储器区块,使⽤的时候相向⽣长,当在heap区分配⼀块存储器空间时需要检查堆栈指针。另⼀种情况是堆栈和heap使⽤两块独⽴的存储器区域。对于速度特别快的RAM,可选择只⽤来作堆栈使⽤。为了使⽤这种 two-region模型,⽤户需要导⼊符
号use_two_region_memory,heap使⽤需要检查heap的长度限制值。
对这两种模型来说,缺省情况下对堆栈的⽣长都不进⾏检查。⽤户可以在程序编译时使⽤ -apcs/swst 编译器选项来进⾏软件堆栈检查。如果使⽤two-region模型,必须得在执⾏_user_initial_stackheap时指定⼀个堆栈限制值。
Semihosting
机制有⼀个专门术语
在ADS 的C语⾔函数库中,某些ANSIC的功能是由主机的调试环境来提供的,这套机制
叫Semihosting。Semihosting通过⼀组软件中断(SWI)指令来实现。如图1所⽰,当⼀个Semihosting软中断被执⾏时,调试系统先识别这个SWI请求,然后挂起正在运⾏的程序,调⽤Semihosting的服务,完成后再恢复原来的程序执⾏。因此,主机执⾏的任务对于程序来说是透明的。
在C语⾔库函数中禁⽤Semihosting
在⼀个独⽴的嵌⼊式应⽤程序中,应该不存在SemihostingSWI操作。因此,⽤户必须确定在所有调⽤到的库函数中没有使
⽤Semihosting。为了保证这⼀点,在程序中可以引进⼀个符号关键字_use_no_semihosting:
在C代码中,使⽤#prgrama #pragmaimport〈_use_no_semihosting_swi〉
在汇编程序中,使⽤IMPORT
IMPORT_use_no_semihosting_swi
这样,当有使⽤SWI机制
机制的库函数被连接时,连接器会进⾏报错:
Error:Symbol_semihosting_swi_guardmultiplydefined
为了确定具体是哪⼀个函数,连接时打开-verbose选项。这样在结果信息输出时,该库函数上将有⼀个
_I_use_semihosting_swi的标记。
Loadingmembersys_wxit.ofromc_a_un.1.
Definition:_sys_exit
Reference:_I_use_semihosting_swi
⽤户必须要把这些函数定义成⾃⼰的执⾏内容。
有⼀点需要注意,连接器只能报告库函数中被调⽤的Semihosting,对⽤户⾃定义函数中使⽤的Semihosting则不会报错。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。