From 60bd1a77a922a525043fea3e64c19ddc9681593d Mon Sep 17 00:00:00 2001 From: Mahe Tardy <mahe.tardy@gmail.com> Date: Tue, 5 Sep 2023 09:00:22 +0000 Subject: [PATCH] tetra: add stack traces human output to compact mode It looks similar to this with the new stack trace encoder: syscall /usr/bin/curl kfree_skb_reason 0xffffbcdee5bf4840: skb_release_head_state+0x110 0xffffbcdee5be3678: __sys_connect_file+0x88 0xffffbcdee5be376c: __sys_connect+0xbc 0xffffbcdee5be37bc: __arm64_sys_connect+0x28 0xffffbcdee4dfcd68: invoke_syscall+0x78 0xffffbcdee4dfcf70: el0_svc_common.constprop.0+0x180 0xffffbcdee4dfcfa4: do_el0_svc+0x30 0xffffbcdee5e861e8: el0_svc+0x48 0xffffbcdee5e881b4: el0t_64_sync_handler+0xa4 0xffffbcdee4de1e38: el0t_64_sync+0x1a4 Signed-off-by: Mahe Tardy <mahe.tardy@gmail.com> --- cmd/tetra/getevents/getevents.go | 8 ++++--- pkg/encoder/encoder.go | 39 ++++++++++++++++++++++++++------ pkg/encoder/encoder_test.go | 24 ++++++++++---------- 3 files changed, 49 insertions(+), 22 deletions(-) diff --git a/cmd/tetra/getevents/getevents.go b/cmd/tetra/getevents/getevents.go index e50477876b0..3da7f712b71 100644 --- a/cmd/tetra/getevents/getevents.go +++ b/cmd/tetra/getevents/getevents.go @@ -51,17 +51,18 @@ type Opts struct { Host bool Timestamps bool TTYEncode string + StackTraces bool } var Options Opts // GetEncoder returns an encoder for an event stream based on configuration options. -var GetEncoder = func(w io.Writer, colorMode encoder.ColorMode, timestamps bool, compact bool, tty string) encoder.EventEncoder { +var GetEncoder = func(w io.Writer, colorMode encoder.ColorMode, timestamps bool, compact bool, tty string, stackTraces bool) encoder.EventEncoder { if tty != "" { return encoder.NewTtyEncoder(w, tty) } if compact { - return encoder.NewCompactEncoder(w, colorMode, timestamps) + return encoder.NewCompactEncoder(w, colorMode, timestamps, stackTraces) } return encoder.NewProtojsonEncoder(w) } @@ -123,7 +124,7 @@ func getEvents(ctx context.Context, client tetragon.FineGuidanceSensorsClient) { if err != nil { logger.GetLogger().WithError(err).Fatal("Failed to call GetEvents") } - eventEncoder := GetEncoder(os.Stdout, encoder.ColorMode(Options.Color), Options.Timestamps, Options.Output == "compact", Options.TTYEncode) + eventEncoder := GetEncoder(os.Stdout, encoder.ColorMode(Options.Color), Options.Timestamps, Options.Output == "compact", Options.TTYEncode, Options.StackTraces) for { res, err := stream.Recv() if err != nil { @@ -191,5 +192,6 @@ func New() *cobra.Command { flags.BoolVar(&Options.Host, "host", false, "Get host events") flags.BoolVar(&Options.Timestamps, "timestamps", false, "Include timestamps in compact output") flags.StringVarP(&Options.TTYEncode, "tty-encode", "t", "", "Encode terminal data by file path (all other events will be ignored)") + flags.BoolVar(&Options.StackTraces, "stack-traces", true, "Include stack traces in compact output") return &cmd } diff --git a/pkg/encoder/encoder.go b/pkg/encoder/encoder.go index 6a19a234fc7..8c586419633 100644 --- a/pkg/encoder/encoder.go +++ b/pkg/encoder/encoder.go @@ -9,6 +9,7 @@ import ( "fmt" "io" "os" + "strings" "github.com/cilium/tetragon/api/v1/tetragon" "github.com/cilium/tetragon/pkg/arch" @@ -83,17 +84,19 @@ func (p *TtyEncoder) Encode(v interface{}) error { // CompactEncoder encodes tetragon.GetEventsResponse in a short format with emojis and colors. type CompactEncoder struct { - Writer io.Writer - Colorer *Colorer - Timestamps bool + Writer io.Writer + Colorer *Colorer + Timestamps bool + StackTraces bool } // NewCompactEncoder initializes and returns a pointer to CompactEncoder. -func NewCompactEncoder(w io.Writer, colorMode ColorMode, timestamps bool) *CompactEncoder { +func NewCompactEncoder(w io.Writer, colorMode ColorMode, timestamps bool, stackTraces bool) *CompactEncoder { return &CompactEncoder{ - Writer: w, - Colorer: NewColorer(colorMode), - Timestamps: timestamps, + Writer: w, + Colorer: NewColorer(colorMode), + Timestamps: timestamps, + StackTraces: stackTraces, } } @@ -113,6 +116,13 @@ func (p *CompactEncoder) Encode(v interface{}) error { str = fmt.Sprintf("%s %s", ts, str) } fmt.Fprintln(p.Writer, str) + + // print stack trace if available + if p.StackTraces { + st := HumanStackTrace(event, p.Colorer) + fmt.Fprint(p.Writer, st) + } + return nil } @@ -188,6 +198,21 @@ func PrintNS(ns int32) string { return nsId[ns] } +func HumanStackTrace(response *tetragon.GetEventsResponse, colorer *Colorer) string { + out := new(strings.Builder) + if ev, ok := response.Event.(*tetragon.GetEventsResponse_ProcessKprobe); ok { + if ev.ProcessKprobe.StackTrace != nil { + for _, st := range ev.ProcessKprobe.StackTrace { + colorer.Green.Fprintf(out, " 0x%x:", st.Address) + colorer.Blue.Fprintf(out, " %s", st.Symbol) + fmt.Fprintf(out, "+") + colorer.Yellow.Fprintf(out, "0x%x\n", st.Offset) + } + } + } + return out.String() +} + func (p *CompactEncoder) EventToString(response *tetragon.GetEventsResponse) (string, error) { switch response.Event.(type) { case *tetragon.GetEventsResponse_ProcessExec: diff --git a/pkg/encoder/encoder_test.go b/pkg/encoder/encoder_test.go index eaed7799d4c..c7d483a97bc 100644 --- a/pkg/encoder/encoder_test.go +++ b/pkg/encoder/encoder_test.go @@ -20,7 +20,7 @@ import ( ) func TestCompactEncoder_InvalidEventToString(t *testing.T) { - p := NewCompactEncoder(os.Stdout, Never, false) + p := NewCompactEncoder(os.Stdout, Never, false, false) // should fail if the event field is nil. _, err := p.EventToString(&tetragon.GetEventsResponse{}) @@ -28,7 +28,7 @@ func TestCompactEncoder_InvalidEventToString(t *testing.T) { } func TestCompactEncoder_ExecEventToString(t *testing.T) { - p := NewCompactEncoder(os.Stdout, Never, false) + p := NewCompactEncoder(os.Stdout, Never, false, false) // should fail if the process field is nil. _, err := p.EventToString(&tetragon.GetEventsResponse{ @@ -73,7 +73,7 @@ func TestCompactEncoder_ExecEventToString(t *testing.T) { } func TestCompactEncoder_ExitEventToString(t *testing.T) { - p := NewCompactEncoder(os.Stdout, Never, false) + p := NewCompactEncoder(os.Stdout, Never, false, false) // should fail if the process field is nil. _, err := p.EventToString(&tetragon.GetEventsResponse{ @@ -123,7 +123,7 @@ func TestCompactEncoder_ExitEventToString(t *testing.T) { } func TestCompactEncoder_KprobeEventToString(t *testing.T) { - p := NewCompactEncoder(os.Stdout, Never, false) + p := NewCompactEncoder(os.Stdout, Never, false, false) // should fail without process field _, err := p.EventToString(&tetragon.GetEventsResponse{ @@ -156,7 +156,7 @@ func TestCompactEncoder_KprobeEventToString(t *testing.T) { } func TestCompactEncoder_KprobeOpenEventToString(t *testing.T) { - p := NewCompactEncoder(os.Stdout, Never, false) + p := NewCompactEncoder(os.Stdout, Never, false, false) // open without args result, err := p.EventToString(&tetragon.GetEventsResponse{ @@ -200,7 +200,7 @@ func TestCompactEncoder_KprobeOpenEventToString(t *testing.T) { } func TestCompactEncoder_KprobeWriteEventToString(t *testing.T) { - p := NewCompactEncoder(os.Stdout, Never, false) + p := NewCompactEncoder(os.Stdout, Never, false, false) // write without args result, err := p.EventToString(&tetragon.GetEventsResponse{ @@ -245,7 +245,7 @@ func TestCompactEncoder_KprobeWriteEventToString(t *testing.T) { } func TestCompactEncoder_KprobeCloseEventToString(t *testing.T) { - p := NewCompactEncoder(os.Stdout, Never, false) + p := NewCompactEncoder(os.Stdout, Never, false, false) // open without args result, err := p.EventToString(&tetragon.GetEventsResponse{ @@ -288,7 +288,7 @@ func TestCompactEncoder_KprobeCloseEventToString(t *testing.T) { } func TestCompactEncoder_KprobeBPFEventToString(t *testing.T) { - p := NewCompactEncoder(os.Stdout, Never, false) + p := NewCompactEncoder(os.Stdout, Never, false, false) // bpf with no args result, err := p.EventToString(&tetragon.GetEventsResponse{ @@ -336,7 +336,7 @@ func TestCompactEncoder_KprobeBPFEventToString(t *testing.T) { } func TestCompactEncoder_KprobePerfEventAllocEventToString(t *testing.T) { - p := NewCompactEncoder(os.Stdout, Never, false) + p := NewCompactEncoder(os.Stdout, Never, false, false) // perf event alloc with no args result, err := p.EventToString(&tetragon.GetEventsResponse{ @@ -383,7 +383,7 @@ func TestCompactEncoder_KprobePerfEventAllocEventToString(t *testing.T) { } func TestCompactEncoder_KprobeBPFMapAllocEventToString(t *testing.T) { - p := NewCompactEncoder(os.Stdout, Never, false) + p := NewCompactEncoder(os.Stdout, Never, false, false) // bpf map with no args result, err := p.EventToString(&tetragon.GetEventsResponse{ @@ -434,7 +434,7 @@ func TestCompactEncoder_KprobeBPFMapAllocEventToString(t *testing.T) { func TestCompactEncoder_Encode(t *testing.T) { var b bytes.Buffer - p := NewCompactEncoder(&b, Never, false) + p := NewCompactEncoder(&b, Never, false, false) // invalid event err := p.Encode(nil) @@ -465,7 +465,7 @@ func TestCompactEncoder_Encode(t *testing.T) { func TestCompactEncoder_EncodeWithTimestamp(t *testing.T) { var b bytes.Buffer - p := NewCompactEncoder(&b, Never, true) + p := NewCompactEncoder(&b, Never, true, false) // invalid event err := p.Encode(nil)