世界を動かす技術を、日本語で。

x86-64アセンブリを学ぼう (2020)

概要

  • x86-64アセンブリ 学習のためのセットアップと最初のステップの紹介
  • FASM(Flat Assembler)WinDbg の導入方法と選定理由
  • レジスタメモリモデル など、x86-64アーキテクチャの基礎解説
  • 最小限の Windowsプログラム をアセンブリで作成・分析
  • 今後のシリーズ の方向性や前提知識について説明

x86-64アセンブリ入門 Part 0:セットアップと最初の一歩

  • 大学で学んだ x86アセンブリ は既に時代遅れだった背景
    • 2008~2009年当時でも64ビットプロセッサが普及し始めていた事実
    • 授業ではDOSやリアルモード、メモリセグメンテーションなど古い内容中心
  • 授業や独学で コンパイラ出力のアセンブリ を理解できる程度には習得
  • これまで本格的なアセンブリプログラムは未経験
  • パンデミックによる自宅待機をきっかけに x86-64アセンブリ 学習を決意
  • レガシーな内容 は排除し、現代的なx86-64のみを対象とする方針
  • 学習記録として チュートリアル形式 でブログ公開を決意
  • Windows向け64ビットプログラム のみを対象
  • ライブラリは使用せず、OS呼び出しのみ許可する方針

必要なツール

  • アセンブラ(FASM)
    • CPUは 機械語 のみ理解
    • アセンブリ言語は 人間向け記法、アセンブラが機械語へ変換
    • x86-64アセンブリには 統一規格が存在しない ため、アセンブラ選択が重要
    • 本シリーズでは Flat Assembler (FASM) を使用
      • 小型・入手容易・マクロ機能・組み込みエディタ付属
  • デバッガ(WinDbg)
    • プログラムの状態確認用
    • Visual Studioの統合デバッガも利用可能だが 独立型デバッガ 推奨
    • これまでは OllyDbg を使用していたが、64ビット未対応
    • WinDbg (Windows 10 SDK版も可)を使用
      • インストール時はWinDbgのみ選択
      • どちらのバージョンも本用途では大差なし

アセンブリ的思考法

  • C言語やC++の知識 を前提とし、アセンブリ初学者向け
  • CPUの命令セット は決まった動作のみ可能
    • 「命令」とはCPUが実行できる一つの操作
    • 例:「8ビット値をメモリアドレスへ書き込む」
    • 命令はシンプルでパラメータ化されている
  • 低レベルモデル の理解が重要
    • 高水準言語の概念も最終的にはこのモデルへマッピングされる
レジスタ
  • レジスタ はCPU内蔵の超高速・小容量メモリ
  • x86-64の 汎用レジスタ は16個、各64ビット幅
  • 各レジスタは下位バイト・ワード・ダブルワード単位でアクセス可能
    • 例:rax→al, ax, eax
  • 特殊用途レジスタ
    • rsp :スタックポインタ(push/pop/call/retで使用)
    • rsi/rdi :文字列操作命令のソース・デスティネーションインデックス
    • mul命令 等一部命令は特定レジスタ限定
  • その他の重要レジスタ
    • rip :次の命令アドレスを保持
    • rflags :演算結果フラグ等、CPU状態管理
  • SIMDや浮動小数点用レジスタも存在するが本シリーズでは扱わない
メモリとアドレス
  • メモリは バイト単位の配列 として認識
    • 各バイトに アドレス が割り振られている
  • 旧x86では セグメントレジスタ による面倒なアドレス計算が必要だった
  • x86-64ではセグメント管理不要
    • セグメントは存在するがユーザープログラムでは意識不要
    • すべてのメモリは フラットなバイト配列 として扱える
  • 実際は 仮想アドレス空間物理アドレス のマッピング
    • OSとCPUが協調して各プロセスに独立したアドレス空間を提供
    • 例:同じアドレス(0x410F119C)が異なるプロセスで異なる物理メモリを指す
  • von Neumann型 :命令とデータが同じメモリ空間
    • 対比:Harvard型(例:ArduinoのAVR)

最初のプログラム

  • FASM をダウンロードし、最小のアセンブリプログラムを作成
  • プログラムの目的: ツールの使い方に慣れる
  • サンプルコード(x86-64アセンブリ)
    format PE64 NX GUI 6.0
    entry start
    section '.text' code readable executable
    start:
        int3
        ret
    
コードの解説
  • format PE64 NX GUI 6.0
    • FASMへの 出力形式指示 (Windows用PEフォーマット)
  • entry start
    • エントリポイント 指定(startラベル)
  • section '.text' code readable executable
    • 実行可能コードセクション の開始
  • start:
    • ラベル 定義(エントリポイント)
  • int3
    • デバッガ割り込み 命令(実行時にブレークポイント)
  • ret
    • リターン命令 (呼び出し元へ制御を戻す)

今後のシリーズの進め方

  • FASMWinDbg を使い、Windows用x86-64アセンブリをゼロから解説
  • C/C++経験者 を想定しつつ、アセンブリ未経験者にも配慮
  • レガシー仕様や不要な複雑さ は極力排除
  • 各回で 実践的なサンプル詳細な解説 を提供する方針

Hackerたちの意見

(2020) 当時のディスカッション (180ポイント, 38コメント) https://news.ycombinator.com/item?id=24195627

ここにいる著者です。このシリーズの最後の部分がまだ下書きに残ってます。本当はフロー制御命令について書く予定だったんだけど、色々と話が広がっちゃって、リロケーションや位置独立コード、ASLRについても触れちゃった… いつか整理して投稿するつもりです。

ぜひお願いします!

さらに、rax、rbx、rcx、rdxの上位8ビットはah、bh、ch、dhと呼ばれます。「ax、bx、cx、dx」のことを言ってるの?

それは下位8ビットのことじゃない?

ax、bx、cx、dxはそれぞれrax、rbx、rcx、rdxの下位16ビットを指す16ビットレジスタです。ビット0..8はal/bl/cl/dl、ビット8..16はah/bh/ch/dhと呼ばれます。

ちょっとアセンブリを触ってみるのは、物事をよりよく理解するための素晴らしい方法だよ。大きなプロジェクトを作る必要はないけど、ぜひチェックしてみてね。

ありがたいことにインテル構文だね。

ちょっと気になるな。他にどんな構文があるの?

すごい投稿だね!実行可能ファイルのリバースエンジニアリングに関するセクションを追加する予定はあるの?

(私は作者です)このシリーズには追加しないけど、ブログの最初の投稿がシンプルなリバースエンジニアリングの例だよ: https://gpfault.net/posts/ripping-sprites-from-super-cyborg....

私も一つ書いたよ: https://www.nayuki.io/page/a-fundamental-introduction-to-x86...

似たようなものだけど、ブラウザでローカルのセットアップなしで例を試せるよ。 https://shikaan.github.io/assembly/x86/guide/2024/09/08/x86-... ちなみに、私が作者だから、宣伝みたいになっちゃってごめんね。

いいね!信頼できない入力はサニタイズしてる?私が見る限り、NASMで直接アセンブルしてバイナリを実行してるみたいだけど。

アセンブリで遊んでみたいけど、面白いことを思いつかないな。

ChatGPTとnasmで再帰的フィボナッチをやってみたら、かなり面白かったよ。C版の方が2倍速かったんだ。1975年には戻れないね。「高級言語の方が最適化しやすい。マシンが何を目指しているのかをよりよく理解できるから。」ってLex Fridmanが言ってたよ。