関数の呼び出し処理、スタックフレームの作成と破棄

この記事では主に、C 言語の関数呼び出しのプロセスをスタック領域のレベルから深く理解することに焦点を当てています。

以下では、簡単なプログラムを使用して説明します。

#include <stdio.h>
int Add(int x, int y)
{
	int sum = 0;
	sum = x + y;
	return sum;
}
int main()
{
	int a = 2;
	int b = 3;
	int ret = 0;
	ret = Add(a, b);
	return 0;
}

上記プログラムでは、メイン関数 main 内で 3 つのローカル変数を定義し、同じファイル内の Add() 関数を呼び出しています。 3 つのローカル変数は間違いなくスタック空間に格納されているので、プログラムを実行しながら、main 関数がスタックを基にどのようにして Add() の呼び出し処理を実装しているのか、そして関数内でどのように Add() が main に戻るのかを少しずつ理解していきましょう。


このプログラムをデバッグし、逆アセンブリを開きます。

以下は main 関数のアセンブリ コードです。

--- d:\baidunetdiskdownload\vs2013\4-15\4-15\test.c ----------------------------
int main()
{
011B1410 55                   push        ebp  
011B1411 8B EC                mov         ebp,esp  
011B1413 81 EC E4 00 00 00    sub         esp,0E4h  
011B1419 53                   push        ebx  
011B141A 56                   push        esi  
011B141B 57                   push        edi  
011B141C 8D BD 1C FF FF FF    lea         edi,[ebp+FFFFFF1Ch]  
011B1422 B9 39 00 00 00       mov         ecx,39h  
011B1427 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
011B142C F3 AB                rep stos    dword ptr es:[edi]  
	int a = 2;
011B142E C7 45 F8 02 00 00 00 mov         dword ptr [ebp-8],2  
	int b = 3;
011B1435 C7 45 EC 03 00 00 00 mov         dword ptr [ebp-14h],3  
	int ret = 0;
011B143C C7 45 E0 00 00 00 00 mov         dword ptr [ebp-20h],0  
	ret = Add(a, b);
011B1443 8B 45 EC             mov         eax,dword ptr [ebp-14h]  
011B1446 50                   push        eax  
011B1447 8B 4D F8             mov         ecx,dword ptr [ebp-8]  
011B144A 51                   push        ecx  
011B144B E8 91 FC FF FF       call        011B10E1  
011B1450 83 C4 08             add         esp,8  
011B1453 89 45 E0             mov         dword ptr [ebp-20h],eax  
	return 0;
011B1456 33 C0                xor         eax,eax  
}
011B1458 5F                   pop         edi  
011B1459 5E                   pop         esi  
011B145A 5B                   pop         ebx  
011B145B 81 C4 E4 00 00 00    add         esp,0E4h  
011B1461 3B EC                cmp         ebp,esp  
011B1463 E8 D3 FC FF FF       call        011B113B  
011B1468 8B E5                mov         esp,ebp  
011B146A 5D                   pop         ebp  
011B146B C3                   ret


Add 関数のアセンブリ コード:

--- d:\baidunetdiskdownload\vs2013\4-15\4-15\test.c ----------------------------
#include <stdio.h>
int Add(int x, int y)
{
011B13C0 55                   push        ebp  
011B13C1 8B EC                mov         ebp,esp  
011B13C3 81 EC CC 00 00 00    sub         esp,0CCh  
011B13C9 53                   push        ebx  
011B13CA 56                   push        esi  
011B13CB 57                   push        edi  
011B13CC 8D BD 34 FF FF FF    lea         edi,[ebp+FFFFFF34h]  
011B13D2 B9 33 00 00 00       mov         ecx,33h  
011B13D7 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
011B13DC F3 AB                rep stos    dword ptr es:[edi]  
	int sum = 0;
011B13DE C7 45 F8 00 00 00 00 mov         dword ptr [ebp-8],0  
	sum = x + y;
011B13E5 8B 45 08             mov         eax,dword ptr [ebp+8]  
011B13E8 03 45 0C             add         eax,dword ptr [ebp+0Ch]  
011B13EB 89 45 F8             mov         dword ptr [ebp-8],eax  
	return sum;
011B13EE 8B 45 F8             mov         eax,dword ptr [ebp-8]  
}
011B13F1 5F                   pop         edi  
011B13F2 5E                   pop         esi  
011B13F3 5B                   pop         ebx  
011B13F4 8B E5                mov         esp,ebp  
011B13F6 5D                   pop         ebp  
011B13F7 C3                   ret


まず次のことを理解する必要があります。

ebp: スタックボトムポインタ

esp: スタックトップポインタ

各関数呼び出しは、edp と esp によって維持されるスペースを開く必要があります。

さらに、ebp と esp は、main 関数を呼び出す mainCRTStartup 関数の空間を維持します。




次に main 関数に入り、関数の呼び出しプロセスとスタック フレームの作成と破棄をアセンブリ コードに従って段階的に理解します。

011B1410 55                   push        ebp

Push はスタックをプッシュすることです。この文は ebp をスタックの先頭にプッシュすることを意味し、esp はスタックの先頭を指します。効果は次のとおりです。



011B1411 8B EC                mov         ebp,esp 

この文は、esp の値を ebp に与えることを意味します。つまり、ebp は、esp が指す場所を指します。結果は以下のようになります。



011B1413 81 EC E4 00 00 00    sub         esp,0E4h  

この文は esp から 0E4h を引いたものを意味し、スタック空間は上位アドレスから下位アドレスを指します。ここで実際に空けるのは04Ehの空間ですが、この空間はmain関数用に空けられており、このときespはスタックの先頭を指します。結果は以下のようになります。



011B13C9 53                   push        ebx  
011B13CA 56                   push        esi  
011B13CB 57                   push        edi  

これら 3 行は引き続きスタックにプッシュされます。つまり、ebx、esi、edi は 1 つずつプッシュされ、esp はスタックの先頭を指します。結果は以下のようになります。



011B141C 8D BD 1C FF FF FF    lea         edi,[ebp-0E4h]  
//lea就是加载的意思,ebp-0E4h就是指刚才开辟的那一段main函数空间。这一行的意思就是说把为main函数开辟的空间加载到edi里面
011B1422 B9 39 00 00 00       mov         ecx,39h  
//把39h给eax
011B1427 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
//把0CCCCCCCCh给mov
011B142C F3 AB                rep stos    dword ptr es:[edi] 
//刚才不是把为main函数开辟的空间加载到edi里面了嘛,把这个空间里重复拷贝内容,拷贝内容eax:0CCCCCCCCh,拷贝ecx:39h次。


つまり、この空間セグメントのアドレスはすべて 0ccccccch に初期化されます。結果は以下のようになります。


これら 4 行の実行が終了したらメモリを確認すると、ebp-0e4h (39h は 57) からの 57 行がすべて 0ccccccch に初期化されています。




int a = 2;
011B142E C7 45 F8 02 00 00 00 mov         dword ptr [ebp-8],2  
	int b = 3;
011B1435 C7 45 EC 03 00 00 00 mov         dword ptr [ebp-14h],3  
	int ret = 0;
011B143C C7 45 E0 00 00 00 00 mov         dword ptr [ebp-20h],0  


これら 4 行のアセンブリ コードにより、ローカル変数の作成が開始されます。ここでは、コード内で a を選択し、右クリックして表示シンボル名を選択し、チェック マークを外して結果を確認します。これら 4 行のコードの効果を次の図に示します。


私たちの記憶を調べてみると、




ローカル変数はスタック領域に格納されることは誰もが知っているので、現在開いている main 関数の領域を main 関数のスタック フレームと呼びます。





次に、アセンブリ コードを見ていきます。

	ret = Add(a, b);
011B1443 8B 45 EC             mov         eax,dword ptr [ebp-14h]  
//把ebp-14h(b)的值放到eax里,esp指向栈顶;
011B1446 50                   push        eax  
//把eax压到栈顶
011B1447 8B 4D F8             mov         ecx,dword ptr [ebp-8]  
//把ebp-8(a)的值放到eax里;esp指向栈顶;
011B144A 51 push ecx
//把ecx压到栈顶;
011B144B E8 91 FC FF FF       call        _Add (011B10E1h)
//call指令,调用函数;这个地方是最关键的地方
//在这里我们按F11进入函数会跳转到如下这样一条语句:011B10E1 E9 DA 02 00 00       jmp         Add (011B13C0h);
//我们还会发现内存里在2的上面又压进去一个地址,这个地址就是我们下面这一行汇编代码开头的地址(函数调用完返回值就要使用这个地址)。如下图所示。
011B1450 83 C4 08             add         esp,8
//效果如图所示:









次に、F11 キーを押して追加機能に入ります。

011B13C0 55                   push        ebp  
//这里压进去的其实是main函数的edp
011B13C1 8B EC                mov         ebp,esp  
011B13C3 81 EC CC 00 00 00    sub         esp,0CCh  
011B13C9 53                   push        ebx  
011B13CA 56                   push        esi  
011B13CB 57                   push        edi  
011B13CC 8D BD 34 FF FF FF    lea         edi,[ebp-0CCh]  
011B13D2 B9 33 00 00 00       mov         ecx,33h  
011B13D7 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
011B13DC F3 AB                rep stos    dword ptr es:[edi] 



ここでのアセンブリ コードは前述の基本原則と一致しており、実行後の効果図は次のとおりです。






int sum = 0;
011B13DE C7 45 F8 00 00 00 00 mov         dword ptr [sum],0   
sum = x + y;
011B13E5 8B 45 08             mov         eax,dword ptr [ebp+8] 
//ebp+8,此时ebp+8指向形参a;
011B13E8 03 45 0C             add         eax,dword ptr [ebp+0Ch]  
//ebp+0ch,指向形参b,把a+b放到eax里
011B13EB 89 45 F8             mov         dword ptr [ebp-8],eax  
//ebp-8就是sum所在的位置,把eax(a+b)的值给sum,sum=5;
return sum;
011B13EE 8B 45 F8             mov         eax,dword ptr [ebp-8] 
//把sum的值再放到eax里,那么eax里存放的就是我们的返回值


効果図は次のとおりです。





011B13F1 5F                   pop         edi  
011B13F2 5E                   pop         esi  
011B13F3 5B                   pop         ebx 
//pop就是出栈的意思,esp此时指向ebx下面的空间,这三个地址相当于被回收了
011B13F4 8B E5                mov         esp,ebp  
//把ebp的值给esp
011B13F6 5D                   pop         ebp
//ebp就是我们所存储的main函数的ebp,那么此时ebp指向main函数里面的ebp
011B13F7 C3                   ret  
//ret指令要返回值,首先把栈顶call执行下一条指令的地址出栈,然后紧接着跳到下面这一行的地址,
这也是之前为什么要把这个地址保存,就起到了一个返回值的作用
//011B1450 83 C4 08             add         esp,8
011B1450 83 C4 08             add         esp,8 
//esp+8直接把定义的形参跳过去,到这一步的时候,我们就是Add的栈桢已经!!!被销毁了!!! 
011B1453 89 45 E0 mov dword ptr [ebp-20h],eax
//eax里存放的是Add函数里sum的值,把eax的值给ebp-20h(ret)就把sum的值返回了


この時点で、関数の呼び出しプロセスは終了です。
 
 
 
 
最終的な効果の図は次のとおりです。


これで関数の呼び出し処理とスタックフレームの作成・破棄が完了しました。


















おすすめ

転載: blog.csdn.net/Quinn0918/article/details/70258234