ブートローダーゴルフ:Brainf ***


20

特定のBrainfuckプログラムを実行するブートローダーを作成します。これはであるため、バイト数が最小のプログラムが優先されます。ブートローダーであるため、プログラムのサイズはコンパイルされたコードでゼロ以外のバイトでカウントされます。

ブレインファック

30000の8ビットオーバーフローセル。ポインターがラップオーバーします。

操作に関する注意事項:

  • 入力は、すべての印刷可能なASCII文字が正しくサポートされるように読み取る必要があります。他のキーストロークは、任意の文字を挿入するか、まったく何もしません。
  • ユーザー入力の読み取りは、行バッファーではなく文字バッファーでなければなりません。
  • ユーザー入力を読み取るには、挿入された文字をエコーする必要があります。
  • 出力は、コードページ437または組み込みのVGAアダプターのデフォルトコードページのいずれかに従う必要があります。

ブートローダー

これはx86ブートローダーです。ブートローダーは、従来の55 AAシーケンスで終了します。コードは、VirtualBox、Qemu、または他の有名なx86エミュレーターで実行する必要があります。

ディスク

実行可能Brainfuckは、ブートローダーの直後の2番目のディスクセクターにあり、通常はMBRセクションのディスクの最初のセクターに配置されます。追加のコード(510バイトを超えるコード)は、他のディスクセクターに配置できます。ストレージデバイスは、ハードドライブまたはフロッピーディスクである必要があります。

STDIO

もちろん、ブートローダーはオペレーティングシステムのIO機能にアクセスできません。そのため、テキストの印刷とユーザー入力の読み取りに代わりにBIOS機能が使用されます。

テンプレート

まず、Nasm(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

これのコンパイルはとても簡単です:

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

そして、それを実行します。たとえば、Qemuの場合:

qemu-system-i386 -fda boot.raw

追加情報:OsDev wikiWikipedia


21
Input must be redほとんどのブートローダーは色をネイティブにサポートしていないと確信しています。
ファンドモニカの訴訟

4
若い開発者にフロッピーディスクとは何かを説明する必要があります!
ボベル

1
ブートローダと@bobbelレッツ・スタート
バリント

5
このタイトルが不快だと思うわけではありませんが、どのように可能ですか?metaによると、タイトルに「brainfuck」を入れることは不可能です。
DJMcMayhem

3万個を超えるセルを使用できますか?
CalculatorFeline

回答:


13

171バイト1

すごい!半日かかりましたが、楽しかったです...

だから、ここにある。私はそれが仕様(セルポインタのラップアラウンド、入力上の文字のエコー、文字ごとの読み取り、入力文字のエコーなど)に準拠していると思います、そして実際に動作するようです(まあ、私は多くのプログラムを試しませんでした、しかし、言語のシンプルさを考えると、カバレッジはそれほど悪くはないと思います)。

制限事項

1つの重要なこと:Brainfuckプログラムに8つのBrainfuck命令以外の文字が含まれて[]いる場合、またはバランスが悪い場合、mouhahahahaがクラッシュします!

また、brainfuckプログラムは512バイト(セクター)を超えることはできません。しかし、「実行可能なBrainfuckは2番目のディスクセクターにある」と言うので、これは適合しているようです。

最後の詳細:セルを明示的にゼロに初期化しませんでした。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

使用されるトリック

OK、少しだまされました。あなたが言っているので、「ブートローダーであり、プログラムのサイズは、コンパイルされたコード内の非ゼロバイトでカウントされた」、Iは8つのbrainfuckオペコードの実装の間に「穴」を可能にすることによって、コード小さくします。このように、大きなテストシーケンスやジャンプテーブルなどは必要ありません。brainfuck命令を実行するために32を掛けたbrainfuck "opcode id"(0〜8)にジャンプするだけです(注意する価値があります)つまり、命令の実装は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バイト以上を必要とするオペコードが1つありますが、それは最後のものです(ジャンプフォワード[)。あとに余裕があるので、すべてが順調です。

その他のトリック:典型的なBrainfuckインタープリターがどのように機能するかわかりませんが、物事をより小さくするために][ポインターのデータがゼロでない場合、対応するものの後にジャンプ」として実装しませんでした。代わりに、私は常に対応するに戻り[、ここから、典型的な[実装を再適用します(その後、]必要に応じて、最終的には再び先に進みます)。このため、aを計算するたびに[、内部命令を実行する前にスタックに現在の「brainfuck命令ポインター」を配置し、]、命令ポインタをポップバックします。まるで関数の呼び出しであるかのように。したがって、理論的には多くの複合ループを作成することでスタックをオーバーフローさせることができますが、とにかくブレインファックコードの現在の512バイトの制限はありません。


1.コード自体の一部であるゼロバイトを含みますが、パディングの一部であるゼロバイトは含みません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.