Assembly
#25
TIOBE#19
GitHub#29
IEEESpectrum#33
プログラミング言語
Assembly
概要
Assembly(アセンブリ言語)は、CPUの機械語命令を人間が読みやすい形で表現した低レベルプログラミング言語です。
詳細
Assembly言語は、コンピュータのCPUが直接実行する機械語命令を、ニーモニック(助記記号)を使って人間が理解しやすい形で記述できる低レベルプログラミング言語です。各CPUアーキテクチャ(x86、ARM、MIPS、RISC-Vなど)ごとに固有の命令セットと構文を持ち、ハードウェアに最も近いレベルでのプログラミングを可能にします。メモリ管理、レジスタ操作、直接的なハードウェア制御が可能で、最高レベルのパフォーマンスと精密な制御を実現できます。組み込みシステム、デバイスドライバ、オペレーティングシステムのカーネル、リアルタイムシステム、セキュリティ研究、リバースエンジニアリングなどの分野で使用されます。現代では高レベル言語のクリティカルな部分の最適化や、特定のハードウェア機能を活用する際に使用されることが多く、C/C++との組み合わせによるインラインアセンブリも広く利用されています。
書き方の例
Hello World(x86-64 Linux)
# AT&T記法(GAS)
.section .data
msg: .ascii "Hello, World!\n"
msg_len = . - msg
.section .text
.global _start
_start:
# write システムコール
mov $1, %rax # sys_write
mov $1, %rdi # stdout
mov $msg, %rsi # メッセージのアドレス
mov $msg_len, %rdx # メッセージの長さ
syscall
# exit システムコール
mov $60, %rax # sys_exit
mov $0, %rdi # 終了コード
syscall
; Intel記法(NASM)
section .data
msg db 'Hello, World!', 0xA
msg_len equ $ - msg
section .text
global _start
_start:
; write システムコール
mov rax, 1 ; sys_write
mov rdi, 1 ; stdout
mov rsi, msg ; メッセージのアドレス
mov rdx, msg_len ; メッセージの長さ
syscall
; exit システムコール
mov rax, 60 ; sys_exit
mov rdi, 0 ; 終了コード
syscall
基本的なデータ操作
# x86-64 AT&T記法
.section .data
number1: .quad 10
number2: .quad 20
result: .quad 0
.section .text
.global _start
_start:
# レジスタにデータをロード
movq number1(%rip), %rax # number1を%raxにロード
movq number2(%rip), %rbx # number2を%rbxにロード
# 加算演算
addq %rbx, %rax # %rax = %rax + %rbx
# 結果を保存
movq %rax, result(%rip) # 結果をメモリに保存
# 減算演算
movq number1(%rip), %rcx
subq number2(%rip), %rcx # %rcx = number1 - number2
# 乗算演算
movq number1(%rip), %rax
movq number2(%rip), %rbx
imulq %rbx, %rax # %rax = %rax * %rbx
# ビット演算
movq $0xFF, %rdx
andq $0x0F, %rdx # AND演算
orq $0xF0, %rdx # OR演算
xorq $0xFF, %rdx # XOR演算
# 終了
mov $60, %rax
mov $0, %rdi
syscall
条件分岐とジャンプ
# x86-64における条件分岐
.section .data
value1: .quad 10
value2: .quad 20
.section .text
.global _start
_start:
# 値の比較
movq value1(%rip), %rax
movq value2(%rip), %rbx
cmpq %rbx, %rax # %rax と %rbx を比較
# 条件ジャンプ
je equal # 等しい場合
jg greater # より大きい場合
jl less # より小さい場合
jmp end # 無条件ジャンプ
equal:
# 等しい場合の処理
movq $1, %rcx
jmp end
greater:
# より大きい場合の処理
movq $2, %rcx
jmp end
less:
# より小さい場合の処理
movq $3, %rcx
jmp end
end:
# プログラム終了
mov $60, %rax
mov $0, %rdi
syscall
ループ処理
# カウンターループの例
.section .data
counter: .quad 5
sum: .quad 0
.section .text
.global _start
_start:
movq $0, %rax # 合計値の初期化
movq counter(%rip), %rcx # カウンターの設定
loop_start:
cmpq $0, %rcx # カウンターが0かチェック
je loop_end # 0なら終了
addq %rcx, %rax # 現在の値を合計に加算
decq %rcx # カウンターをデクリメント
jmp loop_start # ループの先頭に戻る
loop_end:
movq %rax, sum(%rip) # 結果を保存
# プログラム終了
mov $60, %rax
mov $0, %rdi
syscall
# whileループ風の書き方
while_loop_example:
movq $10, %rbx # 初期値
while_start:
cmpq $0, %rbx # 条件チェック
jle while_end # 0以下なら終了
# ループ本体の処理
# 何らかの処理...
decq %rbx # 値をデクリメント
jmp while_start # ループ継続
while_end:
ret # 関数復帰
関数の定義と呼び出し
# 関数の定義例
.section .text
.global _start
.global add_numbers
.global factorial
# 2つの数を加算する関数
add_numbers:
# 引数: %rdi(第1引数), %rsi(第2引数)
# 戻り値: %rax
movq %rdi, %rax
addq %rsi, %rax
ret
# 階乗を計算する関数(再帰)
factorial:
# 引数: %rdi(n)
# 戻り値: %rax
# ベースケース: n <= 1
cmpq $1, %rdi
jle base_case
# 再帰呼び出し
pushq %rdi # nをスタックに保存
decq %rdi # n-1を計算
call factorial # factorial(n-1)を呼び出し
popq %rdi # nを復元
# n * factorial(n-1)
imulq %rdi, %rax
ret
base_case:
movq $1, %rax
ret
_start:
# add_numbers関数の呼び出し
movq $10, %rdi
movq $20, %rsi
call add_numbers
# 結果は%raxに格納される
# factorial関数の呼び出し
movq $5, %rdi
call factorial
# プログラム終了
mov $60, %rax
mov $0, %rdi
syscall
スタック操作とローカル変数
# スタックフレームを使った関数
.section .text
.global complex_function
complex_function:
# 関数プロローグ
pushq %rbp # 古いベースポインタを保存
movq %rsp, %rbp # 新しいベースポインタを設定
subq $32, %rsp # ローカル変数用にスタック領域を確保
# ローカル変数の使用
# -8(%rbp) : 第1ローカル変数
# -16(%rbp) : 第2ローカル変数
# -24(%rbp) : 第3ローカル変数
# -32(%rbp) : 第4ローカル変数
movq $100, -8(%rbp) # ローカル変数1 = 100
movq $200, -16(%rbp) # ローカル変数2 = 200
# ローカル変数を使った計算
movq -8(%rbp), %rax
addq -16(%rbp), %rax
movq %rax, -24(%rbp) # 結果をローカル変数3に保存
# レジスタの保存と復元
pushq %r12 # 使用するレジスタを保存
pushq %r13
# 何らかの処理...
movq -24(%rbp), %r12
imulq $2, %r12
# レジスタの復元
popq %r13
popq %r12
# 戻り値の設定
movq -24(%rbp), %rax
# 関数エピローグ
movq %rbp, %rsp # スタックポインタを復元
popq %rbp # ベースポインタを復元
ret
インラインアセンブリ(C言語との組み合わせ)
// C言語内でのインラインアセンブリ
#include <stdio.h>
int main() {
int input = 10;
int result;
// GCCインラインアセンブリ
asm volatile (
"movl %1, %%eax\n\t" // inputを%eaxに移動
"imull $2, %%eax\n\t" // 2を掛ける
"addl $5, %%eax\n\t" // 5を加える
"movl %%eax, %0" // 結果をresultに保存
: "=r" (result) // 出力オペランド
: "r" (input) // 入力オペランド
: "eax" // 破壊されるレジスタ
);
printf("結果: %d\n", result); // 25が出力される
// CPUID命令の例
unsigned int eax, ebx, ecx, edx;
asm volatile (
"cpuid"
: "=a" (eax), "=b" (ebx), "=c" (ecx), "=d" (edx)
: "a" (0)
);
printf("CPU ID: %08X %08X %08X\n", ebx, edx, ecx);
return 0;
}
SIMD命令(SSE/AVX)の使用
# SIMD(Single Instruction, Multiple Data)命令の例
.section .data
# 4つの32ビット浮動小数点数の配列
array1: .float 1.0, 2.0, 3.0, 4.0
array2: .float 5.0, 6.0, 7.0, 8.0
result: .space 16 # 結果用領域
.section .text
.global simd_example
simd_example:
# SSE命令を使った並列演算
movups array1(%rip), %xmm0 # array1をXMMレジスタにロード
movups array2(%rip), %xmm1 # array2をXMMレジスタにロード
addps %xmm1, %xmm0 # 4つの浮動小数点数を同時に加算
movups %xmm0, result(%rip) # 結果を保存
# AVX命令の例(256ビット)
# vmovups array1(%rip), %ymm0 # 8つの32ビット浮動小数点数
# vmovups array2(%rip), %ymm1
# vaddps %ymm1, %ymm0, %ymm0 # AVX加算
ret
特徴的な機能
直接的なハードウェア制御
# ポート入出力(x86固有)
.section .text
.global port_io_example
port_io_example:
# ポートからデータを読み取り
movw $0x3F8, %dx # COMポートアドレス
inb %dx, %al # ポートから1バイト読み取り
# ポートにデータを書き込み
movb $0x48, %al # 'H'のASCIIコード
outb %al, %dx # ポートに1バイト書き込み
# メモリマップドI/O
movq $0xB8000, %rdi # VGAテキストメモリのアドレス
movb $0x41, (%rdi) # 'A'を画面に表示
movb $0x07, 1(%rdi) # 属性(白文字、黒背景)
ret
割り込みハンドラ
# 割り込みハンドラの例
.section .text
.global interrupt_handler
interrupt_handler:
# レジスタの保存
pushq %rax
pushq %rbx
pushq %rcx
pushq %rdx
pushq %rsi
pushq %rdi
pushq %r8
pushq %r9
pushq %r10
pushq %r11
# 割り込み処理
# ここに実際の割り込み処理を記述
# EOI(End of Interrupt)の送信
movb $0x20, %al
outb %al, $0x20 # PICにEOI送信
# レジスタの復元
popq %r11
popq %r10
popq %r9
popq %r8
popq %rdi
popq %rsi
popq %rdx
popq %rcx
popq %rbx
popq %rax
iretq # 割り込みからの復帰
アトミック操作
# マルチプロセッサ環境でのアトミック操作
.section .data
shared_counter: .quad 0
lock_var: .quad 0
.section .text
.global atomic_increment
atomic_increment:
# アトミックなインクリメント
lock incq shared_counter(%rip) # LOCK prefixでアトミック性を保証
# Compare and Swap (CAS)
movq $0, %rax # 期待値
movq $1, %rbx # 新しい値
lock cmpxchgq %rbx, lock_var(%rip)
# Test and Set
movq $1, %rax
lock xchgq %rax, lock_var(%rip)
ret
アーキテクチャ別の特徴
ARM Assembly(AArch64)
// ARM64アセンブリの例
.global _start
.section .data
msg: .ascii "Hello, ARM!\n"
msg_len = . - msg
.section .text
_start:
// システムコール番号と引数の設定
mov x8, #64 // sys_write
mov x0, #1 // stdout
ldr x1, =msg // メッセージアドレス
mov x2, #msg_len // メッセージ長
svc #0 // システムコール実行
// プログラム終了
mov x8, #93 // sys_exit
mov x0, #0 // 終了コード
svc #0
// ARM64での条件実行
conditional_example:
cmp x0, x1
b.eq equal // 等しい場合
b.gt greater // より大きい場合
b.lt less // より小さい場合
equal:
mov x2, #1
ret
greater:
mov x2, #2
ret
less:
mov x2, #3
ret
バージョンと実装
アーキテクチャ | 命令セット | 主な用途 | 特徴 |
---|---|---|---|
x86-64 | x86_64, AMD64 | デスクトップ、サーバー | CISC、豊富な命令 |
ARM | ARMv8-A (AArch64) | モバイル、組み込み | RISC、省電力 |
RISC-V | RV32I, RV64I | 学術、組み込み | オープンソース、モジュラー |
MIPS | MIPS32, MIPS64 | 組み込み、教育 | RISC、シンプル |
PowerPC | PowerPC64 | サーバー、組み込み | RISC、高性能 |
参考ページ
公式ドキュメント
- Intel 64 and IA-32 Architectures Software Developer's Manuals - Intel公式マニュアル
- ARM Architecture Reference Manual - ARM公式ドキュメント
- RISC-V Instruction Set Manual - RISC-V仕様書
学習リソース
- Programming from the Ground Up - アセンブリ学習書
- x86 Assembly Guide - x86アセンブリガイド
- ARM Assembly Basics - ARM学習チュートリアル
開発ツール
- GNU Assembler (GAS) - GNUアセンブラ
- NASM - Netwide Assembler
- LLVM Assembler - LLVMアセンブラ