|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460 |
- // Copyright 2016 Google LLC
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
-
- // Package valuecollector is used to collect the values of variables in a program.
- package valuecollector
-
- import (
- "bytes"
- "fmt"
- "strconv"
- "strings"
-
- "cloud.google.com/go/cmd/go-cloud-debug-agent/internal/debug"
- cd "google.golang.org/api/clouddebugger/v2"
- )
-
- const (
- maxArrayLength = 50
- maxMapLength = 20
- )
-
- // Collector is given references to variables from a program being debugged
- // using AddVariable. Then when ReadValues is called, the Collector will fetch
- // the values of those variables. Any variables referred to by those values
- // will also be fetched; e.g. the targets of pointers, members of structs,
- // elements of slices, etc. This continues iteratively, building a graph of
- // values, until all the reachable values are fetched, or a size limit is
- // reached.
- //
- // Variables are passed to the Collector as debug.Var, which is used by x/debug
- // to represent references to variables. Values are returned as cd.Variable,
- // which is used by the Debuglet Controller to represent the graph of values.
- //
- // For example, if the program has a struct variable:
- //
- // foo := SomeStruct{a:42, b:"xyz"}
- //
- // and we call AddVariable with a reference to foo, we will get back a result
- // like:
- //
- // cd.Variable{Name:"foo", VarTableIndex:10}
- //
- // which denotes a variable named "foo" which will have its value stored in
- // element 10 of the table that will later be returned by ReadValues. That
- // element might be:
- //
- // out[10] = &cd.Variable{Members:{{Name:"a", VarTableIndex:11},{Name:"b", VarTableIndex:12}}}
- //
- // which denotes a struct with two members a and b, whose values are in elements
- // 11 and 12 of the output table:
- //
- // out[11] = &cd.Variable{Value:"42"}
- // out[12] = &cd.Variable{Value:"xyz"}
- type Collector struct {
- // prog is the program being debugged.
- prog debug.Program
- // limit is the maximum size of the output slice of values.
- limit int
- // index is a map from references (variables and map elements) to their
- // locations in the table.
- index map[reference]int
- // table contains the references, including those given to the
- // Collector directly and those the Collector itself found.
- // If VarTableIndex is set to 0 in a cd.Variable, it is ignored, so the first entry
- // of table can't be used. On initialization we put a dummy value there.
- table []reference
- }
-
- // reference represents a value which is in the queue to be read by the
- // collector. It is either a debug.Var, or a mapElement.
- type reference interface{}
-
- // mapElement represents an element of a map in the debugged program's memory.
- type mapElement struct {
- debug.Map
- index uint64
- }
-
- // NewCollector returns a Collector for the given program and size limit.
- // The limit is the maximum size of the slice of values returned by ReadValues.
- func NewCollector(prog debug.Program, limit int) *Collector {
- return &Collector{
- prog: prog,
- limit: limit,
- index: make(map[reference]int),
- table: []reference{debug.Var{}},
- }
- }
-
- // AddVariable adds another variable to be collected.
- // The Collector doesn't get the value immediately; it returns a cd.Variable
- // that contains an index into the table which will later be returned by
- // ReadValues.
- func (c *Collector) AddVariable(lv debug.LocalVar) *cd.Variable {
- ret := &cd.Variable{Name: lv.Name}
- if index, ok := c.add(lv.Var); !ok {
- // If the add call failed, it's because we reached the size limit.
- // The Debuglet Controller's convention is to pass it a "Not Captured" error
- // in this case.
- ret.Status = statusMessage(messageNotCaptured, true, refersToVariableName)
- } else {
- ret.VarTableIndex = int64(index)
- }
- return ret
- }
-
- // add adds a reference to the set of values to be read from the
- // program. It returns the index in the output table that will contain the
- // corresponding value. It fails if the table has reached the size limit.
- // It deduplicates references, so the index may be the same as one that was
- // returned from an earlier add call.
- func (c *Collector) add(r reference) (outputIndex int, ok bool) {
- if i, ok := c.index[r]; ok {
- return i, true
- }
- i := len(c.table)
- if i >= c.limit {
- return 0, false
- }
- c.index[r] = i
- c.table = append(c.table, r)
- return i, true
- }
-
- func addMember(v *cd.Variable, name string) *cd.Variable {
- v2 := &cd.Variable{Name: name}
- v.Members = append(v.Members, v2)
- return v2
- }
-
- // ReadValues fetches values of the variables that were passed to the Collector
- // with AddVariable. The values of any new variables found are also fetched,
- // e.g. the targets of pointers or the members of structs, until we reach the
- // size limit or we run out of values to fetch.
- // The results are output as a []*cd.Variable, which is the type we need to send
- // to the Debuglet Controller after we trigger a breakpoint.
- func (c *Collector) ReadValues() (out []*cd.Variable) {
- for i := 0; i < len(c.table); i++ {
- // Create a new cd.Variable for this value, and append it to the output.
- dcv := new(cd.Variable)
- out = append(out, dcv)
- if i == 0 {
- // The first element is unused.
- continue
- }
- switch x := c.table[i].(type) {
- case mapElement:
- key, value, err := c.prog.MapElement(x.Map, x.index)
- if err != nil {
- dcv.Status = statusMessage(err.Error(), true, refersToVariableValue)
- continue
- }
- // Add a member for the key.
- member := addMember(dcv, "key")
- if index, ok := c.add(key); !ok {
- // The table is full.
- member.Status = statusMessage(messageNotCaptured, true, refersToVariableName)
- continue
- } else {
- member.VarTableIndex = int64(index)
- }
- // Add a member for the value.
- member = addMember(dcv, "value")
- if index, ok := c.add(value); !ok {
- // The table is full.
- member.Status = statusMessage(messageNotCaptured, true, refersToVariableName)
- } else {
- member.VarTableIndex = int64(index)
- }
- case debug.Var:
- if v, err := c.prog.Value(x); err != nil {
- dcv.Status = statusMessage(err.Error(), true, refersToVariableValue)
- } else {
- c.FillValue(v, dcv)
- }
- }
- }
- return out
- }
-
- // indexable is an interface for arrays, slices and channels.
- type indexable interface {
- Len() uint64
- Element(uint64) debug.Var
- }
-
- // channel implements indexable.
- type channel struct {
- debug.Channel
- }
-
- func (c channel) Len() uint64 {
- return c.Length
- }
-
- var (
- _ indexable = debug.Array{}
- _ indexable = debug.Slice{}
- _ indexable = channel{}
- )
-
- // FillValue copies a value into a cd.Variable. Any variables referred to by
- // that value, e.g. struct members and pointer targets, are added to the
- // collector's queue, to be fetched later by ReadValues.
- func (c *Collector) FillValue(v debug.Value, dcv *cd.Variable) {
- if c, ok := v.(debug.Channel); ok {
- // Convert to channel, which implements indexable.
- v = channel{c}
- }
- // Fill in dcv in a manner depending on the type of the value we got.
- switch val := v.(type) {
- case int8, int16, int32, int64, bool, uint8, uint16, uint32, uint64, float32, float64, complex64, complex128:
- // For simple types, we just print the value to dcv.Value.
- dcv.Value = fmt.Sprint(val)
- case string:
- // Put double quotes around strings.
- dcv.Value = strconv.Quote(val)
- case debug.String:
- if uint64(len(val.String)) < val.Length {
- // This string value was truncated.
- dcv.Value = strconv.Quote(val.String + "...")
- } else {
- dcv.Value = strconv.Quote(val.String)
- }
- case debug.Struct:
- // For structs, we add an entry to dcv.Members for each field in the
- // struct.
- // Each member will contain the name of the field, and the index in the
- // output table which will contain the value of that field.
- for _, f := range val.Fields {
- member := addMember(dcv, f.Name)
- if index, ok := c.add(f.Var); !ok {
- // The table is full.
- member.Status = statusMessage(messageNotCaptured, true, refersToVariableName)
- } else {
- member.VarTableIndex = int64(index)
- }
- }
- case debug.Map:
- dcv.Value = fmt.Sprintf("len = %d", val.Length)
- for i := uint64(0); i < val.Length; i++ {
- field := addMember(dcv, `⚫`)
- if i == maxMapLength {
- field.Name = "..."
- field.Status = statusMessage(messageTruncated, true, refersToVariableName)
- break
- }
- if index, ok := c.add(mapElement{val, i}); !ok {
- // The value table is full; add a member to contain the error message.
- field.Name = "..."
- field.Status = statusMessage(messageNotCaptured, true, refersToVariableName)
- break
- } else {
- field.VarTableIndex = int64(index)
- }
- }
- case debug.Pointer:
- if val.Address == 0 {
- dcv.Value = "<nil>"
- } else if val.TypeID == 0 {
- // We don't know the type of the pointer, so just output the address as
- // the value.
- dcv.Value = fmt.Sprintf("0x%X", val.Address)
- dcv.Status = statusMessage(messageUnknownPointerType, false, refersToVariableName)
- } else {
- // Adds the pointed-to variable to the table, and links this value to
- // that table entry through VarTableIndex.
- dcv.Value = fmt.Sprintf("0x%X", val.Address)
- target := addMember(dcv, "")
- if index, ok := c.add(debug.Var(val)); !ok {
- target.Status = statusMessage(messageNotCaptured, true, refersToVariableName)
- } else {
- target.VarTableIndex = int64(index)
- }
- }
- case indexable:
- // Arrays, slices and channels.
- dcv.Value = "len = " + fmt.Sprint(val.Len())
- for j := uint64(0); j < val.Len(); j++ {
- field := addMember(dcv, fmt.Sprint(`[`, j, `]`))
- if j == maxArrayLength {
- field.Name = "..."
- field.Status = statusMessage(messageTruncated, true, refersToVariableName)
- break
- }
- vr := val.Element(j)
- if index, ok := c.add(vr); !ok {
- // The value table is full; add a member to contain the error message.
- field.Name = "..."
- field.Status = statusMessage(messageNotCaptured, true, refersToVariableName)
- break
- } else {
- // Add a member with the index as the name.
- field.VarTableIndex = int64(index)
- }
- }
- default:
- dcv.Status = statusMessage(messageUnknownType, false, refersToVariableName)
- }
- }
-
- // statusMessage returns a *cd.StatusMessage with the given message, IsError
- // field and refersTo field.
- func statusMessage(msg string, isError bool, refersTo int) *cd.StatusMessage {
- return &cd.StatusMessage{
- Description: &cd.FormatMessage{Format: "$0", Parameters: []string{msg}},
- IsError: isError,
- RefersTo: refersToString[refersTo],
- }
- }
-
- // LogString produces a string for a logpoint, substituting in variable values
- // using evaluatedExpressions and varTable.
- func LogString(s string, evaluatedExpressions []*cd.Variable, varTable []*cd.Variable) string {
- var buf bytes.Buffer
- fmt.Fprintf(&buf, "LOGPOINT: ")
- seen := make(map[*cd.Variable]bool)
- for i := 0; i < len(s); {
- if s[i] == '$' {
- i++
- if num, n, ok := parseToken(s[i:], len(evaluatedExpressions)-1); ok {
- // This token is one of $0, $1, etc. Write the corresponding expression.
- writeExpression(&buf, evaluatedExpressions[num], false, varTable, seen)
- i += n
- } else {
- // Something else, like $$.
- buf.WriteByte(s[i])
- i++
- }
- } else {
- buf.WriteByte(s[i])
- i++
- }
- }
- return buf.String()
- }
-
- func parseToken(s string, max int) (num int, bytesRead int, ok bool) {
- var i int
- for i < len(s) && s[i] >= '0' && s[i] <= '9' {
- i++
- }
- num, err := strconv.Atoi(s[:i])
- return num, i, err == nil && num <= max
- }
-
- // writeExpression recursively writes variables to buf, in a format suitable
- // for logging. If printName is true, writes the name of the variable.
- func writeExpression(buf *bytes.Buffer, v *cd.Variable, printName bool, varTable []*cd.Variable, seen map[*cd.Variable]bool) {
- if v == nil {
- // Shouldn't happen.
- return
- }
- name, value, status, members := v.Name, v.Value, v.Status, v.Members
-
- // If v.VarTableIndex is not zero, it refers to an element of varTable.
- // We merge its fields with the fields we got from v.
- var other *cd.Variable
- if idx := int(v.VarTableIndex); idx > 0 && idx < len(varTable) {
- other = varTable[idx]
- }
- if other != nil {
- if name == "" {
- name = other.Name
- }
- if value == "" {
- value = other.Value
- }
- if status == nil {
- status = other.Status
- }
- if len(members) == 0 {
- members = other.Members
- }
- }
- if printName && name != "" {
- buf.WriteString(name)
- buf.WriteByte(':')
- }
-
- // If we have seen this value before, write "..." rather than repeating it.
- if seen[v] {
- buf.WriteString("...")
- return
- }
- seen[v] = true
- if other != nil {
- if seen[other] {
- buf.WriteString("...")
- return
- }
- seen[other] = true
- }
-
- if value != "" && !strings.HasPrefix(value, "len = ") {
- // A plain value.
- buf.WriteString(value)
- } else if status != nil && status.Description != nil {
- // An error.
- for _, p := range status.Description.Parameters {
- buf.WriteByte('(')
- buf.WriteString(p)
- buf.WriteByte(')')
- }
- } else if name == `⚫` {
- // A map element.
- first := true
- for _, member := range members {
- if first {
- first = false
- } else {
- buf.WriteByte(':')
- }
- writeExpression(buf, member, false, varTable, seen)
- }
- } else {
- // A map, array, slice, channel, or struct.
- isStruct := value == ""
- first := true
- buf.WriteByte('{')
- for _, member := range members {
- if first {
- first = false
- } else {
- buf.WriteString(", ")
- }
- writeExpression(buf, member, isStruct, varTable, seen)
- }
- buf.WriteByte('}')
- }
- }
-
- const (
- // Error messages for cd.StatusMessage
- messageNotCaptured = "Not captured"
- messageTruncated = "Truncated"
- messageUnknownPointerType = "Unknown pointer type"
- messageUnknownType = "Unknown type"
- // RefersTo values for cd.StatusMessage.
- refersToVariableName = iota
- refersToVariableValue
- )
-
- // refersToString contains the strings for each refersTo value.
- // See the definition of StatusMessage in the v2/clouddebugger package.
- var refersToString = map[int]string{
- refersToVariableName: "VARIABLE_NAME",
- refersToVariableValue: "VARIABLE_VALUE",
- }
|