Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

runtime/cgo: calling DLL's export function crashes on Windows #9457

Closed
eahydra opened this issue Dec 27, 2014 · 27 comments
Closed

runtime/cgo: calling DLL's export function crashes on Windows #9457

eahydra opened this issue Dec 27, 2014 · 27 comments

Comments

@eahydra
Copy link

eahydra commented Dec 27, 2014

go1.4 windows amd64
OS: Windows 7 sp1 64Bit

I write a dll that exports some function. then I invoke these function in go through syscall. But it crashed. The error:
Exception 0xc00000fd 0x1 0x33000 0x7fef5017237
PC=0x7fef5017237
signal arrived during cgo execution

syscall.(_Proc).Call(0xc082004620, 0xc082049f58, 0x2, 0x2, 0xc, 0xc0820045c0, 0x0, 0x0)
c:/go/src/syscall/dll_windows.go:132 +0x36e
syscall.(_LazyProc).Call(0xc082007d40, 0xc082049f58, 0x2, 0x2, 0x4ff330, 0xc, 0x0, 0x0)
c:/go/src/syscall/dll_windows.go:279 +0x74
main.main()
D:/GoDll/godll.go:27 +0x1a1
rax 0x40370
rbx 0xc082049da8
rcx 0xc0820045a0
rdx 0xc0820045c0
rdi 0x7fffffde000
rsi 0xc082049e00
rbp 0x57ab00
rsp 0x6fe08
r8 0x0
r9 0xc082007d40
r10 0x2f000
r11 0x33000
r12 0x9
r13 0x0
r14 0x0
r15 0x0
rip 0x7fef5017237
rflags 0x7fe00010287
cs 0x33
fs 0x53
gs 0x2b

When I change the local variable that in Dll from stack to heap, the crash is disappeared, the result is correct.

The test code is at here: https://www.dropbox.com/s/ggxpmey59zex4lg/GoDll.zip?dl=0
If you haven't visual studio, you can download the execute files: https://www.dropbox.com/s/3rnan6sf1killge/GoDll_Binary.zip?dl=0

You can use VS2013 compile this.

@mikioh mikioh changed the title cgo: Call Dll's export function crashed on Windows runtime/cgo: calling DLL's export function crashes on Windows Dec 27, 2014
@alexbrainman
Copy link
Member

Do not have VS2013 to try it.

Alex

@eahydra
Copy link
Author

eahydra commented Dec 30, 2014

@alexbrainman
Copy link
Member

Thank you for DLL. Unfortunately I don't see anything new. Your DLL raises 0xC00000FD EXCEPTION_STACK_OVERFLOW. I don't know why - I don't understand your C++ code. Maybe it needs more stack space. Go is using 64K for its threads stack. Maybe that is not enough.

You can try and change your program so that it uses cgo. Programs built with cgo reserve more stack space: 1M for 386 and 2M for amd64. Just to see if that solves your problem.

I don't see how we can help you here. So closing this issue. Feel free to comment.

Alex

@eahydra
Copy link
Author

eahydra commented Dec 30, 2014

@alexbrainman If you just run the test.exe, everything is OK! Because the test.exe is compile by vs2013, it's written by C++. But, if you use go build with the file that godll.go and run it, you can got the exception。

@alexbrainman
Copy link
Member

Like I said earlier. Go decide how large thread stacks are. 64K is enough for Go. Obviously not enough for your DLL. I suspect your test.exe creates threads with bigger stacks.

Alex

@minux
Copy link
Member

minux commented Dec 30, 2014

I think i understand the underlying problem now.

https://go.googlesource.com/go/+/master/src/cmd/ld/pe.c#685

We disabled the automatic stack expansion for the threads in the
PE header, so if cgo code uses too much stack, the program will
crash. The current limit is 1MB for windows/386 and 2MB for
windows/amd64.

@eahydra
Copy link
Author

eahydra commented Dec 30, 2014

@alexbrainman If there is a dll that export function that need more than 64K stack size, and It's written by third party partner, I use go's syscall indirectly call it, It just crash. But it can work ok with C/C++. So Is it still not a problem?

Before I add the Issue, I have talked with @minux , as @minux said, When I use syscall to call export function, the stack will use the system thread's stack. If so, there must be OK.

@eahydra
Copy link
Author

eahydra commented Dec 30, 2014

@minux The test code just use 4 * 64K size. It's not exceed the default stack size 1MB on windows/386 or 2MB for windows/amd64

@minux
Copy link
Member

minux commented Dec 30, 2014

Aha, I think I found the culprit.

Look at the code I quoted in last comment, if the code is not using cgo
(iscgo == 0),
the stack limit is 64KB.

However, although using syscall to call external DLLs use the same
mechanism as
using cgo, if the program does not actually import "runtime/cgo", the
linker will still
use the smaller stack limit.

I think just adding a blank import of "runtime/cgo" will fix your program.

I'm not sure what to do here.

@eahydra
Copy link
Author

eahydra commented Dec 30, 2014

Holy shit! Just add a blank import of "runtime/cgo", It works OK....
Why?

@minux
Copy link
Member

minux commented Dec 30, 2014

If the linker doesn't find "runtime/cgo" being imported, it will treat the
program as not
using cgo, in other words, "pure" Go, so it will set the maximum stack to
64KB.

This is yet another subtle difference between using cgo to call DLL
functions and just
using the syscall package.

I don't know anything that we could do here, so I guess we should just live
with this
limitation. @alexbrainman, what's your opinion on this?

@alexbrainman
Copy link
Member

No one else complained about this yet, so I don't think it is big problem. But I think we could increase stack size. As far as I remember we needed to have as many threads as possible in the early Go days. Our net package didn't use IOCP back then, so we had to use separate threads for different connections. windows/386 was our only platform so we could only have 2000 threads if their stack was 1M. I don't think this is an issue any more, especially on windows/amd64. Perhaps we could increase stacks now, but by how much?

Perhaps I forget something else. But, I am sure, if we change stack sizes we will discover it :-)

Alex

@minux
Copy link
Member

minux commented Jan 1, 2015

My only concerns for increasing the stack limit is that it might reduce the
maximum number of OS threads usable.

I think we can increase the reserved stack value without such concerns?
(I don't know why we limit the stack reserve value to 64kB in the first
place?
windows shouldn't commit those space anyway. am i missing something?)

@alexbrainman
Copy link
Member

Yes increasing stack limit will reduce the maximum number of OS threads. This is for reserved stack value. We have small reserved stacks to allow for more threads. Windows would not commit all reserved stacks until they are actually read/written from/to.

@minux
Copy link
Member

minux commented Jan 2, 2015

Actually, from my experiments, it seems windows doesn't fully commit the
commit stack size either.

Moreover, the stack limit in PE header only affects the main thread, and
CreateThread only allows us to specify either stack reservation or stack
commit, but not both. my tests specified the stack reservation to 1MB, and
each additional thread uses ~10KB, much less the stack commit size of
64K specified in the PE header (this value is very likely irrelevant in this
case)

As long as we don't use much stack space, I don't think increasing the size
of stack reservation won't reduce the maximum number of OS thread allowed.

@minux
Copy link
Member

minux commented Jan 2, 2015

My test program looks like this:
http://play.golang.org/p/p_STYyr-qY

package main

/*
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <process.h>

__stdcall
static void
threadproc(void *p)
{
for (;;) Sleep(2000);
}

void
do(int nthread)
{
enum { MaxThread = 200000 };
int i;
static uintptr_t thread_id[MaxThread];
if(nthread > MaxThread)
nthread = MaxThread;
for(i=0; i<nthread; i++)
//thread_id[i] = (uintptr_t)CreateThread(0, 0x100000, (void_)threadproc, NULL, STACK_SIZE_PARAM_IS_A_RESERVATION, 0);
thread_id[i] = (uintptr_t)CreateThread(0, 0x10000, (void_)threadproc, NULL, STACK_SIZE_PARAM_IS_A_RESERVATION, 0);
for(i=0; i<nthread; i++) {
WaitForSingleObject((HANDLE)thread_id[i], INFINITE);
CloseHandle((HANDLE)thread_id[i]);
}
}
*/
import "C"
import "flag"

var n = flag.Int("n", 500, "number of threads to create")

func main() {
flag.Parse()
C.do(C.int(*n))
}

No matter the stack reservation size (64KB or 1MB), the maximum number of
thread that I can create on my windows xp VM is about 1230.

Do you spot any problems in this test?

@minux minux reopened this Jan 2, 2015
@alexbrainman
Copy link
Member

Re your test program http://play.golang.org/p/p_STYyr-qY

Why does it use cgo? I though we're discussing/changing non-cgo scenario..

@minux
Copy link
Member

minux commented Jan 2, 2015

There is a typo in my test program (I named the test function "do").
Update: http://play.golang.org/p/z3_cUpH7wG

@alexbrainman
Copy link
Member

Can you, please answer my question above.

@minux
Copy link
Member

minux commented Jan 2, 2015

I created another test using dll + syscall, but the result is the same,
1233 threads max
on 32-bit windows xp.

thrs.c: http://play.golang.org/p/IRoFkJ871l
thrs2.go: http://play.golang.org/p/WTWHbf7OW7

To build:
gcc -o thrs.dll thrs.c -shared
go build thrs2.go

To run
thrs2 -n= -l=true/false
-l will switch between large stack or small stack

@minux
Copy link
Member

minux commented Jan 3, 2015

some of the discussion is on the CL: https://golang.org/cl/2237

@alexbrainman used http://play.golang.org/p/TKkNSAVYnw to prove that on 64-bit system,
after applying the CL, 32-bit Go program could create significantly less OS threads.

However, it seems that even without the CL, my Windows XP VM is allocating 1MB stack
for each thread. See the screen shot below:
1mb_stack

@alexbrainman
Copy link
Member

Running http://play.golang.org/p/TKkNSAVYnw on my 32-bit windows xp:

C:\go\path\mine\src\foo>go run main.go 2> NUL
...
9667
9668
9669
9670

C:\go\path\mine\src\foo>

but after Change 2237 is applied:

C:\go\path\mine\src\foo>go run main.go 2> NUL
...
1233
1234
1235
1236

C:\go\path\mine\src\foo>

So I can see about 8 times less threads after the change. Considering we made mistake about current threads are 64K - they are actually 128K except first thread (see runtime.newosproc), my results fit my understanding of this situation perfectly.

@minux I am not sure why your threads are bigger. Perhaps your system is configured in a particular way. I am running stock standard windows xp system on real hardware. What does http://play.golang.org/p/TKkNSAVYnw print ono your system (on Go tip)?

Alex

@minux
Copy link
Member

minux commented Jan 3, 2015

I did some more experiments, here is the result:
Using this test program: http://play.golang.org/p/GSalHRoxkY

It seems the culprit is the stack commit size in PE header, if I lower it
to 0x1000,
and then without this CL, I can create 18771 threads. (18771 * 64K = 1.23
GB)

After this CL, i can only create 1233 threads, as expected.

@minux
Copy link
Member

minux commented Jan 3, 2015

The stack reservation size is rounded to multiples of 64KB, so
64KB is the smallest possible stack reservation.

Update: in my last comment, I did modify the stack reservation in
os1_windows.go to 64KB, instead of the 128KB.

I will just update my CL to set the stack commit size to 4KB.

And I will close this issue as unfortunate, the workaround is still to
blank import "runtime/cgo" to increase the thread stack size.

@minux
Copy link
Member

minux commented Jan 3, 2015

btw, all.bat still passes after reducing the stack size to 64KB, but
as the default maximum OS thread limit is 10k, and 128KB stack
could get to ~9K threads, I don't think we need to reduce the
default stack size to 64KB.

@minux
Copy link
Member

minux commented Jan 3, 2015

Close as unfortunate.

Workarounds:

  1. use cgo to load DLLs
  2. add a blank import of "runtime/cgo" if the DLL needs more than 128KB of stack.

@minux minux closed this as completed Jan 3, 2015
@gopherbot
Copy link

CL https://golang.org/cl/2237 mentions this issue.

mk0x9 pushed a commit to mk0x9/go that referenced this issue Apr 29, 2015
…ntime

With 128KB stack reservation, on 32-bit Windows, the maximum number
threads is ~9000.

The original 65535-byte stack commit is causing problem on Windows
XP where it makes the stack reservation to be 1MB despite the fact
that the runtime specified 128KB.

While we're at here, also fix the extra spacings in the unable to
create more OS thread error message: println will insert a space
between each argument.

See golang#9457 for more information.

Change-Id: I3a82f7d9717d3d55211b6eb1c34b00b0eaad83ed
Reviewed-on: https://go-review.googlesource.com/2237
Reviewed-by: Alex Brainman <alex.brainman@gmail.com>
Run-TryBot: Minux Ma <minux@golang.org>
@golang golang locked and limited conversation to collaborators Jun 25, 2016
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants