Skip to main content

libc_abi_tools/
libgcc.rs

1//! The compiler runtime `libgcc_s.so.1` as a link-only stub.
2//!
3//! Providers link every release against the interface of GCC 4.8.5's
4//! `libgcc_s.so.1` (CentOS 7), recorded in `libgcc_s-gcc-4.8.5.tsv` by
5//! `scripts/record-libgcc-s.sh`. An executable linked against the stub imports
6//! only symbol versions that runtime defines (`GCC_3.0` through `GCC_4.8.0`), so
7//! it loads with GCC 4.8.5's runtime and every later one. The stub has no code:
8//! function bodies trap, and it must never be loaded at run time.
9use crate::{elf, stub, version::Version};
10use std::collections::BTreeSet;
11
12/// The compiler runtime's SONAME.
13pub const SONAME: &str = "libgcc_s.so.1";
14/// GCC release whose `libgcc_s.so.1` interface the stub reproduces.
15pub const GCC_RELEASE: &str = "4.8.5";
16/// The recorded interface: symbol, version, default, type, size, alignment.
17pub const INTERFACE: &str = include_str!("libgcc_s-gcc-4.8.5.tsv");
18const HEADER: &str = "symbol\tversion\tdefault\ttype\tsize\talignment";
19
20/// One symbol definition of the recorded `libgcc_s.so.1` interface.
21#[derive(Clone, Debug, Eq, PartialEq)]
22pub struct Definition {
23    /// The symbol name.
24    pub name: String,
25    /// Its version node, such as `GCC_3.0`.
26    pub version: String,
27    /// False for a compatibility (non-default) version, which links can no longer select.
28    pub default: bool,
29    /// True for a data object, false for a function.
30    pub object: bool,
31    /// The object's size in bytes; zero for functions.
32    pub size: u64,
33    /// Alignment of an object's definition; one for functions.
34    pub alignment: u64,
35}
36
37/// Parses and validates the recorded interface.
38pub fn definitions() -> Result<Vec<Definition>, String> {
39    parse(INTERFACE)
40}
41
42fn parse(text: &str) -> Result<Vec<Definition>, String> {
43    let mut lines = text.lines().filter(|l| !l.starts_with('#'));
44    if lines.next() != Some(HEADER) {
45        return Err("libgcc_s interface: unexpected header".into());
46    }
47    let mut result = Vec::new();
48    let mut keys = BTreeSet::new();
49    let mut defaults = BTreeSet::new();
50    for line in lines {
51        let fields: Vec<&str> = line.split('\t').collect();
52        let [name, version, default, kind, size, alignment] = fields[..] else {
53            return Err(format!("libgcc_s interface: malformed row {line:?}"));
54        };
55        let numeric = version
56            .strip_prefix("GCC_")
57            .ok_or_else(|| format!("{name}: version {version} is not a GCC version"))?;
58        numeric.parse::<Version>()?;
59        let default = match default {
60            "yes" => true,
61            "no" => false,
62            _ => return Err(format!("{name}@{version}: default must be yes or no")),
63        };
64        let object = match kind {
65            "FUNC" => false,
66            "OBJECT" => true,
67            _ => return Err(format!("{name}@{version}: unsupported type {kind}")),
68        };
69        let size: u64 = size
70            .parse()
71            .map_err(|_| format!("{name}@{version}: invalid size"))?;
72        let alignment = match (object, alignment) {
73            (false, "-") => 1,
74            (true, text) => text
75                .parse::<u64>()
76                .ok()
77                .filter(|a| a.is_power_of_two() && *a <= 4096)
78                .ok_or_else(|| format!("{name}@{version}: invalid alignment"))?,
79            _ => return Err(format!("{name}@{version}: alignment is for objects only")),
80        };
81        if object && size == 0 {
82            return Err(format!("{name}@{version}: empty object"));
83        }
84        if !keys.insert((name.to_string(), version.to_string()))
85            || (default && !defaults.insert(name.to_string()))
86        {
87            return Err(format!("{name}@{version}: duplicate definition or default"));
88        }
89        result.push(Definition {
90            name: name.into(),
91            version: version.into(),
92            default,
93            object,
94            size,
95            alignment,
96        });
97    }
98    if result.is_empty() {
99        return Err("libgcc_s interface is empty".into());
100    }
101    Ok(result)
102}
103
104/// The version nodes, oldest first; each inherits the one before it.
105pub fn versions() -> Result<Vec<String>, String> {
106    let mut versions: Vec<(Version, String)> = definitions()?
107        .into_iter()
108        .map(|d| {
109            (
110                d.version["GCC_".len()..].parse().expect("validated"),
111                d.version,
112            )
113        })
114        .collect();
115    versions.sort();
116    versions.dedup();
117    Ok(versions.into_iter().map(|(_, v)| v).collect())
118}
119
120/// Whether GCC 4.8.5's runtime defines `name@version`, so that an executable
121/// importing it loads there.
122pub fn provides(name: &str, version: &str) -> Result<bool, String> {
123    Ok(definitions()?
124        .iter()
125        .any(|d| d.name == name && d.version == version))
126}
127
128/// Assembly and version script for the stub `libgcc_s.so.1`.
129pub fn stub() -> Result<stub::Sources, String> {
130    let definitions = definitions()?;
131    // Give every definition its own storage: functions in one section, objects in
132    // another, each at an offset aligned for it.
133    let mut ends = [0u64; 2];
134    let mut symbols = Vec::new();
135    for d in &definitions {
136        let (section, alignment) = if d.object { (2, d.alignment) } else { (1, 16) };
137        let end = &mut ends[usize::from(d.object)];
138        let value = end.next_multiple_of(alignment);
139        *end = value + d.size.max(1);
140        symbols.push(elf::Symbol {
141            name: d.name.clone(),
142            version: Some(d.version.clone()),
143            default_version: Some(d.default),
144            symbol_type: if d.object { 1 } else { 2 },
145            binding: 1,
146            visibility: 0,
147            section,
148            section_alignment: alignment,
149            value,
150            size: d.size,
151            same_range: None,
152        });
153    }
154    let reference = elf::Metadata {
155        soname: SONAME.into(),
156        symbols,
157    };
158    let names: BTreeSet<&str> = definitions.iter().map(|d| d.name.as_str()).collect();
159    stub::render_versions(
160        &reference,
161        &names.into_iter().collect::<Vec<_>>(),
162        "GCC_",
163        true,
164    )
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170
171    #[test]
172    fn recorded_interface_is_gcc_4_8_5() {
173        let definitions = definitions().unwrap();
174        assert_eq!(definitions.len(), 138);
175        assert_eq!(definitions.iter().filter(|d| !d.default).count(), 6);
176        assert_eq!(
177            versions().unwrap(),
178            [
179                "GCC_3.0",
180                "GCC_3.3",
181                "GCC_3.3.1",
182                "GCC_3.4",
183                "GCC_3.4.2",
184                "GCC_3.4.4",
185                "GCC_4.0.0",
186                "GCC_4.2.0",
187                "GCC_4.3.0",
188                "GCC_4.7.0",
189                "GCC_4.8.0"
190            ]
191        );
192        let objects: Vec<_> = definitions.iter().filter(|d| d.object).collect();
193        assert_eq!(objects.len(), 1);
194        assert_eq!(
195            (
196                objects[0].name.as_str(),
197                objects[0].size,
198                objects[0].alignment
199            ),
200            ("__cpu_model", 16, 16)
201        );
202        assert!(provides("_Unwind_Resume", "GCC_3.0").unwrap());
203        assert!(provides("__gttf2", "GCC_3.0").unwrap());
204        // Added by later GCC releases.
205        assert!(!provides("__divmodti4", "GCC_7.0.0").unwrap());
206        assert!(!provides("_Unwind_Resume", "GCC_7.0.0").unwrap());
207    }
208
209    #[test]
210    fn stub_defines_each_version_once_in_a_chain() {
211        let sources = stub().unwrap();
212        assert_eq!(sources.selected.symbols.len(), 138);
213        assert!(sources.versions.starts_with("GCC_3.0 {\n  global:\n"));
214        assert!(sources.versions.ends_with("} GCC_4.7.0;\n"));
215        for (child, parent) in [("GCC_3.3", "GCC_3.0"), ("GCC_4.8.0", "GCC_4.7.0")] {
216            let node = &sources.versions[sources.versions.find(&format!("{child} {{")).unwrap()..];
217            let close = &node[node.find('}').unwrap()..];
218            assert!(close.starts_with(&format!("}} {parent};")), "{child}");
219        }
220        assert!(sources
221            .assembly
222            .contains(".symver __abi_stub_0,_Unwind_Backtrace@@GCC_3.3"));
223        assert!(sources.assembly.contains("__gttf2@GCC_3.0\n"));
224        assert!(sources.assembly.contains("__gttf2@@GCC_4.3.0\n"));
225        assert!(sources.assembly.contains(".type __abi_stub_"));
226        assert!(sources.assembly.contains(",@object\n.size __abi_stub_"));
227        assert!(!sources.assembly.contains("GLIBC_"));
228    }
229
230    #[test]
231    fn rejects_malformed_interfaces() {
232        let good = format!("{HEADER}\nf\tGCC_3.0\tyes\tFUNC\t8\t-\n");
233        assert!(parse(&good).is_ok());
234        for bad in [
235            good.replace("GCC_3.0", "GLIBC_2.2.5"),
236            good.replace("GCC_3.0", "GCC_x"),
237            good.replace("\tyes", "\tmaybe"),
238            good.replace("FUNC", "TLS"),
239            good.replace("\t-\n", "\t8\n"),
240            good.replace("FUNC\t8\t-", "OBJECT\t8\t3"),
241            format!("{good}f\tGCC_3.3\tyes\tFUNC\t8\t-\n"),
242            format!("{good}f\tGCC_3.0\tno\tFUNC\t8\t-\n"),
243            good.replace(HEADER, "symbol\tversion"),
244            HEADER.to_string(),
245        ] {
246            assert!(parse(&bad).is_err(), "{bad}");
247        }
248    }
249}