delve/proc/threads.go

318 lines
8.3 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"
2014-12-08 23:40:59 +00:00
"fmt"
"path/filepath"
2015-02-02 21:09:56 +00:00
2015-07-08 20:02:43 +00:00
sys "golang.org/x/sys/unix"
"github.com/derekparker/delve/dwarf/frame"
"github.com/derekparker/delve/source"
2014-12-08 23:40:59 +00:00
)
2015-06-12 19:51:23 +00:00
// Thread represents a single thread in the traced process
// Id represents the thread id or port, Process holds a reference to the
2015-06-20 22:54:52 +00:00
// Process struct that contains info on the process as
2015-02-02 21:09:56 +00:00
// a whole, and Status represents the last result of a `wait` call
// on this thread.
2015-06-12 19:51:23 +00:00
type Thread struct {
2015-06-12 19:53:20 +00:00
Id int // Thread ID or mach port
Status *sys.WaitStatus // Status returned from last wait call
CurrentBreakpoint *Breakpoint // Breakpoint thread is currently stopped at
2015-06-20 22:54:52 +00:00
dbp *Process
2015-06-12 19:53:20 +00:00
singleStepping bool
running bool
os *OSSpecificDetails
}
2015-06-18 03:01:31 +00:00
// Represents the location of a thread.
// 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
}
2015-06-19 13:08:25 +00:00
// Continue the execution of this thread.
//
// If we are currently at a breakpoint, we'll clear it
// first and then resume execution. Thread will continue until
// it hits a breakpoint or is signaled.
2015-06-12 19:51:23 +00:00
func (thread *Thread) Continue() error {
pc, err := thread.PC()
if err != nil {
return err
}
// Check whether we are stopped at a breakpoint, and
// if so, single step over it before continuing.
if bp, ok := thread.dbp.FindBreakpoint(pc); ok {
if !bp.hardware {
if err := thread.Step(); err != nil {
return err
}
2014-12-08 23:40:59 +00:00
}
}
2015-03-01 03:14:22 +00:00
return thread.resume()
2014-12-08 23:40:59 +00:00
}
2015-06-18 03:01:31 +00:00
// Step a single instruction.
//
// Executes exactly one instruction and then returns.
// If the thread is at a breakpoint, we first clear it,
// execute the instruction, and then replace the breakpoint.
// Otherwise we simply execute the next instruction.
2015-06-12 19:51:23 +00:00
func (thread *Thread) Step() (err error) {
thread.running = true
thread.singleStepping = true
defer func() {
thread.singleStepping = false
thread.running = false
}()
2015-04-23 15:40:33 +00:00
pc, err := thread.PC()
2014-12-08 23:40:59 +00:00
if err != nil {
return err
}
2015-06-12 19:32:32 +00:00
bp, ok := thread.dbp.Breakpoints[pc]
2014-12-08 23:40:59 +00:00
if ok {
// Clear the breakpoint so that we can continue execution.
_, err = bp.Clear(thread)
2014-12-08 23:40:59 +00:00
if err != nil {
return err
}
// Restore breakpoint now that we have passed it.
defer func() {
if bp.hardware {
err = thread.dbp.setHardwareBreakpoint(bp.reg, thread.Id, bp.Addr)
} else {
err = thread.dbp.writeSoftwareBreakpoint(thread, bp.Addr)
2015-06-26 12:46:46 +00:00
}
2014-12-08 23:40:59 +00:00
}()
}
2015-01-14 02:37:10 +00:00
err = thread.singleStep()
2014-12-08 23:40:59 +00:00
if err != nil {
return fmt.Errorf("step failed: %s", err.Error())
}
2015-04-28 14:03:43 +00:00
return nil
2014-12-08 23:40:59 +00:00
}
2015-06-18 03:01:31 +00:00
// Returns the threads location, including the file:line
// of the corresponding source code, the function we're in
// and the current instruction address.
2015-06-12 19:51:23 +00:00
func (thread *Thread) Location() (*Location, error) {
pc, err := thread.PC()
if err != nil {
return nil, err
}
f, l, fn := thread.dbp.PCToLine(pc)
return &Location{PC: pc, File: f, Line: l, Fn: fn}, nil
}
2014-12-08 23:40:59 +00:00
type ThreadBlockedError struct{}
func (tbe ThreadBlockedError) Error() string {
return ""
}
// Set breakpoints for potential next lines.
//
// There are two modes of operation for this method. First,
// if we are executing Go code, we can use the stdlib AST
// information to determine which lines we could potentially
// end up at. Parsing the source file into an AST and traversing
// it lets us gain insight into whether we're at a branch, and
// where that branch could end up at, etc...
//
// However, if we are executing C code, we use the DWARF
// debug_line information and essentially set a breakpoint
// at every single line within the current function, and
// another at the functions return address, in case we're at
// the end.
func (thread *Thread) setNextBreakpoints() (err error) {
if thread.blocked() {
return ThreadBlockedError{}
}
2015-04-23 15:40:33 +00:00
curpc, err := thread.PC()
2014-12-08 23:40:59 +00:00
if err != nil {
return err
}
g, err := thread.GetG()
if err != nil {
return err
}
if g.DeferPC != 0 {
if _, err = thread.dbp.SetTempBreakpoint(g.DeferPC); err != nil {
return err
}
}
2014-12-08 23:40:59 +00:00
// Grab info on our current stack frame. Used to determine
// whether we may be stepping outside of the current function.
fde, err := thread.dbp.frameEntries.FDEForPC(curpc)
if err != nil {
return err
2014-12-08 23:40:59 +00:00
}
// Get current file/line.
loc, err := thread.Location()
if err != nil {
return err
}
if filepath.Ext(loc.File) == ".go" {
err = thread.next(curpc, fde, loc.File, loc.Line)
} else {
err = thread.cnext(curpc, fde, loc.File)
2014-12-08 23:40:59 +00:00
}
return err
}
2014-12-08 23:40:59 +00:00
// Go routine is exiting.
type GoroutineExitingError struct {
goid int
}
func (ge GoroutineExitingError) Error() string {
return fmt.Sprintf("goroutine %d is exiting", ge.goid)
}
2015-06-18 03:01:31 +00:00
// Use the AST to determine potential next lines.
2015-06-12 19:51:23 +00:00
func (thread *Thread) next(curpc uint64, fde *frame.FrameDescriptionEntry, file string, line int) error {
lines, err := thread.dbp.ast.NextLines(file, line)
if err != nil {
if _, ok := err.(source.NoNodeError); !ok {
return err
}
}
2014-12-08 23:40:59 +00:00
ret, err := thread.ReturnAddress()
2014-12-08 23:40:59 +00:00
if err != nil {
return err
2014-12-08 23:40:59 +00:00
}
pcs := make([]uint64, 0, len(lines))
for i := range lines {
pcs = append(pcs, thread.dbp.lineInfo.AllPCsForFileLine(file, lines[i])...)
}
2014-12-08 23:40:59 +00:00
var covered bool
for i := range pcs {
if fde.Cover(pcs[i]) {
covered = true
break
}
}
if !covered {
fn := thread.dbp.goSymTable.PCToFunc(ret)
if fn != nil && fn.Name == "runtime.goexit" {
2015-06-28 15:00:56 +00:00
g, err := thread.GetG()
if err != nil {
return err
}
return GoroutineExitingError{goid: g.Id}
}
}
pcs = append(pcs, ret)
return thread.setNextTempBreakpoints(curpc, pcs)
}
// Set a breakpoint at every reachable location, as well as the return address. Without
// the benefit of an AST we can't be sure we're not at a branching statement and thus
// cannot accurately predict where we may end up.
func (thread *Thread) cnext(curpc uint64, fde *frame.FrameDescriptionEntry, file string) error {
pcs := thread.dbp.lineInfo.AllPCsBetween(fde.Begin(), fde.End(), file)
ret, err := thread.ReturnAddress()
if err != nil {
return err
}
pcs = append(pcs, ret)
return thread.setNextTempBreakpoints(curpc, pcs)
}
2015-06-12 19:51:23 +00:00
func (thread *Thread) setNextTempBreakpoints(curpc uint64, pcs []uint64) error {
for i := range pcs {
if pcs[i] == curpc || pcs[i] == curpc-1 {
continue
}
if _, err := thread.dbp.SetTempBreakpoint(pcs[i]); err != nil {
2015-06-12 19:32:32 +00:00
if _, ok := err.(BreakpointExistsError); !ok {
return err
}
}
}
return nil
}
2015-04-23 01:00:42 +00:00
// Sets the PC for this thread.
2015-06-12 19:51:23 +00:00
func (thread *Thread) SetPC(pc uint64) error {
regs, err := thread.Registers()
if err != nil {
return err
}
return regs.SetPC(thread, pc)
}
// Returns information on the G (goroutine) that is executing on this thread.
//
// The G structure for a thread is stored in thread local memory. Execute instructions
2015-06-11 20:34:01 +00:00
// that move the *G structure into a CPU register, and then grab
// the new registers and parse the G structure.
//
// 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.
2015-06-28 15:00:56 +00:00
func (thread *Thread) GetG() (g *G, err error) {
regs, err := thread.Registers()
if err != nil {
return nil, err
}
2015-08-02 02:43:03 +00:00
gaddrbs, err := thread.readMemory(uintptr(regs.TLS()+thread.dbp.arch.GStructOffset()), thread.dbp.arch.PtrSize())
if err != nil {
return nil, err
}
gaddr := binary.LittleEndian.Uint64(gaddrbs)
g, err = parseG(thread, gaddr, false)
if err == nil {
g.thread = thread
}
return
2014-12-08 23:40:59 +00:00
}
// Returns whether the thread is stopped at
// the operating system level. Actual implementation
// is OS dependant, look in OS thread file.
func (thread *Thread) Stopped() bool {
return thread.stopped()
}
// Stops this thread from executing. Actual
// implementation is OS dependant. Look in OS
// thread file.
func (thread *Thread) Halt() (err error) {
defer func() {
if err == nil {
thread.running = false
}
}()
if thread.Stopped() {
return
}
err = thread.halt()
return
}