arm汇编进入C函数分析,C函数压栈,出栈,传参,返回值
2016-03-21 19:50
405 查看
[b]环境及代码介绍[/b]
环境和源码
由于有时候要透彻的理解C里面的一些细节问题,所有有必要看看汇编,首先这一切的开始就是从汇编代码进入C的main函数过程。这里不使用编译器自动生成的这部分汇编代码,因为编译器自动生成的代码会涉及环境变量的传递,参数的传递等等一系列问题。以ARM汇编来进行分析。使用一个启动汇编文件和一个main.c的文件,在ARM 2440板子上调试这段程序,使用JLinkExe借助jlink来调试:
init.s:
main.c:
为什么main函数没有使用 int main(int argc,char **argv) 这种形式?因为我这里是使用的自己写的启动汇编文件,由它来完成从汇编到C代码的进入。
寄存器介绍
ARM在任何一种模式下,都可以访问16个通用寄存器(R0-R15)和1-2个状态寄存器(CPSR,SPSR),只是有些寄存器是每种模式下都共用的(R0-R7),另外一些是同名但是使用的是不同硬件单元(其他,每种模式下有所不同)。这里的寄存器有些有特定用途:
R15--PC:程序计数器,指向要取指的那条指令
R14--LR:链接寄存器,保存发生跳转时,下一条指令的地址,方便使用BL跳回
R13--SP:堆栈指针
R12--IP:暂存SP值
R11--FP: 保存堆栈frame的地址
后面的IP, FP可能需要结合实际代码来理解。
另外,编译器在处理C程序的时候,R0通常用作传递返回值,R1-R4用来传递函数参数。
稍微解释下这段汇编代码的 ldr sp,=4096 ,为什么设置为4096?有2个原因:
1.我这里使用的是nand启动,代码在内部4K SRAM里面执行。
2.ARM压栈时采用的是满递减堆栈。
我觉得更准确的讲是由编译器决定的,其实ARM指令里面有各种类型的堆栈操作指令而不是单单的满递减。满递减就是指堆栈的增长方向向下,堆栈指针指向堆栈的顶端。如果是空递减,它会指向堆栈顶端的下一个地址,这个地址未存放有效堆栈数据。其实这里sp = 4096这个内存地址是无法访问的,4K最大的地址是4096-4,因此进行数据压栈时,要先调整堆栈指针,然后再压入数据,这也是所有满类型堆栈要遵循的原则。
反汇编分析压栈出栈
使用 arm-linux-objdump -DS main.elf > dump 进行反汇编
可以看到进入C函数第一步就是压栈操作,出C函数里面出栈操作,然后跳转返回。关于push,pop ARM官方的文档给出的说明:
仅仅是个别名而已,并且是针对sp寄存器进行操作。
由于我这里的main过于简单,所有并看不出说明名堂,在main中增加点东西:
继续反汇编,只关注main:
可以看到有一对互为逆向操作的指令组合 push {fp}; add fp, sp, #0 <-------> add sp, fp, #0;pop {fp},在这对组合指令之间的代码是不会去修改fp的值的,这样就实现了恢复调用前fp sp的值,而在它们之间的指令是通过修改sp来访问堆栈。但是这里有个问题,此处我仅定义了一个int型变量,为何堆栈向下偏移了12个字节?按道理sp-4即可。未找到原因,虽然对于堆栈,Procedure Call Standard for the ARM Architecture,要求遵守几个约定,比如堆栈指针必须是4字节对齐,此外,对于public interface即全局的接口,要求sp 8字节对齐。这里我的main算是个public interface,因此8字节对齐必须遵守,但是sp-4也是8字节对齐啊,搞不清为什么-12。增加局部变量可以很明细看出8字节对齐的约定。
传参
反汇编:
可以看到参数通过R0-R3寄存器传递过去,函数里面将寄存器值压栈,要用时从栈里面取出值即可。当寄存器不够用时,总共超过4个字长度,就会通过堆栈传递了:
返回值好像也是通过寄存器或者堆栈传递。
环境和源码
由于有时候要透彻的理解C里面的一些细节问题,所有有必要看看汇编,首先这一切的开始就是从汇编代码进入C的main函数过程。这里不使用编译器自动生成的这部分汇编代码,因为编译器自动生成的代码会涉及环境变量的传递,参数的传递等等一系列问题。以ARM汇编来进行分析。使用一个启动汇编文件和一个main.c的文件,在ARM 2440板子上调试这段程序,使用JLinkExe借助jlink来调试:
init.s:
.text .global _start _start: ldr sp,=4096 @设置堆栈指针以便调用C函数 bl main loop: b loop
main.c:
void main(void) { }
为什么main函数没有使用 int main(int argc,char **argv) 这种形式?因为我这里是使用的自己写的启动汇编文件,由它来完成从汇编到C代码的进入。
寄存器介绍
ARM在任何一种模式下,都可以访问16个通用寄存器(R0-R15)和1-2个状态寄存器(CPSR,SPSR),只是有些寄存器是每种模式下都共用的(R0-R7),另外一些是同名但是使用的是不同硬件单元(其他,每种模式下有所不同)。这里的寄存器有些有特定用途:
R15--PC:程序计数器,指向要取指的那条指令
R14--LR:链接寄存器,保存发生跳转时,下一条指令的地址,方便使用BL跳回
R13--SP:堆栈指针
R12--IP:暂存SP值
R11--FP: 保存堆栈frame的地址
后面的IP, FP可能需要结合实际代码来理解。
另外,编译器在处理C程序的时候,R0通常用作传递返回值,R1-R4用来传递函数参数。
稍微解释下这段汇编代码的 ldr sp,=4096 ,为什么设置为4096?有2个原因:
1.我这里使用的是nand启动,代码在内部4K SRAM里面执行。
2.ARM压栈时采用的是满递减堆栈。
我觉得更准确的讲是由编译器决定的,其实ARM指令里面有各种类型的堆栈操作指令而不是单单的满递减。满递减就是指堆栈的增长方向向下,堆栈指针指向堆栈的顶端。如果是空递减,它会指向堆栈顶端的下一个地址,这个地址未存放有效堆栈数据。其实这里sp = 4096这个内存地址是无法访问的,4K最大的地址是4096-4,因此进行数据压栈时,要先调整堆栈指针,然后再压入数据,这也是所有满类型堆栈要遵循的原则。
反汇编分析压栈出栈
使用 arm-linux-objdump -DS main.elf > dump 进行反汇编
00000000 <_start>: .text .global _start _start: ldr sp,=4096 0: e3a0da01 mov sp, #4096 ; 0x1000 bl main 4: eb000000 bl c <main> 00000008 <loop>: loop: b loop 8: eafffffe b 8 <loop> 0000000c <main>: void main(void) { c: e52db004 push {fp} ; (str fp, [sp, #-4]!) 10: e28db000 add fp, sp, #0 } 14: e28bd000 add sp, fp, #0 18: e8bd0800 pop {fp} 1c: e12fff1e bx lr
可以看到进入C函数第一步就是压栈操作,出C函数里面出栈操作,然后跳转返回。关于push,pop ARM官方的文档给出的说明:
PUSHis a synonym for
STMDB sp!, reglistand
POPis a synonym for
LDMIA sp! reglist.
PUSHand
POPare the preferred mnemonics in these cases.
仅仅是个别名而已,并且是针对sp寄存器进行操作。
由于我这里的main过于简单,所有并看不出说明名堂,在main中增加点东西:
int main(void) { int a; a = 3; return 0; }
继续反汇编,只关注main:
0000000c <main>: int main(void) { c: e52db004 push {fp} ; (str fp, [sp, #-4]!) 10: e28db000 add fp, sp, #0 14: e24dd00c sub sp, sp, #12 int a; a = 3; 18: e3a03003 mov r3, #3 1c: e50b3008 str r3, [fp, #-8] return 0; 20: e3a03000 mov r3, #0 } 24: e1a00003 mov r0, r3 28: e28bd000 add sp, fp, #0 2c: e8bd0800 pop {fp} 30: e12fff1e bx lr
可以看到有一对互为逆向操作的指令组合 push {fp}; add fp, sp, #0 <-------> add sp, fp, #0;pop {fp},在这对组合指令之间的代码是不会去修改fp的值的,这样就实现了恢复调用前fp sp的值,而在它们之间的指令是通过修改sp来访问堆栈。但是这里有个问题,此处我仅定义了一个int型变量,为何堆栈向下偏移了12个字节?按道理sp-4即可。未找到原因,虽然对于堆栈,Procedure Call Standard for the ARM Architecture,要求遵守几个约定,比如堆栈指针必须是4字节对齐,此外,对于public interface即全局的接口,要求sp 8字节对齐。这里我的main算是个public interface,因此8字节对齐必须遵守,但是sp-4也是8字节对齐啊,搞不清为什么-12。增加局部变量可以很明细看出8字节对齐的约定。
传参
int foo(int a, int b, int c, int d) { int A,B,C,D; A = a; B = b; C = c; D = d; return 0; } void main(void) { int a; a = foo(1,2,3,4); }
反汇编:
00000000 <_start>: .text .global _start _start: ldr sp,=4096 0: e3a0da01 mov sp, #4096 ; 0x1000 bl main 4: eb000014 bl 5c <main> 00000008 <loop>: loop: b loop 8: eafffffe b 8 <loop> 0000000c <foo>: int foo(int a, int b, int c, int d) { c: e52db004 push {fp} ; (str fp, [sp, #-4]!) 10: e28db000 add fp, sp, #0 14: e24dd024 sub sp, sp, #36 ; 0x24 18: e50b0018 str r0, [fp, #-24] 1c: e50b101c str r1, [fp, #-28] 20: e50b2020 str r2, [fp, #-32] 24: e50b3024 str r3, [fp, #-36] ; 0x24 int A,B,C,D; A = a; 28: e51b3018 ldr r3, [fp, #-24] 2c: e50b3014 str r3, [fp, #-20] B = b; 30: e51b301c ldr r3, [fp, #-28] 34: e50b3010 str r3, [fp, #-16] C = c; 38: e51b3020 ldr r3, [fp, #-32] 3c: e50b300c str r3, [fp, #-12] D = d; 40: e51b3024 ldr r3, [fp, #-36] ; 0x24 44: e50b3008 str r3, [fp, #-8] return 0; 48: e3a03000 mov r3, #0 } 4c: e1a00003 mov r0, r3 50: e28bd000 add sp, fp, #0 54: e8bd0800 pop {fp} 58: e12fff1e bx lr 0000005c <main>: void main(void) { 5c: e92d4800 push {fp, lr} 60: e28db004 add fp, sp, #4 64: e24dd008 sub sp, sp, #8 int a; a = foo(1,2,3,4); 68: e3a00001 mov r0, #1 6c: e3a01002 mov r1, #2 70: e3a02003 mov r2, #3 74: e3a03004 mov r3, #4 78: ebffffe3 bl c <foo> 7c: e1a03000 mov r3, r0 80: e50b3008 str r3, [fp, #-8] } 84: e24bd004 sub sp, fp, #4 88: e8bd4800 pop {fp, lr} 8c: e12fff1e bx lr
可以看到参数通过R0-R3寄存器传递过去,函数里面将寄存器值压栈,要用时从栈里面取出值即可。当寄存器不够用时,总共超过4个字长度,就会通过堆栈传递了:
void main(void) { 64: e92d4800 push {fp, lr} 68: e28db004 add fp, sp, #4 6c: e24dd010 sub sp, sp, #16 int a; a = foo(1,2,3,4,5); 70: e3a03005 mov r3, #5 74: e58d3000 str r3, [sp] @通过堆栈传递多出来的参数 78: e3a00001 mov r0, #1 7c: e3a01002 mov r1, #2 80: e3a02003 mov r2, #3 84: e3a03004 mov r3, #4 88: ebffffdf bl c <foo> 8c: e1a03000 mov r3, r0 90: e50b3008 str r3, [fp, #-8] } 94: e24bd004 sub sp, fp, #4 98: e8bd4800 pop {fp, lr} 9c: e12fff1e bx lr
返回值好像也是通过寄存器或者堆栈传递。
相关文章推荐
- java语言程序设计第十版(Introduce to java) 课后习题 chapter6-17
- ios自定义View:init和initWithFrame方法
- 第四周周赛——我查,我查,我查查查题解(来自poj2524,1664,1182,HDU1021,5524,5645)
- 几种即见即所得Web编辑器优缺点比较
- 第十一讲--Oracle日志核心意义:快速提交、写缓存
- 问题 F: 删出多余的空格
- autogen.sh出错
- VS2015安装与单元测试
- Java Generics and Collections-2.1
- Node.js数据流Stream之Duplex流和Transform流
- mac gulp安装使用
- abort
- hdu5122K.Bro Sorting【线段树逆序数】
- iOS 让只支持竖屏的App横屏播放网页视频
- oracle 分组函数
- java第十二节java中时间处理
- 如何使用unity中Resources文件
- 面试
- 安卓介绍
- android开发系列之回调函数