go: cmd/link: loading c-shared into Go program crashes on Windows

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

go version devel +bb0bfd002a Tue Oct 10 01:02:27 2017 +0000 windows/amd64

Does this issue reproduce with the latest release?

Only tip

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

set GOARCH=amd64
set GOBIN=
set GOEXE=.exe
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOOS=windows
set GOPATH=c:/dev/go
set GORACE=
set GOROOT=c:\go
set GOTOOLDIR=c:\go\pkg\tool\windows_amd64
set GCCGO=gccgo
set CC=gcc
set GOGCCFLAGS=-m64 -mthreads -fmessage-length=0 -fdebug-prefix-map=C:\Users\mattn\AppData\Local\Temp\go-build049037059=/tmp/go-build -gno-record-gcc-switches
set CXX=g++
set CGO_ENABLED=1
set CGO_CFLAGS=-g -O2
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-g -O2
set CGO_FFLAGS=-g -O2
set CGO_LDFLAGS=-g -O2
set PKG_CONFIG=pkg-config

What did you do?

foo.go

package main

import "C"
import (
	"fmt"
)

//export Foo
func Foo() {
	fmt.Println("foo")
}

func main() {
}

creating foo.dll

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

call Foo in foo.dll

package main

import (
	"syscall"
)

func main() {
	syscall.NewLazyDLL("foo.dll").NewProc("Foo").Call()
}

What did you see instead?

output foo

What did you expect to see?

panic

fatal error: unexpected signal during runtime execution
[signal 0xc0000005 code=0x0 addr=0xfffffffac5feddfe pc=0x6ae92064]

goroutine 1 [running]:
runtime.throw(0x6af45d8d, 0x2a)
	c:/go/src/runtime/panic.go:616 +0x88 fp=0xc042033730 sp=0xc042033710 pc=0x6aea9398
runtime.sigpanic()
	c:/go/src/runtime/signal_windows.go:155 +0x170 fp=0xc042033760 sp=0xc042033730 pc=0x6aeb9da0
runtime.heapBitsSetType(0xc042034020, 0x10, 0x10, 0x6af1f520)
	c:/go/src/runtime/mbitmap.go:921 +0x544 fp=0xc0420337b8 sp=0xc042033760 pc=0x6ae92064
runtime.mallocgc(0x10, 0x6af1f520, 0x1, 0xc04200c2c0)
	c:/go/src/runtime/malloc.go:740 +0x582 fp=0xc042033858 sp=0xc0420337b8 pc=0x6ae8f202
runtime.convT2Estring(0x6af1f520, 0xc0420338e8, 0x40, 0x6af52100)
	c:/go/src/runtime/iface.go:357 +0x71 fp=0xc042033890 sp=0xc042033858 pc=0x6ae8d361
runtime.preprintpanics(0xc042033978)
	c:/go/src/runtime/panic.go:402 +0xe6 fp=0xc042033908 sp=0xc042033890 pc=0x6aea8896
panic(0x6af26ee0, 0x6afb2ec0)
	c:/go/src/runtime/panic.go:543 +0x41b fp=0xc0420339b0 sp=0xc042033908 pc=0x6aea8edb
runtime.panicmem()
	c:/go/src/runtime/panic.go:63 +0x59 fp=0xc0420339d0 sp=0xc0420339b0 pc=0x6aea7bc9
runtime.sigpanic()
	c:/go/src/runtime/signal_windows.go:161 +0x83 fp=0xc042033a00 sp=0xc0420339d0 pc=0x6aeb9cb3
os.(*File).write(0x0, 0xc04200e068, 0x4, 0x8, 0x0, 0x0, 0xc2034028)
	c:/go/src/os/file_windows.go:224 +0x29 fp=0xc042033a48 sp=0xc042033a00 pc=0x6aeec0a9
os.(*File).Write(0x0, 0xc04200e068, 0x4, 0x8, 0x0, 0x0, 0x0)
	c:/go/src/os/file.go:140 +0x73 fp=0xc042033ac0 sp=0xc042033a48 pc=0x6aeeb943
fmt.Fprintln(0x6af520a0, 0x0, 0xc042033ba8, 0x1, 0x1, 0x4b3600, 0xc042033c48, 0x3)
	c:/go/src/fmt/print.go:255 +0x89 fp=0xc042033b28 sp=0xc042033ac0 pc=0x6af08109
fmt.Println(0xc042033ba8, 0x1, 0x1, 0x0, 0x4b3600, 0xc042033c90)
	c:/go/src/fmt/print.go:264 +0x5e fp=0xc042033b78 sp=0xc042033b28 pc=0x6af081ce
main.Foo()
	C:/dev/go-sandbox/dll/dll.go:10 +0x64 fp=0xc042033bc8 sp=0xc042033b78 pc=0x6af0f334
main._cgoexpwrap_e001a1a948c6_Foo()
	b002/_cgo_gotypes.go:45 +0x27 fp=0xc042033bd8 sp=0xc042033bc8 pc=0x6af0f2b7
runtime.call32(0x0, 0x22fc90, 0x22fe0f, 0x0)
	c:/go/src/runtime/asm_amd64.s:509 +0x42 fp=0xc042033c08 sp=0xc042033bd8 pc=0x6aecde12
runtime.cgocallbackg1(0x0)
	c:/go/src/runtime/cgocall.go:316 +0x1aa fp=0xc042033c88 sp=0xc042033c08 pc=0x6ae82c0a
runtime.cgocallbackg(0x0)
	c:/go/src/runtime/cgocall.go:194 +0xef fp=0xc042033cf0 sp=0xc042033c88 pc=0x6ae829bf
runtime.cgocallback_gofunc(0x402150, 0x44ac60, 0x4b3948, 0xc042033d50)
	c:/go/src/runtime/asm_amd64.s:762 +0xaf fp=0xc042033d10 sp=0xc042033cf0 pc=0x6aecf46f

goroutine 1 [runnable, locked to thread]:
syscall.Syscall(0x775b54e0, 0x3, 0x3, 0x1, 0x0, 0x0, 0x0, 0x0)
	c:/go/src/runtime/syscall_windows.go:171 +0xf9
syscall.SetHandleInformation(0x3, 0x1, 0x30, 0x6af31900)
	c:/go/src/syscall/zsyscall_windows.go:936 +0x6b
syscall.CloseOnExec(0x3)
	c:/go/src/syscall/exec_windows.go:125 +0x3b
syscall.getStdHandle(0xfffffffffffffff6, 0x6af41949)
	c:/go/src/syscall/syscall_windows.go:370 +0x45

As far as I can see, panic occur at (*File) write(b []byte)

func (f *File) write(b []byte) (n int, err error) {
	n, err = f.pfd.Write(b)
	runtime.KeepAlive(f)
	return n, err
}

Maybe, poller in dll is conflicting with main process.

About this issue

  • Original URL
  • State: open
  • Created 7 years ago
  • Reactions: 11
  • Comments: 44 (33 by maintainers)

Commits related to this issue

Most upvoted comments

I’m also leaning towards a TLS issue. Will give it a try.

I have a (yet-not-submitted) change that fixes this issue by using a proper TLS slot instead of the arbitrary pointer memory. Still fighting a couple of sharp edges, I’ll try to submit it for review during the next week if I time allows.

5 years… and were all still waiting for this to get sorted…

I don’t think this issue is constrained to Windows only. It also occurs on Darwin. Reproducing isn’t the same, but any loading a c-shared dynamic library from Go will have issues.

@cherrymui

Regardless of your decision if calling Go DLL from Go executable, I think you should consider using TlsAlloc Windows API to allocate TLS slots ( what https://go.dev/cl/431775 does ).

Currently Go uses unsupported way to store TLS register ( https://github.com/golang/go/issues/22192#issuecomment-504673356 ). If any DLL imported by Go program decides to use the same unsupported trick, chaos ensues. Go program does not have control of what DLLs it imports, because these DLLs are installed by companies IT departments and people who don’t realise what they are installing (think of antivirus and any other tricky software).

So there is a good chance we fix some unexplained bugs by replacing current TLS implementation with TlsAlloc.

Just MHO.

Alex

The intent of c-shared is to permit loading a Go DLL into a C program. You seem to be using c-shared to load a Go DLL into a Go program. That is problematic. The expectation is that you would be using -buildmode=plugin or -buildmode=shared here.

But I can see why Windows developers would expect this to work, especially since neither -buildmode=plugin nor -buildmode=shared currently work on Windows.

But I don’t know how to make it work. I’m not sure what we should do.

@alexbrainman @qmuntal I confirmed the CL fixes my problem. Thank you.

@aarzilli @derekparker take a look at https://go.dev/cl/431775. If it is finally merged, Delve assumption about G always being at 0x28 bytes from TLS will no longer be true. It will have to be inferred using runtime.tls_g like it is already happing in windows/arm64.

Yeah, I’m not opposing using TlsAlloc, which I think is probably a good thing to do by itself. I’m just saying this doesn’t necessarily make loading c-shared by a Go program (this issue) work. Removed the Hold on the CL. Thanks.

Sorry for being late here (just saw this). But with the current design, I don’t think the c-shared build mode works well (and intended to) with loading from a Go program. Currently it is designed to be the only instance of the Go runtime in the program. It doesn’t know other instances of the Go runtime, and doesn’t do anything to combine the various metadata the runtime needs (some needs to be deduplicated, some needs to be merged, etc.)

It is possible that it works for some simple use cases, if we carefully keep the two Go world completely separated. (And the TLS patch would help on that.) But it wouldn’t be surprised that things would go wrong with more complex programs, especially with passing references around between two Go worlds. I’m not sure it is the right direction to pursue this path.

I suspect this issue might be due to us using unsupported TLS slot.

Our windows-arm port is already doing correct hing - see runtime·save_g and runtime·load_g. But, I still could not copy that code onto amd64. Perhaps amd64 and arm assemblers are different.

@alexbrainman I can confirm that this issue is not reproduced on windows/arm64, at least the basic reproducer in the issue description works well.

I’m also leaning towards a TLS issue. Will give it a try.

I suspect what is happening here is that you cannot have 2 Go runtimes coexist in a single process. In particular the way we access TLS has to be changed to allow 2 or more runtimes in one process. I will let Ian decide what to do here.

Alex