Skip to content

"fatal error: bad pointer in write barrier" #7

@sorenisanerd

Description

@sorenisanerd

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)
	}
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions