2015-01-14 02:37:10 +00:00
|
|
|
package proctl
|
|
|
|
|
|
|
|
// #include "proctl_darwin.h"
|
|
|
|
import "C"
|
|
|
|
import (
|
|
|
|
"debug/gosym"
|
|
|
|
"debug/macho"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"sync"
|
|
|
|
"unsafe"
|
|
|
|
|
|
|
|
"github.com/derekparker/delve/dwarf/frame"
|
|
|
|
sys "golang.org/x/sys/unix"
|
|
|
|
)
|
|
|
|
|
|
|
|
type OSProcessDetails struct {
|
|
|
|
task C.mach_port_name_t
|
|
|
|
exceptionPort C.mach_port_t
|
|
|
|
}
|
|
|
|
|
|
|
|
func (dbp *DebuggedProcess) Halt() (err error) {
|
|
|
|
for _, th := range dbp.Threads {
|
|
|
|
err := th.Halt()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Finds the executable and then uses it
|
|
|
|
// 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
|
|
|
|
exe *macho.File
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
|
|
|
|
if err := dbp.acquireMachTask(); err != nil {
|
|
|
|
return fmt.Errorf("could not acquire mach task")
|
|
|
|
}
|
|
|
|
exe, err = dbp.findExecutable()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
data, err := exe.DWARF()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
dbp.Dwarf = data
|
|
|
|
|
|
|
|
wg.Add(2)
|
|
|
|
go dbp.parseDebugFrame(exe, &wg)
|
|
|
|
go dbp.obtainGoSymbols(exe, &wg)
|
|
|
|
wg.Wait()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (dbp *DebuggedProcess) updateThreadList() error {
|
|
|
|
var (
|
|
|
|
err error
|
|
|
|
kret C.kern_return_t
|
|
|
|
count = C.thread_count(C.task_t(dbp.os.task))
|
|
|
|
)
|
2015-02-27 21:27:48 +00:00
|
|
|
if count == -1 {
|
|
|
|
return fmt.Errorf("could not get thread count")
|
|
|
|
}
|
|
|
|
list := make([]uint32, count)
|
2015-01-14 02:37:10 +00:00
|
|
|
|
2015-03-01 03:34:55 +00:00
|
|
|
// TODO(dp) might be better to malloc mem in C and then free it here
|
2015-01-14 02:37:10 +00:00
|
|
|
// instead of getting count above and passing in a slice
|
|
|
|
kret = C.get_threads(C.task_t(dbp.os.task), unsafe.Pointer(&list[0]))
|
|
|
|
if kret != C.KERN_SUCCESS {
|
|
|
|
return fmt.Errorf("could not get thread list")
|
|
|
|
}
|
|
|
|
if count < 0 {
|
|
|
|
return fmt.Errorf("could not get thread list")
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, port := range list {
|
2015-03-01 03:34:55 +00:00
|
|
|
if _, ok := dbp.Threads[int(port)]; !ok {
|
|
|
|
fmt.Println("new thread spawned", port)
|
|
|
|
_, err = dbp.addThread(int(port), false)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-01-14 02:37:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (dbp *DebuggedProcess) acquireMachTask() error {
|
|
|
|
if ret := C.acquire_mach_task(C.int(dbp.Pid), &dbp.os.task, &dbp.os.exceptionPort); ret != C.KERN_SUCCESS {
|
|
|
|
return fmt.Errorf("could not acquire mach task %d", ret)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// export addThread
|
|
|
|
func (dbp *DebuggedProcess) addThread(port int, attach bool) (*ThreadContext, error) {
|
|
|
|
thread := &ThreadContext{
|
|
|
|
Id: port,
|
|
|
|
Process: dbp,
|
|
|
|
os: new(OSSpecificDetails),
|
|
|
|
}
|
|
|
|
dbp.Threads[port] = thread
|
|
|
|
thread.os.thread_act = C.thread_act_t(port)
|
2015-03-01 03:34:55 +00:00
|
|
|
if dbp.CurrentThread == nil {
|
|
|
|
dbp.CurrentThread = thread
|
|
|
|
}
|
2015-01-14 02:37:10 +00:00
|
|
|
return thread, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (dbp *DebuggedProcess) parseDebugFrame(exe *macho.File, wg *sync.WaitGroup) {
|
|
|
|
defer wg.Done()
|
|
|
|
|
|
|
|
if sec := exe.Section("__debug_frame"); sec != nil {
|
|
|
|
debugFrame, err := exe.Section("__debug_frame").Data()
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println("could not get __debug_frame section", err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
dbp.FrameEntries = frame.Parse(debugFrame)
|
|
|
|
} else {
|
|
|
|
fmt.Println("could not find __debug_frame section in binary")
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (dbp *DebuggedProcess) obtainGoSymbols(exe *macho.File, wg *sync.WaitGroup) {
|
|
|
|
defer wg.Done()
|
|
|
|
|
|
|
|
var (
|
|
|
|
symdat []byte
|
|
|
|
pclndat []byte
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
|
|
|
|
if sec := exe.Section("__gosymtab"); sec != nil {
|
|
|
|
symdat, err = sec.Data()
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println("could not get .gosymtab section", err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if sec := exe.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, exe.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
|
|
|
|
}
|
|
|
|
|
|
|
|
func (dbp *DebuggedProcess) findExecutable() (*macho.File, error) {
|
|
|
|
pathptr, err := C.find_executable(C.int(dbp.Pid))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return macho.Open(C.GoString(pathptr))
|
|
|
|
}
|
|
|
|
|
|
|
|
func trapWait(dbp *DebuggedProcess, pid int) (int, *sys.WaitStatus, error) {
|
|
|
|
port := C.mach_port_wait(dbp.os.exceptionPort)
|
|
|
|
if port == 0 {
|
2015-02-27 21:27:48 +00:00
|
|
|
return -1, nil, ProcessExitedError{}
|
2015-01-14 02:37:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
dbp.updateThreadList()
|
|
|
|
return int(port), nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func wait(pid, options int) (int, *sys.WaitStatus, error) {
|
|
|
|
var status sys.WaitStatus
|
|
|
|
wpid, err := sys.Wait4(pid, &status, options, nil)
|
|
|
|
return wpid, &status, err
|
|
|
|
}
|