Linux x86_64 NASMでのBrainfuckコンパイラ
Brainfuckとは
まず「Brainfuck」とは難解プログラミングの一種でUrban Müllerさんがコンパイラのなるべく小さい言語として考案したプログラミング言語です。
コンパイラのサイズは最小で240バイトと、とても小さいのです。
さらにチューリング完全なので理論上なんでもできます。"理論上"はですが。
NASMとは
**NASM(Netwide Assembler)**は、x86 / x86_64 向けのアセンブリ言語をコンパイルする アセンブラです。
低レベルプログラミングやOS開発、JIT、リバースエンジニアリングなどでよく使われます。
対応 OS
NASM は、Unix/Windows/MacOS X といった、各 OS 用のオブジェクトフォーマットに対応しているので、複数の OS に対応することもできます。
ただし、アセンブラコードは、各 OS の仕様などに対応するように記述しなければならないため、一つのコードで複数の OS に対応するには、マクロなどを使ってコードを分けたりする必要があります。
今回はその中でもLinux x86_64で、Intel記法を使います
Linuxでの機械語の概要
Linux機械語(ELF)の命令まとめ
x86_64 Linuxの実行ファイルは ELF形式で保存されています。
例として次の16進データを解析します。
7F 45 4C 46 02 01 01 00 ...
これは ELF実行ファイルの機械語です。
ELFヘッダ
最初の部分は ELFヘッダです。
| バイト | 意味 |
|---|---|
7F 45 4C 46 |
ELFマジック |
02 |
64bit |
01 |
little endian |
01 |
ELF version |
02 00 |
executable |
3E 00 |
x86_64 |
つまり
ELF64 x86_64 実行ファイル
です。
プログラムヘッダ
次の部分は プログラムヘッダです。
例
01 00 00 00
05 00 00 00
意味
| 値 | 意味 |
|---|---|
01 |
LOADセグメント |
05 |
実行 + 読み取り |
つまり
コードセグメント
です。
機械語命令
ここからが実際のCPU命令です。
例
48 81 EC 30 75 00 00
これは
sub rsp,0x7530
意味
スタックを確保
レジスタ設定
49 89 E5
↓
mov r13,rsp
syscall番号設定
48 C7 C0 01 00 00 00
↓
mov rax,1
意味
sys_write
ファイルディスクリプタ
48 C7 C7 01 00 00 00
↓
mov rdi,1
意味
stdout
出力バイト数
48 C7 C2 01 00 00 00
↓
mov rdx,1
意味
1バイト書き込み
syscall実行
0F 05
↓
syscall
入力(read)
Linuxの入力は read syscall を使います。
syscall番号
rax = 0
例
mov rax,0
mov rdi,0
mov rsi,buffer
mov rdx,1
syscall
意味
stdinから1バイト読む
Linux syscall表(よく使う)
| syscall | rax |
|---|---|
| read | 0 |
| write | 1 |
| open | 2 |
| close | 3 |
| exit | 60 |
NASMとの比較
NASM
mov rax,1
mov rdi,1
mov rsi,msg
mov rdx,5
syscall
機械語
48 C7 C0 01 00 00 00
48 C7 C7 01 00 00 00
48 BE ...
48 C7 C2 05 00 00 00
0F 05
違い
| NASM | 機械語 |
|---|---|
| 人間向け | CPU向け |
| 可読性高い | バイト列 |
| コンパイル必要 | 直接実行 |
まとめ
Linux実行ファイルは
ELFヘッダ
↓
プログラムヘッダ
↓
機械語命令
で構成されます。
CPUは最終的に
48 C7 C0
0F 05
のような 機械語バイト列を実行します。
先ほど説明したNASMはこの機械語を 人間が書きやすい形にしたものです。
NASMで作るBrainfuckコンパイラ
コード全文
global _start
section .bss
program resb 131072
codebuf resb 131072
stack resq 8192
outname resb 256
section .data
ehdr:
db 0x7f,"ELF",2,1,1,0
times 8 db 0
dw 2
dw 0x3e
dd 1
dq 0x400078
dq 64
dq 0
dd 0
dw 64
dw 56
dw 1
dw 0
dw 0
dw 0
phdr:
dd 1
dd 5
dq 0
dq 0x400000
dq 0x400000
dq 0
dq 0
dq 0x200000
section .text
_start:
mov rbx,rsp
mov rax,[rbx]
cmp rax,2
jl exit
mov rsi,[rbx+16]
mov rdi,rsi
mov rdx,outname
nloop:
mov al,[rdi]
cmp al,0
je ndone
cmp al,'.'
je ndone
mov [rdx],al
inc rdi
inc rdx
jmp nloop
ndone:
mov byte [rdx],0
mov rax,2
mov rdi,rsi
xor rsi,rsi
xor rdx,rdx
syscall
mov r12,rax
mov rax,0
mov rdi,r12
mov rsi,program
mov rdx,131072
syscall
mov r13,rax
mov rax,3
mov rdi,r12
syscall
mov r14,codebuf
mov byte [r14],0x48
mov byte [r14+1],0x81
mov byte [r14+2],0xec
mov dword [r14+3],30000
add r14,7
mov byte [r14],0x49
mov byte [r14+1],0x89
mov byte [r14+2],0xe5
add r14,3
mov byte [r14],0x48
mov byte [r14+1],0xc7
mov byte [r14+2],0xc0
mov dword [r14+3],1
add r14,7
mov byte [r14],0x48
mov byte [r14+1],0xc7
mov byte [r14+2],0xc7
mov dword [r14+3],1
add r14,7
mov byte [r14],0x48
mov byte [r14+1],0xc7
mov byte [r14+2],0xc2
mov dword [r14+3],1
add r14,7
mov rbx,program
lea r12,[program+r13]
xor r11,r11
compile:
cmp rbx,r12
jge compile_done
mov al,[rbx]
cmp al,'+'
je plus
cmp al,'-'
je minus
cmp al,'>'
je right
cmp al,'<'
je left
cmp al,'.'
je outc
cmp al,','
je inc
cmp al,'['
je lb
cmp al,']'
je rb
next:
inc rbx
jmp compile
plus:
xor rcx,rcx
pl:
cmp byte [rbx],'+'
jne pld
inc rcx
inc rbx
jmp pl
pld:
mov byte [r14],0x41
mov byte [r14+1],0x80
mov byte [r14+2],0x45
mov byte [r14+3],0
mov byte [r14+4],cl
add r14,5
jmp compile
minus:
xor rcx,rcx
ml:
cmp byte [rbx],'-'
jne mld
inc rcx
inc rbx
jmp ml
mld:
mov byte [r14],0x41
mov byte [r14+1],0x80
mov byte [r14+2],0x6d
mov byte [r14+3],0
mov byte [r14+4],cl
add r14,5
jmp compile
right:
xor rcx,rcx
rl:
cmp byte [rbx],'>'
jne rld
inc rcx
inc rbx
jmp rl
rld:
mov byte [r14],0x49
mov byte [r14+1],0x81
mov byte [r14+2],0xc5
mov dword [r14+3],ecx
add r14,7
jmp compile
left:
xor rcx,rcx
ll:
cmp byte [rbx],'<'
jne lld
inc rcx
inc rbx
jmp ll
lld:
mov byte [r14],0x49
mov byte [r14+1],0x81
mov byte [r14+2],0xed
mov dword [r14+3],ecx
add r14,7
jmp compile
outc:
mov byte [r14],0x4c
mov byte [r14+1],0x89
mov byte [r14+2],0xee
add r14,3
mov byte [r14],0x0f
mov byte [r14+1],0x05
add r14,2
jmp next
inc:
mov byte [r14],0x48
mov byte [r14+1],0x31
mov byte [r14+2],0xc0
add r14,3
mov byte [r14],0x48
mov byte [r14+1],0x31
mov byte [r14+2],0xff
add r14,3
mov byte [r14],0x4c
mov byte [r14+1],0x89
mov byte [r14+2],0xee
add r14,3
mov byte [r14],0x0f
mov byte [r14+1],0x05
add r14,2
jmp next
lb:
cmp byte [rbx+1],'-'
jne lb1
cmp byte [rbx+2],']'
jne lb1
mov byte [r14],0x41
mov byte [r14+1],0xc6
mov byte [r14+2],0x45
mov byte [r14+3],0
mov byte [r14+4],0
add r14,5
add rbx,3
jmp compile
lb1:
mov byte [r14],0x41
mov byte [r14+1],0x80
mov byte [r14+2],0x7d
mov byte [r14+3],0
mov byte [r14+4],0
mov byte [r14+5],0x0f
mov byte [r14+6],0x84
mov dword [r14+7],0
mov [stack+r11*8],r14
inc r11
add r14,11
jmp next
rb:
dec r11
mov rax,[stack+r11*8]
mov byte [r14],0x41
mov byte [r14+1],0x80
mov byte [r14+2],0x7d
mov byte [r14+3],0
mov byte [r14+4],0
mov byte [r14+5],0x0f
mov byte [r14+6],0x85
mov rdx,rax
add rdx,11
sub rdx,r14
sub rdx,11
mov dword [r14+7],edx
add r14,11
mov rdx,r14
sub rdx,rax
sub rdx,11
mov dword [rax+7],edx
jmp next
compile_done:
mov byte [r14],0x48
mov byte [r14+1],0xc7
mov byte [r14+2],0xc0
mov dword [r14+3],60
mov byte [r14+7],0x48
mov byte [r14+8],0x31
mov byte [r14+9],0xff
mov byte [r14+10],0x0f
mov byte [r14+11],0x05
add r14,12
mov rbx,r14
sub rbx,codebuf
mov rax,120
add rax,rbx
mov [phdr+32],rax
mov [phdr+40],rax
mov rax,2
mov rdi,outname
mov rsi,577
mov rdx,0755
syscall
mov r12,rax
mov rax,1
mov rdi,r12
mov rsi,ehdr
mov rdx,64
syscall
mov rax,1
mov rdi,r12
mov rsi,phdr
mov rdx,56
syscall
mov rax,1
mov rdi,r12
mov rsi,codebuf
mov rdx,rbx
syscall
mov rax,3
mov rdi,r12
syscall
exit:
mov rax,60
xor rdi,rdi
syscall
解説
プログラムの役割
このプログラムは次の流れで動作します。
Brainfuckソース
↓
NASMプログラムが読み込む
↓
機械語に変換
↓
ELFヘッダを付ける
↓
Linux実行ファイル生成
つまり
Brainfuck → ELFバイナリ
を直接作ります。
メモリ領域
section .bss
program resb 131072
codebuf resb 131072
stack resq 8192
outname resb 256
用途
| 変数 | 用途 |
|---|---|
| program | Brainfuckコード |
| codebuf | 生成する機械語 |
| stack | ループ管理 |
| outname | 出力ファイル名 |
ELFヘッダ
ehdr:
db 0x7f,"ELF",2,1,1,0
これは前回説明した ELFマジックです。
7F 45 4C 46
意味
| 値 | 内容 |
|---|---|
| 7F ELF | ELF識別 |
| 2 | 64bit |
| 1 | little endian |
エントリポイント
dq 0x400078
これは
実行開始アドレス
です。
プログラムヘッダ
phdr:
dd 1
dd 5
意味
| 値 | 内容 |
|---|---|
| 1 | LOADセグメント |
| 5 | 読み取り + 実行 |
つまり
コードセグメント
になります。
引数取得
mov rbx,rsp
mov rax,[rbx]
cmp rax,2
jl exit
意味
argcチェック
Brainfuckファイルを指定していない場合は終了します。
出力ファイル名生成
mov rdi,rsi
mov rdx,outname
ここでは
test.bf
↓
test
のように 拡張子を削除した名前を作っています。
ファイル読み込み
mov rax,2
syscall
これは
open()
です。
次に
mov rax,0
syscall
これは
read()
です。
機械語生成バッファ
mov r14,codebuf
ここから 実行コードを作ります。
Brainfuckメモリ確保
48 81 EC 30 75 00 00
NASM
sub rsp,30000
Brainfuckテープを確保しています。
テープポインタ
49 89 E5
NASM
mov r13,rsp
r13 = tape pointer
write syscall準備
mov rax,1
mov rdi,1
mov rdx,1
意味
write(stdout,buffer,1)
Brainfuckコンパイル
ここから
compile:
で Brainfuckコードを解析します。
+ 命令
plus:
連続する + をまとめます。
+++++
↓
add byte [r13],5
生成機械語
41 80 45 00 XX
- 命令
41 80 6d 00 XX
NASM
sub byte [r13],X
> 命令
49 81 C5 XX
NASM
add r13,X
< 命令
49 81 ED XX
NASM
sub r13,X
出力 .
outc:
生成コード
mov rsi,r13
syscall
つまり
write(stdout,&cell,1)
入力 ,
生成コード
mov rax,0
mov rdi,0
mov rsi,r13
syscall
意味
read(stdin,&cell,1)
ループ [ ]
Brainfuckのループ
[
while(cell != 0)
]
に変換されます。
生成コード
cmp byte [r13],0
je end
そして
cmp byte [r13],0
jne begin
最適化
このコンパイラには簡単な最適化があります。
[-]
これは
cell = 0
なので
mov byte [r13],0
に変換されます。
プログラム終了
最後に
mov rax,60
xor rdi,rdi
syscall
つまり
exit(0)
です。
ELFファイル生成
mov rax,2
mov rsi,577
mov rdx,0755
syscall
これは
open(outname,O_CREAT|O_WRONLY|O_TRUNC)
次に
write ELF header
write program header
write machine code
を順に書き込みます。
出力ファイル構造
最終的に生成されるバイナリは
ELF header
Program header
Machine code
になります。
つまり
完全なLinux実行ファイル
です。
まとめ
このNASMプログラムは
Brainfuckソース
↓
機械語生成
↓
ELFヘッダ付与
↓
実行ファイル生成
を すべてNASMだけで行うコンパイラです。
特徴
- リンカ不要
- C不要
- ELF直接生成
- 簡単な最適化付き
おまけ
生成されたHello,World!の例
7F 45 4C 46 02 01 01 00 00 00 00 00 00 00 00 00 02 00 3E 00 01 00 00 00 78 00 40 00 00 00 00 00 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 40 00 38 00 01 00 00 00 00 00 00 00 01 00 00 00 05 00 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 40 00 00 00 00 00 9E 01 00 00 00 00 00 00 9E 01 00 00 00 00 00 00 00 00 20 00 00 00 00 00 48 81 EC 30 75 00 00 49 89 E5 48 C7 C0 01 00 00 00 48 C7 C7 01 00 00 00 48 C7 C2 01 00 00 00 41 80 45 00 0A 41 80 7D 00 00 0F 84 47 00 00 00 49 81 C5 01 00 00 00 41 80 45 00 07 49 81 C5 01 00 00 00 41 80 45 00 0A 49 81 C5 01 00 00 00 41 80 45 00 03 49 81 C5 01 00 00 00 41 80 45 00 01 49 81 ED 04 00 00 00 41 80 6D 00 01 41 80 7D 00 00 0F 85 B9 FF FF FF 49 81 C5 01 00 00 00 41 80 45 00 02 4C 89 EE 0F 05 49 81 C5 01 00 00 00 41 80 45 00 01 4C 89 EE 0F 05 41 80 45 00 07 4C 89 EE 0F 05 4C 89 EE 0F 05 41 80 45 00 03 4C 89 EE 0F 05 49 81 C5 01 00 00 00 41 80 45 00 02 4C 89 EE 0F 05 49 81 ED 02 00 00 00 41 80 45 00 0F 4C 89 EE 0F 05 49 81 C5 01 00 00 00 4C 89 EE 0F 05 41 80 45 00 03 4C 89 EE 0F 05 41 80 6D 00 06 4C 89 EE 0F 05 41 80 6D 00 08 4C 89 EE 0F 05 49 81 C5 01 00 00 00 41 80 45 00 01 4C 89 EE 0F 05 49 81 C5 01 00 00 00 4C 89 EE 0F 05 48 C7 C0 3C 00 00 00 48 31 FF 0F 05