// Check that the compiler toolchain (rustc) that we distribute is not using newer glibc // symbols than a specified minimum. // This test should only be executed on an extracted dist archive or in a dist-* CI job. //@ only-dist //@ only-x86_64-unknown-linux-gnu //@ ignore-cross-compile use std::path::{Path, PathBuf}; use run_make_support::{cmd, llvm_objdump, regex, rustc_path}; fn main() { // This is the maximum glibc version that we are *permitted* to use for the // x86_64-unknown-linux-gnu target. // All glibc symbols used in the compiler must be lower or equal than this version. // So that if a given machine only has glibc 2.17, it is able to run the compiler. let max_supported = (2, 17, 99); let rustc = PathBuf::from(rustc_path()); // Check symbols directly in rustc check_symbols(&rustc, max_supported); // Find dynamic libraries referenced by rustc that come from our lib directory let lib_path = rustc.parent().unwrap().parent().unwrap().join("lib"); let dynamic_libs = find_dynamic_libs(&rustc) .into_iter() .filter_map(|path| path.canonicalize().ok()) .filter(|lib| lib.starts_with(&lib_path)) .collect::>(); for lib in dynamic_libs { check_symbols(&lib, max_supported); } } #[derive(Debug, Ord, PartialOrd, Eq, PartialEq)] struct GlibcSymbol { name: String, version: (u32, u32, u32), } fn find_dynamic_libs(path: &Path) -> Vec { cmd("ldd") .arg(path) .run() .stdout_utf8() .lines() .filter_map(|line| { let line = line.trim(); let Some((_, line)) = line.split_once(" => ") else { return None; }; line.split_ascii_whitespace().next().map(|path| PathBuf::from(path)) }) .collect() } fn check_symbols(file: &Path, max_supported: (u32, u32, u32)) { println!("Checking {}", file.display()); let mut invalid: Vec = get_glibc_symbols(file) .into_iter() .filter(|symbol| symbol.version > max_supported) .collect(); if !invalid.is_empty() { invalid.sort(); panic!( "Found invalid glibc symbols in {}:\n{}", file.display(), invalid .into_iter() .map(|symbol| format!( "{} ({:?} higher than max allowed {:?})", symbol.name, symbol.version, max_supported )) .collect::>() .join("\n") ) } } fn get_glibc_symbols(file: &Path) -> Vec { let regex = regex::Regex::new(r#"GLIBC_(\d)+\.(\d+)(:?\.(\d+))?"#).unwrap(); // FIXME(kobzol): llvm-objdump currently chokes on the BOLTed librustc_driver.so file. // Use objdump instead, since it seems to work, and we only run this test in a specific // CI environment anyway. cmd("objdump") .arg("--dynamic-syms") .arg(file) .run() .stdout_utf8() .lines() .filter_map(|line| { // Example line // 0000000000000000 DF *UND* 0000000000000000 (GLIBC_2.2.5) sbrk let mut parts = line.split(" ").collect::>().into_iter().rev(); let Some(name) = parts.next() else { return None; }; let Some(lib) = parts.next() else { return None; }; let Some(version) = regex.captures(lib) else { return None; }; let major = version.get(1).and_then(|m| m.as_str().parse().ok()).unwrap_or(0); let minor = version.get(2).and_then(|m| m.as_str().parse().ok()).unwrap_or(0); let patch = version.get(3).and_then(|m| m.as_str().parse().ok()).unwrap_or(0); Some(GlibcSymbol { version: (major, minor, patch), name: name.to_string() }) }) .collect() }