It would help to be more specific: different -buildmode options are, and always will be, supported on different systems. Which -buildmode options are you specifically interested in?
Probably, most of windows users are looking for a way to generate dlls.:
--buildmode=c-shared
or
--buildmode=dll
I only need -buildmode=c-shared
, to generate DLL for other C/C++ user.
If Go can generate DLL, i can say byebye to C++.
PS: Also hope fix https://github.com/golang/go/issues/9510 in Go1.5!
I would also like to see this happen. Is anyone currently working on this issue? Any CL's which may be reviewed?
Unfortunately, I'm not aware of anyone working
on this issue right now.
I also need this. If I had some idea how much work was involved I might be willing to contribute an implementation. I know that Windows DLLs are fairly different from ELF style shared libraries, so if this is an enormous task it won't be something I can commit to.
Is there any way to find out how much/what kind of work would be involved?
@nadiasvertex The main thing is to make the function hostlink in cmd/link/internal/ld/lb.go do the right thing on Windows. On Darwin it invokes theC linker with -dynamiclib. On GNU/Linux it invokes the C linker with -Wl,-Bsymbolic -Wl,-z,relro -shared -Wl,-z,nodelete.
Basically, if you produce a list of commands that will turn a Windows object file into a Windows DLL, change hostlink to invokes those commands.
That said, it's possible that Windows needs to know the list of symbols that are callable from outside the DLL. It used to need to know that, but it's been many years since I looked at it. If you need that list of symbols, it's available by looping over the symbol table and looking for the Cgoexport field having a CgoExportStatic flag.
Great! I'll look at it. I suspect that the challenge is mostly in knowing
where the linker is on Windows.
On Mon, Dec 7, 2015 at 10:58 AM Ian Lance Taylor [email protected]
wrote:
@nadiasvertex https://github.com/nadiasvertex The main thing is to make
the function hostlink in cmd/link/internal/ld/lb.go do the right thing on
Windows. On Darwin it invokes theC linker with -dynamiclib. On GNU/Linux it
invokes the C linker with -Wl,-Bsymbolic -Wl,-z,relro -shared
-Wl,-z,nodelete.Basically, if you produce a list of commands that will turn a Windows
object file into a Windows DLL, change hostlink to invokes those commands.That said, it's possible that Windows needs to know the list of symbols
that are callable from outside the DLL. It used to need to know that, but
it's been many years since I looked at it. If you need that list of
symbols, it's available by looping over the symbol table and looking for
the Cgoexport field having a CgoExportStatic flag.—
Reply to this email directly or view it on GitHub
https://github.com/golang/go/issues/11058#issuecomment-162566183.
@ianlancetaylor I started looking into this a little more, and it turns out that I have some questions about some fundamental parts of the architecture. For example:
In case it is helpful, the current trace output looks like:
WORK=C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698
mkdir -p $WORK\command-line-arguments_obj\
mkdir -p $WORK\command-line-arguments_obj\exe\
cd Z:\projects\test\src\calc
CGO_LDFLAGS="-g" "-O2" "z:\projects\go\pkg\tool\windows_amd64\cgo.exe" -objdir "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj\" -importpath command-line-arguments "-exportheader=C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj_cgo_install.h" -- -I "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj\" calc.go
gcc -I "Z:\projects\test\src\calc" -m64 -mthreads -fmessage-length=0 -print-libgcc-file-name
gcc -I "Z:\projects\test\src\calc" -m64 -mthreads -fmessage-length=0 -I "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj\" -g -O2 -o "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj_cgo_main.o" -c "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj_cgo_main.c"
gcc -I "Z:\projects\test\src\calc" -m64 -mthreads -fmessage-length=0 -I "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj\" -g -O2 -o "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj_cgo_export.o" -c "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj_cgo_export.c"
gcc -I "Z:\projects\test\src\calc" -m64 -mthreads -fmessage-length=0 -I "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj\" -g -O2 -o "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj\calc.cgo2.o" -c "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj\calc.cgo2.c"
gcc -I "Z:\projects\test\src\calc" -m64 -mthreads -fmessage-length=0 -o "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj_cgo_.o" "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj_cgo_main.o" "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj_cgo_export.o" "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj\calc.cgo2.o" -g -O2
"z:\projects\go\pkg\tool\windows_amd64\cgo.exe" -objdir "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj\" -dynpackage main -dynimport "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj_cgo_.o" -dynout "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj_cgo_import.go"
gcc -I "Z:\projects\test\src\calc" -m64 -mthreads -fmessage-length=0 -o "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj_all.o" "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj_cgo_export.o" "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj\calc.cgo2.o" -g -O2 -Wl,-r -nostdlib -Wl,--start-group -lmingwex -lmingw32 -Wl,--end-group C:/TDM-GCC-64/bin/../lib/gcc/x86_64-w64-mingw32/5.1.0/libgcc.a
"z:\projects\go\pkg\tool\windows_amd64\compile.exe" -o "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments.a" -trimpath "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698" -p main -buildid af4f2da53bc903b4d17a55032a8fca5f579d7452 -D _/Z_/projects/test/src/calc -I "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698" -pack "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj_cgo_gotypes.go" "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj\calc.cgo1.go" "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj_cgo_import.go"
pack r "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments.a" "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj_all.o" # internal
cd .
"z:\projects\go\pkg\tool\windows_amd64\link.exe" -o "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments_obj\exe\a.out.exe" -L "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698" -extld=gcc -buildmode=c-shared -buildid=af4f2da53bc903b4d17a55032a8fca5f579d7452 -v "C:\Users\CHRIST~1\AppData\Local\Temp\go-build642482698\command-line-arguments.a"
# command-line-arguments
HEADER = -H11 -T0x401000 -D0x0 -R0x1000
searching for runtime.a in $WORK/runtime.a
searching for runtime.a in z:\projects\go/pkg/windows_amd64/runtime.a
0.00 deadcode
0.01 pclntab=154410 bytes, funcdata total 33488 bytes
0.01 dodata
0.01 reloc
0.02 reloc
_rt0_amd64_windows_lib.ptr: _rt0_amd64_windows_lib: not defined
0.02 asmb
0.02 codeblk
0.03 datblk
0.03 sym
0.03 dwarf
0.03 headr
0.03 symsize = 0
0.03 symsize = 0
_rt0_amd64_windows_lib.ptr: undefined: _rt0_amd64_windows_lib
I don't think anybody we must require GCC, but clearly we do need some other toolchain. If somebody wants to add support for MSVC, I think that would be great. In any case, I think we do want to make it possible to suppose -buildmode=c-shared using GCC.
_rt0_amd64_windows_lib
should be defined in runtime/rt0_windows_amd64.s, and should probably look something like _rt0_amd64_linux_lib
in runtime/rt0_linux_amd64.s. I admit that I completely forgot about that part of this work. You'll have to tweak the argc/argv handling to be appropriate for Windows, and you'll have to implemented a Windows version of newosproc0.
You might also have to worry about how thread-local storage works in a DLL (I have no idea how this sort of thing works on windows)
@nadiasvertex The main thing is to make the function hostlink in cmd/link/internal/ld/lb.go do the right thing on Windows. On Darwin it invokes theC linker with -dynamiclib. On GNU/Linux it invokes the C linker with -Wl,-Bsymbolic -Wl,-z,relro -shared -Wl,-z,nodelete.
If you just want to build a DLL that does not contain any C code, you don't have to use external linker. I am sure you can modify Go linker to produce what you want. Go linker does just that when it creates windows executables. I don't see how creating of DLL would be different.
You will also have to deal with issues every windows DLL deals with. You must have set of mimimum functions required in DLL. You have to deal with your DLL exported functions called on different threads. You have to deal with exceptions.
You might also have to worry about how thread-local storage works in a DLL (I have no idea how this sort of thing works on windows)
What is wrong with the way thread-local storage works in Go windows executables now?
Alex
Let me be clear, I assume that go has already dealt with most of these
problems. I can certainly provide some glue for existing services. However,
if there is a need to write totally new runtime support I will need a lot
more direction.
On windows there were certain conventions for dll entry points. However,
those are generally C library artifacts. If go has particular needs I might
be able to satisfy those, given some pointers.
My personal need is to take go code, expose some of it via a C ABI, and
link an executable written in another language with it. The code needs to
work on Windows, Linux, and Mac. Android, iOS, and Windows Mobile are a
plus, but not urgently pending. Go satisfies most of these wonderfully. I
am interested in any solution that helps me accomplish this goal. I would
prefer a robust, integrated solution. However, if it is an unreasonably
large task for a new contributor then I would be happy to know about
workarounds.
On Mon, Dec 7, 2015, 7:27 PM Alex Brainman [email protected] wrote:
@nadiasvertex https://github.com/nadiasvertex The main thing is to make
the function hostlink in cmd/link/internal/ld/lb.go do the right thing on
Windows. On Darwin it invokes theC linker with -dynamiclib. On GNU/Linux it
invokes the C linker with -Wl,-Bsymbolic -Wl,-z,relro -shared
-Wl,-z,nodelete.If you just want to build a DLL that does not contain any C code, you
don't have to use external linker. I am sure you can modify Go linker to
produce what you want. Go linker does just that when it creates windows
executables. I don't see how creating of DLL would be different.You will also have to deal with issues every windows DLL deals with. You
must have set of mimimum functions required in DLL. You have to deal with
your DLL exported functions called on different threads. You have to deal
with exceptions.You might also have to worry about how thread-local storage works in a DLL
(I have no idea how this sort of thing works on windows)What is wrong with the way thread-local storage works in Go windows
executables now?Alex
—
Reply to this email directly or view it on GitHub
https://github.com/golang/go/issues/11058#issuecomment-162714478.
If you just want to build a DLL that does not contain any C code, you don't have to use external linker. I am sure you can modify Go linker to produce what you want. Go linker does just that when it creates windows executables. I don't see how creating of DLL would be different.
It's unusual to want to build a shared library/DLL if you don't have a C toolchain available. On Unix we decided to simply rely on that, rather than spend the time to teach the Go linker how to generate a shared library. On Windows, in which DLLs are more different from executables than they are on ELF, I would suggest following the same strategy. Since in the general case we must use external linking when generating a shared library, I don't think it's so bad to always require it.
On windows there were certain conventions for dll entry points. However,
those are generally C library artifacts. ...
It is not true. There are variety of non C compilers on Windows that will allow you to build a DLL.
My personal need is to take go code, expose some of it via a C ABI, and
link an executable written in another language with it.
You don't have to use DLLs for that. You can also include your Go code as part of your final executable. But then you need to be specific about the tools you use to build that executable. Is that going to be gcc? If yes, then what Ian suggests is your path. I do not know much about gcc, so I am not familiar with what is required.
You can also try and create DLL using gcc. Again, that is path that Ian suggested.
If you don't want to rely on gcc to build your programs, then you would have to build windows DLL as part of Go linkers. The code lives in $GOROOT/src/cmd/link/internal/ld/pe.go. Current code produces Windows PE executable (among other things). You can modify it to output Windows DLL (with whatever DLL requires). I am familiar with pe.go, but I have never built a DLL from scratch. But happy to help.
You should, probebly, try gcc approach first, because it has been implemented on some non-Windows OSes already. So, perhaps, it will be easy enough.
It's unusual to want to build a shared library/DLL if you don't have a C toolchain available. ...
Perhaps I misunderstand you, but I disagree. I don't see how building Windows DLL is different from building Windows executable. Surely we require gcc for cgo, but other than that.
On Unix we decided to simply rely on that, rather than spend the time to teach the Go linker how to generate a shared library.
Fair enough.
On Windows, in which DLLs are more different from executables than they are on ELF, I would suggest following the same strategy. Since in the general case we must use external linking when generating a shared library, I don't think it's so bad to always require it.
There are advantages of not requiring gcc on Windows. Go just works out of the box. When things break, you have all source code with you; and source code is Go. You can build Go Windows executable on any other OS - you can use Plan9 computer to build Go Windows executable.
Alex
@alexbrainman This issue is specifically about -buildmode=c-shared. The only reason to use -buildmode=c-shared is to build a DLL that can be linked into a program written in C. My thinking is that people doing that are probably also writing a program in C, and therefore have a C compiler. But I'm obviously not a Windows developer, so I may be wrong.
This issue is specifically about -buildmode=c-shared. The only reason to use -buildmode=c-shared is to build a DLL that can be linked into a program written in C. My thinking is that people doing that are probably also writing a program in C, and therefore have a C compiler. But I'm obviously not a Windows developer, so I may be wrong.
Fair enough. Similar I am not familiar with what -buildmode provides. What -buildmode flag should I use to build Windows DLL? DLL that can be called from any Windows executable or another DLL. These others (executables and DLLs) can be written in C, but don't have to be - they can be written in Go. We (Go executables we build) use system DLLs (produced by Microsoft) all the time. Do you see Go provide way to build DLLs just like these?
Alex
The intent is to write a plugin package that can be used to open Go shared libraries built with -buildmode=plugin. But this has not been implemented (see https://golang.org/s/execmodes for this and more about -buildmode).
On Windows, it may already work to use -buildmode=c-shared and open the DLL from a Go program. The disadvantage would be that you can only use functions with a C style interface, and you would have two different Go heaps and garbage collectors--one in the main program and one in the DLL. That is, -buildmode=c-shared is intended to give you a complete DLL that can be opened by a program written in any language, so it includes a complete copy of the Go runtime.
Thank you for explaining, Ian.
Alex
You might also have to worry about how thread-local storage works in a DLL (I have no idea how this sort of thing works on windows)
What is wrong with the way thread-local storage works in Go windows executables now?
Nothing, but it works by assuming g lives at a fixed offset from $fs, and when you are in a dynamic library that's going to be loaded into another process, you can't assume a fixed offset because something in that process or another dynamic library might already be using that offset. Or at least, that's the sort of problem you get on an ELF system -- like I said, I don't know how windows works here. But I suspect you'll need to change something in the area.
FTR, on windows, mingw doesn't have support for native
thread local storage (__thread keyword is emulated.)
That said, Go uses the ArbitraryUserPointer field in TIB
as TLS slot (https://golang.org/cl/5519054), which means
if you load a Go DLL into another process which also ues
ArbitraryUserPointer, then there could problems.
Yes. We would have to find different way to find TLS slot.
Alex
To provide some clarity on my motivation and goals:
I want to build a .dll because I don't know what code will eventually consume the pieces I am responsible for. In some cases a static library is enough, but in other cases it is not. For example, JNI and C#'s PInvoke require a .dll file. If it turns out that making a go .dll via -buildmode=c-shared is a larger project than I can take on, I hope to fall back to creating a static library via -buildmode=c-archive and then write some C functions that call the Go code, and are themselves DLL exports.
I understand that cgo already lets me register callbacks, and it certainly allows me to call into C code which may do just about anything. Consequently, I would imagine that the TLS problem has already been solved. I understand that a .dll adds some additional wrinkles to this situation, but I have to imagine that that is limited to the setup code.
With respect to @mwhudson, I don't understand what problem you are describing. If you are saying that the $fs segment register is used as a base, I'm not sure why you think what some other process is doing matters. The entire register set contents are isolated per process. The code itself lives where it lives, and is updated through the GOT and PLT tables at dll initialization. I confess that it has been a long time since I dealt with any of this in detail so I certainly may be missing something important here.
However, considering that this all works for non-Windows platforms, and considering that the Windows support for generating native go code is very good, I have to imagine that the vast majority of these issues are known and have been solved. I am very interested in specific guidance, and in known limitations of the go toolchain. I'm not terribly interested in generic 'thar be dragons" commentary because if this was really trivial it would be done already. :-)
I have gotten to the point where I am trying to implement newosproc0. Unfortunately, it seems like implementing this function requires deep understanding of Go's thread creation mechanics on Windows. Any pointers here? I looked at the Linux version but it calls functions which don't even exist for Windows.
I'm no Windows expert, but looking at newosproc in os1_windows.go I suspect that newosproc0 can simply be a copy of that. On Unix the difference between newosproc and newosproc0 is that newosproc0 is responsible for allocating the stack safely, but on Windows newosproc ignores the stk parameter anyhow.
I noticed that the c-shared and c-archive are pretty similar. I switched to c-archive mode for the initial implementation to reduce the complexity. When this works I'll move back to c-shared mode.
I created a small project:
//calc.go
package main
import "C"
//export Sum
func Sum(x, y int) int {
return x+y
}
func main() {
}
//test_driver.c
int main(int argc, char **argv) {
return Sum(5, 10);
}
On Linux this appears to work perfectly:
// commands
csnelson@nix-us2ua34705wc:~/meps/projects/test/src/calc$ go build -buildmode=c-archive -o test.a calc.go
csnelson@nix-us2ua34705wc:~/meps/projects/test/src/calc$ gcc -c test_driver.c -o test_driver.o
csnelson@nix-us2ua34705wc:~/meps/projects/test/src/calc$ gcc -o test test_driver.o test.a -lpthread
csnelson@nix-us2ua34705wc:~/meps/projects/test/src/calc$ ./test
csnelson@nix-us2ua34705wc:~/meps/projects/test/src/calc$ echo $?
15
On Windows I get through the Go part of the build without error, but I get undefined references from the C linker:
csnelson@nix-us2ua34705wc:~/meps/projects/test/src/calc$ gcc -o test test_driver.o test.a
test.a(000000.o): In function Sum':
C:/Users/CHRIST~1/AppData/Local/Temp/go-build344693770/command-line-arguments/_obj/_cgo_export.c:19: undefined reference to
_cgoexp_f070ceaf1261_Sum'
C:/Users/CHRIST~1/AppData/Local/Temp/go-build344693770/command-line-arguments/_obj/_cgo_export.c:19: undefined reference to crosscall2'
test.a(000000.o):calc.cgo2.c:(.rdata$.refptr._cgoexp_f070ceaf1261_Sum[.refptr._cgoexp_f070ceaf1261_Sum]+0x0): undefined reference to
_cgoexp_f070ceaf1261_Sum'
collect2.exe: error: ld returned 1 exit status
The contents of the test.a archive:
$ ar -t test.a
go.o
000000.o
000001.o
$ nm test.a
000000.o:
0000000000000000 b .bss
0000000000000000 b .bss
0000000000000000 d .data
0000000000000000 d .data
00000000000000f2 N .debug_abbrev
0000000000000000 N .debug_abbrev
0000000000000000 N .debug_aranges
0000000000000030 N .debug_aranges
0000000000000000 N .debug_frame
00000000000002ba N .debug_info
0000000000000000 N .debug_info
0000000000000000 N .debug_line
00000000000000ad N .debug_line
0000000000000000 N .debug_loc
0000000000000000 p .pdata
0000000000000000 r .rdata$.refptr._cgoexp_f070ceaf1261_Sum
0000000000000000 r .rdata$zzz
0000000000000020 r .rdata$zzz
0000000000000000 R .refptr._cgoexp_f070ceaf1261_Sum
0000000000000000 t .text
0000000000000040 t .text
0000000000000000 r .xdata
U _cgo_wait_runtime_init_done
U _cgoexp_f070ceaf1261_Sum
U crosscall2
0000000000000000 T Sum
000001.o:
0000000000000000 b .bss
0000000000000000 b .bss
0000000000000000 b .bss
0000000000000000 b .bss
0000000000000000 b .bss
0000000000000000 b .bss
0000000000000000 d .data
0000000000000000 d .data
0000000000000000 d .data
0000000000000000 d .data
0000000000000000 d .data
0000000000000000 d .data
0000000000000130 N .debug_abbrev
0000000000000032 N .debug_abbrev
00000000000002ac N .debug_abbrev
0000000000000019 N .debug_abbrev
0000000000000000 N .debug_abbrev
0000000000000447 N .debug_abbrev
00000000000000d0 N .debug_aranges
00000000000000a0 N .debug_aranges
0000000000000070 N .debug_aranges
0000000000000020 N .debug_aranges
0000000000000000 N .debug_aranges
0000000000000040 N .debug_aranges
0000000000000000 N .debug_frame
0000000000000068 N .debug_frame
00000000000000f8 N .debug_frame
0000000000000a19 N .debug_info
000000000000018b N .debug_info
00000000000005ae N .debug_info
00000000000002e2 N .debug_info
0000000000000000 N .debug_info
0000000000000ea2 N .debug_info
0000000000000298 N .debug_line
00000000000000e9 N .debug_line
000000000000003a N .debug_line
000000000000001d N .debug_line
00000000000001bd N .debug_line
0000000000000000 N .debug_line
0000000000000000 N .debug_loc
0000000000000072 N .debug_loc
0000000000000211 N .debug_loc
000000000000000c N .debug_str
0000000000000000 N .debug_str
0000000000000048 p .pdata
0000000000000024 p .pdata
0000000000000000 p .pdata
0000000000000000 r .rdata
0000000000000060 r .rdata
0000000000000030 r .rdata
0000000000000080 r .rdata$zzz
0000000000000020 r .rdata$zzz
0000000000000040 r .rdata$zzz
0000000000000000 r .rdata$zzz
0000000000000060 r .rdata$zzz
0000000000000110 t .text
0000000000000050 t .text
0000000000000000 t .text
00000000000001f0 t .text
0000000000000000 t .text
0000000000000000 t .text
0000000000000000 r .xdata
0000000000000010 r .xdata
0000000000000028 r .xdata
U __imp___iob_func
U __imp__beginthread
U __imp__errno
00000000000001a0 T _cgo_sys_thread_start
0000000000000030 T _cgo_wait_runtime_init_done
U abort
00000000000001f0 T crosscall_amd64
U fprintf
U free
U fwrite
U malloc
0000000000000110 t threadentry
0000000000000090 T x_cgo_free
0000000000000180 T x_cgo_init
0000000000000050 T x_cgo_malloc
0000000000000040 T x_cgo_notify_runtime_init_done
0000000000000000 T x_cgo_sys_thread_create
00000000000000a0 T x_cgo_thread_start
nm: go.o: File format not recognized
@ianlancetaylor
The only reason to use -buildmode=c-shared is to build a DLL that can be linked into a program written in C. My thinking is that people doing that are probably also writing a program in C, and therefore have a C compiler. But I'm obviously not a Windows developer, so I may be wrong.
Here I dare disagree: on Windows, DLLs are very often used as a mechanism for providing plugins. In such a case, you start with some third-party app written by somebody else (quite often under proprietary license, so no source-code; but it's the same for open-source too, see e.g. Notepad++), which you want to extend with a plugin. If the app supports plugins, it would usually describe an interface that a plugin DLL must conform to, and how to register the DLL in the app (e.g. specific directory where you must put the DLL). So, no C toolchain in sight really; it's quite normal to write the DLL e.g. in Delphi or C#.
Even you're building a DLL for plugin, the plugin interface is usually
specified
as a C compatible interface (COM is even harder).
How could you provide a C compatible interface from a Go DLL without cgo?
If you cgo to create the interface, then gcc is inevitable, and I don't see
any
problem requiring gcc to build the DLL.
Note that Ian's point is not that you can ONLY create DLL with a C compiler,
it is that to create a Go DLL (-buildmode=c-shared), GCC is required.
@nadiasvertex Something is wrong with the test.a file. The output you are showing looks as though -buildmode=c-archive
was not specified at all. Use go build -x
to see the commands that the Go command is executing. Make sure that -buildmode=c-archive
is being passed to the linker. Use go build -ldflags=-v
to pass -v to the linker; make sure it is doing the right thing to generate the archive, which I assume means invoking the ar
command.
To provide some clarity on my motivation and goals:
What about building all your functionality as part of single Go executable and exporting it as RPC (via TCP or similar)?
why you think what some other process is doing matters
@mwhudson is talking about "therad local storage" here, not about processes view. Go runtime needs some block of memory that is "thread specific" - you can read and write that memory from any thread, and memory contents looks different if you look at it from different threads, but the same if you look from the same thread. As @minux mentioned, Windows provide some magic memory block (TIB), you can store a pointer at particular offset in this block, and that pointer will be different on different threads. Go runtime uses that slot for this particular purpose, but we also call some external code (system DLLs and cgo). Luckily for Go, no other code uses that slot. But if, for example, we create a Go DLL with its own runtime that uses the same slot, then calling this DLL from Go executable will be fatal.
How could you provide a C compatible interface from a Go DLL without cgo?
It realy depends on the tool you will use to _consume_ your DLL. For gcc you will provide set of .obj, .lib and .h files. For Delphi you provide small Pascal source file. For C# you provide small C# file (I don't know much about C#, so I could be wrong here). For Go you can provide small Go file (similar to $GOPATH/src/syscall/zsyscall_windows.go).
You can document your interface just like Microsoft do. For example, https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx
I don't see any problem requiring gcc to build the DLL.
You loose all nice things I've mentioned above. IMHO it is too hard for average windows user. It means you exclude them from building DLLs.
Alex
To provide some clarity on my motivation and goals:
What about building all your functionality as part of single Go executable
and exporting it as RPC (via TCP or similar)?
That would be a hard option to sell. It's probably triple the work, and has
performance challenges. Especially when dealing with large string
transfers, which is the majority of the work this library would be doing.
why you think what some other process is doing
matters
But if, for example, we create a Go DLL with its own runtime that uses the
same slot, then calling this DLL from Go executable will be fatal.
I don't see how that can be true. The current go execution modes spec
requires one and only one copy of the go runtime per process. Unless I
misread it, all copies of the runtime must be merged when linked
(statically or dynamically.)
Unless I misread it, all copies of the runtime must be merged when linked (statically or dynamically.)
If there is only one copy of runtime, then surely it will coordinate itself properly. But I wouldn't worry about that yet. You need to build your DLL first.
Alex
@ianlancetaylor : here is the output from the build with verbose flags:
WORK=C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846
mkdir -p $WORK\command-line-arguments_obj\
mkdir -p $WORK\command-line-arguments_obj\exe\
cd Z:\projects\test\src\calc
CGO_LDFLAGS="-g" "-O2" "z:\projects\go\pkg\tool\windows_amd64\cgo.exe" -objdir "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj\" -importpath command-line-arguments "-exportheader=C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj_cgo_install.h" -- -I "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj\" calc.go
gcc -I "Z:\projects\test\src\calc" -m64 -mthreads -fmessage-length=0 -I "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj\" -g -O2 -o "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj_cgo_main.o" -c "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj_cgo_main.c"
gcc -I "Z:\projects\test\src\calc" -m64 -mthreads -fmessage-length=0 -I "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj\" -g -O2 -o "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj_cgo_export.o" -c "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj_cgo_export.c"
gcc -I "Z:\projects\test\src\calc" -m64 -mthreads -fmessage-length=0 -I "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj\" -g -O2 -o "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj\calc.cgo2.o" -c "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj\calc.cgo2.c"
gcc -I "Z:\projects\test\src\calc" -m64 -mthreads -fmessage-length=0 -o "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj_cgo_.o" "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj_cgo_main.o" "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj_cgo_export.o" "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj\calc.cgo2.o" -g -O2
"z:\projects\go\pkg\tool\windows_amd64\cgo.exe" -objdir "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj\" -dynpackage main -dynimport "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj_cgo_.o" -dynout "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj_cgo_import.go"
gcc -I "Z:\projects\test\src\calc" -m64 -mthreads -fmessage-length=0 -o "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj_all.o" "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj_cgo_export.o" "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj\calc.cgo2.o" -g -O2 -Wl,-r -nostdlib -Wl,--start-group -lmingwex -lmingw32 -Wl,--end-group
"z:\projects\go\pkg\tool\windows_amd64\compile.exe" -o "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments.a" -trimpath "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846" -p main -buildid 5b68efefee46667e4a728bc7de39d8436fd9e03f -D _/Z_/projects/test/src/calc -I "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846" -pack "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj_cgo_gotypes.go" "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj\calc.cgo1.go" "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj_cgo_import.go"
pack r "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments.a" "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj_all.o" # internal
cd .
"z:\projects\go\pkg\tool\windows_amd64\link.exe" -o "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments_obj\exe\a.out.a" -L "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846" -extld=gcc -buildmode=c-archive -buildid=5b68efefee46667e4a728bc7de39d8436fd9e03f -v "C:\Users\CHRIST~1\AppData\Local\Temp\go-build079329846\command-line-arguments.a"
# command-line-arguments
HEADER = -H11 -T0x401000 -D0x0 -R0x1000
searching for runtime.a in $WORK/runtime.a
searching for runtime.a in z:\projects\go/pkg/windows_amd64/runtime.a
0.00 deadcode
0.03 pclntab=171685 bytes, funcdata total 25240 bytes
0.03 dodata
0.03 reloc
0.03 reloc
0.03 asmb
0.03 codeblk
0.05 datblk
0.05 sym
0.05 dwarf
0.05 headr
0.05 symsize = 0
0.05 symsize = 0
archive: ar -q -c -s $WORK\command-line-arguments_obj\exe\a.out.a C:\Users\CHRIST~1\AppData\Local\Temp\go-link-523143211/go.o C:\Users\CHRIST~1\AppData\Local\Temp\go-link-523143211/000000.o C:\Users\CHRIST~1\AppData\Local\Temp\go-link-523143211/000001.o
0.11 cpu time
25353 symbols
7280 liveness data
cp $WORK\command-line-arguments_obj_cgo_install.h test.h
cp $WORK\command-line-arguments_obj\exe\a.out.a test.a
That all looks correct. But I don't understand why your nm program can't recognize the format of the go.o file. You, or somebody, is going to have to dig into the linker to find out how it is creating the go.o file and figure out why nm doesn't understand it.
It turns out that the go.o file is getting created perfectly fine. If I pass -tmpdir to link.exe with a folder of my choice, the resulting archive is well-formed. In other words:
go build -x -work -buildmode=c-archive -ldflags="-v -tmpdir ./tmpo" -o test.a calc.go
Results in a perfectly valid test.a, which I can link against with:
gcc -o test test_driver.o test.a -lws2_32 -lntdll
I'm guessing that there is a race condition where the external linker is not finished with its job, but the temporary folder gets pulled out from under it.
This executable segfaults after running runtime.sdtcall1.
(gdb) run
Starting program: Z:\projects\test\src\calc\test.exe
[New Thread 3224.0x14c]
[New Thread 3224.0xb20]
[New Thread 3224.0x38c]
Breakpoint 3, 0x0000000000422760 in runtime.stdcall1 ()
2: x/3i $pc
=> 0x422760
0x422764
0x42276d
(gdb) info registers
rax 0x2a 42
rbx 0x4c41d8 4997592
rcx 0x2 2
rdx 0x24fdf0 2424304
rsi 0x5 5
rdi 0xdb1540 14357824
rbp 0x4018c0 0x4018c0 main._cgoexpwrap_f070ceaf1261_Sum
rsp 0x24fc78 0x24fc78
r8 0x18 24
r9 0xdb15b0 14357936
r10 0x0 0
r11 0x286 646
r12 0x1 1
r13 0x8 8
r14 0x0 0
r15 0x0 0
rip 0x422760 0x422760 runtime.stdcall1
eflags 0x246 [ PF ZF IF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
(gdb) c
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0x000000000042276d in runtime.stdcall1 ()
2: x/3i $pc
=> 0x42276d
0x422774
0x422778
(gdb) info registers
rax 0x2a 42
rbx 0x0 0
rcx 0x2 2
rdx 0x24fdf0 2424304
rsi 0x5 5
rdi 0xdb1540 14357824
rbp 0x4018c0 0x4018c0 main._cgoexpwrap_f070ceaf1261_Sum
rsp 0x24fc68 0x24fc68
r8 0x18 24
r9 0xdb15b0 14357936
r10 0x0 0
r11 0x286 646
r12 0x1 1
r13 0x8 8
r14 0x0 0
r15 0x0 0
rip 0x42276d 0x42276d runtime.stdcall1+13
eflags 0x10202 [ IF RF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
After tracing this a little more, it turns out that I found this:
//gcc_libinit_windows.c
void
_cgo_wait_runtime_init_done() {
// TODO(spetrovic): implement this method.
}
I don't know if this is significant or not because it hasn't been implemented for openbsd or linux_ppc either. I can see the "default" implementation for Linux uses a pthread mutex. I could probably implement something like this for Windows/gcc. In fact, it probably wouldn't have to change much, if at all.
I copied the code verbatim and the program no longer segfaults. Instead it hangs forever inside _cgo_wait_runtime_init_done(), which probably indicates that x_cgo_notify_runtime_init_done() is never getting called.
Any pointers where to go from here?
x_cgo_notify_runtime_init_done is called under the name _cgo_notify_runtime_init_done by the function main in runtime/proc.go.
It's possible that the function main is not being called. That normally happens because the linker puts INITENTRY (the rt0 symbol) in the INITARRAY section (see addinitarrdata in cmd/link/internal/ld/data.go). However, although that works for ELF, it likely does nothing for PE. You need to figure out how to arrange for that symbol to be called at program startup time.
I think you will to synthesize a DllMain function that calls the rt0
function.
@ianlancetaylor I built a C++ file with a static constructor and it looks like the address of that function was put into a section called ".ctors" on Windows. Also https://gcc.gnu.org/onlinedocs/gccint/Initialization.html seems to indicate this is the correct place for this information.
The C++ .o file looks like this:
objdump -h
const.o: file format pe-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000080 0000000000000000 0000000000000000 0000021c 2**4
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 00000000 0000000000000000 0000000000000000 00000000 2**4
ALLOC, LOAD, DATA
2 .bss 00000010 0000000000000000 0000000000000000 00000000 2**4
ALLOC
3 .text$_ZN4testC1Ev 00000010 0000000000000000 0000000000000000 0000029c 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE, LINK_ONCE_DISCARD (COMDAT _ZN4testC1Ev 4)
4 .xdata$_ZN4testC1Ev 00000008 0000000000000000 0000000000000000 000002ac 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA, LINK_ONCE_DISCARD
5 .pdata$_ZN4testC1Ev 0000000c 0000000000000000 0000000000000000 000002b4 2**2
CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA, LINK_ONCE_DISCARD
6 .text$_ZN4testD1Ev 00000010 0000000000000000 0000000000000000 000002c0 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE, LINK_ONCE_DISCARD (COMDAT _ZN4testD1Ev 8)
7 .xdata$_ZN4testD1Ev 00000008 0000000000000000 0000000000000000 000002d0 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA, LINK_ONCE_DISCARD
8 .pdata$_ZN4testD1Ev 0000000c 0000000000000000 0000000000000000 000002d8 2**2
CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA, LINK_ONCE_DISCARD
9 .xdata 00000024 0000000000000000 0000000000000000 000002e4 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
10 .pdata 00000024 0000000000000000 0000000000000000 00000308 2**2
CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
11 .ctors 00000008 0000000000000000 0000000000000000 0000032c 2**3
CONTENTS, ALLOC, LOAD, RELOC, DATA
12 .rdata$zzz 00000020 0000000000000000 0000000000000000 00000334 2**4
CONTENTS, ALLOC, LOAD, READONLY, DATA
But the go.o file looks like this:
tmpo/go.o: file format pe-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 000c1200 0000000000001000 0000000000001000 00000600 2**5
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE, DATA
1 .data 00001200 00000000000c3000 00000000000c3000 000c1800 2**5
CONTENTS, ALLOC, LOAD, RELOC, DATA
2 .bss 0001f4c0 00000000000c5000 00000000000c5000 00000000 2**5
ALLOC
On Linux it looks like this:
tmpo/go.o: file format elf64-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00052650 0000000000000000 0000000000000000 00001000 2**4
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .rodata 00040dd3 0000000000000000 0000000000000000 00053660 2**5
CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
2 .typelink 000009f0 0000000000000000 0000000000000000 00094438 2**3
CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
3 .gosymtab 00000000 0000000000000000 0000000000000000 00094e28 2**0
CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
4 .gopclntab 00028a6b 0000000000000000 0000000000000000 00094e40 2**5
CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
5 .note.go.buildid 00000038 0000000000000000 0000000000000000 000bd8c0 2**5
CONTENTS, ALLOC, LOAD, READONLY, DATA
6 .noptrdata 000003b0 0000000000000000 0000000000000000 000be000 2**5
CONTENTS, ALLOC, LOAD, RELOC, DATA
7 .init_array 00000008 0000000000000000 0000000000000000 000be3b0 2**3
CONTENTS, ALLOC, LOAD, RELOC, DATA
8 .data 000013b0 0000000000000000 0000000000000000 000be3c0 2**5
CONTENTS, ALLOC, LOAD, RELOC, DATA
9 .bss 00023878 0000000000000000 0000000000000000 000bf780 2**5
ALLOC
10 .noptrbss 00004b40 0000000000000000 0000000000000000 000e3000 2**5
ALLOC
11 .tbss 00000008 0000000000000000 0000000000000000 000be000 2**3
ALLOC, THREAD_LOCAL
12 .note.GNU-stack 00000000 0000000000000000 0000000000000000 00000000 2**0
CONTENTS, READONLY
13 .debug_abbrev 000000ff 0000000000000000 0000000000000000 000d0bff 2**0
CONTENTS, READONLY, DEBUGGING
14 .debug_line 0000a793 0000000000000000 0000000000000000 000d0cfe 2**0
CONTENTS, RELOC, READONLY, DEBUGGING
15 .debug_frame 0000a56c 0000000000000000 0000000000000000 000db491 2**0
CONTENTS, RELOC, READONLY, DEBUGGING
16 .debug_info 00027273 0000000000000000 0000000000000000 000e59fd 2**0
CONTENTS, RELOC, READONLY, DEBUGGING
17 .debug_pubnames 00009a03 0000000000000000 0000000000000000 0010cc70 2**0
CONTENTS, READONLY, DEBUGGING
18 .debug_pubtypes 00004c30 0000000000000000 0000000000000000 00116673 2**0
CONTENTS, READONLY, DEBUGGING
19 .debug_aranges 00000030 0000000000000000 0000000000000000 0011b2a3 2**0
CONTENTS, RELOC, READONLY, DEBUGGING
20 .debug_gdb_scripts 0000002a 0000000000000000 0000000000000000 0011b2d3 2**0
CONTENTS, READONLY, DEBUGGING
As an experiment I changed line 1334 in link/internal/ld/data.go to create a section called ".ctors" instead of ".init_array". However, the ".ctors" section never shows up. I added a Diag() call in data.go to make sure that the initarray was actually getting generated, and it is.
At this point, I am not really sure why the .ctor section is not appearing. I read through the doelf() function and the dope() function, but I'm clearly missing something.
I found Asmbpe() in link/internal/ld/pe.go. This actually creates PE sections for .text, .data, and .bss. I tried to add a section for ".ctors" but I'm not actually sure what the right thing to do is in the call.
Putting some hardcoded values in the call does result in a big, empty section called .ctors in the go.o file! So now I suppose I just need to write the contents of the initarray into this area. I'm not really sure how to get a hold of that data, and I'm not really sure how to size the section correctly.
I found Asmbpe() in link/internal/ld/pe.go. This actually creates PE sections for .text, .data, and .bss. I tried to add a section for ".ctors" but I'm not actually sure what the right thing to do is in the call.
Yes, you write pe sections in Asmbpe. You can look at, for example, addimports functions to see how it is done. Basically you write whatever contents you want in there and also call addpesection so your section reference is written in section table later. I have never heard about ".ctors" section, so I wouldn't know what to put there.
Putting some hardcoded values in the call does result in a big, empty section called .ctors in the go.o file! So now I suppose I just need to write the contents of the initarray into this area. I'm not really sure how to get a hold of that data, and I'm not really sure how to size the section correctly.
You use addpesection to specify the size of your section. It is your responsibility to actually write all the data to te file.
Alex
Perhaps someone can clarify some confusion for me.
In the file cmd/link/internal/ld/data.go there is a whole block of code which creates an section called .init_array, and later writes values into this section. I added some diagnostics which show that this code is called on Windows as well.
My question is, where does the section called ".init_array" go after it is created in data.go? A related question is: How do I get access to the the data which is written there?
Also, I found this very interesting discussion of .init_array vs. .ctors in gcc: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=46770
On Fri, Dec 11, 2015 at 5:09 AM, Christopher Nelson <
[email protected]> wrote:
Perhaps someone can clarify some confusion for me.
In the file cmd/link/internal/ld/data.go there is a whole block of code
which creates an section called .init_array, and later writes values into
this section. I added some diagnostics which show that this code is called
on Windows as well.My question is, where does the section called ".init_array" go after it is
created in data.go?
Well, on ELF, it gets written to the file in a section called
".init_array", and it gets put in the DT_INIT_ARRAY dynamic tag. I'm not
sure what happens on PE.
A related question is: How do I get access to the the data which is
written there?That's kind of the wrong question. Either that approach is correct and it
should be written to the file, or it is not correct and we need to do
something else on Windows. One thing we should not do is write a
.init_array section and then read it and change it into something else. We
should the right thing initially.
Also, I found this very interesting discussion of .init_array vs. .ctors in
Yeah, but note that the context of the bug report is systems in which the
dynamic linker implements DT_INIT_ARRAY, an ELF-specific concept. If
Windows doesn't have something like .init_array we need to do something
different on Windows.
Ian
On Fri, Dec 11, 2015 at 8:40 AM Ian Lance Taylor [email protected]
wrote:
On Fri, Dec 11, 2015 at 5:09 AM, Christopher Nelson <
[email protected]> wrote:Perhaps someone can clarify some confusion for me.
In the file cmd/link/internal/ld/data.go there is a whole block of code
which creates an section called .init_array, and later writes values into
this section. I added some diagnostics which show that this code is
called
on Windows as well.My question is, where does the section called ".init_array" go after it
is
created in data.go?Well, on ELF, it gets written to the file in a section called
".init_array", and it gets put in the DT_INIT_ARRAY dynamic tag. I'm not
sure what happens on PE.Yes, I understand that. My confusion arises from the fact that I don't see
any platform-specific code in data.go. I understand that the data "should"
and somehow eventually does make it into the .init_array section on ELF. I
would like to perform the same step for PE on Windows, except redirect the
data to the .ctors section.
In other words, I see the code here:
https://github.com/golang/go/blob/master/src/cmd/link/internal/ld/data.go#L1333
This gets called on Windows and Linux, but I don't understand how that
actually gets picked up by the ELF writer. It certainly doesn't get picked
up by the PE writer.
I also see:
https://github.com/golang/go/blob/master/src/cmd/link/internal/ld/data.go#L1015
That also gets called on Windows and seems to generate the data that I want
to write into the .ctors section in the PE file for Windows. However, again
I don't actually understand "where" the data is going, or how to get back
to it in Asmbpe() so that I can write it into the .ctors section.
I've looked through the ELF writer, and I see that it adds a section name
called .init_array, but it is not clear to me how that actually gets
connected to the section with the same name made in data.go.
After running some experiments, it appears that gcc on Windows exclusively
uses the .ctors data section for this kind of thing. There doesn't appear
to be an .init_array concept, and frankly for static libraries I think this
is completely compiler runtime related. For gcc, the .ctors section seems
like it will work. For MSVC there may be some other mechanism, but that's
not really important to me at the moment. If anybody reading this knows
better I would absolutely like to hear it.
I made some simple C code, which looks like this:
$ cat const_2.c
void do_this_first() __attribute__((constructor));
void do_this_first() {
for(int i=0; i<100; ++i) {}
}
Compiled on Linux it looks like this:
$ objdump -h const_2_u.o
const_2_u.o: file format elf64-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 0000001a 0000000000000000 0000000000000000 00000040 2**0
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .data 00000000 0000000000000000 0000000000000000 0000005a 2**0
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000000 0000000000000000 0000000000000000 0000005a 2**0
ALLOC
3 .init_array 00000008 0000000000000000 0000000000000000 00000060 2**3
CONTENTS, ALLOC, LOAD, RELOC, DATA
4 .comment 0000002e 0000000000000000 0000000000000000 00000068 2**0
CONTENTS, READONLY
5 .note.GNU-stack 00000000 0000000000000000 0000000000000000 00000096 2**0
CONTENTS, READONLY
6 .eh_frame 00000038 0000000000000000 0000000000000000 00000098 2**3
$ objdump -s const_2_u.o
const_2_u.o: file format elf64-x86-64
Contents of section .text:
0000 554889e5 c745fc00 000000eb 048345fc UH...E........E.
0010 01837dfc 637ef690 5dc3 ..}.c~..].
Contents of section .init_array:
0000 00000000 00000000 ........
Contents of section .comment:
0000 00474343 3a202855 62756e74 7520352e .GCC: (Ubuntu 5.
0010 322e312d 32327562 756e7475 32292035 2.1-22ubuntu2) 5
0020 2e322e31 20323031 35313031 3000 .2.1 20151010.
Contents of section .eh_frame:
0000 14000000 00000000 017a5200 01781001 .........zR..x..
0010 1b0c0708 90010000 1c000000 1c000000 ................
0020 00000000 1a000000 00410e10 8602430d .........A....C.
0030 06550c07 08000000 .U......
And on Windows it looks like this:
$ objdump -h const_2.o
const_2.o: file format pe-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000030 0000000000000000 0000000000000000 0000012c 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .data 00000000 0000000000000000 0000000000000000 00000000 2**4
ALLOC, LOAD, DATA
2 .bss 00000000 0000000000000000 0000000000000000 00000000 2**4
ALLOC
3 .xdata 0000000c 0000000000000000 0000000000000000 0000015c 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .pdata 0000000c 0000000000000000 0000000000000000 00000168 2**2
CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
5 .ctors 00000008 0000000000000000 0000000000000000 00000174 2**3
CONTENTS, ALLOC, LOAD, RELOC, DATA
6 .rdata$zzz 00000020 0000000000000000 0000000000000000 0000017c 2**4
$ objdump -s const_2.o
const_2.o: file format pe-x86-64
Contents of section .text:
0000 554889e5 4883ec10 c745fc00 000000eb UH..H....E......
0010 048345fc 01837dfc 637ef690 4883c410 ..E...}.c~..H...
0020 5dc39090 90909090 90909090 90909090 ]...............
Contents of section .xdata:
0000 01080305 08120403 01500000 .........P..
Contents of section .pdata:
0000 00000000 22000000 00000000 ....".......
Contents of section .ctors:
0000 00000000 00000000 ........
Contents of section .rdata$zzz:
0000 4743433a 20287464 6d36342d 31292035 GCC: (tdm64-1) 5
0010 2e312e30 00000000 00000000 00000000 .1.0............
That may not be conclusive, but it strongly suggests to me that in this
fairly simple case, if I take the data that is in .init_array on ELF, and
write it to .ctors on PE, it will probably work.
In other words, I see the code here:
https://github.com/golang/go/blob/master/src/cmd/link/internal/ld/data.go#L1333
This gets called on Windows and Linux, but I don't understand how that
actually gets picked up by the ELF writer. It certainly doesn't get picked
up by the PE writer.
It is confusing.
There is naming confusion first. PE file has "sections" (search for pecoff.doc if you want details), while Go linker refers to "segments" that contains "sections". PE "section" corresponds to Go linker "segment". In terms of segments Go linker produces "text", "data" and list of "dwarf..." segments. These contains minimum code + data + debug_info that every platform need. All extras bits are written by platform specific code.
For example, by the time Asmbpe starts "text", "data" and "dwarf..." segments are already on the disk. All PE writing code do is makes a note (addpesection) about positions of those, so they can be written to "pe section table" as PE file is built. Asmbpe also writes whatever other PE sections this file requires - this time not just by calling addpesection but also by actually writing file contents. For example in addimports it writes a special PE section that is used by Windows program loader to find all system DLLs that Go program will use when it runs.
I suspect what happened here, is someone created new "segment" in Go linker without telling PE writer about it. So what you need to do either just add appropriate addpesection somewhere in Asmbpe. Or copy the writing code into pe.go, if you think it is more correct or clearer or whatever. When calling addpesection, you can name it ".ctors" or anything you want. I am not familiar with ".ctors", so I wouldn't know if it is required or if it will help you any.
Alex
@alexbrainman and @ianlancetaylor Thank you very much for all the help. I am now in a kind of frustrating place, so perhaps you can help me more.
I am pretty sure I know _what_ exactly needs to be written into the PE file, at least for gcc:
The problem I have now is:
I'm sorry to be a pain, but I feel like I'm really close to getting -buildmode=c-archive working, I just can't seem get the final pieces.
I found Cntxt.AllSyms, and I can find the symbol record for "_rt0_amd64_windows_lib", but the symbol record has a lot of data in it that doesn't obviously appear to be the address (or an address.)
I also saw the Vputl, Lputl functions. However, when I call them the result data doesn't appear to show up in the file.
Thanks again for all your help.
I found Cntxt.AllSyms, and I can find the symbol record for "_rt0_amd64_windows_lib", but the symbol record has a lot of data in it that doesn't obviously appear to be the address (or an address.)
I think Value contains address of the function (but I could be wrong). But that address is an "absolute" address. I don't think absolute address will work for you. Maybe you need offset from start of PE section containing that function. Maybe you need to provide relocation for these. I would fiddle with correspondent object file compiled by gcc to see what is required. I have built github.com/alexbrainman/goissue10776/pedump for similar purpose. It is, probably, not as good as objdump, but it is written in Go, and I can do with it what I like easily. It might also help you see how PE file is structured.
I can't seem to figure out how to actually write binary data into the section
See addimports as an example. Vputl and Lputl and many others certainly work. The IO is buffered, so you won't see them writing to the file immediately. I also suggest you check your position in the file - you might be writing somewhere in the middle of the file, instead of end. I am not sure what you're doing. Perhaps if you show us you changes, someone might be able to help you.
Alex
Thank you, that was very helpful. I now have data showing up in the .ctors section. I think it is not the correct data, but I'm getting closer.
I have forked the go repo and put my changes up. The commit with all the work so far is:
https://github.com/nadiasvertex/go/commit/6a98514598645aa03b21c54f1ea0a18e413ce5fb
The particular function that I am focusing on right now is:
https://github.com/nadiasvertex/go/blob/master/src/cmd/link/internal/ld/pe.go#L1089
According to objdump:
$ objdump -S ./tmpo/go.o | grep "_rt0"
000000000004f430 <_rt0_amd64_windows_lib>:
The address of the symbol I want is 0x4f430. This corresponds with what gcc does:
$ objdump -S const_2.o
0000000000000012 <do_this_first>:
$ objdump -s const_2.o
Contents of section .ctors:
0000 12000000 00000000 ........
However, that's not what I'm actually getting in the data:
$ objdump -s -j .ctors tmpo/go.o
Contents of section .ctors:
e5000 30e40400 00000000 00000000 00000000 0...............
Or, if I don't subtract to Segtext.Vaddr:
Contents of section .ctors:
e5000 30f44400 00000000 00000000 00000000 0.D.............
I also notice that the minimum section size I can create is 512 bytes here, but gcc appears to set the section size to the length of the actual written data. In this case, gcc makes .ctors 8 bytes long, but go makes it 512 bytes long. I wonder if that might be confusing the platform linker when it goes to merge the .ctors sections of the .o files at the end.
GCC:
$ objdump -h const_2.o
const_2.o: file format pe-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000040 0000000000000000 0000000000000000 0000012c 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .data 00000000 0000000000000000 0000000000000000 00000000 2**4
ALLOC, LOAD, DATA
2 .bss 00000000 0000000000000000 0000000000000000 00000000 2**4
ALLOC
3 .xdata 00000024 0000000000000000 0000000000000000 0000016c 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .pdata 00000030 0000000000000000 0000000000000000 00000190 2**2
CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
5 .ctors 00000008 0000000000000000 0000000000000000 000001c0 2**3
CONTENTS, ALLOC, LOAD, RELOC, DATA
6 .rdata$zzz 00000020 0000000000000000 0000000000000000 000001c8 2**4
CONTENTS, ALLOC, LOAD, READONLY, DATA
Go:
$ objdump -h tmpo/go.o
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 000c1200 0000000000001000 0000000000001000 00000600 2**5
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE, DATA
1 .data 00001200 00000000000c3000 00000000000c3000 000c1800 2**5
CONTENTS, ALLOC, LOAD, RELOC, DATA
2 .bss 0001f4c0 00000000000c5000 00000000000c5000 00000000 2**5
ALLOC
3 .ctors 00000200 00000000000e5000 00000000000e5000 000c2a00 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
I also notice that the minimum section size I can create is 512 bytes here, but gcc appears to set the section size to the length of the actual written data. In this case, gcc makes .ctors 8 bytes long, but go makes it 512 bytes long. I wonder if that might be confusing the platform linker when it goes to merge the .ctors sections of the .o files at the end.
Yes, that could break things for you.
Go PE linker used to generate executable files only. Windows executables must have sections padded to 512 bytes (maybe some other sizes work too), otherwise Windows program loader fails to execute them.
Recently minux implemented code to allow for external linker with cgo. That code produces object file (for gcc linker to use) instead of executable. The object file sections for the object file are padded same way, but there are only 2 sections present: .text and .data. gcc does not complain about that.
While trying to find solution for issue #10776 (see cl 13571), I have discovered that dwarf sections must not be padded, otherwise gcc complains. You can change your code not to pad .ctors section and see what happens. You can pinch some of my code from cl 13571. I think ALL sections in object file shouldn't be padded, but it is not easy to change - by the time PE linker knows if it is building executable or object file, alignment has already been set. We need to rearrange code for that, but I didn't have time to fiddle with that.
I also suggest you check if your C object file has any relocations. You might have to create them too.
Alex
I've made some more progress. I manually killed the padding on this one section after calling addpesection, and that results in the right values in the section header later.
It turns out that I need to emit a relocation entry for the .ctors section (which makes perfect sense.) I was unable to reuse the peemitreloc code because it wants to walk a whole symbol chain, and I just want to write one symbol.
In any case, I looked at the PE spec from MS, and I think I have most of the information I need. However, I don't seem to be able to get the correct SymbolTableIndex, which is "A zero-based index into the symbol table. This symbol gives the address that is to be used for the relocation. If the specified symbol has section storage class, then the symbol’s address is the address with the
first section of the same name."
Any pointers here would be really helpful.
Also note that gcc appears to generate the relocation IN the .ctors section, AGAINST the .text section:
const_2.o: file format pe-x86-64
RELOCATION RECORDS FOR [.ctors]:
OFFSET TYPE VALUE
0000000000000000 R_X86_64_64 .text
Latest code is: https://github.com/nadiasvertex/go/commit/f0894cbfb254c762a59fc5c4510c5004dfcc76a5
If you have a *LSym
value s
, then the symbol index you are looking for is s.Dynid
. But if that value is negative, then no symbol index has been assigned, and something has gone wrong.
A reloc against the .text section is really a reloc against a symbol named .text
defined at offset 0 in the section named .text
.
I don't seem to be able to get the correct SymbolTableIndex ...
Each PE section has relocation table. Each relocation record is:
type Reloc struct {
VirtualAddress uint32
SymbolTableIndex uint32
Type uint16
}
VirtualAddress is offset in the PE section this relocation table belongs to where the data needs to be adjusted. Type determines how to adjust it. And SymbolTableIndex points to a "symbol" that contains all information external linker needs to produce relocation value. SymbolTableIndex is an index into PE "symbol table". You can read about it in pecoff.doc, but it lives somewhere after all PE sections are finished and is pointed by PointerToSymbolTable. You should be able to use objdump to look at PE symbol table of your C obj file and do something similar. You can look at addpesymtable in pe.go to see how we write symbol table. Perhaps Ian is correct that it points to the whole .text section, but you would have to write a correspondent entry into symbol table anyway.
Alex
A colleague helped me make additional progress. He added some logic that adjusts the existing relocation code to use the .ctors section. It now appears that we have the correct data in the correct place:
$ objdump -r -j .ctors tmpo\go.o
tmpo\go.o: file format pe-x86-64
RELOCATION RECORDS FOR [.ctors]:
OFFSET TYPE VALUE
0000000000000000 R_X86_64_64 _rt0_amd64_windows_lib-0x000000000004f430
This looks slightly different than what gcc outputs because the value of the relocation is ".text". However, if I understand @ianlancetaylor then the value is just a name, and it's not important. This is exactly the symbol we want relocated, and the address is correct (0x4f430).
For some reason if I link the object files produced by Go into an .a file, the .ctors section doesn't get picked up. However, if I use gcc to link the 3 Go files with a C driver the .ctors data does get picked up:
$ gdb --silent <dump_ctorlist.txt
(gdb) Reading symbols from test.exe...done.
(gdb)
0x4cb5f0 <___CTOR_LIST__>: 0xffffffffffffffff 0x000000000089ee40
0x4cb600 <___CTOR_LIST__+16>: 0x00000000004cb5e0 0x0000000000000000
0x4cb610 <___DTOR_LIST__>: 0xffffffffffffffff 0x0000000000000000
0x4cb620: Cannot access memory at address 0x4cb620
The fly in the ointment here is that the relocation happens incorrectly. The 0x000000000089ee40 is the entry that should call _rt0_amd64_windows_lib (0x00000000004cb5e0 = register_frame_ctor in section .text, part of gcc's init code).
However the address is wrong. GDB reports:
0x44fa10 <_rt0_amd64_windows_lib>
So I would expect that the entry in that list would be 0x44fa10.
I'm guessing that we specified the relocation wrong. Code is here: https://github.com/nadiasvertex/go/commit/39b73bf8ee24f869ba15ec29c221b1d69dac7478
Any guidance would be helpful.
It may or may not help you, but: I've once also had to implement some relocations-related code, for building contents of a ".rsrc" section (embedded icons etc.) for Win32 for Go (the tool builds an .o/.syso file). Unfortunately, I don't remember what the calculations meant already (not sure if I even really understood them when writing the stuff), but in case it could help you in any way, the relevant code seems to be here:
https://github.com/akavel/rsrc/blob/ba14da1f827188454a4591717fff29999010887f/coff/coff.go#L386-L387
Quick explanation how the Walk block in l.371-393 works: it sequentially and recursively (DFS) walks through a Coff struct (which represents a "sketch"/template of a Coff output file), and feeds a string representing the "current path in the Coff struct" (e.g. "/Dir/Dirs[1]" means coff.Dir.Dirs[1]) to the closure, while "offset" contains the byte position that the field would have in the final output file (it advances by virtue of the freezeCommon2 call in l.392). The closure updates contents of various fields which in the final file must be filled based on this offset.
Not sure if that can or cannot be helpful for you; sorry if the latter, but just in case.
My colleague and I have gotten to the point that the _rt0_amd64_windows_lib initializer is called correctly from an executable linked to the Go generated static library. That's pretty exciting.
The problem now is that the executable segfaults. I'll briefly explain where I think it is happening, and maybe some guidance can be provided on what the "right" thing to do is.
First, I understand that we have to pass in command-line arguments somehow. On Windows this appears to be really complicated, so I'm not even dealing with it yet. By the time we get to the initialization routine, the registers which held the information from the Windows kernel have been overwritten.
So currently I am initializing the memory locations that we pull this stuff back out of to 0x0. Maybe this is bad, but it seems like the code in the runtime should just skip processing the args if I set them to empty.
The runtime passes through into x_cgo_sys_thread_create(), which receives _rt0_amd64_windows_lib_go as the function to spawn. pthread_create is given this value and returns success.
Walking out, we exit the __do_global_ctors() function fine. In the meantime, the initialized thread starts to run. This thread ends up segfaulting fairly quickly, by jumping to address 0x00000000ffffffff. Clearly this is wrong.
Are there any suggestions where I should look to narrow down why it's going off into the weeds?
First, I understand that we have to pass in command-line arguments somehow. On Windows this appears to be really complicated,
On Windows command-line arguments are handled by the kernel. Your executable don't need to do anything specific but to retrieve then when and if they are needed (use apropriate Windows APIs). Go runtime does is in goenvs function. This is all true for Go (pure and cgo) executable. I don't know what Go DLL does.
Are there any suggestions where I should look to narrow down why it's going off into the weeds?
I don't know what happens in Go DLL. Perhaps others will help.
Alex
@alexbrainman To be clear, this is not the .dll. This is a static library. Currently we can generate code that the C runtime can link against, and it runs the runtime initializer. The runtime initializer crashes somewhere, but there is not backtrace because it jumped to random code way up in the address space.
Still working on runtime initialization. I've adjusted the assembly to avoid trying to setup the command-line arguments. I now get a clean traceback. It looks like the init thread aborts before it ever gets to the runtime initialization part.
The addresses passed in to pthread_create look correct. When I change the code to branch directly to the runtime initializer (instead of spawning a thread) it seems to complete correctly. So it looks like it's isolated to bad setup of the thread.
Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 1280.0x670]
0x000007fefd921520 in msvcrt!_HUGE () from C:\Windows\system32\msvcrt.dll
(gdb) bt
#0 0x000007fefd921520 in msvcrt!_HUGE () from C:\Windows\system32\msvcrt
#1 0x00000000004c7f57 in pthread_create_wrapper ()
#2 0x000007fefd89415f in srand () from C:\Windows\system32\msvcrt.dll
#3 0x000007fefd896ebd in msvcrt!_ftime64_s ()
from C:\Windows\system32\msvcrt.dll
#4 0x0000000076fb5a4d in KERNEL32!BaseThreadInitThunk ()
from C:\Windows\system32\kernel32.dll
#5 0x00000000770eb831 in ntdll!RtlUserThreadStart ()
from C:\Windows\SYSTEM32\ntdll.dll
You may want to call some Windows specific function like _beginthread or _CreateThread rather than pthread_create.
I also want to warn you about using gdb. Go linker does not write dwarf information into object file (see issue #10776).
Alex
Turns out that the runtime code I borrowed from Linux uses a different argument-passing protocol than gcc on windows uses. After fixing that, everything works fine. My simple test runs to completion with no errors.
The changes are https://github.com/nadiasvertex/go/commit/1fc1f59fceb1794a7c4723742434d30f9ed5c14c and https://github.com/nadiasvertex/go/commit/81e3aa8a91b98d255fe872aee6401da356d38fe6
My current build script is awkward because there are still two bugs left to figure out:
I'm wondering if the linker is using any kind of asynchrony for (1), and for (2) I have no clue. Any ideas would be helpful.
I ran some tests, it looks like the second error above is actually fixed. Probably the bad data in the .o file was causing the linker to ignore whatever we wrote, and now that it's correct it works.
The first problem above is still a real issue. If I run
go build -buildmode=c-archive -ldflags="-tmpdir .\tmpo" -o test.a calc.go
gcc -o test test_driver.o test.a -lws2_32 -lntdll
The output is perfect, everything works, rainbows and unicorns.
If I run
go build -buildmode=c-archive -o test.a calc.go
gcc -o test test_driver.o test.a -lws2_32 -lntdll
Then I get:
test.a(000000.o): In function `Sum':
C:/Users/CHRIST~1/AppData/Local/Temp/go-build304995930/command-line-arguments/_obj/_cgo_export.c:19: undefined reference to `_cgoexp_f070ceaf1261_Sum'
C:/Users/CHRIST~1/AppData/Local/Temp/go-build304995930/command-line-arguments/_obj/_cgo_export.c:19: undefined reference to `crosscall2'
test.a(000000.o):calc.cgo2.c:(.rdata$.refptr._cgoexp_f070ceaf1261_Sum[.refptr._cgoexp_f070ceaf1261_Sum]+0x0): undefined reference to `_cgoexp_f070ceaf1261_Sum'
collect2.exe: error: ld returned 1 exit status
Which is similar to a problem I described last week. The objdump output indicates that the go.o file has not apparently been completely consumed:
$ objdump -a test.a
In archive test.a:
objdump: go.o: File format not recognized
000000.o: file format pe-x86-64
rw-rw-rw- 0/0 4165 Dec 19 13:53 2015 000000.o
000001.o: file format pe-x86-64
rw-rw-rw- 0/0 16188 Dec 19 13:53 2015 000001.o
I have a workaround for problem (1) , the workaround is silly, but it seems pretty stable. Right before the spawn of 'ar' to create the C archive, I added a call to os.Stat() to see if go.o exists, and if so if it is empty or not. After I added this call the build worked perfectly every time.
Code is here: https://github.com/nadiasvertex/go/commit/0df614097d48f091d8984db0decfce6edefcb0ea
At this point, all work for issue #13494 should be complete. Of course, it will need review and testing. I'll read the "how to contribute" stuff to submit a patch. As time permits I'll return to the c-shared bug and see what is needed for that.
I submitted a patch for the issue mentioned above, and started work on c-shared. The initial work of generating a .dll is done, but it has references to unexpected files. For example, when I run my test rig it says it cannot find a.out.exe. If I rename the output file "test.dll" to "a.out.exe" the test system runs and crashes. The dependency is probably just an artifact of how the linker generates output. If I use the final output name during the generation it will probably be okay.
The segfault occurs in runtime.rt0_go, here is the context:
0x000000006ab8ba86 <+246>: mov %gs:0x28,%rbx
=> 0x000000006ab8ba8f <+255>: movq $0x123,%fs:(%rbx)
0x000000006ab8ba97 <+263>: mov 0x852da(%rip),%rax # 0x6ac10d78 <runtime.m0+88>
0x000000006ab8ba9e <+270>: cmp $0x123,%rax
0x000000006ab8baa4 <+276>: je 0x6ab8baad <runtime.rt0_go+285>
Registers are:
(gdb) info reg
rax 0x7a318c 8008076
rbx 0x6ac10d78 1791036792
rcx 0x6ac10a20 1791035936
rdx 0x0 0
rsi 0x6ab8d850 1790498896
rdi 0x6ac10d78 1791036792
rbp 0x0 0x0
rsp 0x99fed0 0x99fed0
r8 0x7fffffdb000 8796092870656
r9 0x62 98
r10 0x100000 1048576
r11 0x99fa98 10091160
r12 0x0 0
r13 0x0 0
r14 0x0 0
r15 0x0 0
rip 0x6ab8ba8f 0x6ab8ba8f <runtime.rt0_go+255>
eflags 0x10202 [ IF RF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
I'm really not sure what the expected values are supposed to be here, or why they are wrong when the c-archive has them correct.
Code is here: https://github.com/nadiasvertex/go/commit/4a4e02259f7f7f3926c463b36b976dbfdbe79dde
That looks like the test in rt0_go that TLS has been set up correctly. I guess it hasn't, in fact, been set up correctly!
The question of what should be done about TLS is probably above my pay grade, as it seems fundamental to the ABI of the language. Is there a discussion about options somewhere? If not, can I facilitate such a conversation in some way? It doesn't seem to make much sense for me to continue working on this until there is some consensus around what ought to be done.
@nadiasvertex I don't see how it is possible for it to crash in runtime.rt0_go. Can I see it for myself? How do you make it crash?
But yes we would have to change the way we do TLS, if we want to have Go DLL loaded by Go executable. From what I can gather on the net. We can use PE file facilities (search for .tls in pecoff.doc), or we can use TlsGetValue Windows API. Given that you use gcc to generate DLL, PE file fiddling is not an option, unless someone knows how to arrange for gcc to help with that. That leaves us with TlsGetValue. I am concerned that TlsGetValue call might be expensive comparing with what we do now, but I don't know any alternative. Maybe others will comment too.
Alex
https://github.com/nadiasvertex/go/tree/win-shared has the code. Just build a .dll and call a Go function from a C function.
@nadiasvertex I tried your win-shared branch.
package main
//export Foo
func Foo() {
println("foo")
}
func main() {
}
and build
go build --buildmode=c-shared -o go.dll
nm -s go.dll
https://gist.github.com/mattn/ad8ed59a3efe86db2278
It seems not exported Foo
.
I run make.bat over https://github.com/nadiasvertex/go/tree/win-shared, but it fails with:
runtime/cgo
# runtime/cgo
runtime\cgo\gcc_libinit_windows.c:9:21: fatal error: pthread.h: No such file or directory
#include <pthread.h>
^
compilation terminated.
How do I fix this failure?
My gcc:
c:\dev\winshared\src>gcc --version
gcc (GCC) 4.9.1
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Alex
I used the TDM GCC distribution at http://tdm-gcc.tdragon.net and it just
works. If you can't include pthread, you probably don't have it installed.
If you're using mingw you may need to make sure that package is installed.
On Tue, Dec 22, 2015, 10:12 PM Alex Brainman [email protected]
wrote:
I run make.bat over https://github.com/nadiasvertex/go/tree/win-shared,
but it fails with:runtime/cgo
runtime/cgo
runtime\cgo\gcc_libinit_windows.c:9:21: fatal error: pthread.h: No such file or directory
#include
^
compilation terminated.How do I fix this failure?
My gcc:
c:\dev\winsharedsrc>gcc --version
gcc (GCC) 4.9.1
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.Alex
—
Reply to this email directly or view it on GitHub
https://github.com/golang/go/issues/11058#issuecomment-166794526.
It should be possible to write runtime/cgo/gcc_libinit_windows.c without using pthread at all. It can (and should) use ordinary Windows calls instead.
What do you mean by "ordinary" Windows calls? The msvcrt, or direct win32
API?
On Wed, Dec 23, 2015 at 1:12 PM Ian Lance Taylor [email protected]
wrote:
It should be possible to write runtime/cgo/gcc_libinit_windows.c without
using pthread at all. It can (and should) use ordinary Windows calls
instead.—
Reply to this email directly or view it on GitHub
https://github.com/golang/go/issues/11058#issuecomment-166961224.
_cgo_sys_thread_start in $GOROOT/src/runtime/cgo/gcc_windows_amd64.c uses _beginthread. Maybe do the same.
You can also use Windows CreateThread function. I don't know what is the right thing to do here, because I don't know much about gcc.
Alex
This isn't really a GCC issue. You can disregard the fact that gcc_libinit_windows.c has a name that starts with "gcc". The point is, the file contains C code, and is compiled with a C compiler. It defines three functions
x_cgo_sys_thread_create must start a new thread running func, passing arg to it. This is the win32 CreateThread function or the Visual Studio _beginthread function.
x_cgo_wait_runtime_init_done must wait until x_cgo_notify_runtime_init_done has been called. x_cgo_notify_runtine_init_done must let x_cgo_wait_runtine_init_done execute. I don't know much about Windows synchronization, but I would guess this could be done using the win32 CreateSemaphore and WaitForSingleObject functions.
I will make these adjustments to this code and the c-archive patch.
I have removed the pthread-specific code and pushed it into both the c-archive patch in the official tree, and into the win-shared branch of my github fork of Go. If you would like to take another look at the error, you may get farther. Thanks!
Changes are here: https://github.com/nadiasvertex/go/commit/d88e7e06bea1c7f0a33f87f6cddbdbe149a80619
Code is here: https://github.com/nadiasvertex/go/tree/win-shared
Running make.bat still fails:
...
runtime/cgo
# runtime/cgo
runtime\cgo\gcc_libinit_windows.c:9:21: fatal error: pthread.h: No such file or directory
#include <pthread.h>
^
compilation terminated.
runtime/race
testing/iotest
...
I hope I am running correct version:
c:\dev\winshared\src>git rev-parse HEAD
d88e7e06bea1c7f0a33f87f6cddbdbe149a80619
c:\dev\winshared\src>git status
On branch win-shared
Your branch is up-to-date with 'origin/win-shared'.
nothing to commit, working directory clean
c:\dev\winshared\src>
Alex
I made a typo when I migrated the patch from my c-archive branch. I updated gcc_libinit.c instead of gcc_libinit_windows.c. The code has been updated, and I verified that I patched the right file this time.
The code has been updated
I can build Go now. Thank you.
Just build a .dll and call a Go function from a C function.
I don't know how to do that. I used
go build --buildmode=c-shared -o go.dll
command and that creates a go.dll file. How do you call it from a C function? What are the commands? Thank you.
Alex
I've tried to use TlsGetValue
https://github.com/alexbrainman/winapi/commit/a05e0a78114d5bd0464c0f9c7f27f48e89b218bd
and it works fine. TlsGetValue
(gdb) disas
Dump of assembler code for function TlsGetValue:
=> 0x7c8097e0 <+0>: mov %edi,%edi
0x7c8097e2 <+2>: push %ebp
0x7c8097e3 <+3>: mov %esp,%ebp
0x7c8097e5 <+5>: mov %fs:0x18,%eax
0x7c8097eb <+11>: mov 0x8(%ebp),%ecx
0x7c8097ee <+14>: cmp $0x40,%ecx
0x7c8097f1 <+17>: jae 0x7c845054 <SetUnhandledExceptionFilter+367>
0x7c8097f7 <+23>: andl $0x0,0x34(%eax)
0x7c8097fb <+27>: mov 0xe10(%eax,%ecx,4),%eax
0x7c809802 <+34>: pop %ebp
0x7c809803 <+35>: ret $0x4
End of assembler dump.
(gdb)
looks fairly small, so it is, probably, an option for us (I didn't check amd64 version). I also have found this article https://msdn.microsoft.com/en-us/library/windows/desktop/ms686997(v=vs.85).aspx about how to do the same in a DLL, so we can use that too.
Is that something we want to do? Perhaps there is a way to handle TLS via gcc, maybe it will be simpler.
Alex
PS: We used to have get_tls asm macro defined in runtime package, but now it just say
#define get_tls(r) MOVL TLS, r
How is MOVL TLS, r
implemented?
OT: I really hope you succeed for 1.6 and admire your persistence in tracking down all the little issues m
@alexbrainman
I have a "driver" .c file that looks like this:
#include "test.h"
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char **argv) {
for(int i=0; i<100; i++) {
for(int j=0; j<100; j++) {
GoInt v1 = Sum(i,j);
GoInt v2 = i+j;
printf("Sum=%lld expect %lld\n", v1, v2);
if (v1!=v2) {
abort();
}
}
}
return 0;
}
I have a test .go file that looks like this:
package main
import "C"
//export Sum
func Sum(x, y int) int {
return x+y
}
func main() {
}
I build using the following commands:
go build -buildmode=c-shared -o test.dll test.go
gcc -c -o test_driver.o test_driver.c
gcc -o test-lib test_driver.o test.dll -lws2_32 -lntdll
copy test.dll a.out.exe
That's when I experience the TLS issue.
And where I am suppose to get test.h file from?
Alex
It is generated by cgo.
On Sun, Jan 3, 2016, 7:59 PM Alex Brainman [email protected] wrote:
And where I am suppose to get test.h file from?
Alex
—
Reply to this email directly or view it on GitHub
https://github.com/golang/go/issues/11058#issuecomment-168561360.
I had to modify your C program sligtly (my gcc cannot handle for() loops), and everything builds now:
c:\dev\src\issues\issue11058>dir
Volume in drive C has no label.
Volume Serial Number is D2A1-D2A1
Directory of c:\dev\src\issues\issue11058
04/01/2016 12:50 PM <DIR> .
04/01/2016 12:50 PM <DIR> ..
04/01/2016 12:45 PM <DIR> .hg
04/01/2016 12:45 PM 115 test.go
04/01/2016 12:47 PM 246 test_driver.c
2 File(s) 361 bytes
3 Dir(s) 3,758,317,568 bytes free
c:\dev\src\issues\issue11058>type test.go
package main
import "C"
//export Sum
func Sum(x, y int) int {
return x+y
}
func main() {
}
c:\dev\src\issues\issue11058>type test_driver.c
#include "test.h"
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char **argv) {
int i=2, j=3;
GoInt v1 = Sum(i,j);
GoInt v2 = i+j;
printf("Sum=%lld expect %lld\n", v1, v2);
if (v1!=v2) {
abort();
}
return 0;
}
c:\dev\src\issues\issue11058>go build -buildmode=c-shared -o test.dll test.go
c:\dev\src\issues\issue11058>gcc -c -o test_driver.o test_driver.c
c:\dev\src\issues\issue11058>gcc -o test-lib test_driver.o test.dll -lws2_32 -lntdll
c:\dev\src\issues\issue11058>
But when I run test-lib.exe
, I get this error https://gist.github.com/alexbrainman/72057484f5a42821ca86#file-cgoerror-jpg.
Alex
The go linker generates the .dll with the name a.out. then renames it. This
doesn't work on windows. I will fix that, but for now you have to also copy
test.dll to a.out. I forgot to mention that in the instructions.
On Sun, Jan 3, 2016, 9:30 PM Alex Brainman [email protected] wrote:
I had to modify your C program sligtly (my gcc cannot handle for() loops),
and everything builds now:c:\devsrc\issues\issue11058>dir
Volume in drive C has no label.
Volume Serial Number is D2A1-D2A1Directory of c:\devsrc\issues\issue11058
04/01/2016 12:50 PM
.
04/01/2016 12:50 PM..
04/01/2016 12:45 PM.hg
04/01/2016 12:45 PM 115 test.go
04/01/2016 12:47 PM 246 test_driver.c
2 File(s) 361 bytes
3 Dir(s) 3,758,317,568 bytes freec:\devsrc\issues\issue11058>type test.go
package mainimport "C"
//export Sum
func Sum(x, y int) int {
return x+y
}func main() {
}
c:\devsrc\issues\issue11058>type test_driver.cinclude "test.h"
include
include
int main(int argc, char **argv) {
int i=2, j=3;
GoInt v1 = Sum(i,j);
GoInt v2 = i+j;printf("Sum=%lld expect %lld\n", v1, v2); if (v1!=v2) { abort(); } return 0;
}
c:\devsrc\issues\issue11058>go build -buildmode=c-shared -o test.dll test.goc:\devsrc\issues\issue11058>gcc -c -o test_driver.o test_driver.c
c:\devsrc\issues\issue11058>gcc -o test-lib test_driver.o test.dll -lws2_32 -lntdll
c:\devsrc\issues\issue11058>
But when I run test-lib.exe, I get this error
https://gist.github.com/alexbrainman/72057484f5a42821ca86#file-cgoerror-jpg
.Alex
—
Reply to this email directly or view it on GitHub
https://github.com/golang/go/issues/11058#issuecomment-168567443.
you have to also copy
test.dll to a.out.
copy test.dll a.out.exe
does the trick. Thank you. I need to debug your original problem now.
Alex
@nadiasvertex I can reproduce the crash in runtime.rt0_go. It is crashing on https://github.com/nadiasvertex/go/blob/master/src/runtime/asm_amd64.s#L112 line. I have used objdump to disassemble test.dll. Here is the interesting part:
6dc8bae3: e8 18 38 00 00 callq 6dc8f300 <runtime.settls>
6dc8bae8: 65 48 8b 1c 25 28 00 mov %gs:0x28,%rbx
6dc8baef: 00 00
6dc8baf1: 64 48 c7 03 23 01 00 movq $0x123,%fs:(%rbx)
6dc8baf8: 00
6dc8baf9: 48 8b 05 78 e2 07 00 mov 0x7e278(%rip),%rax # 6dd09d78 <runtime.m0+0x58>
If you compare it to a similar part of simple Go executable (my go.exe):
4e2551: e8 ea 3c 00 00 callq 4e6240 <runtime.settls>
4e2556: 65 48 8b 1c 25 28 00 mov %gs:0x28,%rbx
4e255d: 00 00
4e255f: 48 c7 83 00 00 00 00 movq $0x123,0x0(%rbx)
4e2566: 23 01 00 00
4e256a: 48 8b 05 27 9f 79 00 mov 0x799f27(%rip),%rax # c7c498 <runtime.m0+0x58>
you will see that g(BX)
is transldated into 0x0(%rbx)
, while your new test.dll has %fs:(%rbx)
. I suspect that translation is your problem. I don't know how g(BX)
gets converted, perhaps others will help.
Mind you, I am not sure if you should spend your time fixing this. Because, even if you will make existing code work, you would have to come up with a different way to do TLS inside of your DLL (see discussion before).
Alex
That general kind of transformation occurs in cmd/internal/obj/x86/asm6.go. I have to admit that I have no idea how that specific transformation is occurring in this case.
Thank you @ianlancetaylor for suggestion. I will let @nadiasvertex decide what to do here.
Alex
Is there any workaround for this at the moment? Perhaps a manual approach that let's Go generate the object files that I can link together manually with gcc?
@nadiasvertex Is go.o ever explicitly closed after it's written to? I'm wondering if that could be why you need to do the os.Stat workaround to get it to flush to disk.
@jtsylve There is currently no way to generate a functional .dll straight from a Go
package on Windows. However, the -buildmode=c-archive patch for Windows is
working through the acceptance process. The patch works for me in the
limited testing I have done with it. You could download the patch and apply
it to the compiler source, then generate a static library with it. You
could then write a .c file which exposes .dll entry points and calls into
the static library, and generate a .dll from that .c file and the .a file.
It would be useful to see someone test it in a larger scope, so if you
would like to try this out I would be happy to help you get it working.
On Thu, Jan 7, 2016 at 12:51 PM Joe Sylve [email protected] wrote:
@nadiasvertex https://github.com/nadiasvertex Is go.o ever explicitly
closed after it's written to? I'm wondering if that could be why you need
to do the os.Stat workaround to get it to flush to disk.—
Reply to this email directly or view it on GitHub
https://github.com/golang/go/issues/11058#issuecomment-169754620.
@alexbrainman I'm not sure what I am deciding. The shared library support is pretty important in my environment, so deciding _not_ to do it isn't really much of an option for me.
If you are asking me to track down the compiler issue and figure out why it is misbehaving, that is fine. As long as I can ask questions of people who might know the answers. :-) Also, I have a heavy workload right now, so I might move slowly.
I'm not sure what I am deciding. ...
You need to fix crash in runtime.rt0_go. You can fix the crash by changing some code in cmd/internal/obj/x86/asm6.go to generate same code in both executable and DLL version of runtime.rt0_go. But that is not complete solution. If you reread earlier discussion about TLS, we all agreed that Go DLL cannot use same approach for TLS as current Go executable does. If Go DLL is loaded by Go executable they will be stepping on each other toes. Perhaps it is OK just to get first Go DLL going, but ultimately we'll need to do something different here. There is bigger fish to fry here.
As long as I can ask questions of people who might know the answers.
Everyone is happy to help you with what they know.
I might move slowly.
Noone else wants to do it at this moment. It is completely up to you.
Alex
@nadiasvertex I was able to build and use win-archive to produce a static library, but couldn't get the win-shared to build. The build freezes as shown below.
C:\GoP2\src>make.bat
##### Building Go bootstrap tool.
cmd/dist
Any ideas?
Yes. The win-shared code is broken because the compiler is not emitting the right TLS primitives. This causes the deadlock you are experiencing. Exactly what the right primitives are is a matter I have to investigate.
It would be useful to see someone test it in a larger scope, so if you
would like to try this out I would be happy to help you get it working.
@nadiasvertex we will test that.
Can I rebase https://github.com/nadiasvertex/go/tree/win-shared on top of https://github.com/golang/go/tree/release-branch.go1.6 or your changes don't fit in 1.6?
There is a patch waiting in gerrit, linked to in the c-archive version of
this ticket. That patch has numerous fixes rebased on 1.6. Ideally you
would take 1.6, apply that patch, and then rebase this code on top of that.
On Sat, Feb 13, 2016, 12:33 AM Bruno Clermont [email protected]
wrote:
It would be useful to see someone test it in a larger scope, so if you
would like to try this out I would be happy to help you get it working.@nadiasvertex https://github.com/nadiasvertex we will test that.
Can I just rebase https://github.com/nadiasvertex/go/tree/win-shared on
top of https://github.com/golang/go/tree/release-branch.go1.6 or your
changes don't fit in 1.6?—
Reply to this email directly or view it on GitHub
https://github.com/golang/go/issues/11058#issuecomment-183592803.
Just FYI, I created a new branch in my fork of the Go repo that has the latest c-archive patch rebased on 1.6, along with the initial (minimal) changes needed to allow c-shared to run. It's not "fixed", yet, but it is at least something people can test.
@nadiasvertex We got this error when building ("c:\go-win-shared-1.6\bin\go.exe" build -x -buildmode=c-shared
):
...
mkdir -p $WORK\runtime\cgo\_obj\
cd C:\go-win-shared-1.6\src\runtime\cgo
CGO_LDFLAGS="-g" "-O2" "-lmsvcrt" "-lm" "-mthreads" "C:\\go-win-shared-1.6\\pkg\\tool\\windows_amd64\\cgo.exe" -objdir "C:\\Windows\\TEMP\\go-build208569202\\runtime\\cgo\\_obj\\" -importpath runtime/cgo -import_runtime_cgo=false -import_syscall=false "-exportheader=C:\\Windows\\TEMP\\go-build208569202\\runtime\\cgo\\_obj\\_cgo_install.h" -- -I "C:\\Windows\\TEMP\\go-build208569202\\runtime\\cgo\\_obj\\" -Wall -Werror cgo.go
go build runtime/cgo: C:\go-win-shared-1.6\pkg\tool\windows_amd64\cgo.exe: exit status 2
go version go1.6rc2 windows/amd64
os:
Windows
os_family:
Windows
osfullname:
Microsoft Windows 8.1 Pro
osmanufacturer:
Microsoft Corporation
osrelease:
8
osversion:
6.3.9600
"C:\MinGW\bin\gcc.exe" --version
gcc.exe (GCC) 4.9.3
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
I could make that works in c-archive
only...
But I only tried from a MacOSX host cross building to Windows 32 and 64 bits.
c-shared fail:
go build -o libsimple.a -buildmode=c-shared lib.go
# runtime
asm: invalid instruction: 00019 (/path/to/sandbox/go/src/runtime/sys_windows_386.s:251) SUBL $runtime.callbackasm(SB), AX, R???1
Thanks. I know that this is still broken. Only c-archive actually works.
generated .h
file use GCC internal types:
typedef __SIZE_TYPE__ GoUintptr;
typedef float _Complex GoComplex64;
typedef double _Complex GoComplex128;
_Complex
and __SIZE_TYPE__
, anyone had success build .a
file with VC++?
_Complex is not GCC internal type, it's C99's complex type.
__SIZE_TYPE__ is kinda GCC specific, but we probably
could use size_t instead.
But anyway, I don't think supporting VC++ is of high priority,
esp. because VC++ doesn't support C99.
@minux, VC++ 2015 has nearly complete support for C99.
C99 Conformance Visual Studio 2015 fully implements the C99 Standard Library, with the exception of any library features that depend on compiler features not yet supported by the Visual C++ compiler (for example,
is not implemented).
@ianlancetaylor As the c-archive patch feels like it is wrapping up, I have started looking more closely at the c-shared stuff for Windows. I re-read the discussion here, and I have some notes and questions.
I see in src/cmd/internal/obj/x86/asm6.go:
3610: // Windows TLS base is always 0x14(FS)
3690: // Windows TLS base is always 0x28(GS)
The comments talk about "TLS initial init" and direct-access systems. This looks to me like Windows would be a "TLS initial init" system, since presumably someone has to initialize the TLS area and then store it into those locations above. Is that correct?
In the end, I think the problem is this:
Windows has a Thread Environment Block mapped to FS (32-bit) and GS (64-bit). Many of the "slots" in this block are mapped out for certain values. Windows has a convention for TLS called the "thread-local storage array" at FS:0x2C and GS:0x58, but Go decided to use the so-called "arbitrary" slot at FS:0x14 and GS:0x28 to manage its own TLS.
The concern is that other code may use the "arbitrary" slot for its own purposes, thus accidentally destroying the TLS space for the runtime and causing catastrophic failure.
Is that correct?
I suppose the simple solution is "Do what C does and either call the Win API functions, or re-implement the algorithm."
Clearly, I would prefer to just emit calls to the API.
However, I see a number of problems and it is not clear to me how they are resolved.
If it is reasonable to use the API, it seems like the most reliable implementation would be to use TlsAlloc and TlsFree for TLS slot management and use the normal FS and GS TLS array in the TEB to access the values (for performance.) I saw comments to the effect that Go doesn't really use TLS much anyway. So if this is the case, the performance impact might be quite minimal.
Thanks.
The concern is that other code may use the "arbitrary" slot for its own purposes, thus accidentally destroying the TLS space for the runtime and causing catastrophic failure.
Is that correct?
Kind of. What you have described can happen now, if some "other code" uses same TLS slot (but no one complained about this yet). This will certainly happens in the future, if Go program will load Go dll. They both use the same slot (unless we fix that problem).
Special mention is made of DLLs, but I don't see why they are a bigger problem than static libraries. If stomping is done by another runtime, why should it matter if it is a DLL or a static library?
The problem as I see it is about two independent Go runtimes clashing. Whether second Go runtime lives in DLL or static libary does not matter. If Go DLL or static library is loaded by anything but Go executable, you should be good. But if Go executable loads Go DLL or static library, it will fail.
Alex
Thanks, Alex, that makes sense. That raises other questions.
For c-shared each separate library will have its own copy of the Go runtime. That is normal and expected. Each library should be considered as a separate independent unit.
You mention that the main process will be using its own GOMAXPROCS. When using c-shared, the expectation is that you are creating a DLL to be loaded into a C program. Creating a DLL to be loaded into a Go program implies the plugin interface, which is not yet implemented anywhere.
The runtime does not know when it is calling a DLL. Calling into a c-shared DLL is just like calling into any C DLL. If you are trying to call it from Go, you need to use cgo.
@nadiasvertex Each DLL can have its own linker provided TLS section using IMAGE_TLS_DIRECTORY. It's used for the __declspec(thread) MSVC extension.
Here's an old blog post (using 32bits userspace) describing it; http://blog.dkbza.org/2007/03/pe-trick-thread-local-storage.html or http://www.nynaeve.net/?p=183 (there are many blogs talking about it)
This removes the need to use TEB, which I agree is less safe. As a matter of fact, all golang .exe could use this too, removing the need for other mean of TLS completely.
@ianlancetaylor , thank you.
@maruel , I know about the MSVC extension. However, that only works when using the MSVC toolchain, which Go does not currently support. It requires a very specific kind of cooperation between the compiler, linker, and DLL loader. In addition, this mechanism doesn't work at all for DLLs before Vista, and may not be correctly implemented even in Vista (http://www.nynaeve.net/?p=189). That probably renders it useless for Go, which supports Windows XP.
GCC and Clang appear to use different mechanisms to implement implicit TLS also, so...
Additionally, it doesn't look like TLS is as important to Go as it is to C/C++. That is, Go doesn't really support TLS as a concept in the language, and uses it sparingly in its own runtime. So TLS support doesn't necessarily need to be as... integrated.
Unless I am wrong, it seems like it would be sufficient to find a way to keep Go DLLs from stomping on each other, and the various other language runtimes. The Windows TEB has a first-class TLS mechanism that requires explicit use of certain API functions. I would prefer to start here, if at all possible.
@ianlancetaylor, @alexbrainman : I believe I have located the translation bug in asm6.go. As a refresher:
We want:
4e2556: 65 48 8b 1c 25 28 00 mov %gs:0x28,%rbx
4e255f: 48 c7 83 00 00 00 00 movq $0x123,0x0(%rbx)
but we get:
6dc8bae8: 65 48 8b 1c 25 28 00 mov %gs:0x28,%rbx
6dc8baf1: 64 48 c7 03 23 01 00 movq $0x123,%fs:(%rbx)
It looks like the code at src/cd/internal/obj/x86/asm6.go:2266 is unconditionally overriding the prefix in shared libraries to %fs. I wrote some diagnostics to detect calls to this code, and it is getting activated on Windows during a c-shared build.
I updated my code to avoid this transformation on Windows, and the result is that the .dll that gets created runs perfectly fine on my test harness. So that's one down.
I am just cheering, if I can help test this with my limited knowledge (and
some detailed instructions) - please use me as the "real user aka dumb
tester".
Again, I stand in awe and applaud your work.
Hope I can help,
Maarten
Op woensdag 16 maart 2016 heeft Christopher Nelson [email protected]
het volgende geschreven:
@ianlancetaylor https://github.com/ianlancetaylor, @alexbrainman
https://github.com/alexbrainman : I believe I have located the
translation bug in asm6.go. As a refresher:We want:
4e2556: 65 48 8b 1c 25 28 00 mov %gs:0x28,%rbx
4e255f: 48 c7 83 00 00 00 00 movq $0x123,0x0(%rbx)but we get:
6dc8bae8: 65 48 8b 1c 25 28 00 mov %gs:0x28,%rbx 6dc8baf1: 64 48 c7 03 23 01 00 movq $0x123,%fs:(%rbx)
It looks like the code at src/cd/internal/obj/x86/asm6.go:2266 is
unconditionally overriding the prefix in shared libraries to %fs. I wrote
some diagnostics to detect calls to this code, and it is getting activated
on Windows during a c-shared build.—
You are receiving this because you commented.
Reply to this email directly or view it on GitHub
https://github.com/golang/go/issues/11058#issuecomment-197067982
My win-shared-1.6 branch has been updated with another fix. This one prevents link-time errors by making sure that the output file is created with the right name in the first place. Apparently this is also a problem on OS X, so I reused that code.
If anyone would like to test it and make sure that it solves that problem, that would be great.
The "only" remaining problem now is determining what to do about thread-local variables in the case that multiple Go runtimes are executing. For example, if a process loads two different DLLs that both have the runtime.
@ianlancetaylor, @alexbrainman Would you like me to submit a patch with the fixes "so far"? This would enable c-shared for cases where only one Go DLL is in use. Or would you like me to wait until the therad-local issue is dealt with too?
Would you like me to submit a patch with the fixes "so far"?
I don't mind how you do it. Smaller CLs are always preferred.
Alex
Hi, help me please!
i try to use golang dll as a plugin for C++ application.
simple C++ app:
#include <iostream>
#include <chrono>
#include <thread>
#include <sstream>
#include <windows.h>
typedef long long GoInt64;
typedef GoInt64 GoInt;
typedef GoInt(__stdcall *DoubleIt_Type)(GoInt i);
unsigned int loop = 0;
bool dll_test() {
HINSTANCE hGetProcIDDLL = LoadLibrary("C:\\temp\\tarantool_dll\\libdoubler.dll");
if (!hGetProcIDDLL) {
std::cout << "could not load the dynamic library" << std::endl;
return false;
}
std::this_thread::sleep_for(std::chrono::seconds(3));
DoubleIt_Type DoubleIt = (DoubleIt_Type)GetProcAddress(hGetProcIDDLL, "DoubleIt");
if (!DoubleIt) {
std::cout << "could not locate the DoubleIt function" << std::endl;
return false;
}
std::cout << "loop = " << loop << " DoubleIt returned " << DoubleIt(21) << std::endl;
FreeLibrary(hGetProcIDDLL);
std::this_thread::sleep_for(std::chrono::seconds(3));
loop++;
return true;
}
int main() {
while (true) {
dll_test();
}
return EXIT_SUCCESS;
}
"hello, world" golang dll code:
package main
import (
"C"
"log"
)
//export DoubleIt
func DoubleIt(x int) int {
log.Println("DoubleIt: ", x)
return x * 2
}
func main() {
}
simple build.bat for golang dll:
cd C:\temp\tarantool_dll
set GOROOT=C:\go-win-shared-1.6\
set GOARCH=amd64
set GOPATH=C:\temp\tarantool_dll\app
:: C:\TDM-GCC-64\mingwvars.bat
C:\go-win-shared-1.6\bin\go build -o libdoubler.dll -buildmode=c-shared main
when i run C++ code with vs2015 in debug, x64 mode my app fails with something like
Exception thrown at 0x0000000067255CED in test_plugin.exe: 0xC0000005: Access violation executing location 0x0000000067255CED.
after FreeLibrary(hGetProcIDDLL);
but an exception was thrown not in Main Thread
console log:
2016/06/14 00:21:25 DoubleIt: 21
loop = 0 DoubleIt returned 42
for more complex Golang code i get "stable" work for 10-15 loops
i used Telegram bot api (http://github.com/Syfaro/telegram-bot-api) for logs, but i see that memory of my process increases every loop. And after 10-15 loops exception killing my app.
Sorry for my English)
This doesn't work in stock go yet. I have a patch which makes it work for a
single DLL but I haven't done the now general solution yet.
On Mon, Jun 13, 2016, 5:48 PM jonywtf [email protected] wrote:
Hi, help me please!
i try to use golang dll as a plugin for C++ application.simple C++ app:
include
include
include
include
include
typedef long long GoInt64;typedef GoInt64 GoInt;typedef GoInt(__stdcall *DoubleIt_Type)(GoInt i);
unsigned int loop = 0;bool dll_test() {
HINSTANCE hGetProcIDDLL = LoadLibrary("C:\temp\tarantool_dll\libdoubler.dll");
if (!hGetProcIDDLL) {
std::cout << "could not load the dynamic library" << std::endl;
return false;
}
std::this_thread::sleep_for(std::chrono::seconds(3));DoubleIt_Type DoubleIt = (DoubleIt_Type)GetProcAddress(hGetProcIDDLL, "DoubleIt"); if (!DoubleIt) { std::cout << "could not locate the DoubleIt function" << std::endl; return false; } std::cout << "loop = " << loop << " DoubleIt returned " << DoubleIt(21) << std::endl; FreeLibrary(hGetProcIDDLL); std::this_thread::sleep_for(std::chrono::seconds(3)); loop++; return true;
}
int main() {
while (true) {
dll_test();
}
return EXIT_SUCCESS;
}"hello, world" golang dll code:
package main
import (
"C"
"log"
)
//export DoubleItfunc DoubleIt(x int) int {
log.Println("DoubleIt: ", x)
return x * 2
}
func main() {}
simple build.bat for golang dll:
cd C:\temp\tarantool_dllset GOROOT=C:\go-win-shared-1.6set GOARCH=amd64set GOPATH=C:\temp\tarantool_dll\app:: C:\TDM-GCC-64\mingwvars.bat
C:\go-win-shared-1.6\bin\go build -o libdoubler.dll -buildmode=c-shared mainwhen i run C++ code with vs2015 in debug, x64 mode my app fails with
something likeException thrown at 0x0000000067255CED in test_plugin.exe: 0xC0000005: Access violation executing location 0x0000000067255CED.
after FreeLibrary(hGetProcIDDLL); but an exception was thrown not in Main
Threadconsole log:
2016/06/14 00:21:25 DoubleIt: 21
loop = 0 DoubleIt returned 42for more complex Golang code i get "stable" work for 10-15 loops
i used Telegram bot api (http://github.com/Syfaro/telegram-bot-api
https://github.com/Syfaro/telegram-bot-api) for logs, but i see that
memory of my process increases every loop. And after 10-15 loops exception
killing my app.—
You are receiving this because you were mentioned.Reply to this email directly, view it on GitHub
https://github.com/golang/go/issues/11058#issuecomment-225718860, or mute
the thread
https://github.com/notifications/unsubscribe/AB8JvGrwvJCtziNkanWB1FnatQvoV34vks5qLdAjgaJpZM4E3dB1
.
@nadiasvertex Where are we with this? I'd love to see proper c-shared support in 1.8!
Sorry, I've been really busy the last few months. I could get the "make a DLL" code done for 1.8. (It's already done, I just haven't submitted a patch for it yet.) However, that would come with the limitation that you could only use one Go DLL per process.
The "proper" DLL support is much more difficult because of how Windows performs TLS, and how Go uses Windows' TLS system. The basic problem is that two Go runtimes in the same process will stomp on each other on Windows because they both think they own the TLS "slot" being used. I wrote a proposal to the golang-dev list and literally got zero feedback.
To summarize, I see two possible approaches:
I would love to get some feedback on the ideas. I'm pretty busy through September, and then I am physically relocating my residence in November. I may have some time in October, or in December. If I could get a good design and some buy in from more knowledgeable folks I would be a lot more motivated to work on it. :-)
Personally, I think the dll creation and the proper TLS support should be two separate issues. The former could almost certainly make the 1.8 window, while the later might take a bit more thought and work to get it done right. What do you think @alexbrainman & @ianlancetaylor?
Is there a way to prevent the multiple link collision and make binary fail at linking or execution?
This way, it's safe to implement a single .dll
link for 1.8.
While give developers responsibility to build their "all-in-one" .dll
for all Go libraries they want to embed... if they really need that.
I'm pretty sure most of the users watching this issue are fine with single .dll
limitation.
@bclermont: I'm not sure there's a simple way to have the link explode just for DLLs. They will each have their own private copy of the Go runtime, which is by design as I understand it. I don't think the dynamic linker is going to complain if two DLLs export the same symbol and an executable binds both libraries.
I suppose we could register a named object with Windows and fail at runtime init if we detect another Go runtime has already initialized itself.
Even if you can load two Go DLL into a single process, they
each will have their own Go runtime so they won't interoperate.
Therefore, I don't think the one Go DLL limitation is unreasonable.
And that's the state of c-shared on other OSes too. Please go
ahead and send the CLs.
Switching to use Windows TLS is going to have huge performance
hit for Go. Because we don't know what those TLS functions do
and how much stack space they require, therefore we have to switch
to system stack to call them. This must happen at the beginning at
almost every function.
We do have a general solution: reserve one machine register to
hold g, just like the RISC architectures and treat the reserved
register as a write-through cache for the actual TLS slot. However,
this has not been implemented yet, and it too will have performance
implications.
Switching to use Windows TLS is going to have huge performance
hit for Go. Because we don't know what those TLS functions do
and how much stack space they require, therefore we have to switch
to system stack to call them.
We really only need to make TlsGetValue fast. And I did little experiment (https://github.com/golang/go/issues/11058#issuecomment-167939906) to see how TlsGetValue looks inside. It looks quite simple on my computer. Perhaps we could assume it does the same everywhere, and use same logic to fetch TLS in Go. We could even limit this to Go DLLs only, so we don't affect normal Go users (executables).
Alex
The code you disassembled in
https://github.com/golang/go/issues/11058#issuecomment-167939906
looked safe to call from Go stack, however, it didn't show the whole
function.
For example, when the TLS index is bigger than or equal to 0x40,
it jumps to 0x7c845054.
What does code at that location look like? I imagine code there
probably need to check and allocate new storage for TLS, and that
could easily overflow the limited Go stack lead to hard to diagnose
problems that are hard to reproduce too (the TLS index must be
large enough to trigger the alternate code path, which usually
means the problem won't manifest in simple test case.)
Of course, we can check the TLS index ourselves and switch to
system stack to call TlsGetValue when it's too big, however, this
design looks pretty fragile to me. (We make assumption on:
Regarding two separate TLS mechanism for Windows exe and
DLLs, I think that's hard to maintain and if we don't good test
coverage of the DLL tests, it's bound to bit rot.
What does code at that location look like?
(gdb) disas
Dump of assembler code for function TlsSetValue:
=> 0x7c809c65 <+0>: mov %edi,%edi
0x7c809c67 <+2>: push %ebp
0x7c809c68 <+3>: mov %esp,%ebp
0x7c809c6a <+5>: push %esi
0x7c809c6b <+6>: push %edi
0x7c809c6c <+7>: mov %fs:0x18,%eax
0x7c809c72 <+13>: mov 0x8(%ebp),%edi
0x7c809c75 <+16>: cmp $0x40,%edi
0x7c809c78 <+19>: mov %eax,%esi
0x7c809c7a <+21>: jae 0x7c845087 <SetUnhandledExceptionFilter+418>
0x7c809c80 <+27>: mov 0xc(%ebp),%eax
0x7c809c83 <+30>: mov %eax,0xe10(%esi,%edi,4)
0x7c809c8a <+37>: xor %eax,%eax
0x7c809c8c <+39>: inc %eax
0x7c809c8d <+40>: pop %edi
0x7c809c8e <+41>: pop %esi
0x7c809c8f <+42>: pop %ebp
0x7c809c90 <+43>: ret $0x8
0x7c809c93 <+46>: nop
0x7c809c94 <+47>: nop
0x7c809c95 <+48>: nop
0x7c809c96 <+49>: nop
0x7c809c97 <+50>: nop
End of assembler dump.
(gdb) x/20i 0x7c845087
0x7c845087 <SetUnhandledExceptionFilter+418>: sub $0x40,%edi
0x7c84508a <SetUnhandledExceptionFilter+421>: cmp $0x400,%edi
0x7c845090 <SetUnhandledExceptionFilter+427>: jae 0x7c8450f9 <SetUnhandledExceptionFilter+532>
0x7c845092 <SetUnhandledExceptionFilter+429>: cmpl $0x0,0xf94(%esi)
0x7c845099 <SetUnhandledExceptionFilter+436>: jne 0x7c8450e8 <SetUnhandledExceptionFilter+515>
0x7c84509b <SetUnhandledExceptionFilter+438>: call *0x7c8010bc
0x7c8450a1 <SetUnhandledExceptionFilter+444>: cmpl $0x0,0xf94(%esi)
0x7c8450a8 <SetUnhandledExceptionFilter+451>: jne 0x7c8450e2 <SetUnhandledExceptionFilter+509>
0x7c8450aa <SetUnhandledExceptionFilter+453>: mov %fs:0x18,%eax
0x7c8450b0 <SetUnhandledExceptionFilter+459>: mov 0x7c8856d4,%ecx
0x7c8450b6 <SetUnhandledExceptionFilter+465>: mov 0x30(%eax),%eax
0x7c8450b9 <SetUnhandledExceptionFilter+468>: push $0x1000
0x7c8450be <SetUnhandledExceptionFilter+473>: or $0x8,%ecx
0x7c8450c1 <SetUnhandledExceptionFilter+476>: push %ecx
0x7c8450c2 <SetUnhandledExceptionFilter+477>: pushl 0x18(%eax)
0x7c8450c5 <SetUnhandledExceptionFilter+480>: call *0x7c80100c
0x7c8450cb <SetUnhandledExceptionFilter+486>: test %eax,%eax
0x7c8450cd <SetUnhandledExceptionFilter+488>: mov %eax,0xf94(%esi)
0x7c8450d3 <SetUnhandledExceptionFilter+494>: jne 0x7c8450e2 <SetUnhandledExceptionFilter+509>
0x7c8450d5 <SetUnhandledExceptionFilter+496>: call *0x7c8010b4
(gdb)
You are correct. It is, probably, growing TLS area there. I saw call into SetUnhandledExceptionFilter and assumed it is exception handler, and it will crash there or something.
Alex
On 18 August 2016 at 13:54, Minux Ma [email protected] wrote:
The code you disassembled in
https://github.com/golang/go/issues/11058#issuecomment-167939906
looked safe to call from Go stack, however, it didn't show the whole
function.For example, when the TLS index is bigger than or equal to 0x40,
it jumps to 0x7c845054.What does code at that location look like? I imagine code there
probably need to check and allocate new storage for TLS, and that
could easily overflow the limited Go stack lead to hard to diagnose
problems that are hard to reproduce too (the TLS index must be
large enough to trigger the alternate code path, which usually
means the problem won't manifest in simple test case.)Of course, we can check the TLS index ourselves and switch to
system stack to call TlsGetValue when it's too big, however, this
design looks pretty fragile to me. (We make assumption on:
- the threshold for TLS index values, and
- TlsGetValue is safe to call on Go stack when TLS index is less
than the assumed threshold.)Regarding two separate TLS mechanism for Windows exe and
DLLs, I think that's hard to maintain and if we don't good test
coverage of the DLL tests, it's bound to bit rot.
Well, we have two (slightly) different mechanisms for TLS access on Linux
for executables and shared objects -- local exec and initial exec -- and it
works OK... that it works for the c-shared case though is on slightly thin
ice because you can only load so many shared objects that use the initial
exec model into a process before the loader will complain (I guess this is
similar to the TLS index limit of 40 in the Windows implementation?). For
all that, I haven't seen many real reports of this causing problems.
If you want to do this properly, as Minux said already, g should be stored
in a register most of the time, as we do on most other ports. Then the
(relatively) few times we access the TLS storage can pay the cost of
calling the general dynamic (or whatever the Windows version of this is
called) code on the system stack. If you want to be really brave you can
implement something like the general tlsdesc model and only call via the
systemstack when you really have to...
Is there any work around in Go 1.7 in Windows to get a shared library to work? Reading the comments above it looks like there might be a work around if you just need to load on Go shared library, which is my problem.
Basic problem: I have a shared library and I want to load a Go shared library and perform some work.
Compile a static library using buildmode=c-archive
and then use gcc to link it to a shared library.
Basic problem: I have a shared library and I want to load a Go shared library and perform some work.
That does not describe your problem enough. Please provide more details.
Alex
Some more info on my problem:
I am on Windows 7/10 using Go 1.7. I want to create a Go package that acts like a shared library exposing C functions for other DLLs/exes to load and invoke.
Example: I have a Go package "hello" that has an exposed method "SayHello(name string)". I want to be able to compile that to a hello.dll that has one exported method "SayHello". A separate C/C++ executable load the hello.dll using LoadLibrary and invoke the SayHello function.
Some more info on my problem:
Thank you for explaining. This current issue is it.
Unfortunately I don't have solution to your problem. Maybe others will help.
Alex
@jtsylve Can you please give me an example of the process of how to "Compile a static library using buildmode=c-archive and then use gcc to link it to a shared library."
What are the limitations to this approach?
@LukeMauldin
go build -buildmode=c-archive -o libxxx.a
gcc -m64 -shared -o xxx.dll xxx.def libxxx.a -Wl,--allow-multiple-definition -static -lstdc++ -lwinmm -lntdll -lWs2_32
Then use VS's lib
command to generate xxx.lib:
lib /def:xxx.def /machine:x64
Using the commands to set buildmode=c-archive
and then using gcc
and lib
worked perfectly! Thank you. What are the limitations to this method? Why isn't this method the official recommendation and have this issue closed?
This issue isn't closed because there shouldn't be a need for an external
linker to create a shared library.
On Tue, Sep 20, 2016 at 12:44 PM LukeMauldin [email protected]
wrote:
Using the commands to set buildmode=c-archive and then using gcc and lib
worked perfectly! Thank you. What are the limitations to this method? Why
isn't this method the official recommendation and have this issue closed?—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/golang/go/issues/11058#issuecomment-248376388, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AAkQ9ZWQZDpqu21o_l5LYcTcUZkrJhQ3ks5qsBuYgaJpZM4E3dB1
.
Creating shared library with external linker is perfectly fine.
In fact, building cgo programs require an external linker
by default (and building c-archive/c-shared implicitly enables
cgo.)
The purpose of this ticket is to make it more automatic. I'm hoping to
complete this ticket in October, aside from the TLS issues. TLS should
probably be another issue entirely.
On Tue, Sep 20, 2016 at 9:57 PM Minux Ma [email protected] wrote:
Creating shared library with external linker is perfectly fine.
In fact, building cgo programs require an external linker
by default (and building c-archive/c-shared implicitly enables
cgo.)—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/golang/go/issues/11058#issuecomment-248489183, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AB8JvORPY8F_5KixAqQ0REM4vJxi5Ldrks5qsI8DgaJpZM4E3dB1
.
What practical implications will I see from the TLS issues on windows x64 including a Go shared library .dll (generated by the instructions above) in a C program?
You can only load a single Go runtime, so that means if you have multiple Go-shared libraries then you'll run into problems. If you're only loading a single Go library then you shouldn't see any issues.
Can I load multiple Go libraries as long as they are built with the exact same runtime version (ie 1.7.1)?
What would be great is building all go runtime into a single dll and then link all Go-shared libraries to it.
Basically have 2 options for go runtime : go static link OR go dynamic link. Where go dynamic link would be a dll generated from "-buildmode=shared -linkshared std"
@LukeMauldin No. You would have to link all of your Go static libraries into a single DLL. Multiples DLLs aren't (and won't be) supported in Windows for a while yet. That's the TLS problem.
@spusnei-pbsc That's basically what I'm working on.
I have a scenario on Windows that has several pieces:
In the scenario above, as soon as the third-party COTS library loads A.dll, it seems to conflict with the Go runtime that is actually running the application and the process faults with an error like this
fatal error: unexpected signal during runtime execution
The stack trace generally looks like:
runtime.throw(0x67df97, 0x2a)
C:/Go/src/runtime/panic.go:566 +0x9c fp=0xc04234b4e8 sp=0xc04234b4c8
runtime.sigpanic()
C:/Go/src/runtime/signal_windows.go:155 +0x19c fp=0xc04234b518 sp=0xc04234b4e8
runtime.mallocgc(0x10, 0x0, 0x9e40d00, 0x0)
C:/Go/src/runtime/malloc.go:679 +0x361 fp=0xc04234b5b8 sp=0xc04234b518
runtime.growslice(0x622520, 0x0, 0x0, 0x0, 0x5, 0x3, 0x8, 0x210)
C:/Go/src/runtime/slice.go:126 +0x255 fp=0xc04234b648 sp=0xc04234b5b8
For testing purposes, if I disable the COTS library loading A.dll, then the system works as expected so I am pretty sure it is a conflict between the Go application runtime and the Go runtime in A.dll. I made sure to compile both A.dll and the running Go application with the exact same version of the Go runtime (1.7.1) and using the same C compiler. Is there anyway to work-around this limitation?
As mentioned earlier in this thread, that is not supported. I am given to understand that this scenario does not work on any platform. You cannot have multiple DLLs that have their own Go runtimes loaded into the same process. This certainly doesn't work on Windows because each runtime thinks that it owns a particular slot in the TLS area of the process control block.
Be fine with single .dll limitation.
+1
@nadiasvertex It's a great idea! you metioned above, maybe finished it in November, and How did you make out?
@janckerchen Sorry, I have been in the middle of moving my residence. However, I believe I will have some time this week. I hope to get some PRs in to address this issue.
Just an update. I have finished the work on this, I believe. However, the larger part of the work is getting good tests done. I have started porting the existing c-shared test framework from bash to Go so that it will run under Windows. This is the same pattern we followed with c-archive. However, it's slow going because I have to understand what the tests are doing first, then figure out how to convert them to Go, and then decide what parts of those tests apply to Windows.
Any chance of making the 1.8 window? I don't have an abundance of free time, but if I can help let me know.
It's too late to get something like this into 1.8, sorry.
@nadiasvertex Sounds like you have made positive progress. Have you committed the code changes somewhere? I can't seem to locate them.
No. They are in my local copy of the official Go repo. I don't want to
submit them until I have good tests. I plan to submit the test updates
first, and then submit the DLL updates.
On Fri, Nov 25, 2016, 9:45 AM jjoergensen notifications@github.com wrote:
@nadiasvertex https://github.com/nadiasvertex Sounds like you have made
positive progress. Have you committed the code changes somewhere? I can't
seem to locate them.—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/golang/go/issues/11058#issuecomment-262970194, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AB8JvN_-d2QMdpvRLswYjHKUny97r2PXks5rBvRugaJpZM4E3dB1
.
Stage 1: Rewrite test has been proposed at https://go-review.googlesource.com/33579
I'm sure that I will need to do a lot of work on this patch to get it accepted, but it passes on Linux so far. I haven't tested the Android pieces.
@nadiasvertex Any update is welcome. How can I help you to test?
I think the release team is busy on 1.8. I'm waiting on a review for the
tests. Once that gets through I'll have good tests for Windows to make sure
it works.
I can try to port the changes over to my github fork of Go for others to
look at.
On Fri, Dec 16, 2016 at 9:07 PM janckerchen notifications@github.com
wrote:
@nadiasvertex https://github.com/nadiasvertex Any update is welcome.
How can I help you to test?—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/golang/go/issues/11058#issuecomment-267736255, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AB8JvL8ePmFxIeEJXLqXe88HftgJAJkKks5rI0P3gaJpZM4E3dB1
.
Hi @nadiasvertex. Thank you very much for your effort so far!
This issue has been ongoing for more than 1 year and seems a little stuck? Would you perhaps be able to describe what remains, where to start fixing the remaining bits and how one would approach it? Where is the current source code and how would someone compile it, test it and continue on?
Unfortunately I lack knowledge of the topic, so I will not be able to implement it myself but I would really love to see 'dll' feature implemented. I hope to begin slowly replacing some existing .NET code with golang inside our company and this seems like an easy way to go about it. All the best, Jonathan.
Thanks.
The basic status is that the code is all written. I believe that it works.
I rewrote the DLL test suite in the Go compiler in order to provide good
tests. A patch was submitted a couple months ago, but due to the 1.8
release cycle the core developers did not review it. I am still waiting on
that review. Once that review is complete I can integrate the Windows DLL
changes (which are quite small, really) and make sure it passes the test
suite.
I will try to update my github repository this weekend with the latest Go
source, and then apply my patches on top of it. That way anyone else can
download it and test it.
The test rewrites are at: https://go-review.googlesource.com/#/c/33579/
I can integrate the Windows DLL
change
I would like my changes to fix issue #10776 to go in first.
The test rewrites are at: https://go-review.googlesource.com/#/c/33579/
I will review this when I have free time. But Ian would have to look at it too.
Alex
I will try to update my github repository this weekend with the latest Go source, and then apply my patches on top of it. That way anyone else can download it and test it.
Hi Christopher (@nadiasvertex) - would you mind pushing your DLL implementation code to a github repo or branch so we can work with it before the tests/official merge happen?
Thanks!
Jason
@nadiasvertex: it would be extremely useful to have this for a project I am working on. DLL support in -buildmode
is a deal breaker for that project so having this merged would be fantastic. Let me know if I can help in any ways or simply give it a shot before it's merged.
I have to apologize again, I've been so busy I have forgotten this. I will
make a real effort this weekend to get it tidied up so others can look at
it.
On Tue, Apr 4, 2017 at 12:10 AM Jonathan Stoikovitch <
[email protected]> wrote:
@nadiasvertex https://github.com/nadiasvertex: it would be extremely
useful to have this for a project I am working on. DLL support in
-buildmode is a deal breaker for that project so having this merged would
be fantastic. Let me know if I can help in any ways or simply give it a
shot before it's merged.—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/golang/go/issues/11058#issuecomment-291388305, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AB8JvO5UufSbbMfqkqWZe0PZmP45afxwks5rscLAgaJpZM4E3dB1
.
@nadiasvertex Does your fix add any limitations to the use of CGo etc? Also is there a timeframe on a branch or fork with the fix being accessible?
Correct me if I'm wrong but I don't think @nadiasvertex is solving the tls issue which wood prevent a go library to he used as a dynamic library on Windows
@mattetti Oh, I must of misunderstood. I only skim read the above comments.
This fix is intended to allow you to create a DLL from Go code directly
from the tool. It doesn't fix any of the underlying problems with having
multiple Go DLLs linked into a single process. My understanding is that
this problem exists for all platforms. In any case, there is no consensus
on what the "right" solution to the problem is.
On Fri, Apr 7, 2017 at 4:58 AM avatarscape notifications@github.com wrote:
@mattetti https://github.com/mattetti Oh, I must of misunderstood. I
only skim read the above comments.—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/golang/go/issues/11058#issuecomment-292481410, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AB8JvKXSDhMqKu8JBYwX_n5x9fW_h3xzks5rtfrRgaJpZM4E3dB1
.
@nadiasvertex Does this also fix buildmode=plugin?
Nope. The plugin mode was released with 1.8 and only works on Linux.
However, if they solved the TLS problem generically then it might not be
much work to add it.
On Fri, Apr 7, 2017, 9:45 AM avatarscape notifications@github.com wrote:
@nadiasvertex https://github.com/nadiasvertex Does this also fix
buildmode=plugin?—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/golang/go/issues/11058#issuecomment-292540108, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AB8JvGOg6M4Bmz6Pcnnh2VTaZG7qmT2Wks5rtj3ngaJpZM4E3dB1
.
If you solved the issue, I can work the plugin feature.
last time I checked the TLS problem was still there on Mac and Windows. I used a hack to get around it but it's unreliable and someone should really properly implement TLS based on the OS specs. AFAIK, nobody is working on it.
@nadiasvertex the community can chip in with fixing, cleanup and polish. If you don't mind go ahead and push even broken in-progress files on a branch; wherever things are at now. We don't want to make any more work for you; but would just like to pickup from wherever you left off, even if its not fully ready or if it doesn't build.
@glycerine Last I checked it actually does work, you just can't link multiple Go DLLs into the same binary. I will try to make some time this weekend to update my fork of the Go compiler with the changes for DLL so people can test.
I have a review open for some changes to the DLL compiler tests that has a number of comments I need to address. Then I can submit the code for the DLL fix. However, as I said, I will try to get everything into my github repo so others can test it. That would be very valuable.
With respect to TLS, there is a very long conversation in this thread about that. However, now that the plugin API seems to have been established, it may be possible to revisit the matter.
For people who are concerned about the issue of linking multiple shared libraries into a process: I've been loading buildmode=c-shared libraries with both Ruby and Node.js, and I've gotten around the problem fairly easily by always loading them in a child process. It will be nice to eventually not have to deal with that, but for now it's seemingly a decent workaround.
That's a brilliant idea. I was thinking of something similar.
On 7 Apr 2017 23:54, "Dane Schneider" notifications@github.com wrote:
For people who are concerned about the issue of linking multiple shared
libraries into a process: I've been loading buildmode=c-shared libraries
with both Ruby and Node.js, and I've gotten around the problem fairly
easily by always loading them in a child process. It will be nice to
eventually not have to deal with that, but for now that seems to be a
pretty solid workaround.—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/golang/go/issues/11058#issuecomment-292670613, or mute
the thread
https://github.com/notifications/unsubscribe-auth/ABIw2mD8wyxgMEIUs2pf11rYwTB1CjwDks5rtr6xgaJpZM4E3dB1
.
If someone wants to help @nadiasvertex in this venture, they could take over his https://go-review.googlesource.com/#/c/33579/. The CL converts misc/cgo/testcshared/test.bash into Go, so we could run it on windows. I don't see Go Team accepting any DLL changes without a test.
Alex
I have rebased my changes against go 1.8.1 here:
https://github.com/nadiasvertex/go/tree/win-shared-1.8
I have not tested to make sure this works against 1.8. I compiled it, but that is all. I appreciate the offers of help testing it, and would like to encourage anyone interested in this functionality to test and report their results.
Just a heads up for anyone interested in dll's.... Exe's should also be able to export functions too, so that the dll can call the function located in an exe, so, theoretically any EXE should be possible to just rename as .dll and load it into any program, if that exe has an exported function (this is a cool feature/trick).
Not sure if just making exe's export functions is easier than creating an actual DLL. Just might save one step... if all that is needed is to create an exe with an exported function, then rename it to a dll. It wouldn't be a true and pure dll but might still work exactly like one. Can't remember though if exe's have some different behavior like a main() function that dll's don't have, my memory is not well on this.
Also there is this post:
http://stackoverflow.com/questions/40573401/building-a-dll-with-go-1-7
...which uses GCC to create a DLL from .a and .h files generated by go. Probably been discussed before somewhere in this thread, I don't know.
and this:
https://groups.google.com/forum/#!topic/golang-dev/ckFZAZbnjzU
Hi z505, that stackoverflow that you are referring to is talking about "re-export in Hello2.c"
Do you understand that? Can you explain what is meant there?
Yes I just understood it a few minutes ago when I loaded a GoLang dll into freepascal/lazarus and successfully exported a function that returns the meaning of life (42 as an int32 type).
The code is here:
https://github.com/z505/goDLL
Basically you make a C program (boilerplate) to trick GCC into exporting Go code, since go can generate .a and .h files that a C program can import and use
The C program looks like so:
https://github.com/z505/goDLL/blob/master/goDLL.c
The golang code looks like so where you put your exports:
https://github.com/z505/goDLL/blob/master/exportgo.go
Then you use a program written in any language to import the dll at run time. I used fpc/lazarus but you use whatever you want.
https://github.com/z505/goDLL/blob/master/fpcprog.lpr
PROBLEMS: unloading the dll dynamically at your will, may cause issues, as the Go Runtime, AFAIK cannot just be unloaded at whim or at will... This is being worked on here likely:
https://github.com/golang/go/issues/11100
Unloading a dll any time you wish during the exe lifetime, is very useful for plugin systems that dynamically unload plugins at whim.
Thanks very much, I understand what to do. Very kind that you let me know.
Works fantastic, thanks.
@nadiasvertex: we finally got around to try out your fork. we're getting an error: buildmode=c-shared not supported on windows/amd64
which seems to come from here. Is there a specific flag to pass to go build
other than -buildmode=c-shared
or is it that windows/amd64
is missing from the switch/case above?
I might have missed that in the patch! Let me double check!
On Thu, May 11, 2017, 1:03 AM Jonathan Stoikovitch notifications@github.com
wrote:
@nadiasvertex https://github.com/nadiasvertex: we finally got around to
try out your fork. we're getting an error: buildmode=c-shared not
supported on windows/amd64 which seems to come from here
https://github.com/nadiasvertex/go/blob/066eb2b2eecf4bcdf814d86f3c64ad3c4b3d9593/src/cmd/go/build.go#L362.
Is there a specific flag to pass to go build other than
-buildmode=c-sharedor is it that windows/amd64 is missing from the
switch/case above
https://github.com/nadiasvertex/go/blob/066eb2b2eecf4bcdf814d86f3c64ad3c4b3d9593/src/cmd/go/build.go#L356-L360
?—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/golang/go/issues/11058#issuecomment-300682950, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AB8JvEBNzQfzaEM0n-I_Asty7yyYnMg9ks5r4paHgaJpZM4E3dB1
.
@jstoiko: I fixed this in the github repo. Looks like this is new code in 1.8.
I pull the brand by @nadiasvertex, is there any doc to explain how to use the the feature?
go build --buildmode=c-shared -o test.dll
Just wanted to check in on the status of this. What will it take to get over the line?
@nadiasvertex I also want to echo @janckerchen's question about cross-compiling. Could this work with @karalabe's xgo? This is how I'm outputting a full set of darwin/unix binaries currently.
Would be so great to have this final piece of the puzzle in place, both for a specific project I'm working on and also in general. A fully cross-platform buildmode=c-shared makes it possible to do amazing things in terms of building out libraries in many languages that share a common fast golang core.
If there was some testing done on my GitHub fork,that would give us a lot
more confidence. I am making progress on rewriting the unit test for shared
libraries. It had gone through one round of feedback already. I need to
integrate the new feedback. I may have some more time this weekend.
I have not looked at xgo, but I will try to.
On Fri, Jun 16, 2017, 3:09 PM Dane Schneider notifications@github.com
wrote:
Just wanted to check in on the status of this. What will it take to get
this over the line?@nadiasvertex https://github.com/nadiasvertex I also want to echo
@janckerchen https://github.com/janckerchen's question about
cross-compiling. Could this work with @karalabe
https://github.com/karalabe's xgo https://github.com/karalabe/xgo?
This is how I'm outputting a full set of cross-platform binaries currently.Would be so great to have this final piece of the puzzle in place, both
for a specific project I'm working on and also in general. A fully
cross-platform buildmode=c-shared makes it possible to do amazing things in
terms of building out libraries in many languages that share a common fast
golang core.—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/golang/go/issues/11058#issuecomment-309109977, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AB8JvBLH78kpvqvu8fV15oXmSv7LDyJ-ks5sEtLVgaJpZM4E3dB1
.
sorry , confusing to understand the state of this issue and if there's an ETA in the horizon. It clearly does not work on Win 10 , under GO 1.8.3 i.e using -buildmode=c-shared
thanks
@amiracam: you may want to give @nadiasvertex's fork a try, see link above.
I recently got a patch accepted that was a prerequisite for this one. I
maintain a test branch in GitHub with this change. I'm hoping people will
test it and let me know the results.
Either way, I am going to start updating testcshared to work with Windows
in order to verify that this patch works.
On Aug 22, 2017 14:57, "amiracam" notifications@github.com wrote:
sorry , confusing to understand the state of this issue and if there's an
ETA in the horizon. It clearly does not work on Win 10 , under GO 1.8.3 i.e
using -buildmode=c-shared
thanks—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/golang/go/issues/11058#issuecomment-324119589, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AB8JvGR1BFE29p6h-cVaGhAl_OAI9HzGks5sayScgaJpZM4E3dB1
.
@nadiasvertex btw, on your repo , cloning results in a different repo source content than downloading the zip for the repo. Also it seems that its the zip that contains the code that addresses the Windows support issue
@amiracam As mentioned above, the fix is in a branch of the repo
I am planning on rebasing against 1.9 soon, so I will also add a new branch there. I'm not trying to maintain a replacement, though, just something for testing.
@nadiasvertex I'd be happy to give your branch a shot after a 1.9 rebase! Let us know :)
I'll grab a windows box later today :+1:
understood, given that I navigate to said branch via the provided link, the "CLONE" button copy to a clipboard feature generates a git url that clones a repo that does not contain the mod in question whereas from the same page the download zip feature will generate an archive of the repo that does contain the mod in question.
We used the zip version and preliminary hello world-ish test are successful, will continue to work with this since we want to refactor a GO lib that we are developing into our many, many C programs running on Windows 64 bit, will report back any issues we encounter , thanks
@nadiasvertex btw, not sure if related to your changes but the generated .h has 3 errors:
message: 'identifier "__SIZE_TYPE__" is undefined'at: '29,9' source: ''
message: 'expected a ';''at: '32,24' source: ''
message: 'expected a ';''at: '33,25' source: ''
@amiracam that is odd, since I see the patches in the commit list for that branch. However, I will look into it. Thanks for pointing this out!
With respect to the generated .h file, my patch doesn't have anything to do with that. They are compiler and linker changes only. I didn't touch the C FFI layer. I wonder if you are using a supported C compiler.
@nadiasvertex , yes agreed, i had checked out the patches from within the GIT page and they do indeed show up there, then I took a shot at looking at the zip.
I'm on Win 10 64 bit ,I believe its using GCC and indeed I use GCC to compile the project
@nadiasvertex , so I can generate a .h and .dll and can import that into a c project and do a build which will generate an executable without any errors, however when I try to run the executable it runs without error but does nothing ie. it should have print to console, I can tomorrow morning submit my very trivial experiment, BTW, I'm using the TDM-GCC distribution
@nadiasvertex
GO code:
package main
import "fmt"
import "C"
func main(){}
//export HelloWorld
func HelloWorld() {
fmt.Println("Hello")
}
which via
go build -buildmode=c-shared -o hello.dll hello.go
generates a hello.dll as well as hello.h
btw, also tried naming the output hello.so
C code (driver.c) :
int main(){
setvbuf(stderr, NULL, _IONBF, 0);
FILE *fn = fopen64("hello.txt", "w");
fprintf(fn, "Hello there ");
fclose(fn);
printf("Hello \n");
HelloWorld();
return 0;
}
for which i use
gcc -o hello.exe driver.c hello.so
this will generate hello.exe
which when executed will do absolutely nothing
if I comment out the exported HelloWorld function and as well the include for the hello.h and compile , I do get a working exe but of course thats just a sanity check. Something about linking the shared object / dll generates a silent non-working executable.
I'm new to C and well pretty new to GO as well, how can I go about debugging this ?
thanks
@amiracam Have you tried this under Linux or macOS? If so, does it work there?
yes sir, works fine on MacOS Sierra , have not yet setup GO under Ubuntu but if it works on the Mac pretty sure it would work on Linux, ultimately we want to be cross platform Windows / Linux , I'll start testing there as well
@amiracam okay. That is useful to know. I will look into it.
@nadiasvertex
perhaps something else that is useful is that we are having success using buildmode=c-archive , basically following the instructions here:
https://stackoverflow.com/questions/40573401/building-a-dll-with-go-1-7
but using a clean GO 1.8.3 distribution
on Win 10
we can generate a working executable
@nadiasvertex hi, do you now have a 1.9 rebase? thanks
@ianlancetaylor
I am trying to make c-shared work on windows,. There is a codegenArg variable in cmd/go/internal/work/build.go:BuildModeInit - it controls the linker's -installsuffix argument. The codegenArg is blank for darwin, but it is set to "-shared" for other OSes. What should I set it for windows? It seems to work either way. Thank you.
Alex
Change https://golang.org/cl/68410 mentions this issue: misc/cgo/testcshared: skip all but TestExportedSymbols on windows
@alexbrainman The codegenArg
is passed to the compiler and assembler, and also sets the install suffix. The main purpose of the argument is to change the code generation. If you don't need to pass any special argument to the compiler on Windows--if the normal code works fine in a shared library--then don't set codegenArg
. The bit about installsuffix is to separate the packages compiled with -shared
from the packages compiled in the normal way. Hope that makes sense.
Hope that makes sense.
It does make sense. Thank you for explaining. That is how I read the code.
It just both codegenArg=""
and codegenArg="-shared"
worked for compiler and assembler, and I need to pick one value. And the value I pick will affect where built package will be stored by the linker. For example misc/cgo/testcshared/cshared_test.go has some code https://github.com/golang/go/blob/d153df8e4b5874692f4948e9c8e10720446058e3/misc/cgo/testcshared/cshared_test.go#L43:L54 that needs to change to support my change. I wonder who else will be affected? What happens if one day we need to change codegenArg
value? Darwin is already in that boat. So I will go with codegenArg=""
for the moment as per your suggestion.
Alex
Change https://golang.org/cl/68770 mentions this issue: misc/cgo/testcshared: delete testp0.exe not testp0 file
Change https://golang.org/cl/69091 mentions this issue: cmd/dist, cmd/link, cmd/go: make c-shared work on windows
Change https://golang.org/cl/69090 mentions this issue: misc/cgo/testcshared: use correct install directory on windows
Most helpful comment
Probably, most of windows users are looking for a way to generate dlls.:
or