25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

461 lines
14 KiB

  1. // Copyright 2016 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. // Package valuecollector is used to collect the values of variables in a program.
  15. package valuecollector
  16. import (
  17. "bytes"
  18. "fmt"
  19. "strconv"
  20. "strings"
  21. "cloud.google.com/go/cmd/go-cloud-debug-agent/internal/debug"
  22. cd "google.golang.org/api/clouddebugger/v2"
  23. )
  24. const (
  25. maxArrayLength = 50
  26. maxMapLength = 20
  27. )
  28. // Collector is given references to variables from a program being debugged
  29. // using AddVariable. Then when ReadValues is called, the Collector will fetch
  30. // the values of those variables. Any variables referred to by those values
  31. // will also be fetched; e.g. the targets of pointers, members of structs,
  32. // elements of slices, etc. This continues iteratively, building a graph of
  33. // values, until all the reachable values are fetched, or a size limit is
  34. // reached.
  35. //
  36. // Variables are passed to the Collector as debug.Var, which is used by x/debug
  37. // to represent references to variables. Values are returned as cd.Variable,
  38. // which is used by the Debuglet Controller to represent the graph of values.
  39. //
  40. // For example, if the program has a struct variable:
  41. //
  42. // foo := SomeStruct{a:42, b:"xyz"}
  43. //
  44. // and we call AddVariable with a reference to foo, we will get back a result
  45. // like:
  46. //
  47. // cd.Variable{Name:"foo", VarTableIndex:10}
  48. //
  49. // which denotes a variable named "foo" which will have its value stored in
  50. // element 10 of the table that will later be returned by ReadValues. That
  51. // element might be:
  52. //
  53. // out[10] = &cd.Variable{Members:{{Name:"a", VarTableIndex:11},{Name:"b", VarTableIndex:12}}}
  54. //
  55. // which denotes a struct with two members a and b, whose values are in elements
  56. // 11 and 12 of the output table:
  57. //
  58. // out[11] = &cd.Variable{Value:"42"}
  59. // out[12] = &cd.Variable{Value:"xyz"}
  60. type Collector struct {
  61. // prog is the program being debugged.
  62. prog debug.Program
  63. // limit is the maximum size of the output slice of values.
  64. limit int
  65. // index is a map from references (variables and map elements) to their
  66. // locations in the table.
  67. index map[reference]int
  68. // table contains the references, including those given to the
  69. // Collector directly and those the Collector itself found.
  70. // If VarTableIndex is set to 0 in a cd.Variable, it is ignored, so the first entry
  71. // of table can't be used. On initialization we put a dummy value there.
  72. table []reference
  73. }
  74. // reference represents a value which is in the queue to be read by the
  75. // collector. It is either a debug.Var, or a mapElement.
  76. type reference interface{}
  77. // mapElement represents an element of a map in the debugged program's memory.
  78. type mapElement struct {
  79. debug.Map
  80. index uint64
  81. }
  82. // NewCollector returns a Collector for the given program and size limit.
  83. // The limit is the maximum size of the slice of values returned by ReadValues.
  84. func NewCollector(prog debug.Program, limit int) *Collector {
  85. return &Collector{
  86. prog: prog,
  87. limit: limit,
  88. index: make(map[reference]int),
  89. table: []reference{debug.Var{}},
  90. }
  91. }
  92. // AddVariable adds another variable to be collected.
  93. // The Collector doesn't get the value immediately; it returns a cd.Variable
  94. // that contains an index into the table which will later be returned by
  95. // ReadValues.
  96. func (c *Collector) AddVariable(lv debug.LocalVar) *cd.Variable {
  97. ret := &cd.Variable{Name: lv.Name}
  98. if index, ok := c.add(lv.Var); !ok {
  99. // If the add call failed, it's because we reached the size limit.
  100. // The Debuglet Controller's convention is to pass it a "Not Captured" error
  101. // in this case.
  102. ret.Status = statusMessage(messageNotCaptured, true, refersToVariableName)
  103. } else {
  104. ret.VarTableIndex = int64(index)
  105. }
  106. return ret
  107. }
  108. // add adds a reference to the set of values to be read from the
  109. // program. It returns the index in the output table that will contain the
  110. // corresponding value. It fails if the table has reached the size limit.
  111. // It deduplicates references, so the index may be the same as one that was
  112. // returned from an earlier add call.
  113. func (c *Collector) add(r reference) (outputIndex int, ok bool) {
  114. if i, ok := c.index[r]; ok {
  115. return i, true
  116. }
  117. i := len(c.table)
  118. if i >= c.limit {
  119. return 0, false
  120. }
  121. c.index[r] = i
  122. c.table = append(c.table, r)
  123. return i, true
  124. }
  125. func addMember(v *cd.Variable, name string) *cd.Variable {
  126. v2 := &cd.Variable{Name: name}
  127. v.Members = append(v.Members, v2)
  128. return v2
  129. }
  130. // ReadValues fetches values of the variables that were passed to the Collector
  131. // with AddVariable. The values of any new variables found are also fetched,
  132. // e.g. the targets of pointers or the members of structs, until we reach the
  133. // size limit or we run out of values to fetch.
  134. // The results are output as a []*cd.Variable, which is the type we need to send
  135. // to the Debuglet Controller after we trigger a breakpoint.
  136. func (c *Collector) ReadValues() (out []*cd.Variable) {
  137. for i := 0; i < len(c.table); i++ {
  138. // Create a new cd.Variable for this value, and append it to the output.
  139. dcv := new(cd.Variable)
  140. out = append(out, dcv)
  141. if i == 0 {
  142. // The first element is unused.
  143. continue
  144. }
  145. switch x := c.table[i].(type) {
  146. case mapElement:
  147. key, value, err := c.prog.MapElement(x.Map, x.index)
  148. if err != nil {
  149. dcv.Status = statusMessage(err.Error(), true, refersToVariableValue)
  150. continue
  151. }
  152. // Add a member for the key.
  153. member := addMember(dcv, "key")
  154. if index, ok := c.add(key); !ok {
  155. // The table is full.
  156. member.Status = statusMessage(messageNotCaptured, true, refersToVariableName)
  157. continue
  158. } else {
  159. member.VarTableIndex = int64(index)
  160. }
  161. // Add a member for the value.
  162. member = addMember(dcv, "value")
  163. if index, ok := c.add(value); !ok {
  164. // The table is full.
  165. member.Status = statusMessage(messageNotCaptured, true, refersToVariableName)
  166. } else {
  167. member.VarTableIndex = int64(index)
  168. }
  169. case debug.Var:
  170. if v, err := c.prog.Value(x); err != nil {
  171. dcv.Status = statusMessage(err.Error(), true, refersToVariableValue)
  172. } else {
  173. c.FillValue(v, dcv)
  174. }
  175. }
  176. }
  177. return out
  178. }
  179. // indexable is an interface for arrays, slices and channels.
  180. type indexable interface {
  181. Len() uint64
  182. Element(uint64) debug.Var
  183. }
  184. // channel implements indexable.
  185. type channel struct {
  186. debug.Channel
  187. }
  188. func (c channel) Len() uint64 {
  189. return c.Length
  190. }
  191. var (
  192. _ indexable = debug.Array{}
  193. _ indexable = debug.Slice{}
  194. _ indexable = channel{}
  195. )
  196. // FillValue copies a value into a cd.Variable. Any variables referred to by
  197. // that value, e.g. struct members and pointer targets, are added to the
  198. // collector's queue, to be fetched later by ReadValues.
  199. func (c *Collector) FillValue(v debug.Value, dcv *cd.Variable) {
  200. if c, ok := v.(debug.Channel); ok {
  201. // Convert to channel, which implements indexable.
  202. v = channel{c}
  203. }
  204. // Fill in dcv in a manner depending on the type of the value we got.
  205. switch val := v.(type) {
  206. case int8, int16, int32, int64, bool, uint8, uint16, uint32, uint64, float32, float64, complex64, complex128:
  207. // For simple types, we just print the value to dcv.Value.
  208. dcv.Value = fmt.Sprint(val)
  209. case string:
  210. // Put double quotes around strings.
  211. dcv.Value = strconv.Quote(val)
  212. case debug.String:
  213. if uint64(len(val.String)) < val.Length {
  214. // This string value was truncated.
  215. dcv.Value = strconv.Quote(val.String + "...")
  216. } else {
  217. dcv.Value = strconv.Quote(val.String)
  218. }
  219. case debug.Struct:
  220. // For structs, we add an entry to dcv.Members for each field in the
  221. // struct.
  222. // Each member will contain the name of the field, and the index in the
  223. // output table which will contain the value of that field.
  224. for _, f := range val.Fields {
  225. member := addMember(dcv, f.Name)
  226. if index, ok := c.add(f.Var); !ok {
  227. // The table is full.
  228. member.Status = statusMessage(messageNotCaptured, true, refersToVariableName)
  229. } else {
  230. member.VarTableIndex = int64(index)
  231. }
  232. }
  233. case debug.Map:
  234. dcv.Value = fmt.Sprintf("len = %d", val.Length)
  235. for i := uint64(0); i < val.Length; i++ {
  236. field := addMember(dcv, `⚫`)
  237. if i == maxMapLength {
  238. field.Name = "..."
  239. field.Status = statusMessage(messageTruncated, true, refersToVariableName)
  240. break
  241. }
  242. if index, ok := c.add(mapElement{val, i}); !ok {
  243. // The value table is full; add a member to contain the error message.
  244. field.Name = "..."
  245. field.Status = statusMessage(messageNotCaptured, true, refersToVariableName)
  246. break
  247. } else {
  248. field.VarTableIndex = int64(index)
  249. }
  250. }
  251. case debug.Pointer:
  252. if val.Address == 0 {
  253. dcv.Value = "<nil>"
  254. } else if val.TypeID == 0 {
  255. // We don't know the type of the pointer, so just output the address as
  256. // the value.
  257. dcv.Value = fmt.Sprintf("0x%X", val.Address)
  258. dcv.Status = statusMessage(messageUnknownPointerType, false, refersToVariableName)
  259. } else {
  260. // Adds the pointed-to variable to the table, and links this value to
  261. // that table entry through VarTableIndex.
  262. dcv.Value = fmt.Sprintf("0x%X", val.Address)
  263. target := addMember(dcv, "")
  264. if index, ok := c.add(debug.Var(val)); !ok {
  265. target.Status = statusMessage(messageNotCaptured, true, refersToVariableName)
  266. } else {
  267. target.VarTableIndex = int64(index)
  268. }
  269. }
  270. case indexable:
  271. // Arrays, slices and channels.
  272. dcv.Value = "len = " + fmt.Sprint(val.Len())
  273. for j := uint64(0); j < val.Len(); j++ {
  274. field := addMember(dcv, fmt.Sprint(`[`, j, `]`))
  275. if j == maxArrayLength {
  276. field.Name = "..."
  277. field.Status = statusMessage(messageTruncated, true, refersToVariableName)
  278. break
  279. }
  280. vr := val.Element(j)
  281. if index, ok := c.add(vr); !ok {
  282. // The value table is full; add a member to contain the error message.
  283. field.Name = "..."
  284. field.Status = statusMessage(messageNotCaptured, true, refersToVariableName)
  285. break
  286. } else {
  287. // Add a member with the index as the name.
  288. field.VarTableIndex = int64(index)
  289. }
  290. }
  291. default:
  292. dcv.Status = statusMessage(messageUnknownType, false, refersToVariableName)
  293. }
  294. }
  295. // statusMessage returns a *cd.StatusMessage with the given message, IsError
  296. // field and refersTo field.
  297. func statusMessage(msg string, isError bool, refersTo int) *cd.StatusMessage {
  298. return &cd.StatusMessage{
  299. Description: &cd.FormatMessage{Format: "$0", Parameters: []string{msg}},
  300. IsError: isError,
  301. RefersTo: refersToString[refersTo],
  302. }
  303. }
  304. // LogString produces a string for a logpoint, substituting in variable values
  305. // using evaluatedExpressions and varTable.
  306. func LogString(s string, evaluatedExpressions []*cd.Variable, varTable []*cd.Variable) string {
  307. var buf bytes.Buffer
  308. fmt.Fprintf(&buf, "LOGPOINT: ")
  309. seen := make(map[*cd.Variable]bool)
  310. for i := 0; i < len(s); {
  311. if s[i] == '$' {
  312. i++
  313. if num, n, ok := parseToken(s[i:], len(evaluatedExpressions)-1); ok {
  314. // This token is one of $0, $1, etc. Write the corresponding expression.
  315. writeExpression(&buf, evaluatedExpressions[num], false, varTable, seen)
  316. i += n
  317. } else {
  318. // Something else, like $$.
  319. buf.WriteByte(s[i])
  320. i++
  321. }
  322. } else {
  323. buf.WriteByte(s[i])
  324. i++
  325. }
  326. }
  327. return buf.String()
  328. }
  329. func parseToken(s string, max int) (num int, bytesRead int, ok bool) {
  330. var i int
  331. for i < len(s) && s[i] >= '0' && s[i] <= '9' {
  332. i++
  333. }
  334. num, err := strconv.Atoi(s[:i])
  335. return num, i, err == nil && num <= max
  336. }
  337. // writeExpression recursively writes variables to buf, in a format suitable
  338. // for logging. If printName is true, writes the name of the variable.
  339. func writeExpression(buf *bytes.Buffer, v *cd.Variable, printName bool, varTable []*cd.Variable, seen map[*cd.Variable]bool) {
  340. if v == nil {
  341. // Shouldn't happen.
  342. return
  343. }
  344. name, value, status, members := v.Name, v.Value, v.Status, v.Members
  345. // If v.VarTableIndex is not zero, it refers to an element of varTable.
  346. // We merge its fields with the fields we got from v.
  347. var other *cd.Variable
  348. if idx := int(v.VarTableIndex); idx > 0 && idx < len(varTable) {
  349. other = varTable[idx]
  350. }
  351. if other != nil {
  352. if name == "" {
  353. name = other.Name
  354. }
  355. if value == "" {
  356. value = other.Value
  357. }
  358. if status == nil {
  359. status = other.Status
  360. }
  361. if len(members) == 0 {
  362. members = other.Members
  363. }
  364. }
  365. if printName && name != "" {
  366. buf.WriteString(name)
  367. buf.WriteByte(':')
  368. }
  369. // If we have seen this value before, write "..." rather than repeating it.
  370. if seen[v] {
  371. buf.WriteString("...")
  372. return
  373. }
  374. seen[v] = true
  375. if other != nil {
  376. if seen[other] {
  377. buf.WriteString("...")
  378. return
  379. }
  380. seen[other] = true
  381. }
  382. if value != "" && !strings.HasPrefix(value, "len = ") {
  383. // A plain value.
  384. buf.WriteString(value)
  385. } else if status != nil && status.Description != nil {
  386. // An error.
  387. for _, p := range status.Description.Parameters {
  388. buf.WriteByte('(')
  389. buf.WriteString(p)
  390. buf.WriteByte(')')
  391. }
  392. } else if name == `⚫` {
  393. // A map element.
  394. first := true
  395. for _, member := range members {
  396. if first {
  397. first = false
  398. } else {
  399. buf.WriteByte(':')
  400. }
  401. writeExpression(buf, member, false, varTable, seen)
  402. }
  403. } else {
  404. // A map, array, slice, channel, or struct.
  405. isStruct := value == ""
  406. first := true
  407. buf.WriteByte('{')
  408. for _, member := range members {
  409. if first {
  410. first = false
  411. } else {
  412. buf.WriteString(", ")
  413. }
  414. writeExpression(buf, member, isStruct, varTable, seen)
  415. }
  416. buf.WriteByte('}')
  417. }
  418. }
  419. const (
  420. // Error messages for cd.StatusMessage
  421. messageNotCaptured = "Not captured"
  422. messageTruncated = "Truncated"
  423. messageUnknownPointerType = "Unknown pointer type"
  424. messageUnknownType = "Unknown type"
  425. // RefersTo values for cd.StatusMessage.
  426. refersToVariableName = iota
  427. refersToVariableValue
  428. )
  429. // refersToString contains the strings for each refersTo value.
  430. // See the definition of StatusMessage in the v2/clouddebugger package.
  431. var refersToString = map[int]string{
  432. refersToVariableName: "VARIABLE_NAME",
  433. refersToVariableValue: "VARIABLE_VALUE",
  434. }