Created
November 10, 2023 18:54
-
-
Save rafaelrc7/c604545279db88fe506b3c85719c9e87 to your computer and use it in GitHub Desktop.
Brainfuck x86-64 Linux Assembly Interpreter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* How to build | |
* gcc -nostdlib brainfuck.s | |
* brainfuck source should be in a file called "code.bf" | |
*/ | |
.set alloc_step, 512 | |
.section .rodata | |
source_path: .string "code.bf" | |
.data | |
source_fd: .long 0 | |
buff: .byte 0 | |
source_ptr: .quad 0 | |
source_len: .quad 0 | |
.bss | |
.set data_len, 32768 | |
data_buff: .skip data_len, 0 | |
.text | |
.globl _start | |
_start: | |
/**** Allocate memory for brainfuck source code ****/ | |
/* void *source_ptr = mmap(NULL, alloc_step, PROT_READ | PROT_WRITE, MAP_ANONYMOUS, -1, 0) */ | |
xorq %rdi, %rdi | |
movq $alloc_step, %rsi | |
movq %rsi, source_len /* source_len = alloc_step */ | |
movq $3, %rdx /* PROT_READ | PROT_WRITE = $3 */ | |
movq $34, %r10 /* MAP_PRIVATE | MAP_ANONYMOUS = $34 */ | |
movq $-1, %r8 /* fd should be -1 if using MAP_ANONYMOUS */ | |
movq $0, %r9 /* offset should be 0 if using MAP_ANONYMOUS */ | |
movq $9, %rax | |
syscall | |
cmpq $-4095, %rax | |
jae exit_syscall_error | |
movq %rax, source_ptr | |
/**** Open brainfuck source code file ****/ | |
/* int source_fd = open(source_path, O_RDONLY, O_RDONLY) */ | |
movq $source_path, %rdi | |
xorq %rsi, %rsi | |
xorq %rdx, %rdx | |
movq $2, %rax | |
syscall | |
cmpq $-4095, %rax | |
jae exit_syscall_error | |
movl %eax, source_fd | |
/**** Read source code file into memory ****/ | |
xorq %r12, %r12 /* counter of read bytes */ | |
movq source_ptr, %rbx | |
source_read_loop: | |
/* read(source_fd, &buff, 1) */ | |
movq source_fd, %rdi | |
movq $buff, %rsi | |
movq $1, %rdx | |
movq $0, %rax | |
syscall | |
cmpq $-4095, %rax | |
jae exit_syscall_error | |
/* EOF */ | |
testq %rax, %rax | |
jz read_loop_eof | |
movb buff, %al | |
/**** Ignore invalid brainfuck characters ****/ | |
cmpb $'>', %al | |
je 1f | |
cmpb $'<', %al | |
je 1f | |
cmpb $'+', %al | |
je 1f | |
cmpb $'-', %al | |
je 1f | |
cmpb $'.', %al | |
je 1f | |
cmpb $',', %al | |
je 1f | |
cmpb $'[', %al | |
je 1f | |
cmpb $']', %al | |
je 1f | |
jmp source_read_loop /* ignore char */ | |
1: | |
/**** Store into memory ****/ | |
movb %al, (%rbx, %r12) | |
incq %r12 /* increase number of read bytes */ | |
cmpq source_len, %r12 | |
jl 1f | |
/**** Double source code buffer memory ****/ | |
/*void *source_ptr = mremap(source_ptr, source_len, source_len * 2, MREMAP_MAYMOVE) */ | |
movq source_ptr, %rdi | |
movq source_len, %rsi | |
movq %rsi, %rdx | |
addq %rdx, %rdx | |
movq %rdx, source_len | |
movq $1, %r10 /* MREMAP_MAYMOVE = $1 */ | |
movq $25, %rax | |
syscall | |
cmpq $-4095, %rax | |
jae exit_syscall_error | |
movq %rax, source_ptr | |
movq %rax, %rbx | |
1: | |
jmp source_read_loop | |
read_loop_eof: | |
/***** Close Source file *****/ | |
/* close(source_fd) */ | |
movq source_fd, %rdi | |
movq $3, %rax | |
syscall | |
cmpq $-4095, %rax | |
jae exit_syscall_error | |
/**** ADD NULL TERMINATOR ****/ | |
/**** Doube buffer size if needed ****/ | |
cmpq source_len, %r12 | |
jl 1f | |
movq source_ptr, %rdi | |
movq source_len, %rsi | |
movq %rsi, %rdx | |
addq %rdx, %rdx | |
movq %rdx, source_len | |
movq $1, %r10 /* MREMAP_MAYMOVE = $1 */ | |
movq $25, %rax | |
syscall | |
cmpq $-4095, %rax | |
jae exit_syscall_error | |
movq %rax, source_ptr | |
movq %rax, %rbx | |
1: /* no resize needed */ | |
xorq %rax, %rax | |
movb %al, (%rbx, %r12) /* NULL TERMINATOR */ | |
/**** SYNTAX CHECK - Check if brackets are balanced ****/ | |
xorq %rcx, %rcx | |
xorq %rdx, %rdx | |
validate_syntax_loop: | |
movb (%rbx, %rcx), %al | |
testb %al, %al | |
jz validate_syntax_loop_end | |
cmpb $'[', %al | |
jne 1f | |
incq %rdx | |
jmp 2f | |
1: | |
cmpb $']', %al | |
jne 2f | |
decq %rdx | |
js exit_failure /* signal bit is set, number is negative, unbalanced brackets */ | |
2: | |
incq %rcx | |
jmp validate_syntax_loop | |
validate_syntax_loop_end: | |
testq %rdx, %rdx | |
jnz exit_failure /* Anything other than 0 means unbalanced brackets */ | |
/******** INTERPRETER LOOP ************/ | |
movq %rbx, %r12 /* r12 -> instruction pointer regsiter */ | |
movq $data_buff, %r13 /* r13 -> data pointer regsiter */ | |
interpreter_loop: | |
movb (%r12), %al | |
test %al, %al | |
jz exit_success /* Finished */ | |
cmpb $'>', %al | |
je inc_data_ptr | |
cmpb $'<', %al | |
je dec_data_ptr | |
cmpb $'+', %al | |
je inc_byte | |
cmpb $'-', %al | |
je dec_byte | |
cmpb $'.', %al | |
je print_byte | |
cmpb $',', %al | |
je read_byte | |
cmpb $'[', %al | |
je jmp_ifz | |
cmpb $']', %al | |
je jmp_ifnz | |
jmp exit_failure /* code should not reach this */ | |
/* > */ | |
inc_data_ptr: | |
incq %r13 | |
incq %r12 | |
jmp interpreter_loop | |
/* < */ | |
dec_data_ptr: | |
decq %r13 | |
incq %r12 | |
jmp interpreter_loop | |
/* + */ | |
inc_byte: | |
movb (%r13), %al | |
incb %al | |
movb %al, (%r13) | |
incq %r12 | |
jmp interpreter_loop | |
/* - */ | |
dec_byte: | |
movb (%r13), %al | |
decb %al | |
movb %al, (%r13) | |
incq %r12 | |
jmp interpreter_loop | |
/* . */ | |
print_byte: | |
/* 1 -> ssize_t write(int fd, const void *buf, size_t count); */ | |
movq $1, %rdi /* stdout = $1 */ | |
movq %r13, %rsi | |
movq $1, %rdx /* print single byte */ | |
movq $1, %rax | |
syscall | |
incq %r12 | |
jmp interpreter_loop | |
/* , */ | |
read_byte: | |
/* 0 -> ssize_t read(int fd, void *buf, size_t count); */ | |
movq $0, %rdi /* stdin = $0 */ | |
movq %r13, %rsi | |
movq $1, %rdx /* read single byte */ | |
movq $0, %rax | |
syscall | |
incq %r12 | |
jmp interpreter_loop | |
/* [ */ | |
jmp_ifz: | |
movb (%r13), %al | |
testb %al, %al | |
jnz 3f | |
xorq %r10, %r10 /* math counter */ | |
1: | |
incq %r12 | |
movb (%r12), %al | |
cmpb $']', %al | |
je 2f | |
cmpb $'[', %al | |
jne 1b | |
incq %r10 | |
jmp 1b | |
2: | |
testq %r10, %r10 | |
jz 3f | |
decq %r10 | |
jmp 1b | |
3: | |
incq %r12 | |
jmp interpreter_loop | |
/* ] */ | |
jmp_ifnz: | |
movb (%r13), %al | |
testb %al, %al | |
jz 3f | |
xorq %r10, %r10 /* match counter */ | |
1: | |
decq %r12 | |
movb (%r12), %al | |
cmpb $'[', %al | |
je 2f | |
cmpb $']', %al | |
jne 1b | |
incq %r10 | |
jmp 1b | |
2: | |
testq %r10, %r10 | |
jz 3f | |
dec %r10 | |
jmp 1b | |
3: | |
incq %r12 | |
jmp interpreter_loop | |
exit_success: | |
call unmap_allocs | |
cmpq $-4095, %rax | |
jae exit_syscall_error | |
/* exit(0) */ | |
xorq %rdi, %rdi | |
jmp exit | |
exit_failure: | |
call unmap_allocs | |
cmpq $-4095, %rax | |
jae exit_syscall_error | |
/* exit(1) */ | |
movq $1, %rdi | |
jmp exit | |
exit_syscall_error: | |
pushq %rax | |
call unmap_allocs | |
/* exit(errno) */ | |
popq %rdi | |
negq %rdi | |
jmp exit | |
exit: | |
/* exit(%rdi) */ | |
movq $60, %rax | |
syscall | |
unmap_allocs: | |
/* munmap(source_ptr, source_len) */ | |
movq source_ptr, %rdi | |
movq source_len, %rsi | |
movq $11, %rax | |
syscall | |
ret | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment