Add Linux-compatible /proc/self/smaps emulation - #259
Conversation
jserv
left a comment
There was a problem hiding this comment.
Run make indent before committing.
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 |
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. |
6309119 to
0fcc8f7
Compare
|
I guess there are still corner cases and ways to improve the fork-safe memory statistics. However it's getting too complicated, maybe we can stop digging deeper and start reviewing at this point. Please take another look when you have a chance, Thanks. @jserv @Max042004 |
jserv
left a comment
There was a problem hiding this comment.
Correctness pass over the smaps emulation. Three findings inline. The new dynamic-array and string-builder utilities are careful (overflow-guarded growth, realloc-NULL handled, NUL kept), and the fork IPC carries the new VMA fields via the existing whole-region memcpy with the MAGIC bump, so no serialization gap there.
Two items left out of line, for the record:
-
mem.c:3770 (MAP_SHARED mremap): possible write-loss when the source is a MAP_SHARED file mapping with no live overlay. The aligned/overlay case is fine, since tearing the overlay down flushes dirty pages back to the file; the concern is only the non-overlay fallback, and it looks pre-existing rather than introduced here. Unresolved either way. Worth a targeted test (dirty a misaligned MAP_SHARED file mapping, mremap it, assert the file reflects the writes) before any change.
-
next_vma_id not serialized across fork: the child keeps next_vma_id == 0 while inherited regions carry large ids. Not a correctness bug, allocate_vma_id linear-scans live regions and never hands back an in-use id, so collisions cannot happen; the only cost is O(nregions) work per allocation in a fork child and a field documented as "last allocated" that is silently wrong there. Optional: set it to max(region.vma_id)+1 in the child where inherited_at_fork is stamped.
| { | ||
| split_regions_at_boundary(g, start); | ||
| split_regions_at_boundary(g, end); | ||
| int split_err = split_regions_at_boundary(g, start); |
There was a problem hiding this comment.
capture_region_snapshots is not failure-atomic. split_regions_at_boundary(start) commits its split in place (memmove + nregions++, plus a dup'd backing fd, lines 490-504); if the following split at end then fails with ENOMEM (region table full) or a dup failure, this returns the error with the start split already committed. The caller aborts the syscall, but the region table is left carrying a spurious boundary at start and holding one extra dup'd fd. This is not corruption (the two halves are metadata-equivalent) and not a leak (the fd is owned by the new region), but the operation reports failure after having mutated state, and the ignored guest_region_remove() return downstream at mem.c:3470 is only safe because these boundaries were pre-split here. Preflight/reserve both boundary splits before committing either, or roll back the start split when the end split fails.
Yes, that is exactly the direction of my current research. Beyond addressing corner cases in fork-safe memory statistics, the broader goal is to consolidate a kernel-less approach that provides a Linux system call compatibility layer while rigorously validating memory safety and futex correctness. The challenge is not only functional compatibility but also ensuring that the concurrency semantics remain equivalent to those expected by Linux applications. |
|
Thanks, let me check the comments. |
Preserve fork lineage and smaps consistency when restored regions receive new allocations. Flush shared-file contents before mremap removes source mappings. Make region-boundary preparation transactional so allocation or descriptor failures leave metadata unchanged. Add regressions for repeated post-fork allocations and misaligned moves. Cover PROT_NONE smaps output as well. Related: sysprog21#259
a8072f2 to
c4efc5e
Compare
|
I defer to @Max042004 and @henrybear327 for confirmation. |
90e0745 to
76a80bd
Compare
Thank you for all your help. |
Preserve fork lineage and smaps consistency when restored regions receive new allocations. Flush shared-file contents before mremap removes source mappings. Make region-boundary preparation transactional so allocation or descriptor failures leave metadata unchanged. Add regressions for repeated post-fork allocations and misaligned moves. Cover PROT_NONE smaps output as well. Related: sysprog21#259
Boot redis:7-alpine under HVF with redis-server foreground as the guest process and drive it from a second guest running redis-cli over the shared host loopback: PING, a SET/GET round-trip, then BGSAVE polled through INFO persistence to rdb_bgsave_in_progress:0 with rdb_last_bgsave_status:ok asserted. BGSAVE forks the server and snapshots the dataset copy-on-write, so the lane pins the exact path /proc/self/smaps exists to keep safe. redis cannot announce an ephemeral port (--port 0 disables TCP), so the host probes a free loopback port before boot instead of reading one back as the node lane does. Readiness keeps the node lane's two-stage shape (process alive, then socket accepts), shutdown is an in-band SHUTDOWN NOSAVE, and the server's own exit status is asserted host-side. This leg fails for now: redis-server's ARM64 COW safety check needs /proc/self/smaps (issue sysprog21#258), which elfuse does not yet synthesize; upstream PR sysprog21#259 adds it. The lane is staged in advance so that once that lands and this branch is rebased onto it, the leg turns green with no further changes and keeps the fork/COW path covered from then on. Verified both ways: red on this tree, green with the smaps branch merged locally.
76a80bd to
24c560a
Compare
Sure, I have rebased the PR with the main branch |
| int guest_region_remove(guest_t *g, uint64_t start, uint64_t end) | ||
| { | ||
| int reserved_backing_fd = -1; | ||
| if (guest_region_remove_prepare(g, start, end, &reserved_backing_fd) < 0) |
There was a problem hiding this comment.
P1: Fixed mmap and mremap operations can leave overlapping or stale region metadata when descriptor exhaustion makes the new removal preflight fail, because existing callers ignore guest_region_remove()'s return value. The callers should use the prepare/reserved API and abort before mutating mappings when reservation fails.
https://github.com/sysprog21/elfuse/pull/270/changes#r3723128567
|
Follow-up for commit The review findings are addressed as follows:
|
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 and fork-aware accounting with parser regression coverage.
Keep static-analysis checks and the smaps test matrix green after adding the new procfs emulation coverage.
Apply review feedback to the smaps emulation and its regression tests.
Grow formatted string storage safely and silence the Infer warning exposed by the smaps changes.
Avoid collisions when concurrent shared-memory tests create temporary names.
Keep concurrent shared-memory fixture setup consistent with repository formatting conventions.
Append live and shadow VMAs, then sort and merge once. This keeps fragmented maps and smaps snapshots from quadratic insertion work.
Apply repository formatting conventions to proc VMA snapshot calls.
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.
Preserve VMA lineage across fork-split mappings and keep synthetic smaps accounting consistent with per-VMA fork state. Add file-backed mremap regression coverage and wire the tests into the build and documentation.
Apply consistent line wrapping in the memory syscall implementation and its mremap regression test without changing behavior.
Preserve fork lineage and smaps consistency when restored regions receive new allocations. Flush shared-file contents before mremap removes source mappings. Make region-boundary preparation transactional so allocation or descriptor failures leave metadata unchanged. Add regressions for repeated post-fork allocations and misaligned moves. Cover PROT_NONE smaps output as well. Related: sysprog21#259
Keep synthesized Pss_Dirty consistent with fork-inherited Shared_Dirty, and avoid flushing writable aliases when moving a read-only MAP_SHARED snapshot. Add regressions for both cases.
Keep failed reservation addresses visible to callers when munmap fails. This lets MAP_FAILED cleanup release still-live mappings instead of leaking them.
Reserve descriptors before fixed mmap and mremap teardown Keep EMFILE failures from changing mappings or metadata. Preserve smaps accounting and errno semantics. Add descriptor and full-table regressions.
Derive the host descriptor limit from shared runtime constants, prove filler exhaustion is host-FD driven, and align matrix documentation with the actual test lanes.
Make the mremap probe prove host descriptor pressure by retrying the same gap before and after releasing slots; keep test-config quiet when sourced but preserve CLI output, and run the regression from check.
5f7b860 to
d323921
Compare
|
Rebased with latest main and passed all the checks. |
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>/smapssoredis-serverpasses its ARM64 COW safety check and starts under elfuse. Also standardizes FD-limit handling and gates descriptor-exhaustion regressions inmake check.New Features
VmFlags) for self and PIDs; reuse/proc/self/mapsVMA collection; append then sort+merge once; cap by guest limits.vma_id; keepShared_Dirty/Pss_Dirtyconsistent and exclude post-fork mappings; propagate via fork IPC and memory ops.dynamic-arrayandstring-builderutilities with host tests; add a smaps parser and fork/mremap regressions.elfuse-limits.hfor shared FD limits (FD_TABLE_SIZE,HOST_FD_RESERVE,HOST_NOFILE_MIN); test runners derive limits viatests/test-config.sh;tests/manifest.txtsupportshost_nofile=elfuse-minimum.Bug Fixes
mremap: preserve VMA lineage across fork/restore; make region splits/moves transactional; flush shared files before removes; avoid flushing writable aliases for read-onlyMAP_SHARED; coverPROT_NONE.mmap/mremapfailure paths: reserve FDs before teardown; keepEMFILEfailures side-effect free; preserve smaps accounting and errno; preserve failedmmapreservation addresses; add descriptor- and table-exhaustion regressions; run the EMFILE regression inmake check./dev/shmfixture names to avoid collisions in concurrent test runs.Written for commit d323921. Summary will update on new commits.