Let me start by getting some facts on the table to have them fact-checked, so that there's no confusion:
Now let's discuss this in the context of Go. I've noticed that, if I build a binary with CGO_ENABLED=1 go build ...
, I get a binary with a dynamic section:
david@x1 /tmp (git)-[master] % readelf -d rtloggerd.cgo1
Dynamic section at offset 0x7a6140 contains 19 entries:
Tag Type Name/Value
0x0000000000000004 (HASH) 0x914e40
0x0000000000000006 (SYMTAB) 0x915340
0x000000000000000b (SYMENT) 24 (bytes)
0x0000000000000005 (STRTAB) 0x915100
0x000000000000000a (STRSZ) 570 (bytes)
0x0000000000000007 (RELA) 0x914a38
0x0000000000000008 (RELASZ) 24 (bytes)
0x0000000000000009 (RELAENT) 24 (bytes)
0x0000000000000003 (PLTGOT) 0xba6000
0x0000000000000015 (DEBUG) 0x0
0x0000000000000001 (NEEDED) Shared library: [libpthread.so.0]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000006ffffffe (VERNEED) 0x914de0
0x000000006fffffff (VERNEEDNUM) 2
0x000000006ffffff0 (VERSYM) 0x914d80
0x0000000000000014 (PLTREL) RELA
0x0000000000000002 (PLTRELSZ) 816 (bytes)
0x0000000000000017 (JMPREL) 0x914a50
0x0000000000000000 (NULL) 0x0
david@x1 /tmp (git)-[master] % ldd rtloggerd.cgo1
linux-vdso.so.1 (0x00007ffd9a972000)
libpthread.so.0 => /usr/lib/libpthread.so.0 (0x00007fcb2853c000)
libc.so.6 => /usr/lib/libc.so.6 (0x00007fcb28378000)
/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007fcb2858a000)
If, on the other hand, when I CGO_ENABLED=0 go build ...
, there's no dynamic section:
130 david@x1 /tmp (git)-[master] % readelf -d rtloggerd.cgo0
There is no dynamic section in this file.
libc
(glibc
in my case) in fact? I have assumed there's a native system call interface. On the other hand, I imagine that re-implementing the entire stdlib in native Go would be difficult.Thanks!
On GNU/Linux, almost all Go executables fall into these categories:
Go-related tooling often conflates the two, unfortunately. The main reason for the glibc dependency is that the application uses host name and user lookup (functions like getaddrinfo
and getpwuid_r
). CGO_ENABLED=0
switches from implementations like src/os/user/cgo_lookup_unix.go
(uses glibc) to src/os/user/lookup_unix.go
(does not use glibc). The non-glibc implementation does not use NSS and thus offers somewhat limited functionality (which generally do not affect users that do not store user information in LDAP/Active Directory).
In your case, setting CGO_ENABLED=0
moves your application from the third category to the second. (There is other Go-related tooling that could build an application of the first kind.) The non-NSS lookup code is not very large, so the increase in binary size is not significant. Since the Go run-time was already statically linked, it's even possible that the reduced overhead from static linking results in net reduction of executable size.
The most important issue to consider here is that NSS, threads and static linking do not all that well in glibc. All Go programs are multi-threaded, and the reason to (statically) link glibc into Go programs is precisely access to NSS functions. Therefore, statically linking Go programs against glibc is always the wrong thing to do. It is basically always buggy. Even if Go programs were not multi-threaded, a statically linked program which uses NSS functions needs the exact same version of glibc at run time that was used at build time, so static linking of such application reduces portability.
All these are reasons why Go applications of the first kind are such a bad idea. Producing a statically linked application using CGO_ENABLED=0
does not have these problems because those applications (of the second kind) do not include any glibc code (at the cost of reduced functionality of the user/host lookup functions).
If you want to create a portable binary which needs glibc, you need to link your application dynamically (the third kind), on a system with the oldest glibc you want to support. The application will then run on that glibc version and all later versions (for now, Go does not link libc correctly, so there is no strong compatibility guarantuee even for glibc). Distributions are generally ABI-compatible, but they have different versions of glibc. glibc goes to great lengths to make sure that applications dynamically linked against older versions of glibc will keep running on new versions of glibc, but the converse is not true: Once you link an application on a certain version of glibc, it may pick up features (symbols) that are just not available on older versions, so the application will not work with those older versions.