package proc // #include "proc_darwin.h" // #include "exec_darwin.h" // #include import "C" import ( "debug/gosym" "debug/macho" "fmt" "os" "path/filepath" "sync" "unsafe" "github.com/derekparker/delve/dwarf/frame" "github.com/derekparker/delve/dwarf/line" "github.com/derekparker/delve/source" sys "golang.org/x/sys/unix" ) type OSProcessDetails struct { task C.mach_port_name_t portSet C.mach_port_t exceptionPort C.mach_port_t notificationPort C.mach_port_t } // Create and begin debugging a new process. Uses a // custom fork/exec process in order to take advantage of // PT_SIGEXC on Darwin. func Launch(cmd []string) (*DebuggedProcess, error) { argv0Go, err := filepath.Abs(cmd[0]) if err != nil { return nil, err } argv0 := C.CString(argv0Go) argvSlice := make([]*C.char, 0, len(cmd)) for _, arg := range cmd { argvSlice = append(argvSlice, C.CString(arg)) } var argv **C.char argv = &argvSlice[0] dbp := &DebuggedProcess{ Threads: make(map[int]*Thread), Breakpoints: make(map[uint64]*Breakpoint), firstStart: true, os: new(OSProcessDetails), ast: source.New(), } ret := C.fork_exec(argv0, argv, &dbp.os.task, &dbp.os.portSet, &dbp.os.exceptionPort, &dbp.os.notificationPort) pid := int(ret) if pid <= 0 { return nil, fmt.Errorf("could not fork/exec") } dbp.Pid = pid for i := range argvSlice { C.free(unsafe.Pointer(argvSlice[i])) } dbp, err = initializeDebugProcess(dbp, argv0Go, false) if err != nil { return nil, err } err = dbp.Continue() return dbp, err } func (dbp *DebuggedProcess) requestManualStop() (err error) { var ( task = C.mach_port_t(dbp.os.task) thread = C.mach_port_t(dbp.CurrentThread.os.thread_act) exceptionPort = C.mach_port_t(dbp.os.exceptionPort) ) kret := C.raise_exception(task, thread, exceptionPort, C.EXC_BREAKPOINT) if kret != C.KERN_SUCCESS { return fmt.Errorf("could not raise mach exception") } 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)) ) if count == -1 { return fmt.Errorf("could not get thread count") } list := make([]uint32, count) // TODO(dp) might be better to malloc mem in C and then free it here // 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 { if _, ok := dbp.Threads[int(port)]; !ok { _, err = dbp.addThread(int(port), false) if err != nil { return err } } } return nil } func (dbp *DebuggedProcess) addThread(port int, attach bool) (*Thread, error) { if thread, ok := dbp.Threads[port]; ok { return thread, nil } thread := &Thread{ Id: port, dbp: dbp, os: new(OSSpecificDetails), } dbp.Threads[port] = thread thread.os.thread_act = C.thread_act_t(port) if dbp.CurrentThread == nil { dbp.CurrentThread = thread } 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) parseDebugLineInfo(exe *macho.File, wg *sync.WaitGroup) { defer wg.Done() if sec := exe.Section("__debug_line"); sec != nil { debugLine, err := exe.Section("__debug_line").Data() if err != nil { fmt.Println("could not get __debug_line section", err) os.Exit(1) } dbp.lineInfo = line.Parse(debugLine) } else { fmt.Println("could not find __debug_line section in binary") os.Exit(1) } } func (dbp *DebuggedProcess) findExecutable(path string) (*macho.File, error) { if path == "" { path = C.GoString(C.find_executable(C.int(dbp.Pid))) } exe, err := macho.Open(path) if err != nil { return nil, err } data, err := exe.DWARF() if err != nil { return nil, err } dbp.dwarf = data return exe, nil } func (dbp *DebuggedProcess) trapWait(pid int) (*Thread, error) { var ( th *Thread err error ) for { port := C.mach_port_wait(dbp.os.portSet) switch port { case dbp.os.notificationPort: _, status, err := wait(dbp.Pid, 0) if err != nil { return nil, err } dbp.exited = true return nil, ProcessExitedError{Pid: dbp.Pid, Status: status.ExitStatus()} case C.MACH_RCV_INTERRUPTED: if !dbp.halt { // Call trapWait again, it seems // MACH_RCV_INTERRUPTED is emitted before // process natural death _sometimes_. continue } return nil, ManualStopError{} case 0: return nil, fmt.Errorf("error while waiting for task") } // Since we cannot be notified of new threads on OS X // this is as good a time as any to check for them. dbp.updateThreadList() th, err = dbp.handleBreakpointOnThread(int(port)) if err != nil { if _, ok := err.(NoBreakpointError); ok { th := dbp.Threads[int(port)] if dbp.firstStart || dbp.singleStepping || th.singleStepping { dbp.firstStart = false return dbp.Threads[int(port)], nil } if err := th.Continue(); err != nil { return nil, err } continue } return nil, err } return th, nil } return th, 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 }