Bootloader golf: Brainf ***


20

สร้าง bootloader ที่รันโปรแกรม Brainfuck ที่กำหนด นี่คือดังนั้นโปรแกรมที่มีจำนวนไบต์น้อยที่สุดจะเป็นผู้ชนะ เป็น bootloader ขนาดของโปรแกรมจะถูกนับเป็นไบต์ที่ไม่เป็นศูนย์ในรหัสที่คอมไพล์

brainfuck

30000 8-bit ล้นเซลล์ พอยน์เตอร์แรปทับ

หมายเหตุบางประการเกี่ยวกับการทำงาน:

  • ต้องอ่านอินพุตในลักษณะที่สนับสนุนอักขระ ASCII ที่พิมพ์ได้ทั้งหมดอย่างถูกต้อง การกดแป้นอื่น ๆ อาจแทรกตัวอักษรใดก็ได้หรือไม่ทำอะไรเลย
  • การอ่านอินพุตของผู้ใช้จะต้องเป็นตัวอักษรบัฟเฟอร์ไม่ใช่บัฟเฟอร์บรรทัด
  • การอ่านอินพุตของผู้ใช้ต้องสะท้อนก้องอักขระ
  • ผลลัพธ์จะต้องเป็นไปตามโค้ดเพจ 437หรือโค้ดเพจเริ่มต้นของอะแดปเตอร์ VGA ในตัวของคุณ

bootloader

นี่คือ bootloader x86 bootloader ลงท้ายด้วย55 AAลำดับดั้งเดิม รหัสของคุณจะต้องทำงานบน VirtualBox, Qemu หรือโปรแกรมจำลอง x86 ที่รู้จักกันดีอื่น ๆ

ดิสก์

Brainfuck ที่เรียกใช้งานได้จะอยู่ที่เซกเตอร์ของดิสก์ที่สองหลังจากบูทโหลดเดอร์ของคุณซึ่งโดยปกติจะอยู่ในส่วน MBR เซกเตอร์แรกบนดิสก์ รหัสเพิ่มเติม (รหัสใด ๆ ที่มากกว่า 510 ไบต์) สามารถอยู่ที่ภาคดิสก์อื่น ๆ อุปกรณ์เก็บข้อมูลของคุณต้องเป็นฮาร์ดไดรฟ์หรือฟล็อปปี้ดิสก์

stdio

แน่นอน bootloader ไม่สามารถเข้าถึงความสามารถ IO ของระบบปฏิบัติการ ดังนั้นจึงใช้ฟังก์ชัน BIOS แทนการพิมพ์ข้อความและอ่านอินพุตของผู้ใช้

แบบ

ในการเริ่มต้นนี่เป็นเทมเพลตแบบง่ายที่เขียนในชุดประกอบ Nasm (intel syntax):

[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 wiki , Wikipedia


21
Input must be redฉันค่อนข้างมั่นใจว่า bootloaders ส่วนใหญ่ไม่สนับสนุนสี
คดีฟ้องร้องกองทุนโมนิก้า

4
หนึ่งควรอธิบายให้นักพัฒนาอายุน้อยว่าฟลอปปี้ดิสก์คืออะไร!
bobbel

1
@bobbel เริ่มต้นด้วย bootloader
Bálint

5
ไม่ใช่ว่าฉันพบว่าชื่อนี้เป็นที่น่ารังเกียจ แต่เป็นไปได้อย่างไร ตามmetaมันเป็นไปไม่ได้ที่จะใส่ "brainfuck" ในชื่อ
DJMcMayhem

เราสามารถใช้เซลล์มากกว่า 30k เซลล์ได้หรือไม่?
CalculatorFeline

คำตอบ:


13

171 ไบต์1

Wooohoooo! ใช้เวลาครึ่งวัน แต่มันสนุก ...

ดังนั้นนี่คือ ฉันคิดว่ามันสอดคล้องกับสเปค (โดยประมาณของตัวชี้เซลล์, เสียงก้องของตัวอักษรในอินพุต, การอ่านถ่านโดยถ่าน, เสียงก้องของอินพุตตัวอักษร, ... ) และดูเหมือนว่าจะใช้งานได้จริง (ดีฉันไม่ได้ลองหลายโปรแกรม แต่ด้วยความเรียบง่ายของภาษาความคุ้มครองก็ไม่ได้แย่ขนาดนั้นฉันคิดว่า)

ข้อ จำกัด

สิ่งหนึ่งที่สำคัญ: ถ้าโปรแกรม brainfuck ของคุณมีตัวอักษรอื่นนอกเหนือจาก 8 คำสั่ง brainfuck หรือถ้าโปรแกรม[]ไม่สมดุลกันมันก็จะผิดพลาดกับคุณ

นอกจากนี้โปรแกรม 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

เทคนิคที่ใช้

ตกลงฉันโกงนิดหน่อย เนื่องจากคุณบอกว่า"การเป็น bootloader ขนาดของโปรแกรมจะนับเป็นไบต์ที่ไม่เป็นศูนย์ในโค้ดที่คอมไพล์"ฉันทำให้โค้ดมีขนาดเล็กลงโดยอนุญาตให้ "รู" ระหว่างการใช้งาน opcodes brainfuck แปดตัว ด้วยวิธีนี้ฉันไม่ต้องการลำดับการทดสอบขนาดใหญ่ตารางการกระโดดหรืออะไรก็ตาม: ฉันแค่ข้ามไปที่ "รหัส opcode" (จาก 0 ถึง 8) brainfuck คูณด้วย 32 เพื่อดำเนินการคำสั่ง brainfuck (ควรสังเกตว่า มันหมายถึงการใช้งานคำแนะนำนั้นไม่สามารถใช้เกิน 32 ไบต์)

ยิ่งไปกว่านั้นเพื่อให้ได้ "รหัส opcode" นี้จากการนำโปรแกรมตัวอักษร brainfuck มาใช้ฉันก็สังเกตเห็นว่าจำเป็นต้องมีการสับบิตเล็กน้อย แน่นอนถ้าเราพิจารณาบิต 0, 1 และ 4 ของอักขระ opcode เราจะได้ชุดที่ไม่ซ้ำกัน 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 ไบต์ที่จะใช้ แต่มันเป็นคนสุดท้าย (กระโดดไปข้างหน้า[) เนื่องจากมีห้องเพิ่มเติมหลังจากนั้นทุกอย่างดี

เคล็ดลับอื่น ๆ : ผมไม่ทราบวิธีการที่ brainfuck ทั่วไปล่ามงาน แต่จะทำให้สิ่งที่มีขนาดเล็กมากผมไม่ได้จริงใช้]เป็น"กระโดดกลับมาหลังจากที่สอดคล้องกัน[หากข้อมูลที่ชี้ไม่ใช่ศูนย์" แต่ฉันมักจะกลับไปที่สอดคล้องกัน[และจากที่นี่อีกครั้งใช้ทั่วไป[การดำเนินงาน (ซึ่งในที่สุดจะไปข้างหน้าหลังจากที่]อีกครั้งถ้าจำเป็น) สำหรับสิ่งนี้ทุกครั้งที่ฉันเข้ารหัส[ฉันใส่ "ตัวชี้คำสั่ง brainfuck" ปัจจุบันลงบนสแต็กก่อนเรียกใช้คำแนะนำด้านในและเมื่อฉันพบ]ฉันกลับมาที่ตัวชี้คำสั่ง ค่อนข้างมากราวกับว่ามันเป็นการเรียกฟังก์ชั่น ในทางทฤษฎีแล้วคุณสามารถล้นสแต็กได้โดยสร้างลูปที่มีการวนรอบจำนวนมาก แต่ไม่ได้มีข้อ จำกัด 512 ไบต์ในปัจจุบันของรหัส brainfuck อยู่ดี


1. รวมศูนย์ไบต์ที่เป็นส่วนหนึ่งของรหัส แต่ไม่รวมถึงส่วนที่เป็นส่วนหนึ่งของการแพ็ด

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.