0> 学习思路:先看下标准现场保护与恢复,然后通过不实现现场保护与恢复发生的问题,理解5行神奇代码。
1>【标准现场保护与恢复】 示例:
-
test.c
-
-
#include <stdio.h>
-
-
void fun(int a)
-
{
-
printf("***********************%d*****************",a);
-
}
-
-
int main(void)
-
{
-
int i = 1993;
-
-
fun(i);
-
-
return 0;
-
}
arm-linux-gcc
-g ap.c -mapcs // -g:加了调试 -mapcs:完全安照APCS规编译
arm-linux-objdump
-dS a.out >a.S //带C语言反汇编
vim
a.S :
mov
ip, sp
push
{fp, ip, lr, pc}
sub
fp, ip, #4
...
sub
sp, fp, #12
ldm
sp, {fp, sp, pc}
man函数和fun函数里都有这5行代码,他们就是传说中的保护现场,恢复现场。
下边就看看这5行的神奇。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
2>【问题1】源码:test.c
-
#include <stdio.h>
-
-
__asm__(
-
-
"fool:\n\t"
-
"adr r0, str\n\t" //取地址 等价于:"ldr r0, = str\n\t"
-
"ldr r1, data\n\t" //取值
-
-
"bl printf\n\t" //问题就出在这。。。。。此处修改了lr。
-
-
"mov pc, lr\n\t" //函数返回 //最终死循环在此。
-
-
"str:\n\t"
-
".string \"This year is %d.\\n\"\n\t"
-
"data:\n\t"
-
".word 2015\n\t"
-
-
);
-
-
int main(void)
-
{
-
-
__asm__ __volatile__(
-
-
"mov lr, pc\n\t" //保存print地址到lr。
-
"ldr pc, =fool\n\t" //伪指令
-
-
);
-
-
printf("Come back to %s!\n", __func__);
-
-
return 0;
-
}
编译:arm-linux-gcc
test.c
运行:./a.out:
[root@FriendlyARM
/mnt]# ./a.out
This
year is 2015.
_
死循环了,程序回不来了,分析...
man函数【25】行:lr中保存了子程序【fool】的返回地址,fool中 "bl printf\n\t" 覆盖了lr中的值(此时的lr中的值是【11】行地址),
当fool中的printf执行完,将lr赋给pc,
执行 "mov pc, lr\n\t" 形成死循环。
指令注解:
1》 "ldr r1, data\n\t"
数据加载指令:将内存中的数据加载到寄存器。此指令是标号寻址。
pc是隐含的基址寄存器,偏移量是语句标号所在地址和pc(正在执行的指令)之间差值。
2》"ldr pc, =fool\n\t"
加载32位立即数或一个地址到指定寄存器。
解决办法:
-
#include <stdio.h>
-
-
__asm__(
-
-
"fool:\n\t"
-
"adr r0, str\n\t" //取地址 等价于:"ldr r0, = str\n\t"
-
"ldr r1, data\n\t" //取值
-
-
"push {lr}\n\t"
-
-
"bl printf\n\t"
-
-
"pop {pc}\n\t"
-
-
"str:\n\t"
-
".string \"This year is %d.\\n\"\n\t"
-
"data:\n\t"
-
".word 2015\n\t"
-
-
);
-
-
int main(void)
-
{
-
-
__asm__ __volatile__(
-
-
"mov lr, pc\n\t" //保存print地址到lr。
-
"ldr pc, =fool\n\t" //伪指令
-
-
);
-
-
printf("Come back to %s!\n", __func__);
-
-
return 0;
-
}
分析:
"push {lr}\n\t" 指令相等于:"sub sp, sp, #4\n\t" //因为栈是满递减的,所以先偏移4个字节。
"str lr, [sp]\n\t" //将lr的值保存到
"pop {pc}\n\t" 指令相等于:"ldr lr, [sp]\n\t" //从栈中取出lr原值,保存到lr寄存器。
"add sp, sp, #4\n\t" //sp+4移回原处。
“mov pc, lr\n\t" //返回
问题解决
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
3> 【问题2】细想【问题1】你会发现有非常严重的问题。
栈 : 在程序中用的是非常频繁的,因为定义的局部变量就存放在栈中,所以要经常push,pop,sp的值就会一直改变。
这样当子程序返回时,你找lr的值,以及恢复到原来sp的值是非常木乱的 。
解决:思路:
"mov ip, sp\n\t" //将sp的值先保存到ip。
"mov sp, ip\n\t" //将ip的值赋给sp,还原了。
还有问题:APCS规则中ip是会改变的, 所以也得存入栈中,"push {ip,lr}\n\t" .
这样问题还是没解决:当子程序退出时你还得通过,【sp,#4×num】移动sp找到ip,lr的值,才能返回。
继续解决:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXx
好吧 , 借鉴标准:
-
#include <stdio.h>
-
-
void fun(int a)
-
{
-
int q = 0x111111;
-
int w = 0x222222;
-
int c = 0x333333;
-
printf("***********************%x*****************", a);
-
}
-
-
int main(void)
-
{
-
int i = 0x444444;
-
-
fun(i);
-
-
return 0;
-
}
-
000083b4 :
-
#include
-
void fun(int a)
-
{
-
83b4: e1a0c00d mov ip, sp
-
83b8: e92dd800 push {fp, ip, lr, pc}
-
83bc: e24cb004 sub fp, ip, #4
-
-
83c0: e24dd018 sub sp, sp, #24
-
83c4: e50b0020 str r0, [fp, #-32]
-
-
int q = 0x111111;
-
83c8: e59f3028 ldr r3, [pc, #40] ; 83f8 <fun+0x44>
-
83cc: e50b3010 str r3, [fp, #-16]
-
-
int w = 0x222222;
-
83d0: e59f3024 ldr r3, [pc, #36] ; 83fc <fun+0x48>
-
83d4: e50b3014 str r3, [fp, #-20]
-
-
int c = 0x333333;
-
83d8: e59f3020 ldr r3, [pc, #32] ; 8400 <fun+0x4c>
-
83dc: e50b3018 str r3, [fp, #-24]
-
printf("***********************%x*****************", a);
-
83e0: e59f301c ldr r3, [pc, #28] ; 8404 <fun+0x50>
-
83e4: e1a00003 mov r0, r3
-
83e8: e51b1020 ldr r1, [fp, #-32]
-
83ec: ebffffc2 bl 82fc <_init+0x44>
-
}
-
83f0: e24bd00c sub sp, fp, #12
-
83f4: e89da800 ldm sp, {fp, sp, pc}
-
-
83f8: 00111111 .word 0x00111111
-
83fc: 00222222 .word 0x00222222
-
8400: 00333333 .word 0x00333333
-
8404: 000084b4 .word 0x000084b4
-
-
00008408 <main>:
-
-
-
int main(void)
-
{
-
8408: e1a0c00d mov ip, sp
-
840c: e92dd800 push {fp, ip, lr, pc}
-
8410: e24cb004 sub fp, ip, #4
-
-
8414: e24dd008 sub sp, sp, #8
-
-
int i = 0x444444;
-
8418: e59f3018 ldr r3, [pc, #24] ; 8438 <main+0x30>
-
841c: e50b3010 str r3, [fp, #-16]
-
-
fun(i);
-
8420: e51b0010 ldr r0, [fp, #-16]
-
8424: ebffffe2 bl 83b4 <fun>
-
-
return 0;
-
8428: e3a03000 mov r3, #0
-
}
-
842c: e1a00003 mov r0, r3
-
-
8430: e24bd00c sub sp, fp, #12
-
8434: e89da800 ldm sp, {fp, sp, pc}
-
-
8438: 00444444 .word 0x00444444
分析:
mov ip, sp //ip寄存器保存sp寄存器的值。
push {fp, ip, lr, pc} //可以不考虑, pc用作调试。
sub fp, ip, #4
分析fun函数 3条指令后栈中的造型:
高地址 →
|
。。。
|
|
原sp指向位置 Sp →
|
。。。
|
|
|
Pc
|
← fp(ip-4)
|
|
lr
|
|
|
ip
|
|
现sp指向位置 Sp →
|
fp
|
<======sp恢复现场时
|
|
|
<------------------------fp-16
|
|
|
<---------------fp-20
|
低地址 →
|
|
<--------fp-24
|
|
|
|
|
|
|
|
|
<----------------[sp-24]
|
|
|
|
得到结果是:fp是帧指针寄存器, sub sp, sp, #24:指令先分配内存单元,以后的操作都以fp为基准。
恢复现场:
sub sp, fp, #12 //:sp指向 【sp恢复现场时】
ldm sp, {fp, sp, pc} //相等于: ldmia sp, {fp, sp, pc}
这样就是 fp <- fp, sp <- ip, pc <- lr // fp, sp的值都恢复, 函数也返回了。
不要写成 ldmia sp!, {fp, sp, pc}
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
总结:
ip:保存sp原值,用于恢复sp。
fp: 保存整个子程序栈的起始地址,对栈的操作,以它为坐标。瞧它的名字帧指针寄存器, 帧,恰当!!!
sp:分配栈。这儿请注意,一定要分配足够,并且sp要更新,不然, 再调一个子程序时就会把主调函数的栈内容覆盖。
这样,再也不用计算sp该偏移多少才能找到ip, lr的值。
注意:
1》除了r15,其余都为通用寄存器,没什么特别的,只是APCS,一种规则,你完全可以用,r5等代替fp。
有疑问r13,也是通用的吗?必须是,只是APCS规定。
2》上面说的某个寄存器保存什么值,但你要明白,他们值都保存在栈当中。
阅读(2664) | 评论(0) | 转发(0) |