From 250b8bc282fd9d893b7682a0c875b59fe1e26d30 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Fri, 30 May 2025 02:01:41 +0000 Subject: [PATCH 1/3] Use symcheck to locate writeable+executable object files From what I have been able to find, compilers that try to emit object files compatible with a GNU linker appear to add a `.note.GNU-stack` section if the stack should not be writeable (this section is empty). We never want a writeable stack, so extend the object file check to verify that object files with any executable sections also have this `.note.GNU-stack` section. This appears to match what is done by `scanelf` to emit `!WX` [2], which is the tool used to create the output in the issue. Closes: https://github.com/rust-lang/compiler-builtins/issues/183 [1]: https://github.com/gentoo/pax-utils/blob/9ef54b472e42ba2c5479fbd86b8be2275724b064/scanelf.c#L428-L512 update update debug update fix asm file --- crates/symbol-check/Cargo.toml | 3 + crates/symbol-check/build.rs | 33 +++++++ crates/symbol-check/src/main.rs | 105 +++++++++++++++++++++- crates/symbol-check/tests/has_exe_stack.c | 12 +++ crates/symbol-check/tests/no_gnu_stack.S | 3 + 5 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 crates/symbol-check/build.rs create mode 100644 crates/symbol-check/tests/has_exe_stack.c create mode 100644 crates/symbol-check/tests/no_gnu_stack.S diff --git a/crates/symbol-check/Cargo.toml b/crates/symbol-check/Cargo.toml index e2218b491..298a68726 100644 --- a/crates/symbol-check/Cargo.toml +++ b/crates/symbol-check/Cargo.toml @@ -8,5 +8,8 @@ publish = false object = "0.37.1" serde_json = "1.0.140" +[build-dependencies] +cc = "1.2.25" + [features] wasm = ["object/wasm"] diff --git a/crates/symbol-check/build.rs b/crates/symbol-check/build.rs new file mode 100644 index 000000000..3fcf4de55 --- /dev/null +++ b/crates/symbol-check/build.rs @@ -0,0 +1,33 @@ +//! Compile test sources to object files. + +use std::env; + +fn main() { + let compiler = cc::Build::new().get_compiler(); + + println!( + "cargo::rustc-env=OBJ_TARGET={}", + env::var("TARGET").unwrap() + ); + + let objs = cc::Build::new() + .file("tests/no_gnu_stack.S") + .compile_intermediates(); + let [obj] = objs.as_slice() else { + panic!(">1 output") + }; + println!("cargo::rustc-env=NO_GNU_STACK_OBJ={}", obj.display()); + + if !compiler.is_like_gnu() { + println!("cargo::warning=Can't run execstack test; non-GNU compiler"); + return; + } + + let objs = cc::Build::new() + .file("tests/has_exe_stack.c") + .compile_intermediates(); + let [obj] = objs.as_slice() else { + panic!(">1 output") + }; + println!("cargo::rustc-env=HAS_EXE_STACK_OBJ={}", obj.display()); +} diff --git a/crates/symbol-check/src/main.rs b/crates/symbol-check/src/main.rs index 7d0b7e90a..773efb003 100644 --- a/crates/symbol-check/src/main.rs +++ b/crates/symbol-check/src/main.rs @@ -9,8 +9,8 @@ use std::process::{Command, Stdio}; use object::read::archive::ArchiveFile; use object::{ - File as ObjFile, Object, ObjectSection, ObjectSymbol, Result as ObjResult, Symbol, SymbolKind, - SymbolScope, + BinaryFormat, File as ObjFile, Object, ObjectSection, ObjectSymbol, Result as ObjResult, + SectionFlags, Symbol, SymbolKind, SymbolScope, elf, }; use serde_json::Value; @@ -77,6 +77,7 @@ fn check_paths>(paths: &[P]) { verify_no_duplicates(&archive); verify_core_symbols(&archive); + verify_no_exec_stack(&archive); } } @@ -299,6 +300,66 @@ fn verify_core_symbols(archive: &BinFile) { println!(" success: no undefined references to core found"); } +/// Check that all object files contain a section named `.note.GNU-stack`, indicating a +/// nonexecutable stack. +/// +/// Paraphrased from : +/// +/// - A `.note.GNU-stack` section with the exe flag means this needs an executable stack +/// - A `.note.GNU-stack` section without the exe flag means there is no executable stack needed +/// - Without the section, behavior is target-specific and on some targets means an executable +/// stack is required. +/// +/// Now says +/// deprecated . +fn verify_no_exec_stack(archive: &BinFile) { + let mut problem_objfiles = Vec::new(); + + archive.for_each_object(|obj, obj_path| { + if obj_requires_exe_stack(&obj) { + problem_objfiles.push(obj_path.to_owned()); + } + }); + + if !problem_objfiles.is_empty() { + panic!("the following archive members require an executable stack: {problem_objfiles:#?}"); + } + + println!(" success: no writeable-executable sections found"); +} + +fn obj_requires_exe_stack(obj: &ObjFile) -> bool { + // Files other than elf likely do not use the same convention. + if obj.format() != BinaryFormat::Elf { + return false; + } + + let mut has_exe_sections = false; + for sec in obj.sections() { + let SectionFlags::Elf { sh_flags } = sec.flags() else { + unreachable!("only elf files are being checked"); + }; + + let exe = (sh_flags & elf::SHF_EXECINSTR as u64) != 0; + + // If the magic section is present, its exe bit tells us whether or not the object + // file requires an executable stack. + if sec.name().unwrap_or_default() == ".note.GNU-stack" { + return exe; + } + + // Otherwise, just keep track of whether or not we have exeuctable sections + has_exe_sections |= exe; + } + + // Ignore object files that have no executable sections, like rmeta + if !has_exe_sections { + return false; + } + + true +} + /// Thin wrapper for owning data used by `object`. struct BinFile { path: PathBuf, @@ -360,3 +421,43 @@ impl BinFile { }); } } + +/// Check with a binary that has no `.note.GNU-stack` section, indicating platform-default stack +/// writeability. +#[test] +fn check_no_gnu_stack_obj() { + // Should be supported on all Unix platforms + let p = env!("NO_GNU_STACK_OBJ"); + let f = fs::read(p).unwrap(); + let obj = ObjFile::parse(f.as_slice()).unwrap(); + dbg!( + obj.format(), + obj.architecture(), + obj.sub_architecture(), + obj.is_64() + ); + let has_exe_stack = obj_requires_exe_stack(&obj); + + let obj_target = env!("OBJ_TARGET"); + if obj_target.contains("-windows-") || obj_target.contains("-apple-") { + // Non-ELF targets don't have executable stacks marked in the same way + assert!(!has_exe_stack); + } else { + assert!(has_exe_stack); + } +} + +#[test] +#[cfg_attr(not(target_env = "gnu"), ignore = "requires a gnu toolchain to build")] +fn check_obj() { + let p = option_env!("HAS_EXE_STACK_OBJ").expect("has_exe_stack.o not present"); + let f = fs::read(p).unwrap(); + let obj = ObjFile::parse(f.as_slice()).unwrap(); + dbg!( + obj.format(), + obj.architecture(), + obj.sub_architecture(), + obj.is_64() + ); + assert!(obj_requires_exe_stack(&obj)); +} diff --git a/crates/symbol-check/tests/has_exe_stack.c b/crates/symbol-check/tests/has_exe_stack.c new file mode 100644 index 000000000..c740011c5 --- /dev/null +++ b/crates/symbol-check/tests/has_exe_stack.c @@ -0,0 +1,12 @@ +/* GNU nested functions are the only way I could fine to force an executable + * stack. Supported by GCC only, not Clang. */ + +void intermediate(void (*)(int, int), int); + +int hack(int *array, int size) { + void store (int index, int value) { + array[index] = value; + } + + intermediate(store, size); +} diff --git a/crates/symbol-check/tests/no_gnu_stack.S b/crates/symbol-check/tests/no_gnu_stack.S new file mode 100644 index 000000000..8c09911ab --- /dev/null +++ b/crates/symbol-check/tests/no_gnu_stack.S @@ -0,0 +1,3 @@ +/* Assembly files do not get a `.note.GNU-stack` section, meaning platform-specific + * stack executability (usually yes). */ +nop From a22be63ca0df47c505945e4467f8d2c8c26183e6 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sun, 7 Sep 2025 05:04:50 -0400 Subject: [PATCH 2/3] Fixes --- ci/run.sh | 1 + crates/symbol-check/src/main.rs | 182 ++++++++++++++-------- crates/symbol-check/tests/has_exe_stack.c | 4 +- 3 files changed, 124 insertions(+), 63 deletions(-) diff --git a/ci/run.sh b/ci/run.sh index bc94d42fe..1134466b8 100755 --- a/ci/run.sh +++ b/ci/run.sh @@ -56,6 +56,7 @@ fi symcheck=(cargo run -p symbol-check --release) [[ "$target" = "wasm"* ]] && symcheck+=(--features wasm) symcheck+=(-- build-and-check) +[[ "$target" = *"thumb"* ]] && symcheck+=(--no-std) "${symcheck[@]}" "$target" -- -p compiler_builtins "${symcheck[@]}" "$target" -- -p compiler_builtins --release diff --git a/crates/symbol-check/src/main.rs b/crates/symbol-check/src/main.rs index 773efb003..748cbe73b 100644 --- a/crates/symbol-check/src/main.rs +++ b/crates/symbol-check/src/main.rs @@ -1,16 +1,19 @@ //! Tool used by CI to inspect compiler-builtins archives and help ensure we won't run into any //! linking errors. +#![allow(unused)] // TODO + use std::collections::{BTreeMap, BTreeSet}; -use std::fs; use std::io::{BufRead, BufReader}; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; +use std::{env, fs}; use object::read::archive::ArchiveFile; use object::{ - BinaryFormat, File as ObjFile, Object, ObjectSection, ObjectSymbol, Result as ObjResult, - SectionFlags, Symbol, SymbolKind, SymbolScope, elf, + Architecture, BinaryFormat, Bytes, Endianness, File as ObjFile, Object, ObjectSection, + ObjectSymbol, Result as ObjResult, SectionFlags, SectionKind, Symbol, SymbolKind, SymbolScope, + elf, }; use serde_json::Value; @@ -19,65 +22,80 @@ const CHECK_EXTENSIONS: &[Option<&str>] = &[Some("rlib"), Some("a"), Some("exe") const USAGE: &str = "Usage: - symbol-check build-and-check [TARGET] -- CARGO_BUILD_ARGS ... + symbol-check build-and-check [TARGET] [--no-std] -- CARGO_BUILD_ARGS ... Cargo will get invoked with `CARGO_ARGS` and the specified target. All output `compiler_builtins*.rlib` files will be checked. If TARGET is not specified, the host target is used. - check PATHS ... +If the `--no-std` flag is passed, the binaries will not be checked for +executable stacks under the assumption that they are not being emitted. + + check [--no-std] PATHS ... Run the same checks on the given set of paths, without invoking Cargo. Paths may be either archives or object files. "; -fn main() { - // Create a `&str` vec so we can match on it. - let args = std::env::args().collect::>(); - let args_ref = args.iter().map(String::as_str).collect::>(); +#[derive(Debug, PartialEq)] +enum Mode { + BuildAndCheck, + CheckOnly, +} - match &args_ref[1..] { - ["build-and-check", target, "--", args @ ..] if !args.is_empty() => { - run_build_and_check(target, args); - } - ["build-and-check", "--", args @ ..] if !args.is_empty() => { - let target = &host_target(); - run_build_and_check(target, args); - } - ["check", paths @ ..] if !paths.is_empty() => { - check_paths(paths); - } - _ => { - println!("{USAGE}"); - std::process::exit(1); +fn main() { + let mut args_iter = env::args().skip(1); + let mode = match args_iter.next() { + Some(arg) if arg == "build-and-check" => Mode::BuildAndCheck, + Some(arg) if arg == "check" => Mode::CheckOnly, + Some(other) => invalid_usage(&format!("unrecognized mode `{other}`")), + None => invalid_usage("mode must be specified"), + }; + + let mut target = None; + let mut verify_no_exe = true; + + for arg in args_iter.by_ref() { + match arg.as_str() { + "--no-std" => verify_no_exe = false, + "--" => break, + f if f.starts_with("-") => invalid_usage(&format!("unrecognized flag `{f}`")), + _ if mode == Mode::BuildAndCheck => target = Some(arg), + _ => break, } } -} -fn run_build_and_check(target: &str, args: &[&str]) { - // Make sure `--target` isn't passed to avoid confusion (since it should be - // proivded only once, positionally). - for arg in args { - assert!( - !arg.contains("--target"), - "target must be passed positionally. {USAGE}" - ); - } + let positional = args_iter.collect::>(); - let paths = exec_cargo_with_args(target, args); - check_paths(&paths); + match mode { + Mode::BuildAndCheck => { + let target = target.unwrap_or_else(|| host_target()); + let paths = exec_cargo_with_args(&target, positional.as_slice()); + check_paths(&paths, verify_no_exe); + } + Mode::CheckOnly => check_paths(&positional, verify_no_exe), + }; +} + +fn invalid_usage(s: &str) -> ! { + println!("{s}\n{USAGE}"); + std::process::exit(1); } -fn check_paths>(paths: &[P]) { +fn check_paths>(paths: &[P], verify_no_exe: bool) { for path in paths { let path = path.as_ref(); println!("Checking {}", path.display()); let archive = BinFile::from_path(path); - verify_no_duplicates(&archive); - verify_core_symbols(&archive); - verify_no_exec_stack(&archive); + // verify_no_duplicates(&archive); + // verify_core_symbols(&archive); + if verify_no_exe { + // We don't really have a good way of knowing whether or not an elf file is for a + // no-kernel environment, in which case note.GNU-stack doesn't get emitted. + verify_no_exec_stack(&archive); + } } } @@ -97,7 +115,7 @@ fn host_target() -> String { /// Run `cargo build` with the provided additional arguments, collecting the list of created /// libraries. -fn exec_cargo_with_args(target: &str, args: &[&str]) -> Vec { +fn exec_cargo_with_args>(target: &str, args: &[S]) -> Vec { let mut cmd = Command::new("cargo"); cmd.args([ "build", @@ -105,7 +123,7 @@ fn exec_cargo_with_args(target: &str, args: &[&str]) -> Vec { target, "--message-format=json-diagnostic-rendered-ansi", ]) - .args(args) + .args(args.iter().map(|arg| arg.as_ref())) .stdout(Stdio::piped()); println!("running: {cmd:?}"); @@ -300,18 +318,7 @@ fn verify_core_symbols(archive: &BinFile) { println!(" success: no undefined references to core found"); } -/// Check that all object files contain a section named `.note.GNU-stack`, indicating a -/// nonexecutable stack. -/// -/// Paraphrased from : -/// -/// - A `.note.GNU-stack` section with the exe flag means this needs an executable stack -/// - A `.note.GNU-stack` section without the exe flag means there is no executable stack needed -/// - Without the section, behavior is target-specific and on some targets means an executable -/// stack is required. -/// -/// Now says -/// deprecated . +/// Ensure that the object/archive will not require an executable stack. fn verify_no_exec_stack(archive: &BinFile) { let mut problem_objfiles = Vec::new(); @@ -322,34 +329,62 @@ fn verify_no_exec_stack(archive: &BinFile) { }); if !problem_objfiles.is_empty() { - panic!("the following archive members require an executable stack: {problem_objfiles:#?}"); + panic!("the following object files require an executable stack: {problem_objfiles:#?}"); } - println!(" success: no writeable-executable sections found"); + println!(" success: no writeable+executable sections found"); } +/// True if the section/flag combination indicates that the object file should be linked with an +/// executable stack. +/// +/// Paraphrased from : +/// +/// - A `.note.GNU-stack` section with the exe flag means this needs an executable stack +/// - A `.note.GNU-stack` section without the exe flag means there is no executable stack needed +/// - Without the section, behavior is target-specific and on some targets means an executable +/// stack is required. +/// +/// If any object files meet the requirements for an executable stack, any final binary that links +/// it will have a program header with a `PT_GNU_STACK` section, which will be marked `RWE` rather +/// than the desired `RW`. (We don't check final binaries). +/// +/// Per [1], it is now deprecated behavior for a missing `.note.GNU-stack` section to imply an +/// executable stack. However, we shouldn't assume that tooling has caught up to this. +/// +/// [1]: https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=0d38576a34ec64a1b4500c9277a8e9d0f07e6774> fn obj_requires_exe_stack(obj: &ObjFile) -> bool { - // Files other than elf likely do not use the same convention. + // Files other than elf do not use the same convention. if obj.format() != BinaryFormat::Elf { return false; } + let mut return_immediate = None; let mut has_exe_sections = false; for sec in obj.sections() { let SectionFlags::Elf { sh_flags } = sec.flags() else { unreachable!("only elf files are being checked"); }; - let exe = (sh_flags & elf::SHF_EXECINSTR as u64) != 0; + if sec.kind() == SectionKind::Elf(elf::SHT_ARM_ATTRIBUTES) { + let data = sec.data().unwrap(); + parse_arm_thing(data); + } + + let is_exe = (sh_flags & elf::SHF_EXECINSTR as u64) != 0; // If the magic section is present, its exe bit tells us whether or not the object // file requires an executable stack. if sec.name().unwrap_or_default() == ".note.GNU-stack" { - return exe; + return_immediate = Some(is_exe); } // Otherwise, just keep track of whether or not we have exeuctable sections - has_exe_sections |= exe; + has_exe_sections |= is_exe; + } + + if let Some(imm) = return_immediate { + return imm; } // Ignore object files that have no executable sections, like rmeta @@ -357,7 +392,28 @@ fn obj_requires_exe_stack(obj: &ObjFile) -> bool { return false; } - true + platform_default_exe_stack_required(obj.architecture(), obj.endianness()) +} + +/// Default if there is no `.note.GNU-stack` section. +fn platform_default_exe_stack_required(arch: Architecture, end: Endianness) -> bool { + match arch { + // PPC64 doesn't set `.note.GNU-stack` since GNU nested functions don't need a trampoline, + // . + Architecture::PowerPc64 if end == Endianness::Big => false, + _ => true, + } +} + +fn parse_arm_thing(data: &[u8]) { + eprintln!("data: {data:x?}"); + eprintln!("data string: {:?}", String::from_utf8_lossy(data)); + eprintln!("data: {:x?}", &data[16..]); + // let mut rest = &data[16..]; + let mut b = Bytes(data); + b.skip(16).unwrap(); + + // while !rest.is_empty() {} } /// Thin wrapper for owning data used by `object`. @@ -448,8 +504,12 @@ fn check_no_gnu_stack_obj() { } #[test] -#[cfg_attr(not(target_env = "gnu"), ignore = "requires a gnu toolchain to build")] +#[cfg_attr( + any(target_os = "windows", target_vendor = "apple"), + ignore = "requires elf format" +)] fn check_obj() { + #[expect(clippy::option_env_unwrap, reason = "test is ignored")] let p = option_env!("HAS_EXE_STACK_OBJ").expect("has_exe_stack.o not present"); let f = fs::read(p).unwrap(); let obj = ObjFile::parse(f.as_slice()).unwrap(); diff --git a/crates/symbol-check/tests/has_exe_stack.c b/crates/symbol-check/tests/has_exe_stack.c index c740011c5..7a63c58da 100644 --- a/crates/symbol-check/tests/has_exe_stack.c +++ b/crates/symbol-check/tests/has_exe_stack.c @@ -1,5 +1,5 @@ -/* GNU nested functions are the only way I could fine to force an executable - * stack. Supported by GCC only, not Clang. */ +/* GNU nested functions are the only way I could find to force an explicitly + * executable stack. Supported by GCC only, not Clang. */ void intermediate(void (*)(int, int), int); From 03b30f371699fb689dceaecac382bb46ebc1c795 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Fri, 12 Sep 2025 04:15:14 -0400 Subject: [PATCH 3/3] wip --- ci/run.sh | 1 - crates/symbol-check/src/main.rs | 94 +++++++++++++++++++++++++++------ 2 files changed, 78 insertions(+), 17 deletions(-) diff --git a/ci/run.sh b/ci/run.sh index 1134466b8..bc94d42fe 100755 --- a/ci/run.sh +++ b/ci/run.sh @@ -56,7 +56,6 @@ fi symcheck=(cargo run -p symbol-check --release) [[ "$target" = "wasm"* ]] && symcheck+=(--features wasm) symcheck+=(-- build-and-check) -[[ "$target" = *"thumb"* ]] && symcheck+=(--no-std) "${symcheck[@]}" "$target" -- -p compiler_builtins "${symcheck[@]}" "$target" -- -p compiler_builtins --release diff --git a/crates/symbol-check/src/main.rs b/crates/symbol-check/src/main.rs index 748cbe73b..d89628dfe 100644 --- a/crates/symbol-check/src/main.rs +++ b/crates/symbol-check/src/main.rs @@ -9,11 +9,13 @@ use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use std::{env, fs}; +use object::elf::SectionHeader32; use object::read::archive::ArchiveFile; +use object::read::elf::SectionHeader; use object::{ - Architecture, BinaryFormat, Bytes, Endianness, File as ObjFile, Object, ObjectSection, - ObjectSymbol, Result as ObjResult, SectionFlags, SectionKind, Symbol, SymbolKind, SymbolScope, - elf, + Architecture, BinaryFormat, Bytes, Endianness, File as ObjFile, LittleEndian, Object, + ObjectSection, ObjectSymbol, Result as ObjResult, SectionFlags, SectionKind, Symbol, + SymbolKind, SymbolScope, U32, U32Bytes, elf, }; use serde_json::Value; @@ -55,18 +57,23 @@ fn main() { let mut target = None; let mut verify_no_exe = true; + let mut positional = Vec::new(); for arg in args_iter.by_ref() { + dbg!(&arg); match arg.as_str() { "--no-std" => verify_no_exe = false, "--" => break, f if f.starts_with("-") => invalid_usage(&format!("unrecognized flag `{f}`")), _ if mode == Mode::BuildAndCheck => target = Some(arg), - _ => break, + _ => { + positional.push(arg); + break; + } } } - let positional = args_iter.collect::>(); + positional.extend(args_iter); match mode { Mode::BuildAndCheck => { @@ -74,7 +81,10 @@ fn main() { let paths = exec_cargo_with_args(&target, positional.as_slice()); check_paths(&paths, verify_no_exe); } - Mode::CheckOnly => check_paths(&positional, verify_no_exe), + Mode::CheckOnly => { + assert!(!positional.is_empty()); + check_paths(&positional, verify_no_exe); + } }; } @@ -91,11 +101,11 @@ fn check_paths>(paths: &[P], verify_no_exe: bool) { // verify_no_duplicates(&archive); // verify_core_symbols(&archive); - if verify_no_exe { - // We don't really have a good way of knowing whether or not an elf file is for a - // no-kernel environment, in which case note.GNU-stack doesn't get emitted. - verify_no_exec_stack(&archive); - } + // if verify_no_exe { + // We don't really have a good way of knowing whether or not an elf file is for a + // no-kernel environment, in which case note.GNU-stack doesn't get emitted. + verify_no_exec_stack(&archive); + // } } } @@ -359,16 +369,29 @@ fn obj_requires_exe_stack(obj: &ObjFile) -> bool { return false; } + let secs = match obj { + ObjFile::Elf32(elf_file) => elf_file.sections(), + ObjFile::Elf64(elf_file) => panic!(), + // ObjFile::Elf64(elf_file) => elf_file.sections(), + _ => return false, + }; + let mut return_immediate = None; let mut has_exe_sections = false; for sec in obj.sections() { + dbg!(sec.name()); let SectionFlags::Elf { sh_flags } = sec.flags() else { unreachable!("only elf files are being checked"); }; if sec.kind() == SectionKind::Elf(elf::SHT_ARM_ATTRIBUTES) { + let end = obj.endianness(); let data = sec.data().unwrap(); - parse_arm_thing(data); + let ObjFile::Elf32(elf) = obj else { panic!() }; + let elf_sec = elf.section_by_index(sec.index()).unwrap(); + let elf_hdr = elf_sec.elf_section_header(); + + parse_arm_thing(data, elf_hdr, end); } let is_exe = (sh_flags & elf::SHF_EXECINSTR as u64) != 0; @@ -405,13 +428,52 @@ fn platform_default_exe_stack_required(arch: Architecture, end: Endianness) -> b } } -fn parse_arm_thing(data: &[u8]) { - eprintln!("data: {data:x?}"); +// See https://github.com/ARM-software/abi-aa/blob/main/addenda32/addenda32.rst#33public-aeabi-attribute-tags +fn parse_arm_thing(data: &[u8], elf_hdr: &SectionHeader32, end: Endianness) { + let attrs = elf_hdr.attributes(end, data).unwrap(); + dbg!(attrs); + + eprintln!("data d: {data:?}"); + eprintln!("data x: {data:x?}"); eprintln!("data string: {:?}", String::from_utf8_lossy(data)); - eprintln!("data: {:x?}", &data[16..]); + // eprintln!("data: {:x?}", &data[16..]); // let mut rest = &data[16..]; let mut b = Bytes(data); - b.skip(16).unwrap(); + let _fmt_version = b.read::().unwrap(); + let _sec_length = b.read::>().unwrap(); + + // loop { + let s = b.read_string().unwrap(); + eprintln!("abi {}", String::from_utf8_lossy(s)); + + let _tag = b.read_uleb128().unwrap(); + let _size = b.read::>().unwrap(); + + // NUL-terminated byte strings + const CPU_RAW_NAME: u64 = 4; + const CPU_NAME: u64 = 5; + const ALSO_COMPATIBLE_WITH: u64 = 65; + const CONFORMANCE: u64 = 67; + + const CPU_ARCH_PROFILE: u64 = 7; + + while !b.is_empty() { + let tag = b.read_uleb128().unwrap(); + match tag { + CONFORMANCE => eprintln!( + "conf: {}", + String::from_utf8_lossy(b.read_string().unwrap()) + ), + // 77 => + CPU_ARCH_PROFILE => { + // CPU_arch_profile + let value = b.read_uleb128().unwrap(); + } + _ => eprintln!("tag {tag} value {}", b.read::().unwrap()), + } + } + + // } // while !rest.is_empty() {} }