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)