| author | bors <bors@rust-lang.org> 2025-01-23 01:03:32 UTC |
| committer | bors <bors@rust-lang.org> 2025-01-23 01:03:32 UTC |
| log | 3cd8fcbf87bd28a1f31be000ca906fb66f4d451d |
| tree | 80686070e5783cfda5a586eee1fe2ebb3104e220 |
| parent | a30f9151fe4a841c4643af4e47ad18a23e1fb044 |
| parent | 5482bbac0f18d45f0e02bbe8153e47b160287fff |
Add test for checking used glibc symbols
This test checks that we do not use too new glibc symbols in the compiler on x64 GNU Linux, in order not to break our [glibc promises](https://blog.rust-lang.org/2022/08/01/Increasing-glibc-kernel-requirements.html).
One thing that isn't solved in the PR yet is to make sure that this test will only run on `dist` CI, more specifically on the `dist-x86_64-linux` runner, in the opt-dist post-optimization tests (it can fail elsewhere, that doesn't matter). Any suggestions on how to do that are welcome.
Fixes: https://github.com/rust-lang/rust/issues/134037
r? `@jieyouxu`5 files changed, 129 insertions(+), 1 deletions(-)
src/doc/rustc-dev-guide/src/tests/directives.md+2| ... | @@ -152,6 +152,8 @@ Some examples of `X` in `ignore-X` or `only-X`: | ... | @@ -152,6 +152,8 @@ Some examples of `X` in `ignore-X` or `only-X`: |
| 152 | `compare-mode-split-dwarf`, `compare-mode-split-dwarf-single` | 152 | `compare-mode-split-dwarf`, `compare-mode-split-dwarf-single` |
| 153 | - The two different test modes used by coverage tests: | 153 | - The two different test modes used by coverage tests: |
| 154 | `ignore-coverage-map`, `ignore-coverage-run` | 154 | `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` | ||
| 155 | 157 | ||
| 156 | The following directives will check rustc build settings and target | 158 | The following directives will check rustc build settings and target |
| 157 | settings: | 159 | settings: |
src/tools/compiletest/src/directive-list.rs+1| ... | @@ -175,6 +175,7 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[ | ... | @@ -175,6 +175,7 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[ |
| 175 | "only-beta", | 175 | "only-beta", |
| 176 | "only-bpf", | 176 | "only-bpf", |
| 177 | "only-cdb", | 177 | "only-cdb", |
| 178 | "only-dist", | ||
| 178 | "only-gnu", | 179 | "only-gnu", |
| 179 | "only-i686-pc-windows-gnu", | 180 | "only-i686-pc-windows-gnu", |
| 180 | "only-i686-pc-windows-msvc", | 181 | "only-i686-pc-windows-msvc", |
src/tools/compiletest/src/header/cfg.rs+6| ... | @@ -235,6 +235,12 @@ fn parse_cfg_name_directive<'a>( | ... | @@ -235,6 +235,12 @@ fn parse_cfg_name_directive<'a>( |
| 235 | message: "when the test mode is {name}", | 235 | message: "when the test mode is {name}", |
| 236 | } | 236 | } |
| 237 | 237 | ||
| 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 | |||
| 238 | if prefix == "ignore" && outcome == MatchOutcome::Invalid { | 244 | if prefix == "ignore" && outcome == MatchOutcome::Invalid { |
| 239 | // Don't error out for ignore-tidy-* diretives, as those are not handled by compiletest. | 245 | // Don't error out for ignore-tidy-* diretives, as those are not handled by compiletest. |
| 240 | if name.starts_with("tidy-") { | 246 | if name.starts_with("tidy-") { |
src/tools/opt-dist/src/tests.rs+8-1| ... | @@ -69,6 +69,7 @@ change-id = 115898 | ... | @@ -69,6 +69,7 @@ change-id = 115898 |
| 69 | 69 | ||
| 70 | [rust] | 70 | [rust] |
| 71 | channel = "{channel}" | 71 | channel = "{channel}" |
| 72 | verbose-tests = true | ||
| 72 | 73 | ||
| 73 | [build] | 74 | [build] |
| 74 | rustc = "{rustc}" | 75 | rustc = "{rustc}" |
| ... | @@ -102,13 +103,19 @@ llvm-config = "{llvm_config}" | ... | @@ -102,13 +103,19 @@ llvm-config = "{llvm_config}" |
| 102 | "tests/incremental", | 103 | "tests/incremental", |
| 103 | "tests/mir-opt", | 104 | "tests/mir-opt", |
| 104 | "tests/pretty", | 105 | "tests/pretty", |
| 106 | "tests/run-make/glibc-symbols-x86_64-unknown-linux-gnu", | ||
| 105 | "tests/ui", | 107 | "tests/ui", |
| 106 | "tests/crashes", | 108 | "tests/crashes", |
| 107 | ]; | 109 | ]; |
| 108 | for test_path in env.skipped_tests() { | 110 | for test_path in env.skipped_tests() { |
| 109 | args.extend(["--skip", test_path]); | 111 | args.extend(["--skip", test_path]); |
| 110 | } | 112 | } |
| 111 | cmd(&args).env("COMPILETEST_FORCE_STAGE0", "1").run().context("Cannot execute tests") | 113 | cmd(&args) |
| 114 | .env("COMPILETEST_FORCE_STAGE0", "1") | ||
| 115 | // Also run dist-only tests | ||
| 116 | .env("COMPILETEST_ENABLE_DIST_TESTS", "1") | ||
| 117 | .run() | ||
| 118 | .context("Cannot execute tests") | ||
| 112 | } | 119 | } |
| 113 | 120 | ||
| 114 | /// Tries to find the version of the dist artifacts (either nightly, beta, or 1.XY.Z). | 121 | /// Tries to find the version of the dist artifacts (either nightly, beta, or 1.XY.Z). |
tests/run-make/glibc-symbols-x86_64-unknown-linux-gnu/rmake.rs created+112| ... | @@ -0,0 +1,112 @@ | ||
| 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 that we are *permitted* to use for the | ||
| 15 | // x86_64-unknown-linux-gnu target. | ||
| 16 | // All glibc symbols used in the compiler must be lower or equal than this version. | ||
| 17 | // So that if a given machine only has glibc 2.17, it is able to run the compiler. | ||
| 18 | let max_supported = (2, 17, 99); | ||
| 19 | |||
| 20 | let rustc = PathBuf::from(rustc_path()); | ||
| 21 | // Check symbols directly in rustc | ||
| 22 | check_symbols(&rustc, max_supported); | ||
| 23 | |||
| 24 | // Find dynamic libraries referenced by rustc that come from our lib directory | ||
| 25 | let lib_path = rustc.parent().unwrap().parent().unwrap().join("lib"); | ||
| 26 | let dynamic_libs = find_dynamic_libs(&rustc) | ||
| 27 | .into_iter() | ||
| 28 | .filter_map(|path| path.canonicalize().ok()) | ||
| 29 | .filter(|lib| lib.starts_with(&lib_path)) | ||
| 30 | .collect::<Vec<_>>(); | ||
| 31 | for lib in dynamic_libs { | ||
| 32 | check_symbols(&lib, max_supported); | ||
| 33 | } | ||
| 34 | } | ||
| 35 | |||
| 36 | #[derive(Debug, Ord, PartialOrd, Eq, PartialEq)] | ||
| 37 | struct GlibcSymbol { | ||
| 38 | name: String, | ||
| 39 | version: (u32, u32, u32), | ||
| 40 | } | ||
| 41 | |||
| 42 | fn find_dynamic_libs(path: &Path) -> Vec<PathBuf> { | ||
| 43 | cmd("ldd") | ||
| 44 | .arg(path) | ||
| 45 | .run() | ||
| 46 | .stdout_utf8() | ||
| 47 | .lines() | ||
| 48 | .filter_map(|line| { | ||
| 49 | let line = line.trim(); | ||
| 50 | let Some((_, line)) = line.split_once(" => ") else { | ||
| 51 | return None; | ||
| 52 | }; | ||
| 53 | line.split_ascii_whitespace().next().map(|path| PathBuf::from(path)) | ||
| 54 | }) | ||
| 55 | .collect() | ||
| 56 | } | ||
| 57 | |||
| 58 | fn check_symbols(file: &Path, max_supported: (u32, u32, u32)) { | ||
| 59 | println!("Checking {}", file.display()); | ||
| 60 | let mut invalid: Vec<GlibcSymbol> = get_glibc_symbols(file) | ||
| 61 | .into_iter() | ||
| 62 | .filter(|symbol| symbol.version > max_supported) | ||
| 63 | .collect(); | ||
| 64 | if !invalid.is_empty() { | ||
| 65 | invalid.sort(); | ||
| 66 | panic!( | ||
| 67 | "Found invalid glibc symbols in {}:\n{}", | ||
| 68 | file.display(), | ||
| 69 | invalid | ||
| 70 | .into_iter() | ||
| 71 | .map(|symbol| format!( | ||
| 72 | "{} ({:?} higher than max allowed {:?})", | ||
| 73 | symbol.name, symbol.version, max_supported | ||
| 74 | )) | ||
| 75 | .collect::<Vec<_>>() | ||
| 76 | .join("\n") | ||
| 77 | ) | ||
| 78 | } | ||
| 79 | } | ||
| 80 | |||
| 81 | fn get_glibc_symbols(file: &Path) -> Vec<GlibcSymbol> { | ||
| 82 | let regex = regex::Regex::new(r#"GLIBC_(\d)+\.(\d+)(:?\.(\d+))?"#).unwrap(); | ||
| 83 | |||
| 84 | // FIXME(kobzol): llvm-objdump currently chokes on the BOLTed librustc_driver.so file. | ||
| 85 | // Use objdump instead, since it seems to work, and we only run this test in a specific | ||
| 86 | // CI environment anyway. | ||
| 87 | cmd("objdump") | ||
| 88 | .arg("--dynamic-syms") | ||
| 89 | .arg(file) | ||
| 90 | .run() | ||
| 91 | .stdout_utf8() | ||
| 92 | .lines() | ||
| 93 | .filter_map(|line| { | ||
| 94 | // Example line | ||
| 95 | // 0000000000000000 DF *UND* 0000000000000000 (GLIBC_2.2.5) sbrk | ||
| 96 | let mut parts = line.split(" ").collect::<Vec<_>>().into_iter().rev(); | ||
| 97 | let Some(name) = parts.next() else { | ||
| 98 | return None; | ||
| 99 | }; | ||
| 100 | let Some(lib) = parts.next() else { | ||
| 101 | return None; | ||
| 102 | }; | ||
| 103 | let Some(version) = regex.captures(lib) else { | ||
| 104 | return None; | ||
| 105 | }; | ||
| 106 | let major = version.get(1).and_then(|m| m.as_str().parse().ok()).unwrap_or(0); | ||
| 107 | let minor = version.get(2).and_then(|m| m.as_str().parse().ok()).unwrap_or(0); | ||
| 108 | let patch = version.get(3).and_then(|m| m.as_str().parse().ok()).unwrap_or(0); | ||
| 109 | Some(GlibcSymbol { version: (major, minor, patch), name: name.to_string() }) | ||
| 110 | }) | ||
| 111 | .collect() | ||
| 112 | } | ||