golang/go

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

Open

#28.909 aperta il 21 nov 2018

Vedi su GitHub
 (11 commenti) (4 reazioni) (0 assegnatari)Go (19.008 fork)batch import
NeedsInvestigationcompiler/runtimehelp wanted

Metriche repository

Star
 (133.883 star)
Metriche merge PR
 (Nessuna PR mergiata in 30 g)

Descrizione

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

1.11.2

Does this issue reproduce with the latest release?

yes

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

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 https://github.com/golang/go/issues/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.

Guida contributor