// expressionValues evaluates a slice of expressions and returns a []*cd.Variable // containing the results. // If the result of an expression evaluation refers to values from the program's // memory (e.g., the expression evaluates to a slice) a corresponding variable is // added to the value collector, to be read later. func expressionValues(expressions []string, prog debug.Program, vc *valuecollector.Collector) []*cd.Variable { evaluatedExpressions := make([]*cd.Variable, len(expressions)) for i, exp := range expressions { ee := &cd.Variable{Name: exp} evaluatedExpressions[i] = ee if val, err := prog.Evaluate(exp); err != nil { ee.Status = errorStatusMessage(err.Error(), refersToBreakpointExpression) } else { vc.FillValue(val, ee) } } return evaluatedExpressions }
// condTruth evaluates a condition. func condTruth(condition string, prog debug.Program) (bool, error) { if condition == "" { // A condition wasn't set. return true, nil } val, err := prog.Evaluate(condition) if err != nil { return false, err } if v, ok := val.(bool); !ok { return false, fmt.Errorf("condition expression has type %T, should be bool", val) } else { return v, nil } }
// stackFrames returns a stack trace for the program. It passes references to // function parameters and local variables to the value collector, so it can read // their values later. func stackFrames(prog debug.Program, vc *valuecollector.Collector) ([]*cd.StackFrame, *cd.StatusMessage) { frames, err := prog.Frames(maxCapturedStackFrames) if err != nil { return nil, errorStatusMessage("Error getting stack: "+err.Error(), refersToUnspecified) } stackFrames := make([]*cd.StackFrame, len(frames)) for i, f := range frames { frame := &cd.StackFrame{} frame.Function = f.Function for _, v := range f.Params { frame.Arguments = append(frame.Arguments, vc.AddVariable(debug.LocalVar(v))) } for _, v := range f.Vars { frame.Locals = append(frame.Locals, vc.AddVariable(v)) } frame.Location = &cd.SourceLocation{ Path: f.File, Line: int64(f.Line), } stackFrames[i] = frame } return stackFrames, nil }
// programLoop runs the program being debugged to completion. When a breakpoint's // conditions are satisfied, it sends an Update RPC to the Debuglet Controller. // The function returns when the program exits and all Update RPCs have finished. func programLoop(c *debuglet.Controller, bs *breakpoints.BreakpointStore, prog debug.Program) { var wg sync.WaitGroup for { // Run the program until it hits a breakpoint or exits. status, err := prog.Resume() if err != nil { break } // Get the breakpoints at this address whose conditions were satisfied, // and remove the ones that aren't logpoints. bps := bs.BreakpointsAtPC(status.PC) bps = bpsWithConditionSatisfied(bps, prog) for _, bp := range bps { if bp.Action != "LOG" { bs.RemoveBreakpoint(bp) } } if len(bps) == 0 { continue } // Evaluate expressions and get the stack. vc := valuecollector.NewCollector(prog, maxCapturedVariables) needStackFrames := false for _, bp := range bps { // If evaluating bp's condition didn't return an error, evaluate bp's // expressions, and later get the stack frames. if bp.Status == nil { bp.EvaluatedExpressions = expressionValues(bp.Expressions, prog, vc) needStackFrames = true } } var ( stack []*cd.StackFrame stackFramesStatusMessage *cd.StatusMessage ) if needStackFrames { stack, stackFramesStatusMessage = stackFrames(prog, vc) } // Read variable values from the program. variableTable := vc.ReadValues() // Start a goroutine to send updates to the Debuglet Controller or write // to logs, concurrently with resuming the program. // TODO: retry Update on failure. for _, bp := range bps { wg.Add(1) switch bp.Action { case "LOG": go func(format string, evaluatedExpressions []*cd.Variable) { s := valuecollector.LogString(format, evaluatedExpressions, variableTable) log.Print(s) wg.Done() }(bp.LogMessageFormat, bp.EvaluatedExpressions) bp.Status = nil bp.EvaluatedExpressions = nil default: go func(bp *cd.Breakpoint) { defer wg.Done() bp.IsFinalState = true if bp.Status == nil { // If evaluating bp's condition didn't return an error, include the // stack frames, variable table, and any status message produced when // getting the stack frames. bp.StackFrames = stack bp.VariableTable = variableTable bp.Status = stackFramesStatusMessage } if err := c.Update(bp.Id, bp); err != nil { log.Printf("Failed to send breakpoint update for %s: %s", bp.Id, err) } }(bp) } } } // Wait for all updates to finish before returning. wg.Wait() }