[WIP] Add Linux-compatible /proc/self/smaps emulation - #259
Conversation
Generate Linux-shaped /proc/self/smaps and /proc/<pid>/smaps snapshots from tracked guest VMAs so Redis can complete its ARM64 COW safety check under elfuse. Add growable VMA/string infrastructure, fork-aware accounting, and parser regression coverage.
jserv
left a comment
There was a problem hiding this comment.
Run make indent before committing.
Append live and shadow VMAs, then sort and merge once so fragmented maps and smaps snapshots avoid quadratic insertion work.
Thanks for the review. I’ve addressed all other review comments as well, including the dynamic-array and string-builder initialization/aliasing issues and the |
Track whether each guest VMA existed at the fork snapshot so post-fork mappings are excluded from the synthetic Shared_Dirty compatibility signal. Propagate the metadata through fork IPC and memory-region transformations, and add a regression test for post-fork mappings.
There was a problem hiding this comment.
4 issues found across 10 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="tests/test-proc-smap.c">
<violation number="1" location="tests/test-proc-smap.c:470">
P2: The new child mmap (rw-anon, 1 page) can coalesce with an adjacent rw-anon VMA once the smaps builder merges contiguous regions, so validate_layout's exact per-page boundary assertions for target's last page / the final stress page can break, making the fork test flaky or a false failure. Note the parent fixture already guards against exactly this by making the stress-start page read-only; postfork reintroduces the unguarded case in the child. Consider placing postfork at a dedicated non-adjacent address (e.g. mmap with a chosen high hint or MAP_FIXED_NOREPLACE atop a known gap) so the shared_dirty==0 probe cannot merge with the stress/target ranges.</violation>
</file>
<file name="src/syscall/mem.c">
<violation number="1" location="src/syscall/mem.c:247">
P1: Growing an inherited file-backed mapping can lose its source mapping when this extra `dup()` hits `EMFILE`: the mremap paths have already removed the source before `add_mremap_region()` fails. Pre-acquire both owned descriptors before destructive work, or restore the source snapshot on this failure path.</violation>
<violation number="2" location="src/syscall/mem.c:253">
P1: A second `mremap` of an allocation grown after fork now fails with `EFAULT`, because this split makes the logical mapping span two records while `sys_mremap` requires its source range fit one record. Handle contiguous fork-boundary pieces as one mremap source, or retain the boundary outside the region-table VMA split.</violation>
<violation number="3" location="src/syscall/mem.c:1891">
P1: Repeated post-fork `brk` growth creates overlapping `[heap]` regions: the original inherited segment remains the one found, so each later call adds `[fork_boundary, new_end)` over the existing child tail. Extend the adjacent non-inherited heap segment when present instead of adding from `old_heap_end` again.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| return -1; | ||
| } | ||
| } | ||
| if (guest_region_add_ex_owned(g, start, start + old_size, prot, flags, |
There was a problem hiding this comment.
P1: A second mremap of an allocation grown after fork now fails with EFAULT, because this split makes the logical mapping span two records while sys_mremap requires its source range fit one record. Handle contiguous fork-boundary pieces as one mremap source, or retain the boundary outside the region-table VMA split.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/syscall/mem.c, line 253:
<comment>A second `mremap` of an allocation grown after fork now fails with `EFAULT`, because this split makes the logical mapping span two records while `sys_mremap` requires its source range fit one record. Handle contiguous fork-boundary pieces as one mremap source, or retain the boundary outside the region-table VMA split.</comment>
<file context>
@@ -207,19 +208,67 @@ static void mark_overlay_metadata_range(guest_t *g,
+ return -1;
+ }
+ }
+ if (guest_region_add_ex_owned(g, start, start + old_size, prot, flags,
+ offset, name, backing_fd, true) < 0) {
+ if (tail_backing_fd >= 0)
</file context>
| if (inherited_at_fork && old_size > 0 && old_size < new_size) { | ||
| int tail_backing_fd = -1; | ||
| if (backing_fd >= 0) { | ||
| tail_backing_fd = dup(backing_fd); |
There was a problem hiding this comment.
P1: Growing an inherited file-backed mapping can lose its source mapping when this extra dup() hits EMFILE: the mremap paths have already removed the source before add_mremap_region() fails. Pre-acquire both owned descriptors before destructive work, or restore the source snapshot on this failure path.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/syscall/mem.c, line 247:
<comment>Growing an inherited file-backed mapping can lose its source mapping when this extra `dup()` hits `EMFILE`: the mremap paths have already removed the source before `add_mremap_region()` fails. Pre-acquire both owned descriptors before destructive work, or restore the source snapshot on this failure path.</comment>
<file context>
@@ -207,19 +208,67 @@ static void mark_overlay_metadata_range(guest_t *g,
+ if (inherited_at_fork && old_size > 0 && old_size < new_size) {
+ int tail_backing_fd = -1;
+ if (backing_fd >= 0) {
+ tail_backing_fd = dup(backing_fd);
+ if (tail_backing_fd < 0) {
+ close(backing_fd);
</file context>
| * conservative range and mark it stale rather than | ||
| * failing an otherwise successful brk syscall. | ||
| */ | ||
| if (guest_region_add( |
There was a problem hiding this comment.
P1: Repeated post-fork brk growth creates overlapping [heap] regions: the original inherited segment remains the one found, so each later call adds [fork_boundary, new_end) over the existing child tail. Extend the adjacent non-inherited heap segment when present instead of adding from old_heap_end again.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/syscall/mem.c, line 1891:
<comment>Repeated post-fork `brk` growth creates overlapping `[heap]` regions: the original inherited segment remains the one found, so each later call adds `[fork_boundary, new_end)` over the existing child tail. Extend the adjacent non-inherited heap segment when present instead of adding from `old_heap_end` again.</comment>
<file context>
@@ -1829,7 +1879,26 @@ int64_t sys_brk(guest_t *g, uint64_t addr)
+ * conservative range and mark it stale rather than
+ * failing an otherwise successful brk syscall.
+ */
+ if (guest_region_add(
+ g, old_heap_end, new_off,
+ LINUX_PROT_READ | LINUX_PROT_WRITE,
</file context>
| size_t page_size, | ||
| size_t stress_size) | ||
| { | ||
| void *postfork = mmap(NULL, page_size, PROT_READ | PROT_WRITE, |
There was a problem hiding this comment.
P2: The new child mmap (rw-anon, 1 page) can coalesce with an adjacent rw-anon VMA once the smaps builder merges contiguous regions, so validate_layout's exact per-page boundary assertions for target's last page / the final stress page can break, making the fork test flaky or a false failure. Note the parent fixture already guards against exactly this by making the stress-start page read-only; postfork reintroduces the unguarded case in the child. Consider placing postfork at a dedicated non-adjacent address (e.g. mmap with a chosen high hint or MAP_FIXED_NOREPLACE atop a known gap) so the shared_dirty==0 probe cannot merge with the stress/target ranges.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/test-proc-smap.c, line 470:
<comment>The new child mmap (rw-anon, 1 page) can coalesce with an adjacent rw-anon VMA once the smaps builder merges contiguous regions, so validate_layout's exact per-page boundary assertions for target's last page / the final stress page can break, making the fork test flaky or a false failure. Note the parent fixture already guards against exactly this by making the stress-start page read-only; postfork reintroduces the unguarded case in the child. Consider placing postfork at a dedicated non-adjacent address (e.g. mmap with a chosen high hint or MAP_FIXED_NOREPLACE atop a known gap) so the shared_dirty==0 probe cannot merge with the stress/target ranges.</comment>
<file context>
@@ -467,30 +467,42 @@ static int child_probe(uintptr_t target,
size_t page_size,
size_t stress_size)
{
+ void *postfork = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (postfork == MAP_FAILED)
</file context>
jserv
left a comment
There was a problem hiding this comment.
After responding to @cubic-dev-ai , squash commits and enforce rules described by https://cbea.ms/git-commit/ .
Sure, it's getting complicated when it comes to fork-safe. I'm working on it and trying to implement related book keepings. |
Fixes #258
Summary
Running
redis-serverunder elfuse currently fails during Redis's ARM64 copy-on-write safety check because Redis reads/proc/self/smaps, which elfuse does not provide with Linux-compatible contents. Redis treats the failed check as unsafe and exits to protect background saves.This PR adds synthetic
smapssupport so Redis can inspect guest VMAs and complete startup under elfuse.Implementation
/proc/self/smapsand/proc/<pid>/smapsfrom the tracked guest VMA snapshot.VmFlags.Shared_Dirtycompatibility signal for writable private anonymous mappings in fork children./proc/self/maps.smaps_rollupremain out of scope.Testing
make test-dynamic-array-host test-string-builder-host— passed.git diff --check— passed.test-proc-smapcompilation was attempted, but the local environment does not haveaarch64-linux-gnu-gcc.Compatibility notes
The synthetic values are derived from elfuse's tracked guest VMAs and fork state. Fields requiring host kernel page accounting are emitted as stable compatibility values and should not be interpreted as precise memory profiling data.
Summary by cubic
Adds Linux-compatible
/proc/self/smapsand/proc/<pid>/smapsemulation from tracked guest VMAs soredis-serverpasses its ARM64 COW safety check and starts under elfuse. MakesShared_Dirtyfork-aware per VMA to avoid false positives after fork.New Features
smapswith Linux-shaped headers and standard fields, includingVmFlags.Shared_Dirtyfork-aware per VMA using newinherited_at_forkmetadata; propagate through fork IPC and memory-region operations./proc/self/maps, cap snapshots by guest region limits, and build fragmented snapshots by append-then-sort+merge to avoid quadratic insertion.dynamic-arrayandstring-builderutilities with host unit tests, docs, and a genericsmapsparser wired intotest-matrix.sh.Bug Fixes
/dev/shmtests to prevent collisions during concurrent jobs.smapstests (skip on the QEMU lane).Written for commit 6309119. Summary will update on new commits.