2019-01-18 10:39
浏览 118

静态和动态链接 Go的可移植性[关闭]

Let me start by getting some facts on the table to have them fact-checked, so that there's no confusion:

  • An ELF binary with a dynamic section will be compiled with some symbols unresolved. The resolution will be carried out by the linker sometime during execution of the binary.
  • There are pros and cons to dynamic linking. But if the target library which is required by your binary is not present on the system (in the required version), the binary won't run.
  • Static linking mitigates this problem, but introduces a new one on the lower layer. By linking the binary statically, the libraries' executable code was embedded in your binary, hence there's no problem with the binary-library interface anymore. However, things can now break on the library-OS interface. Is that correct? What problems may arise here?

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: []
 0x0000000000000001 (NEEDED)             Shared library: []
 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 (0x00007ffd9a972000) => /usr/lib/ (0x00007fcb2853c000) => /usr/lib/ (0x00007fcb28378000)
    /lib64/ => /usr/lib64/ (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.
  • Does this mean the libs were linked statically? I guess so, but the size difference is negligible on my machine (some 72 kB), which surprises me.
  • With regard to portability across Linux systems, which one is preferable and why?
  • How does Go's standard library carry out its business? Is it calling the C functions provided by the 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.
  • Finally, I've heard that there's "no guarantee that different distros, or even different versions of the same distro, are ABI compatible". Is this true? I assumed that ABI is mostly the binary executable format (ELF on Linux for quite some time), so I would assume no problems here. What could this mean?


  • 写回答
  • 好问题 提建议
  • 追加酬金
  • 关注问题
  • 收藏
  • 邀请回答

1条回答 默认 最新

  • dpm91915 2019-01-19 13:04

    On GNU/Linux, almost all Go executables fall into these categories:

    1. Those that include the application, the Go run-time, and a statically linked copy of (parts of) glibc.
    2. Those that include just the application and the Go run-time, statically linked, and none of glibc.
    3. Those that include just the application and the Go run-time, statically linked, and link to glibc dynamically.

    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.

    解决 无用
    打赏 举报

相关推荐 更多相似问题