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+
46use std:: collections:: { BTreeMap , BTreeSet } ;
5- use std:: fs;
67use std:: io:: { BufRead , BufReader } ;
78use std:: path:: { Path , PathBuf } ;
89use std:: process:: { Command , Stdio } ;
10+ use std:: { env, fs} ;
911
1012use object:: read:: archive:: ArchiveFile ;
13+ use object:: read:: elf:: SectionHeader ;
1114use 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} ;
1519use serde_json:: Value ;
1620
@@ -32,52 +36,63 @@ Run 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 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.
315319fn 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>
331353fn 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+ ) ]
452508fn 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 ( ) ;
0 commit comments