package proctl import ( "bytes" "encoding/binary" "fmt" "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 } // 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 := ReadMemory(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 = WriteMemory(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 := WriteMemory(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 }