アセンブリの技術名詞
rbp
(Register Base Pointer) は、現在の関数のスタック フレームのベース アドレスを指すベース ポインター レジスタです。スタック フレームは、関数呼び出し中にローカル変数およびその他の関連情報を保持するために使用されるメモリ領域の一部です。スタックフレームのベースアドレスを に保存するとrbp
、関数のパラメータやローカル変数に簡単にアクセスできます。rsp
(レジスタ スタック ポインタ) は、スタックの現在の先頭位置を指すスタック ポインタ レジスタです。スタックは、関数呼び出し中に一時データを格納するために使用される後入れ先出し (LIFO) データ構造です。rsp
スタックの先頭位置を に保存することで、関数呼び出し中にスタック上のメモリ領域を割り当てたり解放したりできます。edx
(Extended Data Register)、データ レジスタ。汎用レジスタの 1 つとして、データの保存と操作によく使用されます。関数呼び出しでは、edx
パラメータ値または一時変数を保存するために使用できます。eax
(拡張アキュムレータ レジスタ)、アキュムレータ。算術演算および論理演算を実行し、関数の戻り値を処理するために使用されます。eax
レジスタは、関数の戻り値を保持するためによく使用されます。esi
(ソース インデックス レジスタ) はソース インデックス レジスタで、通常はソース データのアドレスまたはオフセットを格納するために使用されます。これは、文字列操作やループ トラバーサルなどのシナリオでよく使用されます。edi
(宛先インデックス レジスタ) は宛先インデックス レジスタで、通常は宛先データのアドレスまたはオフセットを格納するために使用されます。また、文字列操作、ループ トラバーサル、その他のシナリオでもよく使用されます。
簡単な C 言語の例: アセンブリ コードを 1 行ずつ解析する
たとえば次のとおりですfunction_example.c
。
// function_example.c
#include <stdio.h>
int static add(int a, int b)
{
return a+b;
}
int main()
{
int x = 5;
int y = 10;
int u = add(x, y);
}
function_example.c
対応するアセンブリ コードは次のとおりです。
int static add(int a, int b)
{
0: 55 push rbp ; 保存调用者的基址指针
1: 48 89 e5 mov rbp,rsp ; 设置当前函数的基址指针
4: 89 7d fc mov DWORD PTR [rbp-0x4],edi ; 将第一个参数 a 保存在栈帧中
7: 89 75 f8 mov DWORD PTR [rbp-0x8],esi ; 将第二个参数 b 保存在栈帧中
return a+b;
a: 8b 55 fc mov edx,DWORD PTR [rbp-0x4] ; 将 a 加载到寄存器 edx
d: 8b 45 f8 mov eax,DWORD PTR [rbp-0x8] ; 将 b 加载到寄存器 eax
10: 01 d0 add eax,edx ; 将 a 和 b 相加并保存在寄存器 eax 中
}
12: 5d pop rbp ; 恢复调用者的基址指针
13: c3 ret ; 返回至调用者
0000000000000014 <main>:
int main()
{
14: 55 push rbp ; 保存调用者的基址指针
15: 48 89 e5 mov rbp,rsp ; 设置当前函数的基址指针
18: 48 83 ec 10 sub rsp,0x10 ; 在栈上分配 16 字节的空间
int x = 5;
1c: c7 45 fc 05 00 00 00 mov DWORD PTR [rbp-0x4],0x5 ; 将值 5 存储在变量 x 的位置上
int y = 10;
23: c7 45 f8 0a 00 00 00 mov DWORD PTR [rbp-0x8],0xa ; 将值 10 存储在变量 y 的位置上
int u = add(x, y);
2a: 8b 55 f8 mov edx,DWORD PTR [rbp-0x8] ; 将变量 y 的值加载到寄存器 edx
2d: 8b 45 fc mov eax,DWORD PTR [rbp-0x4] ; 将变量 x 的值加载到寄存器 eax
30: 89 d6 mov esi,edx ; 将 y 的值复制到 esi 寄存器,作为第二个参数
32: 89 c7 mov edi,eax ; 将 x 的值复制到 edi 寄存器,作为第一个参数
34: e8 c7 ff ff ff call 0 <add> ; 调用函数 add
39:
上記のアセンブリ コードを 1 行ずつ説明し、最初にadd
関数を説明します。
-
push rbp
: 呼び出し元のベース アドレス ポインターをrbp
スタックにプッシュして、呼び出し元関数のスタック フレーム情報を保存します。 -
mov rbp, rsp
: スタック ポインタの値をrsp
現在の関数のベース アドレス ポインタにコピーしますrbp
。これは、現在の関数のスタック フレームを確立するために使用されます。 -
mov DWORD PTR [rbp-0x4], edi
: 最初のパラメータの値a
(レジスタに格納edi
) をスタック フレームの offset に保存します-0x4
。 -
mov DWORD PTR [rbp-0x8], esi
: 2 番目のパラメータの値b
(レジスタに格納esi
) をスタック フレームの offset に保存します-0x8
。 -
mov edx, DWORD PTR [rbp-0x4]
:スタック フレームのオフセットの-0x4
値 (つまりパラメータa
)をレジスタにロードしますedx
。 -
mov eax, DWORD PTR [rbp-0x8]
:スタック フレームのオフセットの-0x8
値 (つまりパラメータb
)をレジスタにロードしますeax
。 -
add eax, edx
:eax
とレジスタedx
の値を加算し、結果eax
を に格納します。つまり、 の結果が得られますa + b
。 -
pop rbp
: 呼び出し元関数のベース アドレス ポインターであるスタックの最上位要素をポップしrbp
、呼び出し元関数のコンテキストを復元します。 -
ret
: 現在の関数から戻り、制御フローを呼び出し元に返します。
次に、main
関数の説明を入力します。
-
push rbp
: 呼び出し元のベース アドレス ポインターをrbp
スタックにプッシュして、呼び出し元関数のスタック フレーム情報を保存します。 -
mov rbp, rsp
: スタック ポインタの値をrsp
現在の関数のベース アドレス ポインタにコピーしますrbp
。これは、現在の関数のスタック フレームを確立するために使用されます。 -
sub rsp, 0x10
: ローカル変数と一時データを保存するためにスタック上に 16 バイトのスペースを割り当てます。 -
mov DWORD PTR [rbp-0x4], 0x5
x
: 変数の場所、つまりスタック フレームのオフセットに値 5 を格納します-0x4
。 -
mov DWORD PTR [rbp-0x8], 0xa
y
: 変数の場所、つまりスタック フレームのオフセットに値 10 を格納します-0x8
。 -
mov edx, DWORD PTR [rbp-0x8]
:y
変数の値をレジスタにロードしますedx
。 -
mov eax, DWORD PTR [rbp-0x4]
:x
変数の値をレジスタにロードしますeax
。 -
mov esi, edx
:レジスタedx
の値(つまり、y
変数の値) を2 番目のパラメータとしてesi
レジスタ1にコピーします。 -
mov edi, eax
:eax
レジスタ内の値 (つまり、x
変数の値)をedi
最初のパラメータとしてレジスタにコピーします。 -
call 0 <add>
: 関数を呼び出しadd
、制御フローをadd
関数のコードに転送します。
参考文献
パラメータ値をレジスタにロードすることで、関数は
add
メモリ内の変数に直接アクセスすることなく、レジスタからパラメータ値を直接読み取って計算できます。これにより、実行効率が向上し、メモリ アクセスの数が削減されます。↩︎