Skip to content

Add Linux-compatible /proc/self/smaps emulation - #259

Open
sunxiaoguang wants to merge 17 commits into
sysprog21:mainfrom
sunxiaoguang:elfuse-redis-server
Open

Add Linux-compatible /proc/self/smaps emulation#259
sunxiaoguang wants to merge 17 commits into
sysprog21:mainfrom
sunxiaoguang:elfuse-redis-server

Conversation

@sunxiaoguang

@sunxiaoguang sunxiaoguang commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator

Fixes #258

Summary

Running redis-server under 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 smaps support so Redis can inspect guest VMAs and complete startup under elfuse.

Implementation

  • Generate /proc/self/smaps and /proc/<pid>/smaps from the tracked guest VMA snapshot.
  • Emit Linux-shaped VMA headers and the standard smaps fields, including VmFlags.
  • Preserve fork-aware COW information with a coarse Shared_Dirty compatibility signal for writable private anonymous mappings in fork children.
  • Share VMA collection and formatting logic with /proc/self/maps.
  • Replace the fixed maps-entry/output limits with checked growable array and string-builder utilities.
  • Add parser and regression coverage for smaps layout, field completeness, fork behavior, and larger VMA snapshots.
  • Document the synthetic smaps semantics and intentional limitations; exact host page residency and smaps_rollup remain out of scope.

Testing

  • make test-dynamic-array-host test-string-builder-host — passed.
  • git diff --check — passed.
  • Guest test-proc-smap compilation was attempted, but the local environment does not have aarch64-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/smaps and /proc/<pid>/smaps so redis-server passes its ARM64 COW safety check and starts under elfuse. Also standardizes FD-limit handling and gates descriptor-exhaustion regressions in make check.

  • New Features

    • Emit Linux-shaped smaps (headers, standard fields, VmFlags) for self and PIDs; reuse /proc/self/maps VMA collection; append then sort+merge once; cap by guest limits.
    • Track per-VMA fork state and stable vma_id; keep Shared_Dirty/Pss_Dirty consistent and exclude post-fork mappings; propagate via fork IPC and memory ops.
    • Add dynamic-array and string-builder utilities with host tests; add a smaps parser and fork/mremap regressions.
    • Introduce elfuse-limits.h for shared FD limits (FD_TABLE_SIZE, HOST_FD_RESERVE, HOST_NOFILE_MIN); test runners derive limits via tests/test-config.sh; tests/manifest.txt supports host_nofile=elfuse-minimum.
  • Bug Fixes

    • Harden fork-aware smaps and mremap: preserve VMA lineage across fork/restore; make region splits/moves transactional; flush shared files before removes; avoid flushing writable aliases for read-only MAP_SHARED; cover PROT_NONE.
    • Harden mmap/mremap failure paths: reserve FDs before teardown; keep EMFILE failures side-effect free; preserve smaps accounting and errno; preserve failed mmap reservation addresses; add descriptor- and table-exhaustion regressions; run the EMFILE regression in make check.
    • Use unique /dev/shm fixture names to avoid collisions in concurrent test runs.

Written for commit d323921. Summary will update on new commits.

Review in cubic

@sunxiaoguang sunxiaoguang changed the title Add Linux-compatible /proc/self/smaps emulation [WIP] Add Linux-compatible /proc/self/smaps emulation Aug 1, 2026
cubic-dev-ai[bot]

This comment was marked as resolved.

@jserv jserv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Run make indent before committing.

cubic-dev-ai[bot]

This comment was marked as resolved.

@sunxiaoguang sunxiaoguang changed the title [WIP] Add Linux-compatible /proc/self/smaps emulation Add Linux-compatible /proc/self/smaps emulation Aug 2, 2026
@sunxiaoguang

Copy link
Copy Markdown
Collaborator Author

make indent

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 /proc/*/maps/smaps overlap and performance issues. make indent now completes successfully. Please take another look when you have a chance.

jserv

This comment was marked as resolved.

@jserv
jserv requested a review from Max042004 August 2, 2026 04:47
cubic-dev-ai[bot]

This comment was marked as resolved.

@jserv jserv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After responding to @cubic-dev-ai , squash commits and enforce rules described by https://cbea.ms/git-commit/ .

@sunxiaoguang sunxiaoguang changed the title Add Linux-compatible /proc/self/smaps emulation [WIP] Add Linux-compatible /proc/self/smaps emulation Aug 2, 2026
@sunxiaoguang

Copy link
Copy Markdown
Collaborator Author

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.

@sunxiaoguang

sunxiaoguang commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator Author

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 jserv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/runtime/procemu.c Outdated
Comment thread src/runtime/procemu.c Outdated
Comment thread src/syscall/mem.c
{
split_regions_at_boundary(g, start);
split_regions_at_boundary(g, end);
int split_err = split_regions_at_boundary(g, start);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@jserv

jserv commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

I guess there are still corner cases and ways to improve the fork-safe memory statistics.

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.

@sunxiaoguang

Copy link
Copy Markdown
Collaborator Author

Thanks, let me check the comments.

cubic-dev-ai[bot]

This comment was marked as resolved.

sunxiaoguang added a commit to sunxiaoguang/elfuse that referenced this pull request Aug 3, 2026
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
@jserv jserv changed the title [WIP] Add Linux-compatible /proc/self/smaps emulation Add Linux-compatible /proc/self/smaps emulation Aug 3, 2026
@jserv
jserv requested a review from henrybear327 August 3, 2026 07:51
@jserv

jserv commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

I defer to @Max042004 and @henrybear327 for confirmation.

cubic-dev-ai[bot]

This comment was marked as resolved.

@sunxiaoguang

Copy link
Copy Markdown
Collaborator Author

I defer to @Max042004 and @henrybear327 for confirmation.

Thank you for all your help.

jserv

This comment was marked as resolved.

henrybear327 pushed a commit to henrybear327/elfuse that referenced this pull request Aug 5, 2026
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
henrybear327 added a commit to henrybear327/elfuse that referenced this pull request Aug 5, 2026
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.
@sunxiaoguang

Copy link
Copy Markdown
Collaborator Author

Rebase latest main branch and resolve conflicts.

Sure, I have rebased the PR with the main branch

Comment thread src/core/guest.c
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)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread src/string-builder.c Outdated
Comment thread docs/internals.md Outdated
cubic-dev-ai[bot]

This comment was marked as resolved.

@sunxiaoguang

sunxiaoguang commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator Author

Follow-up for commit d7db64a (Harden mmap failure handling):

The review findings are addressed as follows:

  • Fixed mmap/mremap descriptor-exhaustion atomicity by reserving the backing descriptors with guest_region_remove_prepare() before overlay teardown, page-table changes, or mapping destruction. The commit consumes the reservation through guest_region_remove_reserved() and closes it on every failure path, so EMFILE leaves the existing mappings and region metadata unchanged.
  • Preserved the caller's errno during both string_builder_appendf() vsnprintf passes, including %m, and added host coverage for the formatted errno expansion.
  • Updated the synthetic smaps documentation to include Pss_Dirty in the fork-inherited dirty-accounting signal.
  • Added test-mremap-tail-emfile to the manifest and test matrix, lowered the host RLIMIT_NOFILE to exercise elfuse's descriptor reserve, and skipped the test in QEMU because it validates elfuse-specific bookkeeping.
  • Changed cleanup_overlays_in_range() to avoid boundary splits when the range contains no live overlay, so a full region table cannot make a reducing-only munmap fail. The new test covers this case.
  • Removed the unrelated PREBUILT_TEST_BINARIES/prebuilt=skip compatibility layer; the repository does not use such a corpus.

@sunxiaoguang sunxiaoguang reopened this Aug 7, 2026
cubic-dev-ai[bot]

This comment was marked as resolved.

@jserv jserv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rebase latest main branch and resolve conflicts, as #272 and #277 introduced machine-checked proofs.

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.
@sunxiaoguang

Copy link
Copy Markdown
Collaborator Author

Rebased with latest main and passed all the checks.

@jserv
jserv requested review from Max042004 and henrybear327 August 9, 2026 18:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Running redis-server under elfuse fails because /proc/self/smaps is unavailable

3 participants