From beb3e76f33259aca28fb8d3f5538fc08072d75c5 Mon Sep 17 00:00:00 2001 From: Matt Bauer Date: Wed, 7 Feb 2018 12:25:34 -0600 Subject: [PATCH] proc: Handle race between fork and task_for_pid (#1117) * Handle race between fork and task_for_pid On macOS a call to fork and a subsequent call to task_for_pid will race each other. This is because the macOS kernel assigns a new proc_t structure early but the new task, thread and uthread come much later. The function exec_mach_imgact in the XNU sources contains this logic. In a system under load or one with delays in fork processing (i.e. various security software), task_for_pid as currently called by Delve often returns the parent task. This can be seen by printing out the task number around line 86. In a normal system we would see three calls: -> ~/go/bin/dlv --listen=localhost:59115 --headless=true --api-version=2 --backend=native exec ./___main_go -- Task: 9731 Task: 9731 Task: 9731 API server listening at: 127.0.0.1:59115 This is the result on a system where the race is lost: -> ~/go/bin/dlv --listen=localhost:59115 --headless=true --api-version=2 --backend=native exec ./___main_go -- Task: 8707 Task: 10499 Task: 10499 could not launch process: could not get thread count In this latter case, task 8707 is the parent task. The child task of 10499 was desired and hence the error. This code change checks to make sure the returned task is not that of the parent. If it is, it retries. It's possible other macOS reported Delve issues are the result of this failed race. * proc: correct formatting --- pkg/proc/native/proc_darwin.go | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/pkg/proc/native/proc_darwin.go b/pkg/proc/native/proc_darwin.go index 488f2325..060966ca 100644 --- a/pkg/proc/native/proc_darwin.go +++ b/pkg/proc/native/proc_darwin.go @@ -84,12 +84,17 @@ func Launch(cmd []string, wd string) (*Process, error) { // trapWait to wait until the child process calls execve. for { - err = dbp.updateThreadListForTask(C.get_task_for_pid(C.int(dbp.pid))) - if err == nil { - break - } - if err != couldNotGetThreadCount && err != couldNotGetThreadList { - return nil, err + task := C.get_task_for_pid(C.int(dbp.pid)) + // The task_for_pid call races with the fork call. This can + // result in the parent task being returned instead of the child. + if task != dbp.os.task { + err = dbp.updateThreadListForTask(task) + if err == nil { + break + } + if err != couldNotGetThreadCount && err != couldNotGetThreadList { + return nil, err + } } }