pwn-0x00(C语言是如何进行函数调用的)
-
C语言是如何进行函数调用的,换句话说C语言进行函数调用时都发生了些什么
先写一段简单的C语言程序(不传参)
#include <iostream> int test(){ int a=0; return 0; } int main() { test(); }
这里我们简单的调用了一个函数test,我们对其进行反汇编,查看汇编代码,我用的编译器是gcc,使用clang会有不同的结果但都是大同小异
test(): push rbp mov rbp, rsp mov DWORD PTR -4[rbp], 0 mov eax, 0 pop rbp ret main: push rbp mov rbp, rsp call test() mov eax, 0 pop rbp ret
首先我们知道,rbp是栈的基指针,rsp是栈的顶部的指针。人话说rsp、rbp这两个指针夹着栈。
在调用的时候执行了一条指令push rbp
也就是把rbp入栈,就是先要把rbp的值暂时存起来,以后要用到。接着是
mov rbp, rsp
将rsp赋值给rbp,也就是将rbp指针移动至rsp。这样做是为了给test函数开辟一个属于他自己的栈。
我会搞一个动画、可能会直观一些首先说明32位、64位下的函数传参有一些不同
传参的情况下(64位)
int test(int b,int c,int d,int e,int f, int g,int h,int i){ b=1; c=2; d=3; e=4; f=5; g=6; h=7; i=8; } int main() { int b=0; test(1,2,3,4,5,6,7,8); }
汇编如下
test: push rbp mov rbp, rsp mov DWORD PTR -4[rbp], edi mov DWORD PTR -8[rbp], esi mov DWORD PTR -12[rbp], edx mov DWORD PTR -16[rbp], ecx mov DWORD PTR -20[rbp], r8d mov DWORD PTR -24[rbp], r9d mov DWORD PTR -4[rbp], 1 mov DWORD PTR -8[rbp], 2 mov DWORD PTR -12[rbp], 3 mov DWORD PTR -16[rbp], 4 mov DWORD PTR -20[rbp], 5 mov DWORD PTR -24[rbp], 6 mov DWORD PTR 16[rbp], 7 mov DWORD PTR 24[rbp], 8 nop pop rbp ret main: push rbp mov rbp, rsp sub rsp, 16 mov DWORD PTR -4[rbp], 0 push 8 push 7 mov r9d, 6 mov r8d, 5 mov ecx, 4 mov edx, 3 mov esi, 2 mov edi, 1 call test add rsp, 16 mov eax, 0 leave ret
可以看到传参的顺序是从右到左的,至于为什么要这样传参是因为栈的性质,先进后出第一个参数最后入栈,那么取出的时候是第一个取出的,这样设计的话就可以支持可变参数了。
然后在看这一段mov r9d, 6 mov r8d, 5 mov ecx, 4 mov edx, 3 mov esi, 2 mov edi, 1
这是主调函数(就是调用其他函数的那个函数)把参数放到了寄存器里,是这样的,在64位C程序中函数调用优先使用寄存器(快)64位CPU的寄存器很多的可以看到顺序
- edi(rdi)
- esi(rsi)
- edx(rdx)
- ecx(rcx)
- r8d
- r9d
如果参数更多的话,使用栈传参。
但是即使他用了寄存器传参他也一定会放到栈里的,这个操作是在test函数里实现的
test: push rbp mov rbp, rsp mov DWORD PTR -4[rbp], edi mov DWORD PTR -8[rbp], esi mov DWORD PTR -12[rbp], edx mov DWORD PTR -16[rbp], ecx mov DWORD PTR -20[rbp], r8d mov DWORD PTR -24[rbp], r9d
这一段。到这里你一定会感到疑惑,为什么
mov DWORD PTR 16[rbp], 7 mov DWORD PTR 24[rbp], 8
这两行和其他两行不一样的说
都是int 前面的占4个字节,这两个占8个字节,并且前面的是减,后面的是加。
然后还是从16开始的,好奇怪!思考
- rbp寄存器的大小.
- pc寄存器大小.
- 再想想为什么函数调用后能回到调用它的地方.
如果想通了这两件事,问题就迎刃而解了,如果没想明白,下一篇文章将会解答
至于为什么下面是占据8个字节全因为push指令,他一次会压入8个字节(64位cpu),所以就要配合人家
然后为什么上面是4个字节,因为我这个编译器下int占据4个字节。有可能其他编译器64位程序中int 占据8个字节传参的情况下(32位)
未完待续。。。
-
@妈耶厥了 可以再加一个参数, 显示函数传参是怎么完成的
-
@sunrisepeak 有道理,完善了