I made a mistake in an application that caused select() (from libc) to get called in a tight loop. To my surprise, I got:
runtime: writebarrierptr *0xc42039c8a0 = 0x2
fatal error: bad pointer in write barrier
runtime stack:
runtime.throw(0x4d1430, 0x1c)
/home/soren/lib/go/go/src/runtime/panic.go:605 +0x95
runtime.writebarrierptr.func1()
/home/soren/lib/go/go/src/runtime/mbarrier.go:213 +0xbd
runtime.systemstack(0xc420026600)
/home/soren/lib/go/go/src/runtime/asm_amd64.s:344 +0x79
runtime.mstart()
/home/soren/lib/go/go/src/runtime/proc.go:1135
goroutine 1 [running]:
runtime.systemstack_switch()
/home/soren/lib/go/go/src/runtime/asm_amd64.s:298 fp=0xc42003fb08 sp=0xc42003fb00 pc=0x450c50
runtime.writebarrierptr(0xc42039c8a0, 0x2)
/home/soren/lib/go/go/src/runtime/mbarrier.go:211 +0x8d fp=0xc42003fb40 sp=0xc42003fb08 pc=0x41143d
github.com/rainycape/dl.makeTrampoline.func1(0xc42039e000, 0x5, 0x5, 0x0, 0x0, 0x0)
/home/soren/src/go/src/github.com/rainycape/dl/trampoline.go:74 +0x866 fp=0xc42003fd60 sp=0xc42003fb40 pc=0x499626
reflect.callReflect(0xc42000c0a0, 0xc42003ff20)
/home/soren/lib/go/go/src/reflect/value.go:514 +0x38b fp=0xc42003ff00 sp=0xc42003fd60 pc=0x47f78b
reflect.makeFuncStub(0x2, 0x0, 0x1294770, 0x0, 0x0, 0x1, 0x1, 0x1294770, 0x4ac600, 0xc420392f60, ...)
/home/soren/lib/go/go/src/reflect/asm_amd64.s:17 +0x33 fp=0xc42003ff20 sp=0xc42003ff00 pc=0x4851c3
main.main()
/home/soren/src/go/src/dlfailuretest/main.go:27 +0xad fp=0xc42003ff80 sp=0xc42003ff20 pc=0x49b22d
runtime.main()
/home/soren/lib/go/go/src/runtime/proc.go:195 +0x226 fp=0xc42003ffe0 sp=0xc42003ff80 pc=0x42aa36
runtime.goexit()
/home/soren/lib/go/go/src/runtime/asm_amd64.s:2337 +0x1 fp=0xc42003ffe8 sp=0xc42003ffe0 pc=0x453311
This happens after MANY calls to select(), so it's very racy.
trampoline.go:74 is where the first argument to select() is being added to the args array. mbarrier.go is a bit over my head, but if I'm guessing correctly, the fact that that first argument (which was C.int(2) in my case) is being passed along as unsafe.Pointer (to satisfy the void** type make_call expects), causes something in Go to get upset, because 2 is not a valid pointer value.
If I disable garbage collection, it never seems to happen, fwiw.
At any rate, it does seem fair that an unsafe.Pointer(2) is indeed considered problematic. It's not just unsafe. It's invalid.
Here's how you can reproduce it:
package main
import "log"
import "github.com/rainycape/dl"
// #include <sys/select.h>
// void setFd(int fd, fd_set *fds) {
// FD_SET(fd, fds);
// }
import "C"
var oldSelect func(nfds C.int, readfds, writefds, exceptfds *C.fd_set, timeval *C.struct_timeval) int
func loadOldFunc(sym string, f interface{}) {
lib, err := dl.Open("libc", 0)
if err != nil {
log.Fatalln(err)
}
defer lib.Close()
lib.Sym(sym, f)
}
func main() {
loadOldFunc("select", &oldSelect)
writefds := (*C.fd_set)(C.malloc(C.sizeof_fd_set))
C.setFd(C.int(0), writefds)
for {
seenFds := oldSelect(C.int(2), (*C.fd_set)(nil), writefds, (*C.fd_set)(nil), (*C.struct_timeval)(nil))
log.Printf("%v", seenFds)
}
}
I made a mistake in an application that caused select() (from libc) to get called in a tight loop. To my surprise, I got:
This happens after MANY calls to select(), so it's very racy.
trampoline.go:74 is where the first argument to select() is being added to the args array. mbarrier.go is a bit over my head, but if I'm guessing correctly, the fact that that first argument (which was C.int(2) in my case) is being passed along as unsafe.Pointer (to satisfy the void** type make_call expects), causes something in Go to get upset, because 2 is not a valid pointer value.
If I disable garbage collection, it never seems to happen, fwiw.
At any rate, it does seem fair that an unsafe.Pointer(2) is indeed considered problematic. It's not just unsafe. It's invalid.
Here's how you can reproduce it: