mirror of
https://github.com/beego/bee.git
synced 2024-11-25 20:10:55 +00:00
795 lines
22 KiB
Go
795 lines
22 KiB
Go
package proc
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/token"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// ErrNotExecutable is returned after attempting to execute a non-executable file
|
|
// to begin a debug session.
|
|
var ErrNotExecutable = errors.New("not an executable file")
|
|
|
|
// ErrNotRecorded is returned when an action is requested that is
|
|
// only possible on recorded (traced) programs.
|
|
var ErrNotRecorded = errors.New("not a recording")
|
|
|
|
const (
|
|
// UnrecoveredPanic is the name given to the unrecovered panic breakpoint.
|
|
UnrecoveredPanic = "unrecovered-panic"
|
|
|
|
// FatalThrow is the name given to the breakpoint triggered when the target process dies because of a fatal runtime error
|
|
FatalThrow = "runtime-fatal-throw"
|
|
|
|
unrecoveredPanicID = -1
|
|
fatalThrowID = -2
|
|
)
|
|
|
|
// ErrProcessExited indicates that the process has exited and contains both
|
|
// process id and exit status.
|
|
type ErrProcessExited struct {
|
|
Pid int
|
|
Status int
|
|
}
|
|
|
|
func (pe ErrProcessExited) Error() string {
|
|
return fmt.Sprintf("Process %d has exited with status %d", pe.Pid, pe.Status)
|
|
}
|
|
|
|
// ProcessDetachedError indicates that we detached from the target process.
|
|
type ProcessDetachedError struct {
|
|
}
|
|
|
|
func (pe ProcessDetachedError) Error() string {
|
|
return "detached from the process"
|
|
}
|
|
|
|
// PostInitializationSetup handles all of the initialization procedures
|
|
// that must happen after Delve creates or attaches to a process.
|
|
func PostInitializationSetup(p Process, path string, debugInfoDirs []string, writeBreakpoint WriteBreakpointFn) error {
|
|
entryPoint, err := p.EntryPoint()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = p.BinInfo().LoadBinaryInfo(path, entryPoint, debugInfoDirs)
|
|
if err == nil {
|
|
err = p.BinInfo().LoadError()
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
g, _ := GetG(p.CurrentThread())
|
|
p.SetSelectedGoroutine(g)
|
|
|
|
createUnrecoveredPanicBreakpoint(p, writeBreakpoint)
|
|
createFatalThrowBreakpoint(p, writeBreakpoint)
|
|
|
|
return nil
|
|
}
|
|
|
|
// FindFileLocation returns the PC for a given file:line.
|
|
// Assumes that `file` is normalized to lower case and '/' on Windows.
|
|
func FindFileLocation(p Process, fileName string, lineno int) (uint64, error) {
|
|
pc, fn, err := p.BinInfo().LineToPC(fileName, lineno)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if fn.Entry == pc {
|
|
pc, _ = FirstPCAfterPrologue(p, fn, true)
|
|
}
|
|
return pc, nil
|
|
}
|
|
|
|
// ErrFunctionNotFound is returned when failing to find the
|
|
// function named 'FuncName' within the binary.
|
|
type ErrFunctionNotFound struct {
|
|
FuncName string
|
|
}
|
|
|
|
func (err *ErrFunctionNotFound) Error() string {
|
|
return fmt.Sprintf("Could not find function %s\n", err.FuncName)
|
|
}
|
|
|
|
// FindFunctionLocation finds address of a function's line
|
|
// If firstLine == true is passed FindFunctionLocation will attempt to find the first line of the function
|
|
// If lineOffset is passed FindFunctionLocation will return the address of that line
|
|
// Pass lineOffset == 0 and firstLine == false if you want the address for the function's entry point
|
|
// Note that setting breakpoints at that address will cause surprising behavior:
|
|
// https://github.com/go-delve/delve/issues/170
|
|
func FindFunctionLocation(p Process, funcName string, firstLine bool, lineOffset int) (uint64, error) {
|
|
bi := p.BinInfo()
|
|
origfn := bi.LookupFunc[funcName]
|
|
if origfn == nil {
|
|
return 0, &ErrFunctionNotFound{funcName}
|
|
}
|
|
|
|
if firstLine {
|
|
return FirstPCAfterPrologue(p, origfn, false)
|
|
} else if lineOffset > 0 {
|
|
filename, lineno := origfn.cu.lineInfo.PCToLine(origfn.Entry, origfn.Entry)
|
|
breakAddr, _, err := bi.LineToPC(filename, lineno+lineOffset)
|
|
return breakAddr, err
|
|
}
|
|
|
|
return origfn.Entry, nil
|
|
}
|
|
|
|
// FunctionReturnLocations will return a list of addresses corresponding
|
|
// to 'ret' or 'call runtime.deferreturn'.
|
|
func FunctionReturnLocations(p Process, funcName string) ([]uint64, error) {
|
|
const deferReturn = "runtime.deferreturn"
|
|
|
|
g := p.SelectedGoroutine()
|
|
fn, ok := p.BinInfo().LookupFunc[funcName]
|
|
if !ok {
|
|
return nil, fmt.Errorf("unable to find function %s", funcName)
|
|
}
|
|
|
|
instructions, err := Disassemble(p, g, fn.Entry, fn.End)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var addrs []uint64
|
|
for _, instruction := range instructions {
|
|
if instruction.IsRet() {
|
|
addrs = append(addrs, instruction.Loc.PC)
|
|
}
|
|
}
|
|
addrs = append(addrs, findDeferReturnCalls(instructions)...)
|
|
|
|
return addrs, nil
|
|
}
|
|
|
|
// Next continues execution until the next source line.
|
|
func Next(dbp Process) (err error) {
|
|
if _, err := dbp.Valid(); err != nil {
|
|
return err
|
|
}
|
|
if dbp.Breakpoints().HasInternalBreakpoints() {
|
|
return fmt.Errorf("next while nexting")
|
|
}
|
|
|
|
if err = next(dbp, false, false); err != nil {
|
|
dbp.ClearInternalBreakpoints()
|
|
return
|
|
}
|
|
|
|
return Continue(dbp)
|
|
}
|
|
|
|
// Continue continues execution of the debugged
|
|
// process. It will continue until it hits a breakpoint
|
|
// or is otherwise stopped.
|
|
func Continue(dbp Process) error {
|
|
if _, err := dbp.Valid(); err != nil {
|
|
return err
|
|
}
|
|
for _, thread := range dbp.ThreadList() {
|
|
thread.Common().returnValues = nil
|
|
}
|
|
dbp.CheckAndClearManualStopRequest()
|
|
defer func() {
|
|
// Make sure we clear internal breakpoints if we simultaneously receive a
|
|
// manual stop request and hit a breakpoint.
|
|
if dbp.CheckAndClearManualStopRequest() {
|
|
dbp.ClearInternalBreakpoints()
|
|
}
|
|
}()
|
|
for {
|
|
if dbp.CheckAndClearManualStopRequest() {
|
|
dbp.ClearInternalBreakpoints()
|
|
return nil
|
|
}
|
|
trapthread, err := dbp.ContinueOnce()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
threads := dbp.ThreadList()
|
|
|
|
if err := pickCurrentThread(dbp, trapthread, threads); err != nil {
|
|
return err
|
|
}
|
|
|
|
curthread := dbp.CurrentThread()
|
|
curbp := curthread.Breakpoint()
|
|
|
|
switch {
|
|
case curbp.Breakpoint == nil:
|
|
// runtime.Breakpoint, manual stop or debugCallV1-related stop
|
|
recorded, _ := dbp.Recorded()
|
|
if recorded {
|
|
return conditionErrors(threads)
|
|
}
|
|
|
|
loc, err := curthread.Location()
|
|
if err != nil || loc.Fn == nil {
|
|
return conditionErrors(threads)
|
|
}
|
|
|
|
switch {
|
|
case loc.Fn.Name == "runtime.breakpoint":
|
|
// Single-step current thread until we exit runtime.breakpoint and
|
|
// runtime.Breakpoint.
|
|
// On go < 1.8 it was sufficient to single-step twice on go1.8 a change
|
|
// to the compiler requires 4 steps.
|
|
if err := stepInstructionOut(dbp, curthread, "runtime.breakpoint", "runtime.Breakpoint"); err != nil {
|
|
return err
|
|
}
|
|
return conditionErrors(threads)
|
|
case strings.HasPrefix(loc.Fn.Name, debugCallFunctionNamePrefix1) || strings.HasPrefix(loc.Fn.Name, debugCallFunctionNamePrefix2):
|
|
fncall := &dbp.Common().fncallState
|
|
if !fncall.inProgress {
|
|
return conditionErrors(threads)
|
|
}
|
|
fncall.step(dbp)
|
|
// only stop execution if the function call finished
|
|
if fncall.finished {
|
|
fncall.inProgress = false
|
|
if fncall.err != nil {
|
|
return fncall.err
|
|
}
|
|
curthread.Common().returnValues = fncall.returnValues()
|
|
return conditionErrors(threads)
|
|
}
|
|
default:
|
|
return conditionErrors(threads)
|
|
}
|
|
case curbp.Active && curbp.Internal:
|
|
switch curbp.Kind {
|
|
case StepBreakpoint:
|
|
// See description of proc.(*Process).next for the meaning of StepBreakpoints
|
|
if err := conditionErrors(threads); err != nil {
|
|
return err
|
|
}
|
|
regs, err := curthread.Registers(false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pc := regs.PC()
|
|
text, err := disassemble(curthread, regs, dbp.Breakpoints(), dbp.BinInfo(), pc, pc+maxInstructionLength, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// here we either set a breakpoint into the destination of the CALL
|
|
// instruction or we determined that the called function is hidden,
|
|
// either way we need to resume execution
|
|
if err = setStepIntoBreakpoint(dbp, text, SameGoroutineCondition(dbp.SelectedGoroutine())); err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
curthread.Common().returnValues = curbp.Breakpoint.returnInfo.Collect(curthread)
|
|
if err := dbp.ClearInternalBreakpoints(); err != nil {
|
|
return err
|
|
}
|
|
return conditionErrors(threads)
|
|
}
|
|
case curbp.Active:
|
|
onNextGoroutine, err := onNextGoroutine(curthread, dbp.Breakpoints())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if onNextGoroutine {
|
|
err := dbp.ClearInternalBreakpoints()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if curbp.Name == UnrecoveredPanic {
|
|
dbp.ClearInternalBreakpoints()
|
|
}
|
|
return conditionErrors(threads)
|
|
default:
|
|
// not a manual stop, not on runtime.Breakpoint, not on a breakpoint, just repeat
|
|
}
|
|
}
|
|
}
|
|
|
|
func conditionErrors(threads []Thread) error {
|
|
var condErr error
|
|
for _, th := range threads {
|
|
if bp := th.Breakpoint(); bp.Breakpoint != nil && bp.CondError != nil {
|
|
if condErr == nil {
|
|
condErr = bp.CondError
|
|
} else {
|
|
return fmt.Errorf("multiple errors evaluating conditions")
|
|
}
|
|
}
|
|
}
|
|
return condErr
|
|
}
|
|
|
|
// pick a new dbp.currentThread, with the following priority:
|
|
// - a thread with onTriggeredInternalBreakpoint() == true
|
|
// - a thread with onTriggeredBreakpoint() == true (prioritizing trapthread)
|
|
// - trapthread
|
|
func pickCurrentThread(dbp Process, trapthread Thread, threads []Thread) error {
|
|
for _, th := range threads {
|
|
if bp := th.Breakpoint(); bp.Active && bp.Internal {
|
|
return dbp.SwitchThread(th.ThreadID())
|
|
}
|
|
}
|
|
if bp := trapthread.Breakpoint(); bp.Active {
|
|
return dbp.SwitchThread(trapthread.ThreadID())
|
|
}
|
|
for _, th := range threads {
|
|
if bp := th.Breakpoint(); bp.Active {
|
|
return dbp.SwitchThread(th.ThreadID())
|
|
}
|
|
}
|
|
return dbp.SwitchThread(trapthread.ThreadID())
|
|
}
|
|
|
|
// stepInstructionOut repeatedly calls StepInstruction until the current
|
|
// function is neither fnname1 or fnname2.
|
|
// This function is used to step out of runtime.Breakpoint as well as
|
|
// runtime.debugCallV1.
|
|
func stepInstructionOut(dbp Process, curthread Thread, fnname1, fnname2 string) error {
|
|
for {
|
|
if err := curthread.StepInstruction(); err != nil {
|
|
return err
|
|
}
|
|
loc, err := curthread.Location()
|
|
if err != nil || loc.Fn == nil || (loc.Fn.Name != fnname1 && loc.Fn.Name != fnname2) {
|
|
if g := dbp.SelectedGoroutine(); g != nil {
|
|
g.CurrentLoc = *loc
|
|
}
|
|
return curthread.SetCurrentBreakpoint()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Step will continue until another source line is reached.
|
|
// Will step into functions.
|
|
func Step(dbp Process) (err error) {
|
|
if _, err := dbp.Valid(); err != nil {
|
|
return err
|
|
}
|
|
if dbp.Breakpoints().HasInternalBreakpoints() {
|
|
return fmt.Errorf("next while nexting")
|
|
}
|
|
|
|
if err = next(dbp, true, false); err != nil {
|
|
switch err.(type) {
|
|
case ErrThreadBlocked: // Noop
|
|
default:
|
|
dbp.ClearInternalBreakpoints()
|
|
return
|
|
}
|
|
}
|
|
|
|
return Continue(dbp)
|
|
}
|
|
|
|
// SameGoroutineCondition returns an expression that evaluates to true when
|
|
// the current goroutine is g.
|
|
func SameGoroutineCondition(g *G) ast.Expr {
|
|
if g == nil {
|
|
return nil
|
|
}
|
|
return &ast.BinaryExpr{
|
|
Op: token.EQL,
|
|
X: &ast.SelectorExpr{
|
|
X: &ast.SelectorExpr{
|
|
X: &ast.Ident{Name: "runtime"},
|
|
Sel: &ast.Ident{Name: "curg"},
|
|
},
|
|
Sel: &ast.Ident{Name: "goid"},
|
|
},
|
|
Y: &ast.BasicLit{Kind: token.INT, Value: strconv.Itoa(g.ID)},
|
|
}
|
|
}
|
|
|
|
func frameoffCondition(frameoff int64) ast.Expr {
|
|
return &ast.BinaryExpr{
|
|
Op: token.EQL,
|
|
X: &ast.SelectorExpr{
|
|
X: &ast.Ident{Name: "runtime"},
|
|
Sel: &ast.Ident{Name: "frameoff"},
|
|
},
|
|
Y: &ast.BasicLit{Kind: token.INT, Value: strconv.FormatInt(frameoff, 10)},
|
|
}
|
|
}
|
|
|
|
func andFrameoffCondition(cond ast.Expr, frameoff int64) ast.Expr {
|
|
if cond == nil {
|
|
return nil
|
|
}
|
|
return &ast.BinaryExpr{
|
|
Op: token.LAND,
|
|
X: cond,
|
|
Y: frameoffCondition(frameoff),
|
|
}
|
|
}
|
|
|
|
// StepOut will continue until the current goroutine exits the
|
|
// function currently being executed or a deferred function is executed
|
|
func StepOut(dbp Process) error {
|
|
if _, err := dbp.Valid(); err != nil {
|
|
return err
|
|
}
|
|
if dbp.Breakpoints().HasInternalBreakpoints() {
|
|
return fmt.Errorf("next while nexting")
|
|
}
|
|
|
|
selg := dbp.SelectedGoroutine()
|
|
curthread := dbp.CurrentThread()
|
|
|
|
topframe, retframe, err := topframe(selg, curthread)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
success := false
|
|
defer func() {
|
|
if !success {
|
|
dbp.ClearInternalBreakpoints()
|
|
}
|
|
}()
|
|
|
|
if topframe.Inlined {
|
|
if err := next(dbp, false, true); err != nil {
|
|
return err
|
|
}
|
|
|
|
success = true
|
|
return Continue(dbp)
|
|
}
|
|
|
|
sameGCond := SameGoroutineCondition(selg)
|
|
retFrameCond := andFrameoffCondition(sameGCond, retframe.FrameOffset())
|
|
|
|
var deferpc uint64
|
|
if filepath.Ext(topframe.Current.File) == ".go" {
|
|
if topframe.TopmostDefer != nil && topframe.TopmostDefer.DeferredPC != 0 {
|
|
deferfn := dbp.BinInfo().PCToFunc(topframe.TopmostDefer.DeferredPC)
|
|
deferpc, err = FirstPCAfterPrologue(dbp, deferfn, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
if deferpc != 0 && deferpc != topframe.Current.PC {
|
|
bp, err := dbp.SetBreakpoint(deferpc, NextDeferBreakpoint, sameGCond)
|
|
if err != nil {
|
|
if _, ok := err.(BreakpointExistsError); !ok {
|
|
return err
|
|
}
|
|
}
|
|
if bp != nil {
|
|
// For StepOut we do not want to step into the deferred function
|
|
// when it's called by runtime.deferreturn so we do not populate
|
|
// DeferReturns.
|
|
bp.DeferReturns = []uint64{}
|
|
}
|
|
}
|
|
|
|
if topframe.Ret == 0 && deferpc == 0 {
|
|
return errors.New("nothing to stepout to")
|
|
}
|
|
|
|
if topframe.Ret != 0 {
|
|
bp, err := dbp.SetBreakpoint(topframe.Ret, NextBreakpoint, retFrameCond)
|
|
if err != nil {
|
|
if _, isexists := err.(BreakpointExistsError); !isexists {
|
|
return err
|
|
}
|
|
}
|
|
if bp != nil {
|
|
configureReturnBreakpoint(dbp.BinInfo(), bp, &topframe, retFrameCond)
|
|
}
|
|
}
|
|
|
|
if bp := curthread.Breakpoint(); bp.Breakpoint == nil {
|
|
curthread.SetCurrentBreakpoint()
|
|
}
|
|
|
|
success = true
|
|
return Continue(dbp)
|
|
}
|
|
|
|
// GoroutinesInfo searches for goroutines starting at index 'start', and
|
|
// returns an array of up to 'count' (or all found elements, if 'count' is 0)
|
|
// G structures representing the information Delve care about from the internal
|
|
// runtime G structure.
|
|
// GoroutinesInfo also returns the next index to be used as 'start' argument
|
|
// while scanning for all available goroutines, or -1 if there was an error
|
|
// or if the index already reached the last possible value.
|
|
func GoroutinesInfo(dbp Process, start, count int) ([]*G, int, error) {
|
|
if _, err := dbp.Valid(); err != nil {
|
|
return nil, -1, err
|
|
}
|
|
if dbp.Common().allGCache != nil {
|
|
// We can't use the cached array to fulfill a subrange request
|
|
if start == 0 && (count == 0 || count >= len(dbp.Common().allGCache)) {
|
|
return dbp.Common().allGCache, -1, nil
|
|
}
|
|
}
|
|
|
|
var (
|
|
threadg = map[int]*G{}
|
|
allg []*G
|
|
rdr = dbp.BinInfo().DwarfReader()
|
|
)
|
|
|
|
threads := dbp.ThreadList()
|
|
for _, th := range threads {
|
|
if th.Blocked() {
|
|
continue
|
|
}
|
|
g, _ := GetG(th)
|
|
if g != nil {
|
|
threadg[g.ID] = g
|
|
}
|
|
}
|
|
|
|
addr, err := rdr.AddrFor("runtime.allglen", dbp.BinInfo().staticBase)
|
|
if err != nil {
|
|
return nil, -1, err
|
|
}
|
|
allglenBytes := make([]byte, 8)
|
|
_, err = dbp.CurrentThread().ReadMemory(allglenBytes, uintptr(addr))
|
|
if err != nil {
|
|
return nil, -1, err
|
|
}
|
|
allglen := binary.LittleEndian.Uint64(allglenBytes)
|
|
|
|
rdr.Seek(0)
|
|
allgentryaddr, err := rdr.AddrFor("runtime.allgs", dbp.BinInfo().staticBase)
|
|
if err != nil {
|
|
// try old name (pre Go 1.6)
|
|
allgentryaddr, err = rdr.AddrFor("runtime.allg", dbp.BinInfo().staticBase)
|
|
if err != nil {
|
|
return nil, -1, err
|
|
}
|
|
}
|
|
faddr := make([]byte, dbp.BinInfo().Arch.PtrSize())
|
|
_, err = dbp.CurrentThread().ReadMemory(faddr, uintptr(allgentryaddr))
|
|
if err != nil {
|
|
return nil, -1, err
|
|
}
|
|
allgptr := binary.LittleEndian.Uint64(faddr)
|
|
|
|
for i := uint64(start); i < allglen; i++ {
|
|
if count != 0 && len(allg) >= count {
|
|
return allg, int(i), nil
|
|
}
|
|
gvar, err := newGVariable(dbp.CurrentThread(), uintptr(allgptr+(i*uint64(dbp.BinInfo().Arch.PtrSize()))), true)
|
|
if err != nil {
|
|
allg = append(allg, &G{Unreadable: err})
|
|
continue
|
|
}
|
|
g, err := gvar.parseG()
|
|
if err != nil {
|
|
allg = append(allg, &G{Unreadable: err})
|
|
continue
|
|
}
|
|
if thg, allocated := threadg[g.ID]; allocated {
|
|
loc, err := thg.Thread.Location()
|
|
if err != nil {
|
|
return nil, -1, err
|
|
}
|
|
g.Thread = thg.Thread
|
|
// Prefer actual thread location information.
|
|
g.CurrentLoc = *loc
|
|
g.SystemStack = thg.SystemStack
|
|
}
|
|
if g.Status != Gdead {
|
|
allg = append(allg, g)
|
|
}
|
|
}
|
|
if start == 0 {
|
|
dbp.Common().allGCache = allg
|
|
}
|
|
|
|
return allg, -1, nil
|
|
}
|
|
|
|
// FindGoroutine returns a G struct representing the goroutine
|
|
// specified by `gid`.
|
|
func FindGoroutine(dbp Process, gid int) (*G, error) {
|
|
if selg := dbp.SelectedGoroutine(); (gid == -1) || (selg != nil && selg.ID == gid) || (selg == nil && gid == 0) {
|
|
// Return the currently selected goroutine in the following circumstances:
|
|
//
|
|
// 1. if the caller asks for gid == -1 (because that's what a goroutine ID of -1 means in our API).
|
|
// 2. if gid == selg.ID.
|
|
// this serves two purposes: (a) it's an optimizations that allows us
|
|
// to avoid reading any other goroutine and, more importantly, (b) we
|
|
// could be reading an incorrect value for the goroutine ID of a thread.
|
|
// This condition usually happens when a goroutine calls runtime.clone
|
|
// and for a short period of time two threads will appear to be running
|
|
// the same goroutine.
|
|
// 3. if the caller asks for gid == 0 and the selected goroutine is
|
|
// either 0 or nil.
|
|
// Goroutine 0 is special, it either means we have no current goroutine
|
|
// (for example, running C code), or that we are running on a speical
|
|
// stack (system stack, signal handling stack) and we didn't properly
|
|
// detect it.
|
|
// Since there could be multiple goroutines '0' running simultaneously
|
|
// if the user requests it return the one that's already selected or
|
|
// nil if there isn't a selected goroutine.
|
|
return selg, nil
|
|
}
|
|
|
|
if gid == 0 {
|
|
return nil, fmt.Errorf("Unknown goroutine %d", gid)
|
|
}
|
|
|
|
// Calling GoroutinesInfo could be slow if there are many goroutines
|
|
// running, check if a running goroutine has been requested first.
|
|
for _, thread := range dbp.ThreadList() {
|
|
g, _ := GetG(thread)
|
|
if g != nil && g.ID == gid {
|
|
return g, nil
|
|
}
|
|
}
|
|
|
|
const goroutinesInfoLimit = 10
|
|
nextg := 0
|
|
for nextg >= 0 {
|
|
var gs []*G
|
|
var err error
|
|
gs, nextg, err = GoroutinesInfo(dbp, nextg, goroutinesInfoLimit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for i := range gs {
|
|
if gs[i].ID == gid {
|
|
if gs[i].Unreadable != nil {
|
|
return nil, gs[i].Unreadable
|
|
}
|
|
return gs[i], nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("Unknown goroutine %d", gid)
|
|
}
|
|
|
|
// ConvertEvalScope returns a new EvalScope in the context of the
|
|
// specified goroutine ID and stack frame.
|
|
// If deferCall is > 0 the eval scope will be relative to the specified deferred call.
|
|
func ConvertEvalScope(dbp Process, gid, frame, deferCall int) (*EvalScope, error) {
|
|
if _, err := dbp.Valid(); err != nil {
|
|
return nil, err
|
|
}
|
|
ct := dbp.CurrentThread()
|
|
g, err := FindGoroutine(dbp, gid)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if g == nil {
|
|
return ThreadScope(ct)
|
|
}
|
|
|
|
var thread MemoryReadWriter
|
|
if g.Thread == nil {
|
|
thread = ct
|
|
} else {
|
|
thread = g.Thread
|
|
}
|
|
|
|
locs, err := g.Stacktrace(frame+1, deferCall > 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if frame >= len(locs) {
|
|
return nil, fmt.Errorf("Frame %d does not exist in goroutine %d", frame, gid)
|
|
}
|
|
|
|
if deferCall > 0 {
|
|
if deferCall-1 >= len(locs[frame].Defers) {
|
|
return nil, fmt.Errorf("Frame %d only has %d deferred calls", frame, len(locs[frame].Defers))
|
|
}
|
|
|
|
d := locs[frame].Defers[deferCall-1]
|
|
if d.Unreadable != nil {
|
|
return nil, d.Unreadable
|
|
}
|
|
|
|
return d.EvalScope(ct)
|
|
}
|
|
|
|
return FrameToScope(dbp.BinInfo(), thread, g, locs[frame:]...), nil
|
|
}
|
|
|
|
// FrameToScope returns a new EvalScope for frames[0].
|
|
// If frames has at least two elements all memory between
|
|
// frames[0].Regs.SP() and frames[1].Regs.CFA will be cached.
|
|
// Otherwise all memory between frames[0].Regs.SP() and frames[0].Regs.CFA
|
|
// will be cached.
|
|
func FrameToScope(bi *BinaryInfo, thread MemoryReadWriter, g *G, frames ...Stackframe) *EvalScope {
|
|
var gvar *Variable
|
|
if g != nil {
|
|
gvar = g.variable
|
|
}
|
|
|
|
// Creates a cacheMem that will preload the entire stack frame the first
|
|
// time any local variable is read.
|
|
// Remember that the stack grows downward in memory.
|
|
minaddr := frames[0].Regs.SP()
|
|
var maxaddr uint64
|
|
if len(frames) > 1 && frames[0].SystemStack == frames[1].SystemStack {
|
|
maxaddr = uint64(frames[1].Regs.CFA)
|
|
} else {
|
|
maxaddr = uint64(frames[0].Regs.CFA)
|
|
}
|
|
if maxaddr > minaddr && maxaddr-minaddr < maxFramePrefetchSize {
|
|
thread = cacheMemory(thread, uintptr(minaddr), int(maxaddr-minaddr))
|
|
}
|
|
|
|
s := &EvalScope{Location: frames[0].Call, Regs: frames[0].Regs, Mem: thread, Gvar: gvar, BinInfo: bi, frameOffset: frames[0].FrameOffset()}
|
|
s.PC = frames[0].lastpc
|
|
return s
|
|
}
|
|
|
|
// createUnrecoveredPanicBreakpoint creates the unrecoverable-panic breakpoint.
|
|
// This function is meant to be called by implementations of the Process interface.
|
|
func createUnrecoveredPanicBreakpoint(p Process, writeBreakpoint WriteBreakpointFn) {
|
|
panicpc, err := FindFunctionLocation(p, "runtime.startpanic", true, 0)
|
|
if _, isFnNotFound := err.(*ErrFunctionNotFound); isFnNotFound {
|
|
panicpc, err = FindFunctionLocation(p, "runtime.fatalpanic", true, 0)
|
|
}
|
|
if err == nil {
|
|
bp, err := p.Breakpoints().SetWithID(unrecoveredPanicID, panicpc, writeBreakpoint)
|
|
if err == nil {
|
|
bp.Name = UnrecoveredPanic
|
|
bp.Variables = []string{"runtime.curg._panic.arg"}
|
|
}
|
|
}
|
|
}
|
|
|
|
func createFatalThrowBreakpoint(p Process, writeBreakpoint WriteBreakpointFn) {
|
|
fatalpc, err := FindFunctionLocation(p, "runtime.fatalthrow", true, 0)
|
|
if err == nil {
|
|
bp, err := p.Breakpoints().SetWithID(fatalThrowID, fatalpc, writeBreakpoint)
|
|
if err == nil {
|
|
bp.Name = FatalThrow
|
|
}
|
|
}
|
|
}
|
|
|
|
// FirstPCAfterPrologue returns the address of the first
|
|
// instruction after the prologue for function fn.
|
|
// If sameline is set FirstPCAfterPrologue will always return an
|
|
// address associated with the same line as fn.Entry.
|
|
func FirstPCAfterPrologue(p Process, fn *Function, sameline bool) (uint64, error) {
|
|
pc, _, line, ok := fn.cu.lineInfo.PrologueEndPC(fn.Entry, fn.End)
|
|
if ok {
|
|
if !sameline {
|
|
return pc, nil
|
|
}
|
|
_, entryLine := fn.cu.lineInfo.PCToLine(fn.Entry, fn.Entry)
|
|
if entryLine == line {
|
|
return pc, nil
|
|
}
|
|
}
|
|
|
|
pc, err := firstPCAfterPrologueDisassembly(p, fn, sameline)
|
|
if err != nil {
|
|
return fn.Entry, err
|
|
}
|
|
|
|
if pc == fn.Entry {
|
|
// Look for the first instruction with the stmt flag set, so that setting a
|
|
// breakpoint with file:line and with the function name always result on
|
|
// the same instruction being selected.
|
|
entryFile, entryLine := fn.cu.lineInfo.PCToLine(fn.Entry, fn.Entry)
|
|
if pc, _, err := p.BinInfo().LineToPC(entryFile, entryLine); err == nil && pc >= fn.Entry && pc < fn.End {
|
|
return pc, nil
|
|
}
|
|
}
|
|
|
|
return pc, nil
|
|
}
|