mirror of
https://github.com/go-delve/delve.git
synced 2025-11-02 21:40:22 +08:00
Isolate proctl_*.go linux specific code
This commit is contained in:
495
proctl/proctl.go
Normal file
495
proctl/proctl.go
Normal file
@ -0,0 +1,495 @@
|
||||
// Package proctl provides functions for attaching to and manipulating
|
||||
// a process during the debug session.
|
||||
package proctl
|
||||
|
||||
import (
|
||||
"debug/gosym"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/derekparker/delve/dwarf/frame"
|
||||
)
|
||||
|
||||
// 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
|
||||
temp bool
|
||||
}
|
||||
|
||||
type BreakPointExistsError struct {
|
||||
file string
|
||||
line int
|
||||
addr uintptr
|
||||
}
|
||||
|
||||
// ProcessStatus is the result of parsing the data from
|
||||
// the /proc/<pid>/stats psuedo file.
|
||||
type ProcessStatus struct {
|
||||
pid int
|
||||
comm string
|
||||
state rune
|
||||
ppid int
|
||||
}
|
||||
|
||||
const (
|
||||
STATUS_SLEEPING = 'S'
|
||||
STATUS_RUNNING = 'R'
|
||||
STATUS_TRACE_STOP = 't'
|
||||
)
|
||||
|
||||
func (bpe BreakPointExistsError) Error() string {
|
||||
return fmt.Sprintf("Breakpoint exists at %s:%d at %x", bpe.file, bpe.line, bpe.addr)
|
||||
}
|
||||
|
||||
func Attach(pid int) (*DebuggedProcess, error) {
|
||||
dbp, err := newDebugProcess(pid, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Attach to all currently active threads.
|
||||
allm, err := dbp.CurrentThread.AllM()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, m := range allm {
|
||||
if m.procid == 0 {
|
||||
continue
|
||||
}
|
||||
_, err := dbp.AttachThread(m.procid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return dbp, nil
|
||||
}
|
||||
|
||||
func Launch(cmd []string) (*DebuggedProcess, error) {
|
||||
proc := exec.Command(cmd[0])
|
||||
proc.Args = cmd
|
||||
proc.Stdout = os.Stdout
|
||||
proc.Stderr = os.Stderr
|
||||
proc.SysProcAttr = &syscall.SysProcAttr{Ptrace: true}
|
||||
|
||||
if err := proc.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, _, err := wait(proc.Process.Pid, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("waiting for target execve failed: %s", err)
|
||||
}
|
||||
|
||||
return newDebugProcess(proc.Process.Pid, false)
|
||||
}
|
||||
|
||||
// Returns a new DebuggedProcess struct with sensible defaults.
|
||||
func newDebugProcess(pid int, attach bool) (*DebuggedProcess, error) {
|
||||
dbp := DebuggedProcess{
|
||||
Pid: pid,
|
||||
Threads: make(map[int]*ThreadContext),
|
||||
BreakPoints: make(map[uint64]*BreakPoint),
|
||||
}
|
||||
|
||||
if attach {
|
||||
thread, err := dbp.AttachThread(pid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dbp.CurrentThread = thread
|
||||
} else {
|
||||
thread, err := dbp.addThread(pid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dbp.CurrentThread = thread
|
||||
}
|
||||
|
||||
proc, err := os.FindProcess(pid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dbp.Process = proc
|
||||
err = dbp.LoadInformation()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &dbp, nil
|
||||
}
|
||||
|
||||
func (dbp *DebuggedProcess) AttachThread(tid int) (*ThreadContext, error) {
|
||||
if thread, ok := dbp.Threads[tid]; ok {
|
||||
return thread, nil
|
||||
}
|
||||
|
||||
err := syscall.PtraceAttach(tid)
|
||||
if err != nil && 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)
|
||||
}
|
||||
|
||||
pid, status, err := wait(tid, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if status.Exited() {
|
||||
return nil, fmt.Errorf("thread already exited %d", pid)
|
||||
}
|
||||
|
||||
return dbp.addThread(tid)
|
||||
}
|
||||
|
||||
// Sets a breakpoint in the current thread.
|
||||
func (dbp *DebuggedProcess) Break(addr uintptr) (*BreakPoint, error) {
|
||||
return dbp.CurrentThread.Break(addr)
|
||||
}
|
||||
|
||||
// Clears a breakpoint in the current thread.
|
||||
func (dbp *DebuggedProcess) Clear(pc uint64) (*BreakPoint, error) {
|
||||
return dbp.CurrentThread.Clear(pc)
|
||||
}
|
||||
|
||||
// Returns the status of the current main thread context.
|
||||
func (dbp *DebuggedProcess) Status() *syscall.WaitStatus {
|
||||
return dbp.CurrentThread.Status
|
||||
}
|
||||
|
||||
// Loop through all threads, printing their information
|
||||
// to the console.
|
||||
func (dbp *DebuggedProcess) PrintThreadInfo() error {
|
||||
for _, th := range dbp.Threads {
|
||||
if err := th.PrintInfo(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
err error
|
||||
)
|
||||
|
||||
err = dbp.findExecutable()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wg.Add(2)
|
||||
go dbp.parseDebugFrame(&wg)
|
||||
go dbp.obtainGoSymbols(&wg)
|
||||
|
||||
wg.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Steps through process.
|
||||
func (dbp *DebuggedProcess) Step() (err error) {
|
||||
var (
|
||||
th *ThreadContext
|
||||
ok bool
|
||||
)
|
||||
|
||||
allm, err := dbp.CurrentThread.AllM()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, m := range allm {
|
||||
th, ok = dbp.Threads[m.procid]
|
||||
if !ok {
|
||||
th = dbp.Threads[dbp.Pid]
|
||||
}
|
||||
|
||||
if m.blocked == 0 {
|
||||
err := th.Step()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Step over function calls.
|
||||
func (dbp *DebuggedProcess) Next() error {
|
||||
var (
|
||||
th *ThreadContext
|
||||
ok bool
|
||||
)
|
||||
|
||||
allm, err := dbp.CurrentThread.AllM()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, m := range allm {
|
||||
th, ok = dbp.Threads[m.procid]
|
||||
if !ok {
|
||||
th = dbp.Threads[dbp.Pid]
|
||||
}
|
||||
|
||||
if m.blocked == 1 {
|
||||
// Continue any blocked M so that the
|
||||
// scheduler can continue to do its'
|
||||
// job correctly.
|
||||
err := th.Continue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
err := th.Next()
|
||||
if err != nil && err != syscall.ESRCH {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return stopTheWorld(dbp)
|
||||
}
|
||||
|
||||
// Resume process.
|
||||
func (dbp *DebuggedProcess) Continue() error {
|
||||
for _, thread := range dbp.Threads {
|
||||
err := thread.Continue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
wpid, _, err := trapWait(dbp, -1, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return handleBreakPoint(dbp, wpid)
|
||||
}
|
||||
|
||||
// Obtains register values from what Delve considers to be the current
|
||||
// thread of the traced process.
|
||||
func (dbp *DebuggedProcess) Registers() (*syscall.PtraceRegs, error) {
|
||||
return dbp.CurrentThread.Registers()
|
||||
}
|
||||
|
||||
type InvalidAddressError struct {
|
||||
address uintptr
|
||||
}
|
||||
|
||||
func (iae InvalidAddressError) Error() string {
|
||||
return fmt.Sprintf("Invalid address %#v\n", iae.address)
|
||||
}
|
||||
|
||||
func (dbp *DebuggedProcess) CurrentPC() (uint64, error) {
|
||||
return dbp.CurrentThread.CurrentPC()
|
||||
}
|
||||
|
||||
// Returns the value of the named symbol.
|
||||
func (dbp *DebuggedProcess) EvalSymbol(name string) (*Variable, error) {
|
||||
return dbp.CurrentThread.EvalSymbol(name)
|
||||
}
|
||||
|
||||
func (dbp *DebuggedProcess) parseDebugFrame(wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
|
||||
debugFrame, err := dbp.Executable.Section(".debug_frame").Data()
|
||||
if err != nil {
|
||||
fmt.Println("could not get .debug_frame section", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
dbp.FrameEntries = frame.Parse(debugFrame)
|
||||
}
|
||||
|
||||
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 {
|
||||
fmt.Println("could not get initialize line table", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
dbp.GoSymTable = tab
|
||||
}
|
||||
|
||||
type ProcessExitedError struct {
|
||||
pid int
|
||||
}
|
||||
|
||||
func (pe ProcessExitedError) Error() string {
|
||||
return fmt.Sprintf("process %d has exited", pe.pid)
|
||||
}
|
||||
|
||||
func trapWait(dbp *DebuggedProcess, pid int, options int) (int, *syscall.WaitStatus, error) {
|
||||
for {
|
||||
wpid, status, err := wait(pid, 0)
|
||||
if err != nil {
|
||||
return -1, nil, fmt.Errorf("wait err %s %d", err, pid)
|
||||
}
|
||||
if wpid == 0 {
|
||||
continue
|
||||
}
|
||||
if th, ok := dbp.Threads[wpid]; ok {
|
||||
th.Status = status
|
||||
}
|
||||
if status.Exited() && wpid == dbp.Pid {
|
||||
return -1, status, ProcessExitedError{wpid}
|
||||
}
|
||||
if status.StopSignal() == syscall.SIGTRAP && status.TrapCause() == syscall.PTRACE_EVENT_CLONE {
|
||||
err = addNewThread(dbp, wpid)
|
||||
if err != nil {
|
||||
return -1, nil, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if status.StopSignal() == syscall.SIGTRAP {
|
||||
return wpid, status, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleBreakPoint(dbp *DebuggedProcess, pid int) error {
|
||||
thread := dbp.Threads[pid]
|
||||
if pid != dbp.CurrentThread.Id {
|
||||
fmt.Printf("thread context changed from %d to %d\n", dbp.CurrentThread.Id, pid)
|
||||
dbp.CurrentThread = thread
|
||||
}
|
||||
|
||||
pc, err := thread.CurrentPC()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get current pc %s", err)
|
||||
}
|
||||
|
||||
// Check to see if we hit a runtime.breakpoint
|
||||
fn := dbp.GoSymTable.PCToFunc(pc)
|
||||
if fn != nil && fn.Name == "runtime.breakpoint" {
|
||||
// step twice to get back to user code
|
||||
for i := 0; i < 2; i++ {
|
||||
err = thread.Step()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
stopTheWorld(dbp)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check to see if we have hit a user set breakpoint.
|
||||
if bp, ok := dbp.BreakPoints[pc-1]; ok {
|
||||
if !bp.temp {
|
||||
stopTheWorld(dbp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("did not hit recognized breakpoint")
|
||||
}
|
||||
|
||||
// Ensure execution of every traced thread is halted.
|
||||
func stopTheWorld(dbp *DebuggedProcess) error {
|
||||
// Loop through all threads and ensure that we
|
||||
// stop the rest of them, so that by the time
|
||||
// we return control to the user, all threads
|
||||
// are inactive. We send SIGSTOP and ensure all
|
||||
// threads are in in signal-delivery-stop mode.
|
||||
for _, th := range dbp.Threads {
|
||||
ps, err := parseProcessStatus(th.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ps.state == STATUS_TRACE_STOP {
|
||||
continue
|
||||
}
|
||||
|
||||
err = syscall.Tgkill(dbp.Pid, th.Id, syscall.SIGSTOP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pid, _, err := wait(th.Id, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("wait err %s %d", err, pid)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func addNewThread(dbp *DebuggedProcess, pid int) error {
|
||||
// A traced thread has cloned a new thread, grab the pid and
|
||||
// add it to our list of traced threads.
|
||||
msg, err := syscall.PtraceGetEventMsg(pid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get event message: %s", err)
|
||||
}
|
||||
fmt.Println("new thread spawned", msg)
|
||||
|
||||
_, err = dbp.addThread(int(msg))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = syscall.PtraceCont(int(msg), 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not continue new thread %d %s", msg, err)
|
||||
}
|
||||
|
||||
err = syscall.PtraceCont(pid, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not continue stopped thread %d %s", pid, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func wait(pid, options int) (int, *syscall.WaitStatus, error) {
|
||||
var status syscall.WaitStatus
|
||||
wpid, err := syscall.Wait4(pid, &status, syscall.WALL|options, nil)
|
||||
return wpid, &status, err
|
||||
}
|
||||
@ -1,13 +1,9 @@
|
||||
// Package proctl provides functions for attaching to and manipulating
|
||||
// a process during the debug session.
|
||||
package proctl
|
||||
|
||||
import (
|
||||
"debug/gosym"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/derekparker/delve/dwarf/frame"
|
||||
@ -28,160 +24,6 @@ type DebuggedProcess struct {
|
||||
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
|
||||
temp bool
|
||||
}
|
||||
|
||||
type BreakPointExistsError struct {
|
||||
file string
|
||||
line int
|
||||
addr uintptr
|
||||
}
|
||||
|
||||
// ProcessStatus is the result of parsing the data from
|
||||
// the /proc/<pid>/stats psuedo file.
|
||||
type ProcessStatus struct {
|
||||
pid int
|
||||
comm string
|
||||
state rune
|
||||
ppid int
|
||||
}
|
||||
|
||||
const (
|
||||
STATUS_SLEEPING = 'S'
|
||||
STATUS_RUNNING = 'R'
|
||||
STATUS_TRACE_STOP = 't'
|
||||
)
|
||||
|
||||
func parseProcessStatus(pid int) (*ProcessStatus, error) {
|
||||
var ps ProcessStatus
|
||||
|
||||
f, err := os.Open(fmt.Sprintf("/proc/%d/stat", pid))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
fmt.Fscanf(f, "%d %s %c %d", &ps.pid, &ps.comm, &ps.state, &ps.ppid)
|
||||
|
||||
return &ps, nil
|
||||
}
|
||||
|
||||
func (bpe BreakPointExistsError) Error() string {
|
||||
return fmt.Sprintf("Breakpoint exists at %s:%d at %x", bpe.file, bpe.line, bpe.addr)
|
||||
}
|
||||
|
||||
func Attach(pid int) (*DebuggedProcess, error) {
|
||||
dbp, err := newDebugProcess(pid, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Attach to all currently active threads.
|
||||
allm, err := dbp.CurrentThread.AllM()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, m := range allm {
|
||||
if m.procid == 0 {
|
||||
continue
|
||||
}
|
||||
_, err := dbp.AttachThread(m.procid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return dbp, nil
|
||||
}
|
||||
|
||||
func Launch(cmd []string) (*DebuggedProcess, error) {
|
||||
proc := exec.Command(cmd[0])
|
||||
proc.Args = cmd
|
||||
proc.Stdout = os.Stdout
|
||||
proc.Stderr = os.Stderr
|
||||
proc.SysProcAttr = &syscall.SysProcAttr{Ptrace: true}
|
||||
|
||||
if err := proc.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, _, err := wait(proc.Process.Pid, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("waiting for target execve failed: %s", err)
|
||||
}
|
||||
|
||||
return newDebugProcess(proc.Process.Pid, false)
|
||||
}
|
||||
|
||||
// Returns a new DebuggedProcess struct with sensible defaults.
|
||||
func newDebugProcess(pid int, attach bool) (*DebuggedProcess, error) {
|
||||
dbp := DebuggedProcess{
|
||||
Pid: pid,
|
||||
Threads: make(map[int]*ThreadContext),
|
||||
BreakPoints: make(map[uint64]*BreakPoint),
|
||||
}
|
||||
|
||||
if attach {
|
||||
thread, err := dbp.AttachThread(pid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dbp.CurrentThread = thread
|
||||
} else {
|
||||
thread, err := dbp.addThread(pid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dbp.CurrentThread = thread
|
||||
}
|
||||
|
||||
proc, err := os.FindProcess(pid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dbp.Process = proc
|
||||
err = dbp.LoadInformation()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &dbp, nil
|
||||
}
|
||||
|
||||
func (dbp *DebuggedProcess) AttachThread(tid int) (*ThreadContext, error) {
|
||||
if thread, ok := dbp.Threads[tid]; ok {
|
||||
return thread, nil
|
||||
}
|
||||
|
||||
err := syscall.PtraceAttach(tid)
|
||||
if err != nil && 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)
|
||||
}
|
||||
|
||||
pid, status, err := wait(tid, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if status.Exited() {
|
||||
return nil, fmt.Errorf("thread already exited %d", pid)
|
||||
}
|
||||
|
||||
return dbp.addThread(tid)
|
||||
}
|
||||
|
||||
func (dbp *DebuggedProcess) addThread(tid int) (*ThreadContext, error) {
|
||||
err := syscall.PtraceSetOptions(tid, syscall.PTRACE_O_TRACECLONE)
|
||||
if err == syscall.ESRCH {
|
||||
@ -205,161 +47,18 @@ func (dbp *DebuggedProcess) addThread(tid int) (*ThreadContext, error) {
|
||||
return dbp.Threads[tid], nil
|
||||
}
|
||||
|
||||
// Sets a breakpoint in the current thread.
|
||||
func (dbp *DebuggedProcess) Break(addr uintptr) (*BreakPoint, error) {
|
||||
return dbp.CurrentThread.Break(addr)
|
||||
}
|
||||
func parseProcessStatus(pid int) (*ProcessStatus, error) {
|
||||
var ps ProcessStatus
|
||||
|
||||
// Clears a breakpoint in the current thread.
|
||||
func (dbp *DebuggedProcess) Clear(pc uint64) (*BreakPoint, error) {
|
||||
return dbp.CurrentThread.Clear(pc)
|
||||
}
|
||||
|
||||
// Returns the status of the current main thread context.
|
||||
func (dbp *DebuggedProcess) Status() *syscall.WaitStatus {
|
||||
return dbp.CurrentThread.Status
|
||||
}
|
||||
|
||||
// Loop through all threads, printing their information
|
||||
// to the console.
|
||||
func (dbp *DebuggedProcess) PrintThreadInfo() error {
|
||||
for _, th := range dbp.Threads {
|
||||
if err := th.PrintInfo(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
err error
|
||||
)
|
||||
|
||||
err = dbp.findExecutable()
|
||||
f, err := os.Open(fmt.Sprintf("/proc/%d/stat", pid))
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
wg.Add(2)
|
||||
go dbp.parseDebugFrame(&wg)
|
||||
go dbp.obtainGoSymbols(&wg)
|
||||
fmt.Fscanf(f, "%d %s %c %d", &ps.pid, &ps.comm, &ps.state, &ps.ppid)
|
||||
|
||||
wg.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Steps through process.
|
||||
func (dbp *DebuggedProcess) Step() (err error) {
|
||||
var (
|
||||
th *ThreadContext
|
||||
ok bool
|
||||
)
|
||||
|
||||
allm, err := dbp.CurrentThread.AllM()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, m := range allm {
|
||||
th, ok = dbp.Threads[m.procid]
|
||||
if !ok {
|
||||
th = dbp.Threads[dbp.Pid]
|
||||
}
|
||||
|
||||
if m.blocked == 0 {
|
||||
err := th.Step()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Step over function calls.
|
||||
func (dbp *DebuggedProcess) Next() error {
|
||||
var (
|
||||
th *ThreadContext
|
||||
ok bool
|
||||
)
|
||||
|
||||
allm, err := dbp.CurrentThread.AllM()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, m := range allm {
|
||||
th, ok = dbp.Threads[m.procid]
|
||||
if !ok {
|
||||
th = dbp.Threads[dbp.Pid]
|
||||
}
|
||||
|
||||
if m.blocked == 1 {
|
||||
// Continue any blocked M so that the
|
||||
// scheduler can continue to do its'
|
||||
// job correctly.
|
||||
err := th.Continue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
err := th.Next()
|
||||
if err != nil && err != syscall.ESRCH {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return stopTheWorld(dbp)
|
||||
}
|
||||
|
||||
// Resume process.
|
||||
func (dbp *DebuggedProcess) Continue() error {
|
||||
for _, thread := range dbp.Threads {
|
||||
err := thread.Continue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
wpid, _, err := trapWait(dbp, -1, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return handleBreakPoint(dbp, wpid)
|
||||
}
|
||||
|
||||
// Obtains register values from what Delve considers to be the current
|
||||
// thread of the traced process.
|
||||
func (dbp *DebuggedProcess) Registers() (*syscall.PtraceRegs, error) {
|
||||
return dbp.CurrentThread.Registers()
|
||||
}
|
||||
|
||||
type InvalidAddressError struct {
|
||||
address uintptr
|
||||
}
|
||||
|
||||
func (iae InvalidAddressError) Error() string {
|
||||
return fmt.Sprintf("Invalid address %#v\n", iae.address)
|
||||
}
|
||||
|
||||
func (dbp *DebuggedProcess) CurrentPC() (uint64, error) {
|
||||
return dbp.CurrentThread.CurrentPC()
|
||||
}
|
||||
|
||||
// Returns the value of the named symbol.
|
||||
func (dbp *DebuggedProcess) EvalSymbol(name string) (*Variable, error) {
|
||||
return dbp.CurrentThread.EvalSymbol(name)
|
||||
return &ps, nil
|
||||
}
|
||||
|
||||
func (dbp *DebuggedProcess) findExecutable() error {
|
||||
@ -379,187 +78,3 @@ func (dbp *DebuggedProcess) findExecutable() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dbp *DebuggedProcess) parseDebugFrame(wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
|
||||
debugFrame, err := dbp.Executable.Section(".debug_frame").Data()
|
||||
if err != nil {
|
||||
fmt.Println("could not get .debug_frame section", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
dbp.FrameEntries = frame.Parse(debugFrame)
|
||||
}
|
||||
|
||||
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 {
|
||||
fmt.Println("could not get initialize line table", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
dbp.GoSymTable = tab
|
||||
}
|
||||
|
||||
type ProcessExitedError struct {
|
||||
pid int
|
||||
}
|
||||
|
||||
func (pe ProcessExitedError) Error() string {
|
||||
return fmt.Sprintf("process %d has exited", pe.pid)
|
||||
}
|
||||
|
||||
func trapWait(dbp *DebuggedProcess, pid int, options int) (int, *syscall.WaitStatus, error) {
|
||||
for {
|
||||
wpid, status, err := wait(pid, 0)
|
||||
if err != nil {
|
||||
return -1, nil, fmt.Errorf("wait err %s %d", err, pid)
|
||||
}
|
||||
if wpid == 0 {
|
||||
continue
|
||||
}
|
||||
if th, ok := dbp.Threads[wpid]; ok {
|
||||
th.Status = status
|
||||
}
|
||||
if status.Exited() && wpid == dbp.Pid {
|
||||
return -1, status, ProcessExitedError{wpid}
|
||||
}
|
||||
if status.StopSignal() == syscall.SIGTRAP && status.TrapCause() == syscall.PTRACE_EVENT_CLONE {
|
||||
err = addNewThread(dbp, wpid)
|
||||
if err != nil {
|
||||
return -1, nil, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if status.StopSignal() == syscall.SIGTRAP {
|
||||
return wpid, status, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleBreakPoint(dbp *DebuggedProcess, pid int) error {
|
||||
thread := dbp.Threads[pid]
|
||||
if pid != dbp.CurrentThread.Id {
|
||||
fmt.Printf("thread context changed from %d to %d\n", dbp.CurrentThread.Id, pid)
|
||||
dbp.CurrentThread = thread
|
||||
}
|
||||
|
||||
pc, err := thread.CurrentPC()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get current pc %s", err)
|
||||
}
|
||||
|
||||
// Check to see if we hit a runtime.breakpoint
|
||||
fn := dbp.GoSymTable.PCToFunc(pc)
|
||||
if fn != nil && fn.Name == "runtime.breakpoint" {
|
||||
// step twice to get back to user code
|
||||
for i := 0; i < 2; i++ {
|
||||
err = thread.Step()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
stopTheWorld(dbp)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check to see if we have hit a user set breakpoint.
|
||||
if bp, ok := dbp.BreakPoints[pc-1]; ok {
|
||||
if !bp.temp {
|
||||
stopTheWorld(dbp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("did not hit recognized breakpoint")
|
||||
}
|
||||
|
||||
// Ensure execution of every traced thread is halted.
|
||||
func stopTheWorld(dbp *DebuggedProcess) error {
|
||||
// Loop through all threads and ensure that we
|
||||
// stop the rest of them, so that by the time
|
||||
// we return control to the user, all threads
|
||||
// are inactive. We send SIGSTOP and ensure all
|
||||
// threads are in in signal-delivery-stop mode.
|
||||
for _, th := range dbp.Threads {
|
||||
ps, err := parseProcessStatus(th.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ps.state == STATUS_TRACE_STOP {
|
||||
continue
|
||||
}
|
||||
|
||||
err = syscall.Tgkill(dbp.Pid, th.Id, syscall.SIGSTOP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pid, _, err := wait(th.Id, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("wait err %s %d", err, pid)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func addNewThread(dbp *DebuggedProcess, pid int) error {
|
||||
// A traced thread has cloned a new thread, grab the pid and
|
||||
// add it to our list of traced threads.
|
||||
msg, err := syscall.PtraceGetEventMsg(pid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get event message: %s", err)
|
||||
}
|
||||
fmt.Println("new thread spawned", msg)
|
||||
|
||||
_, err = dbp.addThread(int(msg))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = syscall.PtraceCont(int(msg), 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not continue new thread %d %s", msg, err)
|
||||
}
|
||||
|
||||
err = syscall.PtraceCont(pid, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not continue stopped thread %d %s", pid, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func wait(pid, options int) (int, *syscall.WaitStatus, error) {
|
||||
var status syscall.WaitStatus
|
||||
wpid, err := syscall.Wait4(pid, &status, syscall.WALL|options, nil)
|
||||
return wpid, &status, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user