x86 和 x86-64 系统采用不同的方式来对函数参数进行传递。x86通过栈进行参数的传递,而x86-64通过寄存器进行参数的传递。
用一段简单的代码来进行分析:
using namespace std;
int add(int a, int b){
return a + b;
}
int main(int argc, char const *argv[])
{
int a, b;
a = 3;
b = 4;
add(3, 4);
return 0;
}
x86-64
首先分析 x86-64 下函数传参的过程。
g++ -g test.cpp -o test
-g
选项表示生成带debug信息的可执行文件。
然后用 file
查看一下生成的test
文件。
~/Desktop/stack> file test
test: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2,
BuildID[sha1]=f734d178caff1fb2bc93c301d110607da34b1336, for GNU/Linux 3.2.0, not stripped
再来看一下汇编代码:
objdump -d .text test
这里只截取了main函数和add函数的汇编。
0000000000001125 <_Z3addii>:
1125: 55 push %rbp
1126: 48 89 e5 mov %rsp,%rbp
1129: 89 7d fc mov %edi,-0x4(%rbp)
112c: 89 75 f8 mov %esi,-0x8(%rbp)
112f: 8b 55 fc mov -0x4(%rbp),%edx
1132: 8b 45 f8 mov -0x8(%rbp),%eax
1135: 01 d0 add %edx,%eax
1137: 5d pop %rbp
1138: c3 retq
0000000000001139 <main>:
1139: 55 push %rbp
113a: 48 89 e5 mov %rsp,%rbp
113d: 48 83 ec 20 sub $0x20,%rsp
1141: 89 7d ec mov %edi,-0x14(%rbp)
1144: 48 89 75 e0 mov %rsi,-0x20(%rbp)
1148: c7 45 f8 03 00 00 00 movl $0x3,-0x8(%rbp)
114f: c7 45 fc 04 00 00 00 movl $0x4,-0x4(%rbp)
1156: be 04 00 00 00 mov $0x4,%esi
115b: bf 03 00 00 00 mov $0x3,%edi
1160: e8 c0 ff ff ff callq 1125 <_Z3addii>
1165: b8 00 00 00 00 mov $0x0,%eax
116a: c9 leaveq
116b: c3 retq
116c: 0f 1f 40 00 nopl 0x0(%rax)
函数汇编代码分析:
- main函数部分:
将edi
,rsi
两个寄存器里现有的值进行压栈保存,因为接下来这两个寄存器要用来传递函数参数。
1141: 89 7d ec mov %edi,-0x14(%rbp)
1144: 48 89 75 e0 mov %rsi,-0x20(%rbp)
将局部类变量a,b压栈。
1148: c7 45 f8 03 00 00 00 movl $0x3,-0x8(%rbp)
114f: c7 45 fc 04 00 00 00 movl $0x4,-0x4(%rbp)
将函数参数放入寄存器,实现函数参数的传递。
1156: be 04 00 00 00 mov $0x4,%esi
115b: bf 03 00 00 00 mov $0x3,%edi
执行到这里add函数的两个参数都已经放到edi
, esi
里了。那么怎么将这两个参数读取出来呢?这一部分工作由add函数完成。
- add函数部分:
参数的读取就是通过这两行汇编实现的,将参数读出,然后压栈。
1129: 89 7d fc mov %edi,-0x4(%rbp)
112c: 89 75 f8 mov %esi,-0x8(%rbp)
重点:x86-64的参数传递方式是通过寄存器传递,这点和x86还是很不一样的,下文会讲到。
当执行到:
return a + b;
进程的栈布局如下:
x86
同理,先来生成一下32位的可执行文件,然后用file
查看一下。
g++ -g -m32 test.cpp -o test32
- 如果这里执行报错,那是系统还没有安装多版本编译模式的缘故,安装如下插件即可。
sudo apt-get install gcc-multilib g++-multilib
~/Desktop/stack> file test32
test32: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux.so.2,
BuildID[sha1]=f734d178caff1fb2bc93c301d110607da34b744b, for GNU/Linux 3.2.0, not stripped
然后来分析一下汇编代码:
00001189 <_Z3addii>:
1189: 55 push %ebp
118a: 89 e5 mov %esp,%ebp
118c: e8 40 00 00 00 call 11d1 <__x86.get_pc_thunk.ax>
1191: 05 6f 2e 00 00 add $0x2e6f,%eax
1196: 8b 55 08 mov 0x8(%ebp),%edx
1199: 8b 45 0c mov 0xc(%ebp),%eax
119c: 01 d0 add %edx,%eax
119e: 5d pop %ebp
119f: c3 ret
000011a0 <main>:
11a0: 55 push %ebp
11a1: 89 e5 mov %esp,%ebp
11a3: 83 ec 10 sub $0x10,%esp
11a6: e8 26 00 00 00 call 11d1 <__x86.get_pc_thunk.ax>
11ab: 05 55 2e 00 00 add $0x2e55,%eax
11b0: c7 45 fc 03 00 00 00 movl $0x3,-0x4(%ebp)
11b7: c7 45 f8 04 00 00 00 movl $0x4,-0x8(%ebp)
11be: 6a 04 push $0x4
11c0: 6a 03 push $0x3
11c2: e8 c2 ff ff ff call 1189 <_Z3addii>
11c7: 83 c4 08 add $0x8,%esp
11ca: b8 00 00 00 00 mov $0x0,%eax
11cf: c9 leave
11d0: c3 ret
可以看到,x86下函数参数的传递是通过push
入栈来实现的。
11be: 6a 04 push $0x4
11c0: 6a 03 push $0x3
在add函数中,通过向高地址读取(即读取当前栈帧的前栈帧的内容)实现参数的读取。
1196: 8b 55 08 mov 0x8(%ebp),%edx
1199: 8b 45 0c mov 0xc(%ebp),%eax