|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?注册
x
本帖最后由 zslhutu 于 2010-8-13 20:44 编辑
最近调试时遇到了点问题,就是SWi这个指令,略解了一二,希望跟大家分享一下,以下均为ARM指令,首先给出一个简单例子:
(0x00000008) BAL SWI_DO ;在异常向量表0x00000008处
;----------------------------------
;BEGIN
;----------------------------------
SWI_DO
;---------------------------------------
;入栈
;--------------------------------------
STMFD sp!, {r0 - r3, r12, lr}
MRS r0, spsr ;为了防止是从SVC模式进来的,所以要保存SPSR
STMFD sp!, {r0}
;-----------------------------------------
; 提取立即数
;-----------------------------------------
TST r0, #0x20
LDR r0, [lr, #-4] ;在指令“SWI #..”指令中提取立即数,立即数是软件
BIC r0, r0, #0xff000000 ;提取的
;---------------------------------------------
;进入功能代码,跳转
;--------------------------------------------
CMP r0, #0x1000000
LDRLS pc, [pc, r0, LSL #2] ;跳到哪了?怎么跳的?在后边详解!
B SWIOutOfRange
;-----------------------------------------------
;SWI功能表 类似向量表
;------------------------------------------
Switable
DCD do_swi_0
DCD do_swi_1
nop
nop
do_swi_0
;----------------------------------------
nop
;我要实现的功能代码,写在此处。。。。
nop
;------------------------------------------
;出栈返回
;-----------------------------------------
LDMFD sp!, {r0}
MSR spsr_cf, r0
LDMFD sp!, {r0 - r3, r12, pc}^
do_swi_1
B do_swi_1 ;假设我这个子功能什么都不做
SWIOutOfRange
B SWIOutOfRange
;---------------------------------------------------
;END!!!!!!!!!!!!1
;----------------------------------------------
上边相当于底层代码了,接下了就是我们再应用程序中调用它了:
int main(void)
{
__asm //C中调用汇编
{
SWI 0
}
return ;
}
这段程序中SWI语句的执行,ARM就会产生异常,从而PC指针跳到0x00000008这个地址(ARM公司做死的),然后执行BAL SWI_DO。接下来就该执行我们写的SWI_DO后边的代码了。在那段代码我想重点解释两个语句;
第一: 提取立即数时 LDR r0, [lr, #-4] 这个代码重点在于lr中是什么?由于SWI异常存的是PC-4,即“SWI 0”这个语句的下一条代码,因此 lr-#4就是"SWI 0"这条指令的地址!因此LDR r0, [lr, #-4] 就是将“SWI 0”指令的二进制编码格式放到了R0,因此只要看官一看SWi编码格式就知道怎么样把立即数提出来的了。
第二:进入功能代码,跳转时,LDRLS pc, [pc, r0, LSL #2] 这个语句困扰了我好几天,一直想不明白,后来终于破解了。在这里R0是#0,但我想以“R0 = 1#”为例(其他的数是一样的),首先假设LDRLS pc, [pc, r0, LSL #2] 这条指令在0x40000100这个地方,再假设do_swi_1标号地址为0x40000150, 那么“DCD do_swi_1”这个伪操作会让0x4000010C这个“字”地址存放值0x40000150。当R0=1时,首先在执行这条指令时PC值是多少呢?根据流水线预取指理论,我们知道在执行这条指令时,其实PC = 0x40000100 + 0x8 = 0x40000108; 因此这条指令被翻译为 pc = [0x40000108 + 0x4] = [0x4000010C] = 0x40000150;(注意0x4000010C可是加方括号的,你懂得~) 你说它接下来怎么做?自然是跳到0x40000150处执行代码。
在第二个语句解释中,需要注意两点,第一左移两位是为了ARM指令的自对齐(伪操作DCD 和B SWIOutOfRange);第二就是DCD表和跳转指令中间隔了一个指令B SWIOutOfRange,因此在考虑PC预取时,刚好抵消了。所以如果中间的指令不是一条,而是多条或没有,则这条跳转指令就要修改了,所以这不是一般的跳转指令而是特殊的跳转指令。
在ARM中存在这样的伪操作"__SWI()"用法如下:
在代码开头声明 __SWI(0) void my_function0(void);
__SWI(1) void my_function0(void);
有了这个声明我可以不采用C中嵌入汇编,我可以这样
int main(void)
{
my_function0();
}
只要这样写就可实现上边main里功能,这回__SWI()这个伪操作会用了吧!
上边写了这么多,问个简单的问题,为什么要用SWI这个东东,如果我想执行do_swi_0(假设已被EXPORT),可以直接在main这样做
int main(void)
{
do_swi_0();
}
这样一样会执行那段代码,不过是直接跳到这个标号执行,没有经过异常。问题就在这里了,就是因为SWI指令能够产生异常,变为系统模式,可以操作USER模式下不能访问的存储器和操作。
说到SWi,大家很容易想到半主机模式,这个问题我没有彻底解决,也没有深究,仅是做了个实验,因为暂时时间比较紧。实验代码如下:
int main(void)
{
char * data1;
data1 = 0x61;
/*注意要在头文件声明它***/
_WriteC(3, data1); //这个函数是printf函数执行时会调用的函数,
//而在retarget.c里有这样的声明
//__swi(0x123456) void _WriteC(unsigned op, char *c);
__asm
{
SWI 0 //这个是刚刚咱们自己写的功能代码
}
return ;
}
如果你在RVDS中运行这个代码,采用ICE来调试,如果不单不调试,就会在RVDS上打印“b” ,同时会执行咱们自己定义的0号功能。这说明什么?我没有定义SWI(0x123456)啊?在执行_WriteC时调用SWI,ARMulator会正确终止你的程序,执行SWI指令来通知仿真器,仿真器可以捕获到该SWI指令,仿真器在地址0x8处设置断点。根据SWI的number来判断这个SWI是不是SEMIHOSTING请求,如果是,再根据具体的semihosting number响应用户的semihosting请求,完成用户的semihosting请求后,返回到SWI的后面一条指令,继续执行,就当没有执行过SWI指令。由于0号功能仿真器不认,所以执行了咱们自己定义的功能代码。
但是,如果你单步调试就会发现问题,它会一步一步的调用,最后会调用SWI 0x123456,这时仿真器不认帐了,它并不接手semihosting请求,而是跳到我们0x08地址执行指令,因为我们我没有做他的服务例程,所以执行服务例程跳转时,跳到了一个“未知”的地方。当然了,单步运行SWI 0,跟之前是一样的了。由于半主机的东西我没怎么看过,时间紧没办法,所以还期望有兴趣者自己探讨。
SWI指令还有一个就是传参的问题,这个问题比较简单,跟函数调用传参原理类似,就是按规定放到特定的寄存器就OK了,一般是R0-R3,同时注意当传参是4个时,需要特别的定义。就不过多说了。
望高手能够指出不对之处,及指点一下相关内容!! |
|