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