about summary refs log tree commit diff
path: root/tests/run-make/glibc-symbols-x86_64-unknown-linux-gnu/rmake.rs
blob: 0134457c5c28ee8db53860fdc17b2058ee2f5621 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// 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::<Vec<_>>();
    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<PathBuf> {
    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<GlibcSymbol> = 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::<Vec<_>>()
                .join("\n")
        )
    }
}

fn get_glibc_symbols(file: &Path) -> Vec<GlibcSymbol> {
    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::<Vec<_>>().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()
}