| // Copyright 2018 Google Inc. All Rights Reserved. |
| // |
| // 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 |
| |
| // Package server provides RPC access to a local program being debugged. |
| // It is the remote end of the client implementation of the Program interface. |
| package server |
| |
| //go:generate sh -c "m4 -P eval.m4 > eval.go" |
| |
| import ( |
| "bytes" |
| "errors" |
| "fmt" |
| "os" |
| "regexp" |
| "strconv" |
| "strings" |
| "sync" |
| "syscall" |
| |
| "cloud.google.com/go/cmd/go-cloud-debug-agent/internal/debug" |
| "cloud.google.com/go/cmd/go-cloud-debug-agent/internal/debug/arch" |
| "cloud.google.com/go/cmd/go-cloud-debug-agent/internal/debug/dwarf" |
| "cloud.google.com/go/cmd/go-cloud-debug-agent/internal/debug/elf" |
| "cloud.google.com/go/cmd/go-cloud-debug-agent/internal/debug/server/protocol" |
| ) |
| |
| type breakpoint struct { |
| pc uint64 |
| origInstr [arch.MaxBreakpointSize]byte |
| } |
| |
| type call struct { |
| req, resp interface{} |
| errc chan error |
| } |
| |
| type Server struct { |
| arch arch.Architecture |
| executable string // Name of executable. |
| dwarfData *dwarf.Data |
| |
| breakpointc chan call |
| otherc chan call |
| |
| fc chan func() error |
| ec chan error |
| |
| proc *os.Process |
| procIsUp bool |
| stoppedPid int |
| stoppedRegs syscall.PtraceRegs |
| topOfStackAddrs []uint64 |
| breakpoints map[uint64]breakpoint |
| files []*file // Index == file descriptor. |
| printer *Printer |
| |
| // goroutineStack reads the stack of a (non-running) goroutine. |
| goroutineStack func(uint64) ([]debug.Frame, error) |
| goroutineStackOnce sync.Once |
| } |
| |
| // peek implements the Peeker interface required by the printer. |
| func (s *Server) peek(offset uintptr, buf []byte) error { |
| return s.ptracePeek(s.stoppedPid, offset, buf) |
| } |
| |
| // New parses the executable and builds local data structures for answering requests. |
| // It returns a Server ready to serve requests about the executable. |
| func New(executable string) (*Server, error) { |
| fd, err := os.Open(executable) |
| if err != nil { |
| return nil, err |
| } |
| defer fd.Close() |
| architecture, dwarfData, err := loadExecutable(fd) |
| if err != nil { |
| return nil, err |
| } |
| srv := &Server{ |
| arch: *architecture, |
| executable: executable, |
| dwarfData: dwarfData, |
| breakpointc: make(chan call), |
| otherc: make(chan call), |
| fc: make(chan func() error), |
| ec: make(chan error), |
| breakpoints: make(map[uint64]breakpoint), |
| } |
| srv.printer = NewPrinter(architecture, dwarfData, srv) |
| go ptraceRun(srv.fc, srv.ec) |
| go srv.loop() |
| return srv, nil |
| } |
| |
| func loadExecutable(f *os.File) (*arch.Architecture, *dwarf.Data, error) { |
| // TODO: How do we detect NaCl? |
| if obj, err := elf.NewFile(f); err == nil { |
| dwarfData, err := obj.DWARF() |
| if err != nil { |
| return nil, nil, err |
| } |
| |
| switch obj.Machine { |
| case elf.EM_ARM: |
| return &arch.ARM, dwarfData, nil |
| case elf.EM_386: |
| switch obj.Class { |
| case elf.ELFCLASS32: |
| return &arch.X86, dwarfData, nil |
| case elf.ELFCLASS64: |
| return &arch.AMD64, dwarfData, nil |
| } |
| case elf.EM_X86_64: |
| return &arch.AMD64, dwarfData, nil |
| } |
| return nil, nil, fmt.Errorf("unrecognized ELF architecture") |
| } |
| return nil, nil, fmt.Errorf("unrecognized binary format") |
| } |
| |
| func (s *Server) loop() { |
| for { |
| var c call |
| select { |
| case c = <-s.breakpointc: |
| case c = <-s.otherc: |
| } |
| s.dispatch(c) |
| } |
| } |
| |
| func (s *Server) dispatch(c call) { |
| switch req := c.req.(type) { |
| case *protocol.BreakpointRequest: |
| c.errc <- s.handleBreakpoint(req, c.resp.(*protocol.BreakpointResponse)) |
| case *protocol.BreakpointAtFunctionRequest: |
| c.errc <- s.handleBreakpointAtFunction(req, c.resp.(*protocol.BreakpointResponse)) |
| case *protocol.BreakpointAtLineRequest: |
| c.errc <- s.handleBreakpointAtLine(req, c.resp.(*protocol.BreakpointResponse)) |
| case *protocol.DeleteBreakpointsRequest: |
| c.errc <- s.handleDeleteBreakpoints(req, c.resp.(*protocol.DeleteBreakpointsResponse)) |
| case *protocol.CloseRequest: |
| c.errc <- s.handleClose(req, c.resp.(*protocol.CloseResponse)) |
| case *protocol.EvalRequest: |
| c.errc <- s.handleEval(req, c.resp.(*protocol.EvalResponse)) |
| case *protocol.EvaluateRequest: |
| c.errc <- s.handleEvaluate(req, c.resp.(*protocol.EvaluateResponse)) |
| case *protocol.FramesRequest: |
| c.errc <- s.handleFrames(req, c.resp.(*protocol.FramesResponse)) |
| case *protocol.OpenRequest: |
| c.errc <- s.handleOpen(req, c.resp.(*protocol.OpenResponse)) |
| case *protocol.ReadAtRequest: |
| c.errc <- s.handleReadAt(req, c.resp.(*protocol.ReadAtResponse)) |
| case *protocol.ResumeRequest: |
| c.errc <- s.handleResume(req, c.resp.(*protocol.ResumeResponse)) |
| case *protocol.RunRequest: |
| c.errc <- s.handleRun(req, c.resp.(*protocol.RunResponse)) |
| case *protocol.VarByNameRequest: |
| c.errc <- s.handleVarByName(req, c.resp.(*protocol.VarByNameResponse)) |
| case *protocol.ValueRequest: |
| c.errc <- s.handleValue(req, c.resp.(*protocol.ValueResponse)) |
| case *protocol.MapElementRequest: |
| c.errc <- s.handleMapElement(req, c.resp.(*protocol.MapElementResponse)) |
| case *protocol.GoroutinesRequest: |
| c.errc <- s.handleGoroutines(req, c.resp.(*protocol.GoroutinesResponse)) |
| default: |
| panic(fmt.Sprintf("unexpected call request type %T", c.req)) |
| } |
| } |
| |
| func (s *Server) call(c chan call, req, resp interface{}) error { |
| errc := make(chan error) |
| c <- call{req, resp, errc} |
| return <-errc |
| } |
| |
| type file struct { |
| mode string |
| index int |
| f debug.File |
| } |
| |
| func (s *Server) Open(req *protocol.OpenRequest, resp *protocol.OpenResponse) error { |
| return s.call(s.otherc, req, resp) |
| } |
| |
| func (s *Server) handleOpen(req *protocol.OpenRequest, resp *protocol.OpenResponse) error { |
| // TODO: Better simulation. For now we just open the named OS file. |
| var flag int |
| switch req.Mode { |
| case "r": |
| flag = os.O_RDONLY |
| case "w": |
| flag = os.O_WRONLY |
| case "rw": |
| flag = os.O_RDWR |
| default: |
| return fmt.Errorf("Open: bad open mode %q", req.Mode) |
| } |
| osFile, err := os.OpenFile(req.Name, flag, 0) |
| if err != nil { |
| return err |
| } |
| // Find a file descriptor (index) slot. |
| index := 0 |
| for ; index < len(s.files) && s.files[index] != nil; index++ { |
| } |
| f := &file{ |
| mode: req.Mode, |
| index: index, |
| f: osFile, |
| } |
| if index == len(s.files) { |
| s.files = append(s.files, f) |
| } else { |
| s.files[index] = f |
| } |
| return nil |
| } |
| |
| func (s *Server) ReadAt(req *protocol.ReadAtRequest, resp *protocol.ReadAtResponse) error { |
| return s.call(s.otherc, req, resp) |
| } |
| |
| func (s *Server) handleReadAt(req *protocol.ReadAtRequest, resp *protocol.ReadAtResponse) error { |
| fd := req.FD |
| if fd < 0 || len(s.files) <= fd || s.files[fd] == nil { |
| return fmt.Errorf("ReadAt: bad file descriptor %d", fd) |
| } |
| f := s.files[fd] |
| buf := make([]byte, req.Len) // TODO: Don't allocate every time |
| n, err := f.f.ReadAt(buf, req.Offset) |
| resp.Data = buf[:n] |
| return err |
| } |
| |
| func (s *Server) Close(req *protocol.CloseRequest, resp *protocol.CloseResponse) error { |
| return s.call(s.otherc, req, resp) |
| } |
| |
| func (s *Server) handleClose(req *protocol.CloseRequest, resp *protocol.CloseResponse) error { |
| fd := req.FD |
| if fd < 0 || fd >= len(s.files) || s.files[fd] == nil { |
| return fmt.Errorf("Close: bad file descriptor %d", fd) |
| } |
| err := s.files[fd].f.Close() |
| // Remove it regardless |
| s.files[fd] = nil |
| return err |
| } |
| |
| func (s *Server) Run(req *protocol.RunRequest, resp *protocol.RunResponse) error { |
| return s.call(s.otherc, req, resp) |
| } |
| |
| func (s *Server) handleRun(req *protocol.RunRequest, resp *protocol.RunResponse) error { |
| if s.proc != nil { |
| s.proc.Kill() |
| s.proc = nil |
| s.procIsUp = false |
| s.stoppedPid = 0 |
| s.stoppedRegs = syscall.PtraceRegs{} |
| s.topOfStackAddrs = nil |
| } |
| argv := append([]string{s.executable}, req.Args...) |
| p, err := s.startProcess(s.executable, argv, &os.ProcAttr{ |
| Files: []*os.File{ |
| nil, // TODO: be able to feed the target's stdin. |
| os.Stderr, // TODO: be able to capture the target's stdout. |
| os.Stderr, |
| }, |
| Sys: &syscall.SysProcAttr{ |
| Pdeathsig: syscall.SIGKILL, |
| Ptrace: true, |
| }, |
| }) |
| if err != nil { |
| return err |
| } |
| s.proc = p |
| s.stoppedPid = p.Pid |
| return nil |
| } |
| |
| func (s *Server) Resume(req *protocol.ResumeRequest, resp *protocol.ResumeResponse) error { |
| return s.call(s.otherc, req, resp) |
| } |
| |
| func (s *Server) handleResume(req *protocol.ResumeRequest, resp *protocol.ResumeResponse) error { |
| if s.proc == nil { |
| return fmt.Errorf("Resume: Run did not successfully start a process") |
| } |
| |
| if !s.procIsUp { |
| s.procIsUp = true |
| if _, err := s.waitForTrap(s.stoppedPid, false); err != nil { |
| return err |
| } |
| if err := s.ptraceSetOptions(s.stoppedPid, syscall.PTRACE_O_TRACECLONE); err != nil { |
| return fmt.Errorf("ptraceSetOptions: %v", err) |
| } |
| } else if _, ok := s.breakpoints[s.stoppedRegs.Rip]; ok { |
| if err := s.ptraceSingleStep(s.stoppedPid); err != nil { |
| return fmt.Errorf("ptraceSingleStep: %v", err) |
| } |
| if _, err := s.waitForTrap(s.stoppedPid, false); err != nil { |
| return err |
| } |
| } |
| |
| for { |
| if err := s.setBreakpoints(); err != nil { |
| return err |
| } |
| if err := s.ptraceCont(s.stoppedPid, 0); err != nil { |
| return fmt.Errorf("ptraceCont: %v", err) |
| } |
| |
| wpid, err := s.waitForTrap(-1, true) |
| if err == nil { |
| s.stoppedPid = wpid |
| break |
| } |
| bce, ok := err.(*breakpointsChangedError) |
| if !ok { |
| return err |
| } |
| |
| if err := syscall.Kill(s.stoppedPid, syscall.SIGSTOP); err != nil { |
| return fmt.Errorf("kill(SIGSTOP): %v", err) |
| } |
| _, status, err := s.wait(s.stoppedPid, false) |
| if err != nil { |
| return fmt.Errorf("wait (after SIGSTOP): %v", err) |
| } |
| if !status.Stopped() || status.StopSignal() != syscall.SIGSTOP { |
| return fmt.Errorf("wait (after SIGSTOP): unexpected wait status 0x%x", status) |
| } |
| |
| if err := s.liftBreakpoints(); err != nil { |
| return err |
| } |
| |
| loop: |
| for c := bce.call; ; { |
| s.dispatch(c) |
| select { |
| case c = <-s.breakpointc: |
| default: |
| break loop |
| } |
| } |
| } |
| if err := s.liftBreakpoints(); err != nil { |
| return err |
| } |
| |
| if err := s.ptraceGetRegs(s.stoppedPid, &s.stoppedRegs); err != nil { |
| return fmt.Errorf("ptraceGetRegs: %v", err) |
| } |
| |
| s.stoppedRegs.Rip -= uint64(s.arch.BreakpointSize) |
| |
| if err := s.ptraceSetRegs(s.stoppedPid, &s.stoppedRegs); err != nil { |
| return fmt.Errorf("ptraceSetRegs: %v", err) |
| } |
| |
| resp.Status.PC = s.stoppedRegs.Rip |
| resp.Status.SP = s.stoppedRegs.Rsp |
| return nil |
| } |
| |
| func (s *Server) waitForTrap(pid int, allowBreakpointsChange bool) (wpid int, err error) { |
| for { |
| wpid, status, err := s.wait(pid, allowBreakpointsChange) |
| if err != nil { |
| if _, ok := err.(*breakpointsChangedError); !ok { |
| err = fmt.Errorf("wait: %v", err) |
| } |
| return 0, err |
| } |
| if status.StopSignal() == syscall.SIGTRAP && status.TrapCause() != syscall.PTRACE_EVENT_CLONE { |
| return wpid, nil |
| } |
| if status.StopSignal() == syscall.SIGPROF { |
| err = s.ptraceCont(wpid, int(syscall.SIGPROF)) |
| } else { |
| err = s.ptraceCont(wpid, 0) // TODO: non-zero when wait catches other signals? |
| } |
| if err != nil { |
| return 0, fmt.Errorf("ptraceCont: %v", err) |
| } |
| } |
| } |
| |
| func (s *Server) Breakpoint(req *protocol.BreakpointRequest, resp *protocol.BreakpointResponse) error { |
| return s.call(s.breakpointc, req, resp) |
| } |
| |
| func (s *Server) handleBreakpoint(req *protocol.BreakpointRequest, resp *protocol.BreakpointResponse) error { |
| return s.addBreakpoints([]uint64{req.Address}, resp) |
| } |
| |
| func (s *Server) BreakpointAtFunction(req *protocol.BreakpointAtFunctionRequest, resp *protocol.BreakpointResponse) error { |
| return s.call(s.breakpointc, req, resp) |
| } |
| |
| func (s *Server) handleBreakpointAtFunction(req *protocol.BreakpointAtFunctionRequest, resp *protocol.BreakpointResponse) error { |
| pc, err := s.functionStartAddress(req.Function) |
| if err != nil { |
| return err |
| } |
| return s.addBreakpoints([]uint64{pc}, resp) |
| } |
| |
| func (s *Server) BreakpointAtLine(req *protocol.BreakpointAtLineRequest, resp *protocol.BreakpointResponse) error { |
| return s.call(s.breakpointc, req, resp) |
| } |
| |
| func (s *Server) handleBreakpointAtLine(req *protocol.BreakpointAtLineRequest, resp *protocol.BreakpointResponse) error { |
| if s.dwarfData == nil { |
| return fmt.Errorf("no DWARF data") |
| } |
| if pcs, err := s.dwarfData.LineToBreakpointPCs(req.File, req.Line); err != nil { |
| return err |
| } else { |
| return s.addBreakpoints(pcs, resp) |
| } |
| } |
| |
| // addBreakpoints adds breakpoints at the addresses in pcs, then stores pcs in the response. |
| func (s *Server) addBreakpoints(pcs []uint64, resp *protocol.BreakpointResponse) error { |
| // Get the original code at each address with ptracePeek. |
| bps := make([]breakpoint, 0, len(pcs)) |
| for _, pc := range pcs { |
| if _, alreadySet := s.breakpoints[pc]; alreadySet { |
| continue |
| } |
| var bp breakpoint |
| if err := s.ptracePeek(s.stoppedPid, uintptr(pc), bp.origInstr[:s.arch.BreakpointSize]); err != nil { |
| return fmt.Errorf("ptracePeek: %v", err) |
| } |
| bp.pc = pc |
| bps = append(bps, bp) |
| } |
| // If all the peeks succeeded, update the list of breakpoints. |
| for _, bp := range bps { |
| s.breakpoints[bp.pc] = bp |
| } |
| resp.PCs = pcs |
| return nil |
| } |
| |
| func (s *Server) DeleteBreakpoints(req *protocol.DeleteBreakpointsRequest, resp *protocol.DeleteBreakpointsResponse) error { |
| return s.call(s.breakpointc, req, resp) |
| } |
| |
| func (s *Server) handleDeleteBreakpoints(req *protocol.DeleteBreakpointsRequest, resp *protocol.DeleteBreakpointsResponse) error { |
| for _, pc := range req.PCs { |
| delete(s.breakpoints, pc) |
| } |
| return nil |
| } |
| |
| func (s *Server) setBreakpoints() error { |
| for pc := range s.breakpoints { |
| err := s.ptracePoke(s.stoppedPid, uintptr(pc), s.arch.BreakpointInstr[:s.arch.BreakpointSize]) |
| if err != nil { |
| return fmt.Errorf("setBreakpoints: %v", err) |
| } |
| } |
| return nil |
| } |
| |
| func (s *Server) liftBreakpoints() error { |
| for pc, breakpoint := range s.breakpoints { |
| err := s.ptracePoke(s.stoppedPid, uintptr(pc), breakpoint.origInstr[:s.arch.BreakpointSize]) |
| if err != nil { |
| return fmt.Errorf("liftBreakpoints: %v", err) |
| } |
| } |
| return nil |
| } |
| |
| func (s *Server) Eval(req *protocol.EvalRequest, resp *protocol.EvalResponse) error { |
| return s.call(s.otherc, req, resp) |
| } |
| |
| func (s *Server) handleEval(req *protocol.EvalRequest, resp *protocol.EvalResponse) (err error) { |
| resp.Result, err = s.eval(req.Expr) |
| return err |
| } |
| |
| // eval evaluates an expression. |
| // TODO: very weak. |
| func (s *Server) eval(expr string) ([]string, error) { |
| switch { |
| case strings.HasPrefix(expr, "re:"): |
| // Regular expression. Return list of symbols. |
| re, err := regexp.Compile(expr[3:]) |
| if err != nil { |
| return nil, err |
| } |
| return s.dwarfData.LookupMatchingSymbols(re) |
| |
| case strings.HasPrefix(expr, "addr:"): |
| // Symbol lookup. Return address. |
| addr, err := s.functionStartAddress(expr[5:]) |
| if err != nil { |
| return nil, err |
| } |
| return []string{fmt.Sprintf("%#x", addr)}, nil |
| |
| case strings.HasPrefix(expr, "val:"): |
| // Symbol lookup. Return formatted value. |
| value, err := s.printer.Sprint(expr[4:]) |
| if err != nil { |
| return nil, err |
| } |
| return []string{value}, nil |
| |
| case strings.HasPrefix(expr, "src:"): |
| // Numerical address. Return file.go:123. |
| addr, err := strconv.ParseUint(expr[4:], 0, 0) |
| if err != nil { |
| return nil, err |
| } |
| file, line, err := s.lookupSource(addr) |
| if err != nil { |
| return nil, err |
| } |
| return []string{fmt.Sprintf("%s:%d", file, line)}, nil |
| |
| case len(expr) > 0 && '0' <= expr[0] && expr[0] <= '9': |
| // Numerical address. Return symbol. |
| addr, err := strconv.ParseUint(expr, 0, 0) |
| if err != nil { |
| return nil, err |
| } |
| entry, _, err := s.dwarfData.PCToFunction(addr) |
| if err != nil { |
| return nil, err |
| } |
| name, ok := entry.Val(dwarf.AttrName).(string) |
| if !ok { |
| return nil, fmt.Errorf("function at 0x%x has no name", addr) |
| } |
| return []string{name}, nil |
| } |
| |
| return nil, fmt.Errorf("bad expression syntax: %q", expr) |
| } |
| |
| func (s *Server) Evaluate(req *protocol.EvaluateRequest, resp *protocol.EvaluateResponse) error { |
| return s.call(s.otherc, req, resp) |
| } |
| |
| func (s *Server) handleEvaluate(req *protocol.EvaluateRequest, resp *protocol.EvaluateResponse) (err error) { |
| resp.Result, err = s.evalExpression(req.Expression, s.stoppedRegs.Rip, s.stoppedRegs.Rsp) |
| return err |
| } |
| |
| func (s *Server) lookupSource(pc uint64) (file string, line uint64, err error) { |
| if s.dwarfData == nil { |
| return |
| } |
| // TODO: The gosym equivalent also returns the relevant Func. Do that when |
| // DWARF has the same facility. |
| return s.dwarfData.PCToLine(pc) |
| } |
| |
| func (s *Server) Frames(req *protocol.FramesRequest, resp *protocol.FramesResponse) error { |
| return s.call(s.otherc, req, resp) |
| } |
| |
| func (s *Server) handleFrames(req *protocol.FramesRequest, resp *protocol.FramesResponse) error { |
| // TODO: verify that we're stopped. |
| if s.topOfStackAddrs == nil { |
| if err := s.evaluateTopOfStackAddrs(); err != nil { |
| return err |
| } |
| } |
| |
| regs := syscall.PtraceRegs{} |
| err := s.ptraceGetRegs(s.stoppedPid, ®s) |
| if err != nil { |
| return err |
| } |
| resp.Frames, err = s.walkStack(regs.Rip, regs.Rsp, req.Count) |
| return err |
| } |
| |
| // walkStack returns up to the requested number of stack frames. |
| func (s *Server) walkStack(pc, sp uint64, count int) ([]debug.Frame, error) { |
| var frames []debug.Frame |
| |
| var buf [8]byte |
| b := new(bytes.Buffer) |
| r := s.dwarfData.Reader() |
| |
| // TODO: handle walking over a split stack. |
| for i := 0; i < count; i++ { |
| b.Reset() |
| file, line, err := s.dwarfData.PCToLine(pc) |
| if err != nil { |
| return frames, err |
| } |
| fpOffset, err := s.dwarfData.PCToSPOffset(pc) |
| if err != nil { |
| return frames, err |
| } |
| fp := sp + uint64(fpOffset) |
| entry, funcEntry, err := s.dwarfData.PCToFunction(pc) |
| if err != nil { |
| return frames, err |
| } |
| frame := debug.Frame{ |
| PC: pc, |
| SP: sp, |
| File: file, |
| Line: line, |
| FunctionStart: funcEntry, |
| } |
| frame.Function, _ = entry.Val(dwarf.AttrName).(string) |
| r.Seek(entry.Offset) |
| for { |
| entry, err := r.Next() |
| if err != nil { |
| return frames, err |
| } |
| if entry.Tag == 0 { |
| break |
| } |
| // TODO: report variables we couldn't parse? |
| if entry.Tag == dwarf.TagFormalParameter { |
| if v, err := s.parseParameterOrLocal(entry, fp); err == nil { |
| frame.Params = append(frame.Params, debug.Param(v)) |
| } |
| } |
| if entry.Tag == dwarf.TagVariable { |
| if v, err := s.parseParameterOrLocal(entry, fp); err == nil { |
| frame.Vars = append(frame.Vars, v) |
| } |
| } |
| } |
| frames = append(frames, frame) |
| |
| // Walk to the caller's PC and SP. |
| if s.topOfStack(funcEntry) { |
| break |
| } |
| err = s.ptracePeek(s.stoppedPid, uintptr(fp-uint64(s.arch.PointerSize)), buf[:s.arch.PointerSize]) |
| if err != nil { |
| return frames, fmt.Errorf("ptracePeek: %v", err) |
| } |
| pc, sp = s.arch.Uintptr(buf[:s.arch.PointerSize]), fp |
| } |
| return frames, nil |
| } |
| |
| // parseParameterOrLocal parses the entry for a function parameter or local |
| // variable, which are both specified the same way. fp contains the frame |
| // pointer, which is used to calculate the variable location. |
| func (s *Server) parseParameterOrLocal(entry *dwarf.Entry, fp uint64) (debug.LocalVar, error) { |
| var v debug.LocalVar |
| v.Name, _ = entry.Val(dwarf.AttrName).(string) |
| if off, err := s.dwarfData.EntryTypeOffset(entry); err != nil { |
| return v, err |
| } else { |
| v.Var.TypeID = uint64(off) |
| } |
| if i := entry.Val(dwarf.AttrLocation); i == nil { |
| return v, fmt.Errorf("missing location description") |
| } else if locationDescription, ok := i.([]uint8); !ok { |
| return v, fmt.Errorf("unsupported location description") |
| } else if offset, err := evalLocation(locationDescription); err != nil { |
| return v, err |
| } else { |
| v.Var.Address = fp + uint64(offset) |
| } |
| return v, nil |
| } |
| |
| func (s *Server) evaluateTopOfStackAddrs() error { |
| var ( |
| lookup func(name string) (uint64, error) |
| indirect bool |
| names []string |
| ) |
| if _, err := s.dwarfData.LookupVariable("runtime.rt0_goPC"); err != nil { |
| // Look for a Go 1.3 binary (or earlier version). |
| lookup, indirect, names = s.functionStartAddress, false, []string{ |
| "runtime.goexit", |
| "runtime.mstart", |
| "runtime.mcall", |
| "runtime.morestack", |
| "runtime.lessstack", |
| "_rt0_go", |
| } |
| } else { |
| // Look for a Go 1.4 binary (or later version). |
| lookup = func(name string) (uint64, error) { |
| entry, err := s.dwarfData.LookupVariable(name) |
| if err != nil { |
| return 0, err |
| } |
| return s.dwarfData.EntryLocation(entry) |
| } |
| indirect, names = true, []string{ |
| "runtime.goexitPC", |
| "runtime.mstartPC", |
| "runtime.mcallPC", |
| "runtime.morestackPC", |
| "runtime.rt0_goPC", |
| } |
| } |
| // TODO: also look for runtime.externalthreadhandlerp, on Windows. |
| |
| addrs := make([]uint64, 0, len(names)) |
| for _, name := range names { |
| addr, err := lookup(name) |
| if err != nil { |
| return err |
| } |
| addrs = append(addrs, addr) |
| } |
| |
| if indirect { |
| buf := make([]byte, s.arch.PointerSize) |
| for i, addr := range addrs { |
| if err := s.ptracePeek(s.stoppedPid, uintptr(addr), buf); err != nil { |
| return fmt.Errorf("ptracePeek: %v", err) |
| } |
| addrs[i] = s.arch.Uintptr(buf) |
| } |
| } |
| |
| s.topOfStackAddrs = addrs |
| return nil |
| } |
| |
| // topOfStack is the out-of-process equivalent of runtime·topofstack. |
| func (s *Server) topOfStack(funcEntry uint64) bool { |
| for _, addr := range s.topOfStackAddrs { |
| if addr == funcEntry { |
| return true |
| } |
| } |
| return false |
| } |
| |
| func (s *Server) VarByName(req *protocol.VarByNameRequest, resp *protocol.VarByNameResponse) error { |
| return s.call(s.otherc, req, resp) |
| } |
| |
| func (s *Server) handleVarByName(req *protocol.VarByNameRequest, resp *protocol.VarByNameResponse) error { |
| entry, err := s.dwarfData.LookupVariable(req.Name) |
| if err != nil { |
| return fmt.Errorf("variable %s: %s", req.Name, err) |
| } |
| |
| loc, err := s.dwarfData.EntryLocation(entry) |
| if err != nil { |
| return fmt.Errorf("variable %s: %s", req.Name, err) |
| } |
| |
| off, err := s.dwarfData.EntryTypeOffset(entry) |
| if err != nil { |
| return fmt.Errorf("variable %s: %s", req.Name, err) |
| } |
| |
| resp.Var.TypeID = uint64(off) |
| resp.Var.Address = loc |
| return nil |
| } |
| |
| func (s *Server) Value(req *protocol.ValueRequest, resp *protocol.ValueResponse) error { |
| return s.call(s.otherc, req, resp) |
| } |
| |
| func (s *Server) handleValue(req *protocol.ValueRequest, resp *protocol.ValueResponse) error { |
| t, err := s.dwarfData.Type(dwarf.Offset(req.Var.TypeID)) |
| if err != nil { |
| return err |
| } |
| resp.Value, err = s.value(t, req.Var.Address) |
| return err |
| } |
| |
| func (s *Server) MapElement(req *protocol.MapElementRequest, resp *protocol.MapElementResponse) error { |
| return s.call(s.otherc, req, resp) |
| } |
| |
| func (s *Server) handleMapElement(req *protocol.MapElementRequest, resp *protocol.MapElementResponse) error { |
| t, err := s.dwarfData.Type(dwarf.Offset(req.Map.TypeID)) |
| if err != nil { |
| return err |
| } |
| m, ok := t.(*dwarf.MapType) |
| if !ok { |
| return fmt.Errorf("variable is not a map") |
| } |
| var count uint64 |
| // fn will be called for each element of the map. |
| // When we reach the requested element, we fill in *resp and stop. |
| // TODO: cache locations of elements. |
| fn := func(keyAddr, valAddr uint64, keyType, valType dwarf.Type) bool { |
| count++ |
| if count == req.Index+1 { |
| resp.Key = debug.Var{TypeID: uint64(keyType.Common().Offset), Address: keyAddr} |
| resp.Value = debug.Var{TypeID: uint64(valType.Common().Offset), Address: valAddr} |
| return false |
| } |
| return true |
| } |
| if err := s.peekMapValues(m, req.Map.Address, fn); err != nil { |
| return err |
| } |
| if count <= req.Index { |
| // There weren't enough elements. |
| return fmt.Errorf("map has no element %d", req.Index) |
| } |
| return nil |
| } |
| |
| func (s *Server) Goroutines(req *protocol.GoroutinesRequest, resp *protocol.GoroutinesResponse) error { |
| return s.call(s.otherc, req, resp) |
| } |
| |
| const invalidStatus debug.GoroutineStatus = 99 |
| |
| var ( |
| gStatus = [...]debug.GoroutineStatus{ |
| 0: debug.Queued, // _Gidle |
| 1: debug.Queued, // _Grunnable |
| 2: debug.Running, // _Grunning |
| 3: debug.Blocked, // _Gsyscall |
| 4: debug.Blocked, // _Gwaiting |
| 5: invalidStatus, // _Gmoribund_unused |
| 6: invalidStatus, // _Gdead |
| 7: invalidStatus, // _Genqueue |
| 8: debug.Running, // _Gcopystack |
| } |
| gScanStatus = [...]debug.GoroutineStatus{ |
| 0: invalidStatus, // _Gscan + _Gidle |
| 1: debug.Queued, // _Gscanrunnable |
| 2: debug.Running, // _Gscanrunning |
| 3: debug.Blocked, // _Gscansyscall |
| 4: debug.Blocked, // _Gscanwaiting |
| 5: invalidStatus, // _Gscan + _Gmoribund_unused |
| 6: invalidStatus, // _Gscan + _Gdead |
| 7: debug.Queued, // _Gscanenqueue |
| } |
| gStatusString = [...]string{ |
| 0: "idle", |
| 1: "runnable", |
| 2: "running", |
| 3: "syscall", |
| 4: "waiting", |
| 8: "copystack", |
| } |
| gScanStatusString = [...]string{ |
| 1: "scanrunnable", |
| 2: "scanrunning", |
| 3: "scansyscall", |
| 4: "scanwaiting", |
| 7: "scanenqueue", |
| } |
| ) |
| |
| func (s *Server) handleGoroutines(req *protocol.GoroutinesRequest, resp *protocol.GoroutinesResponse) error { |
| // Get DWARF type information for runtime.g. |
| ge, err := s.dwarfData.LookupEntry("runtime.g") |
| if err != nil { |
| return err |
| } |
| t, err := s.dwarfData.Type(ge.Offset) |
| if err != nil { |
| return err |
| } |
| gType, ok := followTypedefs(t).(*dwarf.StructType) |
| if !ok { |
| return errors.New("runtime.g is not a struct") |
| } |
| |
| var ( |
| allgPtr, allgLen uint64 |
| allgPtrOk bool |
| ) |
| for { |
| // Try to read the slice runtime.allgs. |
| allgsEntry, err := s.dwarfData.LookupVariable("runtime.allgs") |
| if err != nil { |
| break |
| } |
| allgsAddr, err := s.dwarfData.EntryLocation(allgsEntry) |
| if err != nil { |
| break |
| } |
| off, err := s.dwarfData.EntryTypeOffset(allgsEntry) |
| if err != nil { |
| break |
| } |
| t, err := s.dwarfData.Type(off) |
| if err != nil { |
| break |
| } |
| allgsType, ok := followTypedefs(t).(*dwarf.SliceType) |
| if !ok { |
| break |
| } |
| allgs, err := s.peekSlice(allgsType, allgsAddr) |
| if err != nil { |
| break |
| } |
| |
| allgPtr, allgLen, allgPtrOk = allgs.Address, allgs.Length, true |
| break |
| } |
| if !allgPtrOk { |
| // Read runtime.allg. |
| allgEntry, err := s.dwarfData.LookupVariable("runtime.allg") |
| if err != nil { |
| return err |
| } |
| allgAddr, err := s.dwarfData.EntryLocation(allgEntry) |
| if err != nil { |
| return err |
| } |
| allgPtr, err = s.peekPtr(allgAddr) |
| if err != nil { |
| return fmt.Errorf("reading allg: %v", err) |
| } |
| |
| // Read runtime.allglen. |
| allglenEntry, err := s.dwarfData.LookupVariable("runtime.allglen") |
| if err != nil { |
| return err |
| } |
| off, err := s.dwarfData.EntryTypeOffset(allglenEntry) |
| if err != nil { |
| return err |
| } |
| allglenType, err := s.dwarfData.Type(off) |
| if err != nil { |
| return err |
| } |
| allglenAddr, err := s.dwarfData.EntryLocation(allglenEntry) |
| if err != nil { |
| return err |
| } |
| switch followTypedefs(allglenType).(type) { |
| case *dwarf.UintType, *dwarf.IntType: |
| allgLen, err = s.peekUint(allglenAddr, allglenType.Common().ByteSize) |
| if err != nil { |
| return fmt.Errorf("reading allglen: %v", err) |
| } |
| default: |
| // Some runtimes don't specify the type for allglen. Assume it's uint32. |
| allgLen, err = s.peekUint(allglenAddr, 4) |
| if err != nil { |
| return fmt.Errorf("reading allglen: %v", err) |
| } |
| if allgLen != 0 { |
| break |
| } |
| // Zero? Let's try uint64. |
| allgLen, err = s.peekUint(allglenAddr, 8) |
| if err != nil { |
| return fmt.Errorf("reading allglen: %v", err) |
| } |
| } |
| } |
| |
| // Initialize s.goroutineStack. |
| s.goroutineStackOnce.Do(func() { s.goroutineStackInit(gType) }) |
| |
| for i := uint64(0); i < allgLen; i++ { |
| // allg is an array of pointers to g structs. Read allg[i]. |
| g, err := s.peekPtr(allgPtr + i*uint64(s.arch.PointerSize)) |
| if err != nil { |
| return err |
| } |
| gr := debug.Goroutine{} |
| |
| // Read status from the field named "atomicstatus" or "status". |
| status, err := s.peekUintStructField(gType, g, "atomicstatus") |
| if err != nil { |
| status, err = s.peekUintOrIntStructField(gType, g, "status") |
| } |
| if err != nil { |
| return err |
| } |
| if status == 6 { |
| // _Gdead. |
| continue |
| } |
| gr.Status = invalidStatus |
| if status < uint64(len(gStatus)) { |
| gr.Status = gStatus[status] |
| gr.StatusString = gStatusString[status] |
| } else if status^0x1000 < uint64(len(gScanStatus)) { |
| gr.Status = gScanStatus[status^0x1000] |
| gr.StatusString = gScanStatusString[status^0x1000] |
| } |
| if gr.Status == invalidStatus { |
| return fmt.Errorf("unexpected goroutine status 0x%x", status) |
| } |
| if status == 4 || status == 0x1004 { |
| // _Gwaiting or _Gscanwaiting. |
| // Try reading waitreason to get a better value for StatusString. |
| // Depending on the runtime, waitreason may be a Go string or a C string. |
| if waitreason, err := s.peekStringStructField(gType, g, "waitreason", 80); err == nil { |
| if waitreason != "" { |
| gr.StatusString = waitreason |
| } |
| } else if ptr, err := s.peekPtrStructField(gType, g, "waitreason"); err == nil { |
| waitreason := s.peekCString(ptr, 80) |
| if waitreason != "" { |
| gr.StatusString = waitreason |
| } |
| } |
| } |
| |
| gr.ID, err = s.peekIntStructField(gType, g, "goid") |
| if err != nil { |
| return err |
| } |
| |
| // Best-effort attempt to get the names of the goroutine function and the |
| // function that created the goroutine. They aren't always available. |
| functionName := func(pc uint64) string { |
| entry, _, err := s.dwarfData.PCToFunction(pc) |
| if err != nil { |
| return "" |
| } |
| name, _ := entry.Val(dwarf.AttrName).(string) |
| return name |
| } |
| if startpc, err := s.peekUintStructField(gType, g, "startpc"); err == nil { |
| gr.Function = functionName(startpc) |
| } |
| if gopc, err := s.peekUintStructField(gType, g, "gopc"); err == nil { |
| gr.Caller = functionName(gopc) |
| } |
| if gr.Status != debug.Running { |
| // TODO: running goroutines too. |
| gr.StackFrames, _ = s.goroutineStack(g) |
| } |
| |
| resp.Goroutines = append(resp.Goroutines, &gr) |
| } |
| |
| return nil |
| } |
| |
| // TODO: let users specify how many frames they want. 10 will be enough to |
| // determine the reason a goroutine is blocked. |
| const goroutineStackFrameCount = 10 |
| |
| // goroutineStackInit initializes s.goroutineStack. |
| func (s *Server) goroutineStackInit(gType *dwarf.StructType) { |
| // If we fail to read the DWARF data needed for s.goroutineStack, calling it |
| // will always return the error that occurred during initialization. |
| var err error // err is captured by the func below. |
| s.goroutineStack = func(gAddr uint64) ([]debug.Frame, error) { |
| return nil, err |
| } |
| |
| // Get g field "sched", which contains fields pc and sp. |
| schedField, err := getField(gType, "sched") |
| if err != nil { |
| return |
| } |
| schedOffset := uint64(schedField.ByteOffset) |
| schedType, ok := followTypedefs(schedField.Type).(*dwarf.StructType) |
| if !ok { |
| err = errors.New(`g field "sched" has the wrong type`) |
| return |
| } |
| |
| // Get the size of the pc and sp fields and their offsets inside the g struct, |
| // so we can quickly peek those values for each goroutine later. |
| var ( |
| schedPCOffset, schedSPOffset uint64 |
| schedPCByteSize, schedSPByteSize int64 |
| ) |
| for _, x := range []struct { |
| field string |
| offset *uint64 |
| bytesize *int64 |
| }{ |
| {"pc", &schedPCOffset, &schedPCByteSize}, |
| {"sp", &schedSPOffset, &schedSPByteSize}, |
| } { |
| var f *dwarf.StructField |
| f, err = getField(schedType, x.field) |
| if err != nil { |
| return |
| } |
| *x.offset = schedOffset + uint64(f.ByteOffset) |
| switch t := followTypedefs(f.Type).(type) { |
| case *dwarf.UintType, *dwarf.IntType: |
| *x.bytesize = t.Common().ByteSize |
| default: |
| err = fmt.Errorf("gobuf field %q has the wrong type", x.field) |
| return |
| } |
| } |
| |
| s.goroutineStack = func(gAddr uint64) ([]debug.Frame, error) { |
| schedPC, err := s.peekUint(gAddr+schedPCOffset, schedPCByteSize) |
| if err != nil { |
| return nil, err |
| } |
| schedSP, err := s.peekUint(gAddr+schedSPOffset, schedSPByteSize) |
| if err != nil { |
| return nil, err |
| } |
| return s.walkStack(schedPC, schedSP, goroutineStackFrameCount) |
| } |
| } |