Skip to content

cmd/link: shared object constructor functions are not invoked with internal linker and musl libc #28909

@Hollerberg

Description

@Hollerberg

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.compiler/runtimeIssues related to the Go compiler and/or runtime.help wanted

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions