diff --git a/control/controlclient/debug.go b/control/controlclient/debug.go index fe9d3450a..dc7e12c05 100644 --- a/control/controlclient/debug.go +++ b/control/controlclient/debug.go @@ -11,7 +11,6 @@ import ( "fmt" "log" "net/http" - "regexp" "runtime" "strconv" "time" @@ -42,28 +41,73 @@ func dumpGoroutinesToURL(c *http.Client, targetURL string) { } } -var reHexArgs = regexp.MustCompile(`\b0x[0-9a-f]+\b`) - // scrubbedGoroutineDump returns the list of all current goroutines, but with the actual // values of arguments scrubbed out, lest it contain some private key material. func scrubbedGoroutineDump() []byte { buf := make([]byte, 1<<20) buf = buf[:runtime.Stack(buf, true)] + return scrubHex(buf) +} +func scrubHex(buf []byte) []byte { saw := map[string][]byte{} // "0x123" => "v1%3" (unique value 1 and its value mod 8) - return reHexArgs.ReplaceAllFunc(buf, func(in []byte) []byte { + + foreachHexAddress(buf, func(in []byte) { if string(in) == "0x0" { - return in + return } if v, ok := saw[string(in)]; ok { - return v + for i := range in { + in[i] = '_' + } + copy(in, v) + return } + inStr := string(in) u64, err := strconv.ParseUint(string(in[2:]), 16, 64) + for i := range in { + in[i] = '_' + } if err != nil { - return []byte("??") + in[0] = '?' + return } v := []byte(fmt.Sprintf("v%d%%%d", len(saw)+1, u64%8)) - saw[string(in)] = v - return v + saw[inStr] = v + copy(in, v) }) + return buf +} + +var ohx = []byte("0x") + +// foreachHexAddress calls f with each subslice of b that matches +// regexp `0x[0-9a-f]*`. +func foreachHexAddress(b []byte, f func([]byte)) { + for len(b) > 0 { + i := bytes.Index(b, ohx) + if i == -1 { + return + } + b = b[i:] + hx := hexPrefix(b) + f(hx) + b = b[len(hx):] + } +} + +func hexPrefix(b []byte) []byte { + for i, c := range b { + if i < 2 { + continue + } + if !isHexByte(c) { + return b[:i] + } + } + return b +} + +func isHexByte(b byte) bool { + return '0' <= b && b <= '9' || 'a' <= b && b <= 'f' || 'A' <= b && b <= 'F' } diff --git a/control/controlclient/debug_test.go b/control/controlclient/debug_test.go index 8e928789a..bbcca51ed 100644 --- a/control/controlclient/debug_test.go +++ b/control/controlclient/debug_test.go @@ -9,3 +9,22 @@ import "testing" func TestScrubbedGoroutineDump(t *testing.T) { t.Logf("Got:\n%s\n", scrubbedGoroutineDump()) } + +func TestScrubHex(t *testing.T) { + tests := []struct { + in, want string + }{ + {"foo", "foo"}, + {"", ""}, + {"0x", "?_"}, + {"0x001 and same 0x001", "v1%1_ and same v1%1_"}, + {"0x008 and same 0x008", "v1%0_ and same v1%0_"}, + {"0x001 and diff 0x002", "v1%1_ and diff v2%2_"}, + } + for _, tt := range tests { + got := scrubHex([]byte(tt.in)) + if string(got) != tt.want { + t.Errorf("for input:\n%s\n\ngot:\n%s\n\nwant:\n%s\n", tt.in, got, tt.want) + } + } +}