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 ;
1113use object:: {
12- BinaryFormat , File as ObjFile , Object , ObjectSection , ObjectSymbol , Result as ObjResult ,
13- SectionFlags , Symbol , SymbolKind , SymbolScope , elf,
14+ Architecture , BinaryFormat , Bytes , Endianness , File as ObjFile , Object , ObjectSection ,
15+ ObjectSymbol , Result as ObjResult , SectionFlags , SectionKind , Symbol , SymbolKind , SymbolScope ,
16+ elf,
1417} ;
1518use serde_json:: Value ;
1619
@@ -19,65 +22,80 @@ const CHECK_EXTENSIONS: &[Option<&str>] = &[Some("rlib"), Some("a"), Some("exe")
1922
2023const USAGE : & str = "Usage:
2124
22- symbol-check build-and-check [TARGET] -- CARGO_BUILD_ARGS ...
25+ symbol-check build-and-check [TARGET] [--no-std] -- CARGO_BUILD_ARGS ...
2326
2427Cargo will get invoked with `CARGO_ARGS` and the specified target. All output
2528`compiler_builtins*.rlib` files will be checked.
2629
2730If TARGET is not specified, the host target is used.
2831
29- check PATHS ...
32+ If the `--no-std` flag is passed, the binaries will not be checked for
33+ executable stacks under the assumption that they are not being emitted.
34+
35+ check [--no-std] PATHS ...
3036
3137Run the same checks on the given set of paths, without invoking Cargo. Paths
3238may be either archives or object files.
3339" ;
3440
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 < _ > > ( ) ;
41+ #[ derive( Debug , PartialEq ) ]
42+ enum Mode {
43+ BuildAndCheck ,
44+ CheckOnly ,
45+ }
3946
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 ) ;
47+ fn main ( ) {
48+ let mut args_iter = env:: args ( ) . skip ( 1 ) ;
49+ let mode = match args_iter. next ( ) {
50+ Some ( arg) if arg == "build-and-check" => Mode :: BuildAndCheck ,
51+ Some ( arg) if arg == "check" => Mode :: CheckOnly ,
52+ Some ( other) => invalid_usage ( & format ! ( "unrecognized mode `{other}`" ) ) ,
53+ None => invalid_usage ( "mode must be specified" ) ,
54+ } ;
55+
56+ let mut target = None ;
57+ let mut verify_no_exe = true ;
58+
59+ for arg in args_iter. by_ref ( ) {
60+ match arg. as_str ( ) {
61+ "--no-std" => verify_no_exe = false ,
62+ "--" => break ,
63+ f if f. starts_with ( "-" ) => invalid_usage ( & format ! ( "unrecognized flag `{f}`" ) ) ,
64+ _ if mode == Mode :: BuildAndCheck => target = Some ( arg) ,
65+ _ => break ,
5466 }
5567 }
56- }
5768
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- }
69+ let positional = args_iter. collect :: < Vec < _ > > ( ) ;
6770
68- let paths = exec_cargo_with_args ( target, args) ;
69- check_paths ( & paths) ;
71+ match mode {
72+ Mode :: BuildAndCheck => {
73+ let target = target. unwrap_or_else ( || host_target ( ) ) ;
74+ let paths = exec_cargo_with_args ( & target, positional. as_slice ( ) ) ;
75+ check_paths ( & paths, verify_no_exe) ;
76+ }
77+ Mode :: CheckOnly => check_paths ( & positional, verify_no_exe) ,
78+ } ;
79+ }
80+
81+ fn invalid_usage ( s : & str ) -> ! {
82+ println ! ( "{s}\n {USAGE}" ) ;
83+ std:: process:: exit ( 1 ) ;
7084}
7185
72- fn check_paths < P : AsRef < Path > > ( paths : & [ P ] ) {
86+ fn check_paths < P : AsRef < Path > > ( paths : & [ P ] , verify_no_exe : bool ) {
7387 for path in paths {
7488 let path = path. as_ref ( ) ;
7589 println ! ( "Checking {}" , path. display( ) ) ;
7690 let archive = BinFile :: from_path ( path) ;
7791
78- verify_no_duplicates ( & archive) ;
79- verify_core_symbols ( & archive) ;
80- verify_no_exec_stack ( & archive) ;
92+ // verify_no_duplicates(&archive);
93+ // verify_core_symbols(&archive);
94+ if verify_no_exe {
95+ // We don't really have a good way of knowing whether or not an elf file is for a
96+ // no-kernel environment, in which case note.GNU-stack doesn't get emitted.
97+ verify_no_exec_stack ( & archive) ;
98+ }
8199 }
82100}
83101
@@ -97,15 +115,15 @@ fn host_target() -> String {
97115
98116/// Run `cargo build` with the provided additional arguments, collecting the list of created
99117/// libraries.
100- fn exec_cargo_with_args ( target : & str , args : & [ & str ] ) -> Vec < PathBuf > {
118+ fn exec_cargo_with_args < S : AsRef < str > > ( target : & str , args : & [ S ] ) -> Vec < PathBuf > {
101119 let mut cmd = Command :: new ( "cargo" ) ;
102120 cmd. args ( [
103121 "build" ,
104122 "--target" ,
105123 target,
106124 "--message-format=json-diagnostic-rendered-ansi" ,
107125 ] )
108- . args ( args)
126+ . args ( args. iter ( ) . map ( |arg| arg . as_ref ( ) ) )
109127 . stdout ( Stdio :: piped ( ) ) ;
110128
111129 println ! ( "running: {cmd:?}" ) ;
@@ -300,18 +318,7 @@ fn verify_core_symbols(archive: &BinFile) {
300318 println ! ( " success: no undefined references to core found" ) ;
301319}
302320
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>.
321+ /// Ensure that the object/archive will not require an executable stack.
315322fn verify_no_exec_stack ( archive : & BinFile ) {
316323 let mut problem_objfiles = Vec :: new ( ) ;
317324
@@ -322,42 +329,91 @@ fn verify_no_exec_stack(archive: &BinFile) {
322329 } ) ;
323330
324331 if !problem_objfiles. is_empty ( ) {
325- panic ! ( "the following archive members require an executable stack: {problem_objfiles:#?}" ) ;
332+ panic ! ( "the following object files require an executable stack: {problem_objfiles:#?}" ) ;
326333 }
327334
328- println ! ( " success: no writeable- executable sections found" ) ;
335+ println ! ( " success: no writeable+ executable sections found" ) ;
329336}
330337
338+ /// True if the section/flag combination indicates that the object file should be linked with an
339+ /// executable stack.
340+ ///
341+ /// Paraphrased from <https://www.man7.org/linux/man-pages/man1/ld.1.html>:
342+ ///
343+ /// - A `.note.GNU-stack` section with the exe flag means this needs an executable stack
344+ /// - A `.note.GNU-stack` section without the exe flag means there is no executable stack needed
345+ /// - Without the section, behavior is target-specific and on some targets means an executable
346+ /// stack is required.
347+ ///
348+ /// If any object files meet the requirements for an executable stack, any final binary that links
349+ /// it will have a program header with a `PT_GNU_STACK` section, which will be marked `RWE` rather
350+ /// than the desired `RW`. (We don't check final binaries).
351+ ///
352+ /// Per [1], it is now deprecated behavior for a missing `.note.GNU-stack` section to imply an
353+ /// executable stack. However, we shouldn't assume that tooling has caught up to this.
354+ ///
355+ /// [1]: https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=0d38576a34ec64a1b4500c9277a8e9d0f07e6774>
331356fn obj_requires_exe_stack ( obj : & ObjFile ) -> bool {
332- // Files other than elf likely do not use the same convention.
357+ // Files other than elf do not use the same convention.
333358 if obj. format ( ) != BinaryFormat :: Elf {
334359 return false ;
335360 }
336361
362+ let mut return_immediate = None ;
337363 let mut has_exe_sections = false ;
338364 for sec in obj. sections ( ) {
339365 let SectionFlags :: Elf { sh_flags } = sec. flags ( ) else {
340366 unreachable ! ( "only elf files are being checked" ) ;
341367 } ;
342368
343- let exe = ( sh_flags & elf:: SHF_EXECINSTR as u64 ) != 0 ;
369+ if sec. kind ( ) == SectionKind :: Elf ( elf:: SHT_ARM_ATTRIBUTES ) {
370+ let data = sec. data ( ) . unwrap ( ) ;
371+ parse_arm_thing ( data) ;
372+ }
373+
374+ let is_exe = ( sh_flags & elf:: SHF_EXECINSTR as u64 ) != 0 ;
344375
345376 // If the magic section is present, its exe bit tells us whether or not the object
346377 // file requires an executable stack.
347378 if sec. name ( ) . unwrap_or_default ( ) == ".note.GNU-stack" {
348- return exe ;
379+ return_immediate = Some ( is_exe ) ;
349380 }
350381
351382 // Otherwise, just keep track of whether or not we have exeuctable sections
352- has_exe_sections |= exe;
383+ has_exe_sections |= is_exe;
384+ }
385+
386+ if let Some ( imm) = return_immediate {
387+ return imm;
353388 }
354389
355390 // Ignore object files that have no executable sections, like rmeta
356391 if !has_exe_sections {
357392 return false ;
358393 }
359394
360- true
395+ platform_default_exe_stack_required ( obj. architecture ( ) , obj. endianness ( ) )
396+ }
397+
398+ /// Default if there is no `.note.GNU-stack` section.
399+ fn platform_default_exe_stack_required ( arch : Architecture , end : Endianness ) -> bool {
400+ match arch {
401+ // PPC64 doesn't set `.note.GNU-stack` since GNU nested functions don't need a trampoline,
402+ // <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=21098>.
403+ Architecture :: PowerPc64 if end == Endianness :: Big => false ,
404+ _ => true ,
405+ }
406+ }
407+
408+ fn parse_arm_thing ( data : & [ u8 ] ) {
409+ eprintln ! ( "data: {data:x?}" ) ;
410+ eprintln ! ( "data string: {:?}" , String :: from_utf8_lossy( data) ) ;
411+ eprintln ! ( "data: {:x?}" , & data[ 16 ..] ) ;
412+ // let mut rest = &data[16..];
413+ let mut b = Bytes ( data) ;
414+ b. skip ( 16 ) . unwrap ( ) ;
415+
416+ // while !rest.is_empty() {}
361417}
362418
363419/// Thin wrapper for owning data used by `object`.
@@ -448,8 +504,12 @@ fn check_no_gnu_stack_obj() {
448504}
449505
450506#[ test]
451- #[ cfg_attr( not( target_env = "gnu" ) , ignore = "requires a gnu toolchain to build" ) ]
507+ #[ cfg_attr(
508+ any( target_os = "windows" , target_vendor = "apple" ) ,
509+ ignore = "requires elf format"
510+ ) ]
452511fn check_obj ( ) {
512+ #[ expect( clippy:: option_env_unwrap, reason = "test is ignored" ) ]
453513 let p = option_env ! ( "HAS_EXE_STACK_OBJ" ) . expect ( "has_exe_stack.o not present" ) ;
454514 let f = fs:: read ( p) . unwrap ( ) ;
455515 let obj = ObjFile :: parse ( f. as_slice ( ) ) . unwrap ( ) ;
0 commit comments