dqqpf32897
dqqpf32897
2017-11-11 11:24

当C库使用不透明的结构指针时,如何解决CGO中的“写障碍错误指针”恐慌

已采纳

I'm currently writing a Go wrapper around a C library. That C library uses opaque struct pointers to hide information across the interface. However, the underlying implementation stores size_t values in there. This leads to runtime errors in the resulting program. A minimum working example to reproduce the problem looks like this:

main.go:

package main

/*
#include "stddef.h"
// Create an opaque type to hide the details of the underlying data structure.
typedef struct HandlePrivate *Handle;

// In reality, the implementation uses a type derived from size_t for the Handle.
Handle getInvalidPointer() {
    size_t actualHandle = 1;
    return (Handle) actualHandle;
}
 */
import "C"

// Create a temporary slice containing invalid pointers.
// The idea is that the local variable slice can be garbage collected at the end of the function call.
// When the slice is scanned for linked objects, the GC comes across the invalid pointers.
func getTempSlice() {
    slice := make([]C.Handle, 1000000)
    for i, _ := range slice {
        slice[i] = C.getInvalidPointer()
    }
}

func main() {
    getTempSlice()
}

Running this program will lead to the following error

runtime: writebarrierptr *0xc42006c000 = 0x1
fatal error: bad pointer in write barrier
[...stack trace omitted...]

Note that the errors disappear when the GC is disabled by setting the environment variable GOGC=off.

My question is which is the best way to solve or work around this problem. The library stores integer values in pointers for the sake of information hiding and this seems to confuse the GC. For obvious reasons I don't want to start messing with the library itself but rather absorb this behaviour in my wrapping layer.

My environment is Ubuntu 16.04, with gcc 5.4.0 and Go 1.9.2.

Documentation of cgo

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 复制链接分享
  • 邀请回答

1条回答

  • dongqingcheng2903 dongqingcheng2903 4年前

    I can reproduce the error for go1.8.5 and go1.9.2. I cannot reproduce the error for tip: devel +f01b928 Sat Nov 11 06:17:48 2017 +0000 (effectively go1.10alpha).


    // Create a temporary slice containing invalid pointers.
    // The idea is that the local variable slice can be garbage collected at the end of the function call.
    // When the slice is scanned for linked objects, the GC comes across the invalid pointers.
    

    A Go mantra is do not ignore errors. However, you seem to assume that that the GC will gracefully ignore errors. The GC should complain loudly (go1.8.5 and go1.9.2). At worst, with undefined behavior that may vary from release to release, the GC may appear to ignore errors (go devel).

    The Go compiler sees a pointer and the Go runtime GC expects a valid pointer.

    // go tool cgo
    // type _Ctype_Handle *_Ctype_struct_HandlePrivate
    // var handle _Ctype_Handle
    var handle C.Handle
    // main._Ctype_Handle <nil> 0x0
    fmt.Fprintf(os.Stderr, "%[1]T %[1]v %[1]p
    ", handle)
    
    slice := make([]C.Handle, 1000000)
    for i, _ := range slice {
        slice[i] = C.getInvalidPointer()
    }
    

    Use type uintptr. For example,

    package main
    
    import "unsafe"
    
    /*
    #include "stddef.h"
    // Create an opaque type to hide the details of the underlying data structure.
    typedef struct HandlePrivate *Handle;
    
    // In reality, the implementation uses a type derived from size_t for the Handle.
    Handle getInvalidPointer() {
        size_t actualHandle = 1;
        return (Handle) actualHandle;
    }
    */
    import "C"
    
    // Create a temporary slice of C pointers as Go integer type uintptr.
    func getTempSlice() {
        slice := make([]uintptr, 1000000)
        for i, _ := range slice {
            slice[i] = uintptr(unsafe.Pointer(C.getInvalidPointer()))
        }
    }
    
    func main() {
        getTempSlice()
    }
    
    点赞 评论 复制链接分享