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

时间:2019-05-07 17:27:31

标签: go

对于将Go指针(据我的理解,包括所有指针类型以及unsafe.Pointer)传递给cgo感到困惑。当使用cgo调用C函数时,我只能 提供C端已知类型的变量,如果它与C中的unsafe.Pointer类型的参数匹配,则可以提供void*功能的签名。因此,当"Go pointers passed to C are pinned for lifetime of call"时,如果我被迫事先将其强制转换为C.some_wide_enough_uint_typeC.some_c_pointer_type的话,Go如何知道我实际上传递的是Go指针?强制转换的那一刻,难道不是丢失了一个Go指针的信息,而我冒着GC更改指针的风险吗? (我可以看到,至少在Go-side保留指针类型的引用时,如何防止释放)

我们有一个项目,其中包含大量可使用的CGO代码,但对其可靠性零信任。我想看到一个“这里是如何正确执行”的示例,该示例不求助于使用C.malloc()或类似方法来规避Go的内存模型,不幸的是,大多数示例都这样做。

因此,不管“固定指针以保持通话的生命周期”到底意味着什么,我都会以两种方式看到问题:

  1. 如果这意味着Go会将所有指针固定在整个程序中,我会看到在将Go指针转换为C型指针与实际cgo调用之间的时间间隔内出现竞争情况调用。
  2. 如果这意味着Go将仅锁定正在传递的Go指针,那么在调用时它们只能具有C类型时,如何知道它们是Go指针?

我已经阅读了整整半天的Go问题,开始感到自己缺少一些简单的东西。任何 pointers 都表示赞赏。

编辑:我将尝试通过提供示例来阐明问题。

考虑一下:

/*
#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]))
}

在这里,Go的unsafe.Pointer类型毫不费力地转换为C的void*类型,因此我们在C方面很高兴,我们也应该在Go方面:指针显然指向Go分配的内存,因此Go应当确定在调用过程中应固定该指针,尽管它是不安全指针,这一点并不重要。 是这种情况吗?如果没有进一步研究,我认为这是将Go指针传递给cgo的首选方式。 是吗?

然后,考虑一下:

/*
#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]))))
}

在这里,我希望Go不会对某些C.ulonglong类型的变量是否实际上意味着包含Go指针的地址做出任何猜测。 但是我正确吗?

我的困惑很大程度上是由于实际上不可能编写一些代码来可靠地对其进行测试。

最后,关于这个:

/*
#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])))
}

如果出于某种原因我无法更改C函数的签名,我必须必须强制转换为*C.char当它已经是C指针类型时,Go还会检查该值是否是Go指针吗?

1 个答案:

答案 0 :(得分:0)

看着the section on passing pointers in the current cgo documentation,(感谢peterSO)我们发现

  

术语Go指针表示Go分配的内存的指针

以及

  

指针类型可以包含Go指针或C指针

因此,使用uintptr和其他整数(读取:非指针)类型将使我们无法保证将指针固定。

  

uintptr是整数,而不是引用。将Pointer转换为uintptr会创建一个没有指针语义的整数值。即使uintptr保留了某个对象的地址,垃圾回收器也不会在对象移动时更新该uintptr的值,也不会阻止uintptr回收该对象。

来源:https://golang.org/pkg/unsafe/#Pointer

关于诸如*char / *C.char这样的C指针类型,这些也不能为您提供保证。实际上,可以通过尝试触发Go的Cgo Debug机制来证明这一点。 ,不允许将Go指针传递到(或传入)本身包含另一个Go指针的值:

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

这将打印以下内容:

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

使用*C.char绕过检测。 仅使用unsafe.Pointer会在调用的整个生命周期内固定指针。不幸的是,这意味着我们在C函数的签名中偶尔需要含糊糊的void*参数