From: Daan De Meyer Date: Sun, 24 May 2026 11:41:30 +0000 (+0000) Subject: test-link-abi: fall back to lowest observed glibc version on new architectures X-Git-Tag: v261-rc2~41 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=99d16bbca580f50d9646fc296cb6f89cd3696a3b;p=thirdparty%2Fsystemd.git test-link-abi: fall back to lowest observed glibc version on new architectures On architectures where glibc support was introduced after our baseline (e.g. loongarch64, added in glibc 2.36), every imported symbol is tagged with a version newer than the baseline, so the baseline check fails spuriously. Detect this by taking the minimum version observed across all binaries: if it exceeds the baseline, treat that minimum as the effective baseline instead. Follow up for d9600a2a Co-developed-by: Claude Opus 4.7 --- diff --git a/test/test-link-abi.py b/test/test-link-abi.py index 8a935a513fe..f069060e4fe 100755 --- a/test/test-link-abi.py +++ b/test/test-link-abi.py @@ -86,15 +86,16 @@ def is_libc(name: str) -> bool: return name.startswith(LIBC_LIB_PREFIXES) -def glibc_violations(path: str, baseline: Version) -> list[tuple[str, str]]: - """Return a sorted list of (symbol, "GLIBC_X.Y") pairs above the baseline.""" +def glibc_imports(path: str) -> list[tuple[str, str, Version]]: + """Return a sorted list of (symbol, "GLIBC_X.Y", version) triples for every + imported GLIBC-versioned symbol referenced by ``path``.""" out = subprocess.check_output( ['readelf', '-W', '--dyn-syms', path], text=True, stderr=subprocess.DEVNULL, ) - found: set[tuple[str, str]] = set() + found: set[tuple[str, str, Version]] = set() for line in out.splitlines(): m = GLIBC_RE.search(line) if not m: @@ -106,10 +107,8 @@ def glibc_violations(path: str, baseline: Version) -> list[tuple[str, str]]: if len(parts) < 8 or parts[6] != 'UND': continue ver: Version = (int(m.group(1)), int(m.group(2))) - if ver <= baseline: - continue sym = next((t for t in parts if '@' in t), line.strip()) - found.add((sym, m.group(0))) + found.add((sym, m.group(0), ver)) return sorted(found) @@ -150,23 +149,53 @@ def main() -> int: args = ap.parse_args() baseline: Version = args.baseline - checked = 0 - failed = 0 + + # First pass: collect all imports and NEEDED entries so we can derive an + # effective baseline before reporting any violations. + imports: dict[str, list[tuple[str, str, Version]]] = {} + needed: dict[str, list[str]] = {} for path in args.paths: if not is_elf(path): # Some inputs passed by meson may be scripts or non-ELF # generators; silently skip those rather than fail. continue - checked += 1 + imports[path] = glibc_imports(path) + needed[path] = needed_violations(path) + + # On architectures where glibc support was added after our baseline (e.g. + # loongarch64, introduced in glibc 2.36), every imported symbol will be + # tagged with a version newer than the baseline, so the baseline check + # would spuriously fail. Detect this by taking the minimum version observed + # across all binaries: if it exceeds the baseline, treat that minimum as + # the effective baseline instead. + observed = [v for syms in imports.values() for _, _, v in syms] + effective_baseline = baseline + if observed: + observed_min = min(observed) + if observed_min > baseline: + effective_baseline = observed_min + print( + f'Note: lowest observed GLIBC import is ' + f'GLIBC_{observed_min[0]}.{observed_min[1]}, newer than the requested baseline ' + f'GLIBC_{baseline[0]}.{baseline[1]}; assuming this architecture has no older ' + f'symbols available and using GLIBC_{effective_baseline[0]}.{effective_baseline[1]} ' + f'as the effective baseline.', + file=sys.stderr, + ) - glibc_bad = glibc_violations(path, baseline) - needed_bad = needed_violations(path) + failed = 0 + for path, syms in imports.items(): + glibc_bad = sorted({(sym, ver_str) for sym, ver_str, ver in syms if ver > effective_baseline}) + needed_bad = needed[path] if glibc_bad or needed_bad: failed += 1 print(f'{path}:', file=sys.stderr) if glibc_bad: - print(f' imports symbols newer than GLIBC_{baseline[0]}.{baseline[1]}:', file=sys.stderr) + print( + f' imports symbols newer than GLIBC_{effective_baseline[0]}.{effective_baseline[1]}:', + file=sys.stderr, + ) for sym, ver_str in glibc_bad: print(f' {sym} ({ver_str})', file=sys.stderr) if needed_bad: @@ -174,7 +203,8 @@ def main() -> int: for name in needed_bad: print(f' {name}', file=sys.stderr) - baseline_str = f'GLIBC_{baseline[0]}.{baseline[1]}' + checked = len(imports) + baseline_str = f'GLIBC_{effective_baseline[0]}.{effective_baseline[1]}' if failed: print(f'\nFAIL: {failed} of {checked} ELF objects failed the ABI checks.', file=sys.stderr) return 1