Bootloader golf: Brainf ***


20

Crea un bootloader che esegue il programma Brainfuck dato. Questo è , quindi vince il programma con meno byte. Essendo un bootloader, la dimensione del programma viene conteggiata in byte diversi da zero nel codice compilato.

Brainfuck

30000 celle a straripamento di 8 bit. Il puntatore si chiude.

Alcune note sulle operazioni:

  • L'input deve essere letto in modo che tutti i caratteri ASCII stampabili siano supportati correttamente. Altri tasti possono o inserire un carattere arbitrario o non fare nulla.
  • La lettura dell'input dell'utente deve essere bufferizzata in caratteri, non in buffer di riga.
  • La lettura dell'input dell'utente deve fare eco al carattere inserito.
  • L'output deve seguire la tabella codici 437 o la tabella codici predefinita degli adattatori VGA incorporati.

Boot loader

Questo è un bootloader x86. Un bootloader termina con la 55 AAsequenza tradizionale . Il codice deve essere eseguito su VirtualBox, Qemu o altro emulatore x86 noto.

Disco

Brainfuck eseguibile si trova nel secondo settore del disco, subito dopo il bootloader, che è posizionato, come di solito, nella sezione MBR, il primo settore sul disco. Codice aggiuntivo (qualsiasi codice superiore a 510 byte) può essere posizionato in altri settori del disco. Il dispositivo di archiviazione deve essere un disco rigido o un disco floppy.

STDIO

Naturalmente, un bootloader non può avere accesso alle funzionalità di I / O del sistema operativo. Pertanto le funzioni BIOS vengono utilizzate invece per la stampa di testo e la lettura dell'input dell'utente.

Modello

Per iniziare, ecco un semplice modello scritto nell'assieme Nasm (sintassi 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

Compilare questo è abbastanza semplice:

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

E eseguirlo. Ad esempio, con Qemu:

qemu-system-i386 -fda boot.raw

Informazioni aggiuntive : OsDev wiki , Wikipedia


21
Input must be redSono abbastanza sicuro che la maggior parte dei bootloader non supporta il colore in modo nativo.
Finanzi la causa di Monica

4
Si dovrebbe spiegare agli sviluppatori più giovani cos'è un floppy disk!
Bob

1
@bobbel Cominciamo con il bootloader
Bálint,

5
Non che trovo questo titolo offensivo, ma come è possibile? Secondo meta , è impossibile inserire "brainfuck" nel titolo.
DJMcMayhem

Possiamo usare più di 30.000 celle?
Calcolatrice

Risposte:


13

171 byte 1

Wooohoooo! Ci è voluta mezza giornata, ma è stato divertente ...

Quindi eccolo qui. Penso che sia conforme alle specifiche (avvolgimento del puntatore di cella, eco dei caratteri sull'input, lettura dei caratteri per carattere, eco dei caratteri di input, ...) e sembra funzionare (beh, non ho provato molti programmi , ma data la semplicità della lingua, la copertura non è poi così male, penso).

limitazioni

Una cosa importante: se il tuo programma Brainfuck contiene altri caratteri oltre alle 8 istruzioni Brainfuck, o se []non sono ben bilanciati, si bloccherà su di te, mouhahahaha!

Inoltre, il programma brainfuck non può superare i 512 byte (un settore). Ma questo sembra conforme poiché dici "l'eseguibile Brainfuck si trova nel secondo settore del disco" .

Ultimo dettaglio: non ho inizializzato esplicitamente le celle a zero. Sembra che Qemu lo faccia per me e io mi affido a questo, ma non so se lo farebbe un vero BIOS su un vero computer (l'inizializzazione richiederebbe comunque qualche byte in più).

Il codice

(basato sul tuo modello, e comunque, grazie per questo, non avrei mai provato senza di esso):

[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

Trucchi usati

Ok, ho tradito un po '. Dato che hai detto "essendo un bootloader, la dimensione del programma è conteggiata in byte diversi da zero nel codice compilato" , ho ridotto il codice consentendo "buchi" tra l'implementazione degli otto codici operativi Brainfuck. In questo modo, non ho bisogno di una grande sequenza di test, di una tabella di salto o di qualsiasi altra cosa: salto semplicemente al "opcode id" brainfuck (da 0 a 8) moltiplicato per 32 per eseguire l'istruzione brainfuck (vale la pena notare che significa che l'implementazione delle istruzioni non può richiedere più di 32 byte).

Inoltre, per ottenere questo "opcode id" dal personaggio del programma brainfuck recuperato, ho notato che era necessario solo un po 'di shuffle. Infatti, se consideriamo solo i bit 0, 1 e 4 del carattere opcode, finiamo con le 8 combinazioni uniche:

   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.

E, fortunatamente per me, esiste in realtà un opcode che richiede più di 32 byte per essere implementato, ma è l'ultimo (vai avanti [). Dato che c'è più spazio dopo, tutto va bene.

Altro trucco: non so come funzioni un tipico interprete brainfuck, ma, per rendere le cose molto più piccole, in realtà non ho implementato ]come "Salta indietro dopo il corrispondente [se i dati sul puntatore sono diversi da zero" . Invece, torno sempre alla corrispondente [, e, da qui, riapplico l' [implementazione tipica (che poi, eventualmente, va avanti dopo di ]nuovo se necessario). Per questo, ogni volta che incontro un [, metto l'attuale "puntatore di istruzioni Brainfuck" nello stack prima di eseguire le istruzioni interne e quando incontro un], Riavvio il puntatore dell'istruzione. Praticamente come se fosse una chiamata a una funzione. È quindi possibile teoricamente overflow dello stack creando molti molti loop nascosti, ma non con l'attuale limitazione di 512 byte del codice brainfuck.


1. Compresi gli zero byte che facevano parte del codice stesso, ma non quelli che facevano parte di un riempimento

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.