delve/pkg/proc/threads.go

396 lines
10 KiB
Go
Raw Normal View History

2015-06-12 19:49:23 +00:00
package proc
2014-12-08 23:40:59 +00:00
import (
"debug/gosym"
"encoding/binary"
"errors"
2014-12-08 23:40:59 +00:00
"fmt"
"go/ast"
"path/filepath"
"reflect"
"strings"
2015-07-08 20:02:43 +00:00
"golang.org/x/debug/dwarf"
2014-12-08 23:40:59 +00:00
)
// Thread represents a thread.
type Thread interface {
MemoryReadWriter
Location() (*Location, error)
// Breakpoint will return the breakpoint that this thread is stopped at or
// nil if the thread is not stopped at any breakpoint.
// Active will be true if the thread is stopped at a breakpoint and the
// breakpoint's condition is met.
// If there was an error evaluating the breakpoint's condition it will be
// returned as condErr
Breakpoint() (breakpoint *Breakpoint, active bool, condErr error)
ThreadID() int
Registers(floatingPoint bool) (Registers, error)
Arch() Arch
BinInfo() *BinaryInfo
StepInstruction() error
// Blocked returns true if the thread is blocked
Blocked() bool
}
2016-01-10 08:57:52 +00:00
// Location represents the location of a thread.
2015-06-18 03:01:31 +00:00
// Holds information on the current instruction
// address, the source file:line, and the function.
type Location struct {
PC uint64
File string
Line int
Fn *gosym.Func
}
2016-01-10 08:57:52 +00:00
// ThreadBlockedError is returned when the thread
// is blocked in the scheduler.
type ThreadBlockedError struct{}
func (tbe ThreadBlockedError) Error() string {
return ""
}
// returns topmost frame of g or thread if g is nil
func topframe(g *G, thread Thread) (Stackframe, error) {
var frames []Stackframe
var err error
if g == nil {
if thread.Blocked() {
return Stackframe{}, ThreadBlockedError{}
}
frames, err = ThreadStacktrace(thread, 0)
} else {
frames, err = g.Stacktrace(0)
}
2014-12-08 23:40:59 +00:00
if err != nil {
return Stackframe{}, err
2014-12-08 23:40:59 +00:00
}
if len(frames) < 1 {
return Stackframe{}, errors.New("empty stack trace")
}
return frames[0], nil
}
// Set breakpoints at every line, and the return address. Also look for
// a deferred function and set a breakpoint there too.
// If stepInto is true it will also set breakpoints inside all
// functions called on the current source line, for non-absolute CALLs
// a breakpoint of kind StepBreakpoint is set on the CALL instruction,
// Continue will take care of setting a breakpoint to the destination
// once the CALL is reached.
func next(dbp Process, stepInto bool) error {
selg := dbp.SelectedGoroutine()
curthread := dbp.CurrentThread()
topframe, err := topframe(selg, curthread)
if err != nil {
return err
}
2014-12-08 23:40:59 +00:00
success := false
defer func() {
if !success {
dbp.ClearInternalBreakpoints()
}
}()
csource := filepath.Ext(topframe.Current.File) != ".go"
var thread MemoryReadWriter = curthread
var regs Registers
if selg != nil && selg.Thread != nil {
thread = selg.Thread
regs, err = selg.Thread.Registers(false)
if err != nil {
return err
}
2014-12-08 23:40:59 +00:00
}
text, err := disassemble(thread, regs, dbp.Breakpoints(), dbp.BinInfo(), topframe.FDE.Begin(), topframe.FDE.End())
if err != nil && stepInto {
return err
}
for i := range text {
if text[i].Inst == nil {
fmt.Printf("error at instruction %d\n", i)
}
}
cond := SameGoroutineCondition(selg)
if stepInto {
for _, instr := range text {
if instr.Loc.File != topframe.Current.File || instr.Loc.Line != topframe.Current.Line || !instr.IsCall() {
continue
}
if instr.DestLoc != nil && instr.DestLoc.Fn != nil {
if err := setStepIntoBreakpoint(dbp, []AsmInstruction{instr}, cond); err != nil {
return err
}
} else {
// Non-absolute call instruction, set a StepBreakpoint here
if _, err := dbp.SetBreakpoint(instr.Loc.PC, StepBreakpoint, cond); err != nil {
if _, ok := err.(BreakpointExistsError); !ok {
return err
}
}
}
}
}
if !csource {
deferreturns := []uint64{}
// Find all runtime.deferreturn locations in the function
// See documentation of Breakpoint.DeferCond for why this is necessary
for _, instr := range text {
if instr.IsCall() && instr.DestLoc != nil && instr.DestLoc.Fn != nil && instr.DestLoc.Fn.Name == "runtime.deferreturn" {
deferreturns = append(deferreturns, instr.Loc.PC)
}
}
// Set breakpoint on the most recently deferred function (if any)
var deferpc uint64 = 0
if selg != nil {
deferPCEntry := selg.DeferPC()
if deferPCEntry != 0 {
_, _, deferfn := dbp.BinInfo().PCToLine(deferPCEntry)
var err error
deferpc, err = FirstPCAfterPrologue(dbp, deferfn, false)
if err != nil {
return err
}
}
}
if deferpc != 0 && deferpc != topframe.Current.PC {
bp, err := dbp.SetBreakpoint(deferpc, NextDeferBreakpoint, cond)
if err != nil {
if _, ok := err.(BreakpointExistsError); !ok {
return err
}
}
if bp != nil {
bp.DeferReturns = deferreturns
}
}
}
// Add breakpoints on all the lines in the current function
pcs, err := dbp.BinInfo().lineInfo.AllPCsBetween(topframe.FDE.Begin(), topframe.FDE.End()-1, topframe.Current.File)
if err != nil {
return err
}
2014-12-08 23:40:59 +00:00
if !csource {
var covered bool
for i := range pcs {
if topframe.FDE.Cover(pcs[i]) {
covered = true
break
}
}
if !covered {
fn := dbp.BinInfo().goSymTable.PCToFunc(topframe.Ret)
if selg != nil && fn != nil && fn.Name == "runtime.goexit" {
return nil
}
}
}
// Add a breakpoint on the return address for the current frame
pcs = append(pcs, topframe.Ret)
success = true
return setInternalBreakpoints(dbp, topframe.Current.PC, pcs, NextBreakpoint, cond)
}
func setStepIntoBreakpoint(dbp Process, text []AsmInstruction, cond ast.Expr) error {
if len(text) <= 0 {
return nil
}
instr := text[0]
if instr.DestLoc == nil || instr.DestLoc.Fn == nil {
return nil
}
fn := instr.DestLoc.Fn
// Ensure PC and Entry match, otherwise StepInto is likely to set
// its breakpoint before DestLoc.PC and hence run too far ahead.
// Calls to runtime.duffzero and duffcopy have this problem.
if fn.Entry != instr.DestLoc.PC {
return nil
}
// Skip unexported runtime functions
if strings.HasPrefix(fn.Name, "runtime.") && !isExportedRuntime(fn.Name) {
return nil
}
//TODO(aarzilli): if we want to let users hide functions
// or entire packages from being stepped into with 'step'
// those extra checks should be done here.
// Set a breakpoint after the function's prologue
pc, _ := FirstPCAfterPrologue(dbp, fn, false)
if _, err := dbp.SetBreakpoint(pc, NextBreakpoint, cond); err != nil {
if _, ok := err.(BreakpointExistsError); !ok {
return err
}
}
return nil
}
// setInternalBreakpoints sets a breakpoint to all addresses specified in pcs
// skipping over curpc and curpc-1
func setInternalBreakpoints(dbp Process, curpc uint64, pcs []uint64, kind BreakpointKind, cond ast.Expr) error {
for i := range pcs {
if pcs[i] == curpc || pcs[i] == curpc-1 {
continue
}
if _, err := dbp.SetBreakpoint(pcs[i], kind, cond); err != nil {
2015-06-12 19:32:32 +00:00
if _, ok := err.(BreakpointExistsError); !ok {
dbp.ClearInternalBreakpoints()
return err
}
}
}
return nil
}
func getGVariable(thread Thread) (*Variable, error) {
arch := thread.Arch()
regs, err := thread.Registers(false)
2016-01-10 17:29:14 +00:00
if err != nil {
return nil, err
}
if arch.GStructOffset() == 0 {
2016-01-10 17:29:14 +00:00
// GetG was called through SwitchThread / updateThreadList during initialization
2017-02-08 00:23:47 +00:00
// thread.dbp.arch isn't setup yet (it needs a current thread to read global variables from)
2016-01-10 17:29:14 +00:00
return nil, fmt.Errorf("g struct offset not initialized")
}
gaddr, hasgaddr := regs.GAddr()
if !hasgaddr {
gaddrbs := make([]byte, arch.PtrSize())
_, err := thread.ReadMemory(gaddrbs, uintptr(regs.TLS()+arch.GStructOffset()))
if err != nil {
return nil, err
}
gaddr = binary.LittleEndian.Uint64(gaddrbs)
2016-01-10 17:29:14 +00:00
}
2016-01-24 16:30:23 +00:00
return newGVariable(thread, uintptr(gaddr), arch.DerefTLS())
2016-01-10 17:29:14 +00:00
}
func newGVariable(thread Thread, gaddr uintptr, deref bool) (*Variable, error) {
typ, err := thread.BinInfo().findType("runtime.g")
2016-01-10 17:29:14 +00:00
if err != nil {
return nil, err
}
name := ""
if deref {
typ = &dwarf.PtrType{dwarf.CommonType{int64(thread.Arch().PtrSize()), "", reflect.Ptr, 0}, typ}
2016-01-10 17:29:14 +00:00
} else {
name = "runtime.curg"
}
return newVariableFromThread(thread, name, gaddr, typ), nil
2016-01-10 17:29:14 +00:00
}
2016-01-10 08:57:52 +00:00
// GetG returns information on the G (goroutine) that is executing on this thread.
//
2015-12-17 01:16:59 +00:00
// The G structure for a thread is stored in thread local storage. Here we simply
// calculate the address and read and parse the G struct.
//
// We cannot simply use the allg linked list in order to find the M that represents
// the given OS thread and follow its G pointer because on Darwin mach ports are not
// universal, so our port for this thread would not map to the `id` attribute of the M
// structure. Also, when linked against libc, Go prefers the libc version of clone as
// opposed to the runtime version. This has the consequence of not setting M.id for
// any thread, regardless of OS.
//
// In order to get around all this craziness, we read the address of the G structure for
// the current thread from the thread local storage area.
func GetG(thread Thread) (g *G, err error) {
gaddr, err := getGVariable(thread)
if err != nil {
return nil, err
}
2016-01-10 17:29:14 +00:00
g, err = gaddr.parseG()
if err == nil {
g.Thread = thread
if loc, err := thread.Location(); err == nil {
g.CurrentLoc = *loc
}
}
return
2014-12-08 23:40:59 +00:00
}
// ThreadScope returns an EvalScope for this thread.
func ThreadScope(thread Thread) (*EvalScope, error) {
locations, err := ThreadStacktrace(thread, 0)
if err != nil {
return nil, err
}
if len(locations) < 1 {
return nil, errors.New("could not decode first frame")
}
return &EvalScope{locations[0].Current.PC, locations[0].CFA, thread, nil, thread.BinInfo()}, nil
}
// GoroutineScope returns an EvalScope for the goroutine running on this thread.
func GoroutineScope(thread Thread) (*EvalScope, error) {
locations, err := ThreadStacktrace(thread, 0)
if err != nil {
return nil, err
}
if len(locations) < 1 {
return nil, errors.New("could not decode first frame")
}
gvar, err := getGVariable(thread)
if err != nil {
return nil, err
}
return &EvalScope{locations[0].Current.PC, locations[0].CFA, thread, gvar, thread.BinInfo()}, nil
}
func onRuntimeBreakpoint(thread Thread) bool {
2016-01-10 08:57:52 +00:00
loc, err := thread.Location()
2015-11-24 21:48:28 +00:00
if err != nil {
return false
}
return loc.Fn != nil && loc.Fn.Name == "runtime.breakpoint"
}
2016-03-09 15:11:58 +00:00
// onNextGorutine returns true if this thread is on the goroutine requested by the current 'next' command
func onNextGoroutine(thread Thread, breakpoints map[uint64]*Breakpoint) (bool, error) {
var bp *Breakpoint
for i := range breakpoints {
if breakpoints[i].Internal() {
bp = breakpoints[i]
break
}
}
if bp == nil {
return false, nil
}
if bp.Kind == NextDeferBreakpoint {
// we just want to check the condition on the goroutine id here
bp.Kind = NextBreakpoint
defer func() {
bp.Kind = NextDeferBreakpoint
}()
}
return bp.CheckCondition(thread)
}