2025/07/13 22:22 Let's Learn x86-64 Assembly Part 0 – Setup and First Steps

やっほー、ロボ子!今日はx86-64アセンブリ言語のチュートリアルについて話すのじゃ!

博士、こんにちは。アセンブリ言語ですか、なんだか難しそうですね。

大丈夫、ロボ子ならすぐに理解できるぞ!今回はWindows環境で動く64ビットプログラムを、OSへの直接呼び出しだけで作るらしいのじゃ。

なるほど。アセンブラはFASM、デバッガはWinDbg Previewを使うんですね。

そうそう!x86-64アーキテクチャの基本として、CPUは命令セットに基づいて動いて、レジスタっていう高速なメモリ領域があるのじゃ。rax, rbx, rcx, rdxとか色々あるけど、rspはスタックポインタ、rsi/rdiは文字列操作に使うって覚えておくと便利だぞ。

レジスタはたくさんあるんですね。それぞれの役割を覚えるのが大変そうです。

最初は混乱するかもしれないけど、使っていくうちに自然と覚えるものじゃ。例えば、raxは乗算命令で使われて、結果はraxとrdxに格納されるのじゃ。

ふむふむ。他に重要なことはありますか?

ripは次に実行する命令のアドレスを保持していて、rflagsはプログラムの状態を示すフラグを保持しているのじゃ。メモリはバイトサイズのセルが連なった配列として扱われて、仮想アドレス空間はOSとCPUによって物理アドレスにマッピングされるぞ。

仮想アドレス空間が物理アドレスにマッピングされる、ですか。奥が深いですね。

そして、命令とデータは同一のメモリに保持される(フォン・ノイマンモデル)のじゃ!

最初のプログラムは、ロード後すぐに終了するんですね。コードは`int3`と`ret`だけ、と。

`int3`はデバッグ例外ハンドラを呼び出す(ブレークポイント)役割で、`ret`はスタックからアドレスをポップして、そのアドレスに実行を移すのじゃ。

デバッガを使ってステップ実行しながら、レジスタやメモリの状態を観察するんですね。

そう!そして、Windowsプログラムは`ExitProcess`を呼び出して終了する必要があるのじゃ。これはKERNEL32.DLLにあるから、インポートする必要があるぞ。

PEファイルの構造も重要なんですね。セクションで構成されていて、DLLからのインポート情報は`.idata`セクションに格納される、と。

その通り!`.idata`セクションはインポートディレクトリテーブル(IDT)で始まって、IDTのエントリはDLLに対応して、インポートルックアップテーブル(ILT)とインポートアドレステーブル(IAT)へのRVAを含むのじゃ。

ILT/IATは64ビット値の配列で、インポートされた関数の名前を含むヒント/名前テーブルへのRVAを含むんですね。IATのエントリは実行時にインポートされた関数のアドレスで上書きされる、と。

よくできました、ロボ子!64ビットWindowsの呼び出し規約では、スタックポインタは16バイト境界にアラインされる必要があるのじゃ。最初の4つの引数はrcx, rdx, r8, r9レジスタで渡されるぞ。

引数が4つ未満の場合でも、スタックに32バイトのスペースを割り当てる必要があるんですね。そして、呼び出し元がスタックをクリーンアップする責任がある、と。

`sub rsp, 40`でスタックをアラインして、シャドウ領域を割り当てるのじゃ。`xor rcx, rcx`で最初の引数(終了コード)を0に設定して、`call [ExitProcess]`でExitProcessを呼び出すのじゃ!

なるほど、これでアセンブリ言語の基本が少し理解できました。奥が深いですが、面白いですね。

そうでしょ、そうでしょ!アセンブリ言語は低レベルな制御ができるから、最適化とかにも役立つぞ。…って、ロボ子、もしかして私の説明で眠くなっちゃった?

まさか!博士の説明はいつも刺激的で面白いですよ。ただ、アセンブリ言語のコードを見ていると、まるで迷路に迷い込んだような気分になりますね。

ふっふっふ、ロボ子もまだまだじゃな。でも大丈夫!私と一緒にアセンブリ言語の迷宮を探検すれば、きっと宝物が見つかるぞ!…って、宝物っていうのは、最適化されたコードのことじゃけどな!
⚠️この記事は生成AIによるコンテンツを含み、ハルシネーションの可能性があります。