who does go compiler link executable to glibc without an apparent reason?

225 views
Skip to first unread message

Howard Guo

unread,
Nov 5, 2017, 11:12:52 AM11/5/17
to golang-nuts
Hello fellow gophers.

I've a small number of go programs that only use standard library functions without any 3rd party library dependency. A strange behaviour was observed during compilation (go build) - most of them compile into static executable, but few of them end up linking to glibc. Without an exception, all of them compile into static executable when CGO_ENABLED=0 is set prior to compilation, and their runtime behaviour doesn't change at all.

What could be the obscure reasons for go compiler to link executable to glibc?

Thanks.

Kind regards,
Howard

Jan Mercl

unread,
Nov 5, 2017, 11:21:52 AM11/5/17
to Howard Guo, golang-nuts
Using some of the net/... package calls libc unless disabled.

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
--

-j

Howard Guo

unread,
Nov 7, 2017, 4:28:20 AM11/7/17
to golang-nuts
Just noticed this statement from src/net/net.go:

------------------
On Unix systems, the resolver has two options for resolving names.
It can use a pure Go resolver that sends DNS requests directly to the servers
listed in /etc/resolv.conf, or it can use a cgo-based resolver that calls C
library routines such as getaddrinfo and getnameinfo.
-----------------

However, using its recommended method to force usage of Go resolver still results in a dynamic binary:

    export GODEBUG=netdns=go

Welp, have to dig deeper.

Karan Chaudhary

unread,
Nov 7, 2017, 5:21:26 AM11/7/17
to golang-nuts
With a quick look,  it seems "net" is not the only pkg which depends on cgo,  though by default it uses pure Go DNS resolver only.

I tested on small program:
package main

import (
        "fmt"
        "net/http"
)

func main() {

        fmt.Println("Hello, playground")
        http.ListenAndServe(":9999", nil)

}

Which is dynamically linked if CGO_ENABLED=1 is set,  and statically linked if CGO_ENABLED=0 is set,  irrespective of GODEBUG=netdns=go .
Diffing the cgo symbols from ELFs from the 2 cases shows that enabling cgo adds 61 more functions:
_cgo_b0c710f30cfd_C2func_
_cgo_b0c710f30cfd_C2func_
_cgo_b0c710f30cfd_Cfunc__
_cgo_b0c710f30cfd_Cfunc_f
_cgo_b0c710f30cfd_Cfunc_f
_cgo_b0c710f30cfd_Cfunc_g
_cgo_b0c710f30cfd_Cfunc_g
_cgo_b0c710f30cfd_Cfunc_g
cgo_context_function
_cgo_get_context_function
_cgo_panic
_cgo_panic
_cgo_release_context
_cgo_sys_thread_start
_cgo_topofstack
_cgo_topofstack
_cgo_try_pthread_create
_cgo_wait_runtime_init_do
net._cgo_b0c710f30cfd_C2f
net._cgo_b0c710f30cfd_Cfu
net._cgo_b0c710f30cfd_Cfu
net._cgo_b0c710f30cfd_Cfu
net._cgo_b0c710f30cfd_Cfu
net._cgo_cmalloc
net.cgoIPLookup
net.cgoLookupIP
net.cgoLookupIPCNAME
net.cgoLookupIPCNAME.func
net.cgoLookupIPCNAME.func
net.cgoLookupIPCNAME.func
net.cgoLookupPort
net.cgoLookupServicePort
net.cgoLookupServicePort.
net.cgoLookupServicePort.
net.cgoLookupServicePort.
net.cgoPortLookup
runtime.cgoAlwaysFalse
runtime/cgo(.bss)
runtime.cgocallback
runtime.cgoCheckArg
runtime.cgoCheckPointer
runtime.cgoCheckUnknownPo
runtime._cgo_panic_intern
runtime/cgo(.rodata)
runtime/cgo(.rodata.str1.
runtime/cgo(.rodata.str1.
runtime/cgo(.text)
runtime.cgoUse
x_cgo_callers
x_cgo_init
x_cgo_inittls
x_cgo_mmap
x_cgo_munmap
x_cgo_notify_runtime_init
x_cgo_set_context_functio
x_cgo_setenv
x_cgo_sigaction
x_cgo_sys_thread_create
x_cgo_threadentry
x_cgo_thread_start
x_cgo_unsetenv

Here we can see "net" is not the only pkg that uses CGO,  but runtime uses it as well.
These can be explored to understand more.

Howard Guo

unread,
Nov 7, 2017, 5:47:40 AM11/7/17
to golang-nuts
Thanks very much Karan, that's indeed a good approach for this challenge.

Here is the output from a dynamically linked executable:

abort
__errno_location
fprintf
fputc
free
freeaddrinfo
fwrite
gai_strerror
getaddrinfo
getnameinfo
malloc
mmap
munmap
nanosleep
pthread_attr_destroy
pthread_attr_getstacksize
pthread_attr_init
pthread_cond_broadcast
pthread_cond_wait
pthread_create
pthread_mutex_lock
pthread_mutex_unlock
pthread_sigmask
setenv
sigaction
sigaddset
sigemptyset
sigfillset
sigismember
stderr
strerror
unsetenv
vfprintf

Now this initiative of go compiler producing a dynamic executable looks quite redundant.

I shall find some time to reduce this go project, file an issue, and see if go developers can share more insights.

Kind regards,
Howard

Dave Cheney

unread,
Nov 7, 2017, 5:55:22 AM11/7/17
to golang-nuts
The net and os/user packages will force dynamic linking unless CGO_ENABLED=0 or you are cross compiling.

Howard Guo

unread,
Nov 7, 2017, 6:01:05 AM11/7/17
to golang-nuts
Do you by any chance know what the rationale would be?

Kind regards,
Howard

Dave Cheney

unread,
Nov 7, 2017, 6:15:46 AM11/7/17
to golang-nuts
Until recently some functions in the os/user package were only available via glibc.

The net package links to cgo by default to pick up the nss libraries, which are important if your installation uses ldap.

I argued that this should not be the default in the 1.5 release, that most users deploying on Linux expected a static binary by default, however my arguments were not sufficiently convincing.

The tldr is rationale for the linking behaviour of a go binary is implantation defined, and the go authors choose to make their implementation use cgo where available.

Howard Guo

unread,
Nov 7, 2017, 8:07:11 AM11/7/17
to golang-nuts
Thanks for the info!

Kind regards,
Howard

Ian Lance Taylor

unread,
Nov 7, 2017, 10:23:17 AM11/7/17
to Howard Guo, golang-nuts
On Tue, Nov 7, 2017 at 1:28 AM, Howard Guo <guoh...@gmail.com> wrote:
> Just noticed this statement from src/net/net.go:
>
> ------------------
> On Unix systems, the resolver has two options for resolving names.
> It can use a pure Go resolver that sends DNS requests directly to the
> servers
> listed in /etc/resolv.conf, or it can use a cgo-based resolver that calls C
> library routines such as getaddrinfo and getnameinfo.
> -----------------
>
> However, using its recommended method to force usage of Go resolver still
> results in a dynamic binary:
>
> export GODEBUG=netdns=go

Setting the GODEBUG environment variable changes the behavior at run
time. It does not affect how the program is built.

As you've seen setting CGO_ENABLED=0 is a quick way to ensure that you
get a static binary with no C code. But this does mean that the
os/user package, and less frequently the net package, will not behave
correctly in certain environments. If you are running in a Docker
container or otherwise constrained environment this does not matter.

Ian

Howard Guo

unread,
Nov 8, 2017, 3:34:36 AM11/8/17
to golang-nuts
Thanks Ian.

It came as a pleasant surprise that using statically compiled executable reduced(!) runtime memory usage by a noticeable portion. And gladly, till now the components are behaving well.

Kind regards,
Howard
Reply all
Reply to author
Forward
0 new messages