-
Notifications
You must be signed in to change notification settings - Fork 18.4k
Description
What version of Go are you using (go version
)?
$ go version go version go1.17.1 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 GO111MODULE="" GOARCH="amd64" GOBIN="" GOCACHE="/root/.cache/go-build" GOENV="/root/.config/go/env" GOEXE="" GOEXPERIMENT="" GOFLAGS="" GOHOSTARCH="amd64" GOHOSTOS="linux" GOINSECURE="" GOMODCACHE="/go/pkg/mod" GONOPROXY="" GONOSUMDB="" GOOS="linux" GOPATH="/go" GOPRIVATE="" GOPROXY="https://proxy.golang.org,direct" GOROOT="/usr/local/go" GOSUMDB="sum.golang.org" GOTMPDIR="" GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64" GOVCS="" GOVERSION="go1.17.1" GCCGO="gccgo" AR="ar" CC="gcc" CXX="g++" CGO_ENABLED="1" GOMOD="/dev/null" 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-build1642499936=/tmp/go-build -gno-record-gcc-switches"
What did you do?
Full Gist is available here - https://gist.github.com/auzhva/6e3660c2433f7ead765d4d09e645013b
In provided example there are 3 files:
- app.c - sample loader app
- lib.c - sample C-library
- lib.go - sample Go-library
If built in below fashion it works:
go build -o libGoShared.so -buildmode=c-shared ./lib.go
gcc -shared -lpthread -fPIC lib.c -o libClibShared.so -lGoShared -L.
gcc app.c -o app -ldl
Then when running ./app
it's fine.
While if I do below it fails with cannot allocate memory in static TLS block
error.
go build -ldflags="-s -w" -o libGoStatic.a -buildmode=c-archive ./lib.go
gcc -shared -lpthread -fPIC lib.c -o libClibStatic.so -lGoStatic -L.
gcc app.c -o app -ldl
More investigation on STATIC_TLS flag
It appears that Go always builds with Initial-Exec
TLS model, which appears to lead to STATIC_TLS
flag being set to produced ELF binaries.
This is easily visible with below:
$ readelf --dynamic -W libGoShared.so
... skipped ...
0x000000000000001e (FLAGS) SYMBOLIC STATIC_TLS
... skipped ...
This is a case with c-shared
library itself. But when c-archive
is produced then whatever upper code will use it - it inherits STATIC_TLS flag.
So in second (faulty) example above the entire libClibShared.so
inherits STATIC_TLS flag as well. At least on x86_64 arch (in theory this can be arch-dependent).
Ok, there is STATIC_TLS flag. So what?
Loading STATIC_TLS images is a complicated thing.
In linux version glibc it allocates pre-populated constant surplus
of 512 bytes in TLS block for further unspecified use. It's defined here - https://sourceware.org/git/?p=glibc.git;a=blob;f=elf/dl-tls.c;h=d554ae44976bc494da4e41aaa4f6ecb5ca2ca4be;hb=HEAD
If I'm checking sizes of .tbss/.tdata
sections via readelf -S
then I see that go uses only 0x10 bytes of TLS. Which is lower then glibc surplus. And theoretically glibc is able to dlopen()
up to 32 go libs until it will run out of the buffer.
But it doesn't work with c-archive
. Go lib in c-archive
mode requires same 16 bytes of TLS stack, but other code may have additional TLS requirements. So if additional code needs more TLS then c-archive plus that code runs out of glibc boundaries and entire thing fails.
That wouldn't have been happening if c-shared
and c-archive
would've not been forcing Initial-TLS TLS models. As this 512-byte glibc limit applies only to that model. And any of the other 3 models (General Dynamic, Local Dynamic, Local Executable) do not have this limit.
What did you expect to see?
Both c-shared and c-archive cases working.
What did you see instead?
C-archive case failing.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status