debug.go 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. // Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package controlclient
  5. import (
  6. "bytes"
  7. "compress/gzip"
  8. "context"
  9. "fmt"
  10. "log"
  11. "net/http"
  12. "runtime"
  13. "strconv"
  14. "time"
  15. )
  16. func dumpGoroutinesToURL(c *http.Client, targetURL string) {
  17. ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
  18. defer cancel()
  19. zbuf := new(bytes.Buffer)
  20. zw := gzip.NewWriter(zbuf)
  21. zw.Write(scrubbedGoroutineDump())
  22. zw.Close()
  23. req, err := http.NewRequestWithContext(ctx, "PUT", targetURL, zbuf)
  24. if err != nil {
  25. log.Printf("dumpGoroutinesToURL: %v", err)
  26. return
  27. }
  28. req.Header.Set("Content-Encoding", "gzip")
  29. t0 := time.Now()
  30. _, err = c.Do(req)
  31. d := time.Since(t0).Round(time.Millisecond)
  32. if err != nil {
  33. log.Printf("dumpGoroutinesToURL error: %v to %v (after %v)", err, targetURL, d)
  34. } else {
  35. log.Printf("dumpGoroutinesToURL complete to %v (after %v)", targetURL, d)
  36. }
  37. }
  38. // scrubbedGoroutineDump returns the list of all current goroutines, but with the actual
  39. // values of arguments scrubbed out, lest it contain some private key material.
  40. func scrubbedGoroutineDump() []byte {
  41. var buf []byte
  42. // Grab stacks multiple times into increasingly larger buffer sizes
  43. // to minimize the risk that we blow past our iOS memory limit.
  44. for size := 1 << 10; size <= 1<<20; size += 1 << 10 {
  45. buf = make([]byte, size)
  46. buf = buf[:runtime.Stack(buf, true)]
  47. if len(buf) < size {
  48. // It fit.
  49. break
  50. }
  51. }
  52. return scrubHex(buf)
  53. }
  54. func scrubHex(buf []byte) []byte {
  55. saw := map[string][]byte{} // "0x123" => "v1%3" (unique value 1 and its value mod 8)
  56. foreachHexAddress(buf, func(in []byte) {
  57. if string(in) == "0x0" {
  58. return
  59. }
  60. if v, ok := saw[string(in)]; ok {
  61. for i := range in {
  62. in[i] = '_'
  63. }
  64. copy(in, v)
  65. return
  66. }
  67. inStr := string(in)
  68. u64, err := strconv.ParseUint(string(in[2:]), 16, 64)
  69. for i := range in {
  70. in[i] = '_'
  71. }
  72. if err != nil {
  73. in[0] = '?'
  74. return
  75. }
  76. v := []byte(fmt.Sprintf("v%d%%%d", len(saw)+1, u64%8))
  77. saw[inStr] = v
  78. copy(in, v)
  79. })
  80. return buf
  81. }
  82. var ohx = []byte("0x")
  83. // foreachHexAddress calls f with each subslice of b that matches
  84. // regexp `0x[0-9a-f]*`.
  85. func foreachHexAddress(b []byte, f func([]byte)) {
  86. for len(b) > 0 {
  87. i := bytes.Index(b, ohx)
  88. if i == -1 {
  89. return
  90. }
  91. b = b[i:]
  92. hx := hexPrefix(b)
  93. f(hx)
  94. b = b[len(hx):]
  95. }
  96. }
  97. func hexPrefix(b []byte) []byte {
  98. for i, c := range b {
  99. if i < 2 {
  100. continue
  101. }
  102. if !isHexByte(c) {
  103. return b[:i]
  104. }
  105. }
  106. return b
  107. }
  108. func isHexByte(b byte) bool {
  109. return '0' <= b && b <= '9' || 'a' <= b && b <= 'f' || 'A' <= b && b <= 'F'
  110. }