asm: gc_sweep page-fault on chunk-abandonment gap (segfault at chunk_end)
When heap_alloc walks off the end of a chunk with < 16 tail bytes,
.ha_grow_no_pad skips padding and mmaps a fresh chunk — but
gc_chunk_end[N] for the abandoned chunk stayed at its full mmap
end while %r15 (high-water) sat 1..15 bytes short. The gap held
mmap-zeros that gc_sweep's .gsw_walk decoded as fake dead blocks
(header == 0, payload size == 0, mark == 0). The walker stepped
through the zeros 8 bytes at a time, and on the iteration where
%rbx == chunk_end - 8 the .gsw_dead path stored the free-list
next-pointer to 0x8(%rbx) == chunk_end — the first byte of an
unmapped page — and segfaulted with error 7.
Reproducer (inside QEMU guest, was crashing all three asm
binaries):
lumbda-full tests/unit/test-mod-solinas.lsp
→ segfault at <ptr ending 000> ip:4017d9
(.gsw_dead: mov %rdx, 0x8(%rbx))
Fix: at .ha_grow_no_pad, snapshot %r15 into gc_chunk_end[N] before
allocating the new chunk. In the padded path above the label this
is a no-op (r15 already == r13). In the un-padded path it pins
the walk bound to the high-water mark so the sweep never enters
the gap.
After fix, on lumbda-full inside the ecdsa QEMU guest:
- ecdsa test-mod-solinas: 39/39 PASS (Solinas vs Litinski
byte-equal at p ∈ {11, 13, 251})
- upstream asm test.sh: 158/158 PASS
Discovered while diagnosing ecdsa task #45.
This commit is contained in:
parent
3723a937e3
commit
698a5d5f04
1 changed files with 14 additions and 0 deletions
14
asm/lumbda.s
14
asm/lumbda.s
|
|
@ -1044,6 +1044,20 @@ heap_alloc:
|
|||
movq %r15, gc_free_list(%rip)
|
||||
addq %rax, %r15 # %r15 now == %r13
|
||||
.ha_grow_no_pad:
|
||||
# Pin the abandoned chunk's stored end to %r15 (its high-water).
|
||||
# In the padded path above this is a no-op (r15 already == r13).
|
||||
# In the un-padded path (tail < 16) r15 sits short of r13 and the
|
||||
# gap holds mmap-zeros. Without this update, gc_sweep would still
|
||||
# walk to the original mmap end, decode the zero pages as fake
|
||||
# dead blocks, and on the final iteration write the free-list
|
||||
# next-pointer to (chunk_end - 8) + 8 == chunk_end — the first
|
||||
# byte of an unmapped page — and segfault in .gsw_dead. Tying
|
||||
# chunk_end to r15 stops the walker at the high-water mark.
|
||||
# Discovered while diagnosing ecdsa task #45, 2026-06-05.
|
||||
movq gc_chunk_count(%rip), %rdx
|
||||
decq %rdx
|
||||
leaq gc_chunk_end(%rip), %rcx
|
||||
movq %r15, (%rcx,%rdx,8)
|
||||
# Chunk size: at least HEAP_SIZE, but large enough for this one
|
||||
# block. A single big allocation (e.g. file->string on a ~3 MB
|
||||
# PDF) would otherwise loop forever because each mmap'd 1 MB
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue