【反汇编玩耍1】通过反汇编真正理解函数参数传递过程
过去我一直以为,函数参数就只是通过栈传递的,最近看一些反汇编代码,发现好多时候都是通过寄存器。做了个实验,最终明白了,函数的参数怎样传递,其实是和参数个数有关。
在linux下的objdump反汇编的结果:如果参数很多,有一些参数会通过栈传递,否则通过寄存器。
在windows下的VS反汇编的结果:都通过栈。
C代码:
#include <stdio.h> void func1(int a, int b) { printf("%d", a+b); } void func2(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k) { printf("%d",a+b+c+d+e+f+g+h+i+j+k); } void main () { int a = 1; int b = 1; int c = 1; int d = 1; int e = 1; int f = 1; int g = 1; int h = 1; int i = 1; int j = 1; int k = 1; func1(a, b); func2(a, b, c, d, e, f, g, h, i, j, k); }
linux下通过objdump反汇编:
[root@VM_120_194_centos C]# gcc -E 20170704.c -o 20170704.i [root@VM_120_194_centos C]# gcc -S 20170704.i -o 20170704.s [root@VM_120_194_centos C]# gcc -c 20170704.s -o 20170704.o [root@VM_120_194_centos C]# objdump -S 20170704.o 20170704.o: 文件格式 elf64-x86-64 Disassembly of section .text: 0000000000000000 <func1>: 0: 55 push %rbp 1: 48 89 e5 mov %rsp,%rbp 4: 48 83 ec 10 sub $0x10,%rsp 8: 89 7d fc mov %edi,-0x4(%rbp) b: 89 75 f8 mov %esi,-0x8(%rbp) e: 8b 45 f8 mov -0x8(%rbp),%eax 11: 8b 55 fc mov -0x4(%rbp),%edx 14: 01 d0 add %edx,%eax 16: 89 c6 mov %eax,%esi 18: bf 00 00 00 00 mov $0x0,%edi 1d: b8 00 00 00 00 mov $0x0,%eax 22: e8 00 00 00 00 callq 27 <func1+0x27> 27: c9 leaveq 28: c3 retq 0000000000000029 <func2>: 29: 55 push %rbp 2a: 48 89 e5 mov %rsp,%rbp 2d: 48 83 ec 20 sub $0x20,%rsp 31: 89 7d fc mov %edi,-0x4(%rbp) 34: 89 75 f8 mov %esi,-0x8(%rbp) 37: 89 55 f4 mov %edx,-0xc(%rbp) 3a: 89 4d f0 mov %ecx,-0x10(%rbp) 3d: 44 89 45 ec mov %r8d,-0x14(%rbp) 41: 44 89 4d e8 mov %r9d,-0x18(%rbp) 45: 8b 45 f8 mov -0x8(%rbp),%eax 48: 8b 55 fc mov -0x4(%rbp),%edx 4b: 01 c2 add %eax,%edx 4d: 8b 45 f4 mov -0xc(%rbp),%eax 50: 01 c2 add %eax,%edx 52: 8b 45 f0 mov -0x10(%rbp),%eax 55: 01 c2 add %eax,%edx 57: 8b 45 ec mov -0x14(%rbp),%eax 5a: 01 c2 add %eax,%edx 5c: 8b 45 e8 mov -0x18(%rbp),%eax 5f: 01 c2 add %eax,%edx 61: 8b 45 10 mov 0x10(%rbp),%eax 64: 01 c2 add %eax,%edx 66: 8b 45 18 mov 0x18(%rbp),%eax 69: 01 d0 add %edx,%eax 6b: 89 c6 mov %eax,%esi 6d: bf 00 00 00 00 mov $0x0,%edi 72: b8 00 00 00 00 mov $0x0,%eax 77: e8 00 00 00 00 callq 7c <func2+0x53> 7c: c9 leaveq 7d: c3 retq 000000000000007e <main>: 7e: 55 push %rbp 7f: 48 89 e5 mov %rsp,%rbp 82: 48 83 ec 60 sub $0x60,%rsp 86: c7 45 fc 01 00 00 00 movl $0x1,-0x4(%rbp) 8d: c7 45 f8 01 00 00 00 movl $0x1,-0x8(%rbp) 94: c7 45 f4 01 00 00 00 movl $0x1,-0xc(%rbp) 9b: c7 45 f0 01 00 00 00 movl $0x1,-0x10(%rbp) a2: c7 45 ec 01 00 00 00 movl $0x1,-0x14(%rbp) a9: c7 45 e8 01 00 00 00 movl $0x1,-0x18(%rbp) b0: c7 45 e4 01 00 00 00 movl $0x1,-0x1c(%rbp) b7: c7 45 e0 01 00 00 00 movl $0x1,-0x20(%rbp) be: c7 45 dc 01 00 00 00 movl $0x1,-0x24(%rbp) c5: c7 45 d8 01 00 00 00 movl $0x1,-0x28(%rbp) cc: c7 45 d4 01 00 00 00 movl $0x1,-0x2c(%rbp) d3: 8b 55 f8 mov -0x8(%rbp),%edx d6: 8b 45 fc mov -0x4(%rbp),%eax d9: 89 d6 mov %edx,%esi db: 89 c7 mov %eax,%edi dd: e8 00 00 00 00 callq e2 <main+0x64> e2: 44 8b 4d e8 mov -0x18(%rbp),%r9d e6: 44 8b 45 ec mov -0x14(%rbp),%r8d ea: 8b 4d f0 mov -0x10(%rbp),%ecx ed: 8b 55 f4 mov -0xc(%rbp),%edx f0: 8b 75 f8 mov -0x8(%rbp),%esi f3: 8b 45 fc mov -0x4(%rbp),%eax f6: 8b 7d d4 mov -0x2c(%rbp),%edi f9: 89 7c 24 20 mov %edi,0x20(%rsp) fd: 8b 7d d8 mov -0x28(%rbp),%edi 100: 89 7c 24 18 mov %edi,0x18(%rsp) 104: 8b 7d dc mov -0x24(%rbp),%edi 107: 89 7c 24 10 mov %edi,0x10(%rsp) 10b: 8b 7d e0 mov -0x20(%rbp),%edi 10e: 89 7c 24 08 mov %edi,0x8(%rsp) 112: 8b 7d e4 mov -0x1c(%rbp),%edi 115: 89 3c 24 mov %edi,(%rsp) 118: 89 c7 mov %eax,%edi 11a: e8 00 00 00 00 callq 11f <main+0xa1> 11f: c9 leaveq 120: c3 retq
各局部变量:
f -0x18(%rbp)
e -0x14(%rbp)
d -0x10(%rbp)
c -0xc(%rbp)
b -0x8(%rbp)
a -0x4(%rbp)
k -0x2c(%rbp)
j -0x28(%rbp)
i -0x24(%rbp)
h -0x20(%rbp)
g -0x1c(%rbp)
func1和func2两个函数的反汇编代码不用管,我们看main函数里的。可以清晰看到,86 - cc是给变量(栈空间内存)赋值的过程,也就是
int a = 1;int b = 1;int c = 1;int d = 1;int e = 1;int f = 1;int g = 1;int h = 1;int i = 1;int j = 1;int k = 1;
d3 - dd是func1调用的过程。需要两个参数,赋值给edi和esi寄存器。也就是说,这里函数参数是存在寄存器里的。(2023年12月补充,%ebx,%esi,%edi三个寄存器,是被调用者需要保存的寄存器)
func1里面,则是先将edi和esi的内容copy到自己的栈帧空间。这就是C语言书里说的所谓“值传递”吧?
func1的调用是,main函数的e2 - 11a,这个很有趣,前一部分变量是存在寄存器里(和上面讲的一个道理),而寄存器不够的时候(参数太多了),就通过rsp栈指针寄存器,直接存到下一个要调用的函数的栈帧空间里面。好有趣。(2023年12月补充,%rbp指向当前栈帧的底,%rsp指向当前栈帧的顶,在栈顶下面存参数,开辟的下一个栈帧,通过%rbp在栈帧底取参数)
2023年12月补充:x86-64的机器中,很多情况,调用函数,并不一定开辟新的栈帧,因为大多数参数用寄存器传递即可。毕竟,相比之前的IA32,x86-64的寄存器数量翻倍为16个。
《深入理解操作系统》一书的图:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
2016-07-04 自制操作系统(九) 内存管理