summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-20 23:42:12 +0300
committerPaul Buetow <paul@buetow.org>2026-05-20 23:42:12 +0300
commitbe6d4e8ffc722bf0d36c5b01ff46f817539a1525 (patch)
tree7bb0aeb51e29cfbc6735af15bb812b888f4b3574
parent2156d6e51b18e29fe8dfe8e1a519e1a84e0a1fe6 (diff)
task-47: add KindExec for execve paths
-rw-r--r--cmd/ioworkload/scenario_process.go76
-rw-r--r--cmd/ioworkload/scenarios.go1
-rw-r--r--integrationtests/process_test.go31
-rw-r--r--internal/c/generated_tracepoints.c25
-rw-r--r--internal/c/generated_tracepoints_result.txt4
-rw-r--r--internal/c/types.h14
-rw-r--r--internal/eventloop_exit.go18
-rw-r--r--internal/eventloop_process_test.go80
-rw-r--r--internal/eventloop_runtime.go13
-rw-r--r--internal/generate/bpfhandler.go26
-rw-r--r--internal/generate/classify.go9
-rw-r--r--internal/generate/classify_test.go38
-rw-r--r--internal/generate/codegen_test.go15
-rw-r--r--internal/generate/kindregistry.go1
-rw-r--r--internal/generate/testdata.go32
-rw-r--r--internal/types/fastdecode.go21
-rw-r--r--internal/types/fastdecode_test.go16
-rw-r--r--internal/types/generated_types.go73
18 files changed, 477 insertions, 16 deletions
diff --git a/cmd/ioworkload/scenario_process.go b/cmd/ioworkload/scenario_process.go
new file mode 100644
index 0000000..c2c5bbd
--- /dev/null
+++ b/cmd/ioworkload/scenario_process.go
@@ -0,0 +1,76 @@
+package main
+
+import (
+ "fmt"
+ "runtime"
+ "syscall"
+ "time"
+ "unsafe"
+
+ "golang.org/x/sys/unix"
+)
+
+const processExecEmitFor = 2 * time.Second
+
+func processExecLifecycle() error {
+ deadline := time.Now().Add(processExecEmitFor)
+ for time.Now().Before(deadline) {
+ if err := callExecveMissing(); err != nil {
+ return err
+ }
+ if err := callExecveatMissing(); err != nil {
+ return err
+ }
+ time.Sleep(10 * time.Millisecond)
+ }
+ return nil
+}
+
+func callExecveMissing() error {
+ filename, err := syscall.BytePtrFromString("/tmp/ior-missing-execve-only")
+ if err != nil {
+ return fmt.Errorf("execve filename: %w", err)
+ }
+ argv := []uintptr{uintptr(unsafe.Pointer(filename)), 0}
+ envp := []uintptr{0}
+ _, _, errno := syscall.RawSyscall(
+ syscall.SYS_EXECVE,
+ uintptr(unsafe.Pointer(filename)),
+ uintptr(unsafe.Pointer(&argv[0])),
+ uintptr(unsafe.Pointer(&envp[0])),
+ )
+ runtime.KeepAlive(filename)
+ runtime.KeepAlive(argv)
+ runtime.KeepAlive(envp)
+ if errno != syscall.ENOENT {
+ return fmt.Errorf("execve errno=%v, want ENOENT", errno)
+ }
+ return nil
+}
+
+func callExecveatMissing() error {
+ filename, err := syscall.BytePtrFromString("ior-missing-execveat-only")
+ if err != nil {
+ return fmt.Errorf("execveat filename: %w", err)
+ }
+ argv := []uintptr{uintptr(unsafe.Pointer(filename)), 0}
+ envp := []uintptr{0}
+ dirfdSigned := int64(unix.AT_FDCWD)
+ dirfd := uintptr(dirfdSigned)
+ _, _, errno := syscall.RawSyscall6(
+ unix.SYS_EXECVEAT,
+ dirfd,
+ uintptr(unsafe.Pointer(filename)),
+ uintptr(unsafe.Pointer(&argv[0])),
+ uintptr(unsafe.Pointer(&envp[0])),
+ 0,
+ 0,
+ )
+ runtime.KeepAlive(filename)
+ runtime.KeepAlive(argv)
+ runtime.KeepAlive(envp)
+ if errno != syscall.ENOENT {
+ return fmt.Errorf("execveat errno=%v, want ENOENT", errno)
+ }
+ return nil
+}
diff --git a/cmd/ioworkload/scenarios.go b/cmd/ioworkload/scenarios.go
index a1039e0..1ec8e6d 100644
--- a/cmd/ioworkload/scenarios.go
+++ b/cmd/ioworkload/scenarios.go
@@ -38,6 +38,7 @@ var scenarios = map[string]func() error{
"mountfs-management": mountfsManagement,
"polling-epoll": pollingEpoll,
"sleep-syscalls": sleepSyscalls,
+ "process-exec-lifecycle": processExecLifecycle,
"family-mixed": familyMixed,
"close-basic": closeBasic,
"close-range": closeRange,
diff --git a/integrationtests/process_test.go b/integrationtests/process_test.go
new file mode 100644
index 0000000..e9cd739
--- /dev/null
+++ b/integrationtests/process_test.go
@@ -0,0 +1,31 @@
+package integrationtests
+
+import "testing"
+
+func TestProcessExecLifecycle(t *testing.T) {
+ result, _ := runScenarioResult(t, "process-exec-lifecycle", []ExpectedEvent{
+ {
+ Tracepoint: "enter_execve",
+ PathContains: "ior-missing-execve-only",
+ Comm: "ioworkload",
+ MinCount: 1,
+ },
+ {
+ Tracepoint: "enter_execveat",
+ PathContains: "ior-missing-execveat-only",
+ Comm: "ioworkload",
+ MinCount: 1,
+ },
+ })
+
+ assertEventDurationPositive(t, result, ExpectedEvent{
+ Tracepoint: "enter_execve",
+ PathContains: "ior-missing-execve-only",
+ Comm: "ioworkload",
+ })
+ assertEventDurationPositive(t, result, ExpectedEvent{
+ Tracepoint: "enter_execveat",
+ PathContains: "ior-missing-execveat-only",
+ Comm: "ioworkload",
+ })
+}
diff --git a/internal/c/generated_tracepoints.c b/internal/c/generated_tracepoints.c
index b7fa686..58c993f 100644
--- a/internal/c/generated_tracepoints.c
+++ b/internal/c/generated_tracepoints.c
@@ -8972,7 +8972,7 @@ int handle_sys_exit_pipe(struct syscall_trace_exit *ctx) {
return 0;
}
-/// sys_enter_execve is a struct path_event
+/// sys_enter_execve is a struct exec_event
SEC("tracepoint/syscalls/sys_enter_execve")
int handle_sys_enter_execve(struct syscall_trace_enter *ctx) {
__u32 pid, tid;
@@ -8982,17 +8982,20 @@ int handle_sys_enter_execve(struct syscall_trace_enter *ctx) {
if (!ior_on_syscall_enter(tid, SYS_ENTER_EXECVE))
return 0;
- struct path_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct path_event), 0);
+ struct exec_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct exec_event), 0);
if (!ev)
return 0;
- ev->event_type = ENTER_PATH_EVENT;
+ ev->event_type = ENTER_EXEC_EVENT;
ev->trace_id = SYS_ENTER_EXECVE;
ev->pid = pid;
ev->tid = tid;
ev->time = bpf_ktime_get_boot_ns();
- __builtin_memset(&(ev->pathname), 0, sizeof(ev->pathname));
- bpf_probe_read_user_str(ev->pathname, sizeof(ev->pathname), (void*)ctx->args[0]);
+ __builtin_memset(&(ev->filename), 0, sizeof(ev->filename) + sizeof(ev->comm));
+ bpf_probe_read_user_str(ev->filename, sizeof(ev->filename), (void *)ctx->args[0]);
+ bpf_get_current_comm(&ev->comm, sizeof(ev->comm));
+ ev->dirfd = -1;
+ ev->flags = 0;
bpf_ringbuf_submit(ev, 0);
return 0;
@@ -9024,7 +9027,7 @@ int handle_sys_exit_execve(struct syscall_trace_exit *ctx) {
return 0;
}
-/// sys_enter_execveat is a struct fd_event
+/// sys_enter_execveat is a struct exec_event
SEC("tracepoint/syscalls/sys_enter_execveat")
int handle_sys_enter_execveat(struct syscall_trace_enter *ctx) {
__u32 pid, tid;
@@ -9034,16 +9037,20 @@ int handle_sys_enter_execveat(struct syscall_trace_enter *ctx) {
if (!ior_on_syscall_enter(tid, SYS_ENTER_EXECVEAT))
return 0;
- struct fd_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct fd_event), 0);
+ struct exec_event *ev = bpf_ringbuf_reserve(&event_map, sizeof(struct exec_event), 0);
if (!ev)
return 0;
- ev->event_type = ENTER_FD_EVENT;
+ ev->event_type = ENTER_EXEC_EVENT;
ev->trace_id = SYS_ENTER_EXECVEAT;
ev->pid = pid;
ev->tid = tid;
ev->time = bpf_ktime_get_boot_ns();
- ev->fd = (__s32)ctx->args[0];
+ __builtin_memset(&(ev->filename), 0, sizeof(ev->filename) + sizeof(ev->comm));
+ bpf_probe_read_user_str(ev->filename, sizeof(ev->filename), (void *)ctx->args[1]);
+ bpf_get_current_comm(&ev->comm, sizeof(ev->comm));
+ ev->dirfd = -1;
+ ev->flags = (__s32)ctx->args[4];
bpf_ringbuf_submit(ev, 0);
return 0;
diff --git a/internal/c/generated_tracepoints_result.txt b/internal/c/generated_tracepoints_result.txt
index 0d516db..8328467 100644
--- a/internal/c/generated_tracepoints_result.txt
+++ b/internal/c/generated_tracepoints_result.txt
@@ -40,8 +40,8 @@ sys_enter_epoll_pwait2 is a struct fd_event
sys_enter_epoll_wait is a struct fd_event
sys_enter_eventfd is a struct eventfd_event
sys_enter_eventfd2 is a struct eventfd_event
-sys_enter_execve is a struct path_event
-sys_enter_execveat is a struct fd_event
+sys_enter_execve is a struct exec_event
+sys_enter_execveat is a struct exec_event
sys_enter_exit is a struct null_event
sys_enter_exit_group is a struct null_event
sys_enter_faccessat is a struct path_event
diff --git a/internal/c/types.h b/internal/c/types.h
index 6fde3a1..f384075 100644
--- a/internal/c/types.h
+++ b/internal/c/types.h
@@ -47,6 +47,8 @@
#define EXIT_PTRACE_EVENT 42
#define ENTER_PERF_OPEN_EVENT 43
#define EXIT_PERF_OPEN_EVENT 44
+#define ENTER_EXEC_EVENT 45
+#define EXIT_EXEC_EVENT 46
#define UNCLASSIFIED 0
#define READ_CLASSIFIED 1
@@ -64,6 +66,18 @@ struct open_event {
char comm[MAX_PROGNAME_LENGTH];
};
+struct exec_event {
+ __u32 event_type;
+ __u32 trace_id;
+ __u64 time;
+ __u32 pid;
+ __u32 tid;
+ __s32 dirfd;
+ __s32 flags;
+ char filename[MAX_FILENAME_LENGTH];
+ char comm[MAX_PROGNAME_LENGTH];
+};
+
struct null_event {
__u32 event_type;
__u32 trace_id;
diff --git a/internal/eventloop_exit.go b/internal/eventloop_exit.go
index 5ee31f5..598adba 100644
--- a/internal/eventloop_exit.go
+++ b/internal/eventloop_exit.go
@@ -16,6 +16,8 @@ func (e *eventLoop) handleTracepointExit(ep *event.Pair) bool {
switch ev := ep.EnterEv.(type) {
case *types.OpenEvent:
return e.handleOpenExit(ep, ev)
+ case *types.ExecEvent:
+ return e.handleExecExit(ep, ev)
case *types.NameEvent:
return e.handleNameExit(ep, ev)
case *types.PathEvent:
@@ -83,6 +85,22 @@ func (e *eventLoop) handleOpenExit(ep *event.Pair, openEv *types.OpenEvent) bool
return true
}
+func (e *eventLoop) handleExecExit(ep *event.Pair, execEv *types.ExecEvent) bool {
+ if _, ok := ep.ExitEv.(*types.RetEvent); !ok {
+ e.recyclePair(ep, "Dropped malformed exec exit event")
+ return false
+ }
+ comm := types.StringValue(execEv.Comm[:])
+ ep.Comm = comm
+ ep.File = file.NewPathname(execEv.Filename[:])
+ e.setCachedComm(execEv.Tid, comm)
+ if !e.Filter().MatchPair(ep) {
+ ep.Recycle()
+ return false
+ }
+ return true
+}
+
func (e *eventLoop) handleNameExit(ep *event.Pair, nameEv *types.NameEvent) bool {
ep.File = file.NewOldnameNewname(nameEv.Oldname[:], nameEv.Newname[:])
ep.Comm = e.comm(nameEv.GetTid())
diff --git a/internal/eventloop_process_test.go b/internal/eventloop_process_test.go
new file mode 100644
index 0000000..9a06780
--- /dev/null
+++ b/internal/eventloop_process_test.go
@@ -0,0 +1,80 @@
+package internal
+
+import (
+ "testing"
+
+ "ior/internal/event"
+ "ior/internal/globalfilter"
+ "ior/internal/types"
+)
+
+func TestHandleExecExitSetsPathAndComm(t *testing.T) {
+ el := mustNewEventLoop(t, eventLoopConfig{})
+ enter := &types.ExecEvent{
+ EventType: types.ENTER_EXEC_EVENT,
+ TraceId: types.SYS_ENTER_EXECVEAT,
+ Time: 100,
+ Pid: 200,
+ Tid: 201,
+ Dirfd: -100,
+ Flags: 0,
+ }
+ copy(enter.Filename[:], "/definitely-missing-execveat")
+ copy(enter.Comm[:], "ioworkload")
+ exit := &types.RetEvent{
+ EventType: types.EXIT_RET_EVENT,
+ TraceId: types.SYS_EXIT_EXECVEAT,
+ Time: 200,
+ Ret: -2,
+ Pid: 200,
+ Tid: 201,
+ }
+ ep := &event.Pair{EnterEv: enter, ExitEv: exit}
+
+ if ok := el.handleExecExit(ep, enter); !ok {
+ t.Fatal("handleExecExit returned false")
+ }
+ if got := ep.File.Name(); got != "/definitely-missing-execveat" {
+ t.Fatalf("exec path = %q, want %q", got, "/definitely-missing-execveat")
+ }
+ if got := ep.Comm; got != "ioworkload" {
+ t.Fatalf("comm = %q, want %q", got, "ioworkload")
+ }
+}
+
+func TestHandleExecExitAppliesPairFilter(t *testing.T) {
+ el := mustNewEventLoop(t, eventLoopConfig{
+ filter: globalfilter.Filter{
+ Syscall: &globalfilter.StringFilter{Pattern: "openat"},
+ },
+ })
+ enter := &types.ExecEvent{
+ EventType: types.ENTER_EXEC_EVENT,
+ TraceId: types.SYS_ENTER_EXECVE,
+ Time: 100,
+ Pid: 210,
+ Tid: 211,
+ }
+ copy(enter.Filename[:], "/definitely-missing-execve")
+ copy(enter.Comm[:], "ioworkload")
+ exit := &types.RetEvent{
+ EventType: types.EXIT_RET_EVENT,
+ TraceId: types.SYS_EXIT_EXECVE,
+ Time: 200,
+ Ret: -2,
+ Pid: 210,
+ Tid: 211,
+ }
+ ep := &event.Pair{EnterEv: enter, ExitEv: exit}
+
+ if ok := el.handleExecExit(ep, enter); ok {
+ t.Fatal("handleExecExit should reject pair due to filter mismatch")
+ }
+}
+
+func TestInitRawHandlersRegistersExecEvent(t *testing.T) {
+ el := mustNewEventLoop(t, eventLoopConfig{})
+ if _, ok := el.rawHandlers[types.ENTER_EXEC_EVENT]; !ok {
+ t.Fatal("ENTER_EXEC_EVENT handler is not registered")
+ }
+}
diff --git a/internal/eventloop_runtime.go b/internal/eventloop_runtime.go
index 334fa63..d9d9c4c 100644
--- a/internal/eventloop_runtime.go
+++ b/internal/eventloop_runtime.go
@@ -250,6 +250,7 @@ func (e *eventLoop) initRawHandlers() {
e.registerTwoFdHandlers()
e.registerMemoryHandlers()
e.registerSleepHandlers()
+ e.registerProcessHandlers()
e.registerSecurityHandlers()
}
@@ -483,6 +484,16 @@ func (e *eventLoop) registerSleepHandlers() {
}
}
+func (e *eventLoop) registerProcessHandlers() {
+ e.rawHandlers[types.ENTER_EXEC_EVENT] = func(raw []byte, _ chan<- *event.Pair) {
+ execEv, ok := decodeRawEvent(e, types.ENTER_EXEC_EVENT, raw, types.NewExecEventFast)
+ if !ok {
+ return
+ }
+ e.tracepointEntered(execEv)
+ }
+}
+
func (e *eventLoop) registerSecurityHandlers() {
e.rawHandlers[types.ENTER_KEYCTL_EVENT] = func(raw []byte, _ chan<- *event.Pair) {
keyctlEv, ok := decodeRawEvent(e, types.ENTER_KEYCTL_EVENT, raw, types.NewKeyctlEventFast)
@@ -528,6 +539,8 @@ func (e *eventLoop) tracepointEntered(enterEv event.Event) {
switch enterEv.(type) {
case *types.OpenEvent:
e.pairs.set(enterEv)
+ case *types.ExecEvent:
+ e.pairs.set(enterEv)
default:
// Only, when we have a comm name
if _, ok := e.cachedComm(tid); ok {
diff --git a/internal/generate/bpfhandler.go b/internal/generate/bpfhandler.go
index b166725..5489d88 100644
--- a/internal/generate/bpfhandler.go
+++ b/internal/generate/bpfhandler.go
@@ -111,6 +111,8 @@ func generateExtra(tp GeneratedTracepoint, isEnter bool) string {
return generateExtraOpen(f)
case KindMqOpen:
return generateExtraMqOpen(f)
+ case KindExec:
+ return generateExtraExec(f)
case KindPathname:
return generateExtraPathname(tp, f)
case KindName:
@@ -146,6 +148,30 @@ func generateExtraMqOpen(f *Format) string {
return generateExtraOpenWithFields(f, "u_name", "oflag")
}
+func generateExtraExec(f *Format) string {
+ filenameIdx := f.FieldNumber("filename")
+ dirfdIdx := f.FieldNumber("dfd")
+ flagsIdx := f.FieldNumber("flags")
+ if filenameIdx < 0 {
+ filenameIdx = 0
+ }
+ var b strings.Builder
+ b.WriteString(" __builtin_memset(&(ev->filename), 0, sizeof(ev->filename) + sizeof(ev->comm));\n")
+ fmt.Fprintf(&b, " bpf_probe_read_user_str(ev->filename, sizeof(ev->filename), (void *)ctx->args[%d]);\n", filenameIdx)
+ b.WriteString(" bpf_get_current_comm(&ev->comm, sizeof(ev->comm));\n")
+ if dirfdIdx > -1 {
+ fmt.Fprintf(&b, " ev->dirfd = (__s32)ctx->args[%d];\n", dirfdIdx)
+ } else {
+ b.WriteString(" ev->dirfd = -1;\n")
+ }
+ if flagsIdx > -1 {
+ fmt.Fprintf(&b, " ev->flags = (__s32)ctx->args[%d];\n", flagsIdx)
+ } else {
+ b.WriteString(" ev->flags = 0;\n")
+ }
+ return b.String()
+}
+
func generateExtraOpenWithFields(f *Format, pathnameField, flagsField string) string {
filenameIdx := f.FieldNumber(pathnameField)
flagsIdx := f.FieldNumber(flagsField)
diff --git a/internal/generate/classify.go b/internal/generate/classify.go
index 77bab7e..af7d78d 100644
--- a/internal/generate/classify.go
+++ b/internal/generate/classify.go
@@ -9,6 +9,7 @@ const (
KindFd
KindOpen
KindMqOpen
+ KindExec
KindPathname
KindName
KindRet
@@ -193,6 +194,14 @@ func classifyNameOnly(name string) (ClassificationResult, bool) {
return ClassificationResult{Kind: KindFd}, true
case "sys_enter_mq_getsetattr":
return ClassificationResult{Kind: KindFd}, true
+ case "sys_enter_execve":
+ return ClassificationResult{Kind: KindExec}, true
+ case "sys_enter_execveat":
+ return ClassificationResult{Kind: KindExec}, true
+ case "sys_enter_exit":
+ return ClassificationResult{Kind: KindNull}, true
+ case "sys_enter_exit_group":
+ return ClassificationResult{Kind: KindNull}, true
}
if strings.HasPrefix(name, "sys_enter_io_") {
return ClassificationResult{Kind: KindNull}, true
diff --git a/internal/generate/classify_test.go b/internal/generate/classify_test.go
index 2a12911..5d6424b 100644
--- a/internal/generate/classify_test.go
+++ b/internal/generate/classify_test.go
@@ -231,10 +231,17 @@ func TestClassifyPathnameMknod(t *testing.T) {
}
}
-func TestClassifyPathnameExecve(t *testing.T) {
+func TestClassifyExecExecve(t *testing.T) {
r := classifyFromData(t, FormatExecve)
- if r.Kind != KindPathname {
- t.Errorf("execve: got kind %d, want KindPathname", r.Kind)
+ if r.Kind != KindExec {
+ t.Errorf("execve: got kind %d, want KindExec", r.Kind)
+ }
+}
+
+func TestClassifyExecExecveat(t *testing.T) {
+ r := classifyFromData(t, FormatExecveat)
+ if r.Kind != KindExec {
+ t.Errorf("execveat: got kind %d, want KindExec", r.Kind)
}
}
@@ -694,6 +701,24 @@ func TestClassifyKillRequiresGenerationFallback(t *testing.T) {
}
}
+func TestClassifyNullExitByName(t *testing.T) {
+ tests := []string{"sys_enter_exit", "sys_enter_exit_group"}
+ for _, name := range tests {
+ t.Run(name, func(t *testing.T) {
+ r := ClassifyFormat(&Format{
+ Name: name,
+ ExternalFields: []Field{
+ {Type: "long", Name: "__syscall_nr"},
+ {Type: "int", Name: "error_code"},
+ },
+ })
+ if r.Kind != KindNull {
+ t.Errorf("%s: got kind %d, want KindNull", name, r.Kind)
+ }
+ })
+ }
+}
+
// --- End-to-end classification with enter+exit pairs ---
func TestClassifySyscallPairAccepted(t *testing.T) {
@@ -722,7 +747,8 @@ func TestClassifySyscallPairAccepted(t *testing.T) {
{"pread64", FormatPread64, FormatExitPread64, KindFd},
{"symlink", FormatSymlink, FormatExitSymlink, KindName},
{"mknod", FormatMknod, FormatExitMknod, KindPathname},
- {"execve", FormatExecve, FormatExitExecve, KindPathname},
+ {"execve", FormatExecve, FormatExitExecve, KindExec},
+ {"execveat", FormatExecveat, FormatExitExecveat, KindExec},
{"accept", FormatAccept, FormatExitAccept, KindAccept},
{"accept4", FormatAccept4, FormatExitAccept4, KindAccept},
{"socket", FormatSocket, FormatExitSocket, KindSocket},
@@ -760,6 +786,8 @@ func TestClassifySyscallPairAccepted(t *testing.T) {
{"swapon", FormatSwapon, FormatExitSwapon, KindPathname},
{"swapoff", FormatSwapoff, FormatExitSwapoff, KindPathname},
{"kill", FormatKill, FormatExitKill, KindNull},
+ {"exit", syntheticEnter("exit", 9310), syntheticExit("exit", 9309), KindNull},
+ {"exit_group", syntheticEnter("exit_group", 9312), syntheticExit("exit_group", 9311), KindNull},
}
for _, tt := range tests {
@@ -782,6 +810,7 @@ func TestClassifySyscallPairEmitsAllFamilies(t *testing.T) {
}{
{"mknod", FormatMknod, FormatExitMknod, FamilyFS},
{"execve", FormatExecve, FormatExitExecve, FamilyProcess},
+ {"execveat", FormatExecveat, FormatExitExecveat, FamilyProcess},
{"accept", FormatAccept, FormatExitAccept, FamilyNetwork},
{"accept4", FormatAccept4, FormatExitAccept4, FamilyNetwork},
{"socket", FormatSocket, FormatExitSocket, FamilyNetwork},
@@ -816,6 +845,7 @@ func TestClassifySyscallPairEmitsAllFamilies(t *testing.T) {
{"swapon", FormatSwapon, FormatExitSwapon, FamilyFS},
{"swapoff", FormatSwapoff, FormatExitSwapoff, FamilyFS},
{"kill", FormatKill, FormatExitKill, FamilySignals},
+ {"exit_group", syntheticEnter("exit_group", 9316), syntheticExit("exit_group", 9315), FamilyProcess},
}
for _, tt := range tests {
diff --git a/internal/generate/codegen_test.go b/internal/generate/codegen_test.go
index f1c98df..95ced4d 100644
--- a/internal/generate/codegen_test.go
+++ b/internal/generate/codegen_test.go
@@ -64,6 +64,17 @@ func TestGenerateMqOpenHandler(t *testing.T) {
requireContains(t, output, "ev->flags = ctx->args[1];")
}
+func TestGenerateExecHandler(t *testing.T) {
+ output := generateFromPair(t, FormatExecveat, FormatExitExecveat)
+
+ requireContains(t, output, `SEC("tracepoint/syscalls/sys_enter_execveat")`)
+ requireContains(t, output, "struct exec_event *ev")
+ requireContains(t, output, "ev->event_type = ENTER_EXEC_EVENT;")
+ requireContains(t, output, "bpf_probe_read_user_str(ev->filename, sizeof(ev->filename), (void *)ctx->args[1]);")
+ requireContains(t, output, "ev->dirfd = (__s32)ctx->args[0];")
+ requireContains(t, output, "ev->flags = (__s32)ctx->args[4];")
+}
+
func TestGenerateOpenat2Handler(t *testing.T) {
f := mustParseOne(t, FormatOpenat2)
r := ClassifyFormat(&f)
@@ -515,6 +526,7 @@ func TestGenerateAllEventTypes(t *testing.T) {
{KindFd, "ENTER_FD_EVENT", "EXIT_FD_EVENT"},
{KindOpen, "ENTER_OPEN_EVENT", "EXIT_OPEN_EVENT"},
{KindMqOpen, "ENTER_OPEN_EVENT", "EXIT_OPEN_EVENT"},
+ {KindExec, "ENTER_EXEC_EVENT", "EXIT_EXEC_EVENT"},
{KindPathname, "ENTER_PATH_EVENT", "EXIT_PATH_EVENT"},
{KindName, "ENTER_NAME_EVENT", "EXIT_NAME_EVENT"},
{KindRet, "ENTER_RET_EVENT", "EXIT_RET_EVENT"},
@@ -555,6 +567,7 @@ func TestEventStructNames(t *testing.T) {
{KindFd, "fd_event"},
{KindOpen, "open_event"},
{KindMqOpen, "open_event"},
+ {KindExec, "exec_event"},
{KindPathname, "path_event"},
{KindName, "name_event"},
{KindRet, "ret_event"},
@@ -593,7 +606,7 @@ func TestEnterReject(t *testing.T) {
t.Error("KindNone should be enter-rejected")
}
- accepted := []TracepointKind{KindFd, KindOpen, KindMqOpen, KindPathname, KindName, KindFcntl, KindNull, KindDup3, KindOpenByHandleAt, KindSocket, KindSocketpair, KindAccept, KindPipe, KindEventfd, KindEpollCtl, KindTwoFd, KindPoll, KindMem, KindSleep, KindKeyctl, KindPtrace, KindPerfOpen}
+ accepted := []TracepointKind{KindFd, KindOpen, KindMqOpen, KindExec, KindPathname, KindName, KindFcntl, KindNull, KindDup3, KindOpenByHandleAt, KindSocket, KindSocketpair, KindAccept, KindPipe, KindEventfd, KindEpollCtl, KindTwoFd, KindPoll, KindMem, KindSleep, KindKeyctl, KindPtrace, KindPerfOpen}
for _, k := range accepted {
if isEnterRejected(k) {
t.Errorf("kind %d should NOT be enter-rejected", k)
diff --git a/internal/generate/kindregistry.go b/internal/generate/kindregistry.go
index 6afe4c1..a5f5795 100644
--- a/internal/generate/kindregistry.go
+++ b/internal/generate/kindregistry.go
@@ -19,6 +19,7 @@ var kindRegistry = map[TracepointKind]kindMeta{
KindFd: {structName: "fd_event", enterAccepted: true},
KindOpen: {structName: "open_event", enterAccepted: true},
KindMqOpen: {structName: "open_event", enterAccepted: true},
+ KindExec: {structName: "exec_event", enterAccepted: true},
KindPathname: {structName: "path_event", enterAccepted: true},
KindName: {structName: "name_event", enterAccepted: true},
KindRet: {structName: "ret_event", enterAccepted: false},
diff --git a/internal/generate/testdata.go b/internal/generate/testdata.go
index 2bc041e..d94c141 100644
--- a/internal/generate/testdata.go
+++ b/internal/generate/testdata.go
@@ -1098,6 +1098,38 @@ format:
print fmt: "0x%lx", REC->ret
`
+const FormatExecveat = `name: sys_enter_execveat
+ID: 869
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:int __syscall_nr; offset:8; size:4; signed:1;
+ field:int dfd; offset:16; size:8; signed:0;
+ field:const char * filename; offset:24; size:8; signed:0;
+ field:const char *const * argv; offset:32; size:8; signed:0;
+ field:const char *const * envp; offset:40; size:8; signed:0;
+ field:int flags; offset:48; size:8; signed:0;
+
+print fmt: "dfd: 0x%08lx, filename: 0x%08lx, argv: 0x%08lx, envp: 0x%08lx, flags: 0x%08lx", ((unsigned long)(REC->dfd)), ((unsigned long)(REC->filename)), ((unsigned long)(REC->argv)), ((unsigned long)(REC->envp)), ((unsigned long)(REC->flags))
+`
+
+const FormatExitExecveat = `name: sys_exit_execveat
+ID: 868
+format:
+ field:unsigned short common_type; offset:0; size:2; signed:0;
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
+ field:int common_pid; offset:4; size:4; signed:1;
+
+ field:int __syscall_nr; offset:8; size:4; signed:1;
+ field:long ret; offset:16; size:8; signed:1;
+
+print fmt: "0x%lx", REC->ret
+`
+
const FormatMknod = `name: sys_enter_mknod
ID: 894
format:
diff --git a/internal/types/fastdecode.go b/internal/types/fastdecode.go
index e592e5d..5b0f588 100644
--- a/internal/types/fastdecode.go
+++ b/internal/types/fastdecode.go
@@ -4,6 +4,7 @@ import "encoding/binary"
const (
openEventSize = 300
+ execEventSize = 304
nullEventSize = 24
fdEventSize = 28
retEventSize = 36
@@ -51,6 +52,26 @@ func NewOpenEventFast(raw []byte) *OpenEvent {
return o
}
+func NewExecEventFast(raw []byte) *ExecEvent {
+ if len(raw) < execEventSize {
+ return nil
+ }
+ if len(raw) != execEventSize {
+ return NewExecEvent(raw)
+ }
+ e := poolOfExecEvents.Get().(*ExecEvent)
+ e.EventType = EventType(binary.LittleEndian.Uint32(raw[0:4]))
+ e.TraceId = TraceId(binary.LittleEndian.Uint32(raw[4:8]))
+ e.Time = binary.LittleEndian.Uint64(raw[8:16])
+ e.Pid = binary.LittleEndian.Uint32(raw[16:20])
+ e.Tid = binary.LittleEndian.Uint32(raw[20:24])
+ e.Dirfd = int32(binary.LittleEndian.Uint32(raw[24:28]))
+ e.Flags = int32(binary.LittleEndian.Uint32(raw[28:32]))
+ copy(e.Filename[:], raw[32:288])
+ copy(e.Comm[:], raw[288:304])
+ return e
+}
+
func NewNullEventFast(raw []byte) *NullEvent {
if len(raw) < nullEventSize {
return nil
diff --git a/internal/types/fastdecode_test.go b/internal/types/fastdecode_test.go
index 1a3a8bc..d09f6a9 100644
--- a/internal/types/fastdecode_test.go
+++ b/internal/types/fastdecode_test.go
@@ -21,6 +21,21 @@ func TestFastDecodersMatchGeneratedDecoders(t *testing.T) {
}
})
+ t.Run("ExecEvent", func(t *testing.T) {
+ ev := &ExecEvent{EventType: ENTER_EXEC_EVENT, TraceId: SYS_ENTER_EXECVEAT, Time: 1, Pid: 2, Tid: 3, Dirfd: -100, Flags: 4}
+ copy(ev.Filename[:], "a")
+ copy(ev.Comm[:], "b")
+ raw, _ := ev.Bytes()
+
+ slow := NewExecEvent(raw)
+ fast := NewExecEventFast(raw)
+ defer slow.Recycle()
+ defer fast.Recycle()
+ if !slow.Equals(fast) {
+ t.Fatalf("exec decode mismatch")
+ }
+ })
+
t.Run("NullEvent", func(t *testing.T) {
ev := &NullEvent{EventType: ENTER_NULL_EVENT, TraceId: SYS_ENTER_SYNC, Time: 1, Pid: 2, Tid: 3}
raw, _ := ev.Bytes()
@@ -535,6 +550,7 @@ func TestFastDecodersReturnNilOnShortPayload(t *testing.T) {
decode func([]byte) bool
}{
{name: "OpenEvent", decode: func(raw []byte) bool { return NewOpenEventFast(raw) == nil }},
+ {name: "ExecEvent", decode: func(raw []byte) bool { return NewExecEventFast(raw) == nil }},
{name: "NullEvent", decode: func(raw []byte) bool { return NewNullEventFast(raw) == nil }},
{name: "FdEvent", decode: func(raw []byte) bool { return NewFdEventFast(raw) == nil }},
{name: "RetEvent", decode: func(raw []byte) bool { return NewRetEventFast(raw) == nil }},
diff --git a/internal/types/generated_types.go b/internal/types/generated_types.go
index d378794..8ddac3e 100644
--- a/internal/types/generated_types.go
+++ b/internal/types/generated_types.go
@@ -112,6 +112,8 @@ const ENTER_PTRACE_EVENT = 41
const EXIT_PTRACE_EVENT = 42
const ENTER_PERF_OPEN_EVENT = 43
const EXIT_PERF_OPEN_EVENT = 44
+const ENTER_EXEC_EVENT = 45
+const EXIT_EXEC_EVENT = 46
const UNCLASSIFIED = 0
const READ_CLASSIFIED = 1
const WRITE_CLASSIFIED = 2
@@ -921,6 +923,77 @@ func (o *OpenEvent) Recycle() {
poolOfOpenEvents.Put(o)
}
+type ExecEvent struct {
+ EventType EventType
+ TraceId TraceId
+ Time uint64
+ Pid uint32
+ Tid uint32
+ Dirfd int32
+ Flags int32
+ Filename [MAX_FILENAME_LENGTH]byte
+ Comm [MAX_PROGNAME_LENGTH]byte
+}
+
+func (e ExecEvent) String() string {
+ return fmt.Sprintf("EventType:%v TraceId:%v Time:%v Pid:%v Tid:%v Dirfd:%v Flags:%v Filename:%v Comm:%v", e.EventType, e.TraceId, e.Time, e.Pid, e.Tid, e.Dirfd, e.Flags, string(e.Filename[:]), string(e.Comm[:]))
+}
+
+func (e ExecEvent) Equals(other any) bool {
+ otherConcrete, ok := other.(*ExecEvent)
+ if !ok {
+ return false
+ }
+ return e.EventType == otherConcrete.EventType && e.TraceId == otherConcrete.TraceId && e.Time == otherConcrete.Time && e.Pid == otherConcrete.Pid && e.Tid == otherConcrete.Tid && e.Dirfd == otherConcrete.Dirfd && e.Flags == otherConcrete.Flags && e.Filename == otherConcrete.Filename && e.Comm == otherConcrete.Comm
+}
+
+func (e *ExecEvent) GetEventType() EventType {
+ return e.EventType
+}
+
+func (e *ExecEvent) GetTraceId() TraceId {
+ return e.TraceId
+}
+
+func (e *ExecEvent) GetPid() uint32 {
+ return e.Pid
+}
+
+func (e *ExecEvent) GetTid() uint32 {
+ return e.Tid
+}
+
+func (e *ExecEvent) GetTime() uint64 {
+ return e.Time
+}
+
+var poolOfExecEvents = sync.Pool{
+ New: func() any { return &ExecEvent{} },
+}
+
+func NewExecEvent(raw []byte) *ExecEvent {
+ e := poolOfExecEvents.Get().(*ExecEvent)
+ if err := binary.Read(bytes.NewReader(raw), binary.LittleEndian, e); err != nil {
+ *e = ExecEvent{}
+ poolOfExecEvents.Put(e)
+ return nil
+ }
+ return e
+}
+
+func (e *ExecEvent) Bytes() ([]byte, error) {
+ buf := new(bytes.Buffer)
+ err := binary.Write(buf, binary.LittleEndian, e)
+ if err != nil {
+ return nil, err
+ }
+ return buf.Bytes(), nil
+}
+
+func (e *ExecEvent) Recycle() {
+ poolOfExecEvents.Put(e)
+}
+
type NullEvent struct {
EventType EventType
TraceId TraceId