22//! linking errors.
33
44use std:: collections:: { BTreeMap , BTreeSet } ;
5- use std:: fs;
65use std:: io:: { BufRead , BufReader } ;
76use std:: path:: { Path , PathBuf } ;
87use std:: process:: { Command , Stdio } ;
8+ use std:: { env, fs} ;
99
1010use object:: read:: archive:: ArchiveFile ;
1111use object:: {
12- BinaryFormat , File as ObjFile , Object , ObjectSection , ObjectSymbol , Result as ObjResult ,
13- SectionFlags , Symbol , SymbolKind , SymbolScope , elf,
12+ Architecture , BinaryFormat , Bytes , Endianness , File as ObjFile , Object , ObjectSection ,
13+ ObjectSymbol , Result as ObjResult , SectionFlags , SectionKind , Symbol , SymbolKind , SymbolScope ,
14+ elf,
1415} ;
1516use serde_json:: Value ;
1617
@@ -19,65 +20,78 @@ const CHECK_EXTENSIONS: &[Option<&str>] = &[Some("rlib"), Some("a"), Some("exe")
1920
2021const USAGE : & str = "Usage:
2122
22- symbol-check build-and-check [TARGET] -- CARGO_BUILD_ARGS ...
23+ symbol-check build-and-check [TARGET] [--no-std] -- CARGO_BUILD_ARGS ...
2324
2425Cargo will get invoked with `CARGO_ARGS` and the specified target. All output
2526`compiler_builtins*.rlib` files will be checked.
2627
2728If TARGET is not specified, the host target is used.
2829
29- check PATHS ...
30+ If the `--no-std` flag is passed, the binaries will not be checked for
31+ executable stacks under the assumption that they are not being emitted.
32+
33+ check [--no-std] PATHS ...
3034
3135Run the same checks on the given set of paths, without invoking Cargo. Paths
3236may 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 args_iter = env:: args ( ) . skip ( 1 ) ;
47+ let mode = match args_iter. next ( ) {
48+ Some ( arg) if arg == "build-and-check" => Mode :: BuildAndCheck ,
49+ Some ( arg) if arg == "check" => Mode :: CheckOnly ,
50+ Some ( other) => invalid_usage ( & format ! ( "unrecognized mode `{other}`" ) ) ,
51+ None => invalid_usage ( "mode must be specified" ) ,
52+ } ;
53+
54+ let mut target = None ;
55+ let mut verify_no_exe = true ;
56+
57+ for arg in args_iter. by_ref ( ) {
58+ match arg. as_str ( ) {
59+ "--no-std" => verify_no_exe = false ,
60+ "--" => break ,
61+ f if f. starts_with ( "-" ) => invalid_usage ( & format ! ( "unrecognized flag `{f}`" ) ) ,
62+ _ if mode == Mode :: BuildAndCheck => target = Some ( arg) ,
63+ _ => break ,
5464 }
5565 }
56- }
5766
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- }
67+ let positional = args_iter. collect :: < Vec < _ > > ( ) ;
6768
68- let paths = exec_cargo_with_args ( target, args) ;
69- check_paths ( & paths) ;
69+ match mode {
70+ Mode :: BuildAndCheck => {
71+ let target = target. unwrap_or_else ( || host_target ( ) ) ;
72+ let paths = exec_cargo_with_args ( & target, positional. as_slice ( ) ) ;
73+ check_paths ( & paths, verify_no_exe) ;
74+ }
75+ Mode :: CheckOnly => check_paths ( & positional, verify_no_exe) ,
76+ } ;
7077}
7178
72- fn check_paths < P : AsRef < Path > > ( paths : & [ P ] ) {
79+ fn invalid_usage ( s : & str ) -> ! {
80+ println ! ( "{s}\n {USAGE}" ) ;
81+ std:: process:: exit ( 1 ) ;
82+ }
83+
84+ fn check_paths < P : AsRef < Path > > ( paths : & [ P ] , verify_no_exe : bool ) {
7385 for path in paths {
7486 let path = path. as_ref ( ) ;
7587 println ! ( "Checking {}" , path. display( ) ) ;
7688 let archive = BinFile :: from_path ( path) ;
7789
78- verify_no_duplicates ( & archive) ;
79- verify_core_symbols ( & archive) ;
80- verify_no_exec_stack ( & archive) ;
90+ // verify_no_duplicates(&archive);
91+ // verify_core_symbols(&archive);
92+ if verify_no_exe {
93+ verify_no_exec_stack ( & archive) ;
94+ }
8195 }
8296}
8397
@@ -97,15 +111,15 @@ fn host_target() -> String {
97111
98112/// Run `cargo build` with the provided additional arguments, collecting the list of created
99113/// libraries.
100- fn exec_cargo_with_args ( target : & str , args : & [ & str ] ) -> Vec < PathBuf > {
114+ fn exec_cargo_with_args < S : AsRef < str > > ( target : & str , args : & [ S ] ) -> Vec < PathBuf > {
101115 let mut cmd = Command :: new ( "cargo" ) ;
102116 cmd. args ( [
103117 "build" ,
104118 "--target" ,
105119 target,
106120 "--message-format=json-diagnostic-rendered-ansi" ,
107121 ] )
108- . args ( args)
122+ . args ( args. iter ( ) . map ( |arg| arg . as_ref ( ) ) )
109123 . stdout ( Stdio :: piped ( ) ) ;
110124
111125 println ! ( "running: {cmd:?}" ) ;
@@ -300,18 +314,7 @@ fn verify_core_symbols(archive: &BinFile) {
300314 println ! ( " success: no undefined references to core found" ) ;
301315}
302316
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>.
317+ /// Ensure that the object/archive will not require an executable stack.
315318fn verify_no_exec_stack ( archive : & BinFile ) {
316319 let mut problem_objfiles = Vec :: new ( ) ;
317320
@@ -322,42 +325,91 @@ fn verify_no_exec_stack(archive: &BinFile) {
322325 } ) ;
323326
324327 if !problem_objfiles. is_empty ( ) {
325- panic ! ( "the following archive members require an executable stack: {problem_objfiles:#?}" ) ;
328+ panic ! ( "the following object files require an executable stack: {problem_objfiles:#?}" ) ;
326329 }
327330
328- println ! ( " success: no writeable- executable sections found" ) ;
331+ println ! ( " success: no writeable+ executable sections found" ) ;
329332}
330333
334+ /// True if the section/flag combination indicates that the object file should be linked with an
335+ /// executable stack.
336+ ///
337+ /// Paraphrased from <https://www.man7.org/linux/man-pages/man1/ld.1.html>:
338+ ///
339+ /// - A `.note.GNU-stack` section with the exe flag means this needs an executable stack
340+ /// - A `.note.GNU-stack` section without the exe flag means there is no executable stack needed
341+ /// - Without the section, behavior is target-specific and on some targets means an executable
342+ /// stack is required.
343+ ///
344+ /// If any object files meet the requirements for an executable stack, any final binary that links
345+ /// it will have a program header with a `PT_GNU_STACK` section, which will be marked `RWE` rather
346+ /// than the desired `RW`. (We don't check final binaries).
347+ ///
348+ /// Per [1], it is now deprecated behavior for a missing `.note.GNU-stack` section to imply an
349+ /// executable stack. However, we shouldn't assume that tooling has caught up to this.
350+ ///
351+ /// [1]: https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=0d38576a34ec64a1b4500c9277a8e9d0f07e6774>
331352fn obj_requires_exe_stack ( obj : & ObjFile ) -> bool {
332- // Files other than elf likely do not use the same convention.
353+ // Files other than elf do not use the same convention.
333354 if obj. format ( ) != BinaryFormat :: Elf {
334355 return false ;
335356 }
336357
358+ let mut return_immediate = None ;
337359 let mut has_exe_sections = false ;
338360 for sec in obj. sections ( ) {
339361 let SectionFlags :: Elf { sh_flags } = sec. flags ( ) else {
340362 unreachable ! ( "only elf files are being checked" ) ;
341363 } ;
342364
343- let exe = ( sh_flags & elf:: SHF_EXECINSTR as u64 ) != 0 ;
365+ if sec. kind ( ) == SectionKind :: Elf ( elf:: SHT_ARM_ATTRIBUTES ) {
366+ let data = sec. data ( ) . unwrap ( ) ;
367+ parse_arm_thing ( data) ;
368+ }
369+
370+ let is_exe = ( sh_flags & elf:: SHF_EXECINSTR as u64 ) != 0 ;
344371
345372 // If the magic section is present, its exe bit tells us whether or not the object
346373 // file requires an executable stack.
347374 if sec. name ( ) . unwrap_or_default ( ) == ".note.GNU-stack" {
348- return exe ;
375+ return_immediate = Some ( is_exe ) ;
349376 }
350377
351378 // Otherwise, just keep track of whether or not we have exeuctable sections
352- has_exe_sections |= exe;
379+ has_exe_sections |= is_exe;
380+ }
381+
382+ if let Some ( imm) = return_immediate {
383+ return imm;
353384 }
354385
355386 // Ignore object files that have no executable sections, like rmeta
356387 if !has_exe_sections {
357388 return false ;
358389 }
359390
360- true
391+ platform_default_exe_stack_required ( obj. architecture ( ) , obj. endianness ( ) )
392+ }
393+
394+ /// Default if there is no `.note.GNU-stack` section.
395+ fn platform_default_exe_stack_required ( arch : Architecture , end : Endianness ) -> bool {
396+ match arch {
397+ // PPC64 doesn't set `.note.GNU-stack` since GNU nested functions don't need a trampoline,
398+ // <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=21098>.
399+ Architecture :: PowerPc64 if end == Endianness :: Big => false ,
400+ _ => true ,
401+ }
402+ }
403+
404+ fn parse_arm_thing ( data : & [ u8 ] ) {
405+ eprintln ! ( "data: {data:x?}" ) ;
406+ eprintln ! ( "data string: {:?}" , String :: from_utf8_lossy( data) ) ;
407+ eprintln ! ( "data: {:x?}" , & data[ 16 ..] ) ;
408+ // let mut rest = &data[16..];
409+ let mut b = Bytes ( data) ;
410+ b. skip ( 16 ) . unwrap ( ) ;
411+
412+ // while !rest.is_empty() {}
361413}
362414
363415/// Thin wrapper for owning data used by `object`.
@@ -448,8 +500,12 @@ fn check_no_gnu_stack_obj() {
448500}
449501
450502#[ test]
451- #[ cfg_attr( not( target_env = "gnu" ) , ignore = "requires a gnu toolchain to build" ) ]
503+ #[ cfg_attr(
504+ any( target_os = "windows" , target_vendor = "apple" ) ,
505+ ignore = "requires elf format"
506+ ) ]
452507fn check_obj ( ) {
508+ #[ expect( clippy:: option_env_unwrap, reason = "test is ignored" ) ]
453509 let p = option_env ! ( "HAS_EXE_STACK_OBJ" ) . expect ( "has_exe_stack.o not present" ) ;
454510 let f = fs:: read ( p) . unwrap ( ) ;
455511 let obj = ObjFile :: parse ( f. as_slice ( ) ) . unwrap ( ) ;
0 commit comments