1use crate::{elf, stub, version::Version};
10use std::collections::BTreeSet;
11
12pub const SONAME: &str = "libgcc_s.so.1";
14pub const GCC_RELEASE: &str = "4.8.5";
16pub const INTERFACE: &str = include_str!("libgcc_s-gcc-4.8.5.tsv");
18const HEADER: &str = "symbol\tversion\tdefault\ttype\tsize\talignment";
19
20#[derive(Clone, Debug, Eq, PartialEq)]
22pub struct Definition {
23 pub name: String,
25 pub version: String,
27 pub default: bool,
29 pub object: bool,
31 pub size: u64,
33 pub alignment: u64,
35}
36
37pub 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
104pub 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
120pub 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
128pub fn stub() -> Result<stub::Sources, String> {
130 let definitions = definitions()?;
131 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 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}