-
Notifications
You must be signed in to change notification settings - Fork 17
Report startstack in procfs stat #227
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| 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/<tid>/{stat,status} and the <tid> 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 */ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,7 +17,9 @@ | |
|
|
||
| #include <stdbool.h> | ||
| #include <stdint.h> | ||
| #include <stdio.h> | ||
| #include <string.h> | ||
| #include <sys/wait.h> | ||
|
|
||
| #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) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| 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]"); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test (and the task-stat one below) can pass vacuously. It only asserts |
||
| { | ||
| 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; | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This new wire-format field is the riskiest part of the PR, but nothing tests that it survives the fork IPC round-trip.
test-fork-ipc-protocol-host.conly static-asserts the magic bumped to ELFN; no test forks a child and reads the child's/proc/self/statfield 28 to confirmstart_stackpacks/unpacks at the right offset. A fork→child field-28 check would close this.