diff options
| author | Paul Buetow <paul@buetow.org> | 2026-06-01 10:35:18 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-06-01 10:35:18 +0300 |
| commit | b7a63e9964a865441b4e0791a19b5a7bbfa2eff4 (patch) | |
| tree | e09353be8156e2c4e0877d69476dc52fad2adea3 /cmd | |
| parent | 8549884d1d957821b75dfbd5a4ff746667095f17 (diff) | |
test(integration): add SysV shm tracing coverage
The SysV shared-memory family (shmget/shmat/shmdt/shmctl) had no
end-to-end integration coverage. Add an ioworkload `sysv-shm-basic`
scenario that, without privileges, runs shmget(IPC_PRIVATE) -> shmat ->
write into the mapped segment -> shmdt -> shmctl(IPC_RMID), always
issuing IPC_RMID (via defer) so no kernel segment leaks.
Add TestSysVShmBasic asserting enter_shmget/enter_shmat/enter_shmdt/
enter_shmctl are each traced with a positive (paired enter/exit)
duration.
msg/sem coverage is scoped out and tracked as a follow-up task (7i0).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diffstat (limited to 'cmd')
| -rw-r--r-- | cmd/ioworkload/scenario_sysv.go | 98 | ||||
| -rw-r--r-- | cmd/ioworkload/scenarios.go | 1 |
2 files changed, 99 insertions, 0 deletions
diff --git a/cmd/ioworkload/scenario_sysv.go b/cmd/ioworkload/scenario_sysv.go new file mode 100644 index 0000000..178f04b --- /dev/null +++ b/cmd/ioworkload/scenario_sysv.go @@ -0,0 +1,98 @@ +package main + +import ( + "fmt" + "syscall" + "unsafe" + + "golang.org/x/sys/unix" +) + +// sysvShmPayload is written into the attached SysV shared-memory segment so the +// scenario touches the mapping (forcing a real page fault) rather than just +// attaching and detaching it. +var sysvShmPayload = []byte("ior-sysv-shm") + +// sysvShmBasic exercises the SysV shared-memory family end-to-end without any +// privileges: it creates a private anonymous segment (shmget IPC_PRIVATE), +// attaches it (shmat), writes a few bytes into the mapped region, detaches it +// (shmdt), and finally removes it (shmctl IPC_RMID). IPC_RMID is always issued +// (even on a partial failure) so the test never leaks a kernel IPC object. +func sysvShmBasic() error { + const segSize = 4096 + shmid, err := shmGet(segSize) + if err != nil { + return err + } + // Guarantee the segment is removed regardless of how the rest fares; an + // orphaned IPC_PRIVATE segment would otherwise leak until reboot. + defer func() { _ = shmRemove(shmid) }() + + addr, err := shmAttach(shmid) + if err != nil { + return err + } + if err := shmWrite(addr, segSize); err != nil { + _ = shmDetach(addr) + return err + } + if err := shmDetach(addr); err != nil { + return err + } + return nil +} + +// shmGet creates a new private SysV shared-memory segment of the given size and +// returns its identifier. +func shmGet(size uintptr) (uintptr, error) { + shmid, _, errno := syscall.Syscall( + syscall.SYS_SHMGET, + uintptr(unix.IPC_PRIVATE), + size, + uintptr(unix.IPC_CREAT|0o600), + ) + if errno != 0 { + return 0, fmt.Errorf("shmget: %w", errno) + } + return shmid, nil +} + +// shmAttach attaches the segment into the address space and returns its base +// address. shmflg 0 requests a read/write mapping at a kernel-chosen address. +func shmAttach(shmid uintptr) (uintptr, error) { + addr, _, errno := syscall.Syscall(syscall.SYS_SHMAT, shmid, 0, 0) + if errno != 0 { + return 0, fmt.Errorf("shmat: %w", errno) + } + return addr, nil +} + +// shmWrite copies the payload into the mapped segment, faulting the page in so +// the workload genuinely uses the shared memory. +func shmWrite(addr, size uintptr) error { + if uintptr(len(sysvShmPayload)) > size { + return fmt.Errorf("shm payload (%d) exceeds segment size (%d)", len(sysvShmPayload), size) + } + seg := unsafe.Slice((*byte)(unsafe.Pointer(addr)), len(sysvShmPayload)) + copy(seg, sysvShmPayload) + return nil +} + +// shmDetach detaches the previously attached segment from the address space. +func shmDetach(addr uintptr) error { + _, _, errno := syscall.Syscall(syscall.SYS_SHMDT, addr, 0, 0) + if errno != 0 { + return fmt.Errorf("shmdt: %w", errno) + } + return nil +} + +// shmRemove marks the segment for destruction (IPC_RMID); the kernel frees it +// once the last attachment is gone. +func shmRemove(shmid uintptr) error { + _, _, errno := syscall.Syscall(syscall.SYS_SHMCTL, shmid, uintptr(unix.IPC_RMID), 0) + if errno != 0 { + return fmt.Errorf("shmctl IPC_RMID: %w", errno) + } + return nil +} diff --git a/cmd/ioworkload/scenarios.go b/cmd/ioworkload/scenarios.go index 597d0e8..ae003b6 100644 --- a/cmd/ioworkload/scenarios.go +++ b/cmd/ioworkload/scenarios.go @@ -43,6 +43,7 @@ var scenarios = map[string]func() error{ "eventfd2-basic": eventfd2Basic, "fd-from-air-eventfd-users": fdFromAirEventfdUsers, "mq-posix-basic": mqPosixBasic, + "sysv-shm-basic": sysvShmBasic, "mountfs-management": mountfsManagement, "polling-epoll": pollingEpoll, "sleep-syscalls": sleepSyscalls, |
