Getting an exception when calling Syscall function

2

I am using Go's syscall package to call a DLL that is written in C++.

C++ method signature looks like this.

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

this is the function I am using to call above method in 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
 }

I get this Error When I invoking this method and don't have an Idea about this.

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
c++
go
dll
ffi
asked on Stack Overflow Jun 25, 2019 by Arunwij • edited Jun 25, 2019 by kostix

1 Answer

3

The setting

So, basically, the mapping you've got is

  1. int* bufferbuffer uintptr

  2. int argcunsafe.Pointer(&argsCount), where &argsCount is a pointer to int

  3. char* argv[]unsafe.Pointer(&args), where args is []string

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

    const char* key, const char* prefix, const char* version — same as above.

The problems

Now what's wrong with this.

  1. Passing uintptr values containing addresses of live Go objects between functions is prohibited. (More on this later.)

  2. You pass the address of the argsCount function argument to argc. An address on a typical commodity platform/OS like amd64/Windows is an insanely huge value—if interpreted as a count.

    My bet is that the function crashes when it makes an attempt to read that many elements from argv—causing it to read memory your process had not mapped.

  3. Two problems here:

    1. Passing an address of a slice value as an argument expecting the address of the first element of that slice is wrong.

      This is because a slice value (presently, in the "reference" Go implementation you're supposedly using) is a struct with 3 fields: an address of the underlying data array, the number of elements in that array allowed to be used and the total count of such elements in that array which may be used without reallocation by the append function when called on the slice value.

      When you have args []string and do &args you get the address of that structure, not the address of the first element of that slice.

      To do the latter, use &args[0].

    2. In Go, a string (typically, and let's assume that's the case) contains characters encoded as UTF-8. This is typically not what a Windows-native C++ code expects to deal with when it says it wants a char *.

      My guess, you need to first construct a proper thing for the argv, something like

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

      and then pass &argv[0] to the callee.

      But see below for syscall.StringToUTF16Ptr().

  4. The preparation you're doing to pass string data to the rest of the arguments which have the const char* type appears to be correct but only if the callee really means its char is a 16-bit integer.

    In other words, the source code and the toolchain used to compile that library must have made sure that char was really wchar_t or WCHAR.

    If yes, what you do should be OK; otherwise it's not. You should verify this.

A note on passing uintptrs across expressions

Go features garbage collection, and because of this its runtime must know all pointers to all currently live objects. As long as there exists a variable containing a pointer to a memory block allocated during the program run time, that block won't be garbage-collected.

An unsafe.Pointer value counts as a proper reference to a memory block but an uintptr value is not. This means when the code

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

is running, the GC is free to reclaim the object allocated by p as soon as p was assigned its address—simply because p is the sole reference to that object, and u is not.

Now consider that the GC in the reference Go implementation runs concurrently with the code of your program. This means when foo runs, so may do the GC, and it may sweep your instance of someType right from under the feet of foo.

As an exception to this general rule, the Go compiler guarantees that all the uintptr(unsafe.Pointer) type conversions occuring in the same language expression are immune to the GC. So taking our previous example, it's OK to do

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

and

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

since the type-conversion to uintptr happens in a single expression—which is a function call.

As a consequence, you must not pass pointers to Go objects around as uintptrs unless they contain pointers obtained from the "C side".

answered on Stack Overflow Jun 25, 2019 by kostix • edited Feb 2, 2021 by kostix

User contributions licensed under CC BY-SA 3.0