| // 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. | 
 |  | 
 | // +build linux,go1.7 | 
 |  | 
 | package main | 
 |  | 
 | import ( | 
 | 	"context" | 
 | 	"encoding/json" | 
 | 	"flag" | 
 | 	"fmt" | 
 | 	"io/ioutil" | 
 | 	"log" | 
 | 	"math/rand" | 
 | 	"os" | 
 | 	"sync" | 
 | 	"time" | 
 |  | 
 | 	"cloud.google.com/go/cmd/go-cloud-debug-agent/internal/breakpoints" | 
 | 	debuglet "cloud.google.com/go/cmd/go-cloud-debug-agent/internal/controller" | 
 | 	"cloud.google.com/go/cmd/go-cloud-debug-agent/internal/debug" | 
 | 	"cloud.google.com/go/cmd/go-cloud-debug-agent/internal/debug/local" | 
 | 	"cloud.google.com/go/cmd/go-cloud-debug-agent/internal/valuecollector" | 
 | 	"cloud.google.com/go/compute/metadata" | 
 | 	"golang.org/x/oauth2" | 
 | 	"golang.org/x/oauth2/google" | 
 | 	cd "google.golang.org/api/clouddebugger/v2" | 
 | ) | 
 |  | 
 | var ( | 
 | 	appModule         = flag.String("appmodule", "", "Optional application module name.") | 
 | 	appVersion        = flag.String("appversion", "", "Optional application module version name.") | 
 | 	sourceContextFile = flag.String("sourcecontext", "", "File containing JSON-encoded source context.") | 
 | 	verbose           = flag.Bool("v", false, "Output verbose log messages.") | 
 | 	projectNumber     = flag.String("projectnumber", "", "Project number."+ | 
 | 		"  If this is not set, it is read from the GCP metadata server.") | 
 | 	projectID = flag.String("projectid", "", "Project ID."+ | 
 | 		"  If this is not set, it is read from the GCP metadata server.") | 
 | 	serviceAccountFile = flag.String("serviceaccountfile", "", "File containing JSON service account credentials.") | 
 | ) | 
 |  | 
 | const ( | 
 | 	maxCapturedStackFrames = 50 | 
 | 	maxCapturedVariables   = 1000 | 
 | ) | 
 |  | 
 | func main() { | 
 | 	flag.Usage = usage | 
 | 	flag.Parse() | 
 | 	args := flag.Args() | 
 | 	if len(args) == 0 { | 
 | 		// The user needs to supply the name of the executable to run. | 
 | 		flag.Usage() | 
 | 		return | 
 | 	} | 
 | 	if *projectNumber == "" { | 
 | 		var err error | 
 | 		*projectNumber, err = metadata.NumericProjectID() | 
 | 		if err != nil { | 
 | 			log.Print("Debuglet initialization: ", err) | 
 | 		} | 
 | 	} | 
 | 	if *projectID == "" { | 
 | 		var err error | 
 | 		*projectID, err = metadata.ProjectID() | 
 | 		if err != nil { | 
 | 			log.Print("Debuglet initialization: ", err) | 
 | 		} | 
 | 	} | 
 | 	sourceContexts, err := readSourceContextFile(*sourceContextFile) | 
 | 	if err != nil { | 
 | 		log.Print("Reading source context file: ", err) | 
 | 	} | 
 | 	var ts oauth2.TokenSource | 
 | 	ctx := context.Background() | 
 | 	if *serviceAccountFile != "" { | 
 | 		if ts, err = serviceAcctTokenSource(ctx, *serviceAccountFile, cd.CloudDebuggerScope); err != nil { | 
 | 			log.Fatalf("Error getting credentials from file %s: %v", *serviceAccountFile, err) | 
 | 		} | 
 | 	} else if ts, err = google.DefaultTokenSource(ctx, cd.CloudDebuggerScope); err != nil { | 
 | 		log.Print("Error getting application default credentials for Cloud Debugger:", err) | 
 | 		os.Exit(103) | 
 | 	} | 
 | 	c, err := debuglet.NewController(ctx, debuglet.Options{ | 
 | 		ProjectNumber:  *projectNumber, | 
 | 		ProjectID:      *projectID, | 
 | 		AppModule:      *appModule, | 
 | 		AppVersion:     *appVersion, | 
 | 		SourceContexts: sourceContexts, | 
 | 		Verbose:        *verbose, | 
 | 		TokenSource:    ts, | 
 | 	}) | 
 | 	if err != nil { | 
 | 		log.Fatal("Error connecting to Cloud Debugger: ", err) | 
 | 	} | 
 | 	prog, err := local.New(args[0]) | 
 | 	if err != nil { | 
 | 		log.Fatal("Error loading program: ", err) | 
 | 	} | 
 | 	// Load the program, but don't actually start it running yet. | 
 | 	if _, err = prog.Run(args[1:]...); err != nil { | 
 | 		log.Fatal("Error loading program: ", err) | 
 | 	} | 
 | 	bs := breakpoints.NewBreakpointStore(prog) | 
 |  | 
 | 	// Seed the random number generator. | 
 | 	rand.Seed(time.Now().UnixNano()) | 
 |  | 
 | 	// Now we want to do two things: run the user's program, and start sending | 
 | 	// List requests periodically to the Debuglet Controller to get breakpoints | 
 | 	// to set. | 
 | 	// | 
 | 	// We want to give the Debuglet Controller a chance to give us breakpoints | 
 | 	// before we start the program, otherwise we would miss any breakpoint | 
 | 	// triggers that occur during program startup -- for example, a breakpoint on | 
 | 	// the first line of main. But if the Debuglet Controller is not responding or | 
 | 	// is returning errors, we don't want to delay starting the program | 
 | 	// indefinitely. | 
 | 	// | 
 | 	// We pass a channel to breakpointListLoop, which will close it when the first | 
 | 	// List call finishes.  Then we wait until either the channel is closed or a | 
 | 	// 5-second timer has finished before starting the program. | 
 | 	ch := make(chan bool) | 
 | 	// Start a goroutine that sends List requests to the Debuglet Controller, and | 
 | 	// sets any breakpoints it gets back. | 
 | 	go breakpointListLoop(ctx, c, bs, ch) | 
 | 	// Wait until 5 seconds have passed or breakpointListLoop has closed ch. | 
 | 	select { | 
 | 	case <-time.After(5 * time.Second): | 
 | 	case <-ch: | 
 | 	} | 
 | 	// Run the debuggee. | 
 | 	programLoop(ctx, c, bs, prog) | 
 | } | 
 |  | 
 | // usage prints a usage message to stderr and exits. | 
 | func usage() { | 
 | 	me := "a.out" | 
 | 	if len(os.Args) >= 1 { | 
 | 		me = os.Args[0] | 
 | 	} | 
 | 	fmt.Fprintf(os.Stderr, "Usage of %s:\n", me) | 
 | 	fmt.Fprintf(os.Stderr, "\t%s [flags...] -- <program name> args...\n", me) | 
 | 	fmt.Fprintf(os.Stderr, "Flags:\n") | 
 | 	flag.PrintDefaults() | 
 | 	fmt.Fprintf(os.Stderr, | 
 | 		"See https://cloud.google.com/tools/cloud-debugger/setting-up-on-compute-engine for more information.\n") | 
 | 	os.Exit(2) | 
 | } | 
 |  | 
 | // readSourceContextFile reads a JSON-encoded source context from the given file. | 
 | // It returns a non-empty slice on success. | 
 | func readSourceContextFile(filename string) ([]*cd.SourceContext, error) { | 
 | 	if filename == "" { | 
 | 		return nil, nil | 
 | 	} | 
 | 	scJSON, err := ioutil.ReadFile(filename) | 
 | 	if err != nil { | 
 | 		return nil, fmt.Errorf("reading file %q: %v", filename, err) | 
 | 	} | 
 | 	var sc cd.SourceContext | 
 | 	if err = json.Unmarshal(scJSON, &sc); err != nil { | 
 | 		return nil, fmt.Errorf("parsing file %q: %v", filename, err) | 
 | 	} | 
 | 	return []*cd.SourceContext{&sc}, nil | 
 | } | 
 |  | 
 | // breakpointListLoop repeatedly calls the Debuglet Controller's List RPC, and | 
 | // passes the results to the BreakpointStore so it can set and unset breakpoints | 
 | // in the program. | 
 | // | 
 | // After the first List call finishes, ch is closed. | 
 | func breakpointListLoop(ctx context.Context, c *debuglet.Controller, bs *breakpoints.BreakpointStore, first chan bool) { | 
 | 	const ( | 
 | 		avgTimeBetweenCalls = time.Second | 
 | 		errorDelay          = 5 * time.Second | 
 | 	) | 
 |  | 
 | 	// randomDuration returns a random duration with expected value avg. | 
 | 	randomDuration := func(avg time.Duration) time.Duration { | 
 | 		return time.Duration(rand.Int63n(int64(2*avg + 1))) | 
 | 	} | 
 |  | 
 | 	var consecutiveFailures uint | 
 |  | 
 | 	for { | 
 | 		callStart := time.Now() | 
 | 		resp, err := c.List(ctx) | 
 | 		if err != nil && err != debuglet.ErrListUnchanged { | 
 | 			log.Printf("Debuglet controller server error: %v", err) | 
 | 		} | 
 | 		if err == nil { | 
 | 			bs.ProcessBreakpointList(resp.Breakpoints) | 
 | 		} | 
 |  | 
 | 		if first != nil { | 
 | 			// We've finished one call to List and set any breakpoints we received. | 
 | 			close(first) | 
 | 			first = nil | 
 | 		} | 
 |  | 
 | 		// Asynchronously send updates for any breakpoints that caused an error when | 
 | 		// the BreakpointStore tried to process them.  We don't wait for the update | 
 | 		// to finish before the program can exit, as we do for normal updates. | 
 | 		errorBps := bs.ErrorBreakpoints() | 
 | 		for _, bp := range errorBps { | 
 | 			go func(bp *cd.Breakpoint) { | 
 | 				if err := c.Update(ctx, bp.Id, bp); err != nil { | 
 | 					log.Printf("Failed to send breakpoint update for %s: %s", bp.Id, err) | 
 | 				} | 
 | 			}(bp) | 
 | 		} | 
 |  | 
 | 		// Make the next call not too soon after the one we just did. | 
 | 		delay := randomDuration(avgTimeBetweenCalls) | 
 |  | 
 | 		// If the call returned an error other than ErrListUnchanged, wait longer. | 
 | 		if err != nil && err != debuglet.ErrListUnchanged { | 
 | 			// Wait twice as long after each consecutive failure, to a maximum of 16x. | 
 | 			delay += randomDuration(errorDelay * (1 << consecutiveFailures)) | 
 | 			if consecutiveFailures < 4 { | 
 | 				consecutiveFailures++ | 
 | 			} | 
 | 		} else { | 
 | 			consecutiveFailures = 0 | 
 | 		} | 
 |  | 
 | 		// Sleep until we reach time callStart+delay.  If we've already passed that | 
 | 		// time, time.Sleep will return immediately -- this should be the common | 
 | 		// case, since the server will delay responding to List for a while when | 
 | 		// there are no changes to report. | 
 | 		time.Sleep(callStart.Add(delay).Sub(time.Now())) | 
 | 	} | 
 | } | 
 |  | 
 | // 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(ctx context.Context, 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(ctx, 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() | 
 | } | 
 |  | 
 | // bpsWithConditionSatisfied returns the breakpoints whose conditions are true | 
 | // (or that do not have a condition.) | 
 | func bpsWithConditionSatisfied(bpsIn []*cd.Breakpoint, prog debug.Program) []*cd.Breakpoint { | 
 | 	var bpsOut []*cd.Breakpoint | 
 | 	for _, bp := range bpsIn { | 
 | 		cond, err := condTruth(bp.Condition, prog) | 
 | 		if err != nil { | 
 | 			bp.Status = errorStatusMessage(err.Error(), refersToBreakpointCondition) | 
 | 			// Include bp in the list to be updated when there's an error, so that | 
 | 			// the user gets a response. | 
 | 			bpsOut = append(bpsOut, bp) | 
 | 		} else if cond { | 
 | 			bpsOut = append(bpsOut, bp) | 
 | 		} | 
 | 	} | 
 | 	return bpsOut | 
 | } | 
 |  | 
 | // 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 | 
 | 	} | 
 | } | 
 |  | 
 | // 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 | 
 | } | 
 |  | 
 | // 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 | 
 | } | 
 |  | 
 | // errorStatusMessage returns a *cd.StatusMessage indicating an error, | 
 | // with the given message and refersTo field. | 
 | func errorStatusMessage(msg string, refersTo int) *cd.StatusMessage { | 
 | 	return &cd.StatusMessage{ | 
 | 		Description: &cd.FormatMessage{Format: "$0", Parameters: []string{msg}}, | 
 | 		IsError:     true, | 
 | 		RefersTo:    refersToString[refersTo], | 
 | 	} | 
 | } | 
 |  | 
 | const ( | 
 | 	// RefersTo values for cd.StatusMessage. | 
 | 	refersToUnspecified = iota | 
 | 	refersToBreakpointCondition | 
 | 	refersToBreakpointExpression | 
 | ) | 
 |  | 
 | // refersToString contains the strings for each refersTo value. | 
 | // See the definition of StatusMessage in the v2/clouddebugger package. | 
 | var refersToString = map[int]string{ | 
 | 	refersToUnspecified:          "UNSPECIFIED", | 
 | 	refersToBreakpointCondition:  "BREAKPOINT_CONDITION", | 
 | 	refersToBreakpointExpression: "BREAKPOINT_EXPRESSION", | 
 | } | 
 |  | 
 | func serviceAcctTokenSource(ctx context.Context, filename string, scope ...string) (oauth2.TokenSource, error) { | 
 | 	data, err := ioutil.ReadFile(filename) | 
 | 	if err != nil { | 
 | 		return nil, fmt.Errorf("cannot read service account file: %v", err) | 
 | 	} | 
 | 	cfg, err := google.JWTConfigFromJSON(data, scope...) | 
 | 	if err != nil { | 
 | 		return nil, fmt.Errorf("google.JWTConfigFromJSON: %v", err) | 
 | 	} | 
 | 	return cfg.TokenSource(ctx), nil | 
 | } |