调用 Syscall 函数时出现异常
Getting an exception when calling Syscall function
我正在使用 Go 的系统调用包来调用用 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
设置
所以,基本上,您得到的映射是
int* buffer
→buffer uintptr
int argc
→ unsafe.Pointer(&argsCount)
,其中 &argsCount
是指向 int
的指针
char* argv[]
→ unsafe.Pointer(&args)
,其中 args
是 []string
const char* fileName
→unsafe.Pointer(syscall.StringToUTF16Ptr(fileName))
const char* key
、const char* prefix
、const char* version
— 同上。
问题
现在这有什么问题。
禁止在函数之间传递包含实时 Go 对象地址的 uintptr
值。 (稍后会详细介绍。)
您将 argsCount
函数参数的地址传递给 argc
。像 amd64/Windows 这样的典型商品 platform/OS 上的地址是一个非常巨大的值——如果解释为计数。
我敢打赌,该函数在尝试从 argv
中读取那么多元素时会崩溃——导致它读取您的进程未映射的内存。
这里有两个问题:
将切片值的地址作为期望该切片的第一个元素的地址的参数传递是错误的。
这是因为切片值(目前,在您应该使用的“参考”Go 实现中)是一个具有 3 个字段的 struct
:底层数据数组的地址、元素的数量在允许使用的数组中,以及在调用切片值时 append
函数无需重新分配即可使用的数组中此类元素的总数。
当您有 args []string
并执行 &args
时,您将获得该结构的地址,而不是该切片的第一个元素的地址。
要执行后者,请使用 &args[0]
。
在 Go 中,一个字符串(通常,我们假设是这种情况)包含编码为 UTF-8 的字符。这通常不是 Windows-native 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()
。
您为将字符串数据传递给具有 const char*
类型的其余参数所做的准备似乎是正确的,但前提是被调用者确实意味着其 char
是一个 16 位整数。
换句话说,用于编译该库的源代码和工具链必须确保char
确实是wchar_t
or WCHAR
。
如果是,那么你做的应该没问题;否则不是。你应该验证一下。
关于在表达式中传递 uintptr
的注意事项
Go 具有垃圾收集功能,因此它的 运行time 必须知道指向所有当前活动对象的所有指针。只要存在一个包含指向在程序 运行 期间分配的内存块的指针的变量,该块就不会是 garbage-collected.
unsafe.Pointer
值算作对内存块的正确引用,但 uintptr
值 不是。 这意味着当代码
p := new(someType)
u := uintptr(unsafe.Pointer(&p))
foo(u)
return
是运行ning,一旦p
分配了它的地址,GC就可以自由回收p
分配的对象——仅仅是因为p
是对该对象的唯一引用,而 u
不是。
现在考虑参考 Go 实现中的 GC 运行 与您的程序代码同时发生。
这意味着当 foo
运行s 时,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
因为 type-conversion 到 uintptr
发生在单个表达式中——这是一个函数调用。
因此,您不能将指向 Go 对象的指针作为 uintptr
s 传递,除非 它们包含从“C 端”获得的指针。
我正在使用 Go 的系统调用包来调用用 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
设置
所以,基本上,您得到的映射是
int* buffer
→buffer uintptr
的指针int argc
→unsafe.Pointer(&argsCount)
,其中&argsCount
是指向int
char* argv[]
→unsafe.Pointer(&args)
,其中args
是[]string
const char* fileName
→unsafe.Pointer(syscall.StringToUTF16Ptr(fileName))
const char* key
、const char* prefix
、const char* version
— 同上。
问题
现在这有什么问题。
禁止在函数之间传递包含实时 Go 对象地址的
uintptr
值。 (稍后会详细介绍。)您将
argsCount
函数参数的地址传递给argc
。像 amd64/Windows 这样的典型商品 platform/OS 上的地址是一个非常巨大的值——如果解释为计数。我敢打赌,该函数在尝试从
argv
中读取那么多元素时会崩溃——导致它读取您的进程未映射的内存。这里有两个问题:
将切片值的地址作为期望该切片的第一个元素的地址的参数传递是错误的。
这是因为切片值(目前,在您应该使用的“参考”Go 实现中)是一个具有 3 个字段的
struct
:底层数据数组的地址、元素的数量在允许使用的数组中,以及在调用切片值时append
函数无需重新分配即可使用的数组中此类元素的总数。当您有
args []string
并执行&args
时,您将获得该结构的地址,而不是该切片的第一个元素的地址。要执行后者,请使用
&args[0]
。在 Go 中,一个字符串(通常,我们假设是这种情况)包含编码为 UTF-8 的字符。这通常不是 Windows-native 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()
。
您为将字符串数据传递给具有
const char*
类型的其余参数所做的准备似乎是正确的,但前提是被调用者确实意味着其char
是一个 16 位整数。换句话说,用于编译该库的源代码和工具链必须确保
char
确实是wchar_t
orWCHAR
。如果是,那么你做的应该没问题;否则不是。你应该验证一下。
关于在表达式中传递 uintptr
的注意事项
Go 具有垃圾收集功能,因此它的 运行time 必须知道指向所有当前活动对象的所有指针。只要存在一个包含指向在程序 运行 期间分配的内存块的指针的变量,该块就不会是 garbage-collected.
unsafe.Pointer
值算作对内存块的正确引用,但 uintptr
值 不是。 这意味着当代码
p := new(someType)
u := uintptr(unsafe.Pointer(&p))
foo(u)
return
是运行ning,一旦p
分配了它的地址,GC就可以自由回收p
分配的对象——仅仅是因为p
是对该对象的唯一引用,而 u
不是。
现在考虑参考 Go 实现中的 GC 运行 与您的程序代码同时发生。
这意味着当 foo
运行s 时,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
因为 type-conversion 到 uintptr
发生在单个表达式中——这是一个函数调用。
因此,您不能将指向 Go 对象的指针作为 uintptr
s 传递,除非 它们包含从“C 端”获得的指针。