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

如何“将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.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 如何处理复杂数据表格的除法运算
  • ¥15 如何用stc8h1k08的片子做485数据透传的功能?(关键词-串口)
  • ¥15 有兄弟姐妹会用word插图功能制作类似citespace的图片吗?
  • ¥200 uniapp长期运行卡死问题解决
  • ¥15 请教:如何用postman调用本地虚拟机区块链接上的合约?
  • ¥15 为什么使用javacv转封装rtsp为rtmp时出现如下问题:[h264 @ 000000004faf7500]no frame?
  • ¥15 乘性高斯噪声在深度学习网络中的应用
  • ¥15 关于docker部署flink集成hadoop的yarn,请教个问题 flink启动yarn-session.sh连不上hadoop,这个整了好几天一直不行,求帮忙看一下怎么解决
  • ¥15 深度学习根据CNN网络模型,搭建BP模型并训练MNIST数据集
  • ¥15 C++ 头文件/宏冲突问题解决