1

I have the next case:

There is an application (presumably in C/C ++) that runs on Windows (32 bit) and pulls in a bunch of .dll files. We do not have access to the source code for this application. The task is to write a small driver in the image and likeness of the old .dll, but in Go.

Work flow and the problem: a string of type char* is passed to the Go function from this application, and we need to write some result string by this pointer. As we were told, the memory has already been allocated, i.e. on the Go side, we don't need to do any malloc, just write the result. As a result, an Access violation occurs with the value of the pointer on which we are trying to write.

Does anyone know anything about this?

We wrote a similar shared library in C ++ and assembled everything as a .dll, which also writes the result to the string-input parameter, the result: everything works as expected.

We logged input pointers both, in Go and C++ shared librarys, and they are different. We guess it may be some kind of shift in the areas of memory when pulling in Go and C++ .dll files to main app.

I also wrote a simple app in C that calls .dll and everything worked just fine, Go library was able to write result by pointer that it have received from C app. The difference is that in this case the linking took place at the compilation stage, but on the production it occurs at runtime. So at this point we don't have any ideas.

UPD: My Go code of Test function (doesn't work)

//export Test
func Test(ptrParams *C.char, count C.int, resultStr *C.char) int {
    log.Info("Test func exec start ...")
    str := "test_driver_success"
    C.strcpy((*C.char)(resultStr), (*C.char)(C.CString(str)))
    log.Info("Test func exec end ...")
    return 0
}

Error: Access violation at address 755EF448 in module 'msvcrt.dll'. Write of address 0051B3B1

C++ code that works

EXTERN_DLL_EXPORT int Test(char *params, int params_count, char* result)
{
    strncpy_s(result, 100, "test_driver_success", strlen("test_driver_success")+1);
}
  • `a string of type char* is passed` - surely also the length? `We logged input pointers and they are different` - that is to be expected if it's allocated each time anew? `linking took place at the compilation stage` - do you actually mean the use of a .lib file, or the absence of the need to call GetProcAddress? – GSerg Sep 09 '22 at 07:54
  • You need to show at least _some_ more because the problems really can be in several places. Just off the top of my mind: 1) A string you're writing contains characters outside of the ASCII range, and Go's strings are (usually) encoded in UTF-8, in which the length of the string in characters can be (significantly) shorter than its length in bytes; 2) you're somehow misusing the pointer; 3) Some problem with typing: on the DLL level, that function accepts a pointer, and that's all there is to it. But what real type it was _in the code?_ plain `char*`? `LPSTR`? `LPWSTR`? something else? … – kostix Sep 09 '22 at 08:25
  • … 4) As @GSerg correctly suggested, DLLs usually export C-compatible API, and C has no strings—it has NUL-terminated arrays of bytes, so the question is: does your function accept a NUL-terminated string or do you know the length of the buffer it allocated (and if yes, see # 2 and 3)? do you need to somehow tell the function the length of the data you have written to the buffer? if yes, see #2. I think I could continue, but I hope you see the point. – kostix Sep 09 '22 at 08:27
  • @kostix I've updated my question with code samples. 1) About characters: I need to write result of the following form: 123456FRH 2) Well if so, I'm not sure how to use it then; 3) It was char*; 4) We've tried to convert go string to char* with C.CString() and with adding "\x00" to end of the string, nothing worked; 5) I don't need to pass a length, I just have a pointer to a char array with allocated ~1000 bytes and I need to write some result. – Robert Mulyukov Sep 09 '22 at 10:55
  • Works for me: . I'm on Linux, so I've used cross compiling and MinGW to build a 32-bit x86 DLL, and run the result under Wine, but I assume it should not make any real difference. Hence I think your problem may be deeper. What happens if you write just a single byte at `result[0]`? Does it fail? – kostix Sep 09 '22 at 13:37
  • @kostix thanks for your solution, I'll test it. Actually I've used this approach with my mock.c program and it works just fine, except some flags in commands which you've used during compilation. The issue occurs on production environment which is a virtual machine with windows installed (found out it just now). Now I'm reading this thread https://groups.google.com/g/golang-nuts/c/m7IFRYnI-L4 but not sure if it helps. – Robert Mulyukov Sep 09 '22 at 13:50
  • The VM that thread talks about is not about VMs which run guest operating systems but rather about a type of runtime environment such as that used by vanilla Java—when the byte-compiled Java code runs on a Java Virtual Machine (JVM). – kostix Sep 09 '22 at 14:00
  • «issue occurs on production environment which is a virtual machine with windows»—is it possible to verify the complete program work in _some other_ environment? I mean, Windows, but other OS version/bare metal as opposed to a VM etc. Also: are your sure the `Test` function of your C++ code was compiled in the `extern "C" { ... }` block? I mean, it may turn out that your calling code expects the functions to be of C++ ABI, not C? Start [here](https://en.cppreference.com/w/cpp/language/language_linkage) if not sure. … – kostix Sep 09 '22 at 14:04
  • … I mean, your C++ code is actually C, but _calling convention_ of the produced DLL's function is language-dependent. Also, you might verify no compiler options/macros/whatever affects the calling convention the caller expects from your function. See , etc. – kostix Sep 09 '22 at 14:05
  • @kostix, we've found out that calling app was written in Delphi, not C++. By the way, I've already wrote the .dll in C++, but the question still remains about not being able to write string into received argument-pointer. Do you know something about Delphi-Go linking? – Robert Mulyukov Oct 20 '22 at 13:24
  • @RobertMulyukov, I have had sheer exposure to Delphi in the past but not with glueing Delphi and Go code together. Still, I may offer an idea to explore: in Delphi, as in Go, strings are not mere pointers to data. Delphi has `String` (native) `WideString` (Win32-friendly UTF-16-encoded `\x00\x00`-padded string) and two types for C-style strings: `PChar` and `PWideChar` (there are mere pointers in disguise). The first two types do store the length of the string in them, and since Delphi 2010 (IIRC), `String` even stores the encoding (code page) of the data in it—… – kostix Oct 20 '22 at 13:31
  • @RobertMulyukov, …see [this](https://www.delphipower.xyz/handbook_2009/the_internal_structure_of_strings.html) for a good overview. So it would worth exploring what type of string the DLL actually expects. I hope it `PChar` or `PWideChar`, but who knows. Maybe a bit of exploration (using IDA Pro, for instance) would hint at that. – kostix Oct 20 '22 at 13:33
  • @kostix, thank you very much for the info! Yesterday I've found some other Delphi dll source code and there is used PAnsiChar for the result string. – Robert Mulyukov Oct 21 '22 at 07:10
  • @RobertMulyukov, well, `PAnsiChar` is basically a `*char` in C or C++, and ISTR it's even NUL-terminated, so it can be passed to classic C routines such as `strlen()` right away. So, I'd say it has to work the way you've presented it in your original post. With one caveat. Normal Delphi code does not use `PAnsiChar`; basically this type is there to get a pointer to (the first character of) a "normal" Delphi string and pass it to some C code. That code is expected to _read_ the string.… – kostix Oct 21 '22 at 08:09
  • … If that C code _writes_ via the pointer, things get more complicated because the length of a native string will remain the same. I mean, suppose you have a non-unicode Delphi string "hello". I will be NUL-terminated, but its header will contain the length, and all Delphi code will use it. If you cast that string to `PAnsiChar`, pass that pointer to a C code, and it writes "Hi!" via the pointer, the length in the header won't be magically updated to contain 3. The same applies to writing more data than there's in the string.… – kostix Oct 21 '22 at 08:12
  • …The Delphi code in a DLL might be written cleverly, so after an external code calls that exported function, some other code takes care to use a C-compatible copy routine (I forgot how it's called, but it definitely exists in Delphi) to copy the memory written by whoever made the call into a normal string to get the size right, but it's just guessing and providing you with ideas about what could go wrong. Honestly, by this time I'd be exploring the DLL in IDA Pro or the like—this tool really helps in the situation like this one. – kostix Oct 21 '22 at 08:14
  • BTW, it's `StrCopy` from the `AnsiStrings` unit—[the docs](https://docwiki.embarcadero.com/Libraries/Sydney/en/System.AnsiStrings.StrCopy); well and it seems that plain assignment of a `PAnsiChar` to a variable of a "native" string type [does the copy](https://stackoverflow.com/a/5769316/720999)—even simpler ;-) – kostix Oct 21 '22 at 08:20
  • Oh, sorry, I think I've got the idea backwards: it's Delphi app which is calling the DLL you're about to replace, not the DLL was written in Delphi. – kostix Oct 22 '22 at 09:42
  • @kostix Yeah, there is Delphi caller app and some forgotten dll also written in Delphi that we found recently. The task is to study to write dll in Go as well as in C++. – Robert Mulyukov Oct 25 '22 at 06:46
  • BTW, did you try to log the address of the resulting buffer the DLL code receives? I mean, is the address reported in `…Write of address 0051B3B1` error messages close to that address (equal to or within the range of +100 or so bytes away—depending on the length of the string written to the buffer)? Also: you could try to _read_ the memory in the result buffer as well—to see whether that fails as well. – kostix Oct 25 '22 at 10:44
  • @kostix, yes, we've tried to log the address, it is absolutely the same that appears in the access violation error. By the wat it's quite interesting suggestion to _read_ the buffer. – Robert Mulyukov Oct 25 '22 at 11:25
  • One more question: did you try to not use `strcpy` in the Go code? Basically, replace the statement with `C.strcpy` with `dst := (*[1<<31 - 1]byte)(unsafe.Pointer(resultStr))[:100]; n := copy(dst, str)` and then print `n`. The ugly-looking encantation creates a byte slice of length 100 whose backing array is the caller-allocated buffer. I've verified it works here under Wine by modifying the code in my original gist up this comment thread. The idea to check is to try to rule out some weird possibilty about msvcrt's strcpy misbehaving in this particular setup (security measure maybe?) – kostix Oct 25 '22 at 14:08
  • @kostix, I've tried to use this byte array but I didn't use copy(). Will try that soon, just after completing the tasks with higher priority. – Robert Mulyukov Oct 26 '22 at 07:44
  • @kostix, the idea with byte slice didn't work, nothing happens and it seems like something crushes because log before these code are being printed but after don't and nothing happens. Quite strange behavior but only with dll written in Go. – Robert Mulyukov Oct 26 '22 at 14:04
  • @RobertMulyukov, could you may be drop me a mail (or PM me using some other means listed in my SO profile) with the Go code of your DLL (if it does not pose you at risk of breaching the NDA you've signed, if any, or course)? I think we've pushed the SO's comment thread feature to its limits already ;-) – kostix Oct 26 '22 at 14:26

0 Answers0