authorgravatar for motiejus@jakstys.ltMotiejus Jakštys <motiejus@jakstys.lt> 2022-12-16 09:29:09+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-12-16 07:46:47-05:00
logb3f4e0d091d83874af849f1d1a2328ad7c154d42
tree1d9d749d6b5024c87b5dcd37a1b08dedb98c0064
parentcf85462a78aa767ce7cfd1ebdb06d7405e1b0e5e

Elf: switch link order of libcompiler_rt and libc

Given `main.go`: package main import _ "os/user" func main() {} Compiling it to linux/arm64: $ CGO_CFLAGS='-O0' GOOS=linux GOARCH=arm64 CGO_ENABLED=1 CC="zig cc -target aarch64-linux-gnu.2.28" go build main.go Results in this error: runtime/cgo(.text): unknown symbol memset in callarm64 runtime/cgo(.text): unknown symbol memset in callarm64 runtime/cgo(.text): relocation target memset not defined In the midst of intermediate compilations files we can see this commmand: ld.lld -o _cgo_.o <...> /tmp/go-build206961058/b043/_x009.o <...> ~/.cache/zig/.../libcompiler_rt.a <...> ~/.cache/.../libc.so.6 `_x009.o` needs memset: $ readelf -Ws ./b043/_x009.o | grep memset 22: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND memset Both `libcompiler_rt.a` and `libc.so.6` provide it: $ readelf -Ws ~/.cache/zig/.../libcompiler_rt.a | grep memset 870: 0000000000000000 318 FUNC WEAK DEFAULT 519 memset $ readelf -Ws ~/.cache/zig/.../libc.so.6 | grep -w memset 476: 000000000001d34c 0 FUNC GLOBAL DEFAULT 7 memset@@GLIBC_2.2.5 Since `libcompiler_rt.a` comes before libc in the linker line, the resulting `_cgo_.o` still links to a weak, unversioned memset: $ readelf -Ws ./b043/_cgo_.o | grep -w memset 40: 000000000022c07c 160 FUNC WEAK DEFAULT 14 memset 719: 000000000022c07c 160 FUNC WEAK DEFAULT 14 memset Since the final linking step is done by Golang's linker, it does not know of `libcompiler_rt.a`, and fails to link with the error message above. However, Go linker does recognize memset from glibc. If we specify an `-lc` equivalent before the `libcompiler_rt.a`, it will link to memset from libc: $ readelf -Wa ./b043/_x009.o |grep memset 14: 0000000000000000 0 FUNC GLOBAL DEFAULT UND memset@GLIBC_2.17 (2) 157: 0000000000000000 0 FUNC GLOBAL DEFAULT UND memset@GLIBC_2.17 ... and then `main.go` will compile+link successfully. Why doesn't Go linker take memset from glibc? An educated guess: Go determines whether to link with glibc from what the program asks (I presume `.dynsym`). Since `memset` is no longer attributed to glibc, Go skips linking to glibc altogether. Bonus question: curious why `-O0` is necessary? Because when optimizations are enabled (the default), the C compiler replaces `memset` function call with plain `stp` instructions (on aarch64).

1 files changed, 7 insertions(+), 5 deletions(-)

src/link/Elf.zig+7-5
...@@ -1753,11 +1753,6 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v...@@ -1753,11 +1753,6 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v
1753 try argv.append(ssp.full_object_path);1753 try argv.append(ssp.full_object_path);
1754 }1754 }
17551755
1756 // compiler-rt
1757 if (compiler_rt_path) |p| {
1758 try argv.append(p);
1759 }
1760
1761 // Shared libraries.1756 // Shared libraries.
1762 if (is_exe_or_dyn_lib) {1757 if (is_exe_or_dyn_lib) {
1763 const system_libs = self.base.options.system_libs.keys();1758 const system_libs = self.base.options.system_libs.keys();
...@@ -1836,6 +1831,13 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v...@@ -1836,6 +1831,13 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v
1836 }1831 }
1837 }1832 }
18381833
1834 // compiler-rt. Since compiler_rt exports symbols like `memset`, it needs
1835 // to be after the shared libraries, so they are picked up from the shared
1836 // libraries, not libcompiler_rt.
1837 if (compiler_rt_path) |p| {
1838 try argv.append(p);
1839 }
1840
1839 // crt postlude1841 // crt postlude
1840 if (csu.crtend) |v| try argv.append(v);1842 if (csu.crtend) |v| try argv.append(v);
1841 if (csu.crtn) |v| try argv.append(v);1843 if (csu.crtn) |v| try argv.append(v);