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 (
|
2014-05-23 14:42:06 +00:00
|
|
|
"debug/elf"
|
|
|
|
"debug/gosym"
|
2014-05-20 18:23:35 +00:00
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"syscall"
|
|
|
|
)
|
|
|
|
|
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-05-20 18:23:36 +00:00
|
|
|
Pid int
|
|
|
|
Regs *syscall.PtraceRegs
|
|
|
|
Process *os.Process
|
|
|
|
ProcessState *os.ProcessState
|
2014-05-23 14:42:06 +00:00
|
|
|
Executable *elf.File
|
|
|
|
Symbols []elf.Symbol
|
|
|
|
GoSymTable *gosym.Table
|
2014-05-24 16:22:06 +00:00
|
|
|
BreakPoints map[string]*BreakPoint
|
|
|
|
}
|
|
|
|
|
|
|
|
type BreakPoint struct {
|
|
|
|
FunctionName string
|
2014-05-27 15:43:47 +00:00
|
|
|
File string
|
2014-05-24 16:22:06 +00:00
|
|
|
Line int
|
|
|
|
Addr uint64
|
2014-05-27 15:43:47 +00:00
|
|
|
OriginalData []byte
|
2014-05-20 18:23:35 +00:00
|
|
|
}
|
|
|
|
|
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) {
|
2014-05-23 14:42:06 +00:00
|
|
|
proc, err := os.FindProcess(pid)
|
2014-05-20 18:23:35 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2014-05-23 14:42:06 +00:00
|
|
|
err = syscall.PtraceAttach(pid)
|
2014-05-20 18:23:35 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2014-05-20 18:23:36 +00:00
|
|
|
ps, err := proc.Wait()
|
2014-05-20 18:23:35 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2014-05-20 18:23:36 +00:00
|
|
|
debuggedProc := DebuggedProcess{
|
|
|
|
Pid: pid,
|
|
|
|
Regs: &syscall.PtraceRegs{},
|
|
|
|
Process: proc,
|
|
|
|
ProcessState: ps,
|
2014-05-24 16:22:06 +00:00
|
|
|
BreakPoints: make(map[string]*BreakPoint),
|
2014-05-20 18:23:36 +00:00
|
|
|
}
|
|
|
|
|
2014-05-23 14:42:06 +00:00
|
|
|
err = debuggedProc.LoadInformation()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2014-05-20 18:23:35 +00:00
|
|
|
return &debuggedProc, nil
|
|
|
|
}
|
|
|
|
|
2014-05-23 14:42:06 +00:00
|
|
|
func (dbp *DebuggedProcess) LoadInformation() error {
|
|
|
|
err := dbp.findExecutable()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = dbp.obtainGoSymbols()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2014-05-24 00:44:54 +00:00
|
|
|
// Sets a breakpoint in the running process.
|
2014-05-24 16:22:06 +00:00
|
|
|
func (dbp *DebuggedProcess) Break(fname string) (*BreakPoint, error) {
|
2014-05-24 00:44:54 +00:00
|
|
|
var (
|
2014-05-24 16:22:06 +00:00
|
|
|
int3 = []byte{'0', 'x', 'C', 'C'}
|
|
|
|
fn = dbp.GoSymTable.LookupFunc(fname)
|
2014-05-24 00:44:54 +00:00
|
|
|
)
|
|
|
|
|
2014-05-24 16:36:18 +00:00
|
|
|
if fn == nil {
|
|
|
|
return nil, fmt.Errorf("No function named %s\n", fname)
|
|
|
|
}
|
|
|
|
|
2014-05-27 15:43:47 +00:00
|
|
|
f, l, _ := dbp.GoSymTable.PCToLine(fn.Entry)
|
|
|
|
|
|
|
|
orginalData := make([]byte, 1)
|
|
|
|
addr := uintptr(fn.Entry)
|
|
|
|
_, err := syscall.PtracePeekData(dbp.Pid, addr, orginalData)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2014-05-24 16:22:06 +00:00
|
|
|
}
|
|
|
|
|
2014-05-27 15:43:47 +00:00
|
|
|
_, err = syscall.PtracePokeData(dbp.Pid, addr, int3)
|
2014-05-24 00:44:54 +00:00
|
|
|
if err != nil {
|
2014-05-24 16:22:06 +00:00
|
|
|
return nil, err
|
2014-05-24 00:44:54 +00:00
|
|
|
}
|
|
|
|
|
2014-05-24 16:22:06 +00:00
|
|
|
breakpoint := &BreakPoint{
|
|
|
|
FunctionName: fn.Name,
|
2014-05-27 15:43:47 +00:00
|
|
|
File: f,
|
|
|
|
Line: l,
|
|
|
|
Addr: fn.Entry,
|
|
|
|
OriginalData: orginalData,
|
2014-05-24 16:22:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
dbp.BreakPoints[fname] = breakpoint
|
|
|
|
|
|
|
|
return breakpoint, nil
|
2014-05-24 00:44:54 +00:00
|
|
|
}
|
|
|
|
|
2014-05-21 15:23:14 +00:00
|
|
|
// Steps through process.
|
2014-05-20 18:23:35 +00:00
|
|
|
func (dbp *DebuggedProcess) Step() error {
|
2014-05-27 15:43:47 +00:00
|
|
|
regs, err := dbp.Registers()
|
2014-05-23 14:42:06 +00:00
|
|
|
if err != nil {
|
2014-05-27 15:43:47 +00:00
|
|
|
return err
|
2014-05-23 14:42:06 +00:00
|
|
|
}
|
|
|
|
|
2014-05-27 15:43:47 +00:00
|
|
|
bp, ok := dbp.PCtoBP(regs.PC())
|
|
|
|
if ok {
|
|
|
|
dbp.restoreInstruction(regs.PC(), bp.OriginalData)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = dbp.handleResult(syscall.PtraceSingleStep(dbp.Pid))
|
2014-05-23 14:42:06 +00:00
|
|
|
if err != nil {
|
2014-05-27 15:43:47 +00:00
|
|
|
return fmt.Errorf("step failed: ", err.Error())
|
2014-05-23 14:42:06 +00:00
|
|
|
}
|
|
|
|
|
2014-05-27 12:46:43 +00:00
|
|
|
f, l, fn := dbp.GoSymTable.PCToLine(regs.PC())
|
|
|
|
fmt.Printf("Stopped at: %s %s:%d\n", fn.Name, f, l)
|
2014-05-23 14:42:06 +00:00
|
|
|
|
2014-05-27 15:43:47 +00:00
|
|
|
if ok {
|
|
|
|
_, err = dbp.Break(bp.FunctionName)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-23 14:42:06 +00:00
|
|
|
return nil
|
2014-05-20 18:23:35 +00:00
|
|
|
}
|
2014-05-20 18:23:36 +00:00
|
|
|
|
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 {
|
2014-05-27 15:43:47 +00:00
|
|
|
// 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
|
|
|
}
|
|
|
|
|
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 := dbp.Process.Wait()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
dbp.ProcessState = ps
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2014-05-23 14:42:06 +00:00
|
|
|
|
|
|
|
func (dbp *DebuggedProcess) findExecutable() error {
|
|
|
|
procpath := fmt.Sprintf("/proc/%d/exe", dbp.Pid)
|
|
|
|
|
|
|
|
f, err := os.Open(procpath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
elffile, err := elf.NewFile(f)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
dbp.Executable = elffile
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (dbp *DebuggedProcess) obtainGoSymbols() error {
|
|
|
|
symdat, err := dbp.Executable.Section(".gosymtab").Data()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
pclndat, err := dbp.Executable.Section(".gopclntab").Data()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
pcln := gosym.NewLineTable(pclndat, dbp.Executable.Section(".text").Addr)
|
|
|
|
tab, err := gosym.NewTable(symdat, pcln)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
dbp.GoSymTable = tab
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2014-05-27 15:43:47 +00:00
|
|
|
|
|
|
|
func (dbp *DebuggedProcess) PCtoBP(pc uint64) (*BreakPoint, bool) {
|
|
|
|
_, _, fn := dbp.GoSymTable.PCToLine(pc)
|
|
|
|
bp, ok := dbp.BreakPoints[fn.Name]
|
|
|
|
return bp, ok
|
|
|
|
}
|
|
|
|
|
|
|
|
func (dbp *DebuggedProcess) restoreInstruction(pc uint64, data []byte) error {
|
|
|
|
_, err := syscall.PtracePokeData(dbp.Pid, uintptr(pc), data)
|
|
|
|
return err
|
|
|
|
}
|