dow98764
2019-05-07 17:27
浏览 56
已采纳

如何“将Go指针传递给Cgo”?

I am confused regarding the passing of Go pointers (which, to my understanding, include all pointer types as well as unsafe.Pointer) to cgo. When calling C functions with cgo, I can only provide variables of types known on the C-side, or unsafe.Pointer if it matches with a void*-typed parameter in the C-function's signature. So when "Go pointers passed to C are pinned for lifetime of call", how does Go know that what I am passing is, in fact, a Go pointer, if I am ever forced to cast it to C.some_wide_enough_uint_type or C.some_c_pointer_type beforehand? The moment it is cast, isn't the information that it is a Go pointer lost, and I run risk of the GC changing the pointer? (I can see how freeing is prevented at least, when a pointer-type reference is retained on the Go-side)

We have a project with a fair amount of working cgo code, but zero confidence in its reliability. I would like to see an example of "here is how to do it correctly" which doesn't resort to circumventing Go's memory model by using C.malloc() or such, which most examples unfortunately do.

So regardless of what "pinning the pointer for lifetime of call" actually means, I see a problem either way:

  1. If it means that Go will pin all pointers in the entire program, I see a race condition in the time interval between casting a Go pointer to a C-type and the cgo-call actually being invoked.
  2. If it means that Go will pin only those Go pointers which are being passed, how does it know that they are Go pointers when, at the time of calling, they can only have a C-type?

I've been reading through Go issues for half the day and am starting to feel like I'm just missing something simple. Any pointers are appreciated.

EDIT: I will try to clarify the question by providing examples.

Consider this:

/*
#include <stdio.h>
void myCFunc(void* ptr) {
    printf((char*)ptr);
}
*/
import "C"
import "unsafe"

func callMyCFunc() {
    goPointer := []byte("abc123
\x00")
    C.myCFunc(unsafe.Pointer(&goPointer[0]))
}

Here, Go's unsafe.Pointer-type effortlessly translates into C's void*-type, so we are happy on the C-side of things, and we should be on the Go-side also: the pointer clearly points into Go-allocated memory, so it should be trivial for Go to figure out that it should pin this pointer during the call, despite it being an unsafe one. Is this the case? If it is, without further research, I would consider this to be the preferred way to pass Go pointers to cgo. Is it?

Then, consider this:

/*
#include <stdio.h>
void myCFunc(unsigned long long int stupidlyTypedPointerVariable) {
    char* pointerToHopefullyStillTheSameMemory = (char*)stupidlyTypedPointerVariable;
    printf(pointerToHopefullyStillTheSameMemory);
}
*/
import "C"
import "unsafe"

func callMyCFunc() {
    goPointer := []byte("abc123
\x00")
    C.myCFunc(C.ulonglong(uintptr(unsafe.Pointer(&goPointer[0]))))
}

Here, I would expect that Go won't make any guesses on whether some C.ulonglong-typed variable actually means to contain the address of a Go pointer. But am I correct?

My confusion largely arises from the fact that it's not really possible to write some code to reliably test this with.

Finally, what about this:

/*
#include <stdio.h>
void cFuncOverWhichIHaveNoControl(char* ptr) {
    printf(ptr);
}
*/
import "C"
import "unsafe"

func callMyCFunc() {
    goPointer := []byte("abc123
\x00")
    C.cFuncOverWhichIHaveNoControl((*C.char)(unsafe.Pointer(&goPointer[0])))
}

If I am, for whatever reason, unable to change the signature of the C-function, I must cast to *C.char. Will Go still check if the value is a Go pointer, when it already is a C pointer-type?

  • 写回答
  • 好问题 提建议
  • 关注问题
  • 收藏
  • 邀请回答

1条回答 默认 最新

  • dtng25909 2019-05-08 11:33
    已采纳

    Looking at the section on passing pointers in the current cgo documentation, (thanks to peterSO) we find that

    the term Go pointer means a pointer to memory allocated by Go

    as well as that

    A pointer type may hold a Go pointer or a C pointer

    Thus, using uintptr and other integer (read: non-pointer) types will lose us Go's guarantee of pinning the pointer.

    A uintptr is an integer, not a reference. Converting a Pointer to a uintptr creates an integer value with no pointer semantics. Even if a uintptr holds the address of some object, the garbage collector will not update that uintptr's value if the object moves, nor will that uintptr keep the object from being reclaimed.

    Source: https://golang.org/pkg/unsafe/#Pointer

    Regarding C pointer types such as *char/*C.char, these will not give you a guarantee either. This can actually be proven by trying to trigger Go's Cgo Debug mechanism, which disallows passing a Go pointer to (or into) a value which itself contains another Go pointer:

    package main
    
    import (
        "fmt"
        "unsafe"
    
        /*
        #include <stdio.h>
    
        void cFuncChar(char* ptr) {
            printf("%s
    ", ptr);
        }
    
        void cFuncVoid(void* ptr) {
            printf("%s
    ", (char*)ptr);
        }
        */
        "C"
    )
    
    type MyStruct struct {
        Distraction [2]byte
        Dangerous *MyStruct
    }
    
    func main() {
        bypassDetection()
        triggerDetection()
    }
    
    func bypassDetection() {
        fmt.Println("=== Bypass Detection ===")
        ms := &MyStruct{[2]byte{'A', 0}, &MyStruct{[2]byte{0, 0}, nil}}
        C.cFuncChar((*C.char)(unsafe.Pointer(ms)))
    }
    
    func triggerDetection() {
        fmt.Println("=== Trigger Detection ===")
        ms := &MyStruct{[2]byte{'B', 0}, &MyStruct{[2]byte{0, 0}, nil}}
        C.cFuncVoid(unsafe.Pointer(ms))
    }
    

    This will print the following:

    === Bypass Detection ===
    A
    === Trigger Detection ===
    panic: runtime error: cgo argument has Go pointer to Go pointer
    

    Using *C.char bypassed the detection. Only using unsafe.Pointer will pin the pointer for the lifetime of the call. Unfortunately, this means we will have to have an occasional nebulous void*-parameter in the C-function's signature.

    评论
    解决 无用
    打赏 举报

相关推荐 更多相似问题