Skip to content

Commit ef9349d

Browse files
committed
Add test for checking used glibc symbols
1 parent b605c65 commit ef9349d

File tree

6 files changed

+124
-2
lines changed

6 files changed

+124
-2
lines changed

Diff for: src/doc/rustc-dev-guide/src/tests/directives.md

+2
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,8 @@ Some examples of `X` in `ignore-X` or `only-X`:
152152
`compare-mode-split-dwarf`, `compare-mode-split-dwarf-single`
153153
- The two different test modes used by coverage tests:
154154
`ignore-coverage-map`, `ignore-coverage-run`
155+
- When testing a dist toolchain: `dist`
156+
- This needs to be enabled with `COMPILETEST_ENABLE_DIST_TESTS=1`
155157

156158
The following directives will check rustc build settings and target
157159
settings:

Diff for: src/tools/compiletest/src/directive-list.rs

+1
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
175175
"only-beta",
176176
"only-bpf",
177177
"only-cdb",
178+
"only-dist",
178179
"only-gnu",
179180
"only-i686-pc-windows-gnu",
180181
"only-i686-pc-windows-msvc",

Diff for: src/tools/compiletest/src/header.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1280,7 +1280,7 @@ pub fn llvm_has_libzstd(config: &Config) -> bool {
12801280
let stderr = String::from_utf8(output.stderr).ok()?;
12811281
let zstd_available = !stderr.contains("LLVM was not built with LLVM_ENABLE_ZSTD");
12821282

1283-
// We don't particularly need to clean the link up (so the previous commands could fail
1283+
// We don't partiCOMPILETEST_ENABLE_OPT_DIST_TESTScularly need to clean the link up (so the previous commands could fail
12841284
// in theory but won't in practice), but we can try.
12851285
std::fs::remove_file(lld_symlink_path).ok()?;
12861286

Diff for: src/tools/compiletest/src/header/cfg.rs

+6
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,12 @@ fn parse_cfg_name_directive<'a>(
235235
message: "when the test mode is {name}",
236236
}
237237

238+
condition! {
239+
name: "dist",
240+
condition: std::env::var("COMPILETEST_ENABLE_DIST_TESTS") == Ok("1".to_string()),
241+
message: "when performing tests on dist toolchain"
242+
}
243+
238244
if prefix == "ignore" && outcome == MatchOutcome::Invalid {
239245
// Don't error out for ignore-tidy-* diretives, as those are not handled by compiletest.
240246
if name.starts_with("tidy-") {

Diff for: src/tools/opt-dist/src/tests.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,12 @@ llvm-config = "{llvm_config}"
108108
for test_path in env.skipped_tests() {
109109
args.extend(["--skip", test_path]);
110110
}
111-
cmd(&args).env("COMPILETEST_FORCE_STAGE0", "1").run().context("Cannot execute tests")
111+
cmd(&args)
112+
.env("COMPILETEST_FORCE_STAGE0", "1")
113+
// Also run dist-only tests
114+
.env("COMPILETEST_ENABLE_DIST_TESTS", "1")
115+
.run()
116+
.context("Cannot execute tests")
112117
}
113118

114119
/// Tries to find the version of the dist artifacts (either nightly, beta, or 1.XY.Z).
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Check that the compiler toolchain (rustc) that we distribute is not using newer glibc
2+
// symbols than a specified minimum.
3+
// This test should only be executed on an extracted dist archive or in a dist-* CI job.
4+
5+
//@ only-dist
6+
//@ only-x86_64-unknown-linux-gnu
7+
//@ ignore-cross-compile
8+
9+
use std::path::{Path, PathBuf};
10+
11+
use run_make_support::{cmd, llvm_objdump, regex, rustc_path};
12+
13+
fn main() {
14+
// This is the maximum glibc version *supported* by the x86_64-unknown-linux-gnu target.
15+
// All glibc symbols used in the compiler must be lower or equal than this version.
16+
let max_supported = (2, 17, 99);
17+
18+
let rustc = PathBuf::from(rustc_path());
19+
// Check symbols directly in rustc
20+
check_symbols(&rustc, max_supported);
21+
22+
// Find dynamic libraries referenced by rustc that come from our lib directory
23+
let lib_path = rustc.parent().unwrap().parent().unwrap().join("lib");
24+
let dynamic_libs = find_dynamic_libs(&rustc)
25+
.into_iter()
26+
.filter_map(|path| path.canonicalize().ok())
27+
.filter(|lib| lib.starts_with(&lib_path))
28+
.collect::<Vec<_>>();
29+
for lib in dynamic_libs {
30+
check_symbols(&lib, max_supported);
31+
}
32+
}
33+
34+
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq)]
35+
struct GlibcSymbol {
36+
name: String,
37+
version: (u32, u32, u32),
38+
}
39+
40+
fn find_dynamic_libs(path: &Path) -> Vec<PathBuf> {
41+
cmd("ldd")
42+
.arg(path)
43+
.run()
44+
.stdout_utf8()
45+
.lines()
46+
.filter_map(|line| {
47+
let line = line.trim();
48+
let Some((_, line)) = line.split_once(" => ") else {
49+
return None;
50+
};
51+
line.split_ascii_whitespace().next().map(|path| PathBuf::from(path))
52+
})
53+
.collect()
54+
}
55+
56+
fn check_symbols(file: &Path, max_supported: (u32, u32, u32)) {
57+
println!("Checking {}", file.display());
58+
let mut invalid: Vec<GlibcSymbol> = get_glibc_symbols(file)
59+
.into_iter()
60+
.filter(|symbol| symbol.version > max_supported)
61+
.collect();
62+
if !invalid.is_empty() {
63+
invalid.sort();
64+
panic!(
65+
"Found invalid glibc symbols in {}:\n{}",
66+
file.display(),
67+
invalid
68+
.into_iter()
69+
.map(|symbol| format!(
70+
"{} ({:?} higher than max allowed {:?})",
71+
symbol.name, symbol.version, max_supported
72+
))
73+
.collect::<Vec<_>>()
74+
.join("\n")
75+
)
76+
}
77+
}
78+
79+
fn get_glibc_symbols(file: &Path) -> Vec<GlibcSymbol> {
80+
let regex = regex::Regex::new(r#"GLIBC_(\d)+\.(\d+)(:?\.(\d+))?"#).unwrap();
81+
82+
// Uses llvm-objdump, because implementing this using the `object` crate is quite complicated.
83+
llvm_objdump()
84+
.arg("-T")
85+
.arg(file)
86+
.run()
87+
.stdout_utf8()
88+
.lines()
89+
.filter_map(|line| {
90+
// Example line
91+
// 0000000000000000 DF *UND* 0000000000000000 (GLIBC_2.2.5) sbrk
92+
let mut parts = line.split(" ").collect::<Vec<_>>().into_iter().rev();
93+
let Some(name) = parts.next() else {
94+
return None;
95+
};
96+
let Some(lib) = parts.next() else {
97+
return None;
98+
};
99+
let Some(version) = regex.captures(lib) else {
100+
return None;
101+
};
102+
let major = version.get(1).and_then(|m| m.as_str().parse().ok()).unwrap_or(0);
103+
let minor = version.get(2).and_then(|m| m.as_str().parse().ok()).unwrap_or(0);
104+
let patch = version.get(3).and_then(|m| m.as_str().parse().ok()).unwrap_or(0);
105+
Some(GlibcSymbol { version: (major, minor, patch), name: name.to_string() })
106+
})
107+
.collect()
108+
}

0 commit comments

Comments
 (0)