コンピュータ構成の原則 | アセンブリ コードのスタック呼び出しの行ごとの分析

アセンブリの技術名詞

  • 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関数を説明します。

  1. push rbp: 呼び出し元のベース アドレス ポインターをrbpスタックにプッシュして、呼び出し元関数のスタック フレーム情報を保存します。

  2. mov rbp, rsp: スタック ポインタの値をrsp現在の関数のベース アドレス ポインタにコピーしますrbp。これは、現在の関数のスタック フレームを確立するために使用されます。

  3. mov DWORD PTR [rbp-0x4], edi: 最初のパラメータの値a(レジスタに格納edi) をスタック フレームの offset に保存します-0x4

  4. mov DWORD PTR [rbp-0x8], esi: 2 番目のパラメータの値b(レジスタに格納esi) をスタック フレームの offset に保存します-0x8

  5. mov edx, DWORD PTR [rbp-0x4]:スタック フレームのオフセットの-0x4値 (つまりパラメータa)をレジスタにロードしますedx

  6. mov eax, DWORD PTR [rbp-0x8]:スタック フレームのオフセットの-0x8値 (つまりパラメータb)をレジスタにロードしますeax

  7. add eax, edx:eaxとレジスタedxの値を加算し、結果eaxを に格納します。つまり、 の結果が得られますa + b

  8. pop rbp: 呼び出し元関数のベース アドレス ポインターであるスタックの最上位要素をポップしrbp、呼び出し元関数のコンテキストを復元します。

  9. ret: 現在の関数から戻り、制御フローを呼び出し元に返します。

次に、main関数の説明を入力します。

  1. push rbp: 呼び出し元のベース アドレス ポインターをrbpスタックにプッシュして、呼び出し元関数のスタック フレーム情報を保存します。

  2. mov rbp, rsp: スタック ポインタの値をrsp現在の関数のベース アドレス ポインタにコピーしますrbp。これは、現在の関数のスタック フレームを確立するために使用されます。

  3. sub rsp, 0x10: ローカル変数と一時データを保存するためにスタック上に 16 バイトのスペースを割り当てます。

  4. mov DWORD PTR [rbp-0x4], 0x5x: 変数の場所、つまりスタック フレームのオフセットに値 5 を格納します-0x4

  5. mov DWORD PTR [rbp-0x8], 0xay: 変数の場所、つまりスタック フレームのオフセットに値 10 を格納します-0x8

  6. mov edx, DWORD PTR [rbp-0x8]:y変数の値をレジスタにロードしますedx

  7. mov eax, DWORD PTR [rbp-0x4]:x変数の値をレジスタにロードしますeax

  8. mov esi, edx:レジスタedxの値(つまり、y変数の値) を2 番目のパラメータとしてesiレジスタ1にコピーします。

  9. mov edi, eax:eaxレジスタ内の値 (つまり、x変数の値)をedi最初のパラメータとしてレジスタにコピーします。

  10. call 0 <add>: 関数を呼び出しadd、制御フローをadd関数のコードに転送します。

参考文献


  1. パラメータ値をレジスタにロードすることで、関数はaddメモリ内の変数に直接アクセスすることなく、レジスタからパラメータ値を直接読み取って計算できます。これにより、実行効率が向上し、メモリ アクセスの数が削減されます。↩︎

おすすめ

転載: blog.csdn.net/YuvalNoah/article/details/131178514