rucc_sysroot/link.rs
1//! The start files and the link line for a musl link.
2//!
3//! Design: `spec/cross-compile/08-sysroots.md` section 8.2 and `spec/cross-compile/11-linking.md`.
4//!
5//! # Why musl is first
6//!
7//! `spec/cross-compile/09-libc-stubs.md` section 9.3 is the argument. musl exercises the header
8//! tree, the search paths, the start files, the compiler runtime and the link line, and it does
9//! that without symbol versioning and without stub generation, which are the two hardest pieces of
10//! the glibc path. If a musl cross link works end to end then the pipeline is right and what is
11//! left for M9.5 is the glibc specific parts rather than the shape of the thing.
12//!
13//! # Why the line has three parts and not one
14//!
15//! `crtn.o` goes after the libraries and `crti.o` goes before them, because between them they open
16//! and close the `.init` and `.fini` sections and anything contributing to those has to land in the
17//! middle. A link line that is one list gets this wrong in a way that produces a binary which links,
18//! runs, and does not run its static constructors, so the three parts are three fields here rather
19//! than a comment on an ordering somebody has to preserve.
20
21use std::path::PathBuf;
22
23use rucc_tuple::{Abi, Arch, DataModel, Endian};
24
25use crate::layout::Sysroot;
26
27/// How the program is linked, which decides the first start file and the flags.
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
29pub enum LinkMode {
30 /// Everything in the binary, no interpreter, no relocation at load. The default for musl, and
31 /// the mode `spec/cross-compile/02-the-goal.md`'s exit criterion names.
32 #[default]
33 Static,
34 /// Static, and position independent, so the loader may place it anywhere. A different first
35 /// start file, because the program has to relocate itself before `main` and `rcrt1.o` is what
36 /// does that.
37 StaticPie,
38 /// Against the shared libc, with musl's loader named in the program header.
39 Dynamic,
40}
41
42/// The inputs to a link, in the three groups a linker needs them in.
43///
44/// Paths rather than strings, and no linker flavour spelling, because
45/// `spec/cross-compile/11-linking.md` owns which linker is invoked and how its arguments are
46/// spelled. What is here is what has to be linked and in what order, which is a target fact.
47#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct LinkLine {
49 /// The start files, before the user's objects.
50 pub start: Vec<PathBuf>,
51 /// The libraries, after the user's objects.
52 pub libraries: Vec<PathBuf>,
53 /// The end files, after the libraries.
54 pub end: Vec<PathBuf>,
55 /// The flags the mode needs, which is the static switch and, for a dynamic link, the loader.
56 pub flags: Vec<String>,
57}
58
59impl LinkLine {
60 /// The line for a musl link against this sysroot.
61 ///
62 /// `crt1.o` runs before `main` and calls it. `crti.o` and `crtn.o` are the prologue and the
63 /// epilogue of the `.init` and `.fini` sections, which is why one is at the front and the other
64 /// is at the very back. `libc.a` carries musl's whole C library, and `librucc_builtins.a`
65 /// carries the operations the architecture does not have an instruction for, which
66 /// `spec/cross-compile/10-runtime.md` says has to be ours rather than the platform's.
67 ///
68 /// The builtins go after `libc.a` because musl calls some of them, and an archive that is
69 /// searched before the thing that needs it contributes nothing.
70 #[must_use]
71 pub fn musl(sysroot: &Sysroot, mode: LinkMode) -> Self {
72 let lib = sysroot.lib();
73 let first = match mode {
74 LinkMode::Static => "crt1.o",
75 LinkMode::StaticPie => "rcrt1.o",
76 LinkMode::Dynamic => "Scrt1.o",
77 };
78
79 let mut flags = Vec::new();
80 match mode {
81 LinkMode::Static => flags.push("-static".to_string()),
82 LinkMode::StaticPie => {
83 flags.push("-static-pie".to_string());
84 }
85 LinkMode::Dynamic => {
86 flags.push("-dynamic-linker".to_string());
87 flags.push(musl_loader(sysroot.target()).to_string());
88 }
89 }
90 // An executable stack is a target default nobody wants and several linkers still assume it
91 // when no input says otherwise. Saying so on every line is cheaper than finding out which
92 // object failed to.
93 flags.push("-z".to_string());
94 flags.push("noexecstack".to_string());
95
96 LinkLine {
97 start: vec![lib.join(first), lib.join("crti.o")],
98 libraries: vec![lib.join("libc.a"), lib.join("librucc_builtins.a")],
99 end: vec![lib.join("crtn.o")],
100 flags,
101 }
102 }
103
104 /// Every input, in the order they reach the linker, with the caller's objects in the middle.
105 ///
106 /// The one function that knows the whole order, so that a caller cannot assemble the three
107 /// groups in the wrong sequence.
108 #[must_use]
109 pub fn with_objects(&self, objects: &[PathBuf]) -> Vec<PathBuf> {
110 let mut all = self.start.clone();
111 all.extend_from_slice(objects);
112 all.extend(self.libraries.iter().cloned());
113 all.extend(self.end.iter().cloned());
114 all
115 }
116}
117
118/// The absolute path musl's loader is installed at on the target.
119///
120/// It goes in the program header of a dynamically linked binary, so it is a string about the target
121/// machine's filesystem and not about ours, and it has to be right without anything to check it
122/// against at link time. A wrong one produces a binary that the kernel refuses to start with a
123/// message about a missing file that is on nobody's disk.
124///
125/// 32-bit ARM is the row with two answers, because musl names the hard float and soft float builds
126/// differently and they are not interchangeable. PowerPC is the other row with two, and there the
127/// endianness picks, because musl treats the two byte orders as separate ports.
128#[must_use]
129pub fn musl_loader(target: rucc_tuple::TargetTuple) -> &'static str {
130 match target.arch() {
131 Arch::X86_64 => match target.data_model() {
132 DataModel::Ilp32On64 => "/lib/ld-musl-x32.so.1",
133 _ => "/lib/ld-musl-x86_64.so.1",
134 },
135 Arch::X86 => "/lib/ld-musl-i386.so.1",
136 Arch::Aarch64 | Arch::Arm64Ec => "/lib/ld-musl-aarch64.so.1",
137 Arch::Arm => match target.resolved_abi() {
138 Abi::DoubleFloat => "/lib/ld-musl-armhf.so.1",
139 _ => "/lib/ld-musl-arm.so.1",
140 },
141 Arch::Riscv64 => "/lib/ld-musl-riscv64.so.1",
142 Arch::Riscv32 => "/lib/ld-musl-riscv32.so.1",
143 Arch::S390x => "/lib/ld-musl-s390x.so.1",
144 Arch::PowerPc64 => match target.endian() {
145 Endian::Little => "/lib/ld-musl-powerpc64le.so.1",
146 Endian::Big => "/lib/ld-musl-powerpc64.so.1",
147 },
148 Arch::LoongArch64 => "/lib/ld-musl-loongarch64.so.1",
149 // musl has no wasm port and wasm has no loader. The caller that gets here asked for a
150 // dynamic musl link on a target with neither, which is a driver bug rather than a user
151 // one, and a path that cannot exist is a better report than a plausible wrong one.
152 Arch::Wasm32 => "/lib/ld-musl-none.so.1",
153 }
154}