]> Cypherpunks repositories - gostls13.git/commitdiff
[release-branch.go1.18] runtime: call __fork instead of fork on darwin
authorRuss Cox <rsc@golang.org>
Thu, 22 Dec 2022 14:44:56 +0000 (09:44 -0500)
committerRuss Cox <rsc@golang.org>
Thu, 22 Dec 2022 19:21:48 +0000 (19:21 +0000)
Issues #33565 and #56784 were caused by hangs in the child process
after fork, while it ran atfork handlers that ran into slow paths that
didn't work in the child.

CL 451735 worked around those two issues by calling a couple functions
at startup to try to warm up those child paths. That mostly worked,
but it broke programs using cgo with certain macOS frameworks (#57263).

CL 459175 reverted CL 451735.

This CL introduces a different fix: bypass the atfork child handlers
entirely. For a general fork call where the child and parent are both
meant to keep executing the original program, atfork handlers can be
necessary to fix any state that would otherwise be tied to the parent
process. But Go only uses fork as preparation for exec, and it takes
care to limit what it attempts to do in the child between the fork and
exec. In particular it doesn't use any of the things that the macOS
atfork handlers are trying to fix up (malloc, xpc, others). So we can
use the low-level fork system call (__fork) instead of the
atfork-wrapped one.

The full list of functions that can be called in a child after fork in
exec_libc2.go is:

 - ptrace
 - setsid
 - setpgid
 - getpid
 - ioctl
 - chroot
 - setgroups
 - setgid
 - setuid
 - chdir
 - dup2
 - fcntl
 - close
 - execve
 - write
 - exit

I disassembled all of these while attached to a hung exec.test binary
and confirmed that nearly all of them are making direct kernel calls,
not using anything that the atfork handler needs to fix up.
The exceptions are ioctl, fcntl, and exit.

The ioctl and fcntl implementations do some extra work around the
kernel call but don't call any other functions, so they should still
be OK. (If not, we could use __ioctl and __fcntl instead, but without
a good reason, we should keep using the standard entry points.)

The exit implementation calls atexit handlers. That is almost
certainly inappropriate in a failed fork child, so this CL changes
that call to __exit on darwin. To avoid making unnecessary changes at
this point in the release cycle, this CL leaves OpenBSD calling plain
exit, even though that is probably a bug in the OpenBSD port
(filed #57446).

Fixes #33565.
Fixes #56784.
Fixes #57263.
Fixes #56836.

Change-Id: I26812c26a72bdd7fcf72ec41899ba11cf6b9c4ab
Reviewed-on: https://go-review.googlesource.com/c/go/+/459176
Reviewed-by: David Chase <drchase@google.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Russ Cox <rsc@golang.org>
Reviewed-on: https://go-review.googlesource.com/c/go/+/459179

src/syscall/exec_libc2.go
src/syscall/syscall_darwin.go
src/syscall/syscall_openbsd_libc.go
src/syscall/zsyscall_darwin_amd64.go
src/syscall/zsyscall_darwin_amd64.s
src/syscall/zsyscall_darwin_arm64.go
src/syscall/zsyscall_darwin_arm64.s

index b05f053bbf3fa75cac478f50955454f624f521de..bd2ce6820de13f64bd94584f6d974f9311e17929 100644 (file)
@@ -76,7 +76,7 @@ func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr
        // About to call fork.
        // No more allocation or calls of non-assembly functions.
        runtime_BeforeFork()
-       r1, _, err1 = rawSyscall(abi.FuncPCABI0(libc_fork_trampoline), 0, 0, 0)
+       r1, _, err1 = rawSyscall(forkTrampoline, 0, 0, 0)
        if err1 != 0 {
                runtime_AfterFork()
                return 0, err1
@@ -260,6 +260,6 @@ childerror:
        // send error code on pipe
        rawSyscall(abi.FuncPCABI0(libc_write_trampoline), uintptr(pipe), uintptr(unsafe.Pointer(&err1)), unsafe.Sizeof(err1))
        for {
-               rawSyscall(abi.FuncPCABI0(libc_exit_trampoline), 253, 0, 0)
+               rawSyscall(exitTrampoline, 253, 0, 0)
        }
 }
index 902d6e77e11cb2bbf0e9512704a43ed8e77532f6..346e6862cd86a6f440e7092d5ad276b749a8f325 100644 (file)
@@ -17,6 +17,34 @@ import (
        "unsafe"
 )
 
+// These are called from exec_libc2.go in the child of fork.
+// The names differ between macOS and OpenBSD, so we need
+// to declare the specific ones used here to keep the exec_libc2.go
+// code portable.
+//
+// We use __fork and __exit, not fork and exit, to avoid the libc atfork
+// and atexit handlers. The atfork handlers have caused fork child
+// hangs in the past (see #33565, #56784). The atexit handlers have
+// not, but the non-libc ports all invoke the system call, so doing
+// the same here makes sense. In general we wouldn't expect
+// atexit handlers to work terribly well in a fork child anyway.
+// (Also, perhaps the atfork handlers clear the atexit handlers,
+// in which case we definitely need to avoid calling the libc exit
+// if we bypass the libc fork.)
+//
+// Other calls that are made in the child after the fork are
+// ptrace, setsid, setpgid, getpid, ioctl, chroot, setgroups,
+// setgid, setuid, chdir, dup2, fcntl, close, execve, and write.
+// Those are all simple kernel wrappers that should be safe
+// to be called directly. The fcntl and ioctl functions do run
+// some code around the kernel call, but they don't call any
+// other functions, so for now we keep using them instead of
+// calling the lower-level __fcntl and __ioctl functions.
+var (
+       exitTrampoline = abi.FuncPCABI0(libc___exit_trampoline)
+       forkTrampoline = abi.FuncPCABI0(libc___fork_trampoline)
+)
+
 type SockaddrDatalink struct {
        Len    uint8
        Family uint8
@@ -202,11 +230,12 @@ func Kill(pid int, signum Signal) (err error) { return kill(pid, int(signum), 1)
 //sys  writev(fd int, iovecs []Iovec) (cnt uintptr, err error)
 //sys   mmap(addr uintptr, length uintptr, prot int, flag int, fd int, pos int64) (ret uintptr, err error)
 //sys   munmap(addr uintptr, length uintptr) (err error)
-//sysnb fork() (pid int, err error)
+//sysnb __fork() (pid int, err error)
 //sysnb ioctl(fd int, req int, arg int) (err error)
 //sysnb ioctlPtr(fd int, req uint, arg unsafe.Pointer) (err error) = SYS_ioctl
 //sysnb execve(path *byte, argv **byte, envp **byte) (err error)
 //sysnb exit(res int) (err error)
+//sysnb __exit(res int) (err error)
 //sys  sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error)
 //sys  fcntlPtr(fd int, cmd int, arg unsafe.Pointer) (val int, err error) = SYS_fcntl
 //sys   unlinkat(fd int, path string, flags int) (err error)
index 15b68fd0fcf51c780738f12ee73b00526f1c36ac..ff9fa0025904a76537da72e34be125144b9a96be 100644 (file)
@@ -10,6 +10,11 @@ import (
        "internal/abi"
 )
 
+var (
+       exitTrampoline = abi.FuncPCABI0(libc_exit_trampoline)
+       forkTrampoline = abi.FuncPCABI0(libc_fork_trampoline)
+)
+
 func init() {
        execveOpenBSD = execve
 }
index 0ccdaf2d0eea6d2613201cd72d1951bcbaca527a..2ad03801b2ec824f7da36435556611b48070fbb4 100644 (file)
@@ -1714,8 +1714,8 @@ func libc_munmap_trampoline()
 
 // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
 
-func fork() (pid int, err error) {
-       r0, _, e1 := rawSyscall(abi.FuncPCABI0(libc_fork_trampoline), 0, 0, 0)
+func __fork() (pid int, err error) {
+       r0, _, e1 := rawSyscall(abi.FuncPCABI0(libc___fork_trampoline), 0, 0, 0)
        pid = int(r0)
        if e1 != 0 {
                err = errnoErr(e1)
@@ -1723,9 +1723,9 @@ func fork() (pid int, err error) {
        return
 }
 
-func libc_fork_trampoline()
+func libc___fork_trampoline()
 
-//go:cgo_import_dynamic libc_fork fork "/usr/lib/libSystem.B.dylib"
+//go:cgo_import_dynamic libc___fork __fork "/usr/lib/libSystem.B.dylib"
 
 // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
 
@@ -1781,6 +1781,20 @@ func libc_exit_trampoline()
 
 // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
 
+func __exit(res int) (err error) {
+       _, _, e1 := rawSyscall(abi.FuncPCABI0(libc___exit_trampoline), uintptr(res), 0, 0)
+       if e1 != 0 {
+               err = errnoErr(e1)
+       }
+       return
+}
+
+func libc___exit_trampoline()
+
+//go:cgo_import_dynamic libc___exit __exit "/usr/lib/libSystem.B.dylib"
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
 func sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) {
        var _p0 unsafe.Pointer
        if len(mib) > 0 {
index 563083d441a17c7600c4265e4104adc9498bc60b..1815703803d1c71378ddca834e0b5c3300d3116c 100644 (file)
@@ -219,14 +219,16 @@ TEXT ·libc_mmap_trampoline(SB),NOSPLIT,$0-0
        JMP     libc_mmap(SB)
 TEXT ·libc_munmap_trampoline(SB),NOSPLIT,$0-0
        JMP     libc_munmap(SB)
-TEXT ·libc_fork_trampoline(SB),NOSPLIT,$0-0
-       JMP     libc_fork(SB)
+TEXT ·libc___fork_trampoline(SB),NOSPLIT,$0-0
+       JMP     libc___fork(SB)
 TEXT ·libc_ioctl_trampoline(SB),NOSPLIT,$0-0
        JMP     libc_ioctl(SB)
 TEXT ·libc_execve_trampoline(SB),NOSPLIT,$0-0
        JMP     libc_execve(SB)
 TEXT ·libc_exit_trampoline(SB),NOSPLIT,$0-0
        JMP     libc_exit(SB)
+TEXT ·libc___exit_trampoline(SB),NOSPLIT,$0-0
+       JMP     libc___exit(SB)
 TEXT ·libc_sysctl_trampoline(SB),NOSPLIT,$0-0
        JMP     libc_sysctl(SB)
 TEXT ·libc_unlinkat_trampoline(SB),NOSPLIT,$0-0
index 09bf34bb3c55e4fd8aecaac90af61fb16c18a96c..559bb49c4bcf68b70fb2940bc31546a46b32fe6f 100644 (file)
@@ -1714,8 +1714,8 @@ func libc_munmap_trampoline()
 
 // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
 
-func fork() (pid int, err error) {
-       r0, _, e1 := rawSyscall(abi.FuncPCABI0(libc_fork_trampoline), 0, 0, 0)
+func __fork() (pid int, err error) {
+       r0, _, e1 := rawSyscall(abi.FuncPCABI0(libc___fork_trampoline), 0, 0, 0)
        pid = int(r0)
        if e1 != 0 {
                err = errnoErr(e1)
@@ -1723,9 +1723,9 @@ func fork() (pid int, err error) {
        return
 }
 
-func libc_fork_trampoline()
+func libc___fork_trampoline()
 
-//go:cgo_import_dynamic libc_fork fork "/usr/lib/libSystem.B.dylib"
+//go:cgo_import_dynamic libc___fork __fork "/usr/lib/libSystem.B.dylib"
 
 // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
 
@@ -1781,6 +1781,20 @@ func libc_exit_trampoline()
 
 // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
 
+func __exit(res int) (err error) {
+       _, _, e1 := rawSyscall(abi.FuncPCABI0(libc___exit_trampoline), uintptr(res), 0, 0)
+       if e1 != 0 {
+               err = errnoErr(e1)
+       }
+       return
+}
+
+func libc___exit_trampoline()
+
+//go:cgo_import_dynamic libc___exit __exit "/usr/lib/libSystem.B.dylib"
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
 func sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) {
        var _p0 unsafe.Pointer
        if len(mib) > 0 {
index 0567a42fa337985c3b5d0115cf27299c9ebe04d4..1666cf985c35b0a185ff49e8e9c1ad6f04d8f5d2 100644 (file)
@@ -219,14 +219,16 @@ TEXT ·libc_mmap_trampoline(SB),NOSPLIT,$0-0
        JMP     libc_mmap(SB)
 TEXT ·libc_munmap_trampoline(SB),NOSPLIT,$0-0
        JMP     libc_munmap(SB)
-TEXT ·libc_fork_trampoline(SB),NOSPLIT,$0-0
-       JMP     libc_fork(SB)
+TEXT ·libc___fork_trampoline(SB),NOSPLIT,$0-0
+       JMP     libc___fork(SB)
 TEXT ·libc_ioctl_trampoline(SB),NOSPLIT,$0-0
        JMP     libc_ioctl(SB)
 TEXT ·libc_execve_trampoline(SB),NOSPLIT,$0-0
        JMP     libc_execve(SB)
 TEXT ·libc_exit_trampoline(SB),NOSPLIT,$0-0
        JMP     libc_exit(SB)
+TEXT ·libc___exit_trampoline(SB),NOSPLIT,$0-0
+       JMP     libc___exit(SB)
 TEXT ·libc_sysctl_trampoline(SB),NOSPLIT,$0-0
        JMP     libc_sysctl(SB)
 TEXT ·libc_unlinkat_trampoline(SB),NOSPLIT,$0-0