-
Notifications
You must be signed in to change notification settings - Fork 18.4k
Description
What version of Go are you using (go version
)?
1.11.2
$ go version go version go1.11.2 linux/amd64
Does this issue reproduce with the latest release?
yes
What operating system and processor architecture are you using (go env
)?
go env
Output
$ go env GOARCH="amd64" GOBIN="" GOCACHE="/root/.cache/go-build" GOEXE="" GOFLAGS="" GOHOSTARCH="amd64" GOHOSTOS="linux" GOOS="linux" GOPATH="/go" GOPROXY="" GORACE="" GOROOT="/usr/local/go" GOTMPDIR="" GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64" GCCGO="gccgo" CC="gcc" CXX="g++" CGO_ENABLED="1" GOMOD="" CGO_CFLAGS="-g -O2" CGO_CPPFLAGS="" CGO_CXXFLAGS="-g -O2" CGO_FFLAGS="-g -O2" CGO_LDFLAGS="-g -O2" PKG_CONFIG="pkg-config" GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build960632520=/tmp/go-build -gno-record-gcc-switches"
What did you do?
- Create a dynamically linked Go application with the internal linker.
- Create a shared object (DLL) which has a constructor function - e.g. a C function attributed with __attribute__((constructor))
- Use LD_PRELOAD to preload the shared object into the Go application process - e.g LD_PRELOAD=libsotest.so ./myapp
This issue could be the root cause for failing alpine tests in #20120.
I have created docker image with a reproducer for this issue: https://gist.github.com/Hollerberg/5fc64f8abf0f16d4e801c4ad348f21b6
Download the files from the github gist and execute build-and-run-container.sh
. This script will build the container image. Within the docker build process, libpreload.so
is built from preload.c
, and the Go application gogo.go
is built once to gogo-int-linker
using the internal and once to gogo-ext-linker
using the external (system) linker.
Once the docker image is built and started, the two applications can be started with script runtest.sh
.
The shared object constructor function in libpreload.so
will write a message to stdout. Preloading the shared object into gogo-int-linker
will not execute the constructor and therefore no message is printed to console. Repeating this with gogo-ext-linker
, the constructor message is printed to console.
The root cause for this behavior is a subtle difference between musl and glibc dynamic linker behavior. glibc seems to call shared object constructors in dynamic linker context (_dl_start_user
), while musl libc executes constructors in __libc_start_main
called by crt1.c. musl libc dynamic linker loads the application and preloaded/depending shared object to memory. Then it jumps to the application entry point. Typically, the application entry point is the code defined in crt1.c - but not in case when the application has been linked with the internal Go linker - __libc_start_main
is never invoked.
See also Rich Felkers comments on musl libc mailing list
Short background info on why we are preloading shared objects into Go processes - my company provides an application monitoring solution and the shared object pulls diverse data from the executing Go application. Thus, the use case is very relevant to us in order to be able to support musl libc based systems without constraints.
What did you expect to see?
The constructor function in the preloaded shared object should be invoked before Go application main function.
What did you see instead?
The shared object constructor is not invoked at all.