delve/proctl/proctl_linux_amd64.go

703 lines
15 KiB
Go
Raw Normal View History

2014-05-21 15:23:14 +00:00
// Package proctl provides functions for attaching to and manipulating
// a process during the debug session.
2014-05-20 18:23:35 +00:00
package proctl
import (
"bytes"
"debug/gosym"
"encoding/binary"
2014-05-20 18:23:35 +00:00
"fmt"
"os"
2014-07-30 00:00:24 +00:00
"strconv"
"strings"
2014-08-06 16:14:23 +00:00
"sync"
2014-05-20 18:23:35 +00:00
"syscall"
2014-08-04 18:53:15 +00:00
"unsafe"
"github.com/derekparker/dbg/dwarf/frame"
"github.com/derekparker/dbg/dwarf/line"
2014-07-30 00:00:24 +00:00
"github.com/derekparker/dbg/dwarf/op"
"github.com/derekparker/dbg/vendor/dwarf"
"github.com/derekparker/dbg/vendor/elf"
2014-05-20 18:23:35 +00:00
)
2014-05-21 15:23:14 +00:00
// Struct representing a debugged process. Holds onto pid, register values,
// process struct and process state.
2014-05-20 18:23:35 +00:00
type DebuggedProcess struct {
2014-10-04 23:13:20 +00:00
Pid int
Regs *syscall.PtraceRegs
Process *os.Process
ProcessState *syscall.WaitStatus
Executable *elf.File
Symbols []elf.Symbol
GoSymTable *gosym.Table
FrameEntries *frame.FrameDescriptionEntries
DebugLine *line.DebugLineInfo
BreakPoints map[uint64]*BreakPoint
2014-05-24 16:22:06 +00:00
}
2014-05-27 18:20:10 +00:00
// Represents a single breakpoint. Stores information on the break
2014-06-27 03:43:30 +00:00
// point including the byte of data that originally was stored at that
2014-05-27 18:20:10 +00:00
// address.
2014-05-24 16:22:06 +00:00
type BreakPoint struct {
FunctionName string
File string
2014-05-24 16:22:06 +00:00
Line int
Addr uint64
OriginalData []byte
2014-05-20 18:23:35 +00:00
}
2014-07-30 00:00:24 +00:00
type Variable struct {
Name string
Value string
Type string
}
2014-08-15 20:43:14 +00:00
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)
}
2014-05-21 15:23:14 +00:00
// Returns a new DebuggedProcess struct with sensible defaults.
2014-05-20 18:23:35 +00:00
func NewDebugProcess(pid int) (*DebuggedProcess, error) {
proc, err := os.FindProcess(pid)
2014-05-20 18:23:35 +00:00
if err != nil {
return nil, err
}
err = syscall.PtraceAttach(pid)
2014-05-20 18:23:35 +00:00
if err != nil {
return nil, err
}
ps, err := wait(proc.Pid)
2014-05-20 18:23:35 +00:00
if err != nil {
return nil, err
}
2014-05-20 18:23:36 +00:00
debuggedProc := DebuggedProcess{
2014-10-04 23:13:20 +00:00
Pid: pid,
Regs: new(syscall.PtraceRegs),
Process: proc,
ProcessState: ps,
BreakPoints: make(map[uint64]*BreakPoint),
2014-05-20 18:23:36 +00:00
}
err = debuggedProc.LoadInformation()
if err != nil {
return nil, err
}
2014-05-20 18:23:35 +00:00
return &debuggedProc, nil
}
2014-05-27 18:20:10 +00:00
// Finds the executable from /proc/<pid>/exe and then
// uses that to parse the following information:
// * Dwarf .debug_frame section
// * Dwarf .debug_line section
// * Go symbol table.
func (dbp *DebuggedProcess) LoadInformation() error {
2014-08-06 16:14:23 +00:00
var (
wg sync.WaitGroup
err error
)
2014-08-06 16:14:23 +00:00
err = dbp.findExecutable()
if err != nil {
return err
}
2014-08-06 16:14:23 +00:00
wg.Add(3)
go dbp.parseDebugFrame(&wg)
go dbp.parseDebugLine(&wg)
go dbp.obtainGoSymbols(&wg)
2014-08-06 16:14:23 +00:00
wg.Wait()
return nil
}
2014-05-21 15:23:14 +00:00
// Obtains register values from the debugged process.
2014-05-20 18:23:35 +00:00
func (dbp *DebuggedProcess) Registers() (*syscall.PtraceRegs, error) {
err := syscall.PtraceGetRegs(dbp.Pid, dbp.Regs)
if err != nil {
return nil, fmt.Errorf("Registers():", err)
}
return dbp.Regs, nil
}
// Sets a breakpoint in the running process.
2014-05-27 23:15:18 +00:00
func (dbp *DebuggedProcess) Break(addr uintptr) (*BreakPoint, error) {
var (
2014-05-29 15:19:42 +00:00
int3 = []byte{0xCC}
f, l, fn = dbp.GoSymTable.PCToLine(uint64(addr))
originalData = make([]byte, 1)
)
2014-05-29 15:19:42 +00:00
_, err := syscall.PtracePeekData(dbp.Pid, addr, originalData)
if err != nil {
return nil, err
2014-05-24 16:22:06 +00:00
}
if bytes.Equal(originalData, int3) {
return nil, BreakPointExistsError{f, l, addr}
}
_, err = syscall.PtracePokeData(dbp.Pid, addr, int3)
if err != nil {
2014-05-24 16:22:06 +00:00
return nil, err
}
2014-05-24 16:22:06 +00:00
breakpoint := &BreakPoint{
FunctionName: fn.Name,
File: f,
Line: l,
2014-05-27 23:15:18 +00:00
Addr: uint64(addr),
2014-05-29 15:19:42 +00:00
OriginalData: originalData,
2014-05-24 16:22:06 +00:00
}
dbp.BreakPoints[uint64(addr)] = breakpoint
2014-05-24 16:22:06 +00:00
return breakpoint, nil
}
// Clears a breakpoint.
2014-05-27 23:15:18 +00:00
func (dbp *DebuggedProcess) Clear(pc uint64) (*BreakPoint, error) {
bp, ok := dbp.PCtoBP(pc)
if !ok {
2014-09-05 19:20:12 +00:00
return nil, fmt.Errorf("No breakpoint currently set for %#v", pc)
}
2014-05-29 15:26:42 +00:00
_, err := syscall.PtracePokeData(dbp.Pid, uintptr(bp.Addr), bp.OriginalData)
if err != nil {
2014-05-27 23:15:18 +00:00
return nil, err
}
delete(dbp.BreakPoints, pc)
2014-05-27 23:15:18 +00:00
return bp, nil
}
2014-05-21 15:23:14 +00:00
// Steps through process.
2014-05-29 16:18:28 +00:00
func (dbp *DebuggedProcess) Step() (err error) {
regs, err := dbp.Registers()
if err != nil {
return err
}
2014-05-29 14:34:37 +00:00
bp, ok := dbp.PCtoBP(regs.PC() - 1)
if ok {
2014-05-29 16:18:28 +00:00
// Clear the breakpoint so that we can continue execution.
2014-05-29 14:34:37 +00:00
_, err = dbp.Clear(bp.Addr)
if err != nil {
return err
}
2014-05-29 15:27:03 +00:00
// Reset program counter to our restored instruction.
regs.SetPC(bp.Addr)
err = syscall.PtraceSetRegs(dbp.Pid, regs)
if err != nil {
return err
}
2014-05-29 16:18:28 +00:00
// 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
2014-05-20 18:23:35 +00:00
}
2014-05-20 18:23:36 +00:00
2014-09-05 19:19:03 +00:00
// Step over function calls.
func (dbp *DebuggedProcess) Next() error {
pc, err := dbp.CurrentPC()
if err != nil {
return err
}
2014-10-04 02:44:43 +00:00
if _, ok := dbp.BreakPoints[pc-1]; ok {
2014-10-07 12:57:34 +00:00
// Decrement the PC to be before
// the breakpoint instruction.
2014-10-04 02:44:43 +00:00
pc--
}
f, l, _ := dbp.GoSymTable.PCToLine(pc)
2014-10-04 02:44:43 +00:00
fde, err := dbp.FrameEntries.FDEForPC(pc)
if err != nil {
return err
}
loc := dbp.DebugLine.NextLocation(pc, f, l)
2014-10-04 02:44:43 +00:00
if !fde.AddressRange.Cover(loc.Address) {
// Unconditionally step out of current function
// Don't bother looking up ret addr, next line is
// outside of current fn, should only be a few
// instructions left to RET
for fde.AddressRange.Cover(pc) {
err = dbp.Step()
if err != nil {
return fmt.Errorf("next stepping failed: ", err.Error())
}
2014-10-04 02:44:43 +00:00
pc, err = dbp.CurrentPC()
if err != nil {
return err
}
2014-10-04 02:44:43 +00:00
}
return nil
2014-10-04 02:44:43 +00:00
}
for {
err = dbp.Step()
if err != nil {
return fmt.Errorf("next stepping failed: ", err.Error())
}
pc, err = dbp.CurrentPC()
if err != nil {
return err
}
if !fde.AddressRange.Cover(pc) {
// We've stepped into a function, keep going.
// TODO: Use DWARF frame info to continue to return address.
continue
}
_, nl, _ := dbp.GoSymTable.PCToLine(pc)
if nl != l {
break
2014-10-04 02:44:43 +00:00
}
2014-07-21 23:20:16 +00:00
}
return nil
}
2014-05-21 15:23:14 +00:00
// Continue process until next breakpoint.
2014-05-20 18:23:36 +00:00
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
}
2014-05-21 15:23:45 +00:00
return dbp.handleResult(syscall.PtraceCont(dbp.Pid, 0))
2014-05-20 20:15:52 +00:00
}
func (dbp *DebuggedProcess) CurrentPC() (uint64, error) {
regs, err := dbp.Registers()
if err != nil {
return 0, err
}
return regs.Rip, nil
}
2014-10-04 23:13:20 +00:00
func (dbp *DebuggedProcess) clearTempBreakpoint(pc uint64) error {
if bp, ok := dbp.PCtoBP(pc); ok {
regs, err := dbp.Registers()
if err != nil {
return err
}
2014-09-25 22:20:09 +00:00
// Reset program counter to our restored instruction.
bp, err = dbp.Clear(bp.Addr)
if err != nil {
return err
}
2014-09-25 22:20:09 +00:00
regs.SetPC(bp.Addr)
return syscall.PtraceSetRegs(dbp.Pid, regs)
}
return nil
}
// 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 {
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)
}
2014-07-30 00:00:24 +00:00
// 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) {
2014-07-30 00:00:24 +00:00
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
2014-07-30 00:00:24 +00:00
}
// 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)
2014-08-04 20:21:35 +00:00
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
2014-08-04 18:53:15 +00:00
case *dwarf.StructType:
2014-08-06 14:50:15 +00:00
switch t.StructName {
2014-08-04 20:21:35 +00:00
case "string":
return dbp.readString(offaddr)
2014-08-04 20:41:09 +00:00
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
2014-08-04 20:21:35 +00:00
}
case *dwarf.ArrayType:
return dbp.readIntArray(offaddr, t)
2014-07-30 00:00:24 +00:00
case *dwarf.IntType:
return dbp.readInt(offaddr)
case *dwarf.FloatType:
return dbp.readFloat64(offaddr)
}
2014-07-30 00:00:24 +00:00
return "", fmt.Errorf("could not find value for type %s", typ)
}
2014-07-30 00:00:24 +00:00
2014-08-04 18:53:15 +00:00
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
}
2014-09-30 13:49:30 +00:00
2014-08-04 20:41:09 +00:00
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
}
2014-08-04 18:53:15 +00:00
2014-08-04 20:21:35 +00:00
func (dbp *DebuggedProcess) readIntArray(addr uintptr, t *dwarf.ArrayType) (string, error) {
var (
number uint64
members = make([]uint64, 0, t.ByteSize)
2014-08-04 20:21:35 +00:00
)
val, err := dbp.readMemory(addr, uintptr(t.ByteSize))
2014-08-04 20:21:35 +00:00
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)
2014-08-04 20:21:35 +00:00
return str, nil
2014-08-04 20:21:35 +00:00
}
func (dbp *DebuggedProcess) readInt(addr uintptr) (string, error) {
val, err := dbp.readMemory(addr, 8)
if err != nil {
return "", err
}
n := binary.LittleEndian.Uint64(val)
2014-08-04 18:20:20 +00:00
return strconv.Itoa(int(n)), nil
}
2014-08-06 16:14:23 +00:00
func (dbp *DebuggedProcess) readFloat64(addr uintptr) (string, error) {
var n float64
val, err := dbp.readMemory(addr, 8)
if err != nil {
return "", err
2014-07-30 00:00:24 +00:00
}
buf := bytes.NewBuffer(val)
binary.Read(buf, binary.LittleEndian, &n)
2014-07-30 00:00:24 +00:00
return strconv.FormatFloat(n, 'f', -1, 64), nil
2014-07-30 00:00:24 +00:00
}
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
}
2014-05-21 15:23:45 +00:00
func (dbp *DebuggedProcess) handleResult(err error) error {
2014-05-20 18:23:36 +00:00
if err != nil {
return err
}
ps, err := wait(dbp.Process.Pid)
if err != nil && err != syscall.ECHILD {
2014-05-20 18:23:36 +00:00
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())
}
}
2014-05-20 18:23:36 +00:00
return nil
}
func (dbp *DebuggedProcess) findExecutable() error {
procpath := fmt.Sprintf("/proc/%d/exe", dbp.Pid)
f, err := os.OpenFile(procpath, 0, os.ModePerm)
if err != nil {
return err
}
elffile, err := elf.NewFile(f)
if err != nil {
return err
}
dbp.Executable = elffile
return nil
}
2014-08-06 16:14:23 +00:00
func (dbp *DebuggedProcess) parseDebugLine(wg *sync.WaitGroup) {
defer wg.Done()
debugLine, err := dbp.Executable.Section(".debug_line").Data()
if err != nil {
2014-08-06 16:14:23 +00:00
fmt.Println("could not get .debug_line section", err)
os.Exit(1)
}
dbp.DebugLine = line.Parse(debugLine)
}
2014-07-17 01:13:01 +00:00
2014-08-06 16:14:23 +00:00
func (dbp *DebuggedProcess) parseDebugFrame(wg *sync.WaitGroup) {
defer wg.Done()
debugFrame, err := dbp.Executable.Section(".debug_frame").Data()
if err != nil {
2014-08-06 16:14:23 +00:00
fmt.Println("could not get .debug_frame section", err)
os.Exit(1)
}
dbp.FrameEntries = frame.Parse(debugFrame)
}
2014-08-06 16:14:23 +00:00
func (dbp *DebuggedProcess) obtainGoSymbols(wg *sync.WaitGroup) {
defer wg.Done()
var (
symdat []byte
pclndat []byte
err error
)
if sec := dbp.Executable.Section(".gosymtab"); sec != nil {
symdat, err = sec.Data()
if err != nil {
fmt.Println("could not get .gosymtab section", err)
os.Exit(1)
}
}
if sec := dbp.Executable.Section(".gopclntab"); sec != nil {
pclndat, err = sec.Data()
if err != nil {
fmt.Println("could not get .gopclntab section", err)
os.Exit(1)
}
}
pcln := gosym.NewLineTable(pclndat, dbp.Executable.Section(".text").Addr)
tab, err := gosym.NewTable(symdat, pcln)
if err != nil {
2014-08-06 16:14:23 +00:00
fmt.Println("could not get initialize line table", err)
os.Exit(1)
}
dbp.GoSymTable = tab
}
2014-05-27 18:20:10 +00:00
// Converts a program counter value into a breakpoint, if one was set
// for the function containing pc.
func (dbp *DebuggedProcess) PCtoBP(pc uint64) (*BreakPoint, bool) {
bp, ok := dbp.BreakPoints[pc]
return bp, ok
}
// Takes an offset from RSP and returns the address of the
// instruction the currect function is going to return to.
func (dbp *DebuggedProcess) ReturnAddressFromOffset(offset int64) uint64 {
regs, err := dbp.Registers()
if err != nil {
panic("Could not obtain register values")
}
retaddr := int64(regs.Rsp) + offset
data := make([]byte, 8)
syscall.PtracePeekText(dbp.Pid, uintptr(retaddr), data)
return binary.LittleEndian.Uint64(data)
}
func wait(pid int) (*syscall.WaitStatus, error) {
var status syscall.WaitStatus
var rusage syscall.Rusage
_, e := syscall.Wait4(pid, &status, 0, &rusage)
if e != nil {
return nil, e
}
return &status, nil
}