summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-30 16:27:20 +0300
committerPaul Buetow <paul@buetow.org>2026-05-30 16:27:20 +0300
commit0f470e8e63d9cb458c711ade594e18acd8791504 (patch)
treead4ec784cbe5611dff5ed4ae8f3bb44efb6c6fa8
parent65d276b67e65427e8cd25fd45b142e6fff1259f0 (diff)
test(select): lock in nfds/timeval capture and UNCLASSIFIED exit
Audit of syscall select confirmed the tracing is already correct: select is KindPoll/FamilyPolling like poll/ppoll/pselect6, the enter handler captures nfds from args[0] as a count (not as an fd) and the timeout from the args[4] timeval, and the exit is an UNCLASSIFIED ret_event (ready-fd count, not a byte transfer). Add TestGenerateSelectHandlerCapturesNfdsAndTimevalTimeout mirroring the ppoll lock-in test, with negative assertions that no argument is ever captured as an fd and that the exit carries no bytes/fd fields. This guards against regressing nfds (a count) into a KindFd fd capture. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
-rw-r--r--internal/generate/codegen_test.go35
1 files changed, 35 insertions, 0 deletions
diff --git a/internal/generate/codegen_test.go b/internal/generate/codegen_test.go
index cd34c63..3753b17 100644
--- a/internal/generate/codegen_test.go
+++ b/internal/generate/codegen_test.go
@@ -1394,6 +1394,41 @@ func TestGeneratePselect6HandlerCapturesTimeoutPointer(t *testing.T) {
requireContains(t, output, "ev->timeout_ns = ts.tv_sec * 1000000000LL + ts.tv_nsec;")
}
+// TestGenerateSelectHandlerCapturesNfdsAndTimevalTimeout locks in the select
+// argument layout. select(int nfds, fd_set *readfds, fd_set *writefds,
+// fd_set *exceptfds, struct timeval *timeout): args[0] is nfds — the highest
+// fd number plus one, i.e. a COUNT, NOT a file descriptor — and args[1..3] are
+// userspace fd_set bitmask pointers (also NOT single fds). The timeout is a
+// timeval pointer at args[4]. The handler must therefore capture nfds from
+// args[0] and the timeout from the args[4] timeval (sec*1e9 + usec*1e3), and
+// must NEVER read any argument as an fd: capturing args[0] as an fd would
+// record a garbage fd (it is a count), and capturing the bitmask pointers
+// would record garbage pointers. The exit is an UNCLASSIFIED ret_event because
+// the return value is a ready-fd count (>=0) or -1, never a byte transfer.
+func TestGenerateSelectHandlerCapturesNfdsAndTimevalTimeout(t *testing.T) {
+ output := generateFromPair(t, FormatSelect, FormatExitSelect)
+
+ // Enter: poll_event with nfds from args[0] and timeval timeout from args[4].
+ requireContains(t, output, "struct poll_event *ev")
+ requireContains(t, output, "ev->event_type = ENTER_POLL_EVENT;")
+ requireContains(t, output, "ev->trace_id = SYS_ENTER_SELECT;")
+ requireContains(t, output, "ev->nfds = (__s32)ctx->args[0];")
+ requireContains(t, output, "if (ctx->args[4] != 0) {")
+ requireContains(t, output, "ev->timeout_ns = tv.tv_sec * 1000000000LL + tv.tv_usec * 1000LL;")
+
+ // Negative: nfds is a count and the fd_set args are bitmask pointers, so no
+ // argument may ever be captured as an fd, and the exit carries no bytes/fd
+ // fields (the ready count is not a byte transfer).
+ requireNotContains(t, output, "ev->fd = (__s32)ctx->args[0];")
+ requireNotContains(t, output, "ev->fd = (__s32)ctx->args[1];")
+ requireNotContains(t, output, "ev->bytes")
+
+ // Exit: plain ret_event recording the ready-count (>=0) or -1, UNCLASSIFIED.
+ requireContains(t, output, "ev->event_type = EXIT_RET_EVENT;")
+ requireContains(t, output, "ev->ret = ctx->ret;")
+ requireContains(t, output, "ev->ret_type = UNCLASSIFIED;")
+}
+
// TestGeneratePpollHandlerCapturesNfdsAndTimeoutPointer locks in the ppoll
// argument layout. ppoll(struct pollfd *fds, nfds_t nfds,
// const struct timespec *tmo_p, const sigset_t *sigmask): args[0] is a