Skip to content

Commit 033e5e2

Browse files
committed
Fixes
1 parent 250b8bc commit 033e5e2

File tree

2 files changed

+75
-26
lines changed

2 files changed

+75
-26
lines changed

crates/symbol-check/src/main.rs

Lines changed: 73 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
//! Tool used by CI to inspect compiler-builtins archives and help ensure we won't run into any
22
//! linking errors.
33
4+
#![allow(unused)] // TODO
5+
46
use std::collections::{BTreeMap, BTreeSet};
57
use std::fs;
68
use std::io::{BufRead, BufReader};
79
use std::path::{Path, PathBuf};
810
use std::process::{Command, Stdio};
911

1012
use object::read::archive::ArchiveFile;
13+
use object::read::elf::SectionHeader;
1114
use object::{
12-
BinaryFormat, File as ObjFile, Object, ObjectSection, ObjectSymbol, Result as ObjResult,
13-
SectionFlags, Symbol, SymbolKind, SymbolScope, elf,
15+
Architecture, BinaryFormat, Bytes, Endianness, File as ObjFile, LittleEndian, Object,
16+
ObjectSection, ObjectSymbol, Result as ObjResult, SectionFlags, SectionKind, Symbol,
17+
SymbolKind, SymbolScope, elf,
1418
};
1519
use serde_json::Value;
1620

@@ -45,6 +49,9 @@ fn main() {
4549
let target = &host_target();
4650
run_build_and_check(target, args);
4751
}
52+
["check", "--ignore-exe-stack", paths @ ..] if !paths.is_empty() => {
53+
check_paths(paths);
54+
}
4855
["check", paths @ ..] if !paths.is_empty() => {
4956
check_paths(paths);
5057
}
@@ -75,8 +82,8 @@ fn check_paths<P: AsRef<Path>>(paths: &[P]) {
7582
println!("Checking {}", path.display());
7683
let archive = BinFile::from_path(path);
7784

78-
verify_no_duplicates(&archive);
79-
verify_core_symbols(&archive);
85+
// verify_no_duplicates(&archive);
86+
// verify_core_symbols(&archive);
8087
verify_no_exec_stack(&archive);
8188
}
8289
}
@@ -300,18 +307,7 @@ fn verify_core_symbols(archive: &BinFile) {
300307
println!(" success: no undefined references to core found");
301308
}
302309

303-
/// Check that all object files contain a section named `.note.GNU-stack`, indicating a
304-
/// nonexecutable stack.
305-
///
306-
/// Paraphrased from <https://www.man7.org/linux/man-pages/man1/ld.1.html>:
307-
///
308-
/// - A `.note.GNU-stack` section with the exe flag means this needs an executable stack
309-
/// - A `.note.GNU-stack` section without the exe flag means there is no executable stack needed
310-
/// - Without the section, behavior is target-specific and on some targets means an executable
311-
/// stack is required.
312-
///
313-
/// Now says
314-
/// deprecated <https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=0d38576a34ec64a1b4500c9277a8e9d0f07e6774>.
310+
/// Ensure that the object/archive will not require an executable stack.
315311
fn verify_no_exec_stack(archive: &BinFile) {
316312
let mut problem_objfiles = Vec::new();
317313

@@ -322,42 +318,91 @@ fn verify_no_exec_stack(archive: &BinFile) {
322318
});
323319

324320
if !problem_objfiles.is_empty() {
325-
panic!("the following archive members require an executable stack: {problem_objfiles:#?}");
321+
panic!("the following object files require an executable stack: {problem_objfiles:#?}");
326322
}
327323

328-
println!(" success: no writeable-executable sections found");
324+
println!(" success: no writeable+executable sections found");
329325
}
330326

327+
/// True if the section/flag combination indicates that the object file should be linked with an
328+
/// executable stack.
329+
///
330+
/// Paraphrased from <https://www.man7.org/linux/man-pages/man1/ld.1.html>:
331+
///
332+
/// - A `.note.GNU-stack` section with the exe flag means this needs an executable stack
333+
/// - A `.note.GNU-stack` section without the exe flag means there is no executable stack needed
334+
/// - Without the section, behavior is target-specific and on some targets means an executable
335+
/// stack is required.
336+
///
337+
/// If any object files meet the requirements for an executable stack, any final binary that links
338+
/// it will have a program header with a `PT_GNU_STACK` section, which will be marked `RWE` rather
339+
/// than the desired `RW`. (We don't check final binaries).
340+
///
341+
/// Per [1], it is now deprecated behavior for a missing `.note.GNU-stack` section to imply an
342+
/// executable stack. However, we shouldn't assume that tooling has caught up to this.
343+
///
344+
/// [1]: https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=0d38576a34ec64a1b4500c9277a8e9d0f07e6774>
331345
fn obj_requires_exe_stack(obj: &ObjFile) -> bool {
332-
// Files other than elf likely do not use the same convention.
346+
// Files other than elf do not use the same convention.
333347
if obj.format() != BinaryFormat::Elf {
334348
return false;
335349
}
336350

351+
let mut return_immediate = None;
337352
let mut has_exe_sections = false;
338353
for sec in obj.sections() {
339354
let SectionFlags::Elf { sh_flags } = sec.flags() else {
340355
unreachable!("only elf files are being checked");
341356
};
342357

343-
let exe = (sh_flags & elf::SHF_EXECINSTR as u64) != 0;
358+
if sec.kind() == SectionKind::Elf(elf::SHT_ARM_ATTRIBUTES) {
359+
let data = sec.data().unwrap();
360+
parse_arm_thing(data);
361+
}
362+
363+
let is_exe = (sh_flags & elf::SHF_EXECINSTR as u64) != 0;
344364

345365
// If the magic section is present, its exe bit tells us whether or not the object
346366
// file requires an executable stack.
347367
if sec.name().unwrap_or_default() == ".note.GNU-stack" {
348-
return exe;
368+
return_immediate = Some(is_exe);
349369
}
350370

351371
// Otherwise, just keep track of whether or not we have exeuctable sections
352-
has_exe_sections |= exe;
372+
has_exe_sections |= is_exe;
373+
}
374+
375+
if let Some(imm) = return_immediate {
376+
return imm;
353377
}
354378

355379
// Ignore object files that have no executable sections, like rmeta
356380
if !has_exe_sections {
357381
return false;
358382
}
359383

360-
true
384+
platform_default_exe_stack_required(obj.architecture())
385+
}
386+
387+
/// Default if there is no `.note.GNU-stack` section.
388+
fn platform_default_exe_stack_required(arch: Architecture) -> bool {
389+
match arch {
390+
// PPC64 doesn't set `.note.GNU-stack` since GNU nested functions don't need a trampoline,
391+
// <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=21098>.
392+
Architecture::PowerPc64 => false,
393+
_ => true,
394+
}
395+
}
396+
397+
fn parse_arm_thing(data: &[u8]) {
398+
eprintln!("data: {data:x?}");
399+
eprintln!("data string: {:?}", String::from_utf8_lossy(data));
400+
eprintln!("data: {:x?}", &data[16..]);
401+
// let mut rest = &data[16..];
402+
let mut b = Bytes(data);
403+
b.skip(16).unwrap();
404+
405+
// while !rest.is_empty() {}
361406
}
362407

363408
/// Thin wrapper for owning data used by `object`.
@@ -448,8 +493,12 @@ fn check_no_gnu_stack_obj() {
448493
}
449494

450495
#[test]
451-
#[cfg_attr(not(target_env = "gnu"), ignore = "requires a gnu toolchain to build")]
496+
#[cfg_attr(
497+
any(target_os = "windows", target_vendor = "apple"),
498+
ignore = "requires elf format"
499+
)]
452500
fn check_obj() {
501+
#[expect(clippy::option_env_unwrap, reason = "test is ignored")]
453502
let p = option_env!("HAS_EXE_STACK_OBJ").expect("has_exe_stack.o not present");
454503
let f = fs::read(p).unwrap();
455504
let obj = ObjFile::parse(f.as_slice()).unwrap();

crates/symbol-check/tests/has_exe_stack.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
/* GNU nested functions are the only way I could fine to force an executable
2-
* stack. Supported by GCC only, not Clang. */
1+
/* GNU nested functions are the only way I could find to force an explicitly
2+
* executable stack. Supported by GCC only, not Clang. */
33

44
void intermediate(void (*)(int, int), int);
55

0 commit comments

Comments
 (0)