Fix the uninitialized brainfuck tape (fib.bf going haywire after 89)#1
Merged
Conversation
…ion-header table)
The compiler in the blog post worked for hello.bf but fib.bf "slowly went
haywire" after printing 89 — diagnosed there as "some weird miscompilation in
the number-printing routine." It wasn't a miscompilation; the emitted code is
fine. The data tape was never zeroed:
- `bss_o = shstrtab_o + shstrtab.len` comes out exactly equal to
`sections_off`, so the address handed to the program as cell 0 pointed
straight at the ELF section-header table in the file image.
- the single PT_LOAD had `p_filesz == p_memsz`, so there was no zero-fill
region at all — the `.bss` *section* (SHT_NOBITS) bought us nothing.
So cells 0..7 read as 0 only by luck (they land inside the all-zero NULL
section header) and cell 8+ read leftover section-header bytes. hello.bf only
touches cells 0..6, so it survived; fib.bf keeps one decimal digit per cell and
walks rightward as the numbers grow — the instant 89 -> 144 needs a third digit
cell it reads a never-written cell, gets garbage, and the output falls apart.
Fix: put the tape past the end of the file image and set `p_memsz` accordingly,
so the loader zero-fills it. Also `read()` was reading from fd 1 (stdout)
instead of fd 0 (stdin).
Adds a GitHub Actions workflow that builds bz on x86-64 Linux, compiles the
sample programs to native ELF, runs them, and checks output — including fib.bf
getting past 89 and a torture test that touches a cell ~3000 deep into the
(now genuinely zeroed) tape.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What was wrong
hello.bfworked butfib.bf"slowly went haywire" after printing89— the blog post blamed "some weird miscompilation in the number-printing routine." It isn't a miscompilation; the emitted x86-64 is fine. The brainfuck tape was never zero-initialized — it was aliased on top of the ELF section-header table.In
main.zig:bss_o = shstrtab_o + shstrtab.lencomes out exactly equal tosections_off(becausedatais empty), so the address handed to the program as cell 0 (r10 = base_point + bss_o) pointed straight at the section-header table in the file image.PT_LOADhadp_filesz == p_memsz, so there was no zero-fill region at all — the.bsssection isSHT_NOBITSbut the segment gave us nothing zeroed.So cells 0–7 read as
0only by luck (they land inside the all-zero NULL section header) and cell 8+ read leftover section-header bytes.hello.bfonly ever touches cells 0–6, so it survived.fib.bfkeeps one decimal digit per cell and walks rightward as the numbers grow — the instant89 → 144needs a third digit cell it reads a never-written cell, gets garbage, and the output falls apart.(Bonus:
read()was reading from fd1(stdout) instead of fd0(stdin) — fixed too.)The fix
Put the tape past the end of the file image and bump
p_memszto cover it, so the loader zero-fills it for us. Minimal change, no modernizing — still builds with the same Zig 0.8.1 it was written against.Proof, on a real x86-64 Linux box (new CI workflow)
.github/workflows/bf.ymlinstalls Zig 0.8.1, runszig build, compiles the sample programs to native ELF executables and checks their output:hello.bf→Hello World!fib.bf→0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610— past 89>×3000, write'A', walk back, all reads correct (BAB) — the pre-fix compiler couldn't keep even one page of tape, let alone a zeroed onetest2.bf→NoKnown follow-ups (left out to keep this minimal)
add r10, 8) but.bssisbss_lenbytes (default 30000), so only ~3750 cells are usable; either narrow the stride or scalebss_len.-bis parsed into a?u32but never passed togenElfAndWriteToFs(ElfOpts.bss_lenis alsou16).🤖 Generated with Claude Code