(Mostly) working multithreaded tracing implementation
Areas that need improving: * Code cleanup * Promote breakpoints back out of thread context * Fix potential bug in "Next" implementation, when thread contexts switch
This commit is contained in:
parent
09ff60f1ab
commit
4c95bf7302
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
func printPid(pid int) {
|
||||
@ -18,6 +19,7 @@ func main() {
|
||||
runtime.LockOSThread()
|
||||
pid := os.Getpid()
|
||||
printPid(pid)
|
||||
time.Sleep(10 * time.Second)
|
||||
|
||||
for {
|
||||
printPid(pid)
|
||||
|
21
_fixtures/testthreads.go
Normal file
21
_fixtures/testthreads.go
Normal file
@ -0,0 +1,21 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func anotherthread(wg *sync.WaitGroup) {
|
||||
i := 1 * 5 / 39020
|
||||
fmt.Println(i)
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
func main() {
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 100000; i++ {
|
||||
wg.Add(1)
|
||||
go anotherthread(&wg)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
@ -30,18 +30,12 @@ func WithTestProcess(name string, t *testing.T, fn testfunc) {
|
||||
}
|
||||
defer os.Remove("./" + base)
|
||||
|
||||
cmd, err := startTestProcess(base)
|
||||
if err != nil {
|
||||
t.Fatal("Starting test process:", err)
|
||||
}
|
||||
|
||||
pid := cmd.Process.Pid
|
||||
p, err := proctl.NewDebugProcess(pid)
|
||||
p, err := proctl.AttachBinary("./" + base)
|
||||
if err != nil {
|
||||
t.Fatal("NewDebugProcess():", err)
|
||||
}
|
||||
|
||||
defer cmd.Process.Kill()
|
||||
defer p.Process.Kill()
|
||||
|
||||
fn(p)
|
||||
}
|
||||
|
3
main.go
3
main.go
@ -56,6 +56,7 @@ func main() {
|
||||
if err != nil {
|
||||
die(1, "Could not start process:", err)
|
||||
}
|
||||
syscall.Kill(proc.Process.Pid, syscall.SIGSTOP)
|
||||
|
||||
dbgproc, err = proctl.NewDebugProcess(proc.Process.Pid)
|
||||
if err != nil {
|
||||
@ -98,7 +99,7 @@ func main() {
|
||||
|
||||
if cmdstr == "exit" {
|
||||
err := goreadline.WriteHistoryToFile(historyFile)
|
||||
fmt.Println(err)
|
||||
fmt.Println("readline:", err)
|
||||
handleExit(t, dbgproc, 0)
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,8 @@ func buildBinary(t *testing.T) {
|
||||
|
||||
func startDebugger(t *testing.T, pid int) *os.Process {
|
||||
cmd := exec.Command("sudo", "./dbg-test", "-pid", strconv.Itoa(pid))
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Stdin = bytes.NewBufferString("exit\ny\n")
|
||||
|
||||
err := cmd.Start()
|
||||
|
@ -3,20 +3,15 @@
|
||||
package proctl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"debug/gosym"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/derekparker/delve/dwarf/frame"
|
||||
"github.com/derekparker/delve/dwarf/op"
|
||||
"github.com/derekparker/delve/vendor/dwarf"
|
||||
"github.com/derekparker/delve/vendor/elf"
|
||||
)
|
||||
|
||||
@ -24,76 +19,161 @@ import (
|
||||
// process struct and process state.
|
||||
type DebuggedProcess struct {
|
||||
Pid int
|
||||
Regs *syscall.PtraceRegs
|
||||
Process *os.Process
|
||||
ProcessState *syscall.WaitStatus
|
||||
Executable *elf.File
|
||||
Symbols []elf.Symbol
|
||||
GoSymTable *gosym.Table
|
||||
FrameEntries *frame.FrameDescriptionEntries
|
||||
BreakPoints map[uint64]*BreakPoint
|
||||
Threads map[int]*ThreadContext
|
||||
CurrentThread *ThreadContext
|
||||
}
|
||||
|
||||
// Represents a single breakpoint. Stores information on the break
|
||||
// point including the byte of data that originally was stored at that
|
||||
// address.
|
||||
type BreakPoint struct {
|
||||
FunctionName string
|
||||
File string
|
||||
Line int
|
||||
Addr uint64
|
||||
OriginalData []byte
|
||||
}
|
||||
func AttachBinary(name string) (*DebuggedProcess, error) {
|
||||
proc := exec.Command(name)
|
||||
proc.Stdout = os.Stdout
|
||||
|
||||
type Variable struct {
|
||||
Name string
|
||||
Value string
|
||||
Type string
|
||||
}
|
||||
err := proc.Start()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
type BreakPointExistsError struct {
|
||||
file string
|
||||
line int
|
||||
addr uintptr
|
||||
}
|
||||
dbgproc, err := NewDebugProcess(proc.Process.Pid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (bpe BreakPointExistsError) Error() string {
|
||||
return fmt.Sprintf("Breakpoint exists at %s:%d at %x", bpe.file, bpe.line, bpe.addr)
|
||||
return dbgproc, nil
|
||||
}
|
||||
|
||||
// Returns a new DebuggedProcess struct with sensible defaults.
|
||||
func NewDebugProcess(pid int) (*DebuggedProcess, error) {
|
||||
debuggedProc := DebuggedProcess{
|
||||
Pid: pid,
|
||||
Threads: make(map[int]*ThreadContext),
|
||||
}
|
||||
|
||||
_, err := debuggedProc.AttachThread(pid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proc, err := os.FindProcess(pid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = syscall.PtraceAttach(pid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ps, err := wait(proc.Pid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
debuggedProc := DebuggedProcess{
|
||||
Pid: pid,
|
||||
Regs: new(syscall.PtraceRegs),
|
||||
Process: proc,
|
||||
ProcessState: ps,
|
||||
BreakPoints: make(map[uint64]*BreakPoint),
|
||||
}
|
||||
|
||||
debuggedProc.Process = proc
|
||||
err = debuggedProc.LoadInformation()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: for some reason this isn't grabbing all threads, and
|
||||
// neither is the subsequent ptrace clone op. Maybe when
|
||||
// we attach the process is right in the middle of a clone syscall?
|
||||
for _, tid := range threadIds(pid) {
|
||||
if !debuggedProc.hasthread(tid) {
|
||||
_, err := debuggedProc.AttachThread(tid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &debuggedProc, nil
|
||||
}
|
||||
|
||||
func (dbp *DebuggedProcess) hasthread(tid int) bool {
|
||||
for _, t := range dbp.Threads {
|
||||
if tid == t.Id {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (dbp *DebuggedProcess) AttachThread(tid int) (*ThreadContext, error) {
|
||||
var (
|
||||
status syscall.WaitStatus
|
||||
)
|
||||
if thread, ok := dbp.Threads[tid]; ok {
|
||||
return thread, nil
|
||||
}
|
||||
|
||||
err := syscall.PtraceAttach(tid)
|
||||
if err != nil {
|
||||
if err != syscall.EPERM {
|
||||
// Do not return err if err == EPERM,
|
||||
// we may already be tracing this thread due to
|
||||
// PTRACE_O_TRACECLONE. We will surely blow up later
|
||||
// if we truly don't have permissions.
|
||||
return nil, fmt.Errorf("could not attach to new thread %d %s", tid, err)
|
||||
}
|
||||
} else {
|
||||
pid, e := syscall.Wait4(tid, &status, syscall.WALL, nil)
|
||||
if e != nil {
|
||||
return nil, err
|
||||
}
|
||||
// pid, status, err = wait(dbp, tid, 0)
|
||||
if err != nil && err != syscall.ECHILD {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if pid != 0 && status.Exited() {
|
||||
return nil, fmt.Errorf("thread already exited %d", tid)
|
||||
}
|
||||
}
|
||||
|
||||
return dbp.addThread(tid)
|
||||
}
|
||||
|
||||
func (dbp *DebuggedProcess) addThread(tid int) (*ThreadContext, error) {
|
||||
err := syscall.PtraceSetOptions(tid, syscall.PTRACE_O_TRACECLONE)
|
||||
if err != nil {
|
||||
var status syscall.WaitStatus
|
||||
pid, e := syscall.Wait4(tid, &status, syscall.WALL, nil)
|
||||
if e != nil {
|
||||
if status.Exited() {
|
||||
return nil, ProcessExitedError{tid}
|
||||
}
|
||||
return nil, fmt.Errorf("error while waiting after adding thread: %d %s %v %d", tid, e, status.Exited(), status.TrapCause())
|
||||
}
|
||||
|
||||
if pid != 0 {
|
||||
err := syscall.PtraceSetOptions(tid, syscall.PTRACE_O_TRACECLONE)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not set options for new traced thread %d %s", tid, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tctxt := &ThreadContext{
|
||||
Id: tid,
|
||||
Process: dbp,
|
||||
Regs: new(syscall.PtraceRegs),
|
||||
BreakPoints: make(map[uint64]*BreakPoint),
|
||||
}
|
||||
|
||||
if tid == dbp.Pid {
|
||||
dbp.CurrentThread = tctxt
|
||||
}
|
||||
|
||||
dbp.Threads[tid] = tctxt
|
||||
|
||||
return tctxt, nil
|
||||
}
|
||||
|
||||
// Returns the status of the current main thread context.
|
||||
func (dbp *DebuggedProcess) Status() *syscall.WaitStatus {
|
||||
return dbp.CurrentThread.Status
|
||||
}
|
||||
|
||||
// Returns the breakpoints of the current main thread context.
|
||||
func (dbp *DebuggedProcess) BreakPoints() map[uint64]*BreakPoint {
|
||||
return dbp.CurrentThread.BreakPoints
|
||||
}
|
||||
|
||||
// Finds the executable from /proc/<pid>/exe and then
|
||||
// uses that to parse the following information:
|
||||
// * Dwarf .debug_frame section
|
||||
@ -119,14 +199,54 @@ func (dbp *DebuggedProcess) LoadInformation() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Obtains register values from the debugged process.
|
||||
func (dbp *DebuggedProcess) Registers() (*syscall.PtraceRegs, error) {
|
||||
err := syscall.PtraceGetRegs(dbp.Pid, dbp.Regs)
|
||||
// Steps through process.
|
||||
func (dbp *DebuggedProcess) Step() (err error) {
|
||||
for _, thread := range dbp.Threads {
|
||||
err := thread.Step()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Registers():", err)
|
||||
if _, ok := err.(ProcessExitedError); !ok {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dbp.Regs, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// Step over function calls.
|
||||
func (dbp *DebuggedProcess) Next() error {
|
||||
for _, thread := range dbp.Threads {
|
||||
err := thread.Next()
|
||||
if _, ok := err.(ProcessExitedError); !ok {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Continue process until next breakpoint.
|
||||
func (dbp *DebuggedProcess) Continue() error {
|
||||
for _, thread := range dbp.Threads {
|
||||
err := thread.Continue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
_, _, err := wait(dbp, -1, 0)
|
||||
if err != nil {
|
||||
if _, ok := err.(ProcessExitedError); !ok {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Obtains register values from the debugged process.
|
||||
func (dbp *DebuggedProcess) Registers() (*syscall.PtraceRegs, error) {
|
||||
return dbp.CurrentThread.Registers()
|
||||
}
|
||||
|
||||
type InvalidAddressError struct {
|
||||
@ -139,485 +259,21 @@ func (iae InvalidAddressError) Error() string {
|
||||
|
||||
// Sets a breakpoint in the running process.
|
||||
func (dbp *DebuggedProcess) Break(addr uintptr) (*BreakPoint, error) {
|
||||
var (
|
||||
int3 = []byte{0xCC}
|
||||
f, l, fn = dbp.GoSymTable.PCToLine(uint64(addr))
|
||||
originalData = make([]byte, 1)
|
||||
)
|
||||
|
||||
if fn == nil {
|
||||
return nil, InvalidAddressError{address: addr}
|
||||
}
|
||||
|
||||
_, err := syscall.PtracePeekData(dbp.Pid, addr, originalData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if bytes.Equal(originalData, int3) {
|
||||
return nil, BreakPointExistsError{f, l, addr}
|
||||
}
|
||||
|
||||
_, err = syscall.PtracePokeData(dbp.Pid, addr, int3)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
breakpoint := &BreakPoint{
|
||||
FunctionName: fn.Name,
|
||||
File: f,
|
||||
Line: l,
|
||||
Addr: uint64(addr),
|
||||
OriginalData: originalData,
|
||||
}
|
||||
|
||||
dbp.BreakPoints[uint64(addr)] = breakpoint
|
||||
|
||||
return breakpoint, nil
|
||||
return dbp.CurrentThread.Break(addr)
|
||||
}
|
||||
|
||||
// Clears a breakpoint.
|
||||
func (dbp *DebuggedProcess) Clear(pc uint64) (*BreakPoint, error) {
|
||||
bp, ok := dbp.BreakPoints[pc]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("No breakpoint currently set for %#v", pc)
|
||||
}
|
||||
|
||||
_, err := syscall.PtracePokeData(dbp.Pid, uintptr(bp.Addr), bp.OriginalData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
delete(dbp.BreakPoints, pc)
|
||||
|
||||
return bp, nil
|
||||
}
|
||||
|
||||
// Steps through process.
|
||||
func (dbp *DebuggedProcess) Step() (err error) {
|
||||
regs, err := dbp.Registers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bp, ok := dbp.BreakPoints[regs.PC()-1]
|
||||
if ok {
|
||||
// Clear the breakpoint so that we can continue execution.
|
||||
_, err = dbp.Clear(bp.Addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Reset program counter to our restored instruction.
|
||||
regs.SetPC(bp.Addr)
|
||||
err = syscall.PtraceSetRegs(dbp.Pid, regs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Restore breakpoint now that we have passed it.
|
||||
defer func() {
|
||||
_, err = dbp.Break(uintptr(bp.Addr))
|
||||
}()
|
||||
}
|
||||
|
||||
err = dbp.handleResult(syscall.PtraceSingleStep(dbp.Pid))
|
||||
if err != nil {
|
||||
return fmt.Errorf("step failed: ", err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Step over function calls.
|
||||
func (dbp *DebuggedProcess) Next() error {
|
||||
pc, err := dbp.CurrentPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, ok := dbp.BreakPoints[pc-1]; ok {
|
||||
// Decrement the PC to be before
|
||||
// the breakpoint instruction.
|
||||
pc--
|
||||
}
|
||||
|
||||
_, l, _ := dbp.GoSymTable.PCToLine(pc)
|
||||
fde, err := dbp.FrameEntries.FDEForPC(pc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
step := func() (uint64, error) {
|
||||
err = dbp.Step()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("next stepping failed: ", err.Error())
|
||||
}
|
||||
|
||||
return dbp.CurrentPC()
|
||||
}
|
||||
|
||||
ret := dbp.ReturnAddressFromOffset(fde.ReturnAddressOffset(pc))
|
||||
for {
|
||||
pc, err = step()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !fde.Cover(pc) && pc != ret {
|
||||
dbp.continueToReturnAddress(pc, fde)
|
||||
if err != nil {
|
||||
if ierr, ok := err.(InvalidAddressError); ok {
|
||||
return ierr
|
||||
}
|
||||
}
|
||||
|
||||
pc, _ = dbp.CurrentPC()
|
||||
}
|
||||
|
||||
_, nl, _ := dbp.GoSymTable.PCToLine(pc)
|
||||
if nl != l {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dbp *DebuggedProcess) 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 := dbp.ReturnAddressFromOffset(0)
|
||||
bp, err := dbp.Break(uintptr(addr))
|
||||
if err != nil {
|
||||
if _, ok := err.(BreakPointExistsError); !ok {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = dbp.Continue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = dbp.clearTempBreakpoint(bp.Addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pc, _ = dbp.CurrentPC()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Continue process until next breakpoint.
|
||||
func (dbp *DebuggedProcess) Continue() error {
|
||||
// Stepping first will ensure we are able to continue
|
||||
// past a breakpoint if that's currently where we are stopped.
|
||||
err := dbp.Step()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return dbp.handleResult(syscall.PtraceCont(dbp.Pid, 0))
|
||||
return dbp.CurrentThread.Clear(pc)
|
||||
}
|
||||
|
||||
func (dbp *DebuggedProcess) CurrentPC() (uint64, error) {
|
||||
regs, err := dbp.Registers()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return regs.Rip, nil
|
||||
}
|
||||
|
||||
func (dbp *DebuggedProcess) clearTempBreakpoint(pc uint64) error {
|
||||
if bp, ok := dbp.BreakPoints[pc]; ok {
|
||||
regs, err := dbp.Registers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Reset program counter to our restored instruction.
|
||||
bp, err = dbp.Clear(bp.Addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
regs.SetPC(bp.Addr)
|
||||
return syscall.PtraceSetRegs(dbp.Pid, regs)
|
||||
}
|
||||
|
||||
return nil
|
||||
return dbp.CurrentThread.CurrentPC()
|
||||
}
|
||||
|
||||
// Returns the value of the named symbol.
|
||||
func (dbp *DebuggedProcess) EvalSymbol(name string) (*Variable, error) {
|
||||
data, err := dbp.Executable.DWARF()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reader := data.Reader()
|
||||
|
||||
for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if entry.Tag != dwarf.TagVariable && entry.Tag != dwarf.TagFormalParameter {
|
||||
continue
|
||||
}
|
||||
|
||||
n, ok := entry.Val(dwarf.AttrName).(string)
|
||||
if !ok || n != name {
|
||||
continue
|
||||
}
|
||||
|
||||
offset, ok := entry.Val(dwarf.AttrType).(dwarf.Offset)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
t, err := data.Type(offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
instructions, ok := entry.Val(dwarf.AttrLocation).([]byte)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
val, err := dbp.extractValue(instructions, 0, t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Variable{Name: n, Type: t.String(), Value: val}, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("could not find symbol value for %s", name)
|
||||
}
|
||||
|
||||
// Extracts the value from the instructions given in the DW_AT_location entry.
|
||||
// We execute the stack program described in the DW_OP_* instruction stream, and
|
||||
// then grab the value from the other processes memory.
|
||||
func (dbp *DebuggedProcess) extractValue(instructions []byte, off int64, typ interface{}) (string, error) {
|
||||
regs, err := dbp.Registers()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
fde, err := dbp.FrameEntries.FDEForPC(regs.PC())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
fctx := fde.EstablishFrame(regs.PC())
|
||||
cfaOffset := fctx.CFAOffset()
|
||||
|
||||
offset := off
|
||||
if off == 0 {
|
||||
offset, err = op.ExecuteStackProgram(cfaOffset, instructions)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
offset = int64(regs.Rsp) + offset
|
||||
}
|
||||
|
||||
// If we have a user defined type, find the
|
||||
// underlying concrete type and use that.
|
||||
if tt, ok := typ.(*dwarf.TypedefType); ok {
|
||||
typ = tt.Type
|
||||
}
|
||||
|
||||
offaddr := uintptr(offset)
|
||||
switch t := typ.(type) {
|
||||
case *dwarf.PtrType:
|
||||
addr, err := dbp.readMemory(offaddr, 8)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
adr := binary.LittleEndian.Uint64(addr)
|
||||
val, err := dbp.extractValue(nil, int64(adr), t.Type)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
retstr := fmt.Sprintf("*%s", val)
|
||||
return retstr, nil
|
||||
case *dwarf.StructType:
|
||||
switch t.StructName {
|
||||
case "string":
|
||||
return dbp.readString(offaddr)
|
||||
case "[]int":
|
||||
return dbp.readIntSlice(offaddr)
|
||||
default:
|
||||
// Here we could recursively call extractValue to grab
|
||||
// the value of all the members of the struct.
|
||||
fields := make([]string, 0, len(t.Field))
|
||||
for _, field := range t.Field {
|
||||
val, err := dbp.extractValue(nil, field.ByteOffset+offset, field.Type)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
fields = append(fields, fmt.Sprintf("%s: %s", field.Name, val))
|
||||
}
|
||||
retstr := fmt.Sprintf("%s {%s}", t.StructName, strings.Join(fields, ", "))
|
||||
return retstr, nil
|
||||
}
|
||||
case *dwarf.ArrayType:
|
||||
return dbp.readIntArray(offaddr, t)
|
||||
case *dwarf.IntType:
|
||||
return dbp.readInt(offaddr)
|
||||
case *dwarf.FloatType:
|
||||
return dbp.readFloat64(offaddr)
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("could not find value for type %s", typ)
|
||||
}
|
||||
|
||||
func (dbp *DebuggedProcess) readString(addr uintptr) (string, error) {
|
||||
val, err := dbp.readMemory(addr, 8)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// deref the pointer to the string
|
||||
addr = uintptr(binary.LittleEndian.Uint64(val))
|
||||
val, err = dbp.readMemory(addr, 16)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
i := bytes.IndexByte(val, 0x0)
|
||||
val = val[:i]
|
||||
str := *(*string)(unsafe.Pointer(&val))
|
||||
return str, nil
|
||||
}
|
||||
|
||||
func (dbp *DebuggedProcess) readIntSlice(addr uintptr) (string, error) {
|
||||
var number uint64
|
||||
|
||||
val, err := dbp.readMemory(addr, uintptr(24))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
a := binary.LittleEndian.Uint64(val[:8])
|
||||
l := binary.LittleEndian.Uint64(val[8:16])
|
||||
c := binary.LittleEndian.Uint64(val[16:24])
|
||||
|
||||
val, err = dbp.readMemory(uintptr(a), uintptr(8*l))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
members := make([]uint64, 0, l)
|
||||
buf := bytes.NewBuffer(val)
|
||||
for {
|
||||
err := binary.Read(buf, binary.LittleEndian, &number)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
members = append(members, number)
|
||||
}
|
||||
|
||||
str := fmt.Sprintf("len: %d cap: %d %d", l, c, members)
|
||||
|
||||
return str, err
|
||||
}
|
||||
|
||||
func (dbp *DebuggedProcess) readIntArray(addr uintptr, t *dwarf.ArrayType) (string, error) {
|
||||
var (
|
||||
number uint64
|
||||
members = make([]uint64, 0, t.ByteSize)
|
||||
)
|
||||
|
||||
val, err := dbp.readMemory(addr, uintptr(t.ByteSize))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(val)
|
||||
for {
|
||||
err := binary.Read(buf, binary.LittleEndian, &number)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
members = append(members, number)
|
||||
}
|
||||
|
||||
str := fmt.Sprintf("[%d]int %d", t.ByteSize/8, members)
|
||||
|
||||
return str, nil
|
||||
}
|
||||
|
||||
func (dbp *DebuggedProcess) readInt(addr uintptr) (string, error) {
|
||||
val, err := dbp.readMemory(addr, 8)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
n := binary.LittleEndian.Uint64(val)
|
||||
|
||||
return strconv.Itoa(int(n)), nil
|
||||
}
|
||||
|
||||
func (dbp *DebuggedProcess) readFloat64(addr uintptr) (string, error) {
|
||||
var n float64
|
||||
val, err := dbp.readMemory(addr, 8)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
buf := bytes.NewBuffer(val)
|
||||
binary.Read(buf, binary.LittleEndian, &n)
|
||||
|
||||
return strconv.FormatFloat(n, 'f', -1, 64), nil
|
||||
}
|
||||
|
||||
func (dbp *DebuggedProcess) readMemory(addr uintptr, size uintptr) ([]byte, error) {
|
||||
buf := make([]byte, size)
|
||||
|
||||
_, err := syscall.PtracePeekData(dbp.Pid, addr, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (dbp *DebuggedProcess) handleResult(err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ps, err := wait(dbp.Process.Pid)
|
||||
if err != nil && err != syscall.ECHILD {
|
||||
return err
|
||||
}
|
||||
|
||||
if ps != nil {
|
||||
dbp.ProcessState = ps
|
||||
if ps.TrapCause() == -1 && !ps.Exited() {
|
||||
regs, err := dbp.Registers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("traced program %s at: %#v\n", ps.StopSignal(), regs.PC())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return dbp.CurrentThread.EvalSymbol(name)
|
||||
}
|
||||
|
||||
func (dbp *DebuggedProcess) findExecutable() error {
|
||||
@ -699,14 +355,99 @@ func (dbp *DebuggedProcess) ReturnAddressFromOffset(offset int64) uint64 {
|
||||
return binary.LittleEndian.Uint64(data)
|
||||
}
|
||||
|
||||
func wait(pid int) (*syscall.WaitStatus, error) {
|
||||
var status syscall.WaitStatus
|
||||
var rusage syscall.Rusage
|
||||
type ProcessExitedError struct {
|
||||
pid int
|
||||
}
|
||||
|
||||
_, e := syscall.Wait4(pid, &status, 0, &rusage)
|
||||
func (pe ProcessExitedError) Error() string {
|
||||
return fmt.Sprintf("process %d has exited", pe.pid)
|
||||
}
|
||||
|
||||
func wait(dbp *DebuggedProcess, pid int, options int) (int, *syscall.WaitStatus, error) {
|
||||
var (
|
||||
status syscall.WaitStatus
|
||||
)
|
||||
|
||||
for {
|
||||
pid, e := syscall.Wait4(-1, &status, syscall.WALL|options, nil)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
return -1, nil, fmt.Errorf("wait err %s %d", e, pid)
|
||||
}
|
||||
|
||||
return &status, nil
|
||||
for _, th := range dbp.Threads {
|
||||
if th.Id == pid {
|
||||
th.Status = &status
|
||||
}
|
||||
}
|
||||
|
||||
if status.Exited() {
|
||||
if pid == dbp.Pid {
|
||||
return 0, nil, ProcessExitedError{pid}
|
||||
}
|
||||
|
||||
for _, th := range dbp.Threads {
|
||||
if th.Id == pid {
|
||||
delete(dbp.Threads, pid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if status.StopSignal() == syscall.SIGTRAP {
|
||||
if status.TrapCause() == syscall.PTRACE_EVENT_CLONE {
|
||||
msg, err := syscall.PtraceGetEventMsg(pid)
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("could not get event message: %s", err)
|
||||
}
|
||||
|
||||
_, err = dbp.addThread(int(msg))
|
||||
if err != nil {
|
||||
if _, ok := err.(ProcessExitedError); ok {
|
||||
continue
|
||||
}
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
err = syscall.PtraceCont(int(msg), 0)
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("could not continue new thread %d %s", msg, err)
|
||||
}
|
||||
|
||||
err = syscall.PtraceCont(pid, 0)
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("could not continue stopped thread %d %s", pid, err)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if pid != dbp.CurrentThread.Id {
|
||||
fmt.Printf("changed thread context from %d to %d\n", dbp.CurrentThread.Id, pid)
|
||||
thread, _ := dbp.Threads[pid]
|
||||
dbp.CurrentThread = thread
|
||||
}
|
||||
|
||||
for _, th := range dbp.Threads {
|
||||
if th.Id == pid {
|
||||
pc, _ := th.CurrentPC()
|
||||
_, ok := th.BreakPoints[pc-1]
|
||||
if ok {
|
||||
return pid, &status, nil
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return pid, &status, nil
|
||||
}
|
||||
|
||||
if status.Stopped() {
|
||||
if pid == dbp.Pid {
|
||||
return pid, &status, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0, nil, nil
|
||||
}
|
||||
|
@ -42,20 +42,8 @@ func currentLineNumber(p *proctl.DebuggedProcess, t *testing.T) (string, int) {
|
||||
return f, l
|
||||
}
|
||||
|
||||
func TestAttachProcess(t *testing.T) {
|
||||
helper.WithTestProcess("../_fixtures/testprog", t, func(p *proctl.DebuggedProcess) {
|
||||
if !p.ProcessState.Stopped() {
|
||||
t.Errorf("Process was not stopped correctly")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestStep(t *testing.T) {
|
||||
helper.WithTestProcess("../_fixtures/testprog", t, func(p *proctl.DebuggedProcess) {
|
||||
if p.ProcessState.Exited() {
|
||||
t.Fatal("Process already exited")
|
||||
}
|
||||
|
||||
regs := helper.GetRegisters(p, t)
|
||||
rip := regs.PC()
|
||||
|
||||
@ -71,22 +59,22 @@ func TestStep(t *testing.T) {
|
||||
|
||||
func TestContinue(t *testing.T) {
|
||||
helper.WithTestProcess("../_fixtures/continuetestprog", t, func(p *proctl.DebuggedProcess) {
|
||||
if p.ProcessState.Exited() {
|
||||
t.Fatal("Process already exited")
|
||||
err := p.Continue()
|
||||
if err != nil {
|
||||
if _, ok := err.(proctl.ProcessExitedError); !ok {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
err := p.Continue()
|
||||
assertNoError(err, t, "Continue()")
|
||||
|
||||
if p.ProcessState.ExitStatus() != 0 {
|
||||
t.Fatal("Process did not exit successfully")
|
||||
if p.Status().ExitStatus() != 0 {
|
||||
t.Fatal("Process did not exit successfully", p.Status().ExitStatus())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestBreakPoint(t *testing.T) {
|
||||
helper.WithTestProcess("../_fixtures/testprog", t, func(p *proctl.DebuggedProcess) {
|
||||
sleepytimefunc := p.GoSymTable.LookupFunc("main.sleepytime")
|
||||
sleepytimefunc := p.GoSymTable.LookupFunc("main.helloworld")
|
||||
sleepyaddr := sleepytimefunc.Entry
|
||||
|
||||
bp, err := p.Break(uintptr(sleepyaddr))
|
||||
@ -96,25 +84,59 @@ func TestBreakPoint(t *testing.T) {
|
||||
err = p.Continue()
|
||||
assertNoError(err, t, "Continue()")
|
||||
|
||||
regs := helper.GetRegisters(p, t)
|
||||
pc, err := p.CurrentPC()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
pc := regs.PC()
|
||||
if pc != breakpc {
|
||||
t.Fatalf("Break not respected:\nPC:%d\nFN:%d\n", pc, breakpc)
|
||||
f, l, _ := p.GoSymTable.PCToLine(pc)
|
||||
t.Fatalf("Break not respected:\nPC:%#v %s:%d\nFN:%#v \n", pc, f, l, breakpc)
|
||||
}
|
||||
|
||||
err = p.Step()
|
||||
assertNoError(err, t, "Step()")
|
||||
|
||||
regs = helper.GetRegisters(p, t)
|
||||
pc, err = p.CurrentPC()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
pc = regs.PC()
|
||||
if pc == breakpc {
|
||||
t.Fatalf("Step not respected:\nPC:%d\nFN:%d\n", pc, breakpc)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestBreakPointInSeperateGoRoutine(t *testing.T) {
|
||||
helper.WithTestProcess("../_fixtures/testthreads", t, func(p *proctl.DebuggedProcess) {
|
||||
fn := p.GoSymTable.LookupFunc("main.anotherthread")
|
||||
if fn == nil {
|
||||
t.Fatal("No fn exists")
|
||||
}
|
||||
|
||||
_, err := p.Break(uintptr(fn.Entry))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = p.Continue()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
pc, err := p.CurrentPC()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
f, l, _ := p.GoSymTable.PCToLine(pc)
|
||||
if f != "testthreads.go" && l != 8 {
|
||||
t.Fatal("Program did not hit breakpoint")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestBreakPointWithNonExistantFunction(t *testing.T) {
|
||||
helper.WithTestProcess("../_fixtures/testprog", t, func(p *proctl.DebuggedProcess) {
|
||||
_, err := p.Break(uintptr(0))
|
||||
@ -147,7 +169,7 @@ func TestClearBreakPoint(t *testing.T) {
|
||||
t.Fatalf("Breakpoint was not cleared data: %#v, int3: %#v", data, int3)
|
||||
}
|
||||
|
||||
if len(p.BreakPoints) != 0 {
|
||||
if len(p.BreakPoints()) != 0 {
|
||||
t.Fatal("Breakpoint not removed internally")
|
||||
}
|
||||
})
|
||||
@ -207,7 +229,7 @@ func TestNext(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if len(p.BreakPoints) != 1 {
|
||||
if len(p.BreakPoints()) != 1 {
|
||||
t.Fatal("Not all breakpoints were cleaned up")
|
||||
}
|
||||
})
|
||||
|
604
proctl/threads_linux_amd64.go
Normal file
604
proctl/threads_linux_amd64.go
Normal file
@ -0,0 +1,604 @@
|
||||
package proctl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/derekparker/delve/dwarf/frame"
|
||||
"github.com/derekparker/delve/dwarf/op"
|
||||
"github.com/derekparker/delve/vendor/dwarf"
|
||||
)
|
||||
|
||||
// ThreadContext represents a single thread of execution in the
|
||||
// traced program.
|
||||
type ThreadContext struct {
|
||||
Id int
|
||||
Process *DebuggedProcess
|
||||
Status *syscall.WaitStatus
|
||||
Regs *syscall.PtraceRegs
|
||||
BreakPoints map[uint64]*BreakPoint
|
||||
}
|
||||
|
||||
// Represents a single breakpoint. Stores information on the break
|
||||
// point including the byte of data that originally was stored at that
|
||||
// address.
|
||||
type BreakPoint struct {
|
||||
FunctionName string
|
||||
File string
|
||||
Line int
|
||||
Addr uint64
|
||||
OriginalData []byte
|
||||
}
|
||||
|
||||
type Variable struct {
|
||||
Name string
|
||||
Value string
|
||||
Type string
|
||||
}
|
||||
|
||||
type BreakPointExistsError struct {
|
||||
file string
|
||||
line int
|
||||
addr uintptr
|
||||
}
|
||||
|
||||
func (bpe BreakPointExistsError) Error() string {
|
||||
return fmt.Sprintf("Breakpoint exists at %s:%d at %x", bpe.file, bpe.line, bpe.addr)
|
||||
}
|
||||
|
||||
// Obtains register values from the debugged process.
|
||||
func (thread *ThreadContext) Registers() (*syscall.PtraceRegs, error) {
|
||||
err := syscall.PtraceGetRegs(thread.Id, thread.Regs)
|
||||
if err != nil {
|
||||
syscall.Tgkill(thread.Process.Pid, thread.Id, syscall.SIGSTOP)
|
||||
err = thread.wait()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err := syscall.PtraceGetRegs(thread.Id, thread.Regs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Registers(): %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return thread.Regs, nil
|
||||
}
|
||||
|
||||
func (thread *ThreadContext) CurrentPC() (uint64, error) {
|
||||
regs, err := thread.Registers()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return regs.PC(), nil
|
||||
}
|
||||
|
||||
// Sets a breakpoint in the running process.
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if bytes.Equal(originalData, int3) {
|
||||
return nil, BreakPointExistsError{f, l, addr}
|
||||
}
|
||||
|
||||
_, err = syscall.PtracePokeData(thread.Id, addr, int3)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
breakpoint := &BreakPoint{
|
||||
FunctionName: fn.Name,
|
||||
File: f,
|
||||
Line: l,
|
||||
Addr: uint64(addr),
|
||||
OriginalData: originalData,
|
||||
}
|
||||
|
||||
thread.BreakPoints[uint64(addr)] = breakpoint
|
||||
|
||||
return breakpoint, nil
|
||||
}
|
||||
|
||||
// Clears a breakpoint.
|
||||
func (thread *ThreadContext) Clear(pc uint64) (*BreakPoint, error) {
|
||||
bp, ok := thread.BreakPoints[pc]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("No breakpoint currently set for %#v", pc)
|
||||
}
|
||||
|
||||
_, err := syscall.PtracePokeData(thread.Id, uintptr(bp.Addr), bp.OriginalData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
delete(thread.BreakPoints, pc)
|
||||
|
||||
return bp, nil
|
||||
}
|
||||
|
||||
func (thread *ThreadContext) Continue() error {
|
||||
// Stepping first will ensure we are able to continue
|
||||
// past a breakpoint if that's currently where we are stopped.
|
||||
err := thread.Step()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return syscall.PtraceCont(thread.Id, 0)
|
||||
}
|
||||
|
||||
// Steps through thread of execution.
|
||||
func (thread *ThreadContext) Step() (err error) {
|
||||
regs, err := thread.Registers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bp, ok := thread.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 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: ", err.Error())
|
||||
}
|
||||
|
||||
_, _, err = wait(thread.Process, thread.Id, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("step failed: ", err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Steps through thread of execution.
|
||||
func (thread *ThreadContext) Next() (err error) {
|
||||
pc, err := thread.CurrentPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, ok := thread.BreakPoints[pc-1]; ok {
|
||||
// Decrement the PC to be before
|
||||
// the breakpoint instruction.
|
||||
pc--
|
||||
}
|
||||
|
||||
_, 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, fmt.Errorf("next stepping failed: ", err.Error())
|
||||
}
|
||||
|
||||
return thread.CurrentPC()
|
||||
}
|
||||
|
||||
ret := thread.Process.ReturnAddressFromOffset(fde.ReturnAddressOffset(pc))
|
||||
for {
|
||||
pc, err = step()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !fde.Cover(pc) && pc != ret {
|
||||
thread.continueToReturnAddress(pc, fde)
|
||||
if err != nil {
|
||||
if ierr, ok := err.(InvalidAddressError); ok {
|
||||
return ierr
|
||||
}
|
||||
}
|
||||
|
||||
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.Process.ReturnAddressFromOffset(0)
|
||||
bp, err := thread.Break(uintptr(addr))
|
||||
if err != nil {
|
||||
if _, ok := err.(BreakPointExistsError); !ok {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = thread.Continue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = thread.wait()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = thread.clearTempBreakpoint(bp.Addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pc, _ = thread.CurrentPC()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (thread *ThreadContext) clearTempBreakpoint(pc uint64) error {
|
||||
if bp, ok := thread.BreakPoints[pc]; ok {
|
||||
regs, err := thread.Registers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Reset program counter to our restored instruction.
|
||||
bp, err = thread.Clear(bp.Addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
regs.SetPC(bp.Addr)
|
||||
return syscall.PtraceSetRegs(thread.Id, regs)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (thread *ThreadContext) wait() error {
|
||||
var status syscall.WaitStatus
|
||||
_, err := syscall.Wait4(thread.Id, &status, 0, nil)
|
||||
if err != nil {
|
||||
if status.Exited() {
|
||||
delete(thread.Process.Threads, thread.Id)
|
||||
return ProcessExitedError{thread.Id}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns the value of the named symbol.
|
||||
func (thread *ThreadContext) EvalSymbol(name string) (*Variable, error) {
|
||||
data, err := thread.Process.Executable.DWARF()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reader := data.Reader()
|
||||
|
||||
for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if entry.Tag != dwarf.TagVariable && entry.Tag != dwarf.TagFormalParameter {
|
||||
continue
|
||||
}
|
||||
|
||||
n, ok := entry.Val(dwarf.AttrName).(string)
|
||||
if !ok || n != name {
|
||||
continue
|
||||
}
|
||||
|
||||
offset, ok := entry.Val(dwarf.AttrType).(dwarf.Offset)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
t, err := data.Type(offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
instructions, ok := entry.Val(dwarf.AttrLocation).([]byte)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
val, err := thread.extractValue(instructions, 0, t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Variable{Name: n, Type: t.String(), Value: val}, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("could not find symbol value for %s", name)
|
||||
}
|
||||
|
||||
// Extracts the value from the instructions given in the DW_AT_location entry.
|
||||
// We execute the stack program described in the DW_OP_* instruction stream, and
|
||||
// then grab the value from the other processes memory.
|
||||
func (thread *ThreadContext) extractValue(instructions []byte, off int64, typ interface{}) (string, error) {
|
||||
regs, err := thread.Registers()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
fde, err := thread.Process.FrameEntries.FDEForPC(regs.PC())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
fctx := fde.EstablishFrame(regs.PC())
|
||||
cfaOffset := fctx.CFAOffset()
|
||||
|
||||
offset := off
|
||||
if off == 0 {
|
||||
offset, err = op.ExecuteStackProgram(cfaOffset, instructions)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
offset = int64(regs.Rsp) + offset
|
||||
}
|
||||
|
||||
// If we have a user defined type, find the
|
||||
// underlying concrete type and use that.
|
||||
if tt, ok := typ.(*dwarf.TypedefType); ok {
|
||||
typ = tt.Type
|
||||
}
|
||||
|
||||
offaddr := uintptr(offset)
|
||||
switch t := typ.(type) {
|
||||
case *dwarf.PtrType:
|
||||
addr, err := thread.readMemory(offaddr, 8)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
adr := binary.LittleEndian.Uint64(addr)
|
||||
val, err := thread.extractValue(nil, int64(adr), t.Type)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
retstr := fmt.Sprintf("*%s", val)
|
||||
return retstr, nil
|
||||
case *dwarf.StructType:
|
||||
switch t.StructName {
|
||||
case "string":
|
||||
return thread.readString(offaddr)
|
||||
case "[]int":
|
||||
return thread.readIntSlice(offaddr)
|
||||
default:
|
||||
// Here we could recursively call extractValue to grab
|
||||
// the value of all the members of the struct.
|
||||
fields := make([]string, 0, len(t.Field))
|
||||
for _, field := range t.Field {
|
||||
val, err := thread.extractValue(nil, field.ByteOffset+offset, field.Type)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
fields = append(fields, fmt.Sprintf("%s: %s", field.Name, val))
|
||||
}
|
||||
retstr := fmt.Sprintf("%s {%s}", t.StructName, strings.Join(fields, ", "))
|
||||
return retstr, nil
|
||||
}
|
||||
case *dwarf.ArrayType:
|
||||
return thread.readIntArray(offaddr, t)
|
||||
case *dwarf.IntType:
|
||||
return thread.readInt(offaddr)
|
||||
case *dwarf.FloatType:
|
||||
return thread.readFloat64(offaddr)
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("could not find value for type %s", typ)
|
||||
}
|
||||
|
||||
func (thread *ThreadContext) readString(addr uintptr) (string, error) {
|
||||
val, err := thread.readMemory(addr, 8)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// deref the pointer to the string
|
||||
addr = uintptr(binary.LittleEndian.Uint64(val))
|
||||
val, err = thread.readMemory(addr, 16)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
i := bytes.IndexByte(val, 0x0)
|
||||
val = val[:i]
|
||||
str := *(*string)(unsafe.Pointer(&val))
|
||||
return str, nil
|
||||
}
|
||||
|
||||
func (thread *ThreadContext) readIntSlice(addr uintptr) (string, error) {
|
||||
var number uint64
|
||||
|
||||
val, err := thread.readMemory(addr, uintptr(24))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
a := binary.LittleEndian.Uint64(val[:8])
|
||||
l := binary.LittleEndian.Uint64(val[8:16])
|
||||
c := binary.LittleEndian.Uint64(val[16:24])
|
||||
|
||||
val, err = thread.readMemory(uintptr(a), uintptr(8*l))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
members := make([]uint64, 0, l)
|
||||
buf := bytes.NewBuffer(val)
|
||||
for {
|
||||
err := binary.Read(buf, binary.LittleEndian, &number)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
members = append(members, number)
|
||||
}
|
||||
|
||||
str := fmt.Sprintf("len: %d cap: %d %d", l, c, members)
|
||||
|
||||
return str, err
|
||||
}
|
||||
|
||||
func (thread *ThreadContext) readIntArray(addr uintptr, t *dwarf.ArrayType) (string, error) {
|
||||
var (
|
||||
number uint64
|
||||
members = make([]uint64, 0, t.ByteSize)
|
||||
)
|
||||
|
||||
val, err := thread.readMemory(addr, uintptr(t.ByteSize))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(val)
|
||||
for {
|
||||
err := binary.Read(buf, binary.LittleEndian, &number)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
members = append(members, number)
|
||||
}
|
||||
|
||||
str := fmt.Sprintf("[%d]int %d", t.ByteSize/8, members)
|
||||
|
||||
return str, nil
|
||||
}
|
||||
|
||||
func (thread *ThreadContext) readInt(addr uintptr) (string, error) {
|
||||
val, err := thread.readMemory(addr, 8)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
n := binary.LittleEndian.Uint64(val)
|
||||
|
||||
return strconv.Itoa(int(n)), nil
|
||||
}
|
||||
|
||||
func (thread *ThreadContext) readFloat64(addr uintptr) (string, error) {
|
||||
var n float64
|
||||
val, err := thread.readMemory(addr, 8)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
buf := bytes.NewBuffer(val)
|
||||
binary.Read(buf, binary.LittleEndian, &n)
|
||||
|
||||
return strconv.FormatFloat(n, 'f', -1, 64), nil
|
||||
}
|
||||
|
||||
func (thread *ThreadContext) readMemory(addr uintptr, size uintptr) ([]byte, error) {
|
||||
buf := make([]byte, size)
|
||||
|
||||
_, err := syscall.PtracePeekData(thread.Id, addr, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (thread *ThreadContext) handleResult(err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, ps, err := wait(thread.Process, thread.Id, 0)
|
||||
if err != nil && err != syscall.ECHILD {
|
||||
return err
|
||||
}
|
||||
|
||||
if ps != nil {
|
||||
thread.Status = ps
|
||||
if ps.TrapCause() == -1 && !ps.Exited() {
|
||||
regs, err := thread.Registers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("traced program %s at: %#v\n", ps.StopSignal(), regs.PC())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func threadIds(pid int) []int {
|
||||
var threads []int
|
||||
dir, err := os.Open(fmt.Sprintf("/proc/%d/task", pid))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer dir.Close()
|
||||
|
||||
names, err := dir.Readdirnames(0)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _, strid := range names {
|
||||
tid, err := strconv.Atoi(strid)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
threads = append(threads, tid)
|
||||
}
|
||||
|
||||
return threads
|
||||
}
|
Loading…
Reference in New Issue
Block a user