|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174 |
- // 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.
-
- // Package breakpoints handles breakpoint requests we get from the user through
- // the Debuglet Controller, and manages corresponding breakpoints set in the code.
- package breakpoints
-
- import (
- "log"
- "sync"
-
- "cloud.google.com/go/cmd/go-cloud-debug-agent/internal/debug"
- cd "google.golang.org/api/clouddebugger/v2"
- )
-
- // BreakpointStore stores the set of breakpoints for a program.
- type BreakpointStore struct {
- mu sync.Mutex
- // prog is the program being debugged.
- prog debug.Program
- // idToBreakpoint is a map from breakpoint identifier to *cd.Breakpoint. The
- // map value is nil if the breakpoint is inactive. A breakpoint is active if:
- // - We received it from the Debuglet Controller, and it was active at the time;
- // - We were able to set code breakpoints for it;
- // - We have not reached any of those code breakpoints while satisfying the
- // breakpoint's conditions, or the breakpoint has action LOG; and
- // - The Debuglet Controller hasn't informed us the breakpoint has become inactive.
- idToBreakpoint map[string]*cd.Breakpoint
- // pcToBps and bpToPCs store the many-to-many relationship between breakpoints we
- // received from the Debuglet Controller and the code breakpoints we set for them.
- pcToBps map[uint64][]*cd.Breakpoint
- bpToPCs map[*cd.Breakpoint][]uint64
- // errors contains any breakpoints which couldn't be set because they caused an
- // error. These are retrieved with ErrorBreakpoints, and the caller is
- // expected to handle sending updates for them.
- errors []*cd.Breakpoint
- }
-
- // NewBreakpointStore returns a BreakpointStore for the given program.
- func NewBreakpointStore(prog debug.Program) *BreakpointStore {
- return &BreakpointStore{
- idToBreakpoint: make(map[string]*cd.Breakpoint),
- pcToBps: make(map[uint64][]*cd.Breakpoint),
- bpToPCs: make(map[*cd.Breakpoint][]uint64),
- prog: prog,
- }
- }
-
- // ProcessBreakpointList applies updates received from the Debuglet Controller through a List call.
- func (bs *BreakpointStore) ProcessBreakpointList(bps []*cd.Breakpoint) {
- bs.mu.Lock()
- defer bs.mu.Unlock()
- for _, bp := range bps {
- if storedBp, ok := bs.idToBreakpoint[bp.Id]; ok {
- if storedBp != nil && bp.IsFinalState {
- // IsFinalState indicates that the breakpoint has been made inactive.
- bs.removeBreakpointLocked(storedBp)
- }
- } else {
- if bp.IsFinalState {
- // The controller is notifying us that the breakpoint is no longer active,
- // but we didn't know about it anyway.
- continue
- }
- if bp.Action != "" && bp.Action != "CAPTURE" && bp.Action != "LOG" {
- bp.IsFinalState = true
- bp.Status = &cd.StatusMessage{
- Description: &cd.FormatMessage{Format: "Action is not supported"},
- IsError: true,
- }
- bs.errors = append(bs.errors, bp)
- // Note in idToBreakpoint that we've already seen this breakpoint, so that we
- // don't try to report it as an error multiple times.
- bs.idToBreakpoint[bp.Id] = nil
- continue
- }
- pcs, err := bs.prog.BreakpointAtLine(bp.Location.Path, uint64(bp.Location.Line))
- if err != nil {
- log.Printf("error setting breakpoint at %s:%d: %v", bp.Location.Path, bp.Location.Line, err)
- }
- if len(pcs) == 0 {
- // We can't find a PC for this breakpoint's source line, so don't make it active.
- // TODO: we could snap the line to a location where we can break, or report an error to the user.
- bs.idToBreakpoint[bp.Id] = nil
- } else {
- bs.idToBreakpoint[bp.Id] = bp
- for _, pc := range pcs {
- bs.pcToBps[pc] = append(bs.pcToBps[pc], bp)
- }
- bs.bpToPCs[bp] = pcs
- }
- }
- }
- }
-
- // ErrorBreakpoints returns a slice of Breakpoints that caused errors when the
- // BreakpointStore tried to process them, and resets the list of such
- // breakpoints.
- // The caller is expected to send updates to the server to indicate the errors.
- func (bs *BreakpointStore) ErrorBreakpoints() []*cd.Breakpoint {
- bs.mu.Lock()
- defer bs.mu.Unlock()
- bps := bs.errors
- bs.errors = nil
- return bps
- }
-
- // BreakpointsAtPC returns all the breakpoints for which we set a code
- // breakpoint at the given address.
- func (bs *BreakpointStore) BreakpointsAtPC(pc uint64) []*cd.Breakpoint {
- bs.mu.Lock()
- defer bs.mu.Unlock()
- return bs.pcToBps[pc]
- }
-
- // RemoveBreakpoint makes the given breakpoint inactive.
- // This is called when either the debugged program hits the breakpoint, or the Debuglet
- // Controller informs us that the breakpoint is now inactive.
- func (bs *BreakpointStore) RemoveBreakpoint(bp *cd.Breakpoint) {
- bs.mu.Lock()
- bs.removeBreakpointLocked(bp)
- bs.mu.Unlock()
- }
-
- func (bs *BreakpointStore) removeBreakpointLocked(bp *cd.Breakpoint) {
- // Set the ID's corresponding breakpoint to nil, so that we won't activate it
- // if we see it again.
- // TODO: we could delete it after a few seconds.
- bs.idToBreakpoint[bp.Id] = nil
-
- // Delete bp from the list of cd breakpoints at each of its corresponding
- // code breakpoint locations, and delete any code breakpoints which no longer
- // have a corresponding cd breakpoint.
- var codeBreakpointsToDelete []uint64
- for _, pc := range bs.bpToPCs[bp] {
- bps := remove(bs.pcToBps[pc], bp)
- if len(bps) == 0 {
- // bp was the last breakpoint set at this PC, so delete the code breakpoint.
- codeBreakpointsToDelete = append(codeBreakpointsToDelete, pc)
- delete(bs.pcToBps, pc)
- } else {
- bs.pcToBps[pc] = bps
- }
- }
- if len(codeBreakpointsToDelete) > 0 {
- bs.prog.DeleteBreakpoints(codeBreakpointsToDelete)
- }
- delete(bs.bpToPCs, bp)
- }
-
- // remove updates rs by removing r, then returns rs.
- // The mutex in the BreakpointStore which contains rs should be held.
- func remove(rs []*cd.Breakpoint, r *cd.Breakpoint) []*cd.Breakpoint {
- for i := range rs {
- if rs[i] == r {
- rs[i] = rs[len(rs)-1]
- rs = rs[0 : len(rs)-1]
- return rs
- }
- }
- // We shouldn't reach here.
- return rs
- }
|