diff options
| author | Paul Buetow <paul@buetow.org> | 2026-06-09 22:09:13 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-06-09 22:09:13 +0300 |
| commit | 3cd5e655ee1768b4118815d1ea887acdd57eb498 (patch) | |
| tree | 05285bc148faff79f931059aff97b8d3fbb9b126 /cmd/ioworkload/scenario_pidfd.go | |
| parent | 57615945e6796950e7095a3ee8a97651ae3f1bd9 (diff) | |
test: add coverage for pidfd_send_signal and fadvise64
pidfd_send_signal (FamilyIPC, KindFd@arg0) and fadvise64 (KindFd,
UNCLASSIFIED fd-based hint) previously had no end-to-end integration
coverage despite correct classification/tracing.
pidfd_send_signal: add a pidfd-send-signal ioworkload scenario that
opens a pidfd for the current process and issues a sig-0 liveness probe
(delivers nothing, safe to target self) via syscall.Syscall6 with the
per-arch nr 424. TestPidfdSendSignal asserts enter_pidfd_send_signal is
captured; pidfd_send_signal added to the pidfd -trace-syscalls list.
fadvise64: add readwrite-fadvise64 and readwrite-fadvise64-ebadf
scenarios using unix.Fadvise(fd, 0, 0, FADV_NORMAL), mirroring the
readahead tests. TestReadwriteFadvise64 asserts enter_fadvise64 with
Bytes==0 (UNCLASSIFIED: offset/len are hints, not bytes transferred) and
positive duration; the ebadf variant asserts enter capture with Bytes==0
on the failing call.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diffstat (limited to 'cmd/ioworkload/scenario_pidfd.go')
| -rw-r--r-- | cmd/ioworkload/scenario_pidfd.go | 54 |
1 files changed, 54 insertions, 0 deletions
diff --git a/cmd/ioworkload/scenario_pidfd.go b/cmd/ioworkload/scenario_pidfd.go index 2aafced..fd3df5a 100644 --- a/cmd/ioworkload/scenario_pidfd.go +++ b/cmd/ioworkload/scenario_pidfd.go @@ -75,6 +75,26 @@ func pidfdGetfdFailure() error { return nil } +// pidfdSendSignal opens a pidfd for the current process and issues a +// pidfd_send_signal liveness probe against it. Signal 0 is a special "no signal" +// value: the kernel performs only the permission/existence checks and delivers +// NOTHING, so targeting our own process is completely safe (no signal handler +// runs and the process is not affected). The scenario exercises the enter +// fd_event (pidfd at args[0]) and the exit ret_event (UNCLASSIFIED) end-to-end. +func pidfdSendSignal() error { + pidfd, err := pidfdOpen(os.Getpid(), 0) + if err != nil { + return fmt.Errorf("pidfd_open self: %w", err) + } + defer syscall.Close(pidfd) + + // pidfd_send_signal(pidfd, sig=0, info=NULL, flags=0): liveness probe only. + if err := pidfdSendSignalRaw(pidfd, 0, 0, 0); err != nil { + return fmt.Errorf("pidfd_send_signal: %w", err) + } + return nil +} + func pidfdOpen(pid int, flags uintptr) (int, error) { syscallNr, err := pidfdOpenSyscallNr() if err != nil { @@ -112,6 +132,30 @@ func pidfdGetfdSyscallNr() (uintptr, error) { return pidfdGetfdSyscallNrForArch(runtime.GOARCH) } +func pidfdSendSignalRaw(pidfd int, sig int, info uintptr, flags uintptr) error { + syscallNr, err := pidfdSendSignalSyscallNr() + if err != nil { + return err + } + _, _, errno := syscall.Syscall6( + syscallNr, + uintptr(pidfd), + uintptr(sig), + info, + flags, + 0, + 0, + ) + if errno != 0 { + return errno + } + return nil +} + +func pidfdSendSignalSyscallNr() (uintptr, error) { + return pidfdSendSignalSyscallNrForArch(runtime.GOARCH) +} + func pidfdOpenSyscallNrForArch(arch string) (uintptr, error) { // Go's syscall package does not expose pidfd constants on all toolchains. switch arch { @@ -131,3 +175,13 @@ func pidfdGetfdSyscallNrForArch(arch string) (uintptr, error) { return 0, fmt.Errorf("pidfd_getfd syscall number not defined for GOARCH=%s", arch) } } + +func pidfdSendSignalSyscallNrForArch(arch string) (uintptr, error) { + // Go's syscall package does not expose pidfd constants on all toolchains. + switch arch { + case "amd64", "arm64": + return 424, nil + default: + return 0, fmt.Errorf("pidfd_send_signal syscall number not defined for GOARCH=%s", arch) + } +} |
