linux X64函数参数传递过程研究 - ZhaoKevin - 博客园
基础知识
函数传参存在两种方式,一种是通过栈,一种是通过寄存器。对于x64体系结构,如果函数参数不大于6个时,使用寄存器传参,对于函数参数大于6个的函数,前六个参数使用寄存器传递,后面的使用栈传递。参数传递的规律是固定的,即前6个参数从左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9,后面的依次从 “右向左” 放入栈中。
例如:
H(a, b, c, d, e, f, g, h);
a->%rdi, b->%rsi, c->%rdx, d->%rcx, e->%r8, f->%r9
h->8(%rsp)
g->(%rsp)
实验验证
源码示例
1 #include <stdio.h>2 #include <stdlib.h>3 int test_pass_parm(int a, int b, int c, int d, int e, int f, int g, int h)4 {5 printf("a:%0x, b:%0x c:%0x d:%0x e:%0x f:%0x g;%0x h:%0x\n", a,b,c,d,e,f,g,h);6 return 0;7 }8 int main(int argc, char **argv)9 {10 int a = 1, b= 2, c=3, d = 4, e =5, f=6, g= 7, h =8;11 test_pass_parm(a,b,c,d,e,f,g,h);12 return 0;13 }
使用 gcc pass_parm.c -o pass_parm -g
生成可执行程序 pass_parm
.
反汇编 pass_parm
从中我们取出来main函数以及test_pass_parm 汇编进行分析验证。
main 汇编分析
119 int main(int argc, char **argv)120 {121 40057b: 55 push %rbp122 40057c: 48 89 e5 mov %rsp,%rbp123 40057f: 48 83 ec 40 sub $0x40,%rsp //rsp栈指针下移0x40(64)个字节124 400583: 89 7d dc mov %edi,-0x24(%rbp) // main函数的第一个参数argc放置在距离栈底0x24字节处125 400586: 48 89 75 d0 mov %rsi,-0x30(%rbp) // main函数的第一个参数argv放置在距离栈底0x30字节处126 int a = 1, b= 2, c=3, d = 4, e =5, f=6, g= 7, h =8;127 40058a: c7 45 fc 01 00 00 00 movl $0x1,-0x4(%rbp) //变量a128 400591: c7 45 f8 02 00 00 00 movl $0x2,-0x8(%rbp)//变量b129 400598: c7 45 f4 03 00 00 00 movl $0x3,-0xc(%rbp)//变量c130 40059f: c7 45 f0 04 00 00 00 movl $0x4,-0x10(%rbp)//变量d131 4005a6: c7 45 ec 05 00 00 00 movl $0x5,-0x14(%rbp)//变量e132 4005ad: c7 45 e8 06 00 00 00 movl $0x6,-0x18(%rbp)//变量f133 4005b4: c7 45 e4 07 00 00 00 movl $0x7,-0x1c(%rbp) //变量g134 4005bb: c7 45 e0 08 00 00 00 movl $0x8,-0x20(%rbp) //变量h135 // 以上汇编将main函数的局部a, b, c, d, e, f, g, h变量从左到右依次入栈136 test_pass_parm(a,b,c,d,e,f,g,h);137 4005c2: 44 8b 4d e8 mov -0x18(%rbp),%r9d //传送-0x18(%rbp)(变量f) 位置的值到r9寄存器中138 4005c6: 44 8b 45 ec mov -0x14(%rbp),%r8d //传送-0x14(%rbp)(变量e) 位置的值到r8寄存器中139 4005ca: 8b 4d f0 mov -0x10(%rbp),%ecx //传送-0x10(%rbp)(变量d) 位置的值到cx寄存器中140 4005cd: 8b 55 f4 mov -0xc(%rbp),%edx //传送-0xc(%rbp)(变量c) 位置的值到dx寄存器中141 4005d0: 8b 75 f8 mov -0x8(%rbp),%esi //传送-0x8(%rbp)(变量b) 位置的值到si寄存器中142 4005d3: 8b 45 fc mov -0x4(%rbp),%eax //暂存-0x4(%rbp)(变量a) 位置的值到ax寄存器中143 4005d6: 8b 7d e0 mov -0x20(%rbp),%edi //传送-0x20(%rbp)(变量h) 位置的值到di寄存器中中转144 4005d9: 89 7c 24 08 mov %edi,0x8(%rsp) //传送di寄存器的值到(变量h) 0x8(%rsp)位置145 4005dd: 8b 7d e4 mov -0x1c(%rbp),%edi //传送-0x1c(%rbp)(变量g) 位置的值到di寄存器中中转146 4005e0: 89 3c 24 mov %edi,(%rsp) //传送di寄存器的值到(变量g) (%rsp)位置147 4005e3: 89 c7 mov %eax,%edi //最后将ax寄存器保存的a变量的值传送到di寄存器148 // 以上汇编准备传给test_pass_parm函数的参数,然后调用test_pass_parm149 4005e5: e8 33 ff ff ff callq 40051d <test_pass_parm>150 return 0;151 4005ea: b8 00 00 00 00 mov $0x0,%eax152 }153 4005ef: c9 leaveq 154 4005f0: c3 retq 155 4005f1: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)156 4005f8: 00 00 00 157 4005fb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
test_pass_parm 汇编分析
82 int test_pass_parm(int a, int b, int c, int d, int e, int f, int g, int h)83 {84 40051d: 55 push %rbp85 40051e: 48 89 e5 mov %rsp,%rbp86 400521: 48 83 ec 30 sub $0x30,%rsp87 400525: 89 7d fc mov %edi,-0x4(%rbp) //a88 400528: 89 75 f8 mov %esi,-0x8(%rbp) //b89 40052b: 89 55 f4 mov %edx,-0xc(%rbp) //c90 40052e: 89 4d f0 mov %ecx,-0x10(%rbp) //d91 400531: 44 89 45 ec mov %r8d,-0x14(%rbp) //e92 400535: 44 89 4d e8 mov %r9d,-0x18(%rbp) /f93 //从寄存器恢复实参到当前函数的栈中94 printf("a:%0x, b:%0x c:%0x d:%0x e:%0x f:%0x g;%0x h:%0x\n", a,b,c,d,e,f,g,h);95 400539: 44 8b 45 ec mov -0x14(%rbp),%r8d96 40053d: 8b 7d f0 mov -0x10(%rbp),%edi97 400540: 8b 4d f4 mov -0xc(%rbp),%ecx98 400543: 8b 55 f8 mov -0x8(%rbp),%edx99 400546: 8b 45 fc mov -0x4(%rbp),%eax100 400549: 8b 75 18 mov 0x18(%rbp),%esi //0x18(%rbp)的地址是上一个函数的栈顶位置 + 8,从这拿出h101 40054c: 89 74 24 10 mov %esi,0x10(%rsp)102 400550: 8b 75 10 mov 0x10(%rbp),%esi //0x10(%rbp)的地址是上一个函数的栈顶位置,从这拿出g103 400553: 89 74 24 08 mov %esi,0x8(%rsp)104 400557: 8b 75 e8 mov -0x18(%rbp),%esi105 40055a: 89 34 24 mov %esi,(%rsp)106 40055d: 45 89 c1 mov %r8d,%r9d107 400560: 41 89 f8 mov %edi,%r8d108 400563: 89 c6 mov %eax,%esi109 400565: bf 90 06 40 00 mov $0x400690,%edi110 40056a: b8 00 00 00 00 mov $0x0,%eax111 // 以上汇编准备传给printf函数的参数,然后调用printf112 40056f: e8 8c fe ff ff callq 400400 <printf@plt>113 return 0;114 400574: b8 00 00 00 00 mov $0x0,%eax115 }116 400579: c9 leaveq 117 40057a: c3 retq
实验
使用gdb进行汇编代码调试,分别在执行汇编指令的0x4005c2、0x4005e5、0x400539、0x40056f处设置断点,查看寄存器的值以及函数栈帧中的值,验证分析的结果。
设置断点
断点1处的栈数值
断点2处的栈数值以及寄存器值
断点3处的栈数值以及寄存器值
断点4处的栈数值以及寄存器值
结论
实验结果完全验证了所分析的结果,从这次实践中可以更好地了解函数参数的传递过程以及函数调用所引起的堆栈变化,完整的调试过程见附录。
附录 完整汇编与调试过程
完整汇编代码
完整调试过程