Skip to main content

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}