// 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 }
// 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) } }
func addMember(v *cd.Variable, name string) *cd.Variable { v2 := &cd.Variable{Name: name} v.Members = append(v.Members, v2) return v2 }