From d37ed0371dbe8ed49b48ea56bb3b6fe701f6e48a Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Sat, 30 May 2026 16:47:26 +0300 Subject: test(gettid): lock in KindNull/FamilyProcess/UNCLASSIFIED classification Audit of gettid(2) ('pid_t gettid(void)', no args, always succeeds) found the classification correct and consistent with its no-arg id-returning siblings getpid/getppid/getuid/getgid (FamilyProcess, KindNull enter, ret_event UNCLASSIFIED exit), and mage generate produces no diff. However gettid lacked dedicated lock-in coverage and was missing entirely from the family_test.go Process table despite its siblings being asserted there. Add TestClassifyGettidNullEnter and TestClassifyExitGettidUnclassifiedRet (mirroring the getgid pattern: enter null_event capturing nothing, exit ret classified UNCLASSIFIED so the returned tid is never mistaken for a byte count) plus gettid enter+exit FamilyProcess assertions in family_test.go. Co-Authored-By: Claude Opus 4.8 --- internal/generate/classify_test.go | 49 ++++++++++++++++++++++++++++++++++++++ internal/generate/family_test.go | 8 +++++++ 2 files changed, 57 insertions(+) diff --git a/internal/generate/classify_test.go b/internal/generate/classify_test.go index 560aba2..f915c4a 100644 --- a/internal/generate/classify_test.go +++ b/internal/generate/classify_test.go @@ -603,6 +603,55 @@ func TestClassifyExitGetgidUnclassifiedRet(t *testing.T) { } } +// TestClassifyGettidNullEnter locks in the gettid(2) enter classification using +// the syscall's REAL tracepoint fields. gettid(2) is "pid_t gettid(void)" — it +// takes NO arguments at all, so its enter format carries only the synthetic +// __syscall_nr field and must classify as KindNull (null_event capturing +// nothing). This matches the no-arg id-returning reader cluster +// getuid/geteuid/getegid/getpid/getppid/getgid and the explicit name-only +// mapping in classify.go. With no real argument fields there is nothing the fd +// or path heuristics could latch onto, so PathnameField must stay empty. +func TestClassifyGettidNullEnter(t *testing.T) { + r := ClassifyFormat(&Format{ + Name: "sys_enter_gettid", + ExternalFields: []Field{ + {Type: "int", Name: "__syscall_nr"}, + }, + }) + if r.Kind != KindNull { + t.Fatalf("enter_gettid: got kind %d, want KindNull", r.Kind) + } + // gettid has no arguments, so nothing must be captured as a path/fd. + if r.PathnameField != "" { + t.Errorf("enter_gettid: unexpected PathnameField %q, want empty", r.PathnameField) + } +} + +// TestClassifyExitGettidUnclassifiedRet locks in that the gettid exit +// tracepoint is classified as KindRet and Unclassified. gettid(2) returns the +// caller's thread ID (pid_t) and ALWAYS succeeds — its return is a numeric +// thread identifier, NOT a transferred byte count and never an error status. +// Its exit format carries a single "ret" field and must map to a plain +// ret_event (KindRet) whose ret_type stays UNCLASSIFIED. Misclassifying the tid +// as a READ/WRITE/TRANSFER byte count would be a real bug. This matches its +// no-arg reader siblings getuid/getpid/getgid (no byte semantics on their +// return). +func TestClassifyExitGettidUnclassifiedRet(t *testing.T) { + r := ClassifyFormat(&Format{ + Name: "sys_exit_gettid", + ExternalFields: []Field{ + {Type: "int", Name: "__syscall_nr"}, + {Type: "long", Name: "ret"}, + }, + }) + if r.Kind != KindRet { + t.Fatalf("exit_gettid: got kind %d, want KindRet", r.Kind) + } + if got := ClassifyRet("sys_exit_gettid"); got != Unclassified { + t.Errorf("ClassifyRet(sys_exit_gettid) = %q, want UNCLASSIFIED", got) + } +} + // TestClassifyExitGetpeername locks in that the getpeername exit tracepoint is // classified as KindRet. getpeername(2) returns int (0 on success, -1 on // error), so its exit format carries a single "ret" field and must map to a diff --git a/internal/generate/family_test.go b/internal/generate/family_test.go index d86cc4a..475a75f 100644 --- a/internal/generate/family_test.go +++ b/internal/generate/family_test.go @@ -67,6 +67,14 @@ func TestClassifySyscallFamily(t *testing.T) { {"sys_enter_getpgrp", FamilyProcess}, {"sys_enter_getpid", FamilyProcess}, {"sys_enter_getppid", FamilyProcess}, + // gettid(2) ("pid_t gettid(void)") returns the caller's thread ID and + // belongs with the no-arg id-returning reader cluster + // getpid/getppid/getuid/getgid under FamilyProcess (it is a per-thread + // identity query, not Time/Sched/Misc). Assert enter+exit so a stray + // reclassification trips this test. Keep in sync with the Process list in + // docs/syscall-tracing-plan.md. + {"sys_enter_gettid", FamilyProcess}, + {"sys_exit_gettid", FamilyProcess}, {"sys_enter_rt_sigaction", FamilySignals}, {"sys_enter_clock_gettime", FamilyTime}, // gettimeofday(2) gets wall-clock time via a userspace timeval/timezone -- cgit v1.2.3