Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CGO - doesn't export c entry point - 32bits dll #32851

Closed
code34 opened this issue Jun 29, 2019 · 6 comments
Closed

CGO - doesn't export c entry point - 32bits dll #32851

code34 opened this issue Jun 29, 2019 · 6 comments

Comments

@code34
Copy link

code34 commented Jun 29, 2019

What version of Go are you using (go version)?

go version 1.12.6

Does this issue reproduce with the latest release?

yes

What operating system and processor architecture are you using (go env)?

go env Output
set GOARCH=386
set GOBIN=
set GOCACHE=C:\Users\code34\AppData\Local\go-build
set GOEXE=.exe
set GOFLAGS=
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOOS=windows
set GOPATH=C:\Users\code34\go
set GOPROXY=
set GORACE=
set GOROOT=c:\go
set GOTMPDIR=
set GOTOOLDIR=c:\go\pkg\tool\windows_amd64
set GCCGO=gccgo
set GO386=sse2
set CC=gcc
set CXX=g++
set CGO_ENABLED=1
set GOMOD=
set CGO_CFLAGS=-g -O2
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-g -O2
set CGO_FFLAGS=-g -O2
set CGO_LDFLAGS=-g -O2
set PKG_CONFIG=pkg-config
set GOGCCFLAGS=-m32 -mthreads -fmessage-length=0 -fdebug-prefix-map=C:\Users\code34\AppData\Local\Temp\go-build451563006=/tmp/go-build -gno-record-gcc-switches

What did you do?

Hello,

I tried to create a 32 bits dll wich be called by a C program through an entry point with mangle name : _RVExtension@12

By default //export GO functions are not exported as function with mangle name support for 32 bits dll.

So, i compiled an external c library to build the c entry point attempted by the c program and linked it to the go DLL with cgo

my code is visible at this place:
https://github.com/code34/armago_x64/tree/32bits

i used this for compile it:
$ENV:GOARCH = 386
$ENV:CGO_ENABLED = 1
gcc -c RVExtension.c -m32
ar cru libRVExtension.a RVExtension.o
go build -o armago.dll -buildmode=c-shared armago.go

What did you expect to see?

I expect to see the entry point function visible with dumpbin.

What did you see instead?

The entry point is not exported and not visible in dumpbin

@code34 code34 changed the title CGO - don't export c entry point CGO - doesn't export c entry point - 32bits dll Jun 29, 2019
@lsegal
Copy link

lsegal commented Jun 29, 2019

Odd, I was able to access the exported goRVExtension symbol via the following C program:

#include <stdio.h>
#include <windows.h>

int main(int argc, char **argv) {
        HMODULE mod = LoadLibraryA("armago.dll");
        printf("Module: %x\n", mod);
        FARPROC p = GetProcAddress(mod, "goRVExtension");
        printf("goRVExtension addr: %x\n", p);
        p(); // this crashes on a memmove() call in armago.go
        return 0;
}

It crashes, but on the memmove call written in armago.go, which means it's actually reaching the implementation:

Module: 647c0000
goRVExtension addr: 64840300
Exception 0xc0000005 0x1 0x404049 0x74d1963c
PC=0x74d1963c
signal arrived during external code execution

main._Cfunc_memmove(0x404049, 0x6a1fe8, 0x8, 0x0)
        _cgo_gotypes.go:84 +0x3f
main.goRVExtension.func2(0x404049, 0x6a1fe8, 0x8, 0x1)
        D:/github/lsegal/armago_x64/armago.go:32 +0x91
main.goRVExtension(0x404049, 0x64840300, 0x0)
        D:/github/lsegal/armago_x64/armago.go:32 +0xef
main._cgoexpwrap_6fd27c25576a_goRVExtension(0x404049, 0x64840300, 0x0)
        _cgo_gotypes.go:116 +0x31
eax     0x48
ebx     0x1403ff10
ecx     0x5
edx     0x1
edi     0x404049
esi     0x6a1fe8
ebp     0x60fd98
esp     0x60fd90
eip     0x74d1963c
eflags  0x10202
cs      0x23
fs      0x53
gs      0x2b

It also shows up as an export for me in nm:

λ nm -g armago.dll | grep goRVExtension
6483ff00 T __cgoexp_6fd27c25576a_goRVExtension
64840300 T _goRVExtension
6483ff40 T main._cgoexpwrap_6fd27c25576a_goRVExtension
6483fff0 T main.goRVExtension
64840190 T main.goRVExtension.func1
64840130 T main.goRVExtension.func1.1
64840200 T main.goRVExtension.func2

Can you explain a bit more about how you're reproducing the issue?

@code34
Copy link
Author

code34 commented Jun 30, 2019

Hello Isegal,

Thanks you for your help on this

The entry point is not goRvextension but RVExtension function (which acts as proxy function and called finally goRVExtension)

This entry point should be exposed to an external program (wich is developped by external part) like this (cause it's a 32bits dll with name decoration) : _RVExtension@12

@lsegal
Copy link

lsegal commented Jun 30, 2019

This entry point should be exposed to an external program

But you don't need to do this, as shown above. A 32bit program can call into your 32bit shared library armago.dll by calling goRVExtension directly. See the example I posted, in which main can be the entry to your external program. Or here's a modified version that avoids LoadLibrary:

#include <stdio.h>
#include <windows.h>

extern void goRVExtension(char *output, int outputSize, const char *function);

int main(int argc, char **argv) {
	char *in = "world";
	char out[256] = {0};
	goRVExtension(out, 12, in);
	printf("out: %s\n", out);
	return 0;
}

Compile with:

set GOARCH=386
set CGO_ENABLED=1
go build -o armago.dll -buildmode=c-shared armago.go
gcc -o main main.c -L. -larmago

Run:

$ main.exe
out: Hello world!

You can get rid of all your RVExtension* files and the armago.h, you don't even need em, just armago.go and main.c to reproduce the above working Go library called by an external program (main.exe).

Note that DUMPBIN has no problems with the 32bit armago.dll:

$ dumpbin /exports armago.dll | grep RVExt
         15    E 0007FF00 _cgoexp_7e3fc1cbf906_goRVExtension
         87   56 00080300 goRVExtension
$ dumpbin /headers armago.dll  | grep machine
             14C machine (x86)
                   32 bit word machine

If you're still having issues on your end, it would help a lot if you could show a more built out example of the issue you're running into, perhaps with the error you're seeing.

Note that if you're running into name mangling in your linking step of your external program it might actually be an issue with compilation configuration in your external program, not Go. CGo does not name mangle exported symbols (they are exported exactly as defined in your //export line), so if your linker is expecting to import the symbol with name mangling, you should turn that off, which you can and should totally do.

@code34
Copy link
Author

code34 commented Jun 30, 2019

in fact, i'm not the autor of the external software that the main reason why i should respect the interface contract by exposing the right method with exactly the good mangling name.

https://community.bistudio.com/wiki/Extensions

My idea was to exposed in C the method (because go export command doesn't support decoration name), and call after this the go method, because i believed that could override the limitation(but i m not familiar with cgo)

@lsegal
Copy link

lsegal commented Jun 30, 2019

Ok so you need the mangling. You can do this within cgo (without a separate lib) by simply embedding a C file that exports a mangled symbol.

You were kind of doing this, but it looks like you were missing the __stdcall on RVExtension, plus you need the prefix underscore on the name (gcc won't add that apparently), and finally, you were incorrectly linking the RVExtension files as a separate lib, which may work, but not the way you were doing it-- the good news is you don't need to link it separately at all, which makes compilation much easier.

This is how I got it working, with just the following 2 files:

RVExtension.c

#include <stdlib.h>

extern void goRVExtension(char *output, size_t outputSize, char *input);

__declspec(dllexport) void __stdcall _RVExtension(char *output, size_t outputSize, char *input) {
  goRVExtension(output, outputSize, input);
}

// do this for all the other exported functions

armago.go

package main

/*
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
*/
import "C"

import "unsafe"
import "fmt"

//export goRVExtension
func goRVExtension(output *C.char, outputsize C.size_t, input *C.char) {
	temp := fmt.Sprintf("Hello %s!", C.GoString(input))
	// Return a result to Arma
	result := C.CString(temp)
	defer C.free(unsafe.Pointer(result))
	var size = C.strlen(result) + 1
	if size > outputsize {
		size = outputsize
	}
	C.memmove(unsafe.Pointer(output), unsafe.Pointer(result), size)
}

func main() {}

Compile with:

go build -o armago.dll -buildmode=c-shared .

My main.c test program above can be used to verify this with a small adjustment on the function name to be called (replace goRVExtension with _RVExtension), and dumpbin also shows the exported symbol:

dumpbin /exports armago.dll | grep RVExt
          1    0 00080410 _RVExtension@12

I also figured out how to use callExtension and got it working there:

"armago" callExtension "foo"
"Hello foo!"
Execution time: 0.050400 ms

@code34
Copy link
Author

code34 commented Jun 30, 2019

Thank you so much for your help very detailed and the time you spent, it is very appreciated : )

I will add a link on golang nuts groupe page to documented it.

@code34 code34 closed this as completed Jun 30, 2019
code34 added a commit to code34/armago_x64 that referenced this issue Jun 30, 2019
@golang golang locked and limited conversation to collaborators Jun 29, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants