Skip to content

cmd/link: c-archive Output Mode Forces Initial-Exec TLS model #48596

@auzhva

Description

@auzhva

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

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.

    Type

    No type

    Projects

    Status

    Triage Backlog

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions