diff --git a/src/core/bootstrap.c b/src/core/bootstrap.c index 70bddfd5..481929a1 100644 --- a/src/core/bootstrap.c +++ b/src/core/bootstrap.c @@ -617,6 +617,7 @@ int guest_bootstrap_prepare(guest_t *g, free(rosetta_argv); return -1; } + g->start_stack = boot->stack_pointer; startup_trace_step("build_linux_stack", t0); /* rosetta_argv was copied into the guest stack; the host allocation is no * longer needed. The strings themselves are constants (ROSETTA_PATH) or @@ -868,6 +869,7 @@ int guest_bootstrap_rosetta_post_reset(guest_t *g, log_error("build_linux_stack failed during exec re-bootstrap"); return -1; } + g->start_stack = sp; proc_set_auxv(auxv.words, auxv.nwords * sizeof(auxv.words[0])); *out_entry_point = rr.entry_point; diff --git a/src/core/guest.h b/src/core/guest.h index 78c6200f..5d791948 100644 --- a/src/core/guest.h +++ b/src/core/guest.h @@ -432,6 +432,7 @@ typedef struct { uint64_t brk_current; /* Current brk position */ uint64_t stack_base; /* Bottom of stack region (dynamic, above brk) */ uint64_t stack_top; /* Top of stack (stack grows down from here) */ + uint64_t start_stack; /* Initial userspace SP for /proc//stat */ uint64_t mmap_next; /* RW mmap high-water mark for fork IPC snapshots */ uint64_t mmap_end; /* Current page-table-covered RW mmap limit */ diff --git a/src/runtime/fork-state.h b/src/runtime/fork-state.h index 4f1b77cb..d4a5663e 100644 --- a/src/runtime/fork-state.h +++ b/src/runtime/fork-state.h @@ -19,7 +19,7 @@ /* Fork IPC protocol identity. Bump this whenever the header layout or ordered * fork payload changes incompatibly. */ -#define FORK_IPC_PROTOCOL_MAGIC 0x454C464DU /* "ELFM" */ +#define FORK_IPC_PROTOCOL_MAGIC 0x454C464EU /* "ELFN" */ #define IPC_MAGIC_HEADER FORK_IPC_PROTOCOL_MAGIC #define IPC_MAGIC_SENTINEL 0x454C4F4BU /* "ELOK" */ @@ -34,6 +34,7 @@ typedef struct { uint64_t brk_base, brk_current; uint64_t stack_base; uint64_t stack_top; + uint64_t start_stack; uint64_t mmap_next, mmap_end, pt_pool_next, ttbr0; uint64_t mmap_rx_next; uint64_t mmap_rx_end; diff --git a/src/runtime/forkipc.c b/src/runtime/forkipc.c index c4727b59..03cadcee 100644 --- a/src/runtime/forkipc.c +++ b/src/runtime/forkipc.c @@ -230,6 +230,7 @@ int fork_child_main(int ipc_fd, g.elf_load_min = hdr.elf_load_min; g.stack_base = hdr.stack_base; g.stack_top = hdr.stack_top; + g.start_stack = hdr.start_stack; g.mmap_next = hdr.mmap_next; g.mmap_end = hdr.mmap_end; g.pt_pool_next = hdr.pt_pool_next; @@ -1678,6 +1679,7 @@ int64_t sys_clone(hv_vcpu_t vcpu, .brk_current = g->brk_current, .stack_base = g->stack_base, .stack_top = g->stack_top, + .start_stack = g->start_stack, .mmap_next = g->mmap_next, .mmap_end = g->mmap_end, .pt_pool_next = g->pt_pool_next, diff --git a/src/runtime/procemu.c b/src/runtime/procemu.c index 7a4eb229..d340d75d 100644 --- a/src/runtime/procemu.c +++ b/src/runtime/procemu.c @@ -2538,7 +2538,18 @@ static int proc_open_meminfo(void) * the tid is unparseable or the leaf is unknown. Split out of * proc_intercept_open to keep that dispatcher readable. */ -static int proc_open_self_task_node(const char *path, int linux_flags) +static uint64_t proc_start_stack(const guest_t *g) +{ + if (g->start_stack != 0) + return g->start_stack; + if (g->stack_top > g->stack_base) + return g->stack_top - 16; + return 0; +} + +static int proc_open_self_task_node(const guest_t *g, + const char *path, + int linux_flags) { char *endp; long tid = strtol(path + 16, &endp, 10); @@ -2552,13 +2563,17 @@ static int proc_open_self_task_node(const char *path, int linux_flags) } if (!strcmp(endp, "/stat")) { + uint64_t start_stack = proc_start_stack(g); return proc_emit_fmt( - "%ld (%.15s) R %lld %lld %lld 0 0 0 0 0 0 0 0 0 0 0 " - "20 0 %d 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 " - "0 0 0 0 0 0 0 0\n", + "%ld (%.15s) R %lld %lld %lld 0 0 0 " /* 1-9 */ + "0 0 0 0 0 0 0 0 " /* 10-17 */ + "20 0 %d 0 0 0 0 " /* 18-24 */ + "18446744073709551615 0 0 %llu 0 0 0 " /* 25-31 */ + "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", /* 32-52 */ tid, proc_comm_name(), (long long) proc_get_ppid(), (long long) proc_get_pid(), /* pgid */ - (long long) proc_get_sid(), thread_active_count()); + (long long) proc_get_sid(), thread_active_count(), + (unsigned long long) start_stack); } if (!strcmp(endp, "/status")) { @@ -3025,7 +3040,7 @@ int proc_intercept_open(const guest_t *g, /* /proc/self/task//{stat,status} and the directory. */ if (!strncmp(path, "/proc/self/task/", 16)) - return proc_open_self_task_node(path, linux_flags); + return proc_open_self_task_node(g, path, linux_flags); /* /proc/self/maps -> generated from guest region tracking. Addresses are * page-aligned (rounded down/up) to match real Linux behavior. Output @@ -3333,6 +3348,8 @@ int proc_intercept_open(const guest_t *g, rss_pages += sz / (uint64_t) page_size; } + uint64_t start_stack = proc_start_stack(g); + /* Fields: pid(1) (comm)(2) state(3) ppid(4) pgrp(5) session(6) * tty_nr(7) tpgid(8) flags(9) minflt(10) cminflt(11) majflt(12) * cmajflt(13) utime(14) stime(15) cutime(16) cstime(17) @@ -3343,14 +3360,15 @@ int proc_intercept_open(const guest_t *g, "%lld (%.15s) R %lld %lld %lld 0 -1 0 " /* 1-9 */ "0 0 0 0 %ld %ld 0 0 " /* 10-17 */ "20 0 %d 0 0 %llu %llu " /* 18-24 */ - "18446744073709551615 0 0 0 0 0 0 " /* 25-31 */ + "18446744073709551615 0 0 %llu 0 0 0 " /* 25-31 */ "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", /* 32-52 */ (long long) proc_get_pid(), proc_comm_name(), (long long) proc_get_ppid(), (long long) proc_get_pid(), /* pgrp = pid */ (long long) proc_get_pid(), /* session = pid */ utime_ticks, stime_ticks, thread_active_count(), - (unsigned long long) vsize, (unsigned long long) rss_pages); + (unsigned long long) vsize, (unsigned long long) rss_pages, + (unsigned long long) start_stack); } /* /proc/stat -> synthetic CPU statistics */ diff --git a/src/syscall/exec.c b/src/syscall/exec.c index 72bc7663..84c6f1e1 100644 --- a/src/syscall/exec.c +++ b/src/syscall/exec.c @@ -1282,6 +1282,7 @@ int64_t sys_execve(hv_vcpu_t vcpu, sp = build_linux_stack(g, g->stack_top, argc, argv_const, envp_const, &elf_info, elf_load_base, interp_base, exec_vdso, -1 /* no AT_EXECFD */, &auxv); + g->start_stack = sp; entry_point = (interp_base != 0) ? (interp_info.entry + interp_base) : (elf_info.entry + elf_load_base); diff --git a/tests/test-fork-ipc-protocol-host.c b/tests/test-fork-ipc-protocol-host.c index 7a59dc81..8dc4cc88 100644 --- a/tests/test-fork-ipc-protocol-host.c +++ b/tests/test-fork-ipc-protocol-host.c @@ -18,9 +18,10 @@ #define LEGACY_ELFK_MAGIC 0x454C464BU #define PREVIOUS_ELFL_MAGIC 0x454C464CU +#define PREVIOUS_ELFM_MAGIC 0x454C464DU -_Static_assert(FORK_IPC_PROTOCOL_MAGIC == 0x454C464DU, - "fork IPC protocol magic must remain ELFM until the next " +_Static_assert(FORK_IPC_PROTOCOL_MAGIC == 0x454C464EU, + "fork IPC protocol magic must remain ELFN until the next " "incompatible wire-format change"); _Static_assert(IPC_MAGIC_HEADER == FORK_IPC_PROTOCOL_MAGIC, "header magic must be the protocol identity"); @@ -28,6 +29,8 @@ _Static_assert(FORK_IPC_PROTOCOL_MAGIC != LEGACY_ELFK_MAGIC, "current protocol must reject old ELFK children/parents"); _Static_assert(FORK_IPC_PROTOCOL_MAGIC != PREVIOUS_ELFL_MAGIC, "NOFILE header fields require rejecting ELFL peers"); +_Static_assert(FORK_IPC_PROTOCOL_MAGIC != PREVIOUS_ELFM_MAGIC, + "start_stack header field requires rejecting ELFM peers"); _Static_assert(IPC_MAGIC_SENTINEL != FORK_IPC_PROTOCOL_MAGIC, "process-state sentinel must not alias the header protocol"); diff --git a/tests/test-procfs.c b/tests/test-procfs.c index 96aa6b2d..2ef42d48 100644 --- a/tests/test-procfs.c +++ b/tests/test-procfs.c @@ -17,7 +17,9 @@ #include #include +#include #include +#include #include "test-harness.h" #include "test-util.h" @@ -27,6 +29,93 @@ int passes = 0, fails = 0; #define AT_NULL 0 #define AT_PAGESZ 6 +static bool parse_proc_stat_field_u64(const char *buf, + int target, + uint64_t *out) +{ + if (target < 4) + return false; + + const char *p = strrchr(buf, ')'); + if (!p) + return false; + p++; + + int field = 3; + while (*p == ' ') + p++; + while (*p != '\0' && field <= target) { + char *endp; + if (field == 3) { + if (*p == '\0' || p[1] != ' ') + return false; + endp = (char *) p + 1; + } else { + unsigned long long val = strtoull(p, &endp, 10); + if (endp == p) + return false; + if (field == target) { + *out = (uint64_t) val; + return true; + } + } + p = endp; + while (*p == ' ') + p++; + field++; + } + return false; +} + +static bool parse_stack_range(const char *buf, uint64_t *start, uint64_t *end) +{ + const char *line = buf; + while ((line = strstr(line, "[stack]")) != NULL) { + const char *begin = line; + while (begin > buf && begin[-1] != '\n') + begin--; + + unsigned long long lo, hi; + if (sscanf(begin, "%llx-%llx", &lo, &hi) == 2) { + *start = (uint64_t) lo; + *end = (uint64_t) hi; + return true; + } + line += 7; + } + return false; +} + +static bool read_stack_range(uint64_t *start, uint64_t *end) +{ + char mapsbuf[65536]; + ssize_t maps_n = + raw_read_file_nul("/proc/self/maps", mapsbuf, sizeof(mapsbuf)); + return maps_n > 0 && parse_stack_range(mapsbuf, start, end); +} + +static bool read_start_stack(const char *path, uint64_t *start_stack) +{ + char statbuf[1024]; + ssize_t stat_n = raw_read_file_nul(path, statbuf, sizeof(statbuf)); + return stat_n > 0 && parse_proc_stat_field_u64(statbuf, 28, start_stack); +} + +static bool start_stack_is_recorded(uint64_t start_stack, uint64_t stack_end) +{ + return start_stack != stack_end - 16; +} + +static int child_write_start_stack(int fd) +{ + uint64_t start_stack = 0; + if (!read_start_stack("/proc/self/stat", &start_stack)) + return 1; + if (write_fd_all(fd, &start_stack, sizeof(start_stack)) < 0) + return 2; + return 0; +} + int main(void) { char buf[4096] __attribute__((aligned(8))); @@ -137,6 +226,71 @@ int main(void) } } + TEST("procfs: stat startstack is in [stack]"); + { + uint64_t start_stack = 0, stack_start = 0, stack_end = 0; + bool ok = read_start_stack("/proc/self/stat", &start_stack) && + read_stack_range(&stack_start, &stack_end) && + start_stack >= stack_start && start_stack < stack_end; + EXPECT_TRUE(ok, "startstack not inside [stack]"); + } + + TEST("procfs: task stat startstack is in [stack]"); + { + char path[128]; + snprintf(path, sizeof(path), "/proc/self/task/%ld/stat", + (long) raw_getpid()); + uint64_t start_stack = 0, stack_start = 0, stack_end = 0; + bool ok = read_start_stack(path, &start_stack) && + read_stack_range(&stack_start, &stack_end) && + start_stack >= stack_start && start_stack < stack_end; + EXPECT_TRUE(ok, "task startstack not inside [stack]"); + } + + TEST("procfs: stat startstack is recorded SP"); + { + uint64_t start_stack = 0, stack_start = 0, stack_end = 0; + bool ok = read_start_stack("/proc/self/stat", &start_stack) && + read_stack_range(&stack_start, &stack_end) && + start_stack >= stack_start && start_stack < stack_end && + start_stack_is_recorded(start_stack, stack_end); + EXPECT_TRUE(ok, "startstack used fallback value"); + } + + TEST("procfs: fork preserves stat startstack"); + { + int pipefd[2]; + uint64_t parent_start = 0, child_start = 0; + uint64_t stack_start = 0, stack_end = 0; + bool ok = read_start_stack("/proc/self/stat", &parent_start) && + read_stack_range(&stack_start, &stack_end) && + start_stack_is_recorded(parent_start, stack_end) && + pipe(pipefd) == 0; + if (!ok) { + EXPECT_TRUE(false, "fork startstack setup failed"); + } else { + pid_t pid = fork(); + if (pid == 0) { + close(pipefd[0]); + int rc = child_write_start_stack(pipefd[1]); + close(pipefd[1]); + _exit(rc); + } + close(pipefd[1]); + ssize_t nread = read(pipefd[0], &child_start, sizeof(child_start)); + close(pipefd[0]); + int status = 0; + if (pid < 0 || waitpid(pid, &status, 0) < 0) + ok = false; + else + ok = nread == (ssize_t) sizeof(child_start) && + WIFEXITED(status) && WEXITSTATUS(status) == 0 && + child_start == parent_start && + start_stack_is_recorded(child_start, stack_end); + EXPECT_TRUE(ok, "fork child startstack mismatch"); + } + } + SUMMARY("test-procfs"); return fails > 0 ? 1 : 0; }