函数调用及返回汇编浅析-C语言部分之无参无返回
函数调用过程就是内存申请和数据流动的过程,熟悉其中原理,对于写出“人类高质量源码”有很好的辅助作用
后面将分别从C、C++、Objective-C三种语言来解析
篇幅所限,此篇文章只介绍C语言部分
C语言
C语言的函数调用有如下种类
1、无参无返回
2、无参有返回
3、有参无返回
4、有参有返回
这里取几个典型:
1、无参无返回
2、有基础类型参数无返回(篇幅所限,下篇介绍)
3、有基础类型参数有返回(篇幅所限,下篇介绍)
无参无返回
取简单的helloword来分析下
第一步、将源码编译链接成可执行程序
第二步、用IDA查看生成的汇编程序,这里生成的是x86-64汇编
主函数的汇编如下
sayHello函数生成的汇编
第三步、分析汇编
在进行汇编分析前,先对其中的一些关键字进行解释
寄存器(关于寄存器的知识,感兴趣的可以自行查询):
rbp:是栈帧指针寄存器,用于标识当前栈帧的起始位置,函数的返回地址,rbp和rsp配合使用,rsp和rbp得差值,就是当前函数申请得栈内存得大小
rsp:是堆栈指针寄存器,通常会指向栈顶位置,堆栈的 pop 和push 操作就是通过改变 rsp 的值即移动堆栈指针的位置来实现的。
edi:32位寄存器,用于传递第一个参数
rdi:64位寄存器,用于传递第一个参数
rsi:64位寄存器,用于传递第二个参数
eax:32位寄存器,累加寄存器,用于存储返回值
al:8位寄存器,累加寄存器,用于存储返回值
汇编执行:
push:入栈,将寄存器的值存入栈顶
mov:移动
call:调用
xor:异或,主要用于清空寄存器的值
sub:减
add:加
retn:返回
lea:load effective address, 加载有效地址,将地址加载到寄存器中
main执行过程
push rbp //将调用方分配的栈帧地址存入栈内存,函数执行完后,需要恢复此寄存器,目的是可以返回调用方的内存地址,所保存的地址是rsp寄存器指向的地址
mov rbp, rsp //将堆栈指针寄存器存入rbp
sub rsp, 10h //rsp寄存器的值减0x10,也就是栈顶向下移动0x10大小,用于保存当前函数得栈数据
mov [rbp+var_4], 0 //前面可以看到:var_4 = dword ptr -4,此句话就是mov [rbp -4], 0,其中"[]",用于将寄存器的值转成内存地址(栈地址),也就是将 rbp-4的地址存入0
mov [rbp+var_8], edi //和前面一句一样:将edi里的值存入[rbp - 8]的内存地址,保存main函数的的第一个参数,argc,4个字节
mov [rbp+var_10], rsi //和前面一句一样:将rsi里的值存入[rbp - 0x10]的内存地址,保存main函数的第二个参数的地址,argv,8个字节
call _sayHello //并未传输任何参数调用sayHello函数
xor eax, eax //返回结果置零
add rsp, 10h //恢复堆栈指针寄存器,相当于回收局部变量得内存,但是并未将相应得内存置0,有被重新获取到得风险
pop rbp //恢复栈帧指针寄存器,恢复调用前的帧位置
retn //返回调用方
通过对上述main函数的分析,我们可以将main函数置行过程分为如下几个阶段
第一、保存调用点的栈帧寄存器到栈内存,并重置堆栈指针环境
第二阶段、开辟空间,保存局部变量值
第三阶段、调用sayHello
第四阶段、将返回值置为0,存入寄存器
第五阶段、恢复rsp、rbp
sayHello执行过程
push rbp //同上
mov rbp, rsp //同上
lea rdi, aHelloWorld ; "Hello, World!\n" //加载有效地址,将字符串地址加载到寄存器中,其中rdi用于传入第一个参数
mov al, 0 //清空返回值位0
call _printf //调用printf函数
pop rbp //恢复栈
retn //返回
对sayHello执行的几个阶段进行图形解析
第一阶段、同上
第二阶段、准备参数调用printf
第三阶段、恢复
总结
通过对无参无返回的函数进行分析,我们可以得出如下几个结论
1、栈是自大向小的方向进行扩展的(堆是反过来的,这里没做介绍)
2、函数调用,迭代使用的寄存器是,rbp和rsp
3、函数调用后并不会清理使用过的内存,数据还在,通过一定的手段是可恢复的,所以华为C/C++规范里就明确表示敏感数据用后需清理,敏感数据不能放到string的局部变量里
- 点赞
- 收藏
- 关注作者
评论(0)