Skip to content

Commit f6d4714

Browse files
committed
Fixes
1 parent 250b8bc commit f6d4714

File tree

2 files changed

+118
-61
lines changed

2 files changed

+118
-61
lines changed

crates/symbol-check/src/main.rs

Lines changed: 116 additions & 59 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};
5-
use std::fs;
67
use std::io::{BufRead, BufReader};
78
use std::path::{Path, PathBuf};
89
use std::process::{Command, Stdio};
10+
use std::{env, fs};
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

@@ -32,52 +36,63 @@ Run the same checks on the given set of paths, without invoking Cargo. Paths
3236
may be either archives or object files.
3337
";
3438

35-
fn main() {
36-
// Create a `&str` vec so we can match on it.
37-
let args = std::env::args().collect::<Vec<_>>();
38-
let args_ref = args.iter().map(String::as_str).collect::<Vec<_>>();
39+
#[derive(Debug, PartialEq)]
40+
enum Mode {
41+
BuildAndCheck,
42+
CheckOnly,
43+
}
3944

40-
match &args_ref[1..] {
41-
["build-and-check", target, "--", args @ ..] if !args.is_empty() => {
42-
run_build_and_check(target, args);
43-
}
44-
["build-and-check", "--", args @ ..] if !args.is_empty() => {
45-
let target = &host_target();
46-
run_build_and_check(target, args);
47-
}
48-
["check", paths @ ..] if !paths.is_empty() => {
49-
check_paths(paths);
50-
}
51-
_ => {
52-
println!("{USAGE}");
53-
std::process::exit(1);
45+
fn main() {
46+
let mut error = false;
47+
let mut target = None;
48+
let mut verify_no_exe = true;
49+
50+
let mut args_iter = env::args().skip(1);
51+
let mode = match args_iter.next() {
52+
Some(arg) if arg == "build-and-check" => Mode::BuildAndCheck,
53+
Some(arg) if arg == "check" => Mode::CheckOnly,
54+
Some(other) => invalid_usage(&format!("unrecognized mode `{other}`")),
55+
None => invalid_usage("mode must be specified"),
56+
};
57+
58+
while let Some(arg) = args_iter.next() {
59+
match arg.as_str() {
60+
"--ignore-exe-stack" => verify_no_exe = false,
61+
"--" => break,
62+
f if f.starts_with("-") => invalid_usage(&format!("unrecognized flag `{f}`")),
63+
tgt if mode == Mode::BuildAndCheck => target = Some(arg),
64+
_ => break,
5465
}
5566
}
56-
}
5767

58-
fn run_build_and_check(target: &str, args: &[&str]) {
59-
// Make sure `--target` isn't passed to avoid confusion (since it should be
60-
// proivded only once, positionally).
61-
for arg in args {
62-
assert!(
63-
!arg.contains("--target"),
64-
"target must be passed positionally. {USAGE}"
65-
);
66-
}
68+
let positional = args_iter.collect::<Vec<_>>();
6769

68-
let paths = exec_cargo_with_args(target, args);
69-
check_paths(&paths);
70+
match mode {
71+
Mode::BuildAndCheck => {
72+
let target = target.unwrap_or_else(|| host_target());
73+
let paths = exec_cargo_with_args(&target, positional.as_slice());
74+
check_paths(&paths, verify_no_exe);
75+
}
76+
Mode::CheckOnly => check_paths(&positional, verify_no_exe),
77+
};
7078
}
7179

72-
fn check_paths<P: AsRef<Path>>(paths: &[P]) {
80+
fn invalid_usage(s: &str) -> ! {
81+
println!("{s}\n{USAGE}");
82+
std::process::exit(1);
83+
}
84+
85+
fn check_paths<P: AsRef<Path>>(paths: &[P], verify_no_exe: bool) {
7386
for path in paths {
7487
let path = path.as_ref();
7588
println!("Checking {}", path.display());
7689
let archive = BinFile::from_path(path);
7790

78-
verify_no_duplicates(&archive);
79-
verify_core_symbols(&archive);
80-
verify_no_exec_stack(&archive);
91+
// verify_no_duplicates(&archive);
92+
// verify_core_symbols(&archive);
93+
if verify_no_exe {
94+
verify_no_exec_stack(&archive);
95+
}
8196
}
8297
}
8398

@@ -97,15 +112,15 @@ fn host_target() -> String {
97112

98113
/// Run `cargo build` with the provided additional arguments, collecting the list of created
99114
/// libraries.
100-
fn exec_cargo_with_args(target: &str, args: &[&str]) -> Vec<PathBuf> {
115+
fn exec_cargo_with_args<S: AsRef<str>>(target: &str, args: &[S]) -> Vec<PathBuf> {
101116
let mut cmd = Command::new("cargo");
102117
cmd.args([
103118
"build",
104119
"--target",
105120
target,
106121
"--message-format=json-diagnostic-rendered-ansi",
107122
])
108-
.args(args)
123+
.args(args.iter().map(|arg| arg.as_ref()))
109124
.stdout(Stdio::piped());
110125

111126
println!("running: {cmd:?}");
@@ -300,18 +315,7 @@ fn verify_core_symbols(archive: &BinFile) {
300315
println!(" success: no undefined references to core found");
301316
}
302317

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>.
318+
/// Ensure that the object/archive will not require an executable stack.
315319
fn verify_no_exec_stack(archive: &BinFile) {
316320
let mut problem_objfiles = Vec::new();
317321

@@ -322,42 +326,91 @@ fn verify_no_exec_stack(archive: &BinFile) {
322326
});
323327

324328
if !problem_objfiles.is_empty() {
325-
panic!("the following archive members require an executable stack: {problem_objfiles:#?}");
329+
panic!("the following object files require an executable stack: {problem_objfiles:#?}");
326330
}
327331

328-
println!(" success: no writeable-executable sections found");
332+
println!(" success: no writeable+executable sections found");
329333
}
330334

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

359+
let mut return_immediate = None;
337360
let mut has_exe_sections = false;
338361
for sec in obj.sections() {
339362
let SectionFlags::Elf { sh_flags } = sec.flags() else {
340363
unreachable!("only elf files are being checked");
341364
};
342365

343-
let exe = (sh_flags & elf::SHF_EXECINSTR as u64) != 0;
366+
if sec.kind() == SectionKind::Elf(elf::SHT_ARM_ATTRIBUTES) {
367+
let data = sec.data().unwrap();
368+
parse_arm_thing(data);
369+
}
370+
371+
let is_exe = (sh_flags & elf::SHF_EXECINSTR as u64) != 0;
344372

345373
// If the magic section is present, its exe bit tells us whether or not the object
346374
// file requires an executable stack.
347375
if sec.name().unwrap_or_default() == ".note.GNU-stack" {
348-
return exe;
376+
return_immediate = Some(is_exe);
349377
}
350378

351379
// Otherwise, just keep track of whether or not we have exeuctable sections
352-
has_exe_sections |= exe;
380+
has_exe_sections |= is_exe;
381+
}
382+
383+
if let Some(imm) = return_immediate {
384+
return imm;
353385
}
354386

355387
// Ignore object files that have no executable sections, like rmeta
356388
if !has_exe_sections {
357389
return false;
358390
}
359391

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

363416
/// Thin wrapper for owning data used by `object`.
@@ -448,8 +501,12 @@ fn check_no_gnu_stack_obj() {
448501
}
449502

450503
#[test]
451-
#[cfg_attr(not(target_env = "gnu"), ignore = "requires a gnu toolchain to build")]
504+
#[cfg_attr(
505+
any(target_os = "windows", target_vendor = "apple"),
506+
ignore = "requires elf format"
507+
)]
452508
fn check_obj() {
509+
#[expect(clippy::option_env_unwrap, reason = "test is ignored")]
453510
let p = option_env!("HAS_EXE_STACK_OBJ").expect("has_exe_stack.o not present");
454511
let f = fs::read(p).unwrap();
455512
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)