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.
 
 
 

175 lines
6.3 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. // Package breakpoints handles breakpoint requests we get from the user through
  15. // the Debuglet Controller, and manages corresponding breakpoints set in the code.
  16. package breakpoints
  17. import (
  18. "log"
  19. "sync"
  20. "cloud.google.com/go/cmd/go-cloud-debug-agent/internal/debug"
  21. cd "google.golang.org/api/clouddebugger/v2"
  22. )
  23. // BreakpointStore stores the set of breakpoints for a program.
  24. type BreakpointStore struct {
  25. mu sync.Mutex
  26. // prog is the program being debugged.
  27. prog debug.Program
  28. // idToBreakpoint is a map from breakpoint identifier to *cd.Breakpoint. The
  29. // map value is nil if the breakpoint is inactive. A breakpoint is active if:
  30. // - We received it from the Debuglet Controller, and it was active at the time;
  31. // - We were able to set code breakpoints for it;
  32. // - We have not reached any of those code breakpoints while satisfying the
  33. // breakpoint's conditions, or the breakpoint has action LOG; and
  34. // - The Debuglet Controller hasn't informed us the breakpoint has become inactive.
  35. idToBreakpoint map[string]*cd.Breakpoint
  36. // pcToBps and bpToPCs store the many-to-many relationship between breakpoints we
  37. // received from the Debuglet Controller and the code breakpoints we set for them.
  38. pcToBps map[uint64][]*cd.Breakpoint
  39. bpToPCs map[*cd.Breakpoint][]uint64
  40. // errors contains any breakpoints which couldn't be set because they caused an
  41. // error. These are retrieved with ErrorBreakpoints, and the caller is
  42. // expected to handle sending updates for them.
  43. errors []*cd.Breakpoint
  44. }
  45. // NewBreakpointStore returns a BreakpointStore for the given program.
  46. func NewBreakpointStore(prog debug.Program) *BreakpointStore {
  47. return &BreakpointStore{
  48. idToBreakpoint: make(map[string]*cd.Breakpoint),
  49. pcToBps: make(map[uint64][]*cd.Breakpoint),
  50. bpToPCs: make(map[*cd.Breakpoint][]uint64),
  51. prog: prog,
  52. }
  53. }
  54. // ProcessBreakpointList applies updates received from the Debuglet Controller through a List call.
  55. func (bs *BreakpointStore) ProcessBreakpointList(bps []*cd.Breakpoint) {
  56. bs.mu.Lock()
  57. defer bs.mu.Unlock()
  58. for _, bp := range bps {
  59. if storedBp, ok := bs.idToBreakpoint[bp.Id]; ok {
  60. if storedBp != nil && bp.IsFinalState {
  61. // IsFinalState indicates that the breakpoint has been made inactive.
  62. bs.removeBreakpointLocked(storedBp)
  63. }
  64. } else {
  65. if bp.IsFinalState {
  66. // The controller is notifying us that the breakpoint is no longer active,
  67. // but we didn't know about it anyway.
  68. continue
  69. }
  70. if bp.Action != "" && bp.Action != "CAPTURE" && bp.Action != "LOG" {
  71. bp.IsFinalState = true
  72. bp.Status = &cd.StatusMessage{
  73. Description: &cd.FormatMessage{Format: "Action is not supported"},
  74. IsError: true,
  75. }
  76. bs.errors = append(bs.errors, bp)
  77. // Note in idToBreakpoint that we've already seen this breakpoint, so that we
  78. // don't try to report it as an error multiple times.
  79. bs.idToBreakpoint[bp.Id] = nil
  80. continue
  81. }
  82. pcs, err := bs.prog.BreakpointAtLine(bp.Location.Path, uint64(bp.Location.Line))
  83. if err != nil {
  84. log.Printf("error setting breakpoint at %s:%d: %v", bp.Location.Path, bp.Location.Line, err)
  85. }
  86. if len(pcs) == 0 {
  87. // We can't find a PC for this breakpoint's source line, so don't make it active.
  88. // TODO: we could snap the line to a location where we can break, or report an error to the user.
  89. bs.idToBreakpoint[bp.Id] = nil
  90. } else {
  91. bs.idToBreakpoint[bp.Id] = bp
  92. for _, pc := range pcs {
  93. bs.pcToBps[pc] = append(bs.pcToBps[pc], bp)
  94. }
  95. bs.bpToPCs[bp] = pcs
  96. }
  97. }
  98. }
  99. }
  100. // ErrorBreakpoints returns a slice of Breakpoints that caused errors when the
  101. // BreakpointStore tried to process them, and resets the list of such
  102. // breakpoints.
  103. // The caller is expected to send updates to the server to indicate the errors.
  104. func (bs *BreakpointStore) ErrorBreakpoints() []*cd.Breakpoint {
  105. bs.mu.Lock()
  106. defer bs.mu.Unlock()
  107. bps := bs.errors
  108. bs.errors = nil
  109. return bps
  110. }
  111. // BreakpointsAtPC returns all the breakpoints for which we set a code
  112. // breakpoint at the given address.
  113. func (bs *BreakpointStore) BreakpointsAtPC(pc uint64) []*cd.Breakpoint {
  114. bs.mu.Lock()
  115. defer bs.mu.Unlock()
  116. return bs.pcToBps[pc]
  117. }
  118. // RemoveBreakpoint makes the given breakpoint inactive.
  119. // This is called when either the debugged program hits the breakpoint, or the Debuglet
  120. // Controller informs us that the breakpoint is now inactive.
  121. func (bs *BreakpointStore) RemoveBreakpoint(bp *cd.Breakpoint) {
  122. bs.mu.Lock()
  123. bs.removeBreakpointLocked(bp)
  124. bs.mu.Unlock()
  125. }
  126. func (bs *BreakpointStore) removeBreakpointLocked(bp *cd.Breakpoint) {
  127. // Set the ID's corresponding breakpoint to nil, so that we won't activate it
  128. // if we see it again.
  129. // TODO: we could delete it after a few seconds.
  130. bs.idToBreakpoint[bp.Id] = nil
  131. // Delete bp from the list of cd breakpoints at each of its corresponding
  132. // code breakpoint locations, and delete any code breakpoints which no longer
  133. // have a corresponding cd breakpoint.
  134. var codeBreakpointsToDelete []uint64
  135. for _, pc := range bs.bpToPCs[bp] {
  136. bps := remove(bs.pcToBps[pc], bp)
  137. if len(bps) == 0 {
  138. // bp was the last breakpoint set at this PC, so delete the code breakpoint.
  139. codeBreakpointsToDelete = append(codeBreakpointsToDelete, pc)
  140. delete(bs.pcToBps, pc)
  141. } else {
  142. bs.pcToBps[pc] = bps
  143. }
  144. }
  145. if len(codeBreakpointsToDelete) > 0 {
  146. bs.prog.DeleteBreakpoints(codeBreakpointsToDelete)
  147. }
  148. delete(bs.bpToPCs, bp)
  149. }
  150. // remove updates rs by removing r, then returns rs.
  151. // The mutex in the BreakpointStore which contains rs should be held.
  152. func remove(rs []*cd.Breakpoint, r *cd.Breakpoint) []*cd.Breakpoint {
  153. for i := range rs {
  154. if rs[i] == r {
  155. rs[i] = rs[len(rs)-1]
  156. rs = rs[0 : len(rs)-1]
  157. return rs
  158. }
  159. }
  160. // We shouldn't reach here.
  161. return rs
  162. }