delve/pkg/proc/native/threads.go

183 lines
4.8 KiB
Go
Raw Normal View History

package native
import (
"fmt"
"github.com/go-delve/delve/pkg/proc"
)
// Thread represents a single thread in the traced process
// ID represents the thread id or port, Process holds a reference to the
// Process struct that contains info on the process as
// a whole, and Status represents the last result of a `wait` call
// on this thread.
type nativeThread struct {
ID int // Thread ID or mach port
Status *waitStatus // Status returned from last wait call
CurrentBreakpoint proc.BreakpointState // Breakpoint thread is currently stopped at
dbp *nativeProcess
singleStepping bool
os *osSpecificDetails
common proc.CommonThread
}
// StepInstruction steps 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.
func (t *nativeThread) StepInstruction() (err error) {
t.singleStepping = true
defer func() {
t.singleStepping = false
}()
if bp := t.CurrentBreakpoint.Breakpoint; bp != nil && bp.WatchType != 0 && t.dbp.Breakpoints().M[bp.Addr] == bp {
err = t.clearHardwareBreakpoint(bp.Addr, bp.WatchType, bp.HWBreakIndex)
if err != nil {
return err
}
defer func() {
err = t.writeHardwareBreakpoint(bp.Addr, bp.WatchType, bp.HWBreakIndex)
}()
}
pc, err := t.PC()
if err != nil {
return err
}
bp, ok := t.dbp.FindBreakpoint(pc, false)
if ok {
// Clear the breakpoint so that we can continue execution.
err = t.clearSoftwareBreakpoint(bp)
if err != nil {
return err
}
// Restore breakpoint now that we have passed it.
defer func() {
err = t.dbp.writeSoftwareBreakpoint(t, bp.Addr)
}()
}
err = t.singleStep()
if err != nil {
if _, exited := err.(proc.ErrProcessExited); exited {
return err
}
return fmt.Errorf("step failed: %s", err.Error())
}
return nil
}
// Location returns the threads location, including the file:line
// of the corresponding source code, the function we're in
// and the current instruction address.
func (t *nativeThread) Location() (*proc.Location, error) {
pc, err := t.PC()
if err != nil {
return nil, err
}
f, l, fn := t.dbp.bi.PCToLine(pc)
return &proc.Location{PC: pc, File: f, Line: l, Fn: fn}, nil
}
// BinInfo returns information on the binary.
func (t *nativeThread) BinInfo() *proc.BinaryInfo {
return t.dbp.bi
}
// Common returns information common across Process
// implementations.
func (t *nativeThread) Common() *proc.CommonThread {
return &t.common
}
// SetCurrentBreakpoint sets the current breakpoint that this
// thread is stopped at as CurrentBreakpoint on the thread struct.
func (t *nativeThread) SetCurrentBreakpoint(adjustPC bool) error {
t.CurrentBreakpoint.Clear()
var bp *proc.Breakpoint
if t.dbp.Breakpoints().HasHWBreakpoints() {
var err error
bp, err = t.findHardwareBreakpoint()
if err != nil {
return err
}
}
if bp == nil {
pc, err := t.PC()
if err != nil {
return err
}
// If the breakpoint instruction does not change the value
// of PC after being executed we should look for breakpoints
// with bp.Addr == PC and there is no need to call SetPC
// after finding one.
adjustPC = adjustPC && t.BinInfo().Arch.BreakInstrMovesPC()
var ok bool
bp, ok = t.dbp.FindBreakpoint(pc, adjustPC)
if ok {
if adjustPC {
if err = t.setPC(bp.Addr); err != nil {
return err
}
}
}
}
t.CurrentBreakpoint.Breakpoint = bp
return nil
}
// Breakpoint returns the current breakpoint that is active
// on this thread.
func (t *nativeThread) Breakpoint() *proc.BreakpointState {
return &t.CurrentBreakpoint
}
// ThreadID returns the ID of this thread.
func (t *nativeThread) ThreadID() int {
return t.ID
}
// clearSoftwareBreakpoint clears the specified breakpoint.
func (t *nativeThread) clearSoftwareBreakpoint(bp *proc.Breakpoint) error {
if _, err := t.WriteMemory(bp.Addr, bp.OriginalData); err != nil {
return fmt.Errorf("could not clear breakpoint %s", err)
}
return nil
}
// Registers obtains register values from the debugged process.
func (t *nativeThread) Registers() (proc.Registers, error) {
return registers(t)
}
// RestoreRegisters will set the value of the CPU registers to those
// passed in via 'savedRegs'.
func (t *nativeThread) RestoreRegisters(savedRegs proc.Registers) error {
return t.restoreRegisters(savedRegs)
}
// PC returns the current program counter value for this thread.
func (t *nativeThread) PC() (uint64, error) {
regs, err := t.Registers()
if err != nil {
return 0, err
}
return regs.PC(), nil
}
proc/*: remove proc.Thread.Blocked, refactor memory access (#2206) On linux we can not read memory if the thread we use to do it is occupied doing certain system calls. The exact conditions when this happens have never been clear. This problem was worked around by using the Blocked method which recognized the most common circumstances where this would happen. However this is a hack: Blocked returning true doesn't mean that the problem will manifest and Blocked returning false doesn't necessarily mean the problem will not manifest. A side effect of this is issue #2151 where sometimes we can't read the memory of a thread and find its associated goroutine. This commit fixes this problem by always reading memory using a thread we know to be good for this, specifically the one returned by ContinueOnce. In particular the changes are as follows: 1. Remove (ProcessInternal).CurrentThread and (ProcessInternal).SetCurrentThread, the "current thread" becomes a field of Target, CurrentThread becomes a (*Target) method and (*Target).SwitchThread basically just sets a field Target. 2. The backends keep track of their own internal idea of what the current thread is, to use it to read memory, this is the thread they return from ContinueOnce as trapthread 3. The current thread in the backend and the current thread in Target only ever get synchronized in two places: when the backend creates a Target object the currentThread field of Target is initialized with the backend's current thread and when (*Target).Restart gets called (when a recording is rewound the currentThread used by Target might not exist anymore). 4. We remove the MemoryReadWriter interface embedded in Thread and instead add a Memory method to Process that returns a MemoryReadWriter. The backends will return something here that will read memory using the current thread saved by the backend. 5. The Thread.Blocked method is removed One possible problem with this change is processes that have threads with different memory maps. As far as I can determine this could happen on old versions of linux but this option was removed in linux 2.5. Fixes #2151
2020-11-09 19:28:40 +00:00
// ProcessMemory returns this thread's process memory.
func (t *nativeThread) ProcessMemory() proc.MemoryReadWriter {
return t.dbp.Memory()
}