"依存を排せ。世界を構築するとは、自らの足元を知ることから始まる。"
あなたの書いた「Hello, World!」は、
本当にあなたのものだろうか?
それは、Cランタイムが整えた世界の上に、
無数の関数と抽象が重ねられた結果にすぎないのではないか?
この章は、そのすべてを捨てることから始まる。
libcに依らず、リンカに依らず、OSすら最小限に頼り、
スタックとレジスタのみによって世界を構築する。
それが「スタックアセンブリ」による、最小の“Hello, World”。
前提:libc なしとはどういうことか
通常、C言語で書かれたプログラムは、以下に依存している:
- スタートアップルーチン(
_start
→main
) - 標準入出力ライブラリ(
printf
,puts
) - メモリアロケーション(
malloc
,free
) - グローバル変数の初期化と破棄
つまり、int main()
を書いた時点で、
すでに100KB以上の依存物を呼び出している。
この章の目標は:
- これらをすべて排除する
- 自分の手で「開始」と「終了」を設計する
- 「文字を表示する」とは物理的に何が起きているかを理解する
ということにある。
方法:最小構成での write syscall 利用
Linuxにおいて、write(1, buffer, size)
は、
標準出力に任意のバイト列を書き出すことを意味する。
これを使えば、Cもprintfも不要だ。
必要な構成要素:
-
.text
セクション:エントリポイント_start
-
.data
セクション:表示するメッセージ -
rax/rdi/rsi/rdx
のレジスタ配置 -
syscall
命令 -
exit
の明示
コード例:libc なし “Hello, world!”
section .data
msg db "Hello, world!", 0xA
len equ $ - msg
section .text
global _start
_start:
; write(1, msg, len)
mov rax, 1 ; syscall: write
mov rdi, 1 ; file descriptor: stdout
mov rsi, msg ; pointer to message
mov rdx, len ; length of message
syscall
; exit(0)
mov rax, 60 ; syscall: exit
xor rdi, rdi ; status: 0
syscall
ビルドと実行
nasm -f elf64 hello.asm -o hello.o
ld -o hello hello.o
./hello
これで、完全に libc 非依存の Hello World が完成する。
スタックはどう使われているのか?
ここであえてスタック操作を導入してみよう。
section .text
global _start
_start:
sub rsp, 32 ; スタック確保(アライメント確保)
mov rax, 1
mov rdi, 1
lea rsi, [rel msg]
mov rdx, msglen
syscall
mov rax, 60
xor rdi, rdi
syscall
section .data
msg db "Hello from the stack.", 0xA
msglen equ $ - msg
ここで重要なのは、
**“スタックを安全に操作しながら syscall を行う”**という点。
-
sub rsp, 32
はsyscall
前の16バイト境界アライメントの慣例 -
lea rsi, [rel msg]
によってセグメント超えを防ぐ
スタックの設計は、
単なる退避領域ではない。
それは**「一時的な宇宙」**であり、制御とデータの両方を包摂する構造だ。
さらに一歩:msg 自体をスタック上に構築する
section .text
global _start
_start:
sub rsp, 32
mov rbx, rsp
mov dword [rbx], 0x0A21646C ; 'ld!\n'
mov dword [rbx+4], 0x726F7720 ; ' wor'
mov dword [rbx+8], 0x6F6C6C65 ; 'ello'
mov rax, 1
mov rdi, 1
mov rsi, rbx
mov rdx, 13
syscall
mov rax, 60
xor rdi, rdi
syscall
この例では、文字列ですらデータセクションを使わず、スタック上に構築している。
これは、“自己完結性”の極致であり、
**「構造の中に意味を畳み込む」**という設計思想の表現である。
スタックアセンブリがもたらすもの
-
理解の透明性
- 何が使われ、何が破棄され、何が残るのかすべてが見える
-
最小限の設計哲学
- 使わないものは持ち込まない。書かないことが構造になる。
-
自己完結性の美学
- メモリ空間、実行単位、終了まで、すべて自前で用意できる
-
ライブラリとランタイムからの独立
- 世界を構築する“最初の設計者”としての立場を取り戻せる
結語:Hello Worldは、世界の宣言である
printf("Hello, world!");
は便利だ。
だがその便利さは、すべてを隠すという代償の上に成り立っている。
スタックアセンブリは、
その隠された全てを、一命令ずつ明かしていく設計行為である。
- 世界を知るために
- 意志を刻むために
- 設計とは何かを取り戻すために
あなたの「Hello」は、自分の設計であるべきだ。
"Hello, world." それは、世界の上に自分が立ったことを、たった一度だけ証明する言葉である。