From 7fb497c435596a36c0fb0bd0ecae2a84793bcc70 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Mon, 18 May 2026 14:30:26 +0300 Subject: j6: account bytes for ret-classified syscalls --- internal/eventloop_bytes_test.go | 18 +++++++++++ internal/eventloop_exit.go | 11 +++++-- internal/eventloop_runtime.go | 1 + internal/generate/classify_test.go | 61 +++++++++++++++++++++++++++++++++++ internal/generate/retclassify_test.go | 25 ++++++++++++++ 5 files changed, 113 insertions(+), 3 deletions(-) (limited to 'internal') diff --git a/internal/eventloop_bytes_test.go b/internal/eventloop_bytes_test.go index ed7f7af..a7c25ef 100644 --- a/internal/eventloop_bytes_test.go +++ b/internal/eventloop_bytes_test.go @@ -3,6 +3,7 @@ package internal import ( "testing" + "ior/internal/event" "ior/internal/types" ) @@ -71,3 +72,20 @@ func TestBytesFromRet(t *testing.T) { }) } } + +func TestApplyRetBytesForNullEnterRetExitPair(t *testing.T) { + pair := &event.Pair{ + EnterEv: &types.NullEvent{TraceId: types.SYS_ENTER_SPLICE}, + ExitEv: &types.RetEvent{ + TraceId: types.SYS_EXIT_SPLICE, + Ret: 4096, + RetType: types.TRANSFER_CLASSIFIED, + }, + } + + applyRetBytes(pair) + + if pair.Bytes != 4096 { + t.Fatalf("pair.Bytes = %d, want 4096", pair.Bytes) + } +} diff --git a/internal/eventloop_exit.go b/internal/eventloop_exit.go index 79c1b5b..a0cc675 100644 --- a/internal/eventloop_exit.go +++ b/internal/eventloop_exit.go @@ -109,9 +109,6 @@ func (e *eventLoop) handleFdExit(ep *event.Pair, fdEv *types.FdEvent) bool { if ok := e.applyFdTransferOp(ep, fdEv); !ok { return false } - if retEv, ok := ep.ExitEv.(*types.RetEvent); ok { - ep.Bytes = bytesFromRet(retEv) - } return true } @@ -315,6 +312,14 @@ func (e *eventLoop) recyclePair(ep *event.Pair, warning string) { ep.Recycle() } +func applyRetBytes(ep *event.Pair) { + retEv, ok := ep.ExitEv.(*types.RetEvent) + if !ok { + return + } + ep.Bytes = bytesFromRet(retEv) +} + // dropMalformedRawEvent records a warning when a raw BPF event cannot be // decoded, keeping the error visible without crashing the event loop. func (e *eventLoop) dropMalformedRawEvent(evType types.EventType, raw []byte) { diff --git a/internal/eventloop_runtime.go b/internal/eventloop_runtime.go index 74571c8..c285507 100644 --- a/internal/eventloop_runtime.go +++ b/internal/eventloop_runtime.go @@ -314,6 +314,7 @@ func (e *eventLoop) tracepointExited(exitEv event.Event, ch chan<- *event.Pair) if !e.handleTracepointExit(ep) { return } + applyRetBytes(ep) tid := ep.EnterEv.GetTid() ep.CalculateDurations(e.pairs.prevTime(tid)) e.pairs.setPrevTime(tid, ep.ExitEv.GetTime()) diff --git a/internal/generate/classify_test.go b/internal/generate/classify_test.go index f02f7de..4dd216e 100644 --- a/internal/generate/classify_test.go +++ b/internal/generate/classify_test.go @@ -338,6 +338,67 @@ func TestClassifySyscallPairEmitsAllFamilies(t *testing.T) { } } +func TestClassifyPhaseAByteSyscallPairsAccepted(t *testing.T) { + tests := []struct { + name string + enterKindText string + retText string + }{ + {"recvfrom", "struct fd_event", "READ_CLASSIFIED"}, + {"recvmsg", "struct fd_event", "READ_CLASSIFIED"}, + {"sendto", "struct fd_event", "WRITE_CLASSIFIED"}, + {"sendmsg", "struct fd_event", "WRITE_CLASSIFIED"}, + {"sendfile64", "struct null_event", "TRANSFER_CLASSIFIED"}, + {"splice", "struct null_event", "TRANSFER_CLASSIFIED"}, + {"tee", "struct null_event", "TRANSFER_CLASSIFIED"}, + {"process_vm_readv", "struct null_event", "READ_CLASSIFIED"}, + {"process_vm_writev", "struct null_event", "WRITE_CLASSIFIED"}, + } + + for i, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + formats := phaseAFormats(tt.name, 9000+i*2) + output := GenerateTracepointsC(formats) + if strings.Contains(output, "Ignoring") || strings.Contains(output, "Skipping") { + t.Fatalf("syscall %s was not accepted:\n%s", tt.name, output) + } + if !strings.Contains(output, "/// sys_enter_"+tt.name+" is a "+tt.enterKindText) { + t.Fatalf("sys_enter_%s did not use %s:\n%s", tt.name, tt.enterKindText, output) + } + if !strings.Contains(output, "/// sys_exit_"+tt.name+" is a struct ret_event ("+tt.retText+")") { + t.Fatalf("sys_exit_%s did not use %s:\n%s", tt.name, tt.retText, output) + } + }) + } +} + +func phaseAFormats(name string, enterID int) []Format { + enterFields := []Field{ + {Type: "long", Name: "__syscall_nr"}, + } + if name == "sendto" || name == "recvfrom" || name == "sendmsg" || name == "recvmsg" { + enterFields = append(enterFields, Field{Type: "int", Name: "fd"}) + } + + return []Format{ + { + Name: "sys_enter_" + name, + ID: enterID, + Family: ClassifySyscallFamily("sys_enter_" + name), + ExternalFields: enterFields, + }, + { + Name: "sys_exit_" + name, + ID: enterID - 1, + Family: ClassifySyscallFamily("sys_exit_" + name), + ExternalFields: []Field{ + {Type: "long", Name: "__syscall_nr"}, + {Type: "long", Name: "ret"}, + }, + }, + } +} + func TestClassifyFormatNoExternalFields(t *testing.T) { f := &Format{ Name: "sys_enter_test", diff --git a/internal/generate/retclassify_test.go b/internal/generate/retclassify_test.go index 3152005..9a75a15 100644 --- a/internal/generate/retclassify_test.go +++ b/internal/generate/retclassify_test.go @@ -57,3 +57,28 @@ func TestClassifyRetCaseInsensitive(t *testing.T) { t.Errorf("ClassifyRet(sys_exit_READ) = %q, want READ_CLASSIFIED", got) } } + +func TestPhaseAByteClassifiedSyscallsUseExistingRetClassifications(t *testing.T) { + tests := []struct { + name string + want RetClassification + }{ + {"recvfrom", ReadClassified}, + {"recvmsg", ReadClassified}, + {"sendto", WriteClassified}, + {"sendmsg", WriteClassified}, + {"sendfile64", TransferClassified}, + {"splice", TransferClassified}, + {"tee", TransferClassified}, + {"process_vm_readv", ReadClassified}, + {"process_vm_writev", WriteClassified}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ClassifyRet("sys_exit_" + tt.name); got != tt.want { + t.Fatalf("ClassifyRet(sys_exit_%s) = %q, want %q", tt.name, got, tt.want) + } + }) + } +} -- cgit v1.2.3