x86-64 C言語コードのアセンブリ命令解析処理記録

まず、Xcode を通じてターミナル APP を作成し、言語として C を選択します。コードは以下のように表示されます。

#include <stdio.h>

int main(int argc, const char * argv[]) {
    int a[7]={1,2,3,4,5,6,7};
    int *ptr =(int*)(&a+1);
    printf("%d\n",*(ptr));
    return 0;
}

return 0 にブレークポイントを設定し、Xcode メニューで [デバッグ]、[ワークフローのデバッグ]、[逆アセンブリを常に表示] を選択し、[実行] をクリックします。このとき、ブレークポイントはアセンブリコードにジャンプします。アセンブリコードは次のとおりです。

Terminal`main:
    0x100003ec0 <+0>:   pushq  %rbp
    0x100003ec1 <+1>:   movq   %rsp, %rbp
    0x100003ec4 <+4>:   subq   $0x50, %rsp
    0x100003ec8 <+8>:   movq   0x131(%rip), %rax         ; (void *)0x00007ff84ef2f8a0: __stack_chk_guard
    0x100003ecf <+15>:  movq   (%rax), %rax
    0x100003ed2 <+18>:  movq   %rax, -0x8(%rbp)
    0x100003ed6 <+22>:  movl   $0x0, -0x34(%rbp)
    0x100003edd <+29>:  movl   %edi, -0x38(%rbp)
    0x100003ee0 <+32>:  movq   %rsi, -0x40(%rbp)
    0x100003ee4 <+36>:  movq   0xa5(%rip), %rax
    0x100003eeb <+43>:  movq   %rax, -0x30(%rbp)
    0x100003eef <+47>:  movq   0xa2(%rip), %rax
    0x100003ef6 <+54>:  movq   %rax, -0x28(%rbp)
    0x100003efa <+58>:  movq   0x9f(%rip), %rax
    0x100003f01 <+65>:  movq   %rax, -0x20(%rbp)
    0x100003f05 <+69>:  movl   0x9d(%rip), %eax
    0x100003f0b <+75>:  movl   %eax, -0x18(%rbp)
    0x100003f0e <+78>:  leaq   -0x30(%rbp), %rax
    0x100003f12 <+82>:  addq   $0x1c, %rax
    0x100003f16 <+86>:  movq   %rax, -0x48(%rbp)
    0x100003f1a <+90>:  movq   -0x48(%rbp), %rax
    0x100003f1e <+94>:  movl   (%rax), %esi
    0x100003f20 <+96>:  leaq   0x85(%rip), %rdi          ; "%d\n"
    0x100003f27 <+103>: movb   $0x0, %al
    0x100003f29 <+105>: callq  0x100003f5a               ; symbol stub for: printf
    0x100003f2e <+110>: movq   0xcb(%rip), %rax          ; (void *)0x00007ff84ef2f8a0: __stack_chk_guard
    0x100003f35 <+117>: movq   (%rax), %rax
    0x100003f38 <+120>: movq   -0x8(%rbp), %rcx
    0x100003f3c <+124>: cmpq   %rcx, %rax
    0x100003f3f <+127>: jne    0x100003f4d               ; <+141> at main.c
->  0x100003f45 <+133>: xorl   %eax, %eax
    0x100003f47 <+135>: addq   $0x50, %rsp
    0x100003f4b <+139>: popq   %rbp
    0x100003f4c <+140>: retq   
    0x100003f4d <+141>: callq  0x100003f54               ; symbol stub for: __stack_chk_fail
    0x100003f52 <+146>: ud2    

まず、以下で使用されるいくつかのレジスタを紹介します。

rip:プログラム カウンタ レジスタ
rsp: スタックの最上位を指すスタック ポインタ レジスタ
rbp: スタックの最下位を指すスタック ベース アドレス レジスタ
edi: 関数パラメータ
rsi/esi: 関数パラメータ
eax: アキュムレータまたは関数の戻りに使用価値

1. rbp のアドレスをスタックにプッシュすると、rsp はスタックの先頭を指し続けます。pushq %rbp

2. スタックの最上位の rsp の値をスタックの最下位の rbp に割り当てます。movq %rsp, %rbp

3. スタックの先頭は 5*16 バイト下に移動されます。これは、後方に予約された 80 バイトのスペースとして理解できます。X64 でのスタック割り当てのサイズは 0x10 の倍数です。subq $0x50, %rsp

4、movq 0x131(%rip)、%rax ; (void *)0x00007ff84ef2f8a0: __stack_chk_guard

0x131 (%rip) は、次の命令のアドレス (0x100003ecf) に 0x131 を加算してターゲット アドレス (0x100004000) を取得し、8 バイトの値を取得して rax レジスタに設定することを意味します。

[デバッグ ワークフロー | メモリの表示] を選択し、アドレスに '0x100004000' を入力して Enter キーを押します。ここでの内容が 0x00007ff84ef2f8a0 であることがわかります (ビッグ エンディアンとスモール エンディアンの問題に注意してください)。

 5、movq (%rax)、%rax

rax レジスタに格納されているアドレスが指す値を rax レジスタに渡します。前の操作と同様に、ここでの値は 0x55d1d55afee700d6 であることがわかりました。

 6、movq %rax、-0x8(%rbp)

値を rax に保存します。これは、上の図に示されている 8 バイトであり、rbp-0x8 の場所に保存します。まず rbp と rsp の値を出力し、次に rsp にジャンプしてメモリを表示します。

 

赤いボックスは値を保存する場所です。右側の 8 バイトは、rbp が指す位置です。

7、movl $0x0、-0x34(%rbp)

rbp-0x34 の位置に 4 バイトの 0 を設定します。ここでの目的は、次の命令で -0x38 (%rbp) の上位バイトをクリアすることです。場所は 0x7ff7bfeff3ac です。

 8、movl %edi、-0x38(%rbp)

このコマンドは、edi レジスタの値を rbp-0x38 の場所 (上の図では 0x7ff7bfeff3a8) に保存し、値は 1 です。先ほど、関数パラメータ、つまり int argc を保存するために edi が使用されると述べましたが、この例では argc の値が 1 であるため、edi レジスタの値は 1 になります。

9、movq %rsi、-0x40(%rbp)

このコマンドは、rsi レジスタの値を rbp-0x40 の場所 (上の図では 0x7ff7bfeff3a0) に保存し、その値は 0x7ff7bfeff518 です。これはパラメータ argv 0x7ff7bfeff708 の値です。argv は const char ** なので、これもアドレス値であり、アドレスに移動してその内容を表示します。

10、movq 0xa5(%rip)、%rax

 0x100003eeb+0xa5=0x100003f90 にある 8 バイトのコンテンツを取得し、rax レジスタに保存します。

 11、movq %rax、-0x30(%rbp)

 rax レジスタの値を rbp-0x30 の場所に保存します。

12~17は上記2ステップと同じで、3、4、5、6、7の値が格納されますが、7はmovlが4バイトを表すため、movqは別個に格納されることに注意してくださいは 8 バイト、つまり 2 int を表します。

0x100003eef <+47>:  movq   0xa2(%rip), %rax
    0x100003ef6 <+54>:  movq   %rax, -0x28(%rbp)
    0x100003efa <+58>:  movq   0x9f(%rip), %rax
    0x100003f01 <+65>:  movq   %rax, -0x20(%rbp)
    0x100003f05 <+69>:  movl   0x9d(%rip), %eax
    0x100003f0b <+75>:  movl   %eax, -0x18(%rbp)

18、リーク -0x30(%rbp)、%rax

rbp-0x30=0x7ff7bfeff3b0 を取得し、rax レジスタに格納します。

19、addq $0x1c、%rax

値 0x7ff7bfeff3b0 を rax レジスタに格納し、さらに 0x1c (0x7ff7bfeff3cc) を rax レジスタに格納します。ここでの対応するコードは `int *ptr =(int*)(&a+1);`0x1c は 28 で、配列のサイズが 28 バイトであることを意味します。これは、ポインタの追加が値を特定の値に置き換えることを意味します。コンパイル段階での値。つまり、この値はポインター型のサイズで乗算されます。

20、movq %rax、-0x48(%rbp)

次に、rax 値 0x7ff7bfeff3cc を rbp-0x48 の場所に保存します。

21、movq -0x48(%rbp)、%rax

次に、保存したばかりの値をレジスタ rax に保存します。

22、movl (%rax)、%esi

rax レジスタに格納されているアドレスに対応する値 (1) をレジスタ esi に格納します。これは、以下で呼び出される print メソッドの第 2 パラメータとして使用されます。

23、リーク 0x85(%rip)、%rdi ; 「%d\n」

rip=0x100003f27、0x100003fac に 0x85 を追加し、それを print 呼び出しの最初のパラメータである rdi レジスタに設定します。0x100003fac の位置の値は文字列 "%d\n" になります。

24、movb $0x0、%al

即値 0 を al レジスタに格納します。では、eax、ax、al(ah) の関係をどのように理解すればよいのでしょうか?
専門的なポイントは次のように説明できます: eax は 32 ビット レジスタ、ax は 16 ビット レジスタ、al(ah) は 8 ビット レジスタです。ビットレジスタ。

printf など、可変長パラメータを持つ関数の場合は、%al を使用して使用するベクトル レジスタの数を指定する必要があります。ここでは可変パラメータを使用しないため、al レジスタに 0 を設定する必要があります。参考: gcc でコンパイルされた C「Hello World」プログラムから printf x86 アセンブリを呼び出す前に、%al レジスタとスタックが変更されるのはなぜですか

アセンブリ関数呼び出しで渡すパラメーター 

25、callq 0x100003f5a ; シンボル スタブ: printf

printf関数を呼び出します。call には 1 つの機能があります。call 命令の次の命令のアドレスをスタックにプッシュします。

26、movq 0xcb(%rip)、%rax ; (void *)0x00007ff84ef2f8a0: __stack_chk_guard

27、movq (%rax)、%rax

ステップ 26 と 27 はステップ 4 と 5 に似ているため、ここでは繰り返しません。

28、movq -0x8(%rbp)、%rcx

スタックの下位 8 バイトを rcx レジスタに格納します

29、cmpq %rcx、%rax

rcxレジスタとraxレジスタの値が等しいか比較し、結果をステータスレジスタに書き込みます

30、jne 0x100003f4d ; <+141> (main.c)

比較結果 29 が等しくない場合は、0x100003f4d にジャンプして実行を継続します (つまり 35)。等しい場合は、31 ステップを実行します。ここでは主に __stack_chk_guard_ptr を使用して、スタック オーバーフローが発生し、スタックの下部にある最初の 8 バイトが改ざんされるかどうかを判断します。__stack_chk_guard_ptr の理解を参照できます。

31、xorl %eax、%eax

main関数の戻り値としてeaxレジスタをクリアする

32、addq $0x50、%rsp

この文は前の文と完全に一致していますsubq $0x50, %rsp。通过给栈顶指针加上开辟栈的大小,回收栈顶指针开辟的空间。

33、ポップキュー%rbp

この命令は、スタックをポップし、スタックからポップされた値をレジスタ rbp に入れることを意味します。

34、リキュー

この文は、main 関数を終了すると rip 値が復元されることを意味します。この例ではこれが反映されておらず、main 関数の呼び出し元は rip を保存します。

35、callq 0x100003f54 ; シンボル スタブ: __stack_chk_fail

__stack_chk_fail 関数を呼び出す

36、ud2

UD2命令のバイト エンコーディングは 0F 0B であり、2 バイト命令です。アセンブリ言語では、UD2 命令を使用して、デバッグ ブレークポイントのトリガーやプログラム実行の中断などの特別な機能を実装できます。UD2 命令は、プログラムのデバッグによく使用されます。詳細については、 ud2 組み立て説明書をご覧ください。

最終的なスタックレイアウト

おすすめ

転載: blog.csdn.net/Mamong/article/details/132126024