Bootloader golf: Brainf ***


20

Tạo một bộ tải khởi động thực thi chương trình Brainfuck đã cho. Đây là , vì vậy chương trình có ít byte nhất sẽ thắng. Là một bộ tải khởi động, kích thước của chương trình được tính bằng các byte khác không trong mã được biên dịch.

Brainfuck

30000 tế bào tràn 8 bit. Con trỏ kết thúc tốt đẹp.

Một số lưu ý về hoạt động:

  • Đầu vào phải được đọc theo cách mà tất cả các ký tự ASCII có thể in được hỗ trợ chính xác. Các tổ hợp phím khác có thể chèn một ký tự tùy ý hoặc không làm gì cả.
  • Đọc đầu vào của người dùng phải là bộ đệm ký tự, không được đệm dòng.
  • Đọc đầu vào của người dùng phải lặp lại ký tự chèn.
  • Đầu ra phải tuân theo trang mã 437 hoặc bộ mã mặc định bộ điều hợp VGA tích hợp của bạn.

Bộ tải khởi động

Đây là một bộ tải khởi động x86. Một bộ nạp khởi động kết thúc với 55 AAtrình tự truyền thống . Mã của bạn phải chạy trên VirtualBox, Qemu hoặc trình giả lập x86 nổi tiếng khác.

Đĩa

Brainfuck thực thi được đặt tại khu vực đĩa thứ hai, ngay sau bộ tải khởi động của bạn, được đặt, như thường lệ, trong phần MBR, khu vực đầu tiên trên đĩa. Mã bổ sung (bất kỳ mã nào trên 510 byte) có thể được đặt tại các khu vực đĩa khác. Thiết bị lưu trữ của bạn phải là ổ cứng hoặc đĩa mềm.

STDIO

Tất nhiên, bộ tải khởi động không thể có quyền truy cập vào các khả năng IO của hệ điều hành. Do đó, các chức năng BIOS được sử dụng thay thế để in văn bản và đọc dữ liệu nhập của người dùng.

Bản mẫu

Để bắt đầu, đây là một mẫu đơn giản được viết bằng cụm Nasm (cú pháp intel):

[BITS 16]
[ORG 0x7c00]

; first sector:

boot:
    ; initialize segment registers
    xor ax, ax
    mov ds, ax
    mov es, ax
    mov ss, ax

    ; 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


; fill sector
times (0x200 - 2)-($-$$) db 0

; boot signature
db 0x55, 0xaa

; second sector:

db 'brainfuck code here'

times 0x400-($-$$) db 0

Biên dịch cái này khá dễ:

nasm file.asm -f bin -o boot.raw

Và chạy nó. Ví dụ: với Qemu:

qemu-system-i386 -fda boot.raw

Thông tin bổ sung : OsDev wiki , Wikipedia


21
Input must be redTôi khá chắc chắn rằng hầu hết các bộ tải khởi động không hỗ trợ màu sắc.
Vụ kiện của Quỹ Monica

4
Người ta nên giải thích cho các nhà phát triển trẻ hơn một đĩa mềm là gì!
bobbel

1
@bobbel Hãy bắt đầu với bộ nạp khởi động
Bálint

5
Không phải là tôi thấy tiêu đề này gây khó chịu, nhưng làm thế nào là nó có thể? Theo meta , không thể đặt "brainfuck" vào tiêu đề.
DJMcMayhem

Chúng tôi có thể sử dụng hơn 30k tế bào?
Máy

Câu trả lời:


13

171 byte 1

Wooohoooo! Mất nửa ngày, nhưng thật vui ...

Vì vậy, đây là. Tôi nghĩ rằng nó phù hợp với thông số kỹ thuật (bao quanh con trỏ ô, tiếng vang của các ký tự đầu vào, đọc char bằng char, echo của ký tự đầu vào, ...), và nó dường như thực sự hoạt động (tốt, tôi đã không thử nhiều chương trình , nhưng với sự đơn giản của ngôn ngữ, phạm vi bảo hiểm không tệ đến thế, tôi nghĩ vậy).

Hạn chế

Một điều quan trọng: nếu chương trình brainfuck của bạn có chứa các ký tự khác ngoài 8 hướng dẫn brainfuck hoặc nếu []không được cân bằng tốt, nó sẽ sụp đổ với bạn, mouhahahaha!

Ngoài ra, chương trình brainfuck không thể vượt quá 512 byte (một sector). Nhưng điều này có vẻ phù hợp vì bạn nói "Brainfuck thực thi được đặt tại khu vực đĩa thứ hai" .

Chi tiết cuối cùng: Tôi đã không khởi tạo rõ ràng các ô về không. Qemu dường như làm điều đó cho tôi và tôi đang dựa vào điều này, nhưng tôi không biết liệu một BIOS thực sự trên một máy tính thực sự sẽ làm điều đó hay không (dù sao thì việc khởi tạo sẽ chỉ mất thêm vài byte).

Mật mã

(dựa trên mẫu của bạn và nhân tiện, cảm ơn vì điều này, tôi sẽ không bao giờ thử nếu không có nó):

[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

Thủ thuật được sử dụng

Ok, tôi đã lừa dối một chút. Vì bạn đã nói "là một bộ tải khởi động, kích thước của chương trình được tính bằng các byte khác không trong mã được biên dịch" , tôi đã làm cho mã nhỏ hơn bằng cách cho phép "lỗ hổng" giữa việc thực hiện tám mã não. Bằng cách này, tôi không cần một chuỗi các bài kiểm tra lớn, bảng nhảy hoặc bất cứ điều gì: Tôi chỉ cần nhảy đến "opcode id" (từ 0 đến 8) nhân với 32 để thực hiện hướng dẫn brainfuck (đáng lưu ý rằng điều đó có nghĩa là việc thực hiện các hướng dẫn không thể mất hơn 32 byte).

Hơn nữa, để có được "opcode id" này từ nhân vật chương trình brainfuck, tôi nhận thấy chỉ cần một chút xáo trộn là cần thiết. Thật vậy, nếu chúng ta chỉ xem xét các bit 0, 1 và 4 của ký tự opcode, chúng ta sẽ có 8 kết hợp duy nhất:

   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.

Và thật may mắn cho tôi, thực sự có một opcode yêu cầu hơn 32 byte để thực hiện, nhưng đó là cái cuối cùng (nhảy về phía trước [). Khi có nhiều phòng sau, mọi thứ đều ổn.

Một mẹo khác: Tôi không biết làm thế nào một trình thông dịch Brainfuck điển hình hoạt động, nhưng, để làm cho mọi thứ nhỏ hơn nhiều, tôi đã không thực sự thực hiện ]như là "Quay trở lại sau tương ứng [nếu dữ liệu tại con trỏ là khác không" . Thay vào đó, tôi luôn quay trở lại tương ứng [, và từ đây, áp dụng lại cách [thực hiện điển hình (mà sau đó, cuối cùng, sẽ tiếp tục sau khi ]một lần nữa nếu cần). Đối với điều này, mỗi lần tôi trang bị a [, tôi đặt "con trỏ lệnh Brainfuck" hiện tại lên ngăn xếp trước khi thực hiện các hướng dẫn bên trong và khi tôi gặp phải một], Tôi bật lại con trỏ chỉ dẫn. Khá nhiều như thể đó là một cuộc gọi đến một chức năng. Do đó, về mặt lý thuyết, bạn có thể vượt qua ngăn xếp bằng cách tạo ra nhiều vòng lặp được định nghĩa, nhưng không phải với giới hạn 512 byte hiện tại của mã brainfuck.


1. Bao gồm các byte không là một phần của chính mã, nhưng không bao gồm các byte là một phần của một số phần đệm

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.