From 79bb9f71e7e5fa0b2acfb0ed51a2a2e7e116cc28 Mon Sep 17 00:00:00 2001 From: Aelin Date: Tue, 26 May 2026 13:03:19 +0200 Subject: [PATCH] symcheck: Only ELFv1 ppc64 doesn't set `.note.GNU-stack` `powerpc64-unknown-linux-gnu` currently is an ELFv1 target, while `powerpc64-unknown-linux-musl` is an ELFv2 target. Big-endian and little-endian ELFv2 targets both behave normally: they emit `.note.GNU-stack`. Therefore, currently the tests would fail on big-endian powerpc64 with ELFv2 ABI. To determine whether we need to special-case powerpc64, we should check the ABI instead of the endianness. The problem here is that the `e_flags` part of the ELF header is actually `0` in the output of `cc -O0 -ffunction-sections -fdata-sections -fPIC -m64 -mabi=elfv2 -Wall -Wextra -o missing_gnu_stack_section.o -c missing_gnu_stack_section.S`, the output of that command is bit-for-bit identical on ELFv1 and ELFv2 hosts. In order to know when to allow an unset `.note.GNU-stack` we therefore must set `.abiversion 2` to be able to tell them apart from the ELF header. This makes all tests pass on `powerpc64-unknown-linux-musl`. --- .../crates/symbol-check/src/main.rs | 13 ++++++++----- .../crates/symbol-check/tests/all.rs | 15 +++++++++++---- .../tests/input/missing_gnu_stack_section.S | 6 ++++++ 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/library/compiler-builtins/crates/symbol-check/src/main.rs b/library/compiler-builtins/crates/symbol-check/src/main.rs index 135019e5f729..11486b6ff113 100644 --- a/library/compiler-builtins/crates/symbol-check/src/main.rs +++ b/library/compiler-builtins/crates/symbol-check/src/main.rs @@ -13,8 +13,8 @@ use object::read::archive::ArchiveFile; use object::{ - Architecture, BinaryFormat, Endianness, File as ObjFile, Object, ObjectSection, ObjectSymbol, - Result as ObjResult, SectionFlags, Symbol, SymbolKind, SymbolScope, U32, elf, + Architecture, BinaryFormat, Endianness, File as ObjFile, FileFlags, Object, ObjectSection, + ObjectSymbol, Result as ObjResult, SectionFlags, Symbol, SymbolKind, SymbolScope, U32, elf, }; use regex::Regex; use serde_json::Value; @@ -526,12 +526,15 @@ fn check_elf_exe_stack(obj: &ObjFile) -> Result<(), ExeStack> { return Ok(()); } + let FileFlags::Elf { e_flags, .. } = obj.flags() else { + unreachable!("only elf files are being checked"); + }; + // If there is no `.note.GNU-stack` and no executable sections, behavior differs by platform. match obj.architecture() { // PPC64 doesn't set `.note.GNU-stack` since GNU nested functions don't need a trampoline, - // . From experimentation, it seems - // like this only applies to big endian. - Architecture::PowerPc64 if obj.endianness() == Endianness::Big => Ok(()), + // . This only applies to ELFv1. + Architecture::PowerPc64 if e_flags & elf::EF_PPC64_ABI != 2 => Ok(()), _ => Err(ExeStack::MissingGnuStackSec), } diff --git a/library/compiler-builtins/crates/symbol-check/tests/all.rs b/library/compiler-builtins/crates/symbol-check/tests/all.rs index cfc8641aca82..d038fd0dc935 100644 --- a/library/compiler-builtins/crates/symbol-check/tests/all.rs +++ b/library/compiler-builtins/crates/symbol-check/tests/all.rs @@ -111,8 +111,11 @@ fn test_missing_gnu_stack_section() { let assert = t.symcheck_exe().arg(obj).arg("--no-visibility").assert(); - if t.is_ppc64be() || t.no_os() || t.binary_obj_format() != BinaryFormat::Elf { - // Ppc64be doesn't emit `.note.GNU-stack`, not relevant without an OS, and non-elf + if (t.is_ppc64be() && t.is_glibc()) + || t.no_os() + || t.binary_obj_format() != BinaryFormat::Elf + { + // Ppc64be ELFv1 doesn't emit `.note.GNU-stack`, not relevant without an OS, and non-elf // targets don't use `.note.GNU-stack`. assert.success(); return; @@ -143,8 +146,8 @@ fn test_exe_gnu_stack_section() { let assert = t.symcheck_exe().arg(obj).arg("--no-visibility").assert(); - if t.is_ppc64be() || t.no_os() { - // Ppc64be doesn't emit `.note.GNU-stack`, not relevant without an OS. + if (t.is_ppc64be() && t.is_glibc()) || t.no_os() { + // Ppc64be ELFv1 doesn't emit `.note.GNU-stack`, not relevant without an OS. assert.success(); return; } @@ -316,6 +319,10 @@ fn is_ppc64be(&self) -> bool { self.triple.starts_with("powerpc64-") } + fn is_glibc(&self) -> bool { + self.triple.contains("-linux-gnu") + } + /// True if the target needs `--no-os` passed to symcheck. fn no_os(&self) -> bool { self.triple.contains("-none") diff --git a/library/compiler-builtins/crates/symbol-check/tests/input/missing_gnu_stack_section.S b/library/compiler-builtins/crates/symbol-check/tests/input/missing_gnu_stack_section.S index 09bb761c40ba..99ac3ba51c47 100644 --- a/library/compiler-builtins/crates/symbol-check/tests/input/missing_gnu_stack_section.S +++ b/library/compiler-builtins/crates/symbol-check/tests/input/missing_gnu_stack_section.S @@ -4,6 +4,12 @@ * executability is implied (usually yes on Linux). */ +/* Unless we set the ABI version, we have no way to differentiate between + big-endian ppc64 ELFv1 or ELFv2 since e_flags is otherwise unspecified */ +#if defined(__powerpc64__) && _CALL_ELF == 2 +.abiversion 2 +#endif + .global func #ifdef __wasm__