You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

451 lines
15 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. // +build linux,go1.7
  15. package main
  16. import (
  17. "encoding/json"
  18. "flag"
  19. "fmt"
  20. "io/ioutil"
  21. "log"
  22. "math/rand"
  23. "os"
  24. "sync"
  25. "time"
  26. "cloud.google.com/go/cmd/go-cloud-debug-agent/internal/breakpoints"
  27. debuglet "cloud.google.com/go/cmd/go-cloud-debug-agent/internal/controller"
  28. "cloud.google.com/go/cmd/go-cloud-debug-agent/internal/debug"
  29. "cloud.google.com/go/cmd/go-cloud-debug-agent/internal/debug/local"
  30. "cloud.google.com/go/cmd/go-cloud-debug-agent/internal/valuecollector"
  31. "cloud.google.com/go/compute/metadata"
  32. "golang.org/x/net/context"
  33. "golang.org/x/oauth2"
  34. "golang.org/x/oauth2/google"
  35. cd "google.golang.org/api/clouddebugger/v2"
  36. )
  37. var (
  38. appModule = flag.String("appmodule", "", "Optional application module name.")
  39. appVersion = flag.String("appversion", "", "Optional application module version name.")
  40. sourceContextFile = flag.String("sourcecontext", "", "File containing JSON-encoded source context.")
  41. verbose = flag.Bool("v", false, "Output verbose log messages.")
  42. projectNumber = flag.String("projectnumber", "", "Project number."+
  43. " If this is not set, it is read from the GCP metadata server.")
  44. projectID = flag.String("projectid", "", "Project ID."+
  45. " If this is not set, it is read from the GCP metadata server.")
  46. serviceAccountFile = flag.String("serviceaccountfile", "", "File containing JSON service account credentials.")
  47. )
  48. const (
  49. maxCapturedStackFrames = 50
  50. maxCapturedVariables = 1000
  51. )
  52. func main() {
  53. flag.Usage = usage
  54. flag.Parse()
  55. args := flag.Args()
  56. if len(args) == 0 {
  57. // The user needs to supply the name of the executable to run.
  58. flag.Usage()
  59. return
  60. }
  61. if *projectNumber == "" {
  62. var err error
  63. *projectNumber, err = metadata.NumericProjectID()
  64. if err != nil {
  65. log.Print("Debuglet initialization: ", err)
  66. }
  67. }
  68. if *projectID == "" {
  69. var err error
  70. *projectID, err = metadata.ProjectID()
  71. if err != nil {
  72. log.Print("Debuglet initialization: ", err)
  73. }
  74. }
  75. sourceContexts, err := readSourceContextFile(*sourceContextFile)
  76. if err != nil {
  77. log.Print("Reading source context file: ", err)
  78. }
  79. var ts oauth2.TokenSource
  80. ctx := context.Background()
  81. if *serviceAccountFile != "" {
  82. if ts, err = serviceAcctTokenSource(ctx, *serviceAccountFile, cd.CloudDebuggerScope); err != nil {
  83. log.Fatalf("Error getting credentials from file %s: %v", *serviceAccountFile, err)
  84. }
  85. } else if ts, err = google.DefaultTokenSource(ctx, cd.CloudDebuggerScope); err != nil {
  86. log.Print("Error getting application default credentials for Cloud Debugger:", err)
  87. os.Exit(103)
  88. }
  89. c, err := debuglet.NewController(ctx, debuglet.Options{
  90. ProjectNumber: *projectNumber,
  91. ProjectID: *projectID,
  92. AppModule: *appModule,
  93. AppVersion: *appVersion,
  94. SourceContexts: sourceContexts,
  95. Verbose: *verbose,
  96. TokenSource: ts,
  97. })
  98. if err != nil {
  99. log.Fatal("Error connecting to Cloud Debugger: ", err)
  100. }
  101. prog, err := local.New(args[0])
  102. if err != nil {
  103. log.Fatal("Error loading program: ", err)
  104. }
  105. // Load the program, but don't actually start it running yet.
  106. if _, err = prog.Run(args[1:]...); err != nil {
  107. log.Fatal("Error loading program: ", err)
  108. }
  109. bs := breakpoints.NewBreakpointStore(prog)
  110. // Seed the random number generator.
  111. rand.Seed(time.Now().UnixNano())
  112. // Now we want to do two things: run the user's program, and start sending
  113. // List requests periodically to the Debuglet Controller to get breakpoints
  114. // to set.
  115. //
  116. // We want to give the Debuglet Controller a chance to give us breakpoints
  117. // before we start the program, otherwise we would miss any breakpoint
  118. // triggers that occur during program startup -- for example, a breakpoint on
  119. // the first line of main. But if the Debuglet Controller is not responding or
  120. // is returning errors, we don't want to delay starting the program
  121. // indefinitely.
  122. //
  123. // We pass a channel to breakpointListLoop, which will close it when the first
  124. // List call finishes. Then we wait until either the channel is closed or a
  125. // 5-second timer has finished before starting the program.
  126. ch := make(chan bool)
  127. // Start a goroutine that sends List requests to the Debuglet Controller, and
  128. // sets any breakpoints it gets back.
  129. go breakpointListLoop(ctx, c, bs, ch)
  130. // Wait until 5 seconds have passed or breakpointListLoop has closed ch.
  131. select {
  132. case <-time.After(5 * time.Second):
  133. case <-ch:
  134. }
  135. // Run the debuggee.
  136. programLoop(ctx, c, bs, prog)
  137. }
  138. // usage prints a usage message to stderr and exits.
  139. func usage() {
  140. me := "a.out"
  141. if len(os.Args) >= 1 {
  142. me = os.Args[0]
  143. }
  144. fmt.Fprintf(os.Stderr, "Usage of %s:\n", me)
  145. fmt.Fprintf(os.Stderr, "\t%s [flags...] -- <program name> args...\n", me)
  146. fmt.Fprintf(os.Stderr, "Flags:\n")
  147. flag.PrintDefaults()
  148. fmt.Fprintf(os.Stderr,
  149. "See https://cloud.google.com/tools/cloud-debugger/setting-up-on-compute-engine for more information.\n")
  150. os.Exit(2)
  151. }
  152. // readSourceContextFile reads a JSON-encoded source context from the given file.
  153. // It returns a non-empty slice on success.
  154. func readSourceContextFile(filename string) ([]*cd.SourceContext, error) {
  155. if filename == "" {
  156. return nil, nil
  157. }
  158. scJSON, err := ioutil.ReadFile(filename)
  159. if err != nil {
  160. return nil, fmt.Errorf("reading file %q: %v", filename, err)
  161. }
  162. var sc cd.SourceContext
  163. if err = json.Unmarshal(scJSON, &sc); err != nil {
  164. return nil, fmt.Errorf("parsing file %q: %v", filename, err)
  165. }
  166. return []*cd.SourceContext{&sc}, nil
  167. }
  168. // breakpointListLoop repeatedly calls the Debuglet Controller's List RPC, and
  169. // passes the results to the BreakpointStore so it can set and unset breakpoints
  170. // in the program.
  171. //
  172. // After the first List call finishes, ch is closed.
  173. func breakpointListLoop(ctx context.Context, c *debuglet.Controller, bs *breakpoints.BreakpointStore, first chan bool) {
  174. const (
  175. avgTimeBetweenCalls = time.Second
  176. errorDelay = 5 * time.Second
  177. )
  178. // randomDuration returns a random duration with expected value avg.
  179. randomDuration := func(avg time.Duration) time.Duration {
  180. return time.Duration(rand.Int63n(int64(2*avg + 1)))
  181. }
  182. var consecutiveFailures uint
  183. for {
  184. callStart := time.Now()
  185. resp, err := c.List(ctx)
  186. if err != nil && err != debuglet.ErrListUnchanged {
  187. log.Printf("Debuglet controller server error: %v", err)
  188. }
  189. if err == nil {
  190. bs.ProcessBreakpointList(resp.Breakpoints)
  191. }
  192. if first != nil {
  193. // We've finished one call to List and set any breakpoints we received.
  194. close(first)
  195. first = nil
  196. }
  197. // Asynchronously send updates for any breakpoints that caused an error when
  198. // the BreakpointStore tried to process them. We don't wait for the update
  199. // to finish before the program can exit, as we do for normal updates.
  200. errorBps := bs.ErrorBreakpoints()
  201. for _, bp := range errorBps {
  202. go func(bp *cd.Breakpoint) {
  203. if err := c.Update(ctx, bp.Id, bp); err != nil {
  204. log.Printf("Failed to send breakpoint update for %s: %s", bp.Id, err)
  205. }
  206. }(bp)
  207. }
  208. // Make the next call not too soon after the one we just did.
  209. delay := randomDuration(avgTimeBetweenCalls)
  210. // If the call returned an error other than ErrListUnchanged, wait longer.
  211. if err != nil && err != debuglet.ErrListUnchanged {
  212. // Wait twice as long after each consecutive failure, to a maximum of 16x.
  213. delay += randomDuration(errorDelay * (1 << consecutiveFailures))
  214. if consecutiveFailures < 4 {
  215. consecutiveFailures++
  216. }
  217. } else {
  218. consecutiveFailures = 0
  219. }
  220. // Sleep until we reach time callStart+delay. If we've already passed that
  221. // time, time.Sleep will return immediately -- this should be the common
  222. // case, since the server will delay responding to List for a while when
  223. // there are no changes to report.
  224. time.Sleep(callStart.Add(delay).Sub(time.Now()))
  225. }
  226. }
  227. // programLoop runs the program being debugged to completion. When a breakpoint's
  228. // conditions are satisfied, it sends an Update RPC to the Debuglet Controller.
  229. // The function returns when the program exits and all Update RPCs have finished.
  230. func programLoop(ctx context.Context, c *debuglet.Controller, bs *breakpoints.BreakpointStore, prog debug.Program) {
  231. var wg sync.WaitGroup
  232. for {
  233. // Run the program until it hits a breakpoint or exits.
  234. status, err := prog.Resume()
  235. if err != nil {
  236. break
  237. }
  238. // Get the breakpoints at this address whose conditions were satisfied,
  239. // and remove the ones that aren't logpoints.
  240. bps := bs.BreakpointsAtPC(status.PC)
  241. bps = bpsWithConditionSatisfied(bps, prog)
  242. for _, bp := range bps {
  243. if bp.Action != "LOG" {
  244. bs.RemoveBreakpoint(bp)
  245. }
  246. }
  247. if len(bps) == 0 {
  248. continue
  249. }
  250. // Evaluate expressions and get the stack.
  251. vc := valuecollector.NewCollector(prog, maxCapturedVariables)
  252. needStackFrames := false
  253. for _, bp := range bps {
  254. // If evaluating bp's condition didn't return an error, evaluate bp's
  255. // expressions, and later get the stack frames.
  256. if bp.Status == nil {
  257. bp.EvaluatedExpressions = expressionValues(bp.Expressions, prog, vc)
  258. needStackFrames = true
  259. }
  260. }
  261. var (
  262. stack []*cd.StackFrame
  263. stackFramesStatusMessage *cd.StatusMessage
  264. )
  265. if needStackFrames {
  266. stack, stackFramesStatusMessage = stackFrames(prog, vc)
  267. }
  268. // Read variable values from the program.
  269. variableTable := vc.ReadValues()
  270. // Start a goroutine to send updates to the Debuglet Controller or write
  271. // to logs, concurrently with resuming the program.
  272. // TODO: retry Update on failure.
  273. for _, bp := range bps {
  274. wg.Add(1)
  275. switch bp.Action {
  276. case "LOG":
  277. go func(format string, evaluatedExpressions []*cd.Variable) {
  278. s := valuecollector.LogString(format, evaluatedExpressions, variableTable)
  279. log.Print(s)
  280. wg.Done()
  281. }(bp.LogMessageFormat, bp.EvaluatedExpressions)
  282. bp.Status = nil
  283. bp.EvaluatedExpressions = nil
  284. default:
  285. go func(bp *cd.Breakpoint) {
  286. defer wg.Done()
  287. bp.IsFinalState = true
  288. if bp.Status == nil {
  289. // If evaluating bp's condition didn't return an error, include the
  290. // stack frames, variable table, and any status message produced when
  291. // getting the stack frames.
  292. bp.StackFrames = stack
  293. bp.VariableTable = variableTable
  294. bp.Status = stackFramesStatusMessage
  295. }
  296. if err := c.Update(ctx, bp.Id, bp); err != nil {
  297. log.Printf("Failed to send breakpoint update for %s: %s", bp.Id, err)
  298. }
  299. }(bp)
  300. }
  301. }
  302. }
  303. // Wait for all updates to finish before returning.
  304. wg.Wait()
  305. }
  306. // bpsWithConditionSatisfied returns the breakpoints whose conditions are true
  307. // (or that do not have a condition.)
  308. func bpsWithConditionSatisfied(bpsIn []*cd.Breakpoint, prog debug.Program) []*cd.Breakpoint {
  309. var bpsOut []*cd.Breakpoint
  310. for _, bp := range bpsIn {
  311. cond, err := condTruth(bp.Condition, prog)
  312. if err != nil {
  313. bp.Status = errorStatusMessage(err.Error(), refersToBreakpointCondition)
  314. // Include bp in the list to be updated when there's an error, so that
  315. // the user gets a response.
  316. bpsOut = append(bpsOut, bp)
  317. } else if cond {
  318. bpsOut = append(bpsOut, bp)
  319. }
  320. }
  321. return bpsOut
  322. }
  323. // condTruth evaluates a condition.
  324. func condTruth(condition string, prog debug.Program) (bool, error) {
  325. if condition == "" {
  326. // A condition wasn't set.
  327. return true, nil
  328. }
  329. val, err := prog.Evaluate(condition)
  330. if err != nil {
  331. return false, err
  332. }
  333. if v, ok := val.(bool); !ok {
  334. return false, fmt.Errorf("condition expression has type %T, should be bool", val)
  335. } else {
  336. return v, nil
  337. }
  338. }
  339. // expressionValues evaluates a slice of expressions and returns a []*cd.Variable
  340. // containing the results.
  341. // If the result of an expression evaluation refers to values from the program's
  342. // memory (e.g., the expression evaluates to a slice) a corresponding variable is
  343. // added to the value collector, to be read later.
  344. func expressionValues(expressions []string, prog debug.Program, vc *valuecollector.Collector) []*cd.Variable {
  345. evaluatedExpressions := make([]*cd.Variable, len(expressions))
  346. for i, exp := range expressions {
  347. ee := &cd.Variable{Name: exp}
  348. evaluatedExpressions[i] = ee
  349. if val, err := prog.Evaluate(exp); err != nil {
  350. ee.Status = errorStatusMessage(err.Error(), refersToBreakpointExpression)
  351. } else {
  352. vc.FillValue(val, ee)
  353. }
  354. }
  355. return evaluatedExpressions
  356. }
  357. // stackFrames returns a stack trace for the program. It passes references to
  358. // function parameters and local variables to the value collector, so it can read
  359. // their values later.
  360. func stackFrames(prog debug.Program, vc *valuecollector.Collector) ([]*cd.StackFrame, *cd.StatusMessage) {
  361. frames, err := prog.Frames(maxCapturedStackFrames)
  362. if err != nil {
  363. return nil, errorStatusMessage("Error getting stack: "+err.Error(), refersToUnspecified)
  364. }
  365. stackFrames := make([]*cd.StackFrame, len(frames))
  366. for i, f := range frames {
  367. frame := &cd.StackFrame{}
  368. frame.Function = f.Function
  369. for _, v := range f.Params {
  370. frame.Arguments = append(frame.Arguments, vc.AddVariable(debug.LocalVar(v)))
  371. }
  372. for _, v := range f.Vars {
  373. frame.Locals = append(frame.Locals, vc.AddVariable(v))
  374. }
  375. frame.Location = &cd.SourceLocation{
  376. Path: f.File,
  377. Line: int64(f.Line),
  378. }
  379. stackFrames[i] = frame
  380. }
  381. return stackFrames, nil
  382. }
  383. // errorStatusMessage returns a *cd.StatusMessage indicating an error,
  384. // with the given message and refersTo field.
  385. func errorStatusMessage(msg string, refersTo int) *cd.StatusMessage {
  386. return &cd.StatusMessage{
  387. Description: &cd.FormatMessage{Format: "$0", Parameters: []string{msg}},
  388. IsError: true,
  389. RefersTo: refersToString[refersTo],
  390. }
  391. }
  392. const (
  393. // RefersTo values for cd.StatusMessage.
  394. refersToUnspecified = iota
  395. refersToBreakpointCondition
  396. refersToBreakpointExpression
  397. )
  398. // refersToString contains the strings for each refersTo value.
  399. // See the definition of StatusMessage in the v2/clouddebugger package.
  400. var refersToString = map[int]string{
  401. refersToUnspecified: "UNSPECIFIED",
  402. refersToBreakpointCondition: "BREAKPOINT_CONDITION",
  403. refersToBreakpointExpression: "BREAKPOINT_EXPRESSION",
  404. }
  405. func serviceAcctTokenSource(ctx context.Context, filename string, scope ...string) (oauth2.TokenSource, error) {
  406. data, err := ioutil.ReadFile(filename)
  407. if err != nil {
  408. return nil, fmt.Errorf("cannot read service account file: %v", err)
  409. }
  410. cfg, err := google.JWTConfigFromJSON(data, scope...)
  411. if err != nil {
  412. return nil, fmt.Errorf("google.JWTConfigFromJSON: %v", err)
  413. }
  414. return cfg.TokenSource(ctx), nil
  415. }