调用系统调用函数时出现异常



我正在使用Go的syscall包来调用用C++编写的DLL。

C++方法签名如下所示。

init(int* buffer, int argc, char* argv[], const char* fileName, const char* key, const char* prefix, const char* version)

这是我用来在 Go 中调用上述方法的函数。

func init(
buffer uintptr, 
argsCount int, 
args []string, 
fileName string, 
key string, 
prefix string, 
version string
) uintptr {
// libHandle is handle to the loaded DLL
methodAddress := getProcAddress(libHandle, "init")
status, _, err := syscall.Syscall9(
methodAddress,
7,
buffer,
uintptr(unsafe.Pointer(&argsCount)),
uintptr(unsafe.Pointer(&args)),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(fileName))),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(key))),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(prefix))),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(version))),
0,
0)
fmt.Println(err.Error())
return status
}

当我调用此方法时,我收到此错误,并且对此一无所知。

Exception 0xc0000005 0x0 0x0 0x7fffe30bdb33
PC=0x7fffe30bdb33
syscall.Syscall9(0x7fffe32db600, 0x7, 0x97db50, 0xc00007ff10, 
0xc00007ff70, 0xc000054180, 0xc0000541a0, 0xc0000541c0, 0xc0000541e0, 
0x0, ...)
c:/go/src/runtime/syscall_windows.go:210 +0xf3
main.main()
E:/Path/test.go:157 +0x2be
rax     0x81fbf0
rbx     0x1
rcx     0x7ff804ad1310
rdi     0x7ff10
rsi     0xc00007ff70
rbp     0x0
rsp     0x81fbc0
r8      0x0
r9      0x7ff804ad0000
r10     0xc00007ff00
r11     0x81fbf0
r12     0x7ff10
r13     0xc00007ff70
r14     0xc000054180
r15     0x97db50
rip     0x7fffe30bdb33
rflags  0x10257
cs      0x33
fs      0x53
gs      0x2b

设置

所以,基本上,你得到的映射是

  1. int* bufferbuffer uintptr

  2. int argcunsafe.Pointer(&argsCount),其中&argsCount是指向int的指针

  3. char* argv[]unsafe.Pointer(&args),其中args[]string

  4. const char* fileNameunsafe.Pointer(syscall.StringToUTF16Ptr(fileName))

    const char* keyconst char* prefixconst char* version——同上。

问题所在

现在这有什么问题。

  1. 禁止在函数之间传递包含实时 Go 对象地址uintptr值。(稍后会详细介绍。

  2. argsCount函数参数的地址传递给argc。像amd64/Windows这样的典型商品平台/操作系统上的地址是一个疯狂的巨大价值 - 如果解释为一个计数。

    我敢打赌,当函数尝试从argv读取那么多元素时,它会崩溃——导致它读取你的进程没有映射的内存。

  3. 这里有两个问题:

    将切片值的地址
    1. 作为参数传递到期望该切片的第一个元素的地址是错误的。

      这是因为切片值(目前,在你应该使用的"参考"Go 实现中)是一个具有 3 个字段的struct:底层数据数组的地址、该数组中允许使用的元素数量以及该数组中此类元素的总数,当调用切片值时,append函数可以在不重新分配的情况下使用。

      当您有args []string并执行&args时,您将获得该结构的地址,而不是该切片的第一个元素的地址。

      要执行后者,请使用&args[0].

    2. 在 Go 中,字符串(通常是这样,让我们假设是这种情况)包含编码为 UTF-8 的字符。这通常不是Windows本机C++代码在说它想要char *时所期望处理的。

      我的猜测,你需要首先为argv构建一个合适的东西, 类似的东西

      argv := make([]unsafe.Pointer, 0, len(args))
      for i, s := range args {
      argv[i] = unsafe.Pointer(syscall.StringToUTF16Ptr(s))
      }
      

      然后将&argv[0]传递给被调用方。

      但请参阅下文以获取syscall.StringToUTF16Ptr()

  4. 您正在做的将字符串数据传递给具有const char*类型的其余参数的准备工作似乎是正确的,但前提是被调用方确实意味着其char是 16 位整数。

    换句话说,用于编译该库的源代码和工具链必须确保char确实wchar_tWCHAR

    如果是,你做的应该是可以的;否则就不是。您应该验证这一点。

有关跨表达式传递uintptr的说明

Go 具有垃圾回收功能,因此它的运行时必须知道指向所有当前活动对象的所有指针。只要存在一个变量,其中包含指向在程序运行时分配的内存块的指针,该块就不会被垃圾回收。

unsafe.Pointer值计为对内存块的正确引用,但uintptr则不算。这意味着当代码

p := new(someType)
u := uintptr(unsafe.Pointer(&p))
foo(u)
return

正在运行,GC 可以在p分配地址后立即回收p分配的对象 - 仅仅是因为p是对该对象的唯一引用,而u不是。

现在考虑参考 Go 实现中的 GC 与程序代码同时运行。 这意味着当foo运行时,GC 也可以运行,它可能会从foo脚下扫描您的someType实例。

作为此一般规则的例外,Go 编译器保证在同一语言表达式中发生的所有uintptr(unsafe.Pointer)类型转换不受 GC 的影响。所以以我们之前的例子为例,可以这样做

foo(uintptr(unsafe.Pointer(new(someType)))
return

p := new(someType)
v := unsafe.Pointer(&p)
foo(uintptr(v))
return

因为类型转换为uintptr发生在单个表达式中,这是一个函数调用。

因此,不得将指向 Go 对象的指针作为uintptr传递,除非它们包含从"C 端"获得的指针。

最新更新