Skip to content

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

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

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

Conversation

@sunxiaoguang

@sunxiaoguang sunxiaoguang commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

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. Smaps is fork-aware with stable VMA lineage, bounded snapshots, and safer mremap/mmap failure handling.

  • New Features

    • Emit Linux-shaped smaps (headers, standard fields, VmFlags) for self and PIDs; share VMA collection with /proc/self/maps; build snapshots via append → sort+merge; cap by guest limits.
    • Track per-VMA fork state with inherited_at_fork and stable vma_id; propagate through fork IPC and memory ops; keep Shared_Dirty/Pss_Dirty consistent and exclude post-fork mappings.
    • Add dynamic-array and string-builder utilities with host tests; add a generic smaps parser and regressions for fork tracking and EMFILE mremap.
  • Bug Fixes

    • Harden fork-aware smaps and mremap: preserve VMA lineage after fork/restore; flush shared-file contents only when needed; avoid flushing writable aliases for read-only MAP_SHARED; roll back region splits on failure; cover PROT_NONE.
    • Preserve failed mmap reservations so MAP_FAILED cleanup can release still-live mappings.
    • Use unique /dev/shm fixture names to avoid cross-job collisions; fix string-builder capacity growth; keep CI/static analysis and the smaps test matrix green.

Written for commit 76a80bd. 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
Contributor 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
Contributor 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.

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

sunxiaoguang commented Aug 3, 2026

Copy link
Copy Markdown
Contributor 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
Contributor Author

Thanks, let me check the comments.

cubic-dev-ai[bot]

This comment was marked as resolved.

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.

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.
cubic-dev-ai[bot]

This comment was marked as resolved.

Keep failed reservation addresses visible to callers when munmap fails.
This lets MAP_FAILED cleanup release still-live mappings instead of
leaking them.
@sunxiaoguang

Copy link
Copy Markdown
Contributor Author

I defer to @Max042004 and @henrybear327 for confirmation.

Thank you for all your help.

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

2 participants