Skip to content

[WIP] Add Linux-compatible /proc/self/smaps emulation - #259

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

[WIP] Add Linux-compatible /proc/self/smaps emulation#259
sunxiaoguang wants to merge 9 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 emulation from tracked guest VMAs so redis-server passes its ARM64 COW safety check and starts under elfuse. Makes Shared_Dirty fork-aware per VMA to avoid false positives after fork.

  • New Features

    • Generate smaps with Linux-shaped headers and standard fields, including VmFlags.
    • Make Shared_Dirty fork-aware per VMA using new inherited_at_fork metadata; propagate through fork IPC and memory-region operations.
    • Share VMA collection/formatting with /proc/self/maps, cap snapshots by guest region limits, and build fragmented snapshots by append-then-sort+merge to avoid quadratic insertion.
    • Add dynamic-array and string-builder utilities with host unit tests, docs, and a generic smaps parser wired into test-matrix.sh.
  • Bug Fixes

    • Fix formatted string builder capacity and address an Infer static analysis warning.
    • Use host-unique fixture names and structured setup in /dev/shm tests to prevent collisions during concurrent jobs.
    • Resolve CI static analysis findings; run the new smaps tests (skip on the QEMU lane).
    • Mark backing-file write-loss across overlapping VMAs and format proc VMA snapshot calls for consistency.

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

Review in cubic

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

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Comment thread src/syscall/mem.c
return -1;
}
}
if (guest_region_add_ex_owned(g, start, start + old_size, prot, flags,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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>

Comment thread src/syscall/mem.c
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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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>

Comment thread src/syscall/mem.c
* conservative range and mark it stale rather than
* failing an otherwise successful brk syscall.
*/
if (guest_region_add(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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>

Comment thread tests/test-proc-smap.c
size_t page_size,
size_t stress_size)
{
void *postfork = mmap(NULL, page_size, PROT_READ | PROT_WRITE,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

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