0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

NASMでBrainfuckCompiler

0
Posted at

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
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?