diff --git a/ebpftracer/tls.go b/ebpftracer/tls.go index 771b96a..bf033eb 100644 --- a/ebpftracer/tls.go +++ b/ebpftracer/tls.go @@ -20,18 +20,26 @@ const ( var silentErrors = []string{ "not a Go executable", "not found", + "no symbols found", "no such file or directory", "no such process", "permission denied", } +func isSilentError(err error) bool { + for _, s := range silentErrors { + if strings.HasSuffix(err.Error(), s) { + return true + } + } + return false +} + func uprobeLog(pid uint32, prefix string) func(string, error) { return func(msg string, err error) { if err != nil { - for _, s := range silentErrors { - if strings.HasSuffix(err.Error(), s) { - return - } + if isSilentError(err) { + return } klog.ErrorfDepth(2, "pid=%d%s: %s: %s", pid, prefix, msg, err) return diff --git a/ebpftracer/tls_test.go b/ebpftracer/tls_test.go new file mode 100644 index 0000000..0ab8c55 --- /dev/null +++ b/ebpftracer/tls_test.go @@ -0,0 +1,79 @@ +package ebpftracer + +import ( + "bytes" + "errors" + "flag" + "os" + "os/exec" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/klog/v2" +) + +func buildGoBinary(t *testing.T, ldflags string) string { + t.Helper() + dir := t.TempDir() + src := filepath.Join(dir, "main.go") + require.NoError(t, os.WriteFile(src, []byte("package main\nfunc main() {}\n"), 0644)) + bin := filepath.Join(dir, "app") + args := []string{"build", "-o", bin} + if ldflags != "" { + args = append(args, "-ldflags", ldflags) + } + args = append(args, src) + cmd := exec.Command("go", args...) + cmd.Env = append(os.Environ(), "CGO_ENABLED=0") + out, err := cmd.CombinedOutput() + require.NoError(t, err, string(out)) + return bin +} + +// Stripped binaries (e.g. kubectl built with -s -w) carry no symbol table, so +// Go TLS uprobes can't be attached. This must be treated as a silent condition +// rather than spamming an error for every such process. +func TestGetSymbolStrippedBinary(t *testing.T) { + ef, err := OpenELFFile(buildGoBinary(t, "-s -w")) + require.NoError(t, err) + defer ef.Close() + + _, err = ef.GetSymbol(goTlsWriteSymbol) + require.Error(t, err) + assert.Equal(t, "no symbols found", err.Error()) + assert.True(t, isSilentError(err), "stripped binary error must be silenced") +} + +func captureKlog(t *testing.T, fn func()) string { + t.Helper() + var buf bytes.Buffer + fs := flag.NewFlagSet("klog", flag.ContinueOnError) + klog.InitFlags(fs) + require.NoError(t, fs.Set("logtostderr", "false")) + require.NoError(t, fs.Set("stderrthreshold", "FATAL")) + klog.SetOutput(&buf) + defer func() { + klog.Flush() + _ = fs.Set("logtostderr", "true") + klog.SetOutput(nil) + }() + fn() + klog.Flush() + return buf.String() +} + +func TestUprobeLogSilentError(t *testing.T) { + out := captureKlog(t, func() { + uprobeLog(1, "")("failed to get symbol "+goTlsWriteSymbol, errors.New("no symbols found")) + }) + assert.Empty(t, out) +} + +func TestUprobeLogReportsRealError(t *testing.T) { + out := captureKlog(t, func() { + uprobeLog(1, "")("failed to attach uprobe", errors.New("operation not permitted")) + }) + assert.Contains(t, out, "failed to attach uprobe") +}