まず、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 組み立て説明書をご覧ください。
最終的なスタックレイアウト