// 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) } }