/* agent_hooks.c — the break loop's snapshot and its choice handoff, driven * from inside the stopped thread. * * Every other break-loop test talks to a program from outside it, over the * socket, and so can only ever see the interleavings a 2ms poll happens to * produce. Two properties need a specific one: * * - a choice validated against one break and then met by a *different* * break, nested inside the first, before the first looks for it; * - a restart list longer than the snapshot holds, by count and by bytes. * * [flan_agent_break_poll_hook] runs on the stopped thread once per turn of * the loop, where a thunk from the poll would run. Requests go through * [flan_agent_request], the in-process path, which is the same verb table the * socket reaches. The program under the hook is test/programs/agent-hooks.flan, * which has no [main]; this file is the entry point, as reload_host.c is. * * argv: the mode, and the socket path the agent binds (it binds one to * install its hooks, and nothing here ever connects to it). */ #include #include #include #include void flan_rt_init(int32_t argc, char **argv); int32_t flan_agent_start(const uint8_t *path, int64_t len); char *flan_agent_request(const char *line, uint64_t *len); void flan_agent_request_free(char *p); extern void (*flan_agent_break_poll_hook)(void); extern void (*flan_break_hook)(const uint8_t *name, int64_t namelen, void *condition, void *xfer); void *flan_restart_push_c(const uint8_t *name, int64_t namelen, const uint8_t *report, int64_t reportlen); void flan_restart_pop_c(void *frame); /* The trailing ptr is the transfer channel every Flan signature carries. */ extern int32_t flan_deep(int32_t n, void *xfer) __asm__("flan.deep"); extern int32_t flan_wide(int32_t n, void *xfer) __asm__("flan.wide"); /* One request, its reply printed to [buf] and returned. */ static char reply_buf[1 << 16]; static const char *ask(const char *line) { uint64_t n = 0; char *r = flan_agent_request(line, &n); if (n >= sizeof reply_buf) n = sizeof reply_buf - 1; memcpy(reply_buf, r ? r : "", (size_t)n); reply_buf[n] = '\0'; flan_agent_request_free(r); return reply_buf; } /* ── The snapshot's two caps ───────────────────────────────────────── * * SNAP_MAX is 64 entries and SNAP_NAMES is 4096 bytes of names. [deep 70] * puts 71 restarts on the stack with one-letter names, so the count is what * runs out; [wide 30] puts 31 with 200-byte names, so the bytes run out after * twenty. What is checked is that the listing stops cleanly at the cap — * every entry it does list is whole and takeable — and that the terminal * says how many were left out. The take is the proof that the indices in * the truncated listing still name the frames they claim to. */ static int listed, longest, shortest, taken_once; static const char *take_line; static void list_and_take(void) { const char *r, *p; if (taken_once) return; taken_once = 1; r = ask("restarts"); listed = 0; longest = 0; shortest = 1 << 30; for (p = r; *p != '\0' && *p != '.';) { const char *nl = strchr(p, '\n'); const char *name; int len; if (nl == NULL) break; /* "I F NAME\tARITY\tSIG\tLOC\tREPORT"; the name ends at the tab. */ name = strchr(p, ' '); name = name ? strchr(name + 1, ' ') : NULL; if (name != NULL) { const char *tab = memchr(name + 1, '\t', (size_t)(nl - name - 1)); len = (int)((tab != NULL ? tab : nl) - name - 1); } else len = -1; if (len > longest) longest = len; if (len < shortest) shortest = len; listed++; p = nl + 1; } printf("listed %d\n", listed); printf("names %d..%d\n", shortest, longest); printf("take %s", ask(take_line)); fflush(stdout); } static int snapmax(void) { void *xfer = NULL; int32_t v; take_line = "restart-at 5 retry"; flan_agent_break_poll_hook = list_and_take; v = flan_deep(70, &xfer); flan_agent_break_poll_hook = NULL; printf("returned %d\n", v); return 0; } static int snapnames(void) { void *xfer = NULL; int32_t v; take_line = "restart-at 19"; flan_agent_break_poll_hook = list_and_take; v = flan_wide(30, &xfer); flan_agent_break_poll_hook = NULL; printf("returned %d\n", v); return 0; } /* ── A choice that lands inside the poll, with a break nested under it ── * * The outer break offers outer-b (0) and outer-a (1). On its first turn the * hook chooses outer-a — validated against the outer snapshot, stamped with * its generation — and then, still inside that turn, a second break starts * on top, which is what a thunk that errors does. The inner break's list * begins with [inner] and has outer-a at index 2, so an index 1 read without * its generation would resume the inner break into outer-b: the wrong break, * and the wrong frame. * * What must happen instead: the inner break turns past the choice that is * not addressed to it, for as many turns as it is left alone, and resumes * only on a choice made against its own list. * * What happens after that is pinned as it is, not as it ought to be: the * choice slot is one slot, so the inner choice overwrote the outer one, and * the outer break has to be asked again. TODO.org, "A choice made at an outer * break is lost to a nested one". */ static int level, inner_turns, outer_turns, reasked; static void *outer_a, *outer_b, *inner; static void stale_hook(void) { if (level == 1) { outer_turns++; if (outer_turns == 1) { void *xin = NULL; printf("outer choice %s", ask("restart-at 1 outer-a")); inner = flan_restart_push_c((const uint8_t *)"inner", 5, NULL, 0); level = 2; flan_break_hook((const uint8_t *)"Inner", 5, NULL, &xin); level = 1; printf("inner turns %d\n", inner_turns); printf("inner resumed into %s\n", xin == inner ? "inner" : xin == outer_a ? "outer-a" : xin == outer_b ? "outer-b" : "nothing"); flan_restart_pop_c(inner); fflush(stdout); return; } /* The outer break is still here, so its choice did not survive. */ reasked++; ask("restart-at 1 outer-a"); return; } inner_turns++; if (inner_turns == 5) { printf("status %s", ask("status")); printf("inner choice %s", ask("restart-at 0 inner")); fflush(stdout); } } static int stale(void) { void *xout = NULL; outer_a = flan_restart_push_c((const uint8_t *)"outer-a", 7, NULL, 0); outer_b = flan_restart_push_c((const uint8_t *)"outer-b", 7, NULL, 0); level = 1; flan_agent_break_poll_hook = stale_hook; flan_break_hook((const uint8_t *)"Outer", 5, NULL, &xout); flan_agent_break_poll_hook = NULL; printf("outer re-asked %d\n", reasked); printf("outer resumed into %s\n", xout == outer_a ? "outer-a" : xout == outer_b ? "outer-b" : "nothing"); flan_restart_pop_c(outer_b); flan_restart_pop_c(outer_a); return 0; } int main(int argc, char **argv) { flan_rt_init(argc, argv); if (argc < 3) { fprintf(stderr, "usage: %s snapmax|snapnames|stale SOCKET\n", argv[0]); return 2; } if (flan_agent_start((const uint8_t *)argv[2], (int64_t)strlen(argv[2])) != 0) { fprintf(stderr, "the agent did not start on %s\n", argv[2]); return 2; } setvbuf(stdout, NULL, _IOLBF, 0); if (strcmp(argv[1], "snapmax") == 0) return snapmax(); if (strcmp(argv[1], "snapnames") == 0) return snapnames(); if (strcmp(argv[1], "stale") == 0) return stale(); fprintf(stderr, "unknown mode %s\n", argv[1]); return 2; }