171字节1
oo!花了半天时间,但是很有趣。
所以,就在这里。我认为它符合规范(单元格指针的环绕,输入字符回显,按字符读取字符,输入字符回显...),而且似乎确实有效(嗯,我没有尝试很多程序,但考虑到该语言的简单性,我认为覆盖范围还不错。
局限性
重要的一件事:如果您的Brainfuck程序包含8个Brainfuck指令以外的其他字符,或者如果[]
均衡性不佳,它将对您造成崩溃,mouhahahaha!
另外,brainfuck程序不能超过512字节(一个扇区)。但是,这似乎是合规的,因为您说“可执行的Brainfuck位于第二个磁盘扇区”。
最后一个细节:我没有明确将单元格初始化为零。Qemu似乎是为我这样做的,并且我依赖于此,但是我不知道真实计算机上的真实BIOS是否会这样做(无论如何,初始化只会花费几个字节)。
代码
(根据您的模板,顺便说一句,感谢您,如果没有它,我将永远不会尝试过):
[BITS 16]
[ORG 0x7C00]
%define cellcount 30000 ; you can't actually increase this value much beyond this point...
; first sector:
boot:
; initialize segment registers
xor ax, ax
mov ss, ax
mov ds, ax
mov es, ax
jmp 0x0000:$+5
; initialize stack
mov sp, 0x7bfe
; load brainfuck code into 0x8000
; no error checking is used
mov ah, 2 ; read
mov al, 1 ; one sector
mov ch, 0 ; cylinder & 0xff
mov cl, 2 ; sector | ((cylinder >> 2) & 0xc0)
mov dh, 0 ; head
; dl is already the drive number
mov bx, 0x8000 ; read buffer (es:bx)
int 0x13 ; read sectors
; initialize SI (instruction pointer)
mov si, bx ; 0x8000
; initialize DI (data pointer)
mov bh, 0x82
mov di, bx ; 0x8200
decode:
lodsb ; fetch brainfuck instruction character
.theend:
test al, al ; endless loop on 0x00
jz .theend
and ax, 0x0013 ; otherwise, bit shuffling to get opcode id
shl ax, 4
shl al, 2
shr ax, 1
add ax, getchar ; and compute instruction implementation address
jmp ax
align 32, db 0
getchar:
xor ah, ah
int 0x16
cmp al, 13
jne .normal
mov al, 10 ; "enter" key translated to newline
.normal:
mov byte [di], al
push di
jmp echochar
align 32, db 0
decrementdata:
dec byte [di]
jmp decode
align 32, db 0
putchar:
push di
mov al, byte [di]
echochar:
mov ah, 0x0E
xor bx, bx
cmp al, 10 ; newline needs additional carriage return
jne .normal
mov al, 13
int 0x10
mov al, 10
.normal:
int 0x10
pop di
jmp decode
align 32, db 0
incrementdata:
inc byte [di]
jmp decode
align 32, db 0
decrementptr:
dec di
cmp di, 0x8200 ; pointer wraparound check (really, was that necessary?)
jge decode
add di, cellcount
jmp decode
align 32, db 0
jumpback:
pop si
jmp jumpforward
align 32, db 0
incrementptr:
inc di
cmp di, 0x8200+cellcount ; pointer wraparound check
jl decode
sub di, cellcount
jmp decode
align 32, db 0
jumpforward:
cmp byte [di], 0
jz .skip
push si
jmp decode
.skip:
xor bx, bx ; bx contains the count of [ ] imbrication
.loop:
lodsb
cmp al, '['
je .inc
cmp al, ']'
jne .loop
test bx, bx
jz decode
dec bx
jmp .loop
.inc:
inc bx
jmp .loop
; fill sector
times (0x1FE)-($-$$) db 0
; boot signature
db 0x55, 0xAA
; second sector contains the actual brainfuck program
; currently: "Hello world" followed by a stdin->stdout cat loop
db '++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.,[.,]'
times 0x400-($-$$) db 0
使用技巧
好吧,我有点作弊。既然您说过“作为引导加载程序,程序的大小将在已编译的代码中以非零字节计”,所以我通过允许八个“笨蛋”操作码的实现之间存在“漏洞”来使代码更小。这样,我不需要进行大量的测试,跳转表或其他任何操作:我只需跳转到Brainfuck的“操作码ID”(从0到8)乘以32即可执行Brainfuck指令(值得注意的是,这意味着指令的执行不能超过32个字节)。
此外,为了从获取的Brainfuck程序字符中获取此“操作码ID”,我注意到只需要一点点改组即可。确实,如果仅考虑操作码字符的位0、1和4,则最终得到8个唯一组合:
X XX
00101100 0x2C , Accept one byte of input, storing its value in the byte at the pointer.
00101101 0x2D - Decrement (decrease by one) the byte at the pointer.
00101110 0x2E . Output the value of the byte at the pointer.
00101011 0x2B + Increment (increase by one) the byte at the pointer.
00111100 0x3C < Decrement the pointer (to point to the next cell to the left).
01011101 0x5D ] Jump back after the corresp [ if data at pointer is nonzero.
00111110 0x3E > Increment the pointer (to point to the next cell to the right).
01011011 0x5B [ Jump forward after the corresp ] if data at pointer is zero.
而且,幸运的是,实际上有一个操作码需要32个以上的字节来实现,但这是最后一个(向前跳转[
)。随着之后有更多的空间,一切都很好。
其他技巧:我不知道典型的“脑操”解释器是如何工作的,但是,为了使事情变得更小,我实际上并没有实现 ]
为“ [
如果指针处的数据为非零,则在相应的位置之后跳回”。取而代之的是,我总是回到相应的[
,然后从这里重新应用典型的[
实现(然后,最终,]
如果需要,可以再次进行)。为此,每次访问a时[
,我都会在执行内部指令之前以及当遇到a时将当前的“ brainfuck指令指针”放到堆栈上]
,我弹出指令指针。几乎就像是对函数的调用一样。因此,从理论上讲,您可以通过执行许多合并的循环来使堆栈溢出,但是无论如何,当前脑筋代码的限制不是512字节。
1.包括代码本身的零字节,但不包括部分填充的零字节
Input must be red
我敢肯定,大多数引导程序本身都不支持颜色。