Assembler

アセンブリ言語 包括的技術ガイド

目次

  1. はじめに
  2. CPU アーキテクチャの基礎
  3. x86/x86-64 アーキテクチャ
  4. ARM/AArch64 アーキテクチャ
  5. RISC-V アーキテクチャ
  6. アセンブリ言語の基本構文
  7. データ型とメモリアドレッシングモード
  8. 算術・論理演算命令
  9. 制御フロー
  10. スタック操作と呼び出し規約
  11. システムコールと OS インターフェース
  12. SIMD 命令
  13. インラインアセンブリ
  14. アセンブラツールの設定と使い方
  15. リンカとオブジェクトファイルフォーマット
  16. デバッグ手法
  17. 最適化テクニック
  18. セキュリティ
  19. 実践例
  20. 現代の開発におけるアセンブリの役割

1. はじめに

1.1 アセンブリ言語とは

アセンブリ言語(Assembly Language)は、CPU が直接実行する機械語(マシンコード)と 1 対 1 に近い対応関係を持つ低水準プログラミング言語である。人間が読みやすいニーモニック(mnemonic)を使って機械語命令を記述し、アセンブラ(assembler)と呼ばれるツールがこれを実際の機械語バイナリに変換する。

高水準言語(C、Python、Java など)がハードウェアの詳細を抽象化するのに対し、アセンブリ言語はプロセッサのレジスタ、メモリアドレス、命令パイプラインといったハードウェアの振る舞いを直接的に制御できる。これにより、実行速度・メモリ効率・ハードウェア制御において最高レベルの最適化が可能となる。

高水準言語 (C/Python)
       ↓  コンパイラ / インタプリタ
アセンブリ言語
       ↓  アセンブラ
機械語 (バイナリ)
       ↓  CPU
実行

1.2 歴史と背景

アセンブリ言語の歴史は、コンピュータそのものの歴史と密接に結びついている。

年代出来事意義
1940年代ENIAC の直接配線プログラミングプログラムをハードウェアで構成
1949年EDSAC で最初のアセンブラ開発ニーモニックによる命令記述の誕生
1950年代IBM 704 用 SAP (Symbolic Assembly Program)マクロアセンブラの概念登場
1960年代IBM System/360 のアセンブラ統一アーキテクチャの汎用アセンブラ
1970年代C 言語の登場(1972年)高水準言語への移行が加速
1978年Intel 8086 リリースx86 アーキテクチャの始まり
1985年Intel 80386 リリース32ビット x86(IA-32)の登場
1989年NASM(Netwide Assembler)開発開始オープンソースアセンブラの普及
2003年AMD64(x86-64)リリース64ビット拡張の標準化
2010年ARM Cortex シリーズの普及モバイル・組み込み分野での ARM 拡大
2011年RISC-V プロジェクト開始オープンソース ISA の登場
2020年Apple M1 (ARM ベース) リリースデスクトップ/ラップトップでの ARM 本格化
2024年Apple M4 リリースARM ベース高性能プロセッサの成熟

1.3 なぜアセンブリ言語を学ぶ必要があるのか

現代の開発において、アセンブリ言語を日常的に書く機会は少ない。しかし、以下の理由から学習する価値は極めて高い。

1. コンピュータの動作原理の深い理解

アセンブリ言語を学ぶことで、CPU がどのように命令を実行し、メモリをどのように管理するかという根本的な仕組みを理解できる。高水準言語が「なぜそう動くのか」を理解するための基盤となる。

2. パフォーマンスの最適化

コンパイラが生成するアセンブリコードを読み解くことで、パフォーマンスボトルネックの原因を特定できる。また、SIMD 命令を使ったベクトル処理など、コンパイラが自動最適化しきれない領域での手動最適化が可能になる。

3. セキュリティの理解

バッファオーバーフロー、Return-Oriented Programming(ROP)、シェルコードなどのセキュリティ攻撃手法は、すべてアセンブリレベルの理解が前提となる。脆弱性分析やエクスプロイト開発にはアセンブリの知識が不可欠である。

4. リバースエンジニアリング

マルウェア解析、プロプライエタリソフトウェアの動作解析、レガシーシステムの保守などでは、逆アセンブルされたコードを読む能力が必要となる。

5. 組み込みシステム開発

マイクロコントローラやリアルタイムシステムの開発では、ブートローダー、割り込みハンドラ、デバイスドライバなどの低水準コードをアセンブリで記述する場面がある。

6. OS / カーネル開発

オペレーティングシステムのカーネルには、コンテキストスイッチ、システムコール遷移、割り込み処理など、必然的にアセンブリで記述する部分が存在する。

1.4 本記事の対象読者と前提知識

本記事は、C/C++ など高水準言語の基本的な知識を持ち、コンピュータアーキテクチャやアセンブリ言語をより深く理解したいソフトウェアエンジニアを対象としている。以下の知識があることを前提とする。

  • C 言語の基本構文(ポインタ、配列、構造体、関数呼び出し)
  • 二進数・十六進数の基礎
  • コマンドラインツールの基本操作(Linux/macOS)
  • コンパイルとリンクの基本概念

2. CPU アーキテクチャの基礎

2.1 CPU の基本構成

CPU(Central Processing Unit)は以下の主要コンポーネントから構成される。

┌─────────────────────────────────────────────────────────────┐
│                          CPU                                 │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────┐  │
│  │  制御ユニット  │  │   ALU        │  │   レジスタファイル  │  │
│  │  (Control     │  │  (Arithmetic │  │  (Register File)  │  │
│  │   Unit)       │  │   Logic      │  │                   │  │
│  │              │  │   Unit)      │  │  汎用レジスタ       │  │
│  │  命令デコーダ  │  │              │  │  特殊レジスタ       │  │
│  │  命令ポインタ  │  │  整数演算     │  │  フラグレジスタ     │  │
│  │  マイクロコード │  │  浮動小数点   │  │                   │  │
│  └──────────────┘  │  SIMD         │  └──────────────────┘  │
│                    └──────────────┘                          │
│  ┌──────────────────────────────────────────────────────┐   │
│  │                 キャッシュ階層                          │   │
│  │  L1 命令キャッシュ | L1 データキャッシュ | L2 キャッシュ   │   │
│  └──────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘
                          ↕ バス
┌─────────────────────────────────────────────────────────────┐
│                     メインメモリ (RAM)                        │
└─────────────────────────────────────────────────────────────┘

2.2 レジスタ

レジスタは CPU 内部にある最も高速な記憶装置である。メインメモリ(RAM)へのアクセスが数十~数百サイクルかかるのに対し、レジスタへのアクセスは 1 サイクルで完了する。

レジスタの種類

種類説明
汎用レジスタ (GPR)演算やデータ保持に使用RAX, RBX (x86-64), X0-X30 (AArch64)
プログラムカウンタ (PC)次に実行する命令のアドレスRIP (x86-64), PC (ARM)
スタックポインタ (SP)現在のスタックトップのアドレスRSP (x86-64), SP (ARM)
フラグ/状態レジスタ演算結果の条件フラグRFLAGS (x86-64), NZCV (ARM)
セグメントレジスタメモリセグメンテーションCS, DS, SS (x86-64)
浮動小数点レジスタ浮動小数点演算用XMM0-15, YMM0-15 (x86-64)
ベクトルレジスタSIMD 演算用ZMM0-31 (AVX-512), V0-V31 (ARM NEON)

2.3 メモリモデル

フォン・ノイマン型 vs ハーバード型

フォン・ノイマン型:                    ハーバード型:
┌─────┐    ┌──────────┐           ┌─────┐    ┌──────────────┐
│ CPU │◄──►│命令+データ │           │ CPU │◄──►│ 命令メモリ    │
└─────┘    │  メモリ    │           │     │◄──►│ データメモリ   │
           └──────────┘           └─────┘    └──────────────┘

現代の CPU は修正ハーバード型アーキテクチャを採用している。L1 キャッシュは命令用(I-cache)とデータ用(D-cache)に分離しているが、メインメモリは統一されている。

メモリ階層

レジスタ     : ~1 サイクル,   KB 規模
L1 キャッシュ : ~4 サイクル,   32-64 KB
L2 キャッシュ : ~12 サイクル,  256 KB - 1 MB
L3 キャッシュ : ~40 サイクル,  4-32 MB
メインメモリ  : ~100-300 サイクル, GB 規模
SSD          : ~10,000 サイクル,  TB 規模
HDD          : ~10,000,000 サイクル, TB 規模

エンディアン

データのバイト順序はアーキテクチャによって異なる。

値 0x12345678 のメモリ上の配置:

リトルエンディアン (x86, ARM default):
アドレス: 0x00  0x01  0x02  0x03
値:       0x78  0x56  0x34  0x12   ← 下位バイトが低いアドレス

ビッグエンディアン (ネットワークバイトオーダー):
アドレス: 0x00  0x01  0x02  0x03
値:       0x12  0x34  0x56  0x78   ← 上位バイトが低いアドレス

x86/x86-64 はリトルエンディアン固定。ARM は設定可能だが、通常リトルエンディアンで使用される(AArch64 はデフォルトでリトルエンディアン)。

2.4 命令セットアーキテクチャ (ISA)

ISA は CPU とソフトウェアの間のインターフェースを定義する。

CISC vs RISC

特性CISC (x86)RISC (ARM, RISC-V)
命令長可変長(1-15バイト)固定長(4バイト)
命令の複雑さ複雑な命令が多い単純な命令に限定
メモリアクセス演算命令でメモリ直接参照可能Load/Store アーキテクチャ
レジスタ数比較的少ない(16個)多い(31-32個)
デコード複雑なデコーダが必要シンプルなデコーダ
パイプライン複雑シンプルで効率的
消費電力高い傾向低い傾向

命令の基本形式

; x86-64 (CISC): 操作コード + オペランド(可変長)
mov  rax, [rbx + rcx*8 + 16]   ; メモリから直接ロード
add  [rsp + 8], 42              ; メモリ上の値に直接加算

; ARM64 (RISC): 固定32ビット命令
ldr  x0, [x1, x2, lsl #3]     ; まずメモリからロード
add  x0, x0, #42               ; レジスタ上で加算
str  x0, [x1, x2, lsl #3]     ; メモリへストア

2.5 命令実行のパイプライン

現代の CPU は命令をパイプライン処理して並列実行する。

基本的な5段パイプライン:

時間 →  T1    T2    T3    T4    T5    T6    T7
命令1:  IF    ID    EX    MEM   WB
命令2:        IF    ID    EX    MEM   WB
命令3:              IF    ID    EX    MEM   WB

IF  = Instruction Fetch(命令フェッチ)
ID  = Instruction Decode(命令デコード)
EX  = Execute(実行)
MEM = Memory Access(メモリアクセス)
WB  = Write Back(書き戻し)

現代の高性能 CPU(Intel Core、Apple M4 など)はさらに高度なパイプラインを持つ。

  • スーパースカラ: 複数の命令を同時にフェッチ・デコード・実行
  • アウトオブオーダー実行: データ依存がない命令を順序を入れ替えて実行
  • 投機的実行: 分岐結果を予測して先に実行を開始
  • レジスタリネーミング: WAR/WAW ハザードを解消

3. x86/x86-64 アーキテクチャ

3.1 x86 アーキテクチャの歴史的経緯

x86 アーキテクチャは Intel 8086(1978年)に始まり、40年以上にわたる後方互換性を維持しながら拡張されてきた。

8086 (16bit, 1978)
  → 80286 (保護モード, 1982)
    → 80386 (32bit, 1985)
      → 80486 (統合FPU, 1989)
        → Pentium (スーパースカラ, 1993)
          → Pentium Pro (アウトオブオーダー, 1995)
            → AMD64/x86-64 (64bit, 2003)
              → 現代の Intel Core / AMD Ryzen

3.2 x86-64 汎用レジスタ

x86-64 は 16 個の 64 ビット汎用レジスタを持つ。各レジスタは 8/16/32/64 ビット幅でアクセスできる。

64-bit    32-bit    16-bit    8-bit(H)   8-bit(L)
┌─────────────────────────────────────────────────┐
│ RAX     │ EAX     │ AX      │ AH       │ AL    │  アキュムレータ
│ RBX     │ EBX     │ BX      │ BH       │ BL    │  ベースレジスタ
│ RCX     │ ECX     │ CX      │ CH       │ CL    │  カウンタ
│ RDX     │ EDX     │ DX      │ DH       │ DL    │  データレジスタ
│ RSI     │ ESI     │ SI      │ ─        │ SIL   │  ソースインデックス
│ RDI     │ EDI     │ DI      │ ─        │ DIL   │  デスティネーションインデックス
│ RBP     │ EBP     │ BP      │ ─        │ BPL   │  ベースポインタ
│ RSP     │ ESP     │ SP      │ ─        │ SPL   │  スタックポインタ
│ R8      │ R8D     │ R8W     │ ─        │ R8B   │  汎用 (x86-64 で追加)
│ R9      │ R9D     │ R9W     │ ─        │ R9B   │
│ R10     │ R10D    │ R10W    │ ─        │ R10B  │
│ R11     │ R11D    │ R11W    │ ─        │ R11B  │
│ R12     │ R12D    │ R12W    │ ─        │ R12B  │
│ R13     │ R13D    │ R13W    │ ─        │ R13B  │
│ R14     │ R14D    │ R14W    │ ─        │ R14B  │
│ R15     │ R15D    │ R15W    │ ─        │ R15B  │
└─────────────────────────────────────────────────┘

重要な注意点: 32 ビットレジスタ(EAX など)に書き込むと、上位 32 ビットは自動的にゼロクリアされる。一方、8/16 ビットレジスタへの書き込みでは上位ビットは変更されない。

; x86-64 NASM
mov  eax, 0xFFFFFFFF   ; RAX = 0x00000000FFFFFFFF (上位32bitがゼロクリア)
mov  ax,  0x1234        ; RAX = 0x00000000FFFF1234 (上位48bitは変更されない)
mov  al,  0x56          ; RAX = 0x00000000FFFF1256 (上位56bitは変更されない)

3.3 特殊レジスタ

命令ポインタ (RIP)

; RIP は直接読み書きできないが、RIP 相対アドレッシングで使用可能
lea  rax, [rip + offset]    ; RIP 相対アドレッシング(x86-64 の標準)
; PIC(Position-Independent Code)の基本

フラグレジスタ (RFLAGS)

RFLAGS レジスタのビット構成:

ビット  名前   説明
─────────────────────────────────────────
  0     CF    キャリーフラグ(符号なし演算のオーバーフロー)
  2     PF    パリティフラグ(結果の下位8ビットの1の数が偶数)
  4     AF    補助キャリーフラグ(BCD演算用)
  6     ZF    ゼロフラグ(結果がゼロ)
  7     SF    サインフラグ(結果が負)
  8     TF    トラップフラグ(シングルステップデバッグ)
  9     IF    割り込み許可フラグ
 10     DF    方向フラグ(文字列操作の方向)
 11     OF    オーバーフローフラグ(符号付き演算のオーバーフロー)
12-13   IOPL  I/O特権レベル
 14     NT    ネストタスクフラグ
 21     ID    CPUID命令サポートフラグ
; フラグの使用例
section .text
global _start

_start:
    mov  rax, 0x7FFFFFFFFFFFFFFF  ; 符号付き最大値
    add  rax, 1                    ; オーバーフロー発生
    ; OF=1 (符号付きオーバーフロー), SF=1 (結果が負), CF=0

    mov  rbx, 0xFFFFFFFFFFFFFFFF  ; -1 (符号付き) / 最大値 (符号なし)
    add  rbx, 1                    ; CF=1 (符号なしオーバーフロー), ZF=1, OF=0

    ; 条件分岐でフラグを使用
    cmp  rax, rbx
    je   equal        ; ZF=1 なら分岐
    jg   greater      ; SF=OF かつ ZF=0 なら分岐
    jl   less         ; SF≠OF なら分岐

セグメントレジスタ

CS  - コードセグメント(現在実行中のコードのセグメント)
DS  - データセグメント(デフォルトのデータアクセス)
SS  - スタックセグメント(スタック操作)
ES  - エクストラセグメント(文字列操作の送り先)
FS  - 追加セグメント(Linux: スレッドローカルストレージ)
GS  - 追加セグメント(macOS: スレッドローカルストレージ、Linux カーネル: per-CPU データ)

x86-64 のロングモードでは、セグメンテーションは事実上フラットモデル(ベース=0)として動作する。ただし、FS と GS はスレッドローカルストレージ(TLS)のために使用される。

3.4 浮動小数点・SIMD レジスタ

x87 FPU スタック:   ST(0) - ST(7)     80bit 拡張精度
SSE:               XMM0 - XMM15      128bit
AVX:               YMM0 - YMM15      256bit (XMM の拡張)
AVX-512:           ZMM0 - ZMM31      512bit (YMM の拡張)
                   k0 - k7           マスクレジスタ(64bit)

レジスタの関係:
┌────────────────────────────────────────────────────────────────┐
│                          ZMM0 (512-bit)                        │
│                    ┌───────────────────────────────────────────┤
│                    │           YMM0 (256-bit)                  │
│                    │          ┌────────────────────────────────┤
│                    │          │         XMM0 (128-bit)         │
└────────────────────┴──────────┴────────────────────────────────┘

3.5 メモリモデルとページング

x86-64 では 4 レベルのページテーブル(PML4)を使用し、48 ビット仮想アドレス空間(256 TB)をサポートする。

x86-64 仮想アドレスの構造 (4KB ページ):

63      48 47    39 38    30 29    21 20    12 11       0
┌─────────┬────────┬────────┬────────┬────────┬──────────┐
│ Sign Ext│ PML4   │ PDPT   │ PD     │ PT     │ Offset   │
│ (16bit) │ (9bit) │ (9bit) │ (9bit) │ (9bit) │ (12bit)  │
└─────────┴────────┴────────┴────────┴────────┴──────────┘

5レベルページング(LA57、Intel Ice Lake 以降)では
57ビット仮想アドレス空間(128 PB)をサポート

3.6 動作モード

x86-64 プロセッサは複数の動作モードを持つ。

モードビット幅説明
Real Mode16bit電源投入時のモード。1MB メモリ空間
Protected Mode16/32bitセグメンテーション、ページング、特権レベル
Long Mode (64-bit Mode)64bitx86-64 の完全な 64 ビットモード
Compatibility Mode32bitLong Mode 内で 32 ビットコードを実行
System Management Mode-ファームウェア用の特殊モード

4. ARM/AArch64 アーキテクチャ

4.1 ARM アーキテクチャの概要

ARM(Advanced RISC Machine)は、ARM Holdings(現 Arm Ltd.)が設計する RISC ベースの命令セットアーキテクチャ(ISA)である。自社でプロセッサを製造するのではなく、IP ライセンスモデルにより Apple、Qualcomm、Samsung 等の半導体メーカーに設計をライセンスしている。

ARM アーキテクチャは、ARMv7(32ビット、AArch32)と ARMv8 以降(64ビット、AArch64)に大別される。本記事では主に AArch64 を扱う。

4.2 AArch64 レジスタセット

AArch64 は 31 個の 64 ビット汎用レジスタを持つ(x86-64 の 16 個と比較して約 2 倍)。

汎用レジスタ:
┌──────────────────────────────────────────────┐
│ 64-bit    │ 32-bit    │ 用途                  │
├───────────┼───────────┼───────────────────────┤
│ X0 - X7   │ W0 - W7   │ 引数 / 戻り値         │
│ X8        │ W8        │ 間接結果レジスタ        │
│ X9 - X15  │ W9 - W15  │ 一時レジスタ(caller-saved)│
│ X16 (IP0) │ W16       │ イントラプロシージャコール │
│ X17 (IP1) │ W17       │ イントラプロシージャコール │
│ X18 (PR)  │ W18       │ プラットフォーム予約     │
│ X19 - X28 │ W19 - W28 │ callee-saved レジスタ  │
│ X29 (FP)  │ W29       │ フレームポインタ        │
│ X30 (LR)  │ W30       │ リンクレジスタ          │
├───────────┼───────────┼───────────────────────┤
│ SP        │ WSP       │ スタックポインタ        │
│ PC        │ ─         │ プログラムカウンタ      │
│ XZR       │ WZR       │ ゼロレジスタ(常に0)    │
└──────────────────────────────────────────────┘

重要な相違点(x86-64 との比較):

  • ゼロレジスタ(XZR/WZR): 常にゼロを返す特殊レジスタ。ゼロ初期化やカウント比較で効率的
  • リンクレジスタ(X30/LR): BL 命令で戻りアドレスが自動的に格納される(x86 はスタックに push)
  • X18 はプラットフォーム予約。macOS/iOS ではシステムが使用するため、ユーザーコードでは使用禁止

4.3 条件フラグとシステムレジスタ

NZCV レジスタ (条件フラグ):
  N - Negative(結果が負)
  Z - Zero(結果がゼロ)
  C - Carry(キャリー/ボロー)
  V - Overflow(符号付きオーバーフロー)

FPCR - 浮動小数点制御レジスタ
FPSR - 浮動小数点ステータスレジスタ

4.4 AArch64 命令の特徴

ARM64 の命令はすべて固定 32 ビット長であり、命令デコーダが大幅に簡素化される。

// AArch64 アセンブリの基本命令
// レジスタ間の移動
mov  x0, x1              // x0 = x1
mov  x0, #42             // x0 = 42 (即値)
movz x0, #0x1234         // x0 = 0x1234
movk x0, #0x5678, lsl #16  // x0 の 16-31 ビットに 0x5678 をセット

// 算術演算
add  x0, x1, x2          // x0 = x1 + x2
add  x0, x1, #100        // x0 = x1 + 100
adds x0, x1, x2          // x0 = x1 + x2 (フラグ更新)
sub  x0, x1, x2          // x0 = x1 - x2
mul  x0, x1, x2          // x0 = x1 * x2
madd x0, x1, x2, x3      // x0 = x1 * x2 + x3 (積和演算)

// シフトとビット操作
lsl  x0, x1, #4          // x0 = x1 << 4 (論理左シフト)
lsr  x0, x1, #4          // x0 = x1 >> 4 (論理右シフト)
asr  x0, x1, #4          // x0 = x1 >> 4 (算術右シフト)
and  x0, x1, x2          // x0 = x1 & x2
orr  x0, x1, x2          // x0 = x1 | x2
eor  x0, x1, x2          // x0 = x1 ^ x2

// メモリアクセス (Load/Store)
ldr  x0, [x1]            // x0 = *x1 (64bit ロード)
ldr  w0, [x1]            // w0 = *(uint32_t*)x1 (32bit ロード)
ldrb w0, [x1]            // w0 = *(uint8_t*)x1 (バイトロード)
str  x0, [x1]            // *x1 = x0 (64bit ストア)
ldp  x0, x1, [x2]        // ペアロード: x0=*x2, x1=*(x2+8)
stp  x0, x1, [sp, #-16]! // プリデクリメント付きペアストア

4.5 条件実行と条件選択

AArch64 では ARM32 の条件付き実行(条件プレフィックス)は廃止され、代わりに条件選択命令が導入された。

// 条件分岐
cmp   x0, x1
b.eq  label_equal     // x0 == x1 なら分岐
b.ne  label_not_equal // x0 != x1 なら分岐
b.lt  label_less      // x0 < x1 (符号付き)
b.gt  label_greater   // x0 > x1 (符号付き)
b.lo  label_lower     // x0 < x1 (符号なし)
b.hi  label_higher    // x0 > x1 (符号なし)

// 条件選択命令 (分岐を回避してパイプライン効率を向上)
cmp   x0, x1
csel  x2, x3, x4, eq  // x2 = (x0 == x1) ? x3 : x4
csinc x2, x3, x4, lt  // x2 = (x0 < x1) ? x3 : x4 + 1
cset  x2, eq          // x2 = (x0 == x1) ? 1 : 0

// abs(x0) の実装例
cmp   x0, #0
csneg x0, x0, x0, ge  // x0 = (x0 >= 0) ? x0 : -x0

4.6 Apple Silicon (M1/M2/M3/M4) の特徴

Apple Silicon は ARMv8.5-A 以降をベースとした独自設計の SoC である。

Apple M4 の主な仕様:
─────────────────────────────────
ISA:           ARMv9.2-A
高性能コア:     4x (P-core)
高効率コア:     6x (E-core)
L1 I-cache:    192 KB (P-core), 128 KB (E-core)
L1 D-cache:    128 KB (P-core), 64 KB (E-core)
L2 キャッシュ:   16 MB (P-core), 4 MB (E-core)
SLC:           16 MB (System Level Cache)
SIMD:          128-bit NEON + SME (Scalable Matrix Extension)
メモリ:         統合メモリアーキテクチャ (LPDDR5X)

Apple Silicon 固有の機能:

// ポインタ認証 (PAC - Pointer Authentication Code)
// ARMv8.3-A で導入、Apple が積極的に活用
paciasp             // SP をキーにして LR に署名
autiasp             // LR の署名を検証
retab               // 認証付きリターン

// メモリタグ拡張 (MTE - Memory Tagging Extension)
// ARMv8.5-A で導入
irg   x0, sp        // ランダムタグを生成
stg   x0, [x0]     // メモリにタグを設定
ldg   x0, [x1]     // メモリからタグを読み取り

// Apple 固有の AMX (Apple Matrix Extensions) - 非公開命令セット
// 機械学習の行列演算を高速化(Neural Engine と連携)
// 公式ドキュメントは未公開だが、Accelerate フレームワーク経由で利用可能

4.7 macOS/iOS での AArch64 アセンブリ

macOS (Apple Silicon) でのアセンブリプログラミングには、いくつかの注意点がある。

// macOS AArch64 の Hello World
// ファイル: hello_mac.s
.global _main
.align 4

_main:
    // macOS のシステムコール番号は Unix 番号 + 0x2000000
    // write(1, message, 13)
    mov  x0, #1              // fd = stdout
    adrp x1, message@PAGE
    add  x1, x1, message@PAGEOFF  // message のアドレス
    mov  x2, #13             // length
    mov  x16, #4             // syscall: write (macOS: 0x2000004)
    svc  #0x80               // システムコール呼び出し

    // exit(0)
    mov  x0, #0              // exit code
    mov  x16, #1             // syscall: exit (macOS: 0x2000001)
    svc  #0x80

.data
message:
    .ascii "Hello, ARM64\n"
# macOS でのビルドと実行
as -o hello_mac.o hello_mac.s
ld -o hello_mac hello_mac.o -lSystem -syslibroot $(xcrun --show-sdk-path) -e _main -arch arm64
./hello_mac

5. RISC-V アーキテクチャ

5.1 RISC-V の概要

RISC-V(リスクファイブ)は、カリフォルニア大学バークレー校で 2010 年に開発が始まったオープンソースの ISA(命令セットアーキテクチャ)である。BSD ライセンスで公開されており、誰でも自由に RISC-V プロセッサを設計・製造・販売できる。

ARM が IP ライセンス料を必要とするのに対し、RISC-V はライセンスフリーであるため、特にカスタム SoC、学術研究、IoT デバイスの分野で急速に普及している。

5.2 RISC-V のモジュラー ISA

RISC-V の最大の特徴は、モジュラー(モジュール式)な ISA 設計である。基本整数命令セット(I)に対して、必要な拡張を選択的に追加できる。

基本命令セット:
  RV32I  - 32ビット基本整数命令(47命令)
  RV64I  - 64ビット基本整数命令
  RV128I - 128ビット基本整数命令(仕様策定中)

標準拡張:
  M - 整数乗除算 (Multiply/Divide)
  A - アトミック操作 (Atomic)
  F - 単精度浮動小数点 (Single-precision Float)
  D - 倍精度浮動小数点 (Double-precision Float)
  Q - 四倍精度浮動小数点 (Quad-precision Float)
  C - 圧縮命令 (Compressed: 16ビット命令)
  V - ベクトル拡張 (Vector)
  B - ビット操作 (Bit manipulation)

よく使われる組み合わせ:
  RV32IMAC   - 組み込み向け(32ビット、乗除算、アトミック、圧縮命令)
  RV64GC     - 汎用(G = IMAFD, C = 圧縮命令)
  RV64IMAFDC - RV64GC と同じ(Linux 向け標準)

5.3 RISC-V レジスタセット

RV64I 汎用レジスタ (32個 × 64ビット):
┌─────────┬──────────┬──────────────────────────────┐
│ レジスタ │ ABI 名    │ 用途                          │
├─────────┼──────────┼──────────────────────────────┤
│ x0      │ zero     │ ハードワイヤードゼロ(常に0)    │
│ x1      │ ra       │ リターンアドレス               │
│ x2      │ sp       │ スタックポインタ               │
│ x3      │ gp       │ グローバルポインタ              │
│ x4      │ tp       │ スレッドポインタ               │
│ x5-x7   │ t0-t2    │ 一時レジスタ                   │
│ x8      │ s0/fp    │ saved レジスタ / フレームポインタ │
│ x9      │ s1       │ saved レジスタ                 │
│ x10-x11 │ a0-a1    │ 関数引数 / 戻り値              │
│ x12-x17 │ a2-a7    │ 関数引数                      │
│ x18-x27 │ s2-s11   │ saved レジスタ                 │
│ x28-x31 │ t3-t6    │ 一時レジスタ                   │
├─────────┼──────────┼──────────────────────────────┤
│ pc      │ ─        │ プログラムカウンタ              │
└─────────┴──────────┴──────────────────────────────┘

5.4 RISC-V 命令フォーマット

RISC-V の命令フォーマットは 6 種類に分類される。すべて 32 ビット固定長(C 拡張使用時は 16 ビットも可)。

R-Type (レジスタ間演算):
 31       25 24   20 19   15 14  12 11    7 6      0
┌──────────┬───────┬───────┬──────┬───────┬────────┐
│  funct7  │  rs2  │  rs1  │funct3│  rd   │ opcode │
└──────────┴───────┴───────┴──────┴───────┴────────┘

I-Type (即値演算・ロード):
 31                 20 19   15 14  12 11    7 6      0
┌─────────────────────┬───────┬──────┬───────┬────────┐
│     imm[11:0]       │  rs1  │funct3│  rd   │ opcode │
└─────────────────────┴───────┴──────┴───────┴────────┘

S-Type (ストア):
 31       25 24   20 19   15 14  12 11    7 6      0
┌──────────┬───────┬───────┬──────┬───────┬────────┐
│ imm[11:5]│  rs2  │  rs1  │funct3│imm[4:0]│ opcode │
└──────────┴───────┴───────┴──────┴───────┴────────┘

B-Type (条件分岐):
 31   30     25 24   20 19   15 14  12 11   8  7  6      0
┌───┬─────────┬───────┬───────┬──────┬──────┬───┬────────┐
│[12]│imm[10:5]│  rs2  │  rs1  │funct3│[4:1] │[11]│opcode │
└───┴─────────┴───────┴───────┴──────┴──────┴───┴────────┘

5.5 RISC-V アセンブリの基本

# RISC-V アセンブリ (RV64I)
# GAS (GNU Assembler) 構文

# レジスタ操作
li    a0, 42           # a0 = 42 (疑似命令: addi a0, zero, 42)
mv    a1, a0           # a1 = a0 (疑似命令: addi a1, a0, 0)
add   a0, a1, a2       # a0 = a1 + a2
addi  a0, a1, 10       # a0 = a1 + 10
sub   a0, a1, a2       # a0 = a1 - a2
mul   a0, a1, a2       # a0 = a1 * a2 (M 拡張)
div   a0, a1, a2       # a0 = a1 / a2 (M 拡張)

# 論理演算
and   a0, a1, a2       # a0 = a1 & a2
or    a0, a1, a2       # a0 = a1 | a2
xor   a0, a1, a2       # a0 = a1 ^ a2
sll   a0, a1, a2       # a0 = a1 << a2 (論理左シフト)
srl   a0, a1, a2       # a0 = a1 >> a2 (論理右シフト)
sra   a0, a1, a2       # a0 = a1 >> a2 (算術右シフト)

# メモリアクセス
ld    a0, 0(sp)        # a0 = *(int64_t*)(sp + 0)
lw    a0, 4(sp)        # a0 = *(int32_t*)(sp + 4)
lh    a0, 8(sp)        # a0 = *(int16_t*)(sp + 8)
lb    a0, 12(sp)       # a0 = *(int8_t*)(sp + 12)
sd    a0, 0(sp)        # *(int64_t*)(sp + 0) = a0
sw    a0, 4(sp)        # *(int32_t*)(sp + 4) = a0

# 分岐
beq   a0, a1, label    # a0 == a1 なら分岐
bne   a0, a1, label    # a0 != a1 なら分岐
blt   a0, a1, label    # a0 < a1 (符号付き) なら分岐
bge   a0, a1, label    # a0 >= a1 (符号付き) なら分岐
bltu  a0, a1, label    # a0 < a1 (符号なし) なら分岐

# 関数呼び出し
jal   ra, function     # function を呼び出し (ra に戻りアドレスを保存)
jalr  ra, 0(a0)        # a0 が指すアドレスを呼び出し
ret                    # 疑似命令: jalr zero, 0(ra)

5.6 RISC-V Hello World (Linux)

# hello_riscv.s - RISC-V 64-bit Linux
.global _start

.section .text
_start:
    # write(1, message, 14)
    li    a7, 64          # syscall: write (RISC-V Linux = 64)
    li    a0, 1           # fd = stdout
    la    a1, message     # buffer address
    li    a2, 14          # length
    ecall                 # システムコール呼び出し

    # exit(0)
    li    a7, 93          # syscall: exit (RISC-V Linux = 93)
    li    a0, 0           # exit code
    ecall

.section .rodata
message:
    .ascii "Hello, RISC-V\n"
# クロスコンパイルとエミュレーション
riscv64-linux-gnu-as -o hello_riscv.o hello_riscv.s
riscv64-linux-gnu-ld -o hello_riscv hello_riscv.o
qemu-riscv64 ./hello_riscv

5.7 三大アーキテクチャの比較

特性x86-64AArch64RISC-V (RV64GC)
設計思想CISCRISCRISC
命令長1-15 バイト4 バイト固定4 バイト (C拡張: 2バイト)
汎用レジスタ数163131
ゼロレジスタなしXZR/WZRx0 (zero)
条件実行フラグベース分岐条件選択命令比較分岐命令
ライセンスIntel/AMD 独占Arm のライセンスオープンソース (BSD)
主な用途デスクトップ、サーバーモバイル、サーバー、Mac組み込み、学術、新興
エンディアンリトルエンディアンバイエンディアン(通常LE)リトルエンディアン
メモリモデル強順序 (TSO)弱順序弱順序 (RVWMO)

6. アセンブリ言語の基本構文

6.1 二大構文体系: Intel 構文と AT&T 構文

x86/x86-64 のアセンブリ言語には、2 つの主要な構文体系が存在する。

特性Intel 構文 (NASM)AT&T 構文 (GAS)
オペランド順序dest, srcsrc, dest
レジスタ接頭辞なし%
即値接頭辞なし$
メモリ参照[base + index*scale + disp]disp(%base, %index, scale)
サイズ指定BYTE/WORD/DWORD/QWORDb/w/l/q サフィックス
主なアセンブラNASM, YASM, MASMGAS (GNU Assembler)
主な使用場面Windows, 独立プログラムLinux, GCC 出力, macOS
; === Intel 構文 (NASM) ===
section .text
global _start

_start:
    mov    rax, 1                  ; RAX に 1 を代入
    mov    rdi, 1                  ; 第1引数: stdout
    lea    rsi, [rel message]      ; 第2引数: 文字列アドレス
    mov    rdx, 13                 ; 第3引数: 長さ
    syscall                        ; write(1, message, 13)

    mov    rax, 60                 ; exit syscall
    xor    rdi, rdi                ; exit code 0
    syscall

section .rodata
message:
    db "Hello, World!", 10         ; 10 = '\n'
# === AT&T 構文 (GAS) ===
.section .text
.global _start

_start:
    movq   $1, %rax               # RAX に 1 を代入
    movq   $1, %rdi               # 第1引数: stdout
    leaq   message(%rip), %rsi    # 第2引数: 文字列アドレス (RIP相対)
    movq   $13, %rdx              # 第3引数: 長さ
    syscall                        # write(1, message, 13)

    movq   $60, %rax              # exit syscall
    xorq   %rdi, %rdi             # exit code 0
    syscall

.section .rodata
message:
    .ascii "Hello, World!\n"

6.2 NASM の基本構文

NASM(Netwide Assembler)は最も広く使われるオープンソースの x86/x86-64 アセンブラである。

; NASM の基本構造
; ==============================

; 1. セクション定義
section .data           ; 初期化済みデータ
    myvar    dd  42           ; 32ビット整数
    mystr    db  "Hello", 0   ; NULL 終端文字列
    myarr    times 100 db 0   ; 100バイトのゼロ配列
    float1   dd  3.14         ; 単精度浮動小数点
    double1  dq  2.71828      ; 倍精度浮動小数点

section .bss            ; 未初期化データ
    buffer   resb  1024       ; 1024バイト予約
    counter  resd  1          ; 32ビット整数予約
    bigbuf   resq  256        ; 256個の64ビット値予約

section .rodata         ; 読み取り専用データ
    fmtstr   db  "Value: %d", 10, 0

section .text           ; コード
    global main
    extern printf

; 2. データ定義疑似命令
;    db  - Define Byte (1バイト)
;    dw  - Define Word (2バイト)
;    dd  - Define Doubleword (4バイト)
;    dq  - Define Quadword (8バイト)
;    dt  - Define Ten-byte (10バイト, x87 拡張精度)

; 3. サイズ指定
;    BYTE   - 1バイト
;    WORD   - 2バイト
;    DWORD  - 4バイト
;    QWORD  - 8バイト

; 4. マクロ定義
%macro  pushall 0
    push rax
    push rbx
    push rcx
    push rdx
%endmacro

%macro  popall 0
    pop rdx
    pop rcx
    pop rbx
    pop rax
%endmacro

; 5. 条件付きアセンブル
%ifdef DEBUG
    ; デバッグ用コード
%endif

%if __BITS__ == 64
    ; 64ビット用コード
%else
    ; 32ビット用コード
%endif

; 6. 定数定義
%define BUFFER_SIZE 4096
%define SYS_WRITE   1
%define SYS_EXIT    60
%define STDOUT      1

main:
    push   rbp
    mov    rbp, rsp

    ; printf の呼び出し
    lea    rdi, [rel fmtstr]
    mov    esi, [rel myvar]
    xor    eax, eax           ; 浮動小数点引数の数 = 0
    call   printf wrt ..plt

    xor    eax, eax           ; return 0
    leave
    ret

6.3 GAS (GNU Assembler) の基本構文

# GAS の基本構造 (AT&T 構文)
# ==============================

# 1. ディレクティブ
.section .data
myvar:      .long   42           # 32ビット整数
mystr:      .asciz  "Hello"      # NULL 終端文字列
myarr:      .fill   100, 1, 0    # 100バイトのゼロ
float1:     .float  3.14         # 単精度浮動小数点
double1:    .double 2.71828      # 倍精度浮動小数点
mywords:    .word   1, 2, 3, 4   # 16ビット整数の配列

.section .bss
.lcomm buffer, 1024              # 1024バイト予約
.lcomm counter, 4                # 4バイト予約

.section .rodata
fmtstr:     .string "Value: %d\n"

.section .text
.global main
.type main, @function

# 2. アラインメント
.align 16                        # 16バイト境界にアラインメント
.p2align 4                       # 2^4 = 16バイト境界

# 3. マクロ定義
.macro push_callee_saved
    pushq %rbx
    pushq %r12
    pushq %r13
    pushq %r14
    pushq %r15
.endm

.macro pop_callee_saved
    popq %r15
    popq %r14
    popq %r13
    popq %r12
    popq %rbx
.endm

# 4. 条件付きアセンブル
.ifdef DEBUG
    # デバッグ用コード
.endif

# 5. 定数定義
.equ BUFFER_SIZE, 4096
.equ SYS_WRITE,   1
.equ SYS_EXIT,    60
.equ STDOUT,      1

main:
    pushq  %rbp
    movq   %rsp, %rbp

    # printf の呼び出し
    leaq   fmtstr(%rip), %rdi
    movl   myvar(%rip), %esi
    xorl   %eax, %eax
    call   printf@PLT

    xorl   %eax, %eax
    leave
    ret

.size main, .-main

6.4 GAS の Intel 構文モード

GAS は .intel_syntax ディレクティブで Intel 構文モードに切り替えることもできる。

# GAS で Intel 構文を使用
.intel_syntax noprefix

.section .text
.global main

main:
    push   rbp
    mov    rbp, rsp
    
    mov    eax, 42
    add    eax, 8
    
    leave
    ret

.att_syntax                    # AT&T 構文に戻す

6.5 ARM64 (AArch64) アセンブリ構文

ARM64 は統一アセンブリ言語(UAL)構文を使用する。

// AArch64 アセンブリの基本構造
// GAS 形式 (clang/LLVM-MC も同様)

.global _main
.align 4

// データセクション
.section __DATA,__data
myvar:      .word   42
mystr:      .asciz  "Hello, ARM64"

// テキストセクション
.section __TEXT,__text

_main:
    // スタックフレームの設定
    stp     x29, x30, [sp, #-16]!   // FP, LR を保存
    mov     x29, sp                   // フレームポインタ設定

    // 処理
    adrp    x0, myvar@PAGE
    add     x0, x0, myvar@PAGEOFF
    ldr     w1, [x0]                  // myvar の値をロード
    add     w1, w1, #10               // 10 を加算

    // スタックフレームの復帰
    ldp     x29, x30, [sp], #16      // FP, LR を復帰
    ret                                // リターン

7. データ型とメモリアドレッシングモード

7.1 データ型

アセンブリ言語にはC言語のような型システムは存在しないが、データのサイズを明示的に指定する必要がある。

データサイズの対応表:
┌───────────────┬──────────┬───────────┬───────────┬──────────┐
│ サイズ         │ x86 NASM │ x86 GAS   │ AArch64   │ C/C++    │
├───────────────┼──────────┼───────────┼───────────┼──────────┤
│ 1 byte (8b)   │ BYTE/db  │ .byte/b   │ B suffix  │ char     │
│ 2 bytes (16b) │ WORD/dw  │ .word/w   │ H suffix  │ short    │
│ 4 bytes (32b) │ DWORD/dd │ .long/l   │ W reg     │ int      │
│ 8 bytes (64b) │ QWORD/dq │ .quad/q   │ X reg     │ long     │
│ 16 bytes      │ OWORD    │ .octa     │ Q reg     │ __int128 │
│ 4 bytes (fp)  │ dd       │ .float    │ S reg     │ float    │
│ 8 bytes (fp)  │ dq       │ .double   │ D reg     │ double   │
│ 10 bytes (fp) │ dt       │ ─         │ ─         │ long dbl │
└───────────────┴──────────┴───────────┴───────────┴──────────┘

7.2 x86-64 アドレッシングモード

x86-64 は非常に柔軟なアドレッシングモードを提供する。

; ============ x86-64 アドレッシングモード ============

; 1. 即値 (Immediate)
mov    rax, 42                ; rax = 42
mov    rax, 0xFF              ; rax = 255
mov    rax, 'A'               ; rax = 65

; 2. レジスタ直接 (Register Direct)
mov    rax, rbx               ; rax = rbx

; 3. メモリ直接 (Direct/Absolute)
mov    rax, [0x400000]        ; rax = *(uint64_t*)0x400000
; ※ x86-64 では通常 RIP 相対を使用

; 4. RIP 相対 (RIP-Relative) — x86-64 の標準
mov    rax, [rel myvar]       ; rax = *(myvar) (NASM)
; GAS: movq myvar(%rip), %rax

; 5. レジスタ間接 (Register Indirect)
mov    rax, [rbx]             ; rax = *(uint64_t*)rbx

; 6. ベース + ディスプレースメント (Base + Displacement)
mov    rax, [rbx + 8]         ; rax = *(uint64_t*)(rbx + 8)
mov    rax, [rbp - 16]        ; ローカル変数アクセス(スタックフレーム内)

; 7. ベース + インデックス (Base + Index)
mov    rax, [rbx + rcx]       ; rax = *(uint64_t*)(rbx + rcx)

; 8. ベース + インデックス * スケール (Base + Index * Scale)
mov    rax, [rbx + rcx*8]     ; rax = *(uint64_t*)(rbx + rcx*8)
; スケール: 1, 2, 4, 8 のいずれか
; 配列アクセスに最適: array[i] で sizeof(element) が 1/2/4/8

; 9. 完全形式 (Base + Index * Scale + Displacement)
mov    rax, [rbx + rcx*8 + 16]
; C 言語の struct_array[i].field に相当
; struct myStruct { long pad[2]; long value; };
; value = array[i].value → [base + i*sizeof(struct) + offsetof(value)]

; ============ AT&T 構文での等価表現 ============
; Intel: [base + index*scale + disp]
; AT&T:  disp(%base, %index, scale)

# movq   (%rbx), %rax               # [rbx]
# movq   8(%rbx), %rax              # [rbx + 8]
# movq   (%rbx,%rcx), %rax          # [rbx + rcx]
# movq   (%rbx,%rcx,8), %rax        # [rbx + rcx*8]
# movq   16(%rbx,%rcx,8), %rax      # [rbx + rcx*8 + 16]

7.3 AArch64 アドレッシングモード

AArch64 は Load/Store アーキテクチャであり、メモリアクセスは専用の LDR/STR 命令でのみ行う。

// ============ AArch64 アドレッシングモード ============

// 1. 即値オフセット (Immediate Offset)
ldr  x0, [x1]            // x0 = *x1
ldr  x0, [x1, #8]        // x0 = *(x1 + 8)
ldr  x0, [x1, #-8]       // x0 = *(x1 - 8)

// 2. プリインデックス (Pre-Index) — ベースレジスタを先に更新
ldr  x0, [x1, #8]!       // x1 += 8; x0 = *x1
// C 言語: x0 = *(++ptr)

// 3. ポストインデックス (Post-Index) — アクセス後にベースレジスタを更新
ldr  x0, [x1], #8        // x0 = *x1; x1 += 8
// C 言語: x0 = *(ptr++)

// 4. レジスタオフセット (Register Offset)
ldr  x0, [x1, x2]        // x0 = *(x1 + x2)

// 5. シフト付きレジスタオフセット
ldr  x0, [x1, x2, lsl #3]  // x0 = *(x1 + x2*8)
// 配列アクセス: uint64_t array[]; val = array[i]

// 6. 拡張付きレジスタオフセット
ldr  x0, [x1, w2, sxtw #3] // w2 を符号拡張して x1 + (sext(w2) * 8)
// 符号付きインデックスでの配列アクセス

// 7. PC 相対アドレッシング
adrp x0, myvar@PAGE         // myvar のページアドレス(4KB境界)
add  x0, x0, myvar@PAGEOFF  // ページ内オフセットを加算
ldr  x1, [x0]               // myvar の値をロード

// 8. リテラルロード (PC 相対)
ldr  x0, =0x123456789ABCDEF // リテラルプールからの即値ロード

// 9. ペアロード/ストア
ldp  x0, x1, [x2]           // x0 = *x2, x1 = *(x2+8)
stp  x0, x1, [sp, #-16]!    // sp -= 16; *sp = x0; *(sp+8) = x1

7.4 RISC-V アドレッシングモード

RISC-V は最もシンプルなアドレッシングモードを持つ。

# ============ RISC-V アドレッシングモード ============
# RISC-V は基本的に「ベース + 12ビット符号付きオフセット」のみ

# 1. ベース + オフセット
ld    a0, 0(sp)          # a0 = *(sp + 0)
ld    a0, 8(sp)          # a0 = *(sp + 8)
ld    a0, -8(s0)         # a0 = *(s0 - 8)

# 2. グローバル変数アクセス (2命令必要)
lui   a1, %hi(myvar)     # 上位20ビットをロード
ld    a0, %lo(myvar)(a1) # 下位12ビットオフセットでロード

# または疑似命令を使用
la    a1, myvar          # myvar のアドレスをロード (疑似命令)
ld    a0, 0(a1)          # 値をロード

# 3. PC 相対 (auipc + addi)
auipc a0, %pcrel_hi(myvar)    # PC + 上位20ビット
addi  a0, a0, %pcrel_lo(myvar) # + 下位12ビット

# RISC-V は複雑なアドレッシングモードを持たないため、
# 配列アクセスにはインデックス計算を明示的に行う必要がある
# array[i] のアクセス:
slli  t0, a1, 3          # t0 = i * 8 (int64_t の場合)
add   t0, a0, t0         # t0 = base + i * 8
ld    a2, 0(t0)          # a2 = array[i]

7.5 LEA 命令 (x86-64) の活用

LEA (Load Effective Address) は x86-64 で特に強力な命令で、アドレス計算の結果をレジスタに格納する(メモリアクセスは行わない)。

; LEA の一般的な使用法
; 1. アドレス計算
lea    rax, [rbx + rcx*8 + 16]  ; rax = rbx + rcx*8 + 16 (メモリアクセスなし)

; 2. 高速な算術演算として使用
lea    rax, [rbx + rbx*2]       ; rax = rbx * 3
lea    rax, [rbx + rbx*4]       ; rax = rbx * 5
lea    rax, [rbx*8 + rbx]       ; rax = rbx * 9
lea    rax, [rbx + rbx*2 + 1]   ; rax = rbx * 3 + 1

; 3. 複数の演算を1命令で実行
; C: result = base + index * 4 + offset
lea    rax, [rdi + rsi*4 + 100]

; 4. RIP 相対アドレスの取得
lea    rax, [rip + label]        ; label のアドレスを取得

8. 算術・論理演算命令

8.1 x86-64 の算術演算

; ============ 加算・減算 ============
add    rax, rbx          ; rax += rbx (フラグ更新)
add    rax, 42           ; rax += 42
adc    rax, rbx          ; rax += rbx + CF (キャリー付き加算 → 多倍長演算)
sub    rax, rbx          ; rax -= rbx
sbb    rax, rbx          ; rax -= rbx - CF (ボロー付き減算)
inc    rax               ; rax++ (CF は変更しない)
dec    rax               ; rax-- (CF は変更しない)
neg    rax               ; rax = -rax (2の補数)

; ============ 乗算 ============
imul   rax, rbx          ; rax *= rbx (符号付き、下位64bit)
imul   rax, rbx, 10      ; rax = rbx * 10 (3オペランド形式)
mul    rbx               ; RDX:RAX = RAX * RBX (符号なし、128bit結果)
imul   rbx               ; RDX:RAX = RAX * RBX (符号付き、128bit結果)

; ============ 除算 ============
; 除算は RDX:RAX を被除数として使用
xor    rdx, rdx          ; 符号なし除算の前に上位をゼロクリア
div    rbx               ; RAX = RDX:RAX / RBX, RDX = RDX:RAX % RBX (符号なし)
cqo                      ; RAX を RDX:RAX に符号拡張
idiv   rbx               ; RAX = 商, RDX = 余り (符号付き)

; ============ 128ビット加算の例 ============
; [r9:r8] = [rdx:rax] + [rcx:rbx]
add    rax, rbx          ; 下位64ビットを加算
adc    rdx, rcx          ; 上位64ビットをキャリー付き加算
mov    r8, rax
mov    r9, rdx

8.2 x86-64 の論理演算とビット操作

; ============ 論理演算 ============
and    rax, rbx          ; rax &= rbx
or     rax, rbx          ; rax |= rbx
xor    rax, rbx          ; rax ^= rbx
not    rax               ; rax = ~rax (ビット反転)
test   rax, rbx          ; rax & rbx (結果を捨て、フラグのみ更新)

; ============ シフト演算 ============
shl    rax, 4            ; rax <<= 4 (論理左シフト = 2^4 倍)
shr    rax, 4            ; rax >>= 4 (論理右シフト、符号なし)
sar    rax, 4            ; rax >>= 4 (算術右シフト、符号保持)
sal    rax, 4            ; shl と同じ

; CL レジスタによるシフト
mov    cl, 5
shl    rax, cl           ; rax <<= cl

; ============ ローテーション ============
rol    rax, 4            ; 左ローテーション
ror    rax, 4            ; 右ローテーション
rcl    rax, 1            ; CF を含む左ローテーション
rcr    rax, 1            ; CF を含む右ローテーション

; ============ ビット走査 ============
bsf    rax, rbx          ; Bit Scan Forward: 最下位の1ビットの位置
bsr    rax, rbx          ; Bit Scan Reverse: 最上位の1ビットの位置
popcnt rax, rbx          ; ポピュレーションカウント(1ビットの数)
lzcnt  rax, rbx          ; リーディングゼロカウント
tzcnt  rax, rbx          ; トレーリングゼロカウント

; ============ BMI (Bit Manipulation Instructions) ============
; BMI1
andn   rax, rbx, rcx    ; rax = ~rbx & rcx
bextr  rax, rbx, rcx    ; ビットフィールド抽出
blsi   rax, rbx          ; 最下位セットビットを分離: rax = rbx & (-rbx)
blsmsk rax, rbx          ; 最下位セットビットまでのマスク
blsr   rax, rbx          ; 最下位セットビットをリセット

; BMI2
pdep   rax, rbx, rcx    ; パラレルビットデポジット
pext   rax, rbx, rcx    ; パラレルビットエクストラクト
bzhi   rax, rbx, rcx    ; 指定ビット以上をゼロクリア

8.3 x86-64 の実用的なイディオム

; ============ よく使われるイディオム ============

; レジスタのゼロクリア(最も効率的な方法)
xor    eax, eax          ; RAX = 0 (2バイト、依存関係破壊)
; mov rax, 0 は 7バイト → xor のほうが短くて速い

; 値が0かどうかの判定
test   rax, rax          ; ZF = (rax == 0)
jz     is_zero

; 値の符号判定
test   rax, rax
js     is_negative       ; SF = 1 なら負

; 偶数/奇数の判定
test   rax, 1
jz     is_even           ; 最下位ビットが0なら偶数

; 2のべき乗かどうかの判定
; n が 2のべき乗 ⟺ n > 0 && (n & (n-1)) == 0
mov    rbx, rax
dec    rbx
test   rax, rbx
jz     is_power_of_two

; 絶対値
mov    rbx, rax
sar    rbx, 63           ; rbx = (rax < 0) ? -1 : 0
xor    rax, rbx
sub    rax, rbx          ; rax = abs(rax)

; min(rax, rbx) - 分岐なし
cmp    rax, rbx
cmovg  rax, rbx          ; rax > rbx なら rax = rbx

; max(rax, rbx) - 分岐なし
cmp    rax, rbx
cmovl  rax, rbx          ; rax < rbx なら rax = rbx

; swap(rax, rbx) - 一時変数なし
xchg   rax, rbx          ; ただしメモリオペランドだと暗黙の LOCK
; 代替: 3つの xor
; xor rax, rbx
; xor rbx, rax
; xor rax, rbx

8.4 AArch64 の算術・論理演算

// ============ AArch64 算術演算 ============
add   x0, x1, x2          // x0 = x1 + x2
adds  x0, x1, x2          // x0 = x1 + x2 (フラグ更新)
adc   x0, x1, x2          // x0 = x1 + x2 + C
sub   x0, x1, x2          // x0 = x1 - x2
subs  x0, x1, x2          // x0 = x1 - x2 (フラグ更新)
neg   x0, x1              // x0 = -x1

// シフト付き演算 (ARM の特徴)
add   x0, x1, x2, lsl #3  // x0 = x1 + (x2 << 3) = x1 + x2*8
sub   x0, x1, x2, asr #2  // x0 = x1 - (x2 >> 2) (算術)

// 乗算と積和演算
mul   x0, x1, x2          // x0 = x1 * x2
madd  x0, x1, x2, x3      // x0 = x1 * x2 + x3
msub  x0, x1, x2, x3      // x0 = x3 - x1 * x2
smulh x0, x1, x2          // x0 = (x1 * x2) >> 64 (符号付き上位)
umulh x0, x1, x2          // x0 = (x1 * x2) >> 64 (符号なし上位)
smaddl x0, w1, w2, x3     // x0 = (int64_t)w1 * w2 + x3

// 除算
sdiv  x0, x1, x2          // x0 = x1 / x2 (符号付き)
udiv  x0, x1, x2          // x0 = x1 / x2 (符号なし)
// 余りは直接の命令がない → msub を使用
// remainder = dividend - (quotient * divisor)
sdiv  x0, x1, x2
msub  x3, x0, x2, x1      // x3 = x1 - x0 * x2 = x1 % x2

// ============ AArch64 論理演算 ============
and   x0, x1, x2
orr   x0, x1, x2
eor   x0, x1, x2
bic   x0, x1, x2          // x0 = x1 & ~x2 (bit clear)
orn   x0, x1, x2          // x0 = x1 | ~x2
eon   x0, x1, x2          // x0 = x1 ^ ~x2

// ビットフィールド操作
ubfx  x0, x1, #4, #8      // 符号なしビットフィールド抽出 (bit4から8ビット)
sbfx  x0, x1, #4, #8      // 符号付きビットフィールド抽出
bfi   x0, x1, #4, #8      // ビットフィールド挿入
bfxil x0, x1, #4, #8      // 低位ビットフィールド挿入

// ビットカウント
cls   x0, x1              // Count Leading Signs
clz   x0, x1              // Count Leading Zeros
rbit  x0, x1              // ビットリバース
rev   x0, x1              // バイトリバース (エンディアン変換)

8.5 RISC-V の算術・論理演算

# ============ RISC-V 算術演算 ============
add   a0, a1, a2          # a0 = a1 + a2
addi  a0, a1, 100         # a0 = a1 + 100 (即値は12ビット符号付き)
sub   a0, a1, a2          # a0 = a1 - a2
# subi は存在しない → addi a0, a1, -100

# M 拡張 (乗除算)
mul    a0, a1, a2         # a0 = (a1 * a2)[63:0] (下位64ビット)
mulh   a0, a1, a2         # a0 = (a1 * a2)[127:64] (符号付き上位)
mulhu  a0, a1, a2         # a0 = (a1 * a2)[127:64] (符号なし上位)
div    a0, a1, a2         # a0 = a1 / a2 (符号付き)
divu   a0, a1, a2         # a0 = a1 / a2 (符号なし)
rem    a0, a1, a2         # a0 = a1 % a2 (符号付き)
remu   a0, a1, a2         # a0 = a1 % a2 (符号なし)

# ============ RISC-V 論理演算 ============
and   a0, a1, a2
andi  a0, a1, 0xFF
or    a0, a1, a2
ori   a0, a1, 0xFF
xor   a0, a1, a2
xori  a0, a1, 0xFF

# シフト
sll   a0, a1, a2          # 論理左シフト
slli  a0, a1, 4           # 即値論理左シフト
srl   a0, a1, a2          # 論理右シフト
srli  a0, a1, 4
sra   a0, a1, a2          # 算術右シフト
srai  a0, a1, 4

# 比較 (Set Less Than)
slt   a0, a1, a2          # a0 = (a1 < a2) ? 1 : 0 (符号付き)
sltu  a0, a1, a2          # a0 = (a1 < a2) ? 1 : 0 (符号なし)
slti  a0, a1, 100         # 即値比較

# RISC-V にはフラグレジスタがない
# 条件はすべて比較命令 (slt) または分岐命令 (beq/bne/blt/bge) で処理

9. 制御フロー

9.1 x86-64 の分岐命令

; ============ 無条件ジャンプ ============
jmp    label              ; 直接ジャンプ
jmp    rax                ; レジスタ間接ジャンプ
jmp    [rax]              ; メモリ間接ジャンプ
jmp    [rip + jumptable + rax*8] ; ジャンプテーブル

; ============ 条件ジャンプ ============
; CMP/TEST の後に使用
cmp    rax, rbx

; 符号なし比較
ja     label              ; Above (CF=0 かつ ZF=0)
jae    label              ; Above or Equal (CF=0)
jb     label              ; Below (CF=1)
jbe    label              ; Below or Equal (CF=1 または ZF=1)

; 符号付き比較
jg     label              ; Greater (ZF=0 かつ SF=OF)
jge    label              ; Greater or Equal (SF=OF)
jl     label              ; Less (SF≠OF)
jle    label              ; Less or Equal (ZF=1 または SF≠OF)

; 等価
je     label              ; Equal (ZF=1) (jz と同義)
jne    label              ; Not Equal (ZF=0) (jnz と同義)

; フラグ個別チェック
jz     label              ; Zero (ZF=1)
jnz    label              ; Not Zero (ZF=0)
js     label              ; Sign (SF=1、結果が負)
jns    label              ; Not Sign (SF=0)
jo     label              ; Overflow (OF=1)
jno    label              ; Not Overflow (OF=0)
jc     label              ; Carry (CF=1)
jnc    label              ; Not Carry (CF=0)

9.2 if-else の実装

; ============ C: if (x > 0) { a = 1; } else { a = -1; } ============

; 方法1: 分岐を使用
    cmp    rdi, 0
    jle    .else
    mov    eax, 1
    jmp    .endif
.else:
    mov    eax, -1
.endif:

; 方法2: CMOV を使用(分岐なし — パイプライン効率向上)
    mov    eax, 1
    mov    ecx, -1
    cmp    rdi, 0
    cmovle eax, ecx          ; rdi <= 0 なら eax = ecx

; 方法3: SETcc を使用
    xor    eax, eax
    cmp    rdi, 0
    setg   al                ; rdi > 0 なら al = 1, そうでなければ al = 0
    lea    eax, [rax*2 - 1]  ; 0 → -1, 1 → 1

9.3 ループの実装

; ============ for ループ: for (int i = 0; i < n; i++) sum += array[i] ============
; rdi = array pointer, rsi = n
; 戻り値: rax = sum

sum_array:
    xor    eax, eax          ; sum = 0
    xor    ecx, ecx          ; i = 0
    test   rsi, rsi
    jle    .done             ; n <= 0 ならスキップ
.loop:
    add    rax, [rdi + rcx*8] ; sum += array[i]
    inc    rcx               ; i++
    cmp    rcx, rsi          ; i < n ?
    jl     .loop
.done:
    ret

; ============ while ループ: while (*str != '\0') count++ ============
; rdi = str pointer
; 戻り値: rax = count (strlen の実装)

my_strlen:
    xor    eax, eax          ; count = 0
    test   rdi, rdi
    jz     .done
.loop:
    cmp    byte [rdi + rax], 0
    je     .done
    inc    rax
    jmp    .loop
.done:
    ret

; ============ do-while ループ ============
; 最低1回は実行する
    mov    ecx, 10           ; counter = 10
.do_loop:
    ; ループ本体
    dec    ecx
    jnz    .do_loop          ; counter != 0 なら繰り返し

; ============ LOOP 命令 (レガシー、非推奨) ============
    mov    ecx, 100          ; ループカウンタ
.legacy_loop:
    ; ループ本体
    loop   .legacy_loop      ; ecx--; ecx != 0 なら分岐
; ※ loop 命令は遅い。dec + jnz のほうが高速

9.4 switch-case の実装(ジャンプテーブル)

; ============ switch (value) { case 0: ... case 1: ... case 2: ... } ============
; rdi = value

switch_example:
    cmp    rdi, 2
    ja     .default           ; value > 2 なら default

    ; ジャンプテーブルを使用
    lea    rax, [rel .jumptable]
    movsxd rcx, dword [rax + rdi*4]  ; テーブルから相対オフセット取得
    add    rax, rcx
    jmp    rax

.jumptable:
    dd     .case0 - .jumptable
    dd     .case1 - .jumptable
    dd     .case2 - .jumptable

.case0:
    mov    eax, 100
    ret
.case1:
    mov    eax, 200
    ret
.case2:
    mov    eax, 300
    ret
.default:
    mov    eax, -1
    ret

9.5 AArch64 の制御フロー

// ============ AArch64 分岐命令 ============

// 無条件分岐
b      label              // 直接分岐 (±128MB 範囲)
br     x0                 // レジスタ間接分岐
bl     function           // 分岐とリンク (x30 に戻りアドレス保存)
blr    x0                 // レジスタ間接分岐とリンク
ret                       // x30 へリターン

// 条件分岐
cmp    x0, x1
b.eq   label              // Equal
b.ne   label              // Not Equal
b.lt   label              // Less Than (符号付き)
b.le   label              // Less or Equal
b.gt   label              // Greater Than
b.ge   label              // Greater or Equal
b.lo   label              // Lower (符号なし)
b.hi   label              // Higher (符号なし)
b.mi   label              // Minus (負)
b.pl   label              // Plus (正または0)
b.vs   label              // Overflow
b.vc   label              // No Overflow

// ============ Compare and Branch (ゼロ比較の分岐) ============
cbz    x0, label          // x0 == 0 なら分岐
cbnz   x0, label          // x0 != 0 なら分岐

// ============ Test and Branch (ビットテスト分岐) ============
tbz    x0, #31, label     // x0 のビット31が0なら分岐 (正の値)
tbnz   x0, #0, label      // x0 のビット0が1なら分岐 (奇数)

// ============ for ループの例 ============
// for (int i = 0; i < n; i++) sum += array[i]
// x0 = array, x1 = n
sum_array_arm:
    mov    x2, #0              // sum = 0
    mov    x3, #0              // i = 0
    cmp    x1, #0
    b.le   .Ldone
.Lloop:
    ldr    x4, [x0, x3, lsl #3]  // x4 = array[i]
    add    x2, x2, x4            // sum += array[i]
    add    x3, x3, #1            // i++
    cmp    x3, x1                // i < n ?
    b.lt   .Lloop
.Ldone:
    mov    x0, x2                // return sum
    ret

9.6 RISC-V の制御フロー

# ============ RISC-V 分岐命令 ============
# RISC-V は比較と分岐が一体化した命令を持つ(フラグレジスタなし)

# 条件分岐 (B-Type)
beq   a0, a1, label       # a0 == a1 なら分岐
bne   a0, a1, label       # a0 != a1 なら分岐
blt   a0, a1, label       # a0 < a1 (符号付き) なら分岐
bge   a0, a1, label       # a0 >= a1 (符号付き) なら分岐
bltu  a0, a1, label       # a0 < a1 (符号なし) なら分岐
bgeu  a0, a1, label       # a0 >= a1 (符号なし) なら分岐

# ゼロ比較 (疑似命令)
beqz  a0, label            # beq a0, zero, label
bnez  a0, label            # bne a0, zero, label

# 無条件ジャンプ
jal   ra, function         # function 呼び出し (ra に戻りアドレス)
jalr  ra, 0(a0)            # レジスタ間接呼び出し
j     label                # 疑似命令: jal zero, label
ret                        # 疑似命令: jalr zero, 0(ra)

# ============ RISC-V for ループの例 ============
# for (int i = 0; i < n; i++) sum += array[i]
# a0 = array, a1 = n
sum_array_rv:
    li     a2, 0              # sum = 0
    li     a3, 0              # i = 0
    ble    a1, zero, .Ldone   # n <= 0 ならスキップ
.Lloop:
    slli   t0, a3, 3          # t0 = i * 8
    add    t0, a0, t0         # t0 = &array[i]
    ld     t1, 0(t0)          # t1 = array[i]
    add    a2, a2, t1         # sum += array[i]
    addi   a3, a3, 1          # i++
    blt    a3, a1, .Lloop     # i < n なら繰り返し
.Ldone:
    mv     a0, a2             # return sum
    ret

10. スタック操作と呼び出し規約

10.1 スタックの基本

スタックは後入れ先出し(LIFO)のデータ構造であり、関数呼び出し、ローカル変数の格納、レジスタの退避に使用される。x86-64 および AArch64 では、スタックは高アドレスから低アドレスに向かって成長する。

高アドレス
┌──────────────────┐
│  前のフレーム      │
├──────────────────┤ ← 関数呼び出し前の RSP/SP
│  戻りアドレス      │  (x86: CALL で自動 push)
├──────────────────┤
│  保存された RBP    │  ← RBP (フレームポインタ)
├──────────────────┤
│  ローカル変数1     │
│  ローカル変数2     │
│  ...              │
├──────────────────┤ ← RSP/SP (スタックポインタ)
│  (未使用領域)      │
└──────────────────┘
低アドレス

10.2 x86-64 System V ABI (Linux/macOS/FreeBSD)

System V AMD64 ABI は、Linux、macOS、FreeBSD などの Unix 系 OS で使用される呼び出し規約である。

整数/ポインタ引数:   RDI, RSI, RDX, RCX, R8, R9 (6個まで)
浮動小数点引数:      XMM0-XMM7 (8個まで)
戻り値:             RAX (整数), RAX:RDX (128ビット), XMM0 (浮動小数点)
スタックアラインメント: 16バイト (CALL 命令実行時)

Caller-saved (揮発性):  RAX, RCX, RDX, RSI, RDI, R8-R11, XMM0-XMM15
Callee-saved (不揮発性): RBX, RBP, R12-R15
スタックポインタ:        RSP (callee-saved)
; ============ System V ABI の関数呼び出し例 ============
; C: long result = my_function(1, 2, 3, 4, 5, 6, 7, 8);

    ; 第7, 8引数はスタック経由 (逆順で push)
    push   8                 ; 第8引数
    push   7                 ; 第7引数
    mov    r9d, 6            ; 第6引数
    mov    r8d, 5            ; 第5引数
    mov    ecx, 4            ; 第4引数
    mov    edx, 3            ; 第3引数
    mov    esi, 2            ; 第2引数
    mov    edi, 1            ; 第1引数
    call   my_function
    add    rsp, 16           ; スタック引数のクリーンアップ (8*2)
    ; 戻り値は RAX に入っている

; ============ 関数のプロローグとエピローグ ============
my_function:
    ; プロローグ
    push   rbp               ; 旧フレームポインタを保存
    mov    rbp, rsp           ; 新しいフレームポインタを設定
    sub    rsp, 32            ; ローカル変数用に 32 バイト確保
    push   rbx               ; callee-saved レジスタの保存
    push   r12

    ; ローカル変数へのアクセス
    mov    dword [rbp - 4], edi    ; 第1引数をローカル変数に保存
    mov    dword [rbp - 8], esi    ; 第2引数をローカル変数に保存

    ; 関数本体
    ; ...

    ; エピローグ
    pop    r12               ; callee-saved レジスタの復帰
    pop    rbx
    leave                    ; mov rsp, rbp; pop rbp
    ret                      ; pop rip (戻りアドレスへジャンプ)

10.3 Windows x64 呼び出し規約

整数/ポインタ引数:   RCX, RDX, R8, R9 (4個まで)
浮動小数点引数:      XMM0-XMM3 (4個まで)
戻り値:             RAX
スタック:           32バイトのシャドウスペース必須
スタックアラインメント: 16バイト

Caller-saved:  RAX, RCX, RDX, R8-R11, XMM0-XMM5
Callee-saved:  RBX, RBP, RDI, RSI, R12-R15, XMM6-XMM15
; ============ Windows x64 の関数呼び出し例 ============
; C: long result = my_function(1, 2, 3, 4, 5);

    sub    rsp, 48           ; シャドウスペース(32) + 第5引数(8) + アラインメント
    mov    dword [rsp + 32], 5  ; 第5引数 (スタック)
    mov    r9d, 4            ; 第4引数
    mov    r8d, 3            ; 第3引数
    mov    edx, 2            ; 第2引数
    mov    ecx, 1            ; 第1引数
    call   my_function
    add    rsp, 48

10.4 AArch64 AAPCS64 呼び出し規約

整数/ポインタ引数:   X0-X7 (8個まで)
浮動小数点引数:      D0-D7 / V0-V7 (8個まで)
戻り値:             X0 (整数), X0:X1 (128ビット), D0 (浮動小数点)
フレームポインタ:    X29 (FP)
リンクレジスタ:      X30 (LR)
スタックアラインメント: 16バイト

Caller-saved:  X0-X18, D0-D7, D16-D31
Callee-saved:  X19-X28, X29(FP), X30(LR), D8-D15
// ============ AArch64 関数の例 ============
// int64_t add_numbers(int64_t a, int64_t b, int64_t c)
// a=x0, b=x1, c=x2

.global _add_numbers
.align 4

_add_numbers:
    // プロローグ
    stp    x29, x30, [sp, #-16]!   // FP, LR を保存 (sp -= 16)
    mov    x29, sp                   // フレームポインタ設定

    // 関数本体
    add    x0, x0, x1               // a + b
    add    x0, x0, x2               // + c
    // 戻り値は x0

    // エピローグ
    ldp    x29, x30, [sp], #16      // FP, LR を復帰 (sp += 16)
    ret

// ============ callee-saved レジスタの保存例 ============
complex_function:
    // callee-saved レジスタの保存 (ペアで保存)
    stp    x29, x30, [sp, #-64]!
    mov    x29, sp
    stp    x19, x20, [sp, #16]
    stp    x21, x22, [sp, #32]
    stp    x23, x24, [sp, #48]

    // x19-x24 を自由に使用可能
    mov    x19, x0
    mov    x20, x1
    // ...

    // 復帰
    ldp    x23, x24, [sp, #48]
    ldp    x21, x22, [sp, #32]
    ldp    x19, x20, [sp, #16]
    ldp    x29, x30, [sp], #64
    ret

10.5 RISC-V 呼び出し規約

整数/ポインタ引数:   a0-a7 (8個まで)
浮動小数点引数:      fa0-fa7 (8個まで)
戻り値:             a0 (整数), a0:a1 (128ビット), fa0 (浮動小数点)
リターンアドレス:    ra (x1)
スタックポインタ:    sp (x2)
フレームポインタ:    s0/fp (x8)

Caller-saved:  ra, t0-t6, a0-a7, ft0-ft11, fa0-fa7
Callee-saved:  sp, s0-s11, fs0-fs11
# ============ RISC-V 関数の例 ============
# int64_t factorial(int64_t n)  -- 再帰版

.global factorial
factorial:
    # プロローグ
    addi   sp, sp, -16        # スタックフレーム確保
    sd     ra, 8(sp)          # リターンアドレス保存
    sd     s0, 0(sp)          # s0 (callee-saved) 保存

    # ベースケース: n <= 1
    li     t0, 1
    ble    a0, t0, .Lbase

    # 再帰ケース
    mv     s0, a0             # n を保存 (callee-saved)
    addi   a0, a0, -1         # a0 = n - 1
    call   factorial          # factorial(n - 1)
    mul    a0, s0, a0         # a0 = n * factorial(n - 1)
    j      .Lepilogue

.Lbase:
    li     a0, 1              # return 1

.Lepilogue:
    # エピローグ
    ld     ra, 8(sp)
    ld     s0, 0(sp)
    addi   sp, sp, 16
    ret

10.6 Red Zone

System V AMD64 ABI では、RSP より下の 128 バイトが「Red Zone」として予約されている。リーフ関数(他の関数を呼び出さない関数)は、スタックポインタを調整せずにこの領域を使用できる。

; Red Zone を利用するリーフ関数(スタック調整不要)
leaf_function:
    ; sub rsp, ... は不要
    mov    [rsp - 8], rdi      ; Red Zone 内のローカル変数
    mov    [rsp - 16], rsi
    ; ...
    ret
; ※ 割り込みハンドラや シグナルハンドラでは Red Zone は使用できない
; ※ Windows x64 ABI には Red Zone は存在しない
; ※ Linux カーネルコードでは -mno-red-zone を指定する

11. システムコールと OS インターフェース

11.1 システムコールの仕組み

システムコール(syscall)は、ユーザー空間のプログラムがカーネルの機能を呼び出すためのインターフェースである。

ユーザー空間                    カーネル空間
┌──────────────┐              ┌──────────────┐
│ アプリケーション │    syscall   │   カーネル    │
│              │ ──────────→ │              │
│ write(fd,    │              │ sys_write()  │
│   buf, len)  │ ←────────── │              │
│              │    return    │              │
└──────────────┘              └──────────────┘
     Ring 3                        Ring 0
  (特権レベル3)                  (特権レベル0)

11.2 Linux x86-64 システムコール

; Linux x86-64 のシステムコール規約:
; RAX = システムコール番号
; 引数: RDI, RSI, RDX, R10, R8, R9 (最大6個)
; 戻り値: RAX (-errno は負の値で返る)
; 破壊されるレジスタ: RCX, R11 (syscall 命令が使用)

; 主要なシステムコール番号 (Linux x86-64):
;   0: read      1: write     2: open      3: close
;   9: mmap     11: munmap   12: brk      57: fork
;  59: execve   60: exit     62: kill
; 231: exit_group

; ============ write システムコール ============
section .data
    msg db "Hello, Linux syscall!", 10
    msg_len equ $ - msg

section .text
global _start

_start:
    ; write(STDOUT_FILENO, msg, msg_len)
    mov    rax, 1            ; syscall: write
    mov    rdi, 1            ; fd: stdout
    lea    rsi, [rel msg]    ; buffer
    mov    rdx, msg_len      ; count
    syscall                   ; カーネル呼び出し
    ; 戻り値: rax = 書き込みバイト数 (エラー時は負の errno)

    ; exit(0)
    mov    rax, 60           ; syscall: exit
    xor    rdi, rdi          ; status: 0
    syscall

; ============ ファイル操作の例 ============
; int fd = open("test.txt", O_RDWR | O_CREAT, 0644);
open_file:
    mov    rax, 2            ; syscall: open
    lea    rdi, [rel filename]
    mov    rsi, 0o102        ; O_RDWR(2) | O_CREAT(0100)
    mov    rdx, 0o644        ; パーミッション
    syscall
    ; rax = ファイルディスクリプタ (エラー時は負の値)
    ret

; ============ mmap の例 ============
; void *ptr = mmap(NULL, 4096, PROT_READ|PROT_WRITE,
;                  MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
mmap_example:
    mov    rax, 9            ; syscall: mmap
    xor    rdi, rdi          ; addr: NULL (カーネルに選択させる)
    mov    rsi, 4096         ; length: 4096 bytes
    mov    rdx, 3            ; prot: PROT_READ(1) | PROT_WRITE(2)
    mov    r10, 0x22         ; flags: MAP_PRIVATE(2) | MAP_ANONYMOUS(0x20)
    mov    r8, -1            ; fd: -1 (anonymous)
    xor    r9, r9            ; offset: 0
    syscall
    ; rax = マップされたアドレス
    ret

11.3 macOS (Darwin) x86-64 システムコール

; macOS のシステムコール番号は Unix 番号 + 0x2000000
; (Mach trap は 0x1000000 ベース)
; それ以外は Linux と同じレジスタ規約を使用

; macOS x86-64 のシステムコール
SYS_EXIT   equ 0x2000001
SYS_FORK   equ 0x2000002
SYS_READ   equ 0x2000003
SYS_WRITE  equ 0x2000004
SYS_OPEN   equ 0x2000005
SYS_CLOSE  equ 0x2000006

section .data
    msg db "Hello, macOS!", 10
    msg_len equ $ - msg

section .text
global _main

_main:
    ; write(1, msg, msg_len)
    mov    rax, SYS_WRITE
    mov    rdi, 1
    lea    rsi, [rel msg]
    mov    rdx, msg_len
    syscall

    ; exit(0)
    mov    rax, SYS_EXIT
    xor    rdi, rdi
    syscall

11.4 macOS AArch64 システムコール

// macOS AArch64 のシステムコール規約:
// X16 = システムコール番号 (Unix: 番号そのまま + SVC #0x80)
// 引数: X0-X5 (最大6個)
// 戻り値: X0
// エラー: Carry Flag がセットされる

.global _main
.align 4

.equ SYS_EXIT,  1
.equ SYS_WRITE, 4

_main:
    // write(1, message, 14)
    mov    x0, #1                    // fd = stdout
    adrp   x1, message@PAGE
    add    x1, x1, message@PAGEOFF   // buffer address
    mov    x2, #14                   // count
    mov    x16, #SYS_WRITE
    svc    #0x80                     // supervisor call

    // exit(0)
    mov    x0, #0
    mov    x16, #SYS_EXIT
    svc    #0x80

.data
message:
    .ascii "Hello, macOS!\n"

11.5 Linux AArch64 システムコール

// Linux AArch64 のシステムコール規約:
// X8 = システムコール番号
// 引数: X0-X5
// 戻り値: X0
// 命令: SVC #0

.global _start
.align 4

_start:
    // write(1, message, 14)
    mov    x0, #1              // fd = stdout
    ldr    x1, =message        // buffer
    mov    x2, #14             // count
    mov    x8, #64             // syscall: write (Linux AArch64 = 64)
    svc    #0

    // exit(0)
    mov    x0, #0
    mov    x8, #93             // syscall: exit (Linux AArch64 = 93)
    svc    #0

.data
message:
    .ascii "Hello, Linux!\n"

11.6 C ライブラリ関数との連携

実際のプログラムでは、直接システムコールを発行するよりも、C ライブラリ(libc)の関数を呼び出すほうが一般的である。

; Linux x86-64: C ライブラリ関数を使用
section .data
    fmt db "Result: %ld", 10, 0    ; printf のフォーマット文字列

section .text
global main
extern printf
extern malloc
extern free
extern strlen

main:
    push   rbp
    mov    rbp, rsp

    ; printf("Result: %ld\n", 42)
    lea    rdi, [rel fmt]    ; フォーマット文字列
    mov    rsi, 42           ; 引数
    xor    eax, eax          ; 浮動小数点引数の数 = 0
    call   printf wrt ..plt  ; PLT 経由で呼び出し

    ; char *buf = malloc(1024)
    mov    edi, 1024
    call   malloc wrt ..plt
    ; rax = ポインタ

    ; free(buf)
    mov    rdi, rax
    call   free wrt ..plt

    xor    eax, eax          ; return 0
    leave
    ret

12. SIMD 命令

12.1 SIMD の概要

SIMD(Single Instruction, Multiple Data)は、1 つの命令で複数のデータ要素に対して同じ演算を並列に実行する技術である。画像処理、音声処理、科学計算、機械学習などの並列性が高い処理で大幅な高速化が可能となる。

スカラー演算:                    SIMD 演算 (128bit SSE):
A1 + B1 = C1                   [A1, A2, A3, A4]
A2 + B2 = C2                 + [B1, B2, B3, B4]
A3 + B3 = C3                 = [C1, C2, C3, C4]
A4 + B4 = C4                   → 1命令で4つの演算を実行
(4命令必要)

12.2 x86-64 SSE/AVX

拡張セットの進化:
MMX    (1997): 64-bit,  MM0-MM7,    整数のみ
SSE    (1999): 128-bit, XMM0-XMM7,  単精度浮動小数点
SSE2   (2001): 128-bit, XMM0-XMM15, 倍精度浮動小数点 + 整数
SSE3   (2004): 水平演算の追加
SSE4.1 (2006): blend, round, insert/extract
SSE4.2 (2008): 文字列処理、CRC32、POPCNT
AVX    (2011): 256-bit, YMM0-YMM15
AVX2   (2013): 256-bit 整数演算
AVX-512(2016): 512-bit, ZMM0-ZMM31, マスクレジスタ
; ============ SSE: 4つの float の加算 ============
section .data
align 16
vec_a: dd 1.0, 2.0, 3.0, 4.0      ; 4 x float
vec_b: dd 5.0, 6.0, 7.0, 8.0

section .bss
align 16
vec_c: resd 4

section .text
global sse_add_example

sse_add_example:
    movaps  xmm0, [rel vec_a]       ; xmm0 = [1.0, 2.0, 3.0, 4.0]
    movaps  xmm1, [rel vec_b]       ; xmm1 = [5.0, 6.0, 7.0, 8.0]
    addps   xmm0, xmm1              ; xmm0 = [6.0, 8.0, 10.0, 12.0]
    movaps  [rel vec_c], xmm0       ; 結果を保存
    ret

; ps = Packed Single-precision (4 x float)
; pd = Packed Double-precision (2 x double)
; ss = Scalar Single-precision (1 x float)
; sd = Scalar Double-precision (1 x double)

; ============ AVX: 8つの float の乗算 ============
avx_mul_example:
    vmovaps ymm0, [rdi]             ; ymm0 = 8 floats from array a
    vmovaps ymm1, [rsi]             ; ymm1 = 8 floats from array b
    vmulps  ymm2, ymm0, ymm1        ; ymm2 = ymm0 * ymm1 (要素ごと)
    vmovaps [rdx], ymm2             ; 結果を格納
    vzeroupper                       ; AVX→SSE 遷移ペナルティ回避
    ret

; ============ AVX2: 整数ベクトル演算 ============
; 32バイト(8 x int32)の加算
avx2_int_add:
    vmovdqa ymm0, [rdi]             ; 8 x int32 をロード
    vmovdqa ymm1, [rsi]
    vpaddd  ymm2, ymm0, ymm1        ; 要素ごとに32ビット整数加算
    vmovdqa [rdx], ymm2
    vzeroupper
    ret

12.3 SSE を使った実用例: ドット積

; float dot_product(const float *a, const float *b, int n)
; rdi = a, rsi = b, edx = n

global dot_product
dot_product:
    xorps   xmm0, xmm0          ; sum = 0.0 (4 x float)
    mov     ecx, edx
    shr     ecx, 2               ; n / 4 (4要素ずつ処理)
    jz      .remainder

.loop4:
    movups  xmm1, [rdi]          ; a[i..i+3] をロード (アラインメント不要)
    movups  xmm2, [rsi]          ; b[i..i+3] をロード
    mulps   xmm1, xmm2           ; 要素ごとの乗算
    addps   xmm0, xmm1           ; 部分和に加算
    add     rdi, 16
    add     rsi, 16
    dec     ecx
    jnz     .loop4

    ; 水平加算: xmm0 = [s0, s1, s2, s3]
    haddps  xmm0, xmm0           ; xmm0 = [s0+s1, s2+s3, s0+s1, s2+s3]
    haddps  xmm0, xmm0           ; xmm0 = [s0+s1+s2+s3, ...]

.remainder:
    ; 残り要素の処理 (n % 4)
    and     edx, 3
    jz      .done
.loop1:
    movss   xmm1, [rdi]
    mulss   xmm1, [rsi]
    addss   xmm0, xmm1
    add     rdi, 4
    add     rsi, 4
    dec     edx
    jnz     .loop1

.done:
    ret                           ; 戻り値は xmm0 (float)

12.4 ARM NEON

ARM NEON は AArch64 の標準 SIMD 拡張であり、128 ビットベクトルレジスタ(V0-V31)を使用する。

// ============ NEON の基本 ============
// V0-V31: 128ビットベクトルレジスタ
// アクセスパターン:
//   Vn.16B  - 16 x byte
//   Vn.8H   - 8 x half-word (16-bit)
//   Vn.4S   - 4 x single-word (32-bit)
//   Vn.2D   - 2 x double-word (64-bit)
//   Bn, Hn, Sn, Dn - スカラーアクセス

// ============ 4つの float の加算 ============
neon_add_example:
    ld1     {v0.4s}, [x0]       // v0 = 4 floats from array a
    ld1     {v1.4s}, [x1]       // v1 = 4 floats from array b
    fadd    v2.4s, v0.4s, v1.4s // v2 = v0 + v1 (要素ごと)
    st1     {v2.4s}, [x2]       // 結果を格納
    ret

// ============ NEON ドット積 ============
// float neon_dot_product(const float *a, const float *b, int n)
// x0 = a, x1 = b, w2 = n
neon_dot_product:
    movi    v0.4s, #0            // sum = 0 (4 x float)
    lsr     w3, w2, #2           // n / 4
    cbz     w3, .Lremainder

.Lloop4:
    ld1     {v1.4s}, [x0], #16  // a[i..i+3] をロード、x0 += 16
    ld1     {v2.4s}, [x1], #16  // b[i..i+3] をロード、x1 += 16
    fmla    v0.4s, v1.4s, v2.4s // v0 += v1 * v2 (積和演算)
    subs    w3, w3, #1
    b.ne    .Lloop4

    // 水平加算
    faddp   v0.4s, v0.4s, v0.4s // ペアワイズ加算 [s0+s1, s2+s3, ...]
    faddp   s0, v0.2s           // 最終加算 → s0 に合計

.Lremainder:
    and     w2, w2, #3
    cbz     w2, .Ldone
.Lloop1:
    ldr     s1, [x0], #4
    ldr     s2, [x1], #4
    fmadd   s0, s1, s2, s0      // s0 += s1 * s2
    subs    w2, w2, #1
    b.ne    .Lloop1

.Ldone:
    ret                          // 戻り値は s0 (float)

// ============ NEON 画像処理: RGBA→グレースケール変換 ============
// 各ピクセル: gray = 0.299*R + 0.587*G + 0.114*B
neon_rgba_to_gray:
    // 係数をセットアップ
    movi    v16.8b, #77          // 0.299 * 256 ≈ 77
    movi    v17.8b, #150         // 0.587 * 256 ≈ 150
    movi    v18.8b, #29          // 0.114 * 256 ≈ 29

.Lpixel_loop:
    ld4     {v0.8b, v1.8b, v2.8b, v3.8b}, [x0], #32
    // v0=R, v1=G, v2=B, v3=A (8ピクセル分をデインターリーブ)

    umull   v4.8h, v0.8b, v16.8b  // R * 77
    umlal   v4.8h, v1.8b, v17.8b  // + G * 150
    umlal   v4.8h, v2.8b, v18.8b  // + B * 29
    shrn    v5.8b, v4.8h, #8      // >> 8 (256 で割る)

    st1     {v5.8b}, [x1], #8    // グレースケール結果を保存
    subs    w2, w2, #8
    b.gt    .Lpixel_loop
    ret

12.5 RISC-V ベクトル拡張 (RVV)

# RISC-V ベクトル拡張 (RVV 1.0)
# 特徴: ベクトル長非依存 (VLA) プログラミングモデル
# ハードウェアが実際のベクトル長 (VLEN) を決定

# ベクトル加算: void vadd(float *a, float *b, float *c, int n)
# a0 = a, a1 = b, a2 = c, a3 = n
.global vadd_rvv
vadd_rvv:
    vsetvli   t0, a3, e32, m1     # ベクトル長を設定
                                   # e32=32bit要素, m1=LMUL=1
.Lloop:
    vle32.v   v0, (a0)            # a[] からベクトルロード
    vle32.v   v1, (a1)            # b[] からベクトルロード
    vfadd.vv  v2, v0, v1          # v2 = v0 + v1
    vse32.v   v2, (a2)            # c[] へベクトルストア

    # ポインタとカウンタの更新
    slli      t1, t0, 2           # t1 = vl * 4 (sizeof(float))
    add       a0, a0, t1
    add       a1, a1, t1
    add       a2, a2, t1
    sub       a3, a3, t0          # n -= vl
    bnez      a3, .Lloop
    ret

13. インラインアセンブリ

13.1 GCC/Clang のインラインアセンブリ

インラインアセンブリは、C/C++ のソースコード内にアセンブリ命令を埋め込む機能である。コンパイラの最適化を活用しつつ、特定の部分だけをアセンブリで記述できる。

// ============ 基本構文 (GCC Extended Asm) ============
// asm volatile (
//     "アセンブリテンプレート"
//     : 出力オペランド
//     : 入力オペランド
//     : クロバーリスト (破壊されるレジスタ)
// );

// 制約文字:
// "r" = 汎用レジスタ
// "a" = RAX, "b" = RBX, "c" = RCX, "d" = RDX
// "S" = RSI, "D" = RDI
// "m" = メモリオペランド
// "i" = 即値
// "=" = 出力専用, "+" = 入出力, "&" = 早期クロバー
// "x" = SSE レジスタ

#include <stdint.h>

// ============ 例1: RDTSC (タイムスタンプカウンタ読み取り) ============
static inline uint64_t rdtsc(void) {
    uint32_t lo, hi;
    asm volatile (
        "rdtsc"
        : "=a" (lo), "=d" (hi)  // 出力: EAX → lo, EDX → hi
        :                        // 入力: なし
        :                        // クロバー: なし
    );
    return ((uint64_t)hi << 32) | lo;
}

// ============ 例2: CPUID ============
static inline void cpuid(uint32_t leaf, uint32_t *eax, uint32_t *ebx,
                          uint32_t *ecx, uint32_t *edx) {
    asm volatile (
        "cpuid"
        : "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=d" (*edx)
        : "a" (leaf), "c" (0)
        :
    );
}

// ============ 例3: アトミック CAS (Compare And Swap) ============
static inline int atomic_cas(volatile int64_t *ptr, int64_t expected,
                              int64_t desired) {
    int64_t old_val;
    int success;
    asm volatile (
        "lock cmpxchgq %[desired], %[ptr]"
        : "=a" (old_val),              // RAX に古い値
          [ptr] "+m" (*ptr),           // メモリオペランド (入出力)
          "=@ccz" (success)            // ZF フラグ → success
        : "a" (expected),              // RAX に期待値をセット
          [desired] "r" (desired)      // レジスタに新しい値
        : "memory", "cc"              // メモリとフラグを破壊
    );
    return success;
}

// ============ 例4: ビット操作 ============
static inline int count_leading_zeros(uint64_t x) {
    uint64_t result;
    asm (
        "lzcntq %1, %0"
        : "=r" (result)
        : "r" (x)
        : "cc"
    );
    return (int)result;
}

// ============ 例5: メモリバリア ============
static inline void memory_fence(void) {
    asm volatile ("mfence" ::: "memory");
}

static inline void store_fence(void) {
    asm volatile ("sfence" ::: "memory");
}

static inline void load_fence(void) {
    asm volatile ("lfence" ::: "memory");
}

13.2 AArch64 のインラインアセンブリ

// ============ AArch64 インラインアセンブリ ============

// レジスタ制約:
// "r" = 汎用レジスタ (Xn/Wn)
// "w" = 浮動小数点/SIMD レジスタ (Vn/Dn/Sn)
// "m" = メモリ
// "i" = 即値

// 例1: サイクルカウンタの読み取り
static inline uint64_t read_cycle_counter(void) {
    uint64_t val;
    asm volatile (
        "mrs %0, cntvct_el0"    // Virtual Timer Count register
        : "=r" (val)
    );
    return val;
}

// 例2: キャッシュラインのフラッシュ
static inline void cache_flush(void *addr) {
    asm volatile (
        "dc civac, %0\n\t"     // Clean & Invalidate by VA to PoC
        "dsb sy\n\t"           // Data Synchronization Barrier
        "isb"                  // Instruction Synchronization Barrier
        :
        : "r" (addr)
        : "memory"
    );
}

// 例3: CAS (Compare And Swap) - ARMv8.1 LSE 拡張
static inline int64_t atomic_cas_arm(volatile int64_t *ptr,
                                      int64_t expected, int64_t desired) {
    int64_t old_val = expected;
    asm volatile (
        "casal %0, %2, [%1]"    // Compare And Swap with Acquire-Release
        : "+r" (old_val)         // 入出力: 古い値 / 期待値
        : "r" (ptr), "r" (desired)
        : "memory"
    );
    return old_val;
}

// 例4: NEON を使ったベクトル演算
#include <arm_neon.h>

float neon_horizontal_sum(float32x4_t v) {
    float result;
    asm (
        "faddp %0.4s, %1.4s, %1.4s\n\t"
        "faddp %s0, %0.2s"
        : "=w" (result)           // "w" = SIMD レジスタ
        : "w" (v)
    );
    return result;
}

13.3 コンパイラ組み込み関数 (Intrinsics)

インラインアセンブリの代替として、コンパイラが提供する組み込み関数(intrinsics)を使う方法がある。コンパイラが最適化できるため、一般的にはこちらが推奨される。

// ============ x86-64 SSE/AVX Intrinsics ============
#include <immintrin.h>

// SSE: 4つの float のドット積
float dot_product_sse(const float *a, const float *b, int n) {
    __m128 sum = _mm_setzero_ps();     // sum = [0, 0, 0, 0]

    int i;
    for (i = 0; i + 4 <= n; i += 4) {
        __m128 va = _mm_loadu_ps(a + i);  // a[i..i+3] をロード
        __m128 vb = _mm_loadu_ps(b + i);  // b[i..i+3] をロード
        __m128 prod = _mm_mul_ps(va, vb); // 要素ごとの乗算
        sum = _mm_add_ps(sum, prod);      // 部分和に加算
    }

    // 水平加算
    sum = _mm_hadd_ps(sum, sum);
    sum = _mm_hadd_ps(sum, sum);

    float result;
    _mm_store_ss(&result, sum);

    // 残り要素
    for (; i < n; i++) {
        result += a[i] * b[i];
    }
    return result;
}

// AVX2: 8つの int32 の加算
#include <immintrin.h>

void add_arrays_avx2(const int *a, const int *b, int *c, int n) {
    int i;
    for (i = 0; i + 8 <= n; i += 8) {
        __m256i va = _mm256_loadu_si256((__m256i*)(a + i));
        __m256i vb = _mm256_loadu_si256((__m256i*)(b + i));
        __m256i vc = _mm256_add_epi32(va, vb);
        _mm256_storeu_si256((__m256i*)(c + i), vc);
    }
    for (; i < n; i++) {
        c[i] = a[i] + b[i];
    }
}

// ============ ARM NEON Intrinsics ============
#include <arm_neon.h>

float dot_product_neon(const float *a, const float *b, int n) {
    float32x4_t sum = vdupq_n_f32(0.0f);

    int i;
    for (i = 0; i + 4 <= n; i += 4) {
        float32x4_t va = vld1q_f32(a + i);
        float32x4_t vb = vld1q_f32(b + i);
        sum = vmlaq_f32(sum, va, vb);    // Multiply-Accumulate
    }

    // 水平加算
    float result = vaddvq_f32(sum);

    for (; i < n; i++) {
        result += a[i] * b[i];
    }
    return result;
}

13.4 コンパイラ出力の確認

# GCC/Clang でアセンブリ出力を生成
gcc -S -O2 -o output.s input.c           # AT&T 構文
gcc -S -O2 -masm=intel -o output.s input.c  # Intel 構文
clang -S -O2 -o output.s input.c

# 特定の ISA 拡張を有効化
gcc -S -O2 -mavx2 -o output.s input.c
gcc -S -O2 -march=native -o output.s input.c  # 現在のCPU向け

# objdump で逆アセンブル
objdump -d -M intel binary              # Intel 構文
objdump -d binary                        # AT&T 構文

# LLVM の llvm-objdump
llvm-objdump -d --x86-asm-syntax=intel binary

# Compiler Explorer (Godbolt) をローカルで使う代わりに
# https://godbolt.org/ でリアルタイムにコンパイラ出力を確認可能

14. アセンブラツールの設定と使い方

14.1 NASM (Netwide Assembler)

# インストール
# Ubuntu/Debian
sudo apt install nasm

# macOS
brew install nasm

# バージョン確認
nasm --version
# NASM version 2.16.01

# 基本的な使い方
nasm -f elf64 -o hello.o hello.asm     # Linux x86-64 (ELF)
nasm -f macho64 -o hello.o hello.asm   # macOS x86-64 (Mach-O)
nasm -f win64 -o hello.obj hello.asm   # Windows x86-64 (PE/COFF)

# デバッグ情報付き
nasm -f elf64 -g -F dwarf -o hello.o hello.asm

# プリプロセッサの使用
nasm -f elf64 -DDEBUG -o hello.o hello.asm    # マクロ定義
nasm -f elf64 -I./include/ -o hello.o hello.asm  # インクルードパス

# リスティング出力
nasm -f elf64 -l listing.lst -o hello.o hello.asm
; NASM 固有のディレクティブ
[BITS 64]                    ; 64ビットモード
[DEFAULT REL]                ; デフォルトで RIP 相対アドレッシング
[SECTION .text]              ; セクション指定

; NASM のマクロシステム
%macro function_prologue 0
    push rbp
    mov  rbp, rsp
%endmacro

%macro function_epilogue 0
    leave
    ret
%endmacro

; 条件付きアセンブル
%ifdef __MACHO__             ; macOS Mach-O
    %define SYS_WRITE 0x2000004
%elifdef __ELF__             ; Linux ELF
    %define SYS_WRITE 1
%endif

14.2 GAS (GNU Assembler)

# GAS は binutils に含まれる(通常プリインストール済み)

# 基本的な使い方
as -o hello.o hello.s                  # デフォルト
as --64 -o hello.o hello.s            # 64ビットモード明示
as -g -o hello.o hello.s              # デバッグ情報付き

# クロスアセンブル
aarch64-linux-gnu-as -o hello.o hello.s        # AArch64 Linux
riscv64-linux-gnu-as -o hello.o hello.s        # RISC-V 64

# macOS では clang 内蔵のアセンブラを使用
clang -c hello.s -o hello.o
# または
as -arch arm64 -o hello.o hello.s     # macOS ARM64

14.3 LLVM-MC (LLVM Machine Code)

# LLVM-MC は LLVM プロジェクトのアセンブラ/逆アセンブラ

# アセンブル
llvm-mc -filetype=obj -triple=x86_64-linux-gnu -o hello.o hello.s
llvm-mc -filetype=obj -triple=aarch64-linux-gnu -o hello.o hello.s

# 逆アセンブル
llvm-mc -disassemble -triple=x86_64 <<< "0x48 0x89 0xc1"
# 出力: movq %rax, %rcx

# エンコーディング確認
llvm-mc -show-encoding -triple=x86_64 <<< "mov rax, rbx"
# 出力: movq %rbx, %rax  # encoding: [0x48,0x89,0xd8]

# macOS での使用 (Xcode に含まれる)
xcrun llvm-mc -filetype=obj -triple=arm64-apple-macos -o hello.o hello.s

14.4 リンク

# ============ Linux x86-64 ============
# 静的リンク (libc なし)
ld -o hello hello.o

# 動的リンク (libc 使用)
ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 \
   -o hello hello.o -lc

# GCC 経由でリンク (推奨)
gcc -no-pie -o hello hello.o            # 非PIE
gcc -o hello hello.o                     # PIE (デフォルト)

# ============ macOS x86-64 ============
ld -o hello hello.o -lSystem \
   -syslibroot $(xcrun --show-sdk-path) \
   -e _main -arch x86_64

# ============ macOS ARM64 ============
ld -o hello hello.o -lSystem \
   -syslibroot $(xcrun --show-sdk-path) \
   -e _main -arch arm64

# clang 経由 (推奨)
clang -o hello hello.o

# ============ RISC-V クロスコンパイル ============
riscv64-linux-gnu-ld -o hello hello.o
riscv64-linux-gnu-gcc -o hello hello.o

14.5 Makefile の例

# ============ Linux x86-64 (NASM) ============
AS      = nasm
ASFLAGS = -f elf64 -g -F dwarf
LD      = ld
LDFLAGS =
CC      = gcc

SRCS    = $(wildcard *.asm)
OBJS    = $(SRCS:.asm=.o)
TARGET  = program

.PHONY: all clean

all: $(TARGET)

$(TARGET): $(OBJS)
	$(LD) $(LDFLAGS) -o $@ $^

%.o: %.asm
	$(AS) $(ASFLAGS) -o $@ $<

clean:
	rm -f $(OBJS) $(TARGET)

# ============ macOS ARM64 (GAS) ============
# Makefile.arm64
AS      = as
ASFLAGS = -arch arm64
LD      = ld
LDFLAGS = -lSystem -syslibroot $(shell xcrun --show-sdk-path) -arch arm64
CC      = clang

SRCS    = $(wildcard *.s)
OBJS    = $(SRCS:.s=.o)
TARGET  = program

all: $(TARGET)

$(TARGET): $(OBJS)
	$(LD) $(LDFLAGS) -e _main -o $@ $^

%.o: %.s
	$(AS) $(ASFLAGS) -o $@ $<

clean:
	rm -f $(OBJS) $(TARGET)

# ============ C とアセンブリの混合プロジェクト ============
# Makefile.mixed
CC      = gcc
AS      = nasm
CFLAGS  = -O2 -Wall -g
ASFLAGS = -f elf64 -g -F dwarf

C_SRCS  = main.c utils.c
S_SRCS  = fast_math.asm simd_ops.asm
C_OBJS  = $(C_SRCS:.c=.o)
S_OBJS  = $(S_SRCS:.asm=.o)
TARGET  = mixed_program

all: $(TARGET)

$(TARGET): $(C_OBJS) $(S_OBJS)
	$(CC) $(CFLAGS) -o $@ $^

%.o: %.c
	$(CC) $(CFLAGS) -c -o $@ $<

%.o: %.asm
	$(AS) $(ASFLAGS) -o $@ $<

clean:
	rm -f $(C_OBJS) $(S_OBJS) $(TARGET)

14.6 CMake での設定

# CMakeLists.txt - アセンブリとCの混合プロジェクト
cmake_minimum_required(VERSION 3.20)
project(AsmProject LANGUAGES C ASM_NASM)

# NASM の設定
set(CMAKE_ASM_NASM_OBJECT_FORMAT elf64)
set(CMAKE_ASM_NASM_FLAGS "-g -F dwarf")

# ソースファイル
set(C_SOURCES
    src/main.c
    src/utils.c
)

set(ASM_SOURCES
    src/fast_math.asm
    src/simd_ops.asm
)

# ターゲット
add_executable(myprogram ${C_SOURCES} ${ASM_SOURCES})
target_include_directories(myprogram PRIVATE include)
target_compile_options(myprogram PRIVATE
    $<$<COMPILE_LANGUAGE:C>:-O2 -Wall -mavx2>
)

15. リンカとオブジェクトファイルフォーマット

15.1 オブジェクトファイルフォーマットの概要

フォーマットプラットフォーム拡張子ツール
ELFLinux, FreeBSD, Solaris.o, .so, (なし)readelf, objdump
Mach-OmacOS, iOS.o, .dylibotool, nm, objdump
PE/COFFWindows.obj, .exe, .dlldumpbin, objdump

15.2 ELF (Executable and Linkable Format)

ELF ファイル構造:
┌──────────────────────┐
│    ELF ヘッダー       │  マジックナンバー, アーキテクチャ, エントリポイント
├──────────────────────┤
│  プログラムヘッダー    │  実行時のセグメント情報 (実行ファイル/共有ライブラリ)
│  テーブル             │
├──────────────────────┤
│    .text             │  実行可能コード
├──────────────────────┤
│    .rodata           │  読み取り専用データ (定数文字列など)
├──────────────────────┤
│    .data             │  初期化済みグローバル/静的変数
├──────────────────────┤
│    .bss              │  未初期化データ (ゼロ初期化)
├──────────────────────┤
│    .symtab           │  シンボルテーブル
├──────────────────────┤
│    .strtab           │  文字列テーブル
├──────────────────────┤
│    .rel.text         │  再配置情報
├──────────────────────┤
│  セクションヘッダー    │  各セクションのメタデータ
│  テーブル             │
└──────────────────────┘
# ELF ファイルの解析
readelf -h hello.o           # ELF ヘッダー
readelf -S hello.o           # セクション一覧
readelf -s hello.o           # シンボルテーブル
readelf -r hello.o           # 再配置テーブル
readelf -l hello              # プログラムヘッダー (実行ファイル)

# objdump で逆アセンブル
objdump -d -M intel hello    # コードの逆アセンブル
objdump -t hello             # シンボルテーブル
objdump -x hello             # 全ヘッダー情報

# nm でシンボル一覧
nm hello.o
# T = テキスト(コード)セクションの定義シンボル
# D = データセクションの定義シンボル
# U = 未定義シンボル(外部参照)
# B = BSS セクション

15.3 Mach-O (macOS)

Mach-O ファイル構造:
┌──────────────────────┐
│    Mach-O ヘッダー    │  マジックナンバー, CPU タイプ, ファイルタイプ
├──────────────────────┤
│  ロードコマンド       │  セグメント定義, シンボル情報, 動的リンク情報
├──────────────────────┤
│  __TEXT セグメント     │
│    __text            │  実行可能コード
│    __stubs           │  PLT スタブ
│    __stub_helper     │  遅延バインディングヘルパー
│    __cstring         │  C 文字列定数
├──────────────────────┤
│  __DATA セグメント     │
│    __data            │  初期化済みデータ
│    __bss             │  未初期化データ
│    __la_symbol_ptr   │  遅延バインディングポインタ
│    __got             │  Global Offset Table
├──────────────────────┤
│  __LINKEDIT セグメント │  シンボル, 文字列, 再配置情報
└──────────────────────┘
# macOS での解析ツール
otool -h hello.o             # Mach-O ヘッダー
otool -l hello               # ロードコマンド
otool -tv hello              # テキストセクションの逆アセンブル
otool -L hello               # 動的ライブラリの依存関係

# nm (macOS 版)
nm hello.o
nm -m hello                  # Mach-O 形式で表示

# LLVM ツール
llvm-objdump -d hello        # 逆アセンブル
llvm-nm hello.o              # シンボル一覧
llvm-readobj --headers hello # ヘッダー情報

15.4 リンクの仕組み

コンパイル・リンクの流れ:

main.c ──→ [コンパイラ] ──→ main.o ─┐
                                    │
utils.c ──→ [コンパイラ] ──→ utils.o ┼──→ [リンカ] ──→ program (実行ファイル)
                                    │
math.asm ──→ [アセンブラ] ──→ math.o ┘
                                    ↑
                              libc.so (共有ライブラリ)

シンボル解決

; file1.asm
section .text
global my_function          ; 外部に公開するシンボル
extern printf               ; 外部シンボルの参照

my_function:
    ; printf を呼び出す
    lea    rdi, [rel fmt]
    xor    eax, eax
    call   printf wrt ..plt ; PLT 経由の呼び出し
    ret

section .rodata
fmt: db "Hello from asm", 10, 0
// file2.c
extern void my_function(void);  // アセンブリで定義された関数

int main(void) {
    my_function();               // リンカがシンボルを解決
    return 0;
}
# コンパイルとリンク
nasm -f elf64 -o file1.o file1.asm
gcc -c -o file2.o file2.c
gcc -o program file1.o file2.o   # リンカが my_function と printf を解決

15.5 位置独立コード (PIC/PIE)

; 位置独立コード (Position-Independent Code)
; ASLR (Address Space Layout Randomization) に必要

; PIC でのグローバル変数アクセス
section .text
global get_global_var

get_global_var:
    ; RIP 相対アドレッシング (x86-64 のPICの基本)
    mov    eax, [rel my_global]   ; NASM: rel キーワード
    ret

; PIC での外部関数呼び出し
call_external:
    ; PLT (Procedure Linkage Table) 経由
    call   printf wrt ..plt       ; NASM
    ; GAS: call printf@PLT

; GOT (Global Offset Table) 経由のアクセス
    mov    rax, [rel my_extern wrt ..got]  ; GOT エントリのアドレス
    mov    rax, [rax]                       ; 実際のアドレスをロード
# PIE (Position-Independent Executable) のビルド
gcc -pie -o program main.o asm_module.o      # PIE (デフォルト)
gcc -no-pie -o program main.o asm_module.o   # 非PIE

# 共有ライブラリの作成
nasm -f elf64 -o mylib.o mylib.asm
gcc -shared -o libmylib.so mylib.o

# 使用
gcc -o program main.c -L. -lmylib
LD_LIBRARY_PATH=. ./program

16. デバッグ手法

16.1 GDB でのアセンブリデバッグ

# GDB の起動
gdb ./program

# Intel 構文に設定(推奨)
(gdb) set disassembly-flavor intel
# .gdbinit に追加しておくと便利:
# echo "set disassembly-flavor intel" >> ~/.gdbinit
# ============ GDB 基本コマンド ============

# ブレークポイント
(gdb) break main                # 関数にブレークポイント
(gdb) break *0x401000           # アドレスにブレークポイント
(gdb) break *main+20            # main+20バイトの位置
(gdb) info breakpoints          # ブレークポイント一覧

# 実行制御
(gdb) run                       # プログラム開始
(gdb) run arg1 arg2             # 引数付きで開始
(gdb) continue                  # 続行
(gdb) stepi (si)                # 1命令ステップ実行(関数に入る)
(gdb) nexti (ni)                # 1命令ステップ実行(関数をスキップ)
(gdb) finish                    # 現在の関数を最後まで実行
(gdb) until *0x401050           # 指定アドレスまで実行

# 逆アセンブル
(gdb) disassemble               # 現在の関数
(gdb) disassemble main          # main 関数
(gdb) disassemble /r main       # バイトコード付き
(gdb) disassemble 0x401000,0x401050  # アドレス範囲
(gdb) x/10i $rip               # 現在のRIPから10命令表示

# レジスタ表示
(gdb) info registers            # 全レジスタ
(gdb) info registers rax rbx    # 特定レジスタ
(gdb) print $rax                # RAX の値
(gdb) print/x $rax              # 16進数で表示
(gdb) print/t $rax              # 2進数で表示
(gdb) info registers xmm0       # SIMD レジスタ

# フラグレジスタ
(gdb) print $eflags             # フラグレジスタ
# 出力例: [ CF PF ZF SF IF ]

# メモリ表示 (x コマンド)
(gdb) x/4xg $rsp               # スタックトップから 4 x 8バイト (16進数)
(gdb) x/16xb $rsp              # 16バイト表示
(gdb) x/s 0x402000             # 文字列として表示
(gdb) x/10i $rip               # 命令として表示
(gdb) x/4xw $rsp               # 4 x 4バイト (ワード)

# x コマンドのフォーマット: x/[数][フォーマット][サイズ]
# フォーマット: x(16進), d(10進), u(符号なし), o(8進), t(2進), s(文字列), i(命令)
# サイズ: b(byte), h(halfword), w(word=4byte), g(giant=8byte)

# メモリ書き換え
(gdb) set *0x7fffffffde00 = 42  # メモリに値を書き込み
(gdb) set $rax = 0xFF           # レジスタに値をセット

# スタックの確認
(gdb) backtrace (bt)            # コールスタック
(gdb) info frame                # 現在のスタックフレーム
(gdb) info locals               # ローカル変数

# ウォッチポイント
(gdb) watch *0x404000           # メモリアドレスの変更を監視
(gdb) rwatch *0x404000          # 読み取りを監視
(gdb) awatch *0x404000          # 読み書き両方を監視

16.2 GDB TUI モード

# TUI (Text User Interface) モード
(gdb) layout asm                # アセンブリビュー
(gdb) layout regs               # レジスタ + アセンブリ
(gdb) layout split              # ソース + アセンブリ
(gdb) tui reg general           # 汎用レジスタウィンドウ
(gdb) tui reg float             # 浮動小数点レジスタ
(gdb) focus asm                 # アセンブリウィンドウにフォーカス

# GDB ダッシュボード (gdb-dashboard) もおすすめ
# https://github.com/cyrus-and/gdb-dashboard

16.3 LLDB でのアセンブリデバッグ (macOS)

# LLDB の起動
lldb ./program

# ============ LLDB コマンド ============

# ブレークポイント
(lldb) breakpoint set --name main          # 関数名
(lldb) b main                               # 短縮形
(lldb) breakpoint set --address 0x100003f50 # アドレス
(lldb) b -a 0x100003f50

# 実行制御
(lldb) run
(lldb) process launch -- arg1 arg2
(lldb) continue (c)
(lldb) thread step-inst (si)     # 1命令ステップ
(lldb) thread step-inst-over (ni) # 1命令ステップオーバー

# 逆アセンブル
(lldb) disassemble --frame        # 現在のフレーム
(lldb) di -f                      # 短縮形
(lldb) disassemble --name main    # 関数指定
(lldb) disassemble --start-address 0x100003f50 --count 20
(lldb) di -s 0x100003f50 -c 20

# レジスタ
(lldb) register read              # 全レジスタ
(lldb) register read rax rbx      # 特定レジスタ
(lldb) register read --all        # SIMD レジスタ含む全て
(lldb) register write rax 0x42    # レジスタに書き込み

# メモリ
(lldb) memory read $rsp           # スタックの内容
(lldb) memory read --size 8 --format x --count 4 $rsp
(lldb) x -s8 -fx -c4 $rsp        # 短縮形
(lldb) memory read --format s 0x100004000  # 文字列

# AArch64 固有
(lldb) register read x0 x1 x29 x30 sp pc
(lldb) register read cpsr          # 条件フラグ
(lldb) register read v0 v1         # NEON レジスタ

16.4 デバッグの実践テクニック

# ============ コアダンプの解析 ============
# コアダンプを有効化
ulimit -c unlimited

# クラッシュしたプログラムのコアダンプを GDB で解析
gdb ./program core
(gdb) bt                        # クラッシュ時のスタックトレース
(gdb) info registers            # クラッシュ時のレジスタ
(gdb) x/10i $rip-20            # クラッシュ前後のコード

# ============ strace/dtrace との併用 ============
# システムコールのトレース (Linux)
strace ./program
strace -e trace=write,read ./program  # 特定のシステムコールのみ

# macOS の場合
dtruss ./program

# ============ Valgrind でのメモリエラー検出 ============
valgrind --tool=memcheck ./program
valgrind --tool=callgrind ./program   # プロファイリング

# ============ perf でのパフォーマンス分析 (Linux) ============
perf stat ./program                    # 基本統計
perf record -g ./program               # サンプリング
perf report                            # レポート表示
perf annotate --stdio -s function_name # 関数のアセンブリレベル分析

17. 最適化テクニック

17.1 パイプライン最適化

; ============ 命令レベル並列性 (ILP) の向上 ============

; 悪い例: データ依存のチェーン (各命令が前の結果に依存)
add    rax, rbx       ; rax = rax + rbx
add    rax, rcx       ; rax に依存 → パイプラインストール
add    rax, rdx       ; rax に依存 → パイプラインストール
; レイテンシ: 3サイクル (直列実行)

; 良い例: 依存関係を分散
add    rax, rbx       ; 独立した加算
add    rcx, rdx       ; rax に依存しない → 並列実行可能
add    rax, rcx       ; 最後に統合
; レイテンシ: 2サイクル (add rax,rbx と add rcx,rdx が並列実行)

; ============ ループ展開 (Loop Unrolling) ============

; 展開前
.loop:
    add    rax, [rdi]
    add    rdi, 8
    dec    ecx
    jnz    .loop

; 4倍展開
    shr    ecx, 2          ; n / 4
.loop4:
    add    rax, [rdi]
    add    rax, [rdi + 8]
    add    rax, [rdi + 16]
    add    rax, [rdi + 24]
    add    rdi, 32
    dec    ecx
    jnz    .loop4
; 利点: 分岐予測ミスの頻度が1/4に、ループオーバーヘッド削減

; さらなる最適化: アキュムレータを分散して依存チェーンを分割
    xor    eax, eax        ; sum0 = 0
    xor    r8d, r8d        ; sum1 = 0
    xor    r9d, r9d        ; sum2 = 0
    xor    r10d, r10d      ; sum3 = 0
    shr    ecx, 2
.loop4_parallel:
    add    rax, [rdi]
    add    r8,  [rdi + 8]
    add    r9,  [rdi + 16]
    add    r10, [rdi + 24]
    add    rdi, 32
    dec    ecx
    jnz    .loop4_parallel
    add    rax, r8
    add    r9, r10
    add    rax, r9          ; total = sum0 + sum1 + sum2 + sum3

17.2 キャッシュ最適化

; ============ キャッシュラインサイズの考慮 ============
; 典型的なキャッシュラインサイズ: 64バイト

; ソフトウェアプリフェッチ
prefetcht0  [rdi + 256]    ; L1 キャッシュにプリフェッチ
prefetcht1  [rdi + 512]    ; L2 キャッシュにプリフェッチ
prefetchnta [rdi + 256]    ; Non-Temporal Access (キャッシュ汚染を回避)

; ストリーミングストア (キャッシュをバイパス)
; 大量データの書き込み時にキャッシュ汚染を回避
vmovntps [rdi], ymm0       ; Non-Temporal Store (AVX)
vmovntdq [rdi], ymm0       ; Non-Temporal Store (整数)
; ※ ストア後に sfence が必要
sfence

; キャッシュラインアラインメント
section .data
align 64                    ; 64バイトアラインメント (キャッシュライン境界)
my_array: times 1024 dq 0
// C でのキャッシュフレンドリーなアクセスパターン
// 悪い例: 列優先アクセス (キャッシュミス多発)
for (int j = 0; j < N; j++)
    for (int i = 0; i < N; i++)
        sum += matrix[i][j];  // 行をまたいでアクセス → キャッシュミス

// 良い例: 行優先アクセス (キャッシュフレンドリー)
for (int i = 0; i < N; i++)
    for (int j = 0; j < N; j++)
        sum += matrix[i][j];  // 連続メモリアクセス → キャッシュヒット

17.3 分岐予測の最適化

; ============ 分岐予測ミスの回避 ============

; 方法1: CMOV を使った分岐レス化
; if (a > b) max = a; else max = b;
    cmp    rdi, rsi
    mov    rax, rsi          ; max = b
    cmovg  rax, rdi          ; a > b なら max = a
    ; 分岐なし → 分岐予測ミスペナルティなし

; 方法2: SETcc + 算術
; result = (x == 0) ? a : b
    test   rdi, rdi
    setz   al                ; al = (x == 0) ? 1 : 0
    movzx  eax, al
    ; eax を使って条件に応じた値を計算

; 方法3: ビット演算による条件選択
; mask = (a > b) ? -1 : 0
    cmp    rdi, rsi
    sbb    rax, rax          ; CF=1 なら rax=-1, CF=0 なら rax=0
    ; max = (a & ~mask) | (b & mask) のように使用

; ============ 分岐のヒント (Likely/Unlikely) ============
; GCC __builtin_expect に相当するアセンブリヒント
; x86: Intel Pentium 4 以降、静的分岐予測ヒント (0x2E: not taken, 0x3E: taken)
; ただし現代のCPUでは通常無視される。コンパイラに任せるのが最善。

17.4 命令選択の最適化

; ============ 効率的な命令の選択 ============

; ゼロクリア
xor    eax, eax             ; 2バイト、依存関係破壊 (最速)
; mov rax, 0 は 7バイト。sub rax, rax は依存関係あり

; 2のべき乗による乗算
shl    rax, 3               ; rax *= 8
lea    rax, [rax + rax*2]   ; rax *= 3
lea    rax, [rax*4 + rax]   ; rax *= 5

; 2のべき乗による除算 (符号なし)
shr    rax, 3               ; rax /= 8

; 2のべき乗による除算 (符号付き、丸め補正付き)
; C: int result = x / 8;  (x が負の場合の切り捨て方向が異なる)
mov    rcx, rax
sar    rcx, 63              ; rcx = (x < 0) ? -1 : 0
shr    rcx, 61              ; rcx = (x < 0) ? 7 : 0
add    rax, rcx             ; 負の値に対する丸め補正
sar    rax, 3               ; /8

; 定数による乗算の最適化 (コンパイラが自動生成する例)
; x * 10 = x * 8 + x * 2 = (x << 3) + (x << 1)
lea    rax, [rdi + rdi*4]   ; rax = rdi * 5
shl    rax, 1               ; rax = rdi * 10

; x * 7 = x * 8 - x
lea    rax, [rdi*8]
sub    rax, rdi             ; rax = rdi * 7

; ============ アラインメント ============
; ループ先頭を16バイト境界にアラインメント
align 16
.hot_loop:
    ; 頻繁に実行される命令
    jnz    .hot_loop

17.5 メモリアクセスの最適化

; ============ アラインメントの重要性 ============
; アラインされたアクセスは高速
movaps  xmm0, [rdi]          ; 16バイトアラインメント必須 (高速)
movups  xmm0, [rdi]          ; アラインメント不要 (やや遅い可能性)
; 現代のCPUではキャッシュライン境界をまたがなければ差は小さい

; ============ メモリコピーの最適化 ============
; rep movsb (Enhanced REP MOVSB, ERMS 対応CPUで高速)
my_memcpy:
    mov    rcx, rdx          ; 長さ
    ; rdi = dest (呼び出し規約で設定済み)
    ; rsi = src (呼び出し規約で設定済み)
    rep    movsb             ; バイト単位コピー (ERMS対応なら高速)
    ret

; ============ メモリゼロクリアの最適化 ============
my_memset_zero:
    mov    rcx, rsi          ; 長さ
    xor    eax, eax          ; 0
    rep    stosb             ; ゼロフィル
    ret

18. セキュリティ

18.1 バッファオーバーフロー

バッファオーバーフローは、確保されたバッファの境界を超えてデータを書き込む脆弱性であり、アセンブリレベルの理解が不可欠である。

// 脆弱なコード例
#include <string.h>

void vulnerable_function(char *input) {
    char buffer[64];
    strcpy(buffer, input);  // 境界チェックなし!
}
正常時のスタックレイアウト:
高アドレス
┌──────────────────┐
│  戻りアドレス      │ ← これを上書きされると任意コード実行
├──────────────────┤
│  保存された RBP    │
├──────────────────┤
│  buffer[56..63]   │
│  buffer[48..55]   │
│  ...              │
│  buffer[0..7]     │ ← バッファの先頭
├──────────────────┤ ← RSP
低アドレス

オーバーフロー時:
高アドレス
┌──────────────────┐
│  攻撃者の値!!!     │ ← 戻りアドレスを上書き → 任意コード実行
├──────────────────┤
│  AAAAAAAAAAAAA    │ ← RBP を上書き
├──────────────────┤
│  AAAAAAAAAAAAA    │
│  AAAAAAAAAAAAA    │  ← オーバーフローしたデータ
│  ...              │
│  入力データの先頭   │
├──────────────────┤
低アドレス

18.2 防御メカニズム

スタックカナリア (Stack Canary / Stack Protector)

; GCC -fstack-protector-strong が生成するコード

my_function:
    push   rbp
    mov    rbp, rsp
    sub    rsp, 80

    ; カナリア値をスタックに配置
    mov    rax, qword [fs:0x28]    ; スレッドローカルストレージからカナリア取得
    mov    qword [rbp - 8], rax    ; スタックに保存

    ; ... 関数本体 ...

    ; カナリアの検証
    mov    rax, qword [rbp - 8]    ; スタックからカナリア読み取り
    xor    rax, qword [fs:0x28]    ; 元の値と比較
    jne    .stack_smash_detected   ; 不一致ならスタック破壊を検出

    leave
    ret

.stack_smash_detected:
    call   __stack_chk_fail        ; abort() を呼び出して終了

ASLR (Address Space Layout Randomization)

# ASLR の確認と設定 (Linux)
cat /proc/sys/kernel/randomize_va_space
# 0 = 無効, 1 = スタック+ライブラリ, 2 = スタック+ライブラリ+ヒープ

# PIE (Position Independent Executable) が ASLR の完全な効果に必要
gcc -pie -o program program.c

NX ビット (No-Execute / DEP)

仮想メモリのページ属性:
┌──────────────────┬──────┬──────┬──────┐
│ セグメント        │ 読み  │ 書き  │ 実行  │
├──────────────────┼──────┼──────┼──────┤
│ .text            │  ✓   │  ✗   │  ✓   │  コード: 読み取り+実行可能
│ .rodata          │  ✓   │  ✗   │  ✗   │  定数: 読み取り専用
│ .data / .bss     │  ✓   │  ✓   │  ✗   │  データ: 読み書き、実行不可
│ スタック          │  ✓   │  ✓   │  ✗   │  NX: スタック上のコード実行を防止
│ ヒープ            │  ✓   │  ✓   │  ✗   │  NX: ヒープ上のコード実行を防止
└──────────────────┴──────┴──────┴──────┘

18.3 Return-Oriented Programming (ROP)

NX ビットによりスタック上のシェルコード実行が防止されたため、既存のコード断片(ガジェット)を連鎖させる攻撃手法が開発された。

ROP ガジェット:  ret で終わるコード断片

ガジェット1: pop rdi; ret    (引数をレジスタにセット)
ガジェット2: pop rsi; ret
ガジェット3: syscall; ret    (システムコール実行)

攻撃者が構築するスタック:
┌──────────────────────┐
│ ガジェット1のアドレス   │  ← 最初の戻りアドレスを上書き
├──────────────────────┤
│ "/bin/sh" のアドレス   │  ← pop rdi で RDI にロードされる
├──────────────────────┤
│ ガジェット2のアドレス   │  ← ガジェット1の ret で飛ぶ
├──────────────────────┤
│ 0                    │  ← pop rsi で RSI にロードされる
├──────────────────────┤
│ ガジェット3のアドレス   │
├──────────────────────┤
│ 59 (execve番号)       │  ← RAX にセットするガジェットが必要
└──────────────────────┘
# ROP ガジェットの検索ツール
# ROPgadget
ROPgadget --binary ./program
ROPgadget --binary ./program --only "pop|ret"
ROPgadget --binary /lib/x86_64-linux-gnu/libc.so.6

# ropper
ropper --file ./program --search "pop rdi; ret"

18.4 ROP に対する防御

// ============ AArch64 ポインタ認証 (PAC) ============
// Apple Silicon で積極的に活用されている

_my_function:
    // 関数エントリで LR に署名
    paciasp                    // LR を SP をキーにして署名

    stp    x29, x30, [sp, #-16]!
    mov    x29, sp

    // ... 関数本体 ...

    ldp    x29, x30, [sp], #16

    // リターン前に LR を検証
    autiasp                    // LR の署名を検証
    ret                        // 検証失敗時はフォールト

// PAC によって ROP 攻撃は困難になる:
// 戻りアドレスを改ざんすると署名の検証に失敗する
; ============ x86-64 Control Flow Integrity (CET) ============
; Intel CET: Shadow Stack + Indirect Branch Tracking

; Shadow Stack: CALL/RET の戻りアドレスを別のスタックにも保存
; RET 時に通常スタックとシャドウスタックの値を比較
; 不一致 → #CP (Control Protection) 例外

; Indirect Branch Tracking (IBT)
; 間接分岐の着地点に ENDBR64 命令が必要
endbr64                     ; 間接分岐の有効な着地点をマーク
; ENDBR64 がない場所への間接ジャンプ → #CP 例外

18.5 セキュアコーディングのベストプラクティス

; ============ センシティブデータのクリア ============
; 使用後に秘密鍵やパスワードをメモリから確実に消去

secure_clear:
    ; コンパイラの最適化で消去が削除されないよう volatile アクセスが必要
    ; アセンブリでは最適化の心配なし
    mov    rcx, rsi          ; 長さ
    xor    eax, eax
    rep    stosb             ; メモリをゼロクリア

    ; さらにキャッシュからもフラッシュ
    ; clflush [rdi] (必要に応じて)
    ret

; ============ 定数時間比較 (タイミング攻撃対策) ============
; 暗号処理で使用: 入力の値によらず一定時間で実行

; int constant_time_compare(const uint8_t *a, const uint8_t *b, size_t len)
constant_time_compare:
    ; rdi = a, rsi = b, rdx = len
    xor    eax, eax          ; result = 0
    xor    ecx, ecx          ; i = 0
.loop:
    cmp    rcx, rdx
    jge    .done
    movzx  r8d, byte [rdi + rcx]
    xor    r8b, byte [rsi + rcx]  ; 差異を検出
    or     al, r8b                 ; 差異を蓄積 (短絡評価しない)
    inc    rcx
    jmp    .loop
.done:
    ; al != 0 なら不一致、al == 0 なら一致
    test   al, al
    setnz  al
    movzx  eax, al
    ret
    ; 重要: 不一致を検出しても即座に return しない
    ; 常に全バイトを比較することで、タイミングから情報が漏れない

19. 実践例

19.1 Hello World プログラム(3アーキテクチャ比較)

x86-64 Linux (NASM)

; hello_x86.asm
section .data
    msg db "Hello, World!", 10
    msg_len equ $ - msg

section .text
global _start

_start:
    mov    rax, 1            ; sys_write
    mov    rdi, 1            ; stdout
    lea    rsi, [rel msg]
    mov    rdx, msg_len
    syscall

    mov    rax, 60           ; sys_exit
    xor    rdi, rdi
    syscall
nasm -f elf64 -o hello_x86.o hello_x86.asm
ld -o hello_x86 hello_x86.o
./hello_x86

AArch64 macOS

// hello_arm.s
.global _main
.align 4

_main:
    mov    x0, #1
    adrp   x1, msg@PAGE
    add    x1, x1, msg@PAGEOFF
    mov    x2, #14
    mov    x16, #4
    svc    #0x80

    mov    x0, #0
    mov    x16, #1
    svc    #0x80

.data
msg: .ascii "Hello, World!\n"
as -arch arm64 -o hello_arm.o hello_arm.s
ld -o hello_arm hello_arm.o -lSystem -syslibroot $(xcrun --show-sdk-path) -e _main -arch arm64
./hello_arm

RISC-V Linux

# hello_rv.s
.global _start

.text
_start:
    li     a7, 64
    li     a0, 1
    la     a1, msg
    li     a2, 14
    ecall

    li     a7, 93
    li     a0, 0
    ecall

.rodata
msg: .ascii "Hello, World!\n"
riscv64-linux-gnu-as -o hello_rv.o hello_rv.s
riscv64-linux-gnu-ld -o hello_rv hello_rv.o
qemu-riscv64 ./hello_rv

19.2 フィボナッチ数列(反復版)

; fibonacci.asm - x86-64 Linux
; uint64_t fibonacci(uint64_t n)
; 引数: rdi = n
; 戻り値: rax = fib(n)

section .text
global fibonacci

fibonacci:
    cmp    rdi, 1
    jbe    .base_case        ; n <= 1 なら n を返す

    xor    eax, eax          ; a = 0 (fib(0))
    mov    rcx, 1            ; b = 1 (fib(1))
    mov    rdx, 1            ; i = 1

.loop:
    mov    r8, rcx           ; temp = b
    add    rcx, rax          ; b = a + b
    mov    rax, r8           ; a = temp
    inc    rdx               ; i++
    cmp    rdx, rdi
    jb     .loop             ; i < n なら継続

    mov    rax, rcx          ; return b
    ret

.base_case:
    mov    rax, rdi          ; return n (0 or 1)
    ret
// main.c - テスト用
#include <stdio.h>
#include <stdint.h>

extern uint64_t fibonacci(uint64_t n);

int main(void) {
    for (uint64_t i = 0; i <= 20; i++) {
        printf("fib(%lu) = %lu\n", i, fibonacci(i));
    }
    return 0;
}
nasm -f elf64 -o fibonacci.o fibonacci.asm
gcc -o fib_test main.c fibonacci.o
./fib_test

19.3 文字列操作: strlen と memcpy

; string_ops.asm - x86-64
section .text

; size_t my_strlen(const char *s)
global my_strlen
my_strlen:
    mov    rax, rdi          ; ポインタを保存
.scan:
    cmp    byte [rdi], 0     ; NULL 文字チェック
    je     .found
    inc    rdi
    jmp    .scan
.found:
    sub    rdi, rax          ; 長さ = 現在位置 - 開始位置
    mov    rax, rdi
    ret

; SIMD を使った高速版 strlen
global my_strlen_sse
my_strlen_sse:
    mov     rax, rdi
    pxor    xmm0, xmm0       ; xmm0 = all zeros

    ; 16バイトアラインメントまで処理
    mov     rcx, rdi
    and     rcx, 15           ; アラインメントオフセット
    jz      .aligned

    ; 非アライン部分をバイト単位で処理
.byte_scan:
    cmp     byte [rdi], 0
    je      .done
    inc     rdi
    test    rdi, 15
    jnz     .byte_scan

.aligned:
    ; 16バイトずつ NULL バイトを検索
    movdqa  xmm1, [rdi]      ; 16バイトロード
    pcmpeqb xmm1, xmm0       ; 各バイトを0と比較
    pmovmskb ecx, xmm1        ; 結果のマスクビット
    test    ecx, ecx
    jnz     .found_in_chunk
    add     rdi, 16
    jmp     .aligned

.found_in_chunk:
    bsf     ecx, ecx          ; 最初のNULLバイトの位置
    add     rdi, rcx

.done:
    sub     rdi, rax
    mov     rax, rdi
    ret

; void *my_memcpy(void *dest, const void *src, size_t n)
global my_memcpy
my_memcpy:
    mov    rax, rdi           ; 戻り値 = dest
    mov    rcx, rdx           ; n
    rep    movsb              ; RDI←RSI をRCXバイトコピー
    ret

19.4 クイックソートの実装

; quicksort.asm - x86-64 System V ABI
; void quicksort(int64_t *arr, int64_t lo, int64_t hi)
; rdi = arr, rsi = lo, rdx = hi

section .text
global quicksort

quicksort:
    cmp    rsi, rdx
    jge    .done              ; lo >= hi なら終了

    ; スタックフレームと callee-saved レジスタの保存
    push   rbp
    mov    rbp, rsp
    push   rbx
    push   r12
    push   r13
    push   r14

    mov    r12, rdi           ; arr
    mov    r13, rsi           ; lo
    mov    r14, rdx           ; hi

    ; partition
    mov    rax, [r12 + r14*8] ; pivot = arr[hi]
    mov    rcx, r13           ; i = lo
    mov    rbx, r13           ; j = lo

.partition_loop:
    cmp    rbx, r14
    jge    .partition_done

    cmp    qword [r12 + rbx*8], rax  ; arr[j] <= pivot?
    jg     .skip_swap

    ; swap(arr[i], arr[j])
    mov    r8, [r12 + rcx*8]
    mov    r9, [r12 + rbx*8]
    mov    [r12 + rcx*8], r9
    mov    [r12 + rbx*8], r8
    inc    rcx                ; i++

.skip_swap:
    inc    rbx                ; j++
    jmp    .partition_loop

.partition_done:
    ; swap(arr[i], arr[hi])
    mov    r8, [r12 + rcx*8]
    mov    r9, [r12 + r14*8]
    mov    [r12 + rcx*8], r9
    mov    [r12 + r14*8], r8
    ; rcx = pivot index

    ; quicksort(arr, lo, pivot - 1)
    mov    rdi, r12
    mov    rsi, r13
    lea    rdx, [rcx - 1]
    push   rcx               ; pivot index を保存
    call   quicksort

    ; quicksort(arr, pivot + 1, hi)
    pop    rcx
    mov    rdi, r12
    lea    rsi, [rcx + 1]
    mov    rdx, r14
    call   quicksort

    pop    r14
    pop    r13
    pop    r12
    pop    rbx
    pop    rbp
.done:
    ret

19.5 逆アセンブルとリバースエンジニアリング

# ============ バイナリ解析の基本ワークフロー ============

# 1. ファイル種別の確認
file ./target_binary
# 出力例: ELF 64-bit LSB executable, x86-64, dynamically linked

# 2. シンボル情報の確認
nm ./target_binary | head -20
nm -D ./target_binary        # 動的シンボルのみ
strings ./target_binary | grep -i password  # 文字列検索

# 3. セクション情報
readelf -S ./target_binary

# 4. 逆アセンブル
objdump -d -M intel ./target_binary | less
# 特定の関数だけ逆アセンブル
objdump -d -M intel ./target_binary | grep -A 50 '<main>:'

# 5. 動的解析 (GDB)
gdb ./target_binary
(gdb) break main
(gdb) run
(gdb) disassemble
(gdb) info registers
(gdb) x/20i $rip

# 6. システムコールのトレース
strace ./target_binary 2>&1 | head -50
ltrace ./target_binary 2>&1 | head -50   # ライブラリ関数トレース

19.6 C コンパイラ出力の読解

// example.c
int sum_of_squares(int *arr, int n) {
    int sum = 0;
    for (int i = 0; i < n; i++) {
        sum += arr[i] * arr[i];
    }
    return sum;
}
gcc -O2 -S -masm=intel -o example.s example.c
; GCC -O2 が生成するコード (概要)
sum_of_squares:
    test   esi, esi           ; n == 0?
    jle    .L4                ; n <= 0 なら 0 を返す
    lea    eax, [rsi-1]       ; eax = n - 1
    xor    edx, edx           ; sum = 0
    lea    rcx, [rdi+rax*4+4] ; 配列の終端アドレス
.L3:
    mov    eax, [rdi]         ; arr[i]
    imul   eax, eax           ; arr[i] * arr[i]
    add    rdi, 4             ; ポインタを進める
    add    edx, eax           ; sum += arr[i]^2
    cmp    rdi, rcx           ; 終端に達したか?
    jne    .L3
    mov    eax, edx           ; return sum
    ret
.L4:
    xor    edx, edx           ; sum = 0
    mov    eax, edx
    ret

20. 現代の開発におけるアセンブリの役割

20.1 コンパイラ出力の読解とパフォーマンスチューニング

現代のソフトウェア開発において、アセンブリを「書く」よりも「読む」能力のほうがはるかに重要である。

# Compiler Explorer (Godbolt) の活用
# https://godbolt.org/
# リアルタイムでコンパイラの出力を確認できる Web ツール
# 対応コンパイラ: GCC, Clang, MSVC, ICC
# 対応アーキテクチャ: x86-64, ARM64, RISC-V, etc.

# ローカルでのコンパイラ出力確認
gcc -O2 -S -fverbose-asm -o output.s input.c
# -fverbose-asm: C のソースコードをコメントとして付加

# 特定の最適化の確認
gcc -O2 -ftree-vectorize -fopt-info-vec -S input.c
# ベクトル化の成功/失敗を報告

# clang の最適化レポート
clang -O2 -Rpass=loop-vectorize -Rpass-missed=loop-vectorize -S input.c

20.2 パフォーマンスプロファイリング

# ============ Linux perf ============
# ハードウェアパフォーマンスカウンタを使用した精密な分析

# 基本統計
perf stat ./program
# 出力例:
#  1,234,567,890  cycles
#  2,345,678,901  instructions  # IPC = 1.90
#       12,345    cache-misses
#        1,234    branch-misses

# サンプリングプロファイル
perf record -g ./program
perf report                    # インタラクティブレポート

# アセンブリレベルのアノテーション
perf annotate -s hot_function
# 各命令のサンプル数(実行頻度)が表示される

# ============ Apple Instruments (macOS) ============
# Time Profiler で CPU ホットスポットを特定
# Counters テンプレートでハードウェアカウンタを収集
xcrun xctrace record --template 'Time Profiler' --launch ./program

20.3 アセンブリが依然として必要な領域

カーネル / OS 開発

// Linux カーネルのコンテキストスイッチ (arch/x86/entry/entry_64.S より概念)
// ユーザーモード→カーネルモードの遷移は必然的にアセンブリ

// カーネルのスタック切り替え (概念的なコード)
// swapgs                    ; GS ベースをカーネル用に切り替え
// mov rsp, [gs:cpu_tss + TSS_sp0]  ; カーネルスタックに切り替え

暗号ライブラリ

// OpenSSL, BoringSSL などの暗号ライブラリは
// パフォーマンスとタイミング攻撃耐性のためにアセンブリで実装
// 例: AES-NI を使った AES 暗号化
// 例: SHA-256 の SIMD 実装

ブートローダー

; x86 ブートセクタ (MBR) の例 (概念)
; BIOS は最初のセクタ (512 バイト) を 0x7C00 にロードして実行

[BITS 16]
[ORG 0x7C00]

boot:
    cli                      ; 割り込み禁止
    xor    ax, ax
    mov    ds, ax
    mov    es, ax
    mov    ss, ax
    mov    sp, 0x7C00

    ; メッセージ表示
    mov    si, msg
.print:
    lodsb
    test   al, al
    jz     .halt
    mov    ah, 0x0E          ; BIOS テレタイプ出力
    int    0x10
    jmp    .print

.halt:
    hlt
    jmp    .halt

msg: db "Boot!", 0

times 510-($-$$) db 0        ; 512バイトまでパディング
dw 0xAA55                     ; ブートシグネチャ

20.4 WASM (WebAssembly) とアセンブリ

WebAssembly は仮想的な命令セットアーキテクチャであり、スタックマシンベースのバイナリフォーマットである。

;; WebAssembly テキスト形式 (WAT)
;; アセンブリ言語に似た低水準な記述が可能

(module
  ;; 関数定義: fibonacci(n) -> result
  (func $fibonacci (param $n i32) (result i32)
    (local $a i32)
    (local $b i32)
    (local $i i32)

    ;; if n <= 1 return n
    (if (i32.le_s (local.get $n) (i32.const 1))
      (then (return (local.get $n)))
    )

    (local.set $a (i32.const 0))
    (local.set $b (i32.const 1))
    (local.set $i (i32.const 1))

    (block $break
      (loop $loop
        (local.set $a
          (i32.add (local.get $a) (local.get $b)))
        ;; swap a, b
        (local.set $b
          (i32.sub (local.get $a) (local.get $b)))
        (local.set $a
          (i32.sub (local.get $a) (local.get $b)))

        (local.set $i (i32.add (local.get $i) (i32.const 1)))
        (br_if $loop (i32.lt_s (local.get $i) (local.get $n)))
      )
    )
    (local.get $b)
  )

  (export "fibonacci" (func $fibonacci))
)

20.5 学習リソースとツール

リソース種類URL / 説明
Compiler ExplorerWeb ツールhttps://godbolt.org/
Intel SDM公式マニュアルIntel 64 and IA-32 Software Developer's Manual
ARM ARM公式マニュアルARM Architecture Reference Manual
RISC-V Spec公式仕様https://riscv.org/specifications/
x86 and amd64 instruction referenceリファレンスhttps://www.felixcloutier.com/x86/
Programming from the Ground Up書籍Jonathan Bartlett
Computer Systems: A Programmer's Perspective書籍Bryant & O'Hallaron (CS:APP)
Hacker's Delight書籍Henry S. Warren Jr. (ビット演算テクニック)

20.6 まとめ

アセンブリ言語は、以下のような場面で今なお重要な役割を果たしている。

  1. パフォーマンスクリティカルなコードの最適化: コンパイラが生成するコードの品質を評価し、必要に応じて手動最適化を行う。SIMD 命令を活用した高速な数値計算、暗号処理、コーデック処理など。

  2. セキュリティ分析: 脆弱性の解析、マルウェアのリバースエンジニアリング、エクスプロイト開発と防御技術の理解。

  3. システムプログラミング: OS カーネル、ブートローダー、デバイスドライバ、割り込みハンドラなど、高水準言語では記述できない低水準処理。

  4. コンピュータサイエンスの基礎理解: CPU の動作原理、メモリ階層、パイプライン、分岐予測など、コンピュータアーキテクチャの深い理解。

  5. デバッグとトラブルシューティング: コアダンプの解析、コンパイラのバグの特定、最適化の問題の調査。

現代のソフトウェアエンジニアにとって、アセンブリ言語を日常的に書く必要はないが、読み理解する能力は、高品質なソフトウェアの開発、パフォーマンス問題の解決、セキュリティの強化において不可欠なスキルである。アセンブリ言語の理解は、ソフトウェアとハードウェアの境界を超えた包括的な技術力の基盤となる。


本記事で使用した主な環境:

  • x86-64: NASM 2.16+, GAS (GNU Binutils), GCC 13+, Clang 17+
  • AArch64: macOS Sonoma/Sequoia (Apple Silicon M1-M4), Clang/LLVM
  • RISC-V: GCC RISC-V Cross-Compiler, QEMU User Mode Emulation
  • デバッガ: GDB 14+, LLDB (Xcode 15+)