mirror of
				https://github.com/go-delve/delve.git
				synced 2025-11-04 06:32:16 +08:00 
			
		
		
		
	Begin thread code isolation
This commit is contained in:
		@ -1,315 +1,11 @@
 | 
			
		||||
package proctl
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/binary"
 | 
			
		||||
	"fmt"
 | 
			
		||||
import "syscall"
 | 
			
		||||
 | 
			
		||||
	"syscall"
 | 
			
		||||
 | 
			
		||||
	"github.com/derekparker/delve/dwarf/frame"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ThreadContext represents a single thread of execution in the
 | 
			
		||||
// traced program.
 | 
			
		||||
type ThreadContext struct {
 | 
			
		||||
	Id      int
 | 
			
		||||
	Process *DebuggedProcess
 | 
			
		||||
	Status  *syscall.WaitStatus
 | 
			
		||||
	Regs    *syscall.PtraceRegs
 | 
			
		||||
func WriteMemory(tid int, addr uintptr, data []byte) (int, error) {
 | 
			
		||||
	return syscall.PtracePokeData(tid, addr, data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Obtains register values from the debugged process.
 | 
			
		||||
func (thread *ThreadContext) Registers() (*syscall.PtraceRegs, error) {
 | 
			
		||||
	err := syscall.PtraceGetRegs(thread.Id, thread.Regs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("could not get registers %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return thread.Regs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Returns the current PC for this thread id.
 | 
			
		||||
func (thread *ThreadContext) CurrentPC() (uint64, error) {
 | 
			
		||||
	regs, err := thread.Registers()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return regs.PC(), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PrintInfo prints out the thread status
 | 
			
		||||
// including: PC, tid, file, line, and function.
 | 
			
		||||
func (thread *ThreadContext) PrintInfo() error {
 | 
			
		||||
	pc, err := thread.CurrentPC()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	f, l, fn := thread.Process.GoSymTable.PCToLine(pc)
 | 
			
		||||
	if fn != nil {
 | 
			
		||||
		fmt.Printf("Thread %d at %#v %s:%d %s\n", thread.Id, pc, f, l, fn.Name)
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Printf("Thread %d at %#v\n", thread.Id, pc)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Sets a software breakpoint at addr, and stores it in the process wide
 | 
			
		||||
// break point table. Setting a break point must be thread specific due to
 | 
			
		||||
// ptrace actions needing the thread to be in a signal-delivery-stop in order
 | 
			
		||||
// to initiate any ptrace command. Otherwise, it really doesn't matter
 | 
			
		||||
// as we're only dealing with threads.
 | 
			
		||||
func (thread *ThreadContext) Break(addr uintptr) (*BreakPoint, error) {
 | 
			
		||||
	var (
 | 
			
		||||
		int3         = []byte{0xCC}
 | 
			
		||||
		f, l, fn     = thread.Process.GoSymTable.PCToLine(uint64(addr))
 | 
			
		||||
		originalData = make([]byte, 1)
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	if fn == nil {
 | 
			
		||||
		return nil, InvalidAddressError{address: addr}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := syscall.PtracePeekData(thread.Id, addr, originalData)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("PEEK ERR")
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if bytes.Equal(originalData, int3) {
 | 
			
		||||
		return nil, BreakPointExistsError{f, l, addr}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err = syscall.PtracePokeData(thread.Id, addr, int3)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("POKE ERR")
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	breakpoint := &BreakPoint{
 | 
			
		||||
		FunctionName: fn.Name,
 | 
			
		||||
		File:         f,
 | 
			
		||||
		Line:         l,
 | 
			
		||||
		Addr:         uint64(addr),
 | 
			
		||||
		OriginalData: originalData,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	thread.Process.BreakPoints[uint64(addr)] = breakpoint
 | 
			
		||||
 | 
			
		||||
	return breakpoint, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Clears a software breakpoint, and removes it from the process level
 | 
			
		||||
// break point table.
 | 
			
		||||
func (thread *ThreadContext) Clear(pc uint64) (*BreakPoint, error) {
 | 
			
		||||
	bp, ok := thread.Process.BreakPoints[pc]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, fmt.Errorf("No breakpoint currently set for %#v", pc)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := syscall.PtracePokeData(thread.Id, uintptr(bp.Addr), bp.OriginalData); err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("could not clear breakpoint %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	delete(thread.Process.BreakPoints, pc)
 | 
			
		||||
 | 
			
		||||
	return bp, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (thread *ThreadContext) Continue() error {
 | 
			
		||||
	// Check whether we are stopped at a breakpoint, and
 | 
			
		||||
	// if so, single step over it before continuing.
 | 
			
		||||
	regs, err := thread.Registers()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("could not get registers %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, ok := thread.Process.BreakPoints[regs.PC()-1]; ok {
 | 
			
		||||
		err := thread.Step()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("could not step %s", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return syscall.PtraceCont(thread.Id, 0)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Single steps this thread a single instruction, ensuring that
 | 
			
		||||
// we correctly handle the likely case that we are at a breakpoint.
 | 
			
		||||
func (thread *ThreadContext) Step() (err error) {
 | 
			
		||||
	regs, err := thread.Registers()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bp, ok := thread.Process.BreakPoints[regs.PC()-1]
 | 
			
		||||
	if ok {
 | 
			
		||||
		// Clear the breakpoint so that we can continue execution.
 | 
			
		||||
		_, err = thread.Clear(bp.Addr)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Reset program counter to our restored instruction.
 | 
			
		||||
		regs.SetPC(bp.Addr)
 | 
			
		||||
		err = syscall.PtraceSetRegs(thread.Id, regs)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("could not set registers %s", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Restore breakpoint now that we have passed it.
 | 
			
		||||
		defer func() {
 | 
			
		||||
			_, err = thread.Break(uintptr(bp.Addr))
 | 
			
		||||
		}()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = syscall.PtraceSingleStep(thread.Id)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("step failed: %s", err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, _, err = wait(thread.Id, 0)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Step to next source line. Next will step over functions,
 | 
			
		||||
// and will follow through to the return address of a function.
 | 
			
		||||
// Next is implemented on the thread context, however during the
 | 
			
		||||
// course of this function running, it's very likely that the
 | 
			
		||||
// goroutine our M is executing will switch to another M, therefore
 | 
			
		||||
// this function cannot assume all execution will happen on this thread
 | 
			
		||||
// in the traced process.
 | 
			
		||||
func (thread *ThreadContext) Next() (err error) {
 | 
			
		||||
	pc, err := thread.CurrentPC()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, ok := thread.Process.BreakPoints[pc-1]; ok {
 | 
			
		||||
		pc-- // Decrement PC to account for BreakPoint
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, l, _ := thread.Process.GoSymTable.PCToLine(pc)
 | 
			
		||||
	fde, err := thread.Process.FrameEntries.FDEForPC(pc)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	step := func() (uint64, error) {
 | 
			
		||||
		err = thread.Step()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return thread.CurrentPC()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ret := thread.ReturnAddressFromOffset(fde.ReturnAddressOffset(pc))
 | 
			
		||||
	for {
 | 
			
		||||
		pc, err = step()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !fde.Cover(pc) && pc != ret {
 | 
			
		||||
			err := thread.continueToReturnAddress(pc, fde)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				if _, ok := err.(InvalidAddressError); !ok {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			pc, _ = thread.CurrentPC()
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		_, nl, _ := thread.Process.GoSymTable.PCToLine(pc)
 | 
			
		||||
		if nl != l {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (thread *ThreadContext) continueToReturnAddress(pc uint64, fde *frame.FrameDescriptionEntry) error {
 | 
			
		||||
	for !fde.Cover(pc) {
 | 
			
		||||
		// Our offset here is be 0 because we
 | 
			
		||||
		// have stepped into the first instruction
 | 
			
		||||
		// of this function. Therefore the function
 | 
			
		||||
		// has not had a chance to modify its' stack
 | 
			
		||||
		// and change our offset.
 | 
			
		||||
		addr := thread.ReturnAddressFromOffset(0)
 | 
			
		||||
		bp, err := thread.Break(uintptr(addr))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			if _, ok := err.(BreakPointExistsError); !ok {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		bp.temp = true
 | 
			
		||||
		// Ensure we cleanup after ourselves no matter what.
 | 
			
		||||
		defer thread.clearTempBreakpoint(bp.Addr)
 | 
			
		||||
 | 
			
		||||
		for {
 | 
			
		||||
			err = thread.Continue()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			// We wait on -1 here because once we continue this
 | 
			
		||||
			// thread, it's very possible the scheduler could of
 | 
			
		||||
			// change the goroutine context on us, we there is
 | 
			
		||||
			// no guarantee that waiting on this tid will ever
 | 
			
		||||
			// return.
 | 
			
		||||
			wpid, _, err := trapWait(thread.Process, -1, 0)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			if wpid != thread.Id {
 | 
			
		||||
				thread = thread.Process.Threads[wpid]
 | 
			
		||||
			}
 | 
			
		||||
			pc, _ = thread.CurrentPC()
 | 
			
		||||
			if (pc - 1) == bp.Addr {
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Takes an offset from RSP and returns the address of the
 | 
			
		||||
// instruction the currect function is going to return to.
 | 
			
		||||
func (thread *ThreadContext) ReturnAddressFromOffset(offset int64) uint64 {
 | 
			
		||||
	regs, err := thread.Registers()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic("Could not obtain register values")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	retaddr := int64(regs.Rsp) + offset
 | 
			
		||||
	data := make([]byte, 8)
 | 
			
		||||
	syscall.PtracePeekText(thread.Id, uintptr(retaddr), data)
 | 
			
		||||
	return binary.LittleEndian.Uint64(data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (thread *ThreadContext) clearTempBreakpoint(pc uint64) error {
 | 
			
		||||
	if bp, ok := thread.Process.BreakPoints[pc]; ok {
 | 
			
		||||
		_, err := thread.Clear(bp.Addr)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Reset program counter to our restored instruction.
 | 
			
		||||
		regs, err := thread.Registers()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		regs.SetPC(bp.Addr)
 | 
			
		||||
		return syscall.PtraceSetRegs(thread.Id, regs)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
func ReadMemory(tid int, addr uintptr, data []byte) (int, error) {
 | 
			
		||||
	return syscall.PtracePeekData(tid, addr, data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user