简体   繁体   中英

How to "pass a Go pointer to 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\n\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\n\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\n\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?

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 are only safe when the pointed data does not itself contain pointers to other memory allocated by Go. This can actually be shown 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\n", ptr);
    }

    void cFuncVoid(void* ptr) {
        printf("%s\n", (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 detect Go pointer to Go pointer scenarios. Unfortunately, this means we will have to have an occasional nebulous void* -parameter in the C-function's signature.

Adding for clarity: Go may very well pin the value pointed by a *C.char or such, which is safe to pass; it just (reasonably) won't make an effort to find out whether it might be something else which could contain additional pointers into memory allocated by Go. Casting to unsafe.Pointer is actually safe; casting from it is what may be dangerous.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM