Beautiful in mathematical but ugly for programmer
Brainfuck ,也稱做是 BrainF 是一個 Esolang 但符合圖靈完全的程式語言: 主要利用八個符號來轉移所有的狀態,其中包含了目前狀態的的轉移、值的運算、條件運算以及 I/O。 因為結構簡單也自我完備 (可以用自己來撰寫自己的編譯器或直譯器)。 下面是一個用 Python 實作的直譯器:利用八個 if 條件式來判斷 input 並且作狀態的轉移。
if __name__ == "__main__":
import sys
import argparse
parser = argparse.ArgumentParser(description="BrainF Interpreter")
parser.add_argument("file")
args = parser.parse_args()
with open(args.file) as f:
buf = f.read()
space, cur, idx = ['\x00']*10240, 0, 0
while idx < len(buf):
if '+' == buf[idx]:
space[cur] = chr(ord(space[cur])+1)
elif '-' == buf[idx]:
space[cur] = chr(ord(space[cur])-1)
elif '<' == buf[idx]:
cur-= 1
elif '>' == buf[idx]:
cur += 1
elif '[' == buf[idx]:
if '\x00' == space[cur]:
cnt = 0
for idx in range(idx+1, len(buf)):
if '[' == buf[idx]:
cnt += 1
elif ']' == buf[idx] and cnt:
cnt -= 1
elif ']' == buf[idx]:
break
else:
raise TypeError("Missing ']'")
elif ']' == buf[idx]:
if '\x00' != space[cur]:
cnt = 0
for idx in range(idx-1, -1, -1):
if ']' == buf[idx]:
cnt += 1
elif '[' == buf[idx] and cnt:
cnt -= 1
elif '[' == buf[idx]:
break
else:
raise TypeError("Missing '['")
elif ',' == buf[idx]:
space[cur] = sys.stdin.read(1)
elif '.' == buf[idx]:
sys.stdout.write(space[cur])
idx += 1
上面的程式碼只是一個直譯器,但是當我們需要產生一個可以執行的 BrainF 的程式碼時, 需要的則是一個編譯器,我們利用 nasm 的 assembly language 來當作是一個 IR : 產生一個合法的 ASM 並且交由 nasm 跟 gcc 來產生一個可執行的 BrainF 執行檔。
if __name__ == "__main__":
import sys, commands
import argparse
parser = argparse.ArgumentParser(description="BrainF Compiler on ELF/64")
parser.add_argument("file")
parser.add_argument("--outfile", default="a.out")
args = parser.parse_args()
## Read the BrainF source code
with open(args.file) as f:
buf = f.read()
with open("BrainF.S", 'w') as fd:
## Set 64 bits mode
fd.write("BITS 64\n")
## Initial section
fd.write("SECTION .text\n")
fd.write("global main\n")
fd.write("main:\n")
## Dynamically allocat a memory space
fd.write("mov rax, 0x09\n")
fd.write("mov rdi, 0x00\n")
fd.write("mov rsi, 0x100000\n")
fd.write("mov rdx, 0x03\n")
fd.write("mov r10, 0x22\n")
fd.write("mov r8, -1\n")
fd.write("mov r9, 0x00\n")
fd.write("syscall\n")
## Scanner and Lexer
lable, cnt = [], 0
for idx in range(len(buf)):
if '+' == buf[idx]:
fd.write("inc byte [rax]\n")
elif '-' == buf[idx]:
fd.write("dec byte [rax]\n")
elif '<' == buf[idx]:
fd.write("sub rax, 0x04\n")
elif '>' == buf[idx]:
fd.write("add rax, 0x04\n")
elif '[' == buf[idx]:
lable.append("lable{0}".format(cnt))
cnt += 1
fd.write("cmp byte [rax], 0x0\n")
fd.write("je {0}_END\n".format(lable[-1]))
fd.write("{0}:\n".format(lable[-1]))
elif ']' == buf[idx]:
fd.write("cmp byte [rax], 0x0\n")
fd.write("jne {0}\n".format(lable[-1]))
fd.write("{0}_END:\n".format(lable[-1]))
lable = lable[:-1]
elif ',' == buf[idx]:
fd.write("mov rsi, rax\n")
fd.write("push rax\n")
fd.write("mov rax, 0x00\n")
fd.write("mov rdi, 0x00\n")
fd.write("mov rdx, 0x01\n")
fd.write("syscall\n")
fd.write("pop rax\n")
elif '.' == buf[idx]:
fd.write("mov rsi, rax\n")
fd.write("push rax\n")
fd.write("mov rax, 0x01\n")
fd.write("mov rdi, 0x01\n")
fd.write("mov rdx, 0x01\n")
fd.write("syscall\n")
fd.write("pop rax\n")
## Exit via exit_group
fd.write("mov rax, 0xE7\n")
fd.write("syscall")
## Compile with nasm and gcc
print "Assemble ...", commands.getoutput("nasm -f elf64 BrainF.S")
print "Link ...", commands.getoutput("gcc -o {0} BrainF.o".format(args.outfile))
當然,上面的程式碼完全有改善的空間,包含了:Code Generator 應該重複利用相同的邏輯、 使用更短的指令集等。但是這只是個 POC ,就不用在意這麼多了。
接下來另一個實作是不利用 ASM 以及其他的工具,而是直接產生出 ELF64 的可執行檔: 包含需要實作符合 ELF 64-bits 格式的 header 以及相對應的 64-bits opcode 。 所以首先需要有一個 ELF 檔案格式的產生器:在這個產生器中,我們只需要提供 BrainF 所需要的八個能力,並且轉換成相對的 x86_64 opcode。
class ELF64(object):
""" Generate the valid ELF/64 format file """
def __init__(self, size=2**20):
self.data, self.offset = "", 0
self.mmap(size)
def __str__(self):
self.data += self.exit_group
return self.header + self.segment + self.data
@property
def header(self):
""" ELF/64 header """
header = "\x7FELF" ## Magic
header += '\x02' ## Class
header += '\x01' ## Endianness
header += '\x01' ## Version
header += '\x00' ## OS ABI
header += '\x00' ## OS ABI Ver
header += '\x00'*7 ## Reserverd
header += '\x02\x00' ## Executable
header += '\x3E\x00' ## Machine (AMD-64)
header += '\x01\x00\x00\x00' ## Version
header += '\x78\x00\x10\x00\x00\x00\x00\x00' ## Entry
header += '\x40\x00\x00\x00\x00\x00\x00\x00' ## Segment offset
header += '\x00\x00\x00\x00\x00\x00\x00\x00' ## Section offset
header += '\x00\x00\x00\x00' ## Flag
header += '\x40\x00' ## Size of this header
header += '\x38\x00' ## Segment size
header += '\x01\x00' ## Number of segment
header += '\x00\x00' ## Section size
header += '\x00\x00' ## Number of section
header += '\x00\x00' ## Index of the .shstrtab section
return header
@property
def segment(self):
from struct import pack
size = pack("Q", len(self.data) + 64 + 56)
header = '\x01\x00\x00\x00' ## Type
header += '\x05\x00\x00\x00' ## Flags
header += '\x00\x00\x00\x00\x00\x00\x00\x00' ## Offset
header += '\x00\x00\x10\x00\x00\x00\x00\x00' ## Virt Addr
header += '\x00\x00\x10\x00\x00\x00\x00\x00' ## Phy Addr
header += size
header += size
header += '\x00\x10\x00\x00\x00\x00\x00\x00' ## Align
return header
@property
def exit_group(self):
opcode = '\x48\x31\xFF\x48\xC7\xC0\xE7\x00\x00\x00\x0F\x05'
self.offset += len(opcode)
return opcode
def mmap(self, size):
""" Create a memoery space and return on rax"""
from struct import pack
size = pack("I", size)
opcode = '\x48\xC7\xC0\x09\x00\x00\x00' ## mov 0x09 rax
opcode += '\x48\xC7\xC7\x00\x00\x00\x00' ## mov 0x00 rdi
opcode += '\x48\xC7\xC6'+size ## mov size rsi
opcode += '\x48\xC7\xC2\x03\x00\x00\x00' ## mov 0x05 rdx
opcode += '\x49\xC7\xC2\x22\x00\x00\x00' ## mov 0x01 r10
opcode += '\x49\xC7\xC0\xFF\xFF\xFF\xFF' ## mov 0xFFFFFFFF r8
opcode += '\x41\xB9\x00\x00\x00\x00' ## mov 0x00 r9d
opcode += '\x0F\x05' ## syscall
self.data += opcode
self.offset += len(opcode)
return opcode
def add(self):
opcode = '\x48\x83\xC0\x04' ## add 0x04 rax
self.data += opcode
self.offset += len(opcode)
return opcode
def sub(self):
opcode = '\x48\x83\xE8\x04' ## sub 0x04 rax
self.data += opcode
self.offset += len(opcode)
return opcode
def inc(self):
opcode = '\xFE\x00' ## inc rax
self.data += opcode
self.offset += len(opcode)
return opcode
def dec(self):
opcode = '\xFE\x08' ## dec rax
self.data += opcode
self.offset += len(opcode)
return opcode
def read(self):
opcode = '\x48\x89\xC6' ## mov rax rsi
opcode += '\x50' ## push rax
opcode += '\x48\xC7\xC0\x00\x00\x00\x00' ## mov 1 rax
opcode += '\x48\xC7\xC7\x01\x00\x00\x00' ## mov 1 rdi
opcode += '\x48\xC7\xC2\x01\x00\x00\x00' ## mov 1 rdx
opcode += '\x0F\x05' ## syscall
opcode += '\x58' ## pop rax
self.data += opcode
self.offset += len(opcode)
return opcode
def write(self):
opcode = '\x48\x89\xC6' ## mov rax rsi
opcode += '\x50' ## push rax
opcode += '\x48\xC7\xC0\x01\x00\x00\x00' ## mov 1 rax
opcode += '\x48\xC7\xC7\x01\x00\x00\x00' ## mov 1 rdi
opcode += '\x48\xC7\xC2\x01\x00\x00\x00' ## mov 1 rdx
opcode += '\x0F\x05' ## syscall
opcode += '\x58' ## pop rax
self.data += opcode
self.offset += len(opcode)
return opcode
def jumpIfZero(self, label):
opcode = '\x80\x38\x00' ## cmp 0x00 rax
opcode += '\x74{{{label}}}' ## je
## FIXME - Always add one byte for address shift
self.offset += 5
self.data += opcode.format(label=label)
return opcode
def jumpIfNotZero(self, label):
opcode = '\x80\x38\x00' ## cmp 0x00 rax
opcode += '\x75{{{label}}}' ## jnz
## FIXME - Always add one byte for address shift
self.offset += 5
self.data += opcode.format(label=label)
return opcode
最後跟原本的邏輯一樣,一個簡單的 Scanner/Lexer 處理檔案並且利用之前的 ELF Generator 來產生合法的可執行檔。
if __name__ == "__main__":
import sys, os
import argparse
parser = argparse.ArgumentParser(description="BrainF Compiler on ELF/64")
parser.add_argument("file")
parser.add_argument("--outfile", default="a.out")
args = parser.parse_args()
with open(args.file) as f:
buf = f.read()
## ELF Generator, label count and label stack
elf, cnt, label, stack = ELF64(), 0, {}, []
if True:
space, cur, idx = ['\x00']*10240, 0, 0
while idx < len(buf):
if '+' == buf[idx]:
elf.inc()
elif '-' == buf[idx]:
elf.dec()
elif '<' == buf[idx]:
elf.sub()
elif '>' == buf[idx]:
elf.add()
elif '[' == buf[idx]:
## Set the jump label, and fill the address finally
_label_ = "label{0}".format(cnt)
elf.jumpIfZero(_label_)
label.update({_label_ : elf.offset})
stack.append(_label_)
cnt += 1
elif ']' == buf[idx]:
from struct import pack
_label_ = "{0}_END".format(stack[-1])
elf.jumpIfNotZero(_label_)
## First set the end of condition offset
off = 0xFF + label[stack[-1]] - elf.offset + 1
label.update({_label_ : pack("B", off)})
## Second, reset the offset at the previous condition
off = elf.offset - label[stack[-1]]
label.update({stack[-1] : pack("B", off)})
stack = stack[:-1]
elif ',' == buf[idx]:
elf.read()
elif '.' == buf[idx]:
elf.write()
idx += 1
with open(args.outfile, 'wb') as f:
f.write((str(elf).format(**label)))
os.chmod(args.outfile, 0777)