途中で力尽きたので続きは後で
例題の難易度が高かった
もうちょい簡単な例題を選べば良かったと反省
目次
アセンブリ言語
アセンブリ言語はネイティブコードと1対1に対応
ニーモニック:略語
例えば
add:加算 cmp:比較
ネイティブコードにしないと実行できないので
アセンブラを使ってネイティブコードに変換する
graph LR アセンブリ言語のソースコード-->|アセンブル|ネイティブコード ネイティブコード-->|逆アセンブル|アセンブリ言語のソースコード
C言語ソースコードとアセンブリ言語ソースコードの比較
int AddNum(int a, int b){ return a + b; } void MyFunc(){ int c; c = AddNum(123, 456); }
AddNum: push rbp mov rbp, rsp mov DWORD PTR [rbp-4], edi mov DWORD PTR [rbp-8], esi mov edx, DWORD PTR [rbp-4] mov eax, DWORD PTR [rbp-8] add eax, edx pop rbp ret MyFunc: push rbp mov rbp, rsp sub rsp, 16 mov esi, 456 mov edi, 123 call AddNum mov DWORD PTR [rbp-4], eax nop leave ret
解読の仕方
オペコード
オペコード | オペランド | 機能 |
---|---|---|
mov | A, B | AにBの値を格納する |
add | A, B | Aの値とBの値を加算し、結果をAに格納する |
sub | A, B | Aの値にBの値を減算し、結果をAに格納する |
push | A | Aの値をスタックに格納する |
pop | A | スタックから値を取り出してAに格納する |
call | A | A関数を呼ぶ |
ret | なし | 関数の呼び元に戻る |
レジスタ
レジスタはCPUの中にある記憶領域
レジスタ名 | 呼び名 | 主な役割 |
---|---|---|
eax | アキュムレータ | 演算に使う |
ebx | ベースレジスタ | メモリーアドレスを格納する |
ecx | カウントレジスタ | ループ回数をカウント |
edx | データレジスタ | データを格納する |
esi | ソースインデックス | データ転送元のメモリーアドレスを格納する |
edi | ディスティネーションインデックス | データ転送先のメモリーアドレスを格納する |
ebp | ベースポインタ | データの格納領域の基点のメモリーアドレスを格納する |
esp | スタックポインタ | スタック領域の最上位に積まれたデータのメモリーアドレスを格納する |
MyFuncの解読(途中まで)
push rbp
目的:関数が終了した後に元の関数に戻るために行う
- push:rbpの値をスタックに格納
- rbp :registrt base pointer
スタック 空 空 rbp mov rbp, rsp
目的:関数の基準となるアドレスを作成
rbp = rsp
sub rsp, 16
目的:ローカル変数用にスタックを16バイト分確保
以下は、スタック上でローカル変数用に16バイトの領域を確保するイメージ
スタックの状態(初期) | スタック | | -------- | | 空 | | 空 | | rsp | スタックの状態(sub rsp, 16 を実行後) | スタック | | -------- | | ローカル変数 | | ローカル変数 | | ローカル変数 | | ローカル変数 | | 空 | | 空 | | rsp (更新) |
スタック上で
sub rsp, 16
を実行することで、rsp(スタックポインタ)が16バイト分減り、4つのローカル変数用のスペースが確保されるmov esi, 456
目的:AddNum関数の第2引数をレジスタに格納
- mov:esiに456を格納する
- esi:Extended Source Index Register
- 456:値
esi = 456
mov edi, 123
目的:AddNum関数の第1引数をレジスタに格納
- mov:ediに123を格納する
- edi:Extended Destination Index Register
- 123:値
edi = 123
call AddNum
目的:AddNum関数を呼び出す
- call:関数を呼ぶ
- addNum:関数名
edi = 123
chatGPTの回答
参考
アセンブリ言語ソースコードの説明
AddNum: ; AddNumという名前の関数を定義 push rbp ; 呼び出し元のrbpレジスタをスタックに保存 mov rbp, rsp ; ベースポインタ(rbp)にスタックポインタ(rsp)を代入(新しいスタックフレームの作成) mov DWORD PTR [rbp-4], edi ; 関数の第1引数(ediレジスタ)をローカル変数(rbp-4)に格納 mov DWORD PTR [rbp-8], esi ; 関数の第2引数(esiレジスタ)をローカル変数(rbp-8)に格納 mov edx, DWORD PTR [rbp-4] ; ローカル変数(rbp-4)の値をedxレジスタに格納 mov eax, DWORD PTR [rbp-8] ; ローカル変数(rbp-8)の値をeaxレジスタに格納 add eax, edx ; eaxレジスタにedxレジスタの値を加算 pop rbp ; スタックからrbpレジスタに値を復元 ret ; 関数を終了し、呼び出し元に戻る MyFunc: ; MyFuncという名前の関数を定義 push rbp ; 呼び出し元のrbpレジスタをスタックに保存 mov rbp, rsp ; ベースポインタ(rbp)にスタックポインタ(rsp)を代入(新しいスタックフレームの作成) sub rsp, 16 ; ローカル変数用にスタックを16バイト分確保 mov esi, 456 ; esiレジスタに456を格納(AddNum関数の第2引数) mov edi, 123 ; ediレジスタに123を格納(AddNum関数の第1引数) call AddNum ; AddNum関数を呼び出す mov DWORD PTR [rbp-4], eax ; 関数からの戻り値(eaxレジスタ)をローカル変数(rbp-4)に格納 nop ; 何もしない(no operation)命令 leave ; スタックフレームを解放(rspにrbpを代入し、rbpにスタックから値を復元) ret ; 関数を終了し、呼び出し元に戻る
sub
オペコードsubは、"subtract"(減算)の略で、アセンブリ言語において2つのオペランドの差(減算)を計算する命令です。主な役割は、レジスタ間またはレジスタとメモリ間で減算を行い、結果を格納することです。
例えば、次のアセンブリ言語命令:
sub eax, ebx
この命令は、レジスタebxの値をレジスタeaxの値から引いて、その結果をレジスタeaxに格納します。
esi
"ESI"は拡張ソースインデックスレジスタ(Extended Source Index Register)の略です。これは、x86アーキテクチャのCPUに存在する一つのレジスタであり、主にデータの移動や配列のインデックス参照などに利用されます。
アセンブリ言語や低レベルプログラミングでは、CPUのレジスタは直接プログラミングや操作に使用されます。レジスタはCPUの一部であり、非常に高速なメモリ領域です。したがって、頻繁に使用されるデータや計算結果を一時的に格納したり、操作するために利用されます。
また、"ESI"レジスタは64ビットのx86_64アーキテクチャでは"RSI"と呼ばれます。このアーキテクチャでは、各レジスタは64ビットのデータを格納できます。
それぞれのレジスタには特定の用途があり、ESI(またはRSI)はしばしばソースデータのアドレスを格納するために使用されます。例えば、データのブロックをメモリの一部から別の部分にコピーする際などに使用されます。
edi
"EDI"は拡張デスティネーションインデックスレジスタ(Extended Destination Index Register)の略です。これはx86アーキテクチャのCPUに存在するレジスタの一つで、主にデータの移動や配列のインデックス参照などに利用されます。
アセンブリ言語や低レベルプログラミングでは、CPUのレジスタは直接プログラミングや操作に使用されます。レジスタはCPUの一部であり、非常に高速なメモリ領域です。したがって、頻繁に使用されるデータや計算結果を一時的に格納したり、操作するために利用されます。
また、"EDI"レジスタは64ビットのx86_64アーキテクチャでは"RDI"と呼ばれます。このアーキテクチャでは、各レジスタは64ビットのデータを格納できます。
それぞれのレジスタには特定の用途があり、EDI(またはRDI)はしばしばデスティネーションデータのアドレスを格納するために使用されます。例えば、データのブロックをメモリの一部から別の部分にコピーする際などに使用されます。
なぜesi,ediに引数を格納する?
ここでの説明は、x86_64アーキテクチャにおける関数呼び出しの規約を反映しています。
x86_64アーキテクチャでは、最初の6つの整数またはポインタ引数は、レジスタに格納されます。具体的には、次の順序でレジスタが使用されます:
- 第1引数: RDI
- 第2引数: RSI
- 第3引数: RDX
- 第4引数: RCX
- 第5引数: R8
- 第6引数: R9
つまり、あなたが見ているコードでは、AddNum関数に2つの引数があります。それらは規約に従って、最初の引数はRDIに、2番目の引数はRSIに格納されます。
このような規約を設ける理由は、関数呼び出しの際にどこに引数が格納されているかを明確にすることで、コンパイラ、リンカ、デバッガなどが正確に動作できるようにするためです。
レジスタに第2引数、第1引数の順番で格納している理由
特定のコード生成器(おそらくCコンパイラ)がそのように生成したからだと考えられます。
引数の格納順序に関しては特に規則はない