diff options
| author | Mason Reed <mason@vector35.com> | 2024-12-22 01:21:02 -0500 |
|---|---|---|
| committer | Mason Reed <mason@vector35.com> | 2025-01-25 00:52:29 -0500 |
| commit | 6a63c17853009ce7a0071458e39c140a09440230 (patch) | |
| tree | ad59ab3b356b0a2c60423e55f62c996891788788 /plugins/dwarf | |
| parent | bfa1d409b2d5e734804a5fdb4c68c90a93712f1f (diff) | |
Rust refactor
Moves a bunch of stuff out of src/types.rs that did not belong:
- Confidence
- Variable
- Function specific stuff
- Refactored InstructionInfo, see the msp430 and riscv examples.
- Renamed Function::from_raw to Function::ref_from_raw and fixed places where the ref was incremented twice
- Fixed FunctionRecognizer leaking functions (see above)
- Fixed some apis incorrectly returning Result where Option is clearer
- Started to move destructured types to the From trait for converting to an from ffi types, see Location for an example
- Started to remove bad module level imports (importing std modules like mem all over the place)
- Moved some wrapper handle types to named handle field (this improves readability), see CoreArchitecture for an example
- Removed some unintuitive getters, this is bad practice for Rust code, just access the field directly, see DataVariable for an example
- General code cleanup, purposely did not run rustfmt, that will be a single seperate commit
More rust cleanup
- Fixed invalid views being able to invoke UB when dealing with databases
- Cleaned up some helper code in dwarf_import
- Fixed inverted is_null checks causing crashes! Oops!
More rust cleanup
Still a WIP, I think branch info is still invalid, need to figure out the issue there.
- Fixed some invalid Ref lifetimes when constructing indirectly, see Array<DataVariable> for example
- Added some more comments
- Removed some "magic" functions like MLIL Function::ssa_variables
There are still a bunch of invalid lifetimes that aren't crashing us due to the usage of those API's not living long enough. But they ARE an issue.
More rust cleanup
Trying to comment more TODO's as I go along.
- Renamed llil::Function to llil::LowLevelILFunction for clarity and consistency
- Take base structures by ref in StructureBuilder::set_base_structures to prevent premature drops
- Added more raw to wrapper conversions
- Removed UB prone apis
- Getting rid of more core module references, use std!
- Removed improper Guard usage in array impls for wrapper types with no context
- Untangling the UB of the Label api, still only half done :/
More rust cleanup
- Misc formatting
- Made Logger ref counted
- Fixed leaking name of logger every time something was logged
- Fixed the last (hopefully) of the unresolved labels
- Simplified some code
Fix leak in DebugInfo::AddType
componentArray was never freed
Add more HLIL related functions to rust
More rust cleanup
improve the CustomBinaryView init process
Canonicalize path in `create_delete_empty` test
Link core in rust
When linking you must depend on the -sys crate.
This is because linker arguments (what says where to find binaryninjacore) are NOT transitive. The top level application crate MUST provide it.
For more information see:
- https://github.com/rust-lang/cargo/issues/9554#issuecomment-857882964
- https://github.com/oxidecomputer/omicron/pull/225
Remove vendored pdb crate
Use cargo to manage the git repo ref instead
Fix misc rustdoc warnings
Move actual plugins out of `rust/examples` and into `plugins`
This is where all shipped public plugins that are not arch/view/platform/lang will be at from now on
Originally they were in the rust workspace, meaning they all shared a Cargo.lock which is ill-advised.
More rust cleanup
- More clarification on plugin/executable requirements
- Made examples actually rust examples
- Add Display impl for InstructionTextToken
- Renamed feature "noexports" to "no_exports"
Move under_construction.png to assets directory
This really did bother me
Remove unneeded `extern crate bindgen`
Replace nop'd log::info with println
We don't register a compatible log sink so they will just get sent into the void
Move inline tests into tests directory
This is being done in the hopes of curbing the multi-thousand line files that will occur once we flesh out the tests
Format rust code
Update rust ci
Still need to add support for running tests in ci
More rust cleanup
- Architecture id's are now typed accordingly
- Fix some clippy lints
- Make instruction index public in LLIL
- Removed useless helper functions
- LLIL expressions and instruction indexes are now typed accordingly
Generate binaryninjacore-sys documentation
This should show binaryninjacore-sys alongside binaryninja crate
More rust cleanup
- Remove lazy_static dependency
- Remove hacky impl Debug for Type and just use the display impl
- Add more debug impls
- Reorder some top level namespace items
- Add first type test
Remove unneeded script helper in rust api
More rust cleanup
- Added main thread handler api
- Register a headless main thread handler by default in initialization
- Refactor QualifiedName to be properly owned
- Loosened some type constraints on some apis involving QualifiedName
- Fixed some apis that were crashing due to incorrect param types
- Removed extern crate cruft for log crate
- Simplified headless initialization using more wrapper apis
- Fixed segments leaking because of no ref wrapper, see BinaryViewExt::segment_at
- Added rstest to manage headless init in unit tests
- Added some more unit tests
- Refactored demangler api to be more ergonomic
- Fixed minidump plugin not building
More rust cleanup
- Fixup usage of QualifiedName in plugins
- Make QualifiedName more usable
Implement rust TypeParser
fix Platform TypeParser related functions
separate User and System implementations of TypeParserResult
Implement rust TypeContainer
More rust cleanup
- Hopefully fixed the rust.yml CI
- Added TypePrinter API (this is still WIP and will crash)
- Added TypeParser API
- Added TypeContainer API
- More work around QualifiedName apis
Oh your suppose to do this
Add workflow_dispatch trigger to rust.yml
More rust fixes
- Swapped some usage of raw 255 to MAX_CONFIDENCE, no one likes magic numbers
- New InstructionTextToken API, complete with owned data, this still needs a lot of testing.
- InstructionTextTokenKind describes a destructured InstructionTextToken, this should make token usage much clearer, some docs pending
- Added some misc Default and Debug impls
- Updated TypePrinter and architectures to use new InstructionTextToken API
Misc formatting changes
More rust cleanup
- Fixed MANY memory leaks (most due to QualifiedName)
- Made StructureBuilder more builder and less structure
- Fixed CMakeLists.txt that were globbing entire api, resulting in 100 second slowdown on cmake generation
- Added more Debug impl's
- Added some more tests
- Fixed TypeParserResult UB
- Moved the From impls to blank impl for clarity, we have multiple different variants of core to rust for some structures, it should be explicit which one you are choosing.
- PossibleValueSet should now be able to allocate so we can go from rust to core with those variants that require allocation
- Misc doc code formatting
Misc clippy lints and clippy CI
Co-authored-by: Michael Krasnitski <michael.krasnitski@gmail.com>
Fix typo in rust CI
Misc rust formatting
Fix misc typos and add typos to rust CI
Add cargo workspace
This will help tooling and external contributors get a map of the rust crates within binaryninja-api
More rust cleanup
- Format all rust plugins
- Fix some tests that were out of date
- Simplify WARP tests to only binaries, building object files from source is a pain
- Link to core in all rust plugins
- Fix some memory leaks
- Update warp insta snapshots
- Fix some misc clippy lints
Run rust tests in CI
This commit also coincides with the creation of the "testing" environment which exposes a BN_SERIAL secret for pulling a headless Binary Ninja
Install missing wayland dependency in github CI
Apparently its needed for linux file picker for the WARP integration
Set the BINARYNINJADIR so rust can find binaryninjacore in CI
The chances of this working are low
Misc remove unused dependency
Rust misc formatting fixes
Improve initialization in rust headless scripts
Provide sensible errors and validation to rust headless scripts, solves https://github.com/Vector35/binaryninja-api/issues/5796
Add BN_LICENSE environment variable to rust CI
We pass the serial to download binary ninja, but we never provided the license for core initialization
Fix typo
More rust cleanup
- Improved binary view initialization (see init_with_opts)
- Allow floating license to free itself before initialization
- Add initialization unit test
- Add better Debug impls for some common types
- Use Path api for opening binary views, this is not breaking as it uses the AsRef impl
- Bump rayon dependency and constrain dependencies to x.x
Update readme and include that directly in the rustdocs
More rust documentation changes
Add format comment to InitializationOptions::with_license
Misc formatting and clippy lint allow
More rust cleanup
- Remove under_construction.png from the build.rs it has been removed
- Use InstructionIndex in more places
- Add missing PartialOrd and Ord impls for id types
More rust cleanup
- Make workflow cloning explicit
- Add workflow tests
- Add missing property string list getting for settings
- Remove IntoActivityName (see https://github.com/Vector35/binaryninja-api/pull/6257)
More rust cleanup
This commit is half done
Misc rust formatting
More rust cleanup
- Renamed common name conflictions (I will put my justification in the PR)
- Fixed invalid instruction retrieval for LLIL
- Added common aliases for llil function, instruction and expression types (see my comment in the PR)
- Refactored the instruction retrieval for LLIL, MLIL and HLIL
- Added instruction index types to MLIL and HLIL
- Moved llil module to lowlevelil module (mlil and hlil will be moved as well)
- Added preliminary LLIL unit testing
Fix typos
Misc clippy fixes
More rust cleanup
- Normalized modules
- Split some code out into own files
- Fixed some UB in type archive and projects
- Improved API around type archives and projects substantially
- Added ProgressExecutor abstraction for functions which have a progress callback
- Improved background task documentation and added unit tests
- Added worker thread api and unit tests
- Moved some owned types to ref types, this is still not complete, but this is the path forward.
- Add external location/library accessors to the binary view
- Added some misc documentation
- Replaced mod.rs with the module name source file
Still need to normalize some paths and also update some documentation surrounding that change.
Update some tests and examples
Fix background task tests colliding
We were creating multiple background tasks with the same progress text on multiple threads
More rust cleanup
- Fixed progress executor freeing itself after one iteration
- Updated the last of the doc imports
- Moved mainthread to main_thread
- Made project creation and opening failable
We could probably AsRef<ProgressExecutor> to get around the allocation and free inside the function bodies, not high priority as those functions are long running anyways.
Move binary view initialization into function body for LLIL test
Normalize test file names
More rust cleanup
- Updated README to clarify offline documentation
- Refactored settings api
- Added settings example to show dumping settings value and specific properties
- Use the workspace to depend on binaryninja and binaryninjacore-sys
- Remove binaryninjacore-sys as a workspace member (its not really required)
Update workflow test to new settings api
More rust cleanup
- Rename Label to LowLevelILLabel
- Update the functions label map automatically
This fixed a major api blunder where the label map is returned as a reference and originally resulted in UB prone lifetime semantics. It was temporarily "fixed" with explicit label updates in the architecture implementation code. But that was less than ideal and was easy to mess up. Now the label map will be updated automatically as the location of labels is now tracked.
Misc clippy lints
More rust cleanup
- Get rid of RawFunctionViewType
- Add better Debug impl for Function
More rust cleanup
- Fixed the documentation icon using local files (thank you @mkrasnitski)
- Fixed labels being updated and overwriting the label location used to update the label map
More rust cleanup
- Added unit tests for MLIL and HLIL
- "Fixed" MLIL, LLIL, and HLIL having issues regarding Instruction vs Expression indexes
- Renamed CallingConvention to CoreCallingConvention and removed architecture generic
- Renamed CallingConventionBase to CallingConvention
- Simplified calling convention code and fixed some bugs with implicit registers
- Added impl Debug to MLIL and HLIL instructions
Still need to at some point add an Expression to MLIL and HLIL. We also might want to look into having the Instruction kind just return the expression kind.
Misc clippy lint
More rust cleanup
- Allow calling conventions to be registered for multiple architectures
- Swapped a unreachable statement to an unimplemented statement
More rust cleanup
- Fixed the issue with PDB types, this has caused me an insane amount of grief
- Fixed LLIL visit_tree missing LLIL_LOAD expressions
- Added LLIL visitor test
- Made all WARP file pickers use the rfd crate
Use the dev branch of Binary Ninja in rust CI
Misc rust fmt
More rust cleanup
- Refactored BinaryReader and BinaryWriter
- Added some warnings to high_level_il and medium_level_il modules that they are unstable
- Add BinaryReader and BinaryWriter tests
- Changed BinaryView len to return u64 (that is what the core returns)
- Misc formatting changes
- Remove extern uses in lib.rs
Add impl Debug for BinaryReader and BinaryWriter
Turn off broken tests
Add more info to the rust README.md
More rust cleanup
- Make EdgeStyle type not wrap raw
- Regression tests for WARP will run on all bins in the out dir now
impl rust Collaboration and Remote API
Fix typo
Update collaboration API
Makes collaboration more in line with the refactor. Still a lot of work to do.
Namely still need:
- Proper errors
- _with_opts functions
- More ergonomic api
- Better connection procedure
- Updated documentation
- A LOT of unit tests
- An example
- Typed id's for everything (i dont want BnString as the id!!!)
- NEED to refactor the progress callbacks into the new progress api, but we should pull in some of the stuff the collab progress has
- Elimination of apis that are dumb helpers
Separate out the rust testing and use pull_request_target
pull_request_target allows PR's to access the headless license, for this to be safe we need to prevent people from running the job.
To prevent the job from being ran we add an environment requirement on testing that a reviewer must review the code and then manually approve it to run.
More rust cleanup
- Use GroupId instead of u64
- Use ProgressCallback in place of ProgressExecutor
- Misc cleanup of FileMetadata
- Add `save_to_path` and `save_to_accessor` to save modified binaries
- Added binary_view unit tests
- Added collaboration unit tests
- Fixed a few issues with the collaboration apis
- Renamed Command registration functions so that there is no import ambiguity
- Split out RemoteUndoEntry
- Collaboration apis now have a explicit `_with_progress` set of apis
- Misc clippy lint fixes
Fix some typos
More rust cleanup
- Add extra info to README.md
- Refactor components api
- Add components unit test
Add testing and documentation to contributing section in README.md
Fix misc doc comments
Diffstat (limited to 'plugins/dwarf')
22 files changed, 4798 insertions, 0 deletions
diff --git a/plugins/dwarf/dwarf_export/CMakeLists.txt b/plugins/dwarf/dwarf_export/CMakeLists.txt new file mode 100644 index 00000000..fc54f2ee --- /dev/null +++ b/plugins/dwarf/dwarf_export/CMakeLists.txt @@ -0,0 +1,93 @@ +cmake_minimum_required(VERSION 3.9 FATAL_ERROR) + +project(dwarf_export) + +file(GLOB PLUGIN_SOURCES CONFIGURE_DEPENDS + ${PROJECT_SOURCE_DIR}/Cargo.toml + ${PROJECT_SOURCE_DIR}/src/*.rs + ${PROJECT_SOURCE_DIR}/../shared/Cargo.toml + ${PROJECT_SOURCE_DIR}/../shared/src/*.rs) + +file(GLOB_RECURSE API_SOURCES CONFIGURE_DEPENDS + ${PROJECT_SOURCE_DIR}/../../binaryninjacore.h + ${PROJECT_SOURCE_DIR}/../../rust/binaryninjacore-sys/build.rs + ${PROJECT_SOURCE_DIR}/../../rust/binaryninjacore-sys/Cargo.toml + ${PROJECT_SOURCE_DIR}/../../rust/binaryninjacore-sys/src/* + ${PROJECT_SOURCE_DIR}/../../rust/Cargo.toml + ${PROJECT_SOURCE_DIR}/../../rust/src/*.rs) + +if(CMAKE_BUILD_TYPE MATCHES Debug) + set(TARGET_DIR ${PROJECT_BINARY_DIR}/target/debug) + set(CARGO_OPTS --target-dir=${PROJECT_BINARY_DIR}/target) +else() + set(TARGET_DIR ${PROJECT_BINARY_DIR}/target/release) + set(CARGO_OPTS --target-dir=${PROJECT_BINARY_DIR}/target --release) + set(OUTPUT_PDB_NAME ${CMAKE_SHARED_LIBRARY_PREFIX}dwarf_export.pdb) +endif() + +set(OUTPUT_FILE ${CMAKE_STATIC_LIBRARY_PREFIX}dwarf_export${CMAKE_SHARED_LIBRARY_SUFFIX}) +set(PLUGIN_PATH ${TARGET_DIR}/${OUTPUT_FILE}) + +add_custom_target(dwarf_export ALL DEPENDS ${PLUGIN_PATH}) +add_dependencies(dwarf_export binaryninjaapi) + +find_program(RUSTUP_PATH rustup REQUIRED HINTS ~/.cargo/bin) +if(CARGO_API_VERSION) + set(RUSTUP_COMMAND ${RUSTUP_PATH} run ${CARGO_API_VERSION} cargo build) +else() + set(RUSTUP_COMMAND ${RUSTUP_PATH} run ${CARGO_STABLE_VERSION} cargo build) +endif() + +if(APPLE) + if(UNIVERSAL) + if(CMAKE_BUILD_TYPE MATCHES Debug) + set(AARCH64_LIB_PATH ${PROJECT_BINARY_DIR}/target/aarch64-apple-darwin/debug/${OUTPUT_FILE}) + set(X86_64_LIB_PATH ${PROJECT_BINARY_DIR}/target/x86_64-apple-darwin/debug/${OUTPUT_FILE}) + else() + set(AARCH64_LIB_PATH ${PROJECT_BINARY_DIR}/target/aarch64-apple-darwin/release/${OUTPUT_FILE}) + set(X86_64_LIB_PATH ${PROJECT_BINARY_DIR}/target/x86_64-apple-darwin/release/${OUTPUT_FILE}) + endif() + + add_custom_command( + OUTPUT ${PLUGIN_PATH} + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BN_CORE_OUTPUT_DIR} + ${RUSTUP_COMMAND} --target=aarch64-apple-darwin ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BN_CORE_OUTPUT_DIR} + ${RUSTUP_COMMAND} --target=x86_64-apple-darwin ${CARGO_OPTS} + COMMAND mkdir -p ${TARGET_DIR} + COMMAND lipo -create ${AARCH64_LIB_PATH} ${X86_64_LIB_PATH} -output ${PLUGIN_PATH} + COMMAND ${CMAKE_COMMAND} -E copy ${PLUGIN_PATH} ${BN_CORE_PLUGIN_DIR} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES}) + else() + if(CMAKE_BUILD_TYPE MATCHES Debug) + set(LIB_PATH ${PROJECT_BINARY_DIR}/target/debug/${OUTPUT_FILE}) + else() + set(LIB_PATH ${PROJECT_BINARY_DIR}/target/release/${OUTPUT_FILE}) + endif() + + add_custom_command( + OUTPUT ${PLUGIN_PATH} + COMMAND ${CMAKE_COMMAND} -E env MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BN_CORE_OUTPUT_DIR} ${RUSTUP_COMMAND} ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E copy ${PLUGIN_PATH} ${BN_CORE_PLUGIN_DIR} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES}) + endif() +elseif(WIN32) + add_custom_command( + OUTPUT ${PLUGIN_PATH} + COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BN_CORE_OUTPUT_DIR} ${RUSTUP_COMMAND} ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E copy ${PLUGIN_PATH} ${BN_CORE_PLUGIN_DIR} + COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_PDB_NAME} ${BN_CORE_PLUGIN_DIR} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES}) +else() + add_custom_command( + OUTPUT ${PLUGIN_PATH} + COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BN_CORE_OUTPUT_DIR} ${RUSTUP_COMMAND} ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E copy ${PLUGIN_PATH} ${BN_CORE_PLUGIN_DIR} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES}) +endif() diff --git a/plugins/dwarf/dwarf_export/Cargo.toml b/plugins/dwarf/dwarf_export/Cargo.toml new file mode 100644 index 00000000..a3d0e75b --- /dev/null +++ b/plugins/dwarf/dwarf_export/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "dwarf_export" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +binaryninja.workspace = true +binaryninjacore-sys.workspace = true +gimli = "^0.31" +log = "0.4" +object = { version = "0.32.1", features = ["write"] } diff --git a/plugins/dwarf/dwarf_export/README.md b/plugins/dwarf/dwarf_export/README.md new file mode 100644 index 00000000..cea8e3b4 --- /dev/null +++ b/plugins/dwarf/dwarf_export/README.md @@ -0,0 +1 @@ +# DWARF Export diff --git a/plugins/dwarf/dwarf_export/build.rs b/plugins/dwarf/dwarf_export/build.rs new file mode 100644 index 00000000..ed6cec7d --- /dev/null +++ b/plugins/dwarf/dwarf_export/build.rs @@ -0,0 +1,15 @@ +fn main() { + let link_path = std::env::var_os("DEP_BINARYNINJACORE_PATH") + .expect("DEP_BINARYNINJACORE_PATH not specified"); + + println!("cargo::rustc-link-lib=dylib=binaryninjacore"); + println!("cargo::rustc-link-search={}", link_path.to_str().unwrap()); + + #[cfg(not(target_os = "windows"))] + { + println!( + "cargo::rustc-link-arg=-Wl,-rpath,{0},-L{0}", + link_path.to_string_lossy() + ); + } +} diff --git a/plugins/dwarf/dwarf_export/src/edit_distance.rs b/plugins/dwarf/dwarf_export/src/edit_distance.rs new file mode 100644 index 00000000..9f135451 --- /dev/null +++ b/plugins/dwarf/dwarf_export/src/edit_distance.rs @@ -0,0 +1,44 @@ +pub(crate) fn distance(a: &str, b: &str) -> usize { + if a == b { + return 0; + } + match (a.chars().count(), b.chars().count()) { + (0, b) => return b, + (a, 0) => return a, + // (a_len, b_len) if a_len < b_len => return distance(b, a), + _ => (), + } + + let mut result = 0; + let mut cache: Vec<usize> = (1..a.chars().count() + 1).collect(); + + for (index_b, char_b) in b.chars().enumerate() { + result = index_b; + let mut distance_a = index_b; + + for (index_a, char_a) in a.chars().enumerate() { + let distance_b = if char_a == char_b { + distance_a + } else { + distance_a + 1 + }; + + distance_a = cache[index_a]; + + result = if distance_a > result { + if distance_b > result { + result + 1 + } else { + distance_b + } + } else if distance_b > distance_a { + distance_a + 1 + } else { + distance_b + }; + + cache[index_a] = result; + } + } + result +} diff --git a/plugins/dwarf/dwarf_export/src/lib.rs b/plugins/dwarf/dwarf_export/src/lib.rs new file mode 100644 index 00000000..26be0434 --- /dev/null +++ b/plugins/dwarf/dwarf_export/src/lib.rs @@ -0,0 +1,791 @@ +mod edit_distance; + +use gimli::{ + constants, + write::{ + Address, AttributeValue, DwarfUnit, EndianVec, Expression, Range, RangeList, Sections, + UnitEntryId, + }, +}; +use object::{write, Architecture, BinaryFormat, SectionKind}; +use std::fs; + +use binaryninja::logger::Logger; +use binaryninja::{ + binary_view::{BinaryView, BinaryViewBase, BinaryViewExt}, + command::{register_command, Command}, + confidence::Conf, + interaction, + interaction::{FormResponses, FormResponses::Index}, + rc::Ref, + string::BnString, + symbol::SymbolType, + types::{MemberAccess, StructureType, Type, TypeClass}, +}; +use log::{error, info, LevelFilter}; + +fn export_type( + name: String, + t: &Type, + bv: &BinaryView, + defined_types: &mut Vec<(Ref<Type>, UnitEntryId)>, + dwarf: &mut DwarfUnit, +) -> Option<UnitEntryId> { + if let Some((_, die)) = defined_types + .iter() + .find(|(defined_type, _)| defined_type.as_ref() == t) + { + return Some(*die); + } + + let root = dwarf.unit.root(); + match t.type_class() { + TypeClass::VoidTypeClass => { + let void_die_uid = dwarf.unit.add(root, constants::DW_TAG_unspecified_type); + defined_types.push((t.to_owned(), void_die_uid)); + + dwarf.unit.get_mut(void_die_uid).set( + gimli::DW_AT_name, + AttributeValue::String("void".as_bytes().to_vec()), + ); + Some(void_die_uid) + } + TypeClass::BoolTypeClass => { + let bool_die_uid = dwarf.unit.add(root, constants::DW_TAG_base_type); + defined_types.push((t.to_owned(), bool_die_uid)); + + dwarf.unit.get_mut(bool_die_uid).set( + gimli::DW_AT_name, + AttributeValue::String(name.as_bytes().to_vec()), + ); + dwarf.unit.get_mut(bool_die_uid).set( + gimli::DW_AT_byte_size, + AttributeValue::Data1(t.width() as u8), + ); + dwarf.unit.get_mut(bool_die_uid).set( + gimli::DW_AT_encoding, + AttributeValue::Encoding(constants::DW_ATE_float), + ); + Some(bool_die_uid) + } + TypeClass::IntegerTypeClass => { + let int_die_uid = dwarf.unit.add(root, constants::DW_TAG_base_type); + defined_types.push((t.to_owned(), int_die_uid)); + + dwarf.unit.get_mut(int_die_uid).set( + gimli::DW_AT_name, + AttributeValue::String(name.as_bytes().to_vec()), + ); + dwarf.unit.get_mut(int_die_uid).set( + gimli::DW_AT_byte_size, + AttributeValue::Data1(t.width() as u8), + ); + dwarf.unit.get_mut(int_die_uid).set( + gimli::DW_AT_encoding, + if t.is_signed().contents { + AttributeValue::Encoding(constants::DW_ATE_signed) + } else { + AttributeValue::Encoding(constants::DW_ATE_unsigned) + }, + ); + Some(int_die_uid) + } + TypeClass::FloatTypeClass => { + let float_die_uid = dwarf.unit.add(root, constants::DW_TAG_base_type); + defined_types.push((t.to_owned(), float_die_uid)); + + dwarf.unit.get_mut(float_die_uid).set( + gimli::DW_AT_name, + AttributeValue::String(name.as_bytes().to_vec()), + ); + dwarf.unit.get_mut(float_die_uid).set( + gimli::DW_AT_byte_size, + AttributeValue::Data1(t.width() as u8), + ); + dwarf.unit.get_mut(float_die_uid).set( + gimli::DW_AT_encoding, + AttributeValue::Encoding(constants::DW_ATE_float), + ); + Some(float_die_uid) + } + TypeClass::StructureTypeClass => { + let structure_die_uid = match t.get_structure().unwrap().structure_type() { + StructureType::ClassStructureType => { + dwarf.unit.add(root, constants::DW_TAG_class_type) + } + StructureType::StructStructureType => { + dwarf.unit.add(root, constants::DW_TAG_structure_type) + } + StructureType::UnionStructureType => { + dwarf.unit.add(root, constants::DW_TAG_union_type) + } + }; + defined_types.push((t.to_owned(), structure_die_uid)); + + dwarf.unit.get_mut(structure_die_uid).set( + gimli::DW_AT_name, + AttributeValue::String(name.as_bytes().to_vec()), + ); + dwarf.unit.get_mut(structure_die_uid).set( + gimli::DW_AT_byte_size, + AttributeValue::Data2(t.width() as u16), + ); + + for struct_member in t.get_structure().unwrap().members() { + let struct_member_die_uid = + dwarf.unit.add(structure_die_uid, constants::DW_TAG_member); + dwarf.unit.get_mut(struct_member_die_uid).set( + gimli::DW_AT_name, + AttributeValue::String(struct_member.name.as_bytes().to_vec()), + ); + match struct_member.access { + MemberAccess::PrivateAccess => { + dwarf.unit.get_mut(struct_member_die_uid).set( + gimli::DW_AT_accessibility, + AttributeValue::Accessibility(gimli::DW_ACCESS_private), + ); + } + MemberAccess::ProtectedAccess => { + dwarf.unit.get_mut(struct_member_die_uid).set( + gimli::DW_AT_accessibility, + AttributeValue::Accessibility(gimli::DW_ACCESS_protected), + ); + } + MemberAccess::PublicAccess => { + dwarf.unit.get_mut(struct_member_die_uid).set( + gimli::DW_AT_accessibility, + AttributeValue::Accessibility(gimli::DW_ACCESS_public), + ); + } + _ => (), + }; + dwarf.unit.get_mut(struct_member_die_uid).set( + gimli::DW_AT_data_member_location, + AttributeValue::Data8(struct_member.offset), + ); + + if let Some(target_die_uid) = export_type( + format!("{}", struct_member.ty.contents), + struct_member.ty.contents.as_ref(), + bv, + defined_types, + dwarf, + ) { + dwarf + .unit + .get_mut(struct_member_die_uid) + .set(gimli::DW_AT_type, AttributeValue::UnitRef(target_die_uid)); + } + } + + Some(structure_die_uid) + } + TypeClass::EnumerationTypeClass => { + let enum_die_uid = dwarf.unit.add(root, constants::DW_TAG_enumeration_type); + defined_types.push((t.to_owned(), enum_die_uid)); + + dwarf.unit.get_mut(enum_die_uid).set( + gimli::DW_AT_name, + AttributeValue::String(name.as_bytes().to_vec()), + ); + dwarf.unit.get_mut(enum_die_uid).set( + gimli::DW_AT_byte_size, + AttributeValue::Data1(t.width() as u8), + ); + + for enum_field in t.get_enumeration().unwrap().members() { + let enum_field_die_uid = dwarf.unit.add(enum_die_uid, constants::DW_TAG_enumerator); + dwarf.unit.get_mut(enum_field_die_uid).set( + gimli::DW_AT_name, + AttributeValue::String(enum_field.name.as_bytes().to_vec()), + ); + dwarf.unit.get_mut(enum_field_die_uid).set( + gimli::DW_AT_const_value, + AttributeValue::Data4(enum_field.value as u32), + ); + } + + Some(enum_die_uid) + } + TypeClass::PointerTypeClass => { + let pointer_die_uid = dwarf.unit.add(root, constants::DW_TAG_pointer_type); + defined_types.push((t.to_owned(), pointer_die_uid)); + + dwarf.unit.get_mut(pointer_die_uid).set( + gimli::DW_AT_byte_size, + AttributeValue::Data1(t.width() as u8), + ); + if let Some(Conf { + contents: target_type, + .. + }) = t.target() + { + // TODO : Passing through the name here might be wrong + if let Some(target_die_uid) = + export_type(name, &target_type, bv, defined_types, dwarf) + { + dwarf + .unit + .get_mut(pointer_die_uid) + .set(gimli::DW_AT_type, AttributeValue::UnitRef(target_die_uid)); + } + } + Some(pointer_die_uid) + } + TypeClass::ArrayTypeClass => { + let array_die_uid = dwarf.unit.add(root, constants::DW_TAG_array_type); + defined_types.push((t.to_owned(), array_die_uid)); + + // Name + dwarf.unit.get_mut(array_die_uid).set( + gimli::DW_AT_name, + AttributeValue::String(name.as_bytes().to_vec()), + ); + + // Element type + if let Some(Conf { + contents: element_type, + .. + }) = t.element_type() + { + // TODO : Passing through the name here might be wrong + if let Some(target_die_uid) = + export_type(name, &element_type, bv, defined_types, dwarf) + { + dwarf + .unit + .get_mut(array_die_uid) + .set(gimli::DW_AT_type, AttributeValue::UnitRef(target_die_uid)); + } + } + + // For some reason subrange types have a 'type' field that is just "some type" that'll work to index this array + // We're hardcoding this to a uint64_t. This could be unsound. + let array_accessor_type = export_type( + "uint64_t".to_string(), + &Type::named_int(8, false, "uint64_t"), + bv, + defined_types, + dwarf, + ) + .unwrap(); + + // Array length and multidimensional arrays + let mut current_t = t.to_owned(); + while let Some(Conf { + contents: element_type, + .. + }) = current_t.element_type() + { + let array_dimension_die_uid = dwarf + .unit + .add(array_die_uid, constants::DW_TAG_subrange_type); + + dwarf.unit.get_mut(array_dimension_die_uid).set( + gimli::DW_AT_type, + AttributeValue::UnitRef(array_accessor_type), + ); + + dwarf.unit.get_mut(array_dimension_die_uid).set( + gimli::DW_AT_upper_bound, + AttributeValue::Data8(current_t.count() - 1), + ); + + if element_type.type_class() != TypeClass::ArrayTypeClass { + break; + } else { + current_t = element_type; + } + } + + Some(array_die_uid) + } + TypeClass::FunctionTypeClass => { + Some(dwarf.unit.add(root, constants::DW_TAG_unspecified_type)) + } + TypeClass::VarArgsTypeClass => { + Some(dwarf.unit.add(root, constants::DW_TAG_unspecified_type)) + } + TypeClass::ValueTypeClass => Some(dwarf.unit.add(root, constants::DW_TAG_unspecified_type)), + TypeClass::NamedTypeReferenceClass => { + let ntr = t.get_named_type_reference().unwrap(); + if let Some(target_type) = ntr.target(bv) { + if target_type.type_class() == TypeClass::StructureTypeClass { + export_type( + ntr.name().to_string(), + &target_type, + bv, + defined_types, + dwarf, + ) + } else { + let typedef_die_uid = dwarf.unit.add(root, constants::DW_TAG_typedef); + defined_types.push((t.to_owned(), typedef_die_uid)); + + dwarf.unit.get_mut(typedef_die_uid).set( + gimli::DW_AT_name, + AttributeValue::String(ntr.name().to_string().as_bytes().to_vec()), + ); + + if let Some(target_die_uid) = export_type( + ntr.name().to_string(), + &target_type, + bv, + defined_types, + dwarf, + ) { + dwarf + .unit + .get_mut(typedef_die_uid) + .set(gimli::DW_AT_type, AttributeValue::UnitRef(target_die_uid)); + } + Some(typedef_die_uid) + } + } else { + error!("Could not get target of typedef `{}`", ntr.name()); + None + } + } + TypeClass::WideCharTypeClass => { + let wide_char_die_uid = dwarf.unit.add(root, constants::DW_TAG_base_type); + defined_types.push((t.to_owned(), wide_char_die_uid)); + + dwarf.unit.get_mut(wide_char_die_uid).set( + gimli::DW_AT_name, + AttributeValue::String(name.as_bytes().to_vec()), + ); + dwarf.unit.get_mut(wide_char_die_uid).set( + gimli::DW_AT_byte_size, + AttributeValue::Data1(t.width() as u8), + ); + dwarf.unit.get_mut(wide_char_die_uid).set( + gimli::DW_AT_encoding, + if t.is_signed().contents { + AttributeValue::Encoding(constants::DW_ATE_signed_char) + } else { + AttributeValue::Encoding(constants::DW_ATE_unsigned_char) + }, + ); + Some(wide_char_die_uid) + } + } +} + +fn export_types( + bv: &BinaryView, + dwarf: &mut DwarfUnit, + defined_types: &mut Vec<(Ref<Type>, UnitEntryId)>, +) { + for t in &bv.types() { + export_type(t.name.to_string(), &t.ty, bv, defined_types, dwarf); + } +} + +fn export_functions( + bv: &BinaryView, + dwarf: &mut DwarfUnit, + defined_types: &mut Vec<(Ref<Type>, UnitEntryId)>, +) { + let entry_point = bv.entry_point_function(); + + for function in &bv.functions() { + // Create function DIE as child of the compilation unit DIE + let root = dwarf.unit.root(); + let function_die_uid = dwarf.unit.add(root, constants::DW_TAG_subprogram); + // let function_die = dwarf.unit.get_mut(function_die_uid); + + // Set subprogram DIE attributes + dwarf.unit.get_mut(function_die_uid).set( + gimli::DW_AT_name, + AttributeValue::String(function.symbol().short_name().as_bytes().to_vec()), + ); + + // TODO : (DW_AT_main_subprogram VS DW_TAG_entry_point) + // TODO : This attribute seems maybe usually unused? + if let Some(entry_point_function) = &entry_point { + if entry_point_function.as_ref() == function.as_ref() { + dwarf + .unit + .get_mut(function_die_uid) + .set(gimli::DW_AT_main_subprogram, AttributeValue::Flag(true)); + dwarf.unit.get_mut(function_die_uid).set( + gimli::DW_AT_low_pc, + AttributeValue::Address(Address::Constant(function.start())), // TODO: Relocations + ); + } + } + + let address_ranges = function.address_ranges(); + if address_ranges.len() == 1 { + let address_range = address_ranges.get(0); + dwarf.unit.get_mut(function_die_uid).set( + gimli::DW_AT_low_pc, + AttributeValue::Address(Address::Constant(address_range.start)), // TODO: Relocations + ); + dwarf.unit.get_mut(function_die_uid).set( + gimli::DW_AT_high_pc, + AttributeValue::Address(Address::Constant(address_range.end)), + ); + } else { + let range_list = RangeList( + address_ranges + .into_iter() + .map(|range| Range::StartLength { + begin: Address::Constant(range.start), // TODO: Relocations? + length: range.end - range.start, + }) + .collect(), + ); + let range_list_id = dwarf.unit.ranges.add(range_list); + dwarf.unit.get_mut(function_die_uid).set( + gimli::DW_AT_ranges, + AttributeValue::RangeListRef(range_list_id), + ); + } + + // DWARFv4 2.18: " If no DW_AT_entry_pc attribute is present, then the entry address is assumed to be the same as the value of the DW_AT_low_pc attribute" + if address_ranges.get(0).start != function.start() { + dwarf.unit.get_mut(function_die_uid).set( + gimli::DW_AT_entry_pc, + AttributeValue::Address(Address::Constant(function.start())), + ); + } + + if function.return_type().contents.type_class() != TypeClass::VoidTypeClass { + if let Some(return_type_die_uid) = export_type( + format!("{}", function.return_type().contents), + function.return_type().contents.as_ref(), + bv, + defined_types, + dwarf, + ) { + dwarf.unit.get_mut(function_die_uid).set( + gimli::DW_AT_type, + AttributeValue::UnitRef(return_type_die_uid), + ); + } + } + + for parameter in function.function_type().parameters().unwrap() { + let param_die_uid = dwarf + .unit + .add(function_die_uid, constants::DW_TAG_formal_parameter); + + dwarf.unit.get_mut(param_die_uid).set( + gimli::DW_AT_name, + AttributeValue::String(parameter.name.as_bytes().to_vec()), + ); + + if let Some(target_die_uid) = export_type( + format!("{}", parameter.ty.contents), + ¶meter.ty.contents, + bv, + defined_types, + dwarf, + ) { + dwarf + .unit + .get_mut(param_die_uid) + .set(gimli::DW_AT_type, AttributeValue::UnitRef(target_die_uid)); + } + } + + if function.function_type().has_variable_arguments().contents { + dwarf + .unit + .add(function_die_uid, constants::DW_TAG_unspecified_parameters); + } + + if function.symbol().external() { + dwarf + .unit + .get_mut(function_die_uid) + .set(gimli::DW_AT_external, AttributeValue::Flag(true)); + } + + // TODO : calling convention attr + // TODO : local vars + } +} + +fn export_data_vars( + bv: &BinaryView, + dwarf: &mut DwarfUnit, + defined_types: &mut Vec<(Ref<Type>, UnitEntryId)>, +) { + let root = dwarf.unit.root(); + + for data_variable in &bv.data_variables() { + let data_var_sym = bv.symbol_by_address(data_variable.address); + if let Some(symbol) = &data_var_sym { + if let SymbolType::External + | SymbolType::Function + | SymbolType::ImportedFunction + | SymbolType::LibraryFunction = symbol.sym_type() + { + continue; + } + } + + let var_die_uid = dwarf.unit.add(root, constants::DW_TAG_variable); + + if let Some(symbol) = data_var_sym { + dwarf.unit.get_mut(var_die_uid).set( + gimli::DW_AT_name, + AttributeValue::String(symbol.full_name().as_bytes().to_vec()), + ); + + if symbol.external() { + dwarf + .unit + .get_mut(var_die_uid) + .set(gimli::DW_AT_external, AttributeValue::Flag(true)); + } + } else { + dwarf.unit.get_mut(var_die_uid).set( + gimli::DW_AT_name, + AttributeValue::String( + format!("data_{:x}", data_variable.address) + .as_bytes() + .to_vec(), + ), + ); + } + + let mut variable_location = Expression::new(); + variable_location.op_addr(Address::Constant(data_variable.address)); + dwarf.unit.get_mut(var_die_uid).set( + gimli::DW_AT_location, + AttributeValue::Exprloc(variable_location), + ); + + if let Some(target_die_uid) = export_type( + format!("{}", data_variable.ty), + &data_variable.ty.contents, + bv, + defined_types, + dwarf, + ) { + dwarf + .unit + .get_mut(var_die_uid) + .set(gimli::DW_AT_type, AttributeValue::UnitRef(target_die_uid)); + } + } +} + +fn present_form(bv_arch: &str) -> Vec<FormResponses> { + // TODO : Verify inputs (like save location) so that we can fail early + // TODO : Add Language field + // TODO : Choose to export types/functions/etc + let archs = [ + "Unknown", + "Aarch64", + "Aarch64_Ilp32", + "Arm", + "Avr", + "Bpf", + "I386", + "X86_64", + "X86_64_X32", + "Hexagon", + "LoongArch64", + "Mips", + "Mips64", + "Msp430", + "PowerPc", + "PowerPc64", + "Riscv32", + "Riscv64", + "S390x", + "Sbf", + "Sparc64", + "Wasm32", + "Xtensa", + ]; + interaction::FormInputBuilder::new() + .save_file_field( + "Save Location", + Some("Debug Files (*.dwo *.debug);;All Files (*)"), + None, + None, + ) + .choice_field( + "Architecture", + &archs, + archs + .iter() + .enumerate() + .min_by(|&(_, arch_name_1), &(_, arch_name_2)| { + edit_distance::distance(bv_arch, arch_name_1) + .cmp(&edit_distance::distance(bv_arch, arch_name_2)) + }) + .map(|(index, _)| index), + ) + // Add actual / better support for formats other than elf? + // .choice_field( + // "Container Format", + // &["Coff", "Elf", "MachO", "Pe", "Wasm", "Xcoff"], + // None, + // ) + .get_form_input("Export as DWARF") +} + +fn write_dwarf<T: gimli::Endianity>( + responses: Vec<FormResponses>, + endian: T, + dwarf: &mut DwarfUnit, +) { + if responses.len() < 2 { + return; + } + + let arch = match responses[1] { + Index(0) => Architecture::Unknown, + Index(1) => Architecture::Aarch64, + Index(2) => Architecture::Aarch64_Ilp32, + Index(3) => Architecture::Arm, + Index(4) => Architecture::Avr, + Index(5) => Architecture::Bpf, + Index(6) => Architecture::I386, + Index(7) => Architecture::X86_64, + Index(8) => Architecture::X86_64_X32, + Index(9) => Architecture::Hexagon, + Index(10) => Architecture::LoongArch64, + Index(11) => Architecture::Mips, + Index(12) => Architecture::Mips64, + Index(13) => Architecture::Msp430, + Index(14) => Architecture::PowerPc, + Index(15) => Architecture::PowerPc64, + Index(16) => Architecture::Riscv32, + Index(17) => Architecture::Riscv64, + Index(18) => Architecture::S390x, + Index(19) => Architecture::Sbf, + Index(20) => Architecture::Sparc64, + Index(21) => Architecture::Wasm32, + Index(22) => Architecture::Xtensa, + _ => Architecture::Unknown, + }; + + // let format = match responses[2] { + // Index(0) => BinaryFormat::Coff, + // Index(1) => BinaryFormat::Elf, + // Index(2) => BinaryFormat::MachO, + // Index(3) => BinaryFormat::Pe, + // Index(4) => BinaryFormat::Wasm, + // Index(5) => BinaryFormat::Xcoff, + // _ => BinaryFormat::Elf, + // }; + + // TODO : Look in to other options (mangling, flags, etc (see Object::new)) + let mut out_object = write::Object::new( + BinaryFormat::Elf, + arch, + if endian.is_little_endian() { + object::Endianness::Little + } else { + object::Endianness::Big + }, + ); + + // Finally, write the DWARF data to the sections. + let mut sections = Sections::new(EndianVec::new(endian)); + dwarf.write(&mut sections).unwrap(); + + sections + .for_each(|input_id, input_data| { + // Create section in output object + let output_id = out_object.add_section( + vec![], // Only machos have segment names? see object::write::Object::segment_name + input_id.name().as_bytes().to_vec(), + SectionKind::Debug, // TODO: Might be wrong + ); + + // Write data to section in output object + let out_section = out_object.section_mut(output_id); + if out_section.is_bss() { + panic!("Please report this as a bug: output section is bss"); + } else { + out_section.set_data(input_data.clone().into_vec(), 1); + } + // out_section.flags = in_section.flags(); // TODO + + Ok::<(), ()>(()) + }) + .unwrap(); + + if let interaction::FormResponses::String(filename) = &responses[0] { + if let Ok(out_data) = out_object.write() { + if let Err(err) = fs::write(filename, out_data) { + error!("Failed to write DWARF file: {}", err); + } else { + info!("Successfully saved as DWARF to `{}`", filename); + } + } else { + error!("Failed to write DWARF with requested settings"); + } + } +} + +fn export_dwarf(bv: &BinaryView) { + let arch_name = if let Some(arch) = bv.default_arch() { + arch.name() + } else { + BnString::new("Unknown") + }; + let responses = present_form(arch_name.as_str()); + + let encoding = gimli::Encoding { + format: gimli::Format::Dwarf32, + version: 4, + address_size: bv.address_size() as u8, + }; + + // Create a container for a single compilation unit. + // TODO : Add attributes to the compilation unit DIE? + let mut dwarf = DwarfUnit::new(encoding); + dwarf.unit.get_mut(dwarf.unit.root()).set( + gimli::DW_AT_producer, + AttributeValue::String("Binary Ninja DWARF Export Plugin".as_bytes().to_vec()), + ); + + // Everything has types, so we need to track what is already defined globally as to not duplicate type entries + let mut defined_types: Vec<(Ref<Type>, UnitEntryId)> = vec![]; + export_types(bv, &mut dwarf, &mut defined_types); + export_functions(bv, &mut dwarf, &mut defined_types); + export_data_vars(bv, &mut dwarf, &mut defined_types); + // TODO: Export all symbols instead of just data vars? + // TODO: Sections? Segments? + + if bv.default_endianness() == binaryninja::Endianness::LittleEndian { + write_dwarf(responses, gimli::LittleEndian, &mut dwarf); + } else { + write_dwarf(responses, gimli::BigEndian, &mut dwarf); + }; +} + +struct MyCommand; +impl Command for MyCommand { + fn action(&self, view: &BinaryView) { + export_dwarf(view) + } + + fn valid(&self, _view: &BinaryView) -> bool { + true + } +} + +#[no_mangle] +pub extern "C" fn CorePluginInit() -> bool { + Logger::new("DWARF Export") + .with_level(LevelFilter::Debug) + .init(); + + register_command( + "Export as DWARF", + "Export current analysis state and annotations as DWARF for import into other tools", + MyCommand {}, + ); + + true +} diff --git a/plugins/dwarf/dwarf_import/CMakeLists.txt b/plugins/dwarf/dwarf_import/CMakeLists.txt new file mode 100644 index 00000000..480fc442 --- /dev/null +++ b/plugins/dwarf/dwarf_import/CMakeLists.txt @@ -0,0 +1,93 @@ +cmake_minimum_required(VERSION 3.9 FATAL_ERROR) + +project(dwarf_import) + +file(GLOB PLUGIN_SOURCES CONFIGURE_DEPENDS + ${PROJECT_SOURCE_DIR}/Cargo.toml + ${PROJECT_SOURCE_DIR}/src/*.rs + ${PROJECT_SOURCE_DIR}/../shared/Cargo.toml + ${PROJECT_SOURCE_DIR}/../shared/src/*.rs) + +file(GLOB_RECURSE API_SOURCES CONFIGURE_DEPENDS + ${PROJECT_SOURCE_DIR}/../../binaryninjacore.h + ${PROJECT_SOURCE_DIR}/../../rust/binaryninjacore-sys/build.rs + ${PROJECT_SOURCE_DIR}/../../rust/binaryninjacore-sys/Cargo.toml + ${PROJECT_SOURCE_DIR}/../../rust/binaryninjacore-sys/src/* + ${PROJECT_SOURCE_DIR}/../../rust/Cargo.toml + ${PROJECT_SOURCE_DIR}/../../rust/src/*.rs) + +if(CMAKE_BUILD_TYPE MATCHES Debug) + set(TARGET_DIR ${PROJECT_BINARY_DIR}/target/debug) + set(CARGO_OPTS --target-dir=${PROJECT_BINARY_DIR}/target) +else() + set(TARGET_DIR ${PROJECT_BINARY_DIR}/target/release) + set(CARGO_OPTS --target-dir=${PROJECT_BINARY_DIR}/target --release) + set(OUTPUT_PDB_NAME ${CMAKE_SHARED_LIBRARY_PREFIX}dwarf_import.pdb) +endif() + +set(OUTPUT_FILE ${CMAKE_STATIC_LIBRARY_PREFIX}dwarf_import${CMAKE_SHARED_LIBRARY_SUFFIX}) +set(PLUGIN_PATH ${TARGET_DIR}/${OUTPUT_FILE}) + +add_custom_target(dwarf_import ALL DEPENDS ${PLUGIN_PATH}) +add_dependencies(dwarf_import binaryninjaapi) + +find_program(RUSTUP_PATH rustup REQUIRED HINTS ~/.cargo/bin) +if(CARGO_API_VERSION) + set(RUSTUP_COMMAND ${RUSTUP_PATH} run ${CARGO_API_VERSION} cargo build) +else() + set(RUSTUP_COMMAND ${RUSTUP_PATH} run ${CARGO_STABLE_VERSION} cargo build) +endif() + +if(APPLE) + if(UNIVERSAL) + if(CMAKE_BUILD_TYPE MATCHES Debug) + set(AARCH64_LIB_PATH ${PROJECT_BINARY_DIR}/target/aarch64-apple-darwin/debug/${OUTPUT_FILE}) + set(X86_64_LIB_PATH ${PROJECT_BINARY_DIR}/target/x86_64-apple-darwin/debug/${OUTPUT_FILE}) + else() + set(AARCH64_LIB_PATH ${PROJECT_BINARY_DIR}/target/aarch64-apple-darwin/release/${OUTPUT_FILE}) + set(X86_64_LIB_PATH ${PROJECT_BINARY_DIR}/target/x86_64-apple-darwin/release/${OUTPUT_FILE}) + endif() + + add_custom_command( + OUTPUT ${PLUGIN_PATH} + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BN_CORE_OUTPUT_DIR} + ${RUSTUP_COMMAND} --target=aarch64-apple-darwin ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E env + MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BN_CORE_OUTPUT_DIR} + ${RUSTUP_COMMAND} --target=x86_64-apple-darwin ${CARGO_OPTS} + COMMAND mkdir -p ${TARGET_DIR} + COMMAND lipo -create ${AARCH64_LIB_PATH} ${X86_64_LIB_PATH} -output ${PLUGIN_PATH} + COMMAND ${CMAKE_COMMAND} -E copy ${PLUGIN_PATH} ${BN_CORE_PLUGIN_DIR} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES}) + else() + if(CMAKE_BUILD_TYPE MATCHES Debug) + set(LIB_PATH ${PROJECT_BINARY_DIR}/target/debug/${OUTPUT_FILE}) + else() + set(LIB_PATH ${PROJECT_BINARY_DIR}/target/release/${OUTPUT_FILE}) + endif() + + add_custom_command( + OUTPUT ${PLUGIN_PATH} + COMMAND ${CMAKE_COMMAND} -E env MACOSX_DEPLOYMENT_TARGET=10.14 BINARYNINJADIR=${BN_CORE_OUTPUT_DIR} ${RUSTUP_COMMAND} ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E copy ${PLUGIN_PATH} ${BN_CORE_PLUGIN_DIR} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES}) + endif() +elseif(WIN32) + add_custom_command( + OUTPUT ${PLUGIN_PATH} + COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BN_CORE_OUTPUT_DIR} ${RUSTUP_COMMAND} ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E copy ${PLUGIN_PATH} ${BN_CORE_PLUGIN_DIR} + COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_PDB_NAME} ${BN_CORE_PLUGIN_DIR} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES}) +else() + add_custom_command( + OUTPUT ${PLUGIN_PATH} + COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BN_CORE_OUTPUT_DIR} ${RUSTUP_COMMAND} ${CARGO_OPTS} + COMMAND ${CMAKE_COMMAND} -E copy ${PLUGIN_PATH} ${BN_CORE_PLUGIN_DIR} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES}) +endif() diff --git a/plugins/dwarf/dwarf_import/Cargo.toml b/plugins/dwarf/dwarf_import/Cargo.toml new file mode 100644 index 00000000..52f89a46 --- /dev/null +++ b/plugins/dwarf/dwarf_import/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "dwarf_import" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +dwarfreader = { path = "../shared/" } +binaryninja.workspace = true +binaryninjacore-sys.workspace = true +gimli = "0.31" +log = "0.4" +iset = "0.2.2" +cpp_demangle = "0.4.3" +regex = "1" +indexmap = "2.5.0"
\ No newline at end of file diff --git a/plugins/dwarf/dwarf_import/build.rs b/plugins/dwarf/dwarf_import/build.rs new file mode 100644 index 00000000..ed6cec7d --- /dev/null +++ b/plugins/dwarf/dwarf_import/build.rs @@ -0,0 +1,15 @@ +fn main() { + let link_path = std::env::var_os("DEP_BINARYNINJACORE_PATH") + .expect("DEP_BINARYNINJACORE_PATH not specified"); + + println!("cargo::rustc-link-lib=dylib=binaryninjacore"); + println!("cargo::rustc-link-search={}", link_path.to_str().unwrap()); + + #[cfg(not(target_os = "windows"))] + { + println!( + "cargo::rustc-link-arg=-Wl,-rpath,{0},-L{0}", + link_path.to_string_lossy() + ); + } +} diff --git a/plugins/dwarf/dwarf_import/src/die_handlers.rs b/plugins/dwarf/dwarf_import/src/die_handlers.rs new file mode 100644 index 00000000..a2db2fb3 --- /dev/null +++ b/plugins/dwarf/dwarf_import/src/die_handlers.rs @@ -0,0 +1,404 @@ +// Copyright 2021-2024 Vector 35 Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::dwarfdebuginfo::{DebugInfoBuilder, DebugInfoBuilderContext, TypeUID}; +use crate::types::get_type; +use crate::{helpers::*, ReaderType}; + +use binaryninja::{ + rc::*, + types::{EnumerationBuilder, FunctionParameter, ReferenceType, Type, TypeBuilder}, +}; + +use gimli::Dwarf; +use gimli::{constants, AttributeValue::Encoding, DebuggingInformationEntry, Unit}; + +pub(crate) fn handle_base_type<R: ReaderType>( + dwarf: &Dwarf<R>, + unit: &Unit<R>, + entry: &DebuggingInformationEntry<R>, + debug_info_builder_context: &DebugInfoBuilderContext<R>, +) -> Option<Ref<Type>> { + // All base types have: + // DW_AT_encoding (our concept of type_class) + // DW_AT_byte_size and/or DW_AT_bit_size + // *DW_AT_name + // *DW_AT_endianity (assumed default for arch) + // *DW_AT_data_bit_offset (assumed 0) + // *Some indication of signedness? + // * = Optional + + let name = debug_info_builder_context.get_name(dwarf, unit, entry)?; + let size = get_size_as_usize(entry)?; + match entry.attr_value(constants::DW_AT_encoding) { + Ok(Some(Encoding(encoding))) => { + match encoding { + constants::DW_ATE_address => None, + constants::DW_ATE_boolean => Some(Type::bool()), + constants::DW_ATE_complex_float => None, + constants::DW_ATE_float => Some(Type::named_float(size, name)), + constants::DW_ATE_signed => Some(Type::named_int(size, true, name)), + constants::DW_ATE_signed_char => Some(Type::named_int(size, true, name)), + constants::DW_ATE_unsigned => Some(Type::named_int(size, false, name)), + constants::DW_ATE_unsigned_char => Some(Type::named_int(size, false, name)), + constants::DW_ATE_imaginary_float => None, + constants::DW_ATE_packed_decimal => None, + constants::DW_ATE_numeric_string => None, + constants::DW_ATE_edited => None, + constants::DW_ATE_signed_fixed => None, + constants::DW_ATE_unsigned_fixed => None, + constants::DW_ATE_decimal_float => Some(Type::named_float(size, name)), + constants::DW_ATE_UTF => Some(Type::named_int(size, false, name)), // TODO : Verify + constants::DW_ATE_UCS => None, + constants::DW_ATE_ASCII => None, // Some sort of array? + constants::DW_ATE_lo_user => None, + constants::DW_ATE_hi_user => None, + _ => None, // Anything else is invalid at time of writing (gimli v0.23.0) + } + } + _ => None, + } +} + +pub(crate) fn handle_enum<R: ReaderType>( + dwarf: &Dwarf<R>, + unit: &Unit<R>, + entry: &DebuggingInformationEntry<R>, + debug_info_builder_context: &DebugInfoBuilderContext<R>, +) -> Option<Ref<Type>> { + // All base types have: + // DW_AT_byte_size + // *DW_AT_name + // *DW_AT_enum_class + // *DW_AT_type + // ?DW_AT_abstract_origin + // ?DW_AT_accessibility + // ?DW_AT_allocated + // ?DW_AT_associated + // ?DW_AT_bit_size + // ?DW_AT_bit_stride + // ?DW_AT_byte_stride + // ?DW_AT_data_location + // ?DW_AT_declaration + // ?DW_AT_description + // ?DW_AT_sibling + // ?DW_AT_signature + // ?DW_AT_specification + // ?DW_AT_start_scope + // ?DW_AT_visibility + // * = Optional + + // Children of enumeration_types are enumerators which contain: + // DW_AT_name + // DW_AT_const_value + // *DW_AT_description + + let enumeration_builder = EnumerationBuilder::new(); + + let mut tree = unit.entries_tree(Some(entry.offset())).unwrap(); + let mut children = tree.root().unwrap().children(); + while let Ok(Some(child)) = children.next() { + if child.entry().tag() == constants::DW_TAG_enumerator { + let name = debug_info_builder_context.get_name(dwarf, unit, child.entry())?; + let attr = &child + .entry() + .attr(constants::DW_AT_const_value) + .unwrap() + .unwrap(); + if let Some(value) = get_attr_as_u64(attr) { + enumeration_builder.insert(name, value); + } else { + log::error!("Unhandled enum member value type - please report this"); + return None; + } + } + } + + let width = match get_size_as_usize(entry).unwrap_or(8) { + 0 => debug_info_builder_context.default_address_size(), + x => x, + }; + + Some(Type::enumeration( + &enumeration_builder.finalize(), + // TODO: This looks bad, look at the comment in [`Type::width`]. + width.try_into().unwrap(), + false, + )) +} + +pub(crate) fn handle_typedef( + debug_info_builder: &mut DebugInfoBuilder, + entry_type: Option<TypeUID>, + typedef_name: &str, +) -> (Option<Ref<Type>>, bool) { + // All base types have: + // DW_AT_name + // *DW_AT_type + // * = Optional + + // This will fail in the case where we have a typedef to a type that doesn't exist (failed to parse, incomplete, etc) + if let Some(entry_type_offset) = entry_type { + if let Some(t) = debug_info_builder.get_type(entry_type_offset) { + return (Some(t.get_type()), typedef_name != t.name); + } + } + + // 5.3: "typedef represents a declaration of the type that is not also a definition" + (None, false) +} + +pub(crate) fn handle_pointer<R: ReaderType>( + entry: &DebuggingInformationEntry<R>, + debug_info_builder_context: &DebugInfoBuilderContext<R>, + debug_info_builder: &mut DebugInfoBuilder, + entry_type: Option<TypeUID>, + reference_type: ReferenceType, +) -> Option<Ref<Type>> { + // All pointer types have: + // DW_AT_type + // *DW_AT_byte_size + // ?DW_AT_name + // ?DW_AT_address + // ?DW_AT_allocated + // ?DW_AT_associated + // ?DW_AT_data_location + // * = Optional + + if let Some(pointer_size) = get_size_as_usize(entry) { + if let Some(entry_type_offset) = entry_type { + let parent_type = debug_info_builder + .get_type(entry_type_offset) + .unwrap() + .get_type(); + Some(Type::pointer_of_width( + parent_type.as_ref(), + pointer_size, + false, + false, + Some(reference_type), + )) + } else { + Some(Type::pointer_of_width( + Type::void().as_ref(), + pointer_size, + false, + false, + Some(reference_type), + )) + } + } else if let Some(entry_type_offset) = entry_type { + let parent_type = debug_info_builder + .get_type(entry_type_offset) + .unwrap() + .get_type(); + Some(Type::pointer_of_width( + parent_type.as_ref(), + debug_info_builder_context.default_address_size(), + false, + false, + Some(reference_type), + )) + } else { + Some(Type::pointer_of_width( + Type::void().as_ref(), + debug_info_builder_context.default_address_size(), + false, + false, + Some(reference_type), + )) + } +} + +pub(crate) fn handle_array<R: ReaderType>( + unit: &Unit<R>, + entry: &DebuggingInformationEntry<R>, + debug_info_builder: &mut DebugInfoBuilder, + entry_type: Option<TypeUID>, +) -> Option<Ref<Type>> { + // All array types have: + // DW_AT_type + // *DW_AT_name + // *DW_AT_ordering + // *DW_AT_byte_stride or DW_AT_bit_stride + // *DW_AT_byte_size or DW_AT_bit_size + // *DW_AT_allocated + // *DW_AT_associated and + // *DW_AT_data_location + // * = Optional + // For multidimensional arrays, DW_TAG_subrange_type or DW_TAG_enumeration_type + + if let Some(entry_type_offset) = entry_type { + let parent_type = debug_info_builder + .get_type(entry_type_offset) + .unwrap() + .get_type(); + + let mut tree = unit.entries_tree(Some(entry.offset())).unwrap(); + let mut children = tree.root().unwrap().children(); + + // TODO : This is currently applying the size in reverse order + let mut result_type: Option<Ref<Type>> = None; + while let Ok(Some(child)) = children.next() { + if let Some(inner_type) = result_type { + result_type = Some(Type::array( + inner_type.as_ref(), + get_subrange_size(child.entry()), + )); + } else { + result_type = Some(Type::array( + parent_type.as_ref(), + get_subrange_size(child.entry()), + )); + } + } + + result_type.map_or(Some(Type::array(parent_type.as_ref(), 0)), Some) + } else { + None + } +} + +pub(crate) fn handle_function<R: ReaderType>( + dwarf: &Dwarf<R>, + unit: &Unit<R>, + entry: &DebuggingInformationEntry<R>, + debug_info_builder_context: &DebugInfoBuilderContext<R>, + debug_info_builder: &mut DebugInfoBuilder, + entry_type: Option<TypeUID>, +) -> Option<Ref<Type>> { + // All subroutine types have: + // *DW_AT_name + // *DW_AT_type (if not provided, void) + // *DW_AT_prototyped + // ?DW_AT_abstract_origin + // ?DW_AT_accessibility + // ?DW_AT_address_class + // ?DW_AT_allocated + // ?DW_AT_associated + // ?DW_AT_data_location + // ?DW_AT_declaration + // ?DW_AT_description + // ?DW_AT_sibling + // ?DW_AT_start_scope + // ?DW_AT_visibility + // * = Optional + + // May have children, including DW_TAG_formal_parameters, which all have: + // *DW_AT_type + // * = Optional + // or is otherwise DW_TAG_unspecified_parameters + + let return_type = match entry_type { + Some(entry_type_offset) => debug_info_builder + .get_type(entry_type_offset) + .expect("Subroutine return type was not processed") + .get_type(), + None => Type::void(), + }; + + // Alias function type in the case that it contains itself + if let Some(name) = debug_info_builder_context.get_name(dwarf, unit, entry) { + let ntr = + Type::named_type_from_type(&name, &Type::function(return_type.as_ref(), vec![], false)); + debug_info_builder.add_type(get_uid(dwarf, unit, entry), name, ntr, false); + } + + let mut parameters: Vec<FunctionParameter> = vec![]; + let mut variable_arguments = false; + + // Get all the children and populate + let mut tree = unit.entries_tree(Some(entry.offset())).unwrap(); + let mut children = tree.root().unwrap().children(); + while let Ok(Some(child)) = children.next() { + if child.entry().tag() == constants::DW_TAG_formal_parameter { + if let (Some(child_uid), Some(name)) = { + ( + get_type( + dwarf, + unit, + child.entry(), + debug_info_builder_context, + debug_info_builder, + ), + debug_info_builder_context.get_name(dwarf, unit, child.entry()), + ) + } { + let child_type = debug_info_builder.get_type(child_uid).unwrap().get_type(); + parameters.push(FunctionParameter::new(child_type, name, None)); + } + } else if child.entry().tag() == constants::DW_TAG_unspecified_parameters { + variable_arguments = true; + } + } + + if debug_info_builder_context + .get_name(dwarf, unit, entry) + .is_some() + { + debug_info_builder.remove_type(get_uid(dwarf, unit, entry)); + } + + Some(Type::function( + return_type.as_ref(), + parameters, + variable_arguments, + )) +} + +pub(crate) fn handle_const( + debug_info_builder: &mut DebugInfoBuilder, + entry_type: Option<TypeUID>, +) -> Option<Ref<Type>> { + // All const types have: + // ?DW_AT_allocated + // ?DW_AT_associated + // ?DW_AT_data_location + // ?DW_AT_name + // ?DW_AT_sibling + // ?DW_AT_type + + if let Some(entry_type_offset) = entry_type { + let parent_type = debug_info_builder + .get_type(entry_type_offset) + .unwrap() + .get_type(); + Some((*parent_type).to_builder().set_const(true).finalize()) + } else { + Some(TypeBuilder::void().set_const(true).finalize()) + } +} + +pub(crate) fn handle_volatile( + debug_info_builder: &mut DebugInfoBuilder, + entry_type: Option<TypeUID>, +) -> Option<Ref<Type>> { + // All const types have: + // ?DW_AT_allocated + // ?DW_AT_associated + // ?DW_AT_data_location + // ?DW_AT_name + // ?DW_AT_sibling + // ?DW_AT_type + + if let Some(entry_type_offset) = entry_type { + let parent_type = debug_info_builder + .get_type(entry_type_offset) + .unwrap() + .get_type(); + Some((*parent_type).to_builder().set_volatile(true).finalize()) + } else { + Some(TypeBuilder::void().set_volatile(true).finalize()) + } +} diff --git a/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs b/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs new file mode 100644 index 00000000..6bf71489 --- /dev/null +++ b/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs @@ -0,0 +1,657 @@ +// Copyright 2021-2024 Vector 35 Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{ + helpers::{get_uid, resolve_specification, DieReference}, + ReaderType, +}; + +use binaryninja::{ + binary_view::{BinaryView, BinaryViewBase, BinaryViewExt}, + debuginfo::{DebugFunctionInfo, DebugInfo}, + platform::Platform, + rc::*, + symbol::SymbolType, + template_simplifier::simplify_str_to_fqn, + types::{FunctionParameter, Type}, + variable::NamedVariableWithType, +}; + +use gimli::{DebuggingInformationEntry, Dwarf, Unit}; + +use binaryninja::confidence::Conf; +use binaryninja::variable::{Variable, VariableSourceType}; +use indexmap::{map::Values, IndexMap}; +use log::{debug, error, warn}; +use std::{cmp::Ordering, collections::HashMap, hash::Hash}; + +pub(crate) type TypeUID = usize; + +///////////////////////// +// FunctionInfoBuilder + +// TODO : Function local variables +#[derive(PartialEq, Eq, Hash)] +pub(crate) struct FunctionInfoBuilder { + pub(crate) full_name: Option<String>, + pub(crate) raw_name: Option<String>, + pub(crate) return_type: Option<TypeUID>, + pub(crate) address: Option<u64>, + pub(crate) parameters: Vec<Option<(String, TypeUID)>>, + pub(crate) platform: Option<Ref<Platform>>, + pub(crate) variable_arguments: bool, + pub(crate) stack_variables: Vec<NamedVariableWithType>, + pub(crate) use_cfa: bool, //TODO actually store more info about the frame base +} + +impl FunctionInfoBuilder { + pub(crate) fn update( + &mut self, + full_name: Option<String>, + raw_name: Option<String>, + return_type: Option<TypeUID>, + address: Option<u64>, + parameters: &Vec<Option<(String, TypeUID)>>, + ) { + if full_name.is_some() { + self.full_name = full_name; + } + + if raw_name.is_some() { + self.raw_name = raw_name; + } + + if return_type.is_some() { + self.return_type = return_type; + } + + if address.is_some() { + self.address = address; + } + + for (i, new_parameter) in parameters.iter().enumerate() { + match self.parameters.get(i) { + Some(None) => self.parameters[i] = new_parameter.clone(), + Some(Some(_)) => (), + // Some(Some((name, _))) if name.as_bytes().is_empty() => { + // self.parameters[i] = new_parameter + // } + // Some(Some((_, uid))) if *uid == 0 => self.parameters[i] = new_parameter, // TODO : This is a placebo....void types aren't actually UID 0 + _ => self.parameters.push(new_parameter.clone()), + } + } + } +} + +////////////////////// +// DebugInfoBuilder + +// TODO : Don't make this pub...fix the value thing +pub(crate) struct DebugType { + pub name: String, + pub ty: Ref<Type>, + pub commit: bool, +} + +impl DebugType { + pub fn get_type(&self) -> Ref<Type> { + self.ty.clone() + } +} + +pub(crate) struct DebugInfoBuilderContext<R: ReaderType> { + units: Vec<Unit<R>>, + sup_units: Vec<Unit<R>>, + names: HashMap<TypeUID, String>, + default_address_size: usize, + pub(crate) total_die_count: usize, + pub(crate) total_unit_size_bytes: usize, +} + +impl<R: ReaderType> DebugInfoBuilderContext<R> { + pub(crate) fn new(view: &BinaryView, dwarf: &Dwarf<R>) -> Option<Self> { + let mut units = vec![]; + let mut iter = dwarf.units(); + while let Ok(Some(header)) = iter.next() { + if let Ok(unit) = dwarf.unit(header) { + units.push(unit); + } else { + error!("Unable to read DWARF information. File may be malformed or corrupted. Not applying debug info."); + return None; + } + } + + let mut sup_units = vec![]; + if let Some(sup_dwarf) = dwarf.sup() { + let mut sup_iter = sup_dwarf.units(); + while let Ok(Some(header)) = sup_iter.next() { + if let Ok(unit) = sup_dwarf.unit(header) { + sup_units.push(unit); + } else { + error!("Unable to read supplementary DWARF information. File may be malformed or corrupted. Not applying debug info."); + return None; + } + } + } + + Some(Self { + units, + sup_units, + names: HashMap::new(), + default_address_size: view.address_size(), + total_die_count: 0, + total_unit_size_bytes: 0, + }) + } + + pub(crate) fn units(&self) -> &[Unit<R>] { + &self.units + } + + pub(crate) fn sup_units(&self) -> &[Unit<R>] { + &self.sup_units + } + + pub(crate) fn default_address_size(&self) -> usize { + self.default_address_size + } + + pub(crate) fn set_name(&mut self, die_uid: TypeUID, name: String) { + // die_uids need to be unique here + assert!(self.names.insert(die_uid, name).is_none()); + } + + pub(crate) fn get_name( + &self, + dwarf: &Dwarf<R>, + unit: &Unit<R>, + entry: &DebuggingInformationEntry<R>, + ) -> Option<String> { + match resolve_specification(dwarf, unit, entry, self) { + DieReference::UnitAndOffset((dwarf, entry_unit, entry_offset)) => self + .names + .get(&get_uid( + dwarf, + entry_unit, + &entry_unit.entry(entry_offset).unwrap(), + )) + .cloned(), + DieReference::Err => None, + } + } +} + +// DWARF info is stored and displayed in a tree, but is really a graph +// The purpose of this builder is to help resolve those graph edges by mapping partial function +// info and types to one DIE's UID (T) before adding the completed info to BN's debug info +pub(crate) struct DebugInfoBuilder { + functions: Vec<FunctionInfoBuilder>, + raw_function_name_indices: HashMap<String, usize>, + full_function_name_indices: HashMap<String, usize>, + types: IndexMap<TypeUID, DebugType>, + data_variables: HashMap<u64, (Option<String>, TypeUID)>, + range_data_offsets: iset::IntervalMap<u64, i64>, +} + +impl DebugInfoBuilder { + pub(crate) fn new() -> Self { + Self { + functions: vec![], + raw_function_name_indices: HashMap::new(), + full_function_name_indices: HashMap::new(), + types: IndexMap::new(), + data_variables: HashMap::new(), + range_data_offsets: iset::IntervalMap::new(), + } + } + + pub(crate) fn set_range_data_offsets(&mut self, offsets: iset::IntervalMap<u64, i64>) { + self.range_data_offsets = offsets + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn insert_function( + &mut self, + full_name: Option<String>, + raw_name: Option<String>, + return_type: Option<TypeUID>, + address: Option<u64>, + parameters: &Vec<Option<(String, TypeUID)>>, + variable_arguments: bool, + use_cfa: bool, + ) -> Option<usize> { + // Returns the index of the function + // Raw names should be the primary key, but if they don't exist, use the full name + // TODO : Consider further falling back on address/architecture + + /* + If it has a raw_name and we know it, update it and return + Else if it has a full_name and we know it, update it and return + Else Add a new entry if we don't know the full_name or raw_name + */ + + if let Some(ident) = &raw_name { + // check if we already know about this raw name's index + // if we do, and the full name will change, remove the known full index if it exists + // update the function + // if the full name exists, update the stored index for the full name + if let Some(idx) = self.raw_function_name_indices.get(ident) { + let function = self.functions.get_mut(*idx).unwrap(); + + if function.full_name.is_some() && function.full_name != full_name { + self.full_function_name_indices + .remove(function.full_name.as_ref().unwrap()); + } + + function.update(full_name, raw_name, return_type, address, parameters); + + if function.full_name.is_some() { + self.full_function_name_indices + .insert(function.full_name.clone().unwrap(), *idx); + } + + return Some(*idx); + } + } else if let Some(ident) = &full_name { + // check if we already know about this full name's index + // if we do, and the raw name will change, remove the known raw index if it exists + // update the function + // if the raw name exists, update the stored index for the raw name + if let Some(idx) = self.full_function_name_indices.get(ident) { + let function = self.functions.get_mut(*idx).unwrap(); + + if function.raw_name.is_some() && function.raw_name != raw_name { + self.raw_function_name_indices + .remove(function.raw_name.as_ref().unwrap()); + } + + function.update(full_name, raw_name, return_type, address, parameters); + + if function.raw_name.is_some() { + self.raw_function_name_indices + .insert(function.raw_name.clone().unwrap(), *idx); + } + + return Some(*idx); + } + } else { + debug!("Function entry in DWARF without full or raw name."); + return None; + } + + let function = FunctionInfoBuilder { + full_name, + raw_name, + return_type, + address, + parameters: parameters.clone(), + platform: None, + variable_arguments, + stack_variables: vec![], + use_cfa, + }; + + if let Some(n) = &function.full_name { + self.full_function_name_indices + .insert(n.clone(), self.functions.len()); + } + + if let Some(n) = &function.raw_name { + self.raw_function_name_indices + .insert(n.clone(), self.functions.len()); + } + + self.functions.push(function); + Some(self.functions.len() - 1) + } + + pub(crate) fn functions(&self) -> &[FunctionInfoBuilder] { + &self.functions + } + + #[allow(dead_code)] + pub(crate) fn types(&self) -> Values<'_, TypeUID, DebugType> { + self.types.values() + } + + pub(crate) fn add_type(&mut self, type_uid: TypeUID, name: String, t: Ref<Type>, commit: bool) { + if let Some(DebugType { + name: existing_name, + ty: existing_type, + commit: _, + }) = self.types.insert( + type_uid, + DebugType { + name: name.clone(), + ty: t.clone(), + commit, + }, + ) { + if existing_type != t && commit { + warn!("DWARF info contains duplicate type definition. Overwriting type `{}` (named `{:?}`) with `{}` (named `{:?}`)", + existing_type, + existing_name, + t, + name + ); + } + } + } + + pub(crate) fn remove_type(&mut self, type_uid: TypeUID) { + self.types.swap_remove(&type_uid); + } + + pub(crate) fn get_type(&self, type_uid: TypeUID) -> Option<&DebugType> { + self.types.get(&type_uid) + } + + pub(crate) fn contains_type(&self, type_uid: TypeUID) -> bool { + self.types.contains_key(&type_uid) + } + + pub(crate) fn add_stack_variable( + &mut self, + fn_idx: Option<usize>, + offset: i64, + name: Option<String>, + type_uid: Option<TypeUID>, + lexical_block: Option<&iset::IntervalSet<u64>>, + ) { + let name = match name { + Some(x) => { + if x.len() == 1 && x.chars().next() == Some('\x00') { + // Anonymous variable, generate name + format!("debug_var_{}", offset) + } else { + x + } + } + None => { + // Anonymous variable, generate name + format!("debug_var_{}", offset) + } + }; + + let Some(function_index) = fn_idx else { + // If we somehow lost track of what subprogram we're in or we're not actually in a subprogram + error!( + "Trying to add a local variable outside of a subprogram. Please report this issue." + ); + return; + }; + + // Either get the known type or use a 0 confidence void type so we at least get the name applied + let ty = match type_uid { + Some(uid) => Conf::new(self.get_type(uid).unwrap().ty.clone(), 128), + None => Conf::new(Type::void(), 0), + }; + let function = &mut self.functions[function_index]; + + // TODO: If we can't find a known offset can we try to guess somehow? + + let Some(func_addr) = function.address else { + // If we somehow are processing a function's variables before the function is created + error!("Trying to add a local variable without a known function start. Please report this issue."); + return; + }; + + let Some(adjustment_at_variable_lifetime_start) = lexical_block + .and_then(|block_ranges| { + block_ranges + .unsorted_iter() + .find_map(|x| self.range_data_offsets.values_overlap(x.start).next()) + }) + .or_else(|| { + // Try using the offset at the adjustment 4 bytes after the function start, in case the function starts with a stack adjustment + // TODO: This is a decent heuristic but not perfect, since further adjustments could still be made + self.range_data_offsets.values_overlap(func_addr + 4).next() + }) + .or_else(|| { + // If all else fails, use the function start address + self.range_data_offsets.values_overlap(func_addr).next() + }) + else { + // Unknown why, but this is happening with MachO + external dSYM + debug!("Refusing to add a local variable ({}@{}) to function at {} without a known CIE offset.", name, offset, func_addr); + return; + }; + + // TODO: handle non-sp frame bases + // TODO: if not in a lexical block these can be wrong, see https://github.com/Vector35/binaryninja-api/issues/5882#issuecomment-2406065057 + let adjusted_offset = if function.use_cfa { + // Apply CFA offset to variable storage offset if DW_AT_frame_base is frame base is CFA + offset + adjustment_at_variable_lifetime_start + } else { + // If it's using SP, we know the SP offset is <SP offset> + (<entry SP CFA offset> - <SP CFA offset>) + let Some(adjustment_at_entry) = + self.range_data_offsets.values_overlap(func_addr).next() + else { + // Unknown why, but this is happening with MachO + external dSYM + debug!("Refusing to add a local variable ({}@{}) to function at {} without a known CIE offset for function start.", name, offset, func_addr); + return; + }; + + offset + (adjustment_at_entry - adjustment_at_variable_lifetime_start) + }; + + if adjusted_offset > 0 { + // If we somehow end up with a positive sp offset + error!("Trying to add a local variable \"{}\" in function at {:#x} at positive storage offset {}. Please report this issue.", name, func_addr, adjusted_offset); + return; + } + + let var = Variable::new( + VariableSourceType::StackVariableSourceType, + 0, + adjusted_offset, + ); + function + .stack_variables + .push(NamedVariableWithType::new(var, ty, name, false)); + } + + pub(crate) fn add_data_variable( + &mut self, + address: u64, + name: Option<String>, + type_uid: TypeUID, + ) { + if let Some((_existing_name, existing_type_uid)) = + self.data_variables.insert(address, (name, type_uid)) + { + let existing_type = self.get_type(existing_type_uid).unwrap().ty.as_ref(); + let new_type = self.get_type(type_uid).unwrap().ty.as_ref(); + + if existing_type_uid != type_uid || existing_type != new_type { + warn!("DWARF info contains duplicate data variable definition. Overwriting data variable at 0x{:08x} (`{}`) with `{}`", + address, + existing_type, + new_type + ); + } + } + } + + fn commit_types(&self, debug_info: &mut DebugInfo) { + let mut type_uids_by_name: HashMap<String, TypeUID> = HashMap::new(); + + for (debug_type_uid, debug_type) in self.types.iter() { + if !debug_type.commit { + continue; + } + + let mut debug_type_name = debug_type.name.clone(); + + // Prevent storing two types with the same name and differing definitions + if let Some(stored_uid) = type_uids_by_name.get(&debug_type_name) { + let Some(stored_debug_type) = self.types.get(stored_uid) else { + error!("Stored type name without storing a type! Please report this error. UID: {}, name: {}", stored_uid, debug_type_name); + continue; + }; + + let mut skip_adding_type = false; + if stored_debug_type.ty != debug_type.ty { + // We already stored a type with this name and it's a different type, deconflict the name and try again + let mut i = 1; + loop { + if let Some(stored_uid) = type_uids_by_name.get(&debug_type_name) { + if debug_type_uid == stored_uid { + // We already have a type with this name but it's the same type so we're ok + skip_adding_type = true; + break; + } + if let Some(stored_debug_type) = self.types.get(stored_uid) { + if stored_debug_type.ty == debug_type.ty { + // We already have a type with this name but it's the same type so we're ok + skip_adding_type = true; + break; + } + } + + debug_type_name = format!("{}_{}", debug_type.name, i); + i += 1; + } else { + // We found a unique name + break; + } + } + } + + if skip_adding_type { + continue; + } + }; + + // TODO : Components + debug_info.add_type(&debug_type_name, &debug_type.ty, &[]); + type_uids_by_name.insert(debug_type_name, *debug_type_uid); + } + } + + // TODO : Consume data? + fn commit_data_variables(&self, debug_info: &mut DebugInfo) { + for (&address, (name, type_uid)) in &self.data_variables { + assert!(debug_info.add_data_variable( + address, + &self.get_type(*type_uid).unwrap().ty, + name.clone(), + &[] // TODO : Components + )); + } + } + + fn get_function_type(&self, function: &FunctionInfoBuilder) -> Ref<Type> { + let return_type = match function.return_type { + Some(return_type_id) => { + Conf::new(self.get_type(return_type_id).unwrap().ty.clone(), 128) + } + _ => Conf::new(Type::void(), 0), + }; + + let parameters: Vec<FunctionParameter> = function + .parameters + .iter() + .filter_map(|parameter| match parameter { + Some((name, 0)) => Some(FunctionParameter::new(Type::void(), name.clone(), None)), + Some((name, uid)) => Some(FunctionParameter::new( + self.get_type(*uid).unwrap().ty.clone(), + name.clone(), + None, + )), + _ => None, + }) + .collect(); + + Type::function(&return_type, parameters, function.variable_arguments) + } + + fn commit_functions(&self, debug_info: &mut DebugInfo) { + for function in self.functions() { + // let calling_convention: Option<Ref<CallingConvention<CoreArchitecture>>> = None; + + debug_info.add_function(DebugFunctionInfo::new( + function.full_name.clone(), + function.full_name.clone(), // TODO : This should eventually be changed, but the "full_name" should probably be the unsimplified version, and the "short_name" should be the simplified version...currently the symbols view shows the full version, so changing it here too makes it look bad in the UI + function.raw_name.clone(), + Some(self.get_function_type(function)), + function.address, + function.platform.clone(), + vec![], // TODO : Components + function.stack_variables.clone(), // TODO: local non-stack variables + )); + } + } + + pub(crate) fn post_process(&mut self, bv: &BinaryView, _debug_info: &mut DebugInfo) -> &Self { + // When originally resolving names, we need to check: + // If there's already a name from binja that's "more correct" than what we found (has more namespaces) + // If there's no name for the DIE, but there's a linkage name that's resolved in binja to a usable name + // This is no longer true, because DWARF doesn't provide platform information for functions, so we at least need to post-process thumb functions + + for func in &mut self.functions { + // If the function's raw name already exists in the binary... + if let Some(raw_name) = &func.raw_name { + if let Some(symbol) = bv.symbol_by_raw_name(raw_name) { + // Link mangled names without addresses to existing symbols in the binary + if func.address.is_none() && func.raw_name.is_some() { + // DWARF doesn't contain GOT info, so remove any entries there...they will be wrong (relying on Binja's mechanisms for the GOT is good ) + if symbol.sym_type() != SymbolType::ImportAddress { + func.address = Some(symbol.address()); + } + } + + if let Some(full_name) = &func.full_name { + let func_full_name = full_name; + let symbol_full_name = symbol.full_name(); + + // If our name has fewer namespaces than the existing name, assume we lost the namespace info + if simplify_str_to_fqn(func_full_name, true).items.len() + < simplify_str_to_fqn(symbol_full_name.clone(), true) + .items + .len() + { + func.full_name = Some(symbol_full_name.to_string()); + } + } + } + } + + if let Some(address) = func.address.as_mut() { + let (diff, overflowed) = bv.start().overflowing_sub(bv.original_image_base()); + if !overflowed { + *address = (*address).overflowing_add(diff).0; // rebase the address + let existing_functions = bv.functions_at(*address); + match existing_functions.len().cmp(&1) { + Ordering::Greater => { + warn!("Multiple existing functions at address {address:08x}. One or more functions at this address may have the wrong platform information. Please report this binary."); + } + Ordering::Equal => { + func.platform = Some(existing_functions.get(0).platform()) + } + Ordering::Less => {} + } + } + } + } + + self + } + + pub(crate) fn commit_info(&self, debug_info: &mut DebugInfo) { + self.commit_types(debug_info); + self.commit_data_variables(debug_info); + self.commit_functions(debug_info); + } +} diff --git a/plugins/dwarf/dwarf_import/src/functions.rs b/plugins/dwarf/dwarf_import/src/functions.rs new file mode 100644 index 00000000..47829503 --- /dev/null +++ b/plugins/dwarf/dwarf_import/src/functions.rs @@ -0,0 +1,226 @@ +// Copyright 2021-2024 Vector 35 Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::sync::OnceLock; + +use crate::dwarfdebuginfo::{DebugInfoBuilder, DebugInfoBuilderContext, TypeUID}; +use crate::types::get_type; +use crate::{helpers::*, ReaderType}; + +use binaryninja::template_simplifier::simplify_str_to_str; +use cpp_demangle::DemangleOptions; +use gimli::{constants, AttributeValue, DebuggingInformationEntry, Dwarf, Operation, Unit}; +use log::{debug, error}; +use regex::Regex; + +fn get_parameters<R: ReaderType>( + dwarf: &Dwarf<R>, + unit: &Unit<R>, + entry: &DebuggingInformationEntry<R>, + debug_info_builder_context: &DebugInfoBuilderContext<R>, + debug_info_builder: &mut DebugInfoBuilder, +) -> (Vec<Option<(String, TypeUID)>>, bool) { + if !entry.has_children() { + return (vec![], false); + } + + // We make a new tree from the current entry to iterate over its children + let mut sub_die_tree = unit.entries_tree(Some(entry.offset())).unwrap(); + let root = sub_die_tree.root().unwrap(); + + let mut variable_arguments = false; + let mut result = vec![]; + let mut children = root.children(); + while let Some(child) = children.next().unwrap() { + match child.entry().tag() { + constants::DW_TAG_formal_parameter => { + //TODO: if the param type is a typedef to an anonymous struct (typedef struct {...} foo) then this is reoslved to an anonymous struct instead of foo + // We should still recurse to make sure we load all types this param type depends on, but + let name = debug_info_builder_context.get_name(dwarf, unit, child.entry()); + + let type_ = get_type( + dwarf, + unit, + child.entry(), + debug_info_builder_context, + debug_info_builder, + ); + if let Some(parameter_name) = name { + if let Some(parameter_type) = type_ { + result.push(Some((parameter_name, parameter_type))); + } else { + result.push(Some((parameter_name, 0))) + } + } else { + result.push(None) + } + } + constants::DW_TAG_unspecified_parameters => variable_arguments = true, + _ => (), + } + } + (result, variable_arguments) +} + +pub(crate) fn parse_function_entry<R: ReaderType>( + dwarf: &Dwarf<R>, + unit: &Unit<R>, + entry: &DebuggingInformationEntry<R>, + debug_info_builder_context: &DebugInfoBuilderContext<R>, + debug_info_builder: &mut DebugInfoBuilder, +) -> Option<usize> { + // Collect function properties (if they exist in this DIE) + let raw_name = get_raw_name(dwarf, unit, entry); + let return_type = get_type( + dwarf, + unit, + entry, + debug_info_builder_context, + debug_info_builder, + ); + let address = get_start_address(dwarf, unit, entry); + let (parameters, variable_arguments) = get_parameters( + dwarf, + unit, + entry, + debug_info_builder_context, + debug_info_builder, + ); + + // If we have a raw name, it might be mangled, see if we can demangle it into full_name + // raw_name should contain a superset of the info we have in full_name + let mut full_name = None; + if let Some(possibly_mangled_name) = &raw_name { + if possibly_mangled_name.starts_with('_') { + static OPTIONS_MEM: OnceLock<DemangleOptions> = OnceLock::new(); + let demangle_options = OPTIONS_MEM.get_or_init(|| { + DemangleOptions::new() + .no_return_type() + .hide_expression_literal_types() + .no_params() + }); + + static ABI_REGEX_MEM: OnceLock<Regex> = OnceLock::new(); + let abi_regex = ABI_REGEX_MEM.get_or_init(|| Regex::new(r"\[abi:v\d+\]").unwrap()); + if let Ok(sym) = cpp_demangle::Symbol::new(possibly_mangled_name) { + if let Ok(demangled) = sym.demangle(demangle_options) { + let cleaned = abi_regex.replace_all(&demangled, ""); + let simplified = simplify_str_to_str(&cleaned); + full_name = Some(simplified.to_string()); + } + } + } + } + + // If we didn't demangle the raw name, fetch the name given + if full_name.is_none() { + full_name = debug_info_builder_context.get_name(dwarf, unit, entry) + } + + if raw_name.is_none() && full_name.is_none() { + debug!( + "Function entry in DWARF without full or raw name: .debug_info offset {:?}", + entry.offset().to_debug_info_offset(&unit.header) + ); + return None; + } + + let use_cfa; + if let Ok(Some(AttributeValue::Exprloc(mut expression))) = + entry.attr_value(constants::DW_AT_frame_base) + { + use_cfa = match Operation::parse(&mut expression.0, unit.encoding()) { + Ok(Operation::Register { register: _ }) => false, // TODO: handle register-relative encodings later + Ok(Operation::CallFrameCFA) => true, + _ => false, + }; + } else { + use_cfa = false; + } + + debug_info_builder.insert_function( + full_name, + raw_name, + return_type, + address, + ¶meters, + variable_arguments, + use_cfa, + ) +} + +pub(crate) fn parse_lexical_block<R: ReaderType>( + dwarf: &Dwarf<R>, + unit: &Unit<R>, + entry: &DebuggingInformationEntry<R>, +) -> Option<iset::IntervalSet<u64>> { + // Return lexical block ranges + // Must have either DW_AT_ranges or DW_AT_low_pc and DW_AT_high_pc + let mut result = iset::IntervalSet::new(); + if let Ok(Some(attr_value)) = entry.attr_value(constants::DW_AT_ranges) { + if let Ok(Some(ranges_offset)) = dwarf.attr_ranges_offset(unit, attr_value) { + if let Ok(mut ranges) = dwarf.ranges(unit, ranges_offset) { + while let Ok(Some(range)) = ranges.next() { + // Ranges where start == end may be ignored (DWARFv5 spec, 2.17.3 line 17) + if range.begin == range.end { + continue; + } + result.insert(range.begin..range.end); + } + } + } + } else if let Ok(Some(low_pc_value)) = entry.attr_value(constants::DW_AT_low_pc) { + let Ok(Some(low_pc)) = dwarf.attr_address(unit, low_pc_value.clone()) else { + let unit_base: usize = unit.header.offset().as_debug_info_offset().unwrap().0; + error!( + "Failed to read lexical block low_pc for entry {:#x}, please report this bug.", + unit_base + entry.offset().0 + ); + return None; + }; + + let Ok(Some(high_pc_value)) = entry.attr_value(constants::DW_AT_high_pc) else { + let unit_base: usize = unit.header.offset().as_debug_info_offset().unwrap().0; + error!("Failed to read lexical block high_pc attribute for entry {:#x}, please report this bug.", unit_base + entry.offset().0); + return None; + }; + + let Some(high_pc) = high_pc_value + .udata_value() + .and_then(|x| Some(low_pc + x)) + .or_else(|| dwarf.attr_address(unit, high_pc_value).unwrap_or(None)) + else { + let unit_base: usize = unit.header.offset().as_debug_info_offset().unwrap().0; + error!( + "Failed to read lexical block high_pc for entry {:#x}, please report this bug.", + unit_base + entry.offset().0 + ); + return None; + }; + + if low_pc < high_pc { + result.insert(low_pc..high_pc); + } else { + error!( + "Invalid lexical block range: {:#x} -> {:#x}", + low_pc, high_pc + ); + } + } else { + // If neither case is hit the lexical block doesn't define any ranges and we should ignore it + return None; + } + + Some(result) +} diff --git a/plugins/dwarf/dwarf_import/src/helpers.rs b/plugins/dwarf/dwarf_import/src/helpers.rs new file mode 100644 index 00000000..0a0b90a7 --- /dev/null +++ b/plugins/dwarf/dwarf_import/src/helpers.rs @@ -0,0 +1,605 @@ +// Copyright 2021-2024 Vector 35 Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::ffi::OsStr; +use std::path::{Path, PathBuf}; +use std::{collections::HashMap, ops::Deref, str::FromStr, sync::mpsc}; + +use crate::{DebugInfoBuilderContext, ReaderType}; +use binaryninja::binary_view::BinaryViewBase; +use binaryninja::file_metadata::FileMetadata; +use binaryninja::Endianness; +use binaryninja::{ + binary_view::{BinaryView, BinaryViewExt}, + download_provider::{DownloadInstanceInputOutputCallbacks, DownloadProvider}, + rc::Ref, + settings::Settings, +}; +use gimli::Dwarf; +use gimli::{ + constants, Attribute, AttributeValue, + AttributeValue::{DebugInfoRef, DebugInfoRefSup, UnitRef}, + DebuggingInformationEntry, Operation, Unit, UnitOffset, UnitSectionOffset, +}; + +use binaryninja::settings::QueryOptions; +use log::warn; + +pub(crate) fn get_uid<R: ReaderType>( + dwarf: &Dwarf<R>, + unit: &Unit<R>, + entry: &DebuggingInformationEntry<R>, +) -> usize { + // We set a large gap between supplementary and main entries + let adj = dwarf.sup().map_or(0, |_| 0x1000000000000000); + let entry_offset = match entry.offset().to_unit_section_offset(unit) { + UnitSectionOffset::DebugInfoOffset(o) => o.0, + UnitSectionOffset::DebugTypesOffset(o) => o.0, + }; + entry_offset + adj +} + +//////////////////////////////////// +// DIE attr convenience functions + +pub(crate) enum DieReference<'a, R: ReaderType> { + UnitAndOffset((&'a Dwarf<R>, &'a Unit<R>, UnitOffset)), + Err, +} + +pub(crate) fn get_attr_die<'a, R: ReaderType>( + dwarf: &'a Dwarf<R>, + unit: &'a Unit<R>, + entry: &DebuggingInformationEntry<R>, + debug_info_builder_context: &'a DebugInfoBuilderContext<R>, + attr: constants::DwAt, +) -> Option<DieReference<'a, R>> { + match entry.attr_value(attr) { + Ok(Some(UnitRef(offset))) => Some(DieReference::UnitAndOffset((dwarf, unit, offset))), + Ok(Some(DebugInfoRef(offset))) => { + if dwarf.sup().is_some() { + for source_unit in debug_info_builder_context.units() { + if let Some(new_offset) = offset.to_unit_offset(&source_unit.header) { + return Some(DieReference::UnitAndOffset(( + dwarf, + source_unit, + new_offset, + ))); + } + } + } else { + // This could either have no supplementary file because it is one or because it just doesn't have one + // operate on supplementary file if dwarf is a supplementary file, else self + + // It's possible this is a reference in the supplementary file to itself + for source_unit in debug_info_builder_context.sup_units() { + if let Some(new_offset) = offset.to_unit_offset(&source_unit.header) { + return Some(DieReference::UnitAndOffset(( + dwarf, + source_unit, + new_offset, + ))); + } + } + + // ... or it just doesn't have a supplementary file + for source_unit in debug_info_builder_context.units() { + if let Some(new_offset) = offset.to_unit_offset(&source_unit.header) { + return Some(DieReference::UnitAndOffset(( + dwarf, + source_unit, + new_offset, + ))); + } + } + } + + None + } + Ok(Some(DebugInfoRefSup(offset))) => { + for source_unit in debug_info_builder_context.sup_units() { + if let Some(new_offset) = offset.to_unit_offset(&source_unit.header) { + return Some(DieReference::UnitAndOffset(( + dwarf.sup().unwrap(), + source_unit, + new_offset, + ))); + } + } + warn!("Failed to fetch DIE. Supplementary debug information may be incomplete."); + None + } + _ => None, + } +} + +pub(crate) fn resolve_specification<'a, R: ReaderType>( + dwarf: &'a Dwarf<R>, + unit: &'a Unit<R>, + entry: &DebuggingInformationEntry<R>, + debug_info_builder_context: &'a DebugInfoBuilderContext<R>, +) -> DieReference<'a, R> { + if let Some(die_reference) = get_attr_die( + dwarf, + unit, + entry, + debug_info_builder_context, + constants::DW_AT_specification, + ) { + match die_reference { + DieReference::UnitAndOffset((dwarf, entry_unit, entry_offset)) => { + if let Ok(entry) = entry_unit.entry(entry_offset) { + resolve_specification(dwarf, entry_unit, &entry, debug_info_builder_context) + } else { + warn!("Failed to fetch DIE for attr DW_AT_specification. Debug information may be incomplete."); + DieReference::Err + } + } + DieReference::Err => DieReference::Err, + } + } else if let Some(die_reference) = get_attr_die( + dwarf, + unit, + entry, + debug_info_builder_context, + constants::DW_AT_abstract_origin, + ) { + match die_reference { + DieReference::UnitAndOffset((dwarf, entry_unit, entry_offset)) => { + if entry_offset == entry.offset() + && unit.header.offset() == entry_unit.header.offset() + { + warn!("DWARF information is invalid (infinite abstract origin reference cycle). Debug information may be incomplete."); + DieReference::Err + } else if let Ok(new_entry) = entry_unit.entry(entry_offset) { + resolve_specification(dwarf, entry_unit, &new_entry, debug_info_builder_context) + } else { + warn!("Failed to fetch DIE for attr DW_AT_abstract_origin. Debug information may be incomplete."); + DieReference::Err + } + } + DieReference::Err => DieReference::Err, + } + } else { + DieReference::UnitAndOffset((dwarf, unit, entry.offset())) + } +} + +// Get name from DIE, or referenced dependencies +pub(crate) fn get_name<R: ReaderType>( + dwarf: &Dwarf<R>, + unit: &Unit<R>, + entry: &DebuggingInformationEntry<R>, + debug_info_builder_context: &DebugInfoBuilderContext<R>, +) -> Option<String> { + match resolve_specification(dwarf, unit, entry, debug_info_builder_context) { + DieReference::UnitAndOffset((dwarf, entry_unit, entry_offset)) => { + if let Ok(Some(attr_val)) = entry_unit + .entry(entry_offset) + .unwrap() + .attr_value(constants::DW_AT_name) + { + if let Ok(attr_string) = dwarf.attr_string(entry_unit, attr_val.clone()) { + if let Ok(attr_string) = attr_string.to_string() { + return Some(attr_string.to_string()); + } + } else if let Some(dwarf) = &dwarf.sup { + if let Ok(attr_string) = dwarf.attr_string(entry_unit, attr_val) { + if let Ok(attr_string) = attr_string.to_string() { + return Some(attr_string.to_string()); + } + } + } + } + + // if let Some(raw_name) = get_raw_name(unit, entry, debug_info_builder_context) { + // if let Some(arch) = debug_info_builder_context.default_architecture() { + // if let Ok((_, names)) = demangle_gnu3(&arch, raw_name, true) { + // return Some(names.join("::")); + // } + // } + // } + None + } + DieReference::Err => None, + } +} + +// Get raw name from DIE, or referenced dependencies +pub(crate) fn get_raw_name<R: ReaderType>( + dwarf: &Dwarf<R>, + unit: &Unit<R>, + entry: &DebuggingInformationEntry<R>, +) -> Option<String> { + if let Ok(Some(attr_val)) = entry.attr_value(constants::DW_AT_linkage_name) { + if let Ok(attr_string) = dwarf.attr_string(unit, attr_val.clone()) { + if let Ok(attr_string) = attr_string.to_string() { + return Some(attr_string.to_string()); + } + } else if let Some(dwarf) = dwarf.sup() { + if let Ok(attr_string) = dwarf.attr_string(unit, attr_val) { + if let Ok(attr_string) = attr_string.to_string() { + return Some(attr_string.to_string()); + } + } + } + } + None +} + +// Get the size of an object as a usize +pub(crate) fn get_size_as_usize<R: ReaderType>( + entry: &DebuggingInformationEntry<R>, +) -> Option<usize> { + if let Ok(Some(attr)) = entry.attr(constants::DW_AT_byte_size) { + get_attr_as_usize(attr) + } else if let Ok(Some(attr)) = entry.attr(constants::DW_AT_bit_size) { + get_attr_as_usize(attr).map(|attr_value| attr_value / 8) + } else { + None + } +} + +// Get the size of an object as a u64 +pub(crate) fn get_size_as_u64<R: ReaderType>(entry: &DebuggingInformationEntry<R>) -> Option<u64> { + if let Ok(Some(attr)) = entry.attr(constants::DW_AT_byte_size) { + get_attr_as_u64(&attr) + } else if let Ok(Some(attr)) = entry.attr(constants::DW_AT_bit_size) { + get_attr_as_u64(&attr).map(|attr_value| attr_value / 8) + } else { + None + } +} + +// Get the size of a subrange as a u64 +pub(crate) fn get_subrange_size<R: ReaderType>(entry: &DebuggingInformationEntry<R>) -> u64 { + if let Ok(Some(attr)) = entry.attr(constants::DW_AT_upper_bound) { + get_attr_as_u64(&attr).map_or(0, |v| v + 1) + } else if let Ok(Some(attr)) = entry.attr(constants::DW_AT_count) { + get_attr_as_u64(&attr).unwrap_or(0) + } else if let Ok(Some(attr)) = entry.attr(constants::DW_AT_lower_bound) { + get_attr_as_u64(&attr).map_or(0, |v| v + 1) + } else { + 0 + } +} + +// Get the start address of a function +pub(crate) fn get_start_address<R: ReaderType>( + dwarf: &Dwarf<R>, + unit: &Unit<R>, + entry: &DebuggingInformationEntry<R>, +) -> Option<u64> { + if let Ok(Some(attr_val)) = entry.attr_value(constants::DW_AT_low_pc) { + match dwarf.attr_address(unit, attr_val) { + Ok(Some(val)) => Some(val), + _ => None, + } + } else if let Ok(Some(attr_val)) = entry.attr_value(constants::DW_AT_entry_pc) { + match dwarf.attr_address(unit, attr_val) { + Ok(Some(val)) => Some(val), + _ => None, + } + } else if let Ok(Some(attr_value)) = entry.attr_value(constants::DW_AT_ranges) { + if let Ok(Some(ranges_offset)) = dwarf.attr_ranges_offset(unit, attr_value) { + if let Ok(mut ranges) = dwarf.ranges(unit, ranges_offset) { + if let Ok(Some(range)) = ranges.next() { + return Some(range.begin); + } + } + } + return None; + } else { + None + } +} + +// Get an attribute value as a u64 if it can be coerced +pub(crate) fn get_attr_as_u64<R: ReaderType>(attr: &Attribute<R>) -> Option<u64> { + if let Some(value) = attr.udata_value() { + Some(value) + } else if let Some(value) = attr.sdata_value() { + Some(value as u64) + } else if let AttributeValue::Block(mut data) = attr.value() { + match data.len() { + 1 => data.read_u8().map(u64::from).ok(), + 2 => data.read_u16().map(u64::from).ok(), + 4 => data.read_u32().map(u64::from).ok(), + 8 => data.read_u64().ok(), + _ => None, + } + } else { + None + } +} + +// Get an attribute value as a usize if it can be coerced +pub(crate) fn get_attr_as_usize<R: ReaderType>(attr: Attribute<R>) -> Option<usize> { + if let Some(value) = attr.u8_value() { + Some(value.into()) + } else if let Some(value) = attr.u16_value() { + Some(value.into()) + } else if let Some(value) = attr.udata_value() { + Some(value as usize) + } else { + attr.sdata_value().map(|value| value as usize) + } +} + +// Get an attribute value as a usize if it can be coerced +// Parses DW_OP_address, DW_OP_const +pub(crate) fn get_expr_value<R: ReaderType>(unit: &Unit<R>, attr: Attribute<R>) -> Option<u64> { + if let AttributeValue::Exprloc(mut expression) = attr.value() { + match Operation::parse(&mut expression.0, unit.encoding()) { + Ok(Operation::PlusConstant { value }) => Some(value), + Ok(Operation::UnsignedConstant { value }) => Some(value), + Ok(Operation::Address { address: 0 }) => None, + Ok(Operation::Address { address }) => Some(address), + _ => None, + } + } else { + None + } +} + +pub(crate) fn get_build_id(view: &BinaryView) -> Result<String, String> { + let mut build_id: Option<String> = None; + + if let Some(raw_view) = view.raw_view() { + if let Some(build_id_section) = raw_view.section_by_name(".note.gnu.build-id") { + // Name size - 4 bytes + // Desc size - 4 bytes + // Type - 4 bytes + // Name - n bytes + // Desc - n bytes + let build_id_bytes = + raw_view.read_vec(build_id_section.start(), build_id_section.len()); + if build_id_bytes.len() < 12 { + return Err("Build id section must be at least 12 bytes".to_string()); + } + + let name_len: u32; + let desc_len: u32; + let note_type: u32; + match raw_view.default_endianness() { + Endianness::LittleEndian => { + name_len = u32::from_le_bytes(build_id_bytes[0..4].try_into().unwrap()); + desc_len = u32::from_le_bytes(build_id_bytes[4..8].try_into().unwrap()); + note_type = u32::from_le_bytes(build_id_bytes[8..12].try_into().unwrap()); + } + Endianness::BigEndian => { + name_len = u32::from_be_bytes(build_id_bytes[0..4].try_into().unwrap()); + desc_len = u32::from_be_bytes(build_id_bytes[4..8].try_into().unwrap()); + note_type = u32::from_be_bytes(build_id_bytes[8..12].try_into().unwrap()); + } + }; + + if note_type != 3 { + return Err(format!("Build id section has wrong type: {}", note_type)); + } + + let expected_len = (12 + name_len + desc_len) as usize; + + if build_id_bytes.len() < expected_len { + return Err(format!( + "Build id section not expected length: expected {}, got {}", + expected_len, + build_id_bytes.len() + )); + } + + let desc: &[u8] = &build_id_bytes[(12 + name_len as usize)..expected_len]; + build_id = Some(desc.iter().map(|b| format!("{:02x}", b)).collect()); + } + } + + if let Some(x) = build_id { + Ok(x) + } else { + Err("Failed to get build id".to_string()) + } +} + +pub(crate) fn download_debug_info( + build_id: &str, + view: &BinaryView, +) -> Result<Ref<BinaryView>, String> { + let mut settings_query_opts = QueryOptions::new_with_view(view); + let settings = Settings::new(); + let debug_server_urls = + settings.get_string_list_with_opts("network.debuginfodServers", &mut settings_query_opts); + + for debug_server_url in debug_server_urls.iter() { + let artifact_url = format!("{}/buildid/{}/debuginfo", debug_server_url, build_id); + + // Download from remote + let (tx, rx) = mpsc::channel(); + let write = move |data: &[u8]| -> usize { + if tx.send(Vec::from(data)).is_ok() { + data.len() + } else { + 0 + } + }; + + let dp = DownloadProvider::try_default().map_err(|_| "No default download provider")?; + let mut inst = dp + .create_instance() + .map_err(|_| "Couldn't create download instance")?; + let result = inst + .perform_custom_request( + "GET", + artifact_url, + HashMap::<String, String>::new(), + DownloadInstanceInputOutputCallbacks { + read: None, + write: Some(Box::new(write)), + progress: None, + }, + ) + .map_err(|e| e.to_string())?; + if result.status_code != 200 { + continue; + } + + let mut expected_length = None; + for (k, v) in result.headers.iter() { + if k.to_lowercase() == "content-length" { + expected_length = Some(usize::from_str(v).map_err(|e| e.to_string())?); + } + } + + let mut data = vec![]; + while let Ok(packet) = rx.try_recv() { + data.extend(packet.into_iter()); + } + + if let Some(length) = expected_length { + if data.len() != length { + return Err(format!( + "Bad length: expected {} got {}", + length, + data.len() + )); + } + } + + let options = "{\"analysis.debugInfo.internal\": false}"; + let bv = BinaryView::from_data(FileMetadata::new().deref(), &data) + .map_err(|_| "Unable to create binary view from downloaded data".to_string())?; + + return binaryninja::load_view(bv.deref(), false, Some(options)) + .ok_or("Unable to load binary view from downloaded data".to_string()); + } + Err("Could not find a server with debug info for this file".to_string()) +} + +pub(crate) fn find_local_debug_file_for_build_id( + build_id: &str, + view: &BinaryView, +) -> Option<String> { + let mut settings_query_opts = QueryOptions::new_with_view(view); + let settings = Settings::new(); + let debug_dirs_enabled = settings.get_bool_with_opts( + "analysis.debugInfo.enableDebugDirectories", + &mut settings_query_opts, + ); + + if !debug_dirs_enabled { + return None; + } + + let debug_info_paths = settings.get_string_list_with_opts( + "analysis.debugInfo.debugDirectories", + &mut settings_query_opts, + ); + + if debug_info_paths.is_empty() { + return None; + } + + for debug_info_path in debug_info_paths.into_iter() { + let path = PathBuf::from(debug_info_path); + let elf_path = path.join(&build_id[..2]).join(&build_id[2..]).join("elf"); + + let debug_ext_path = path + .join(&build_id[..2]) + .join(format!("{}.debug", &build_id[2..])); + + let final_path = if debug_ext_path.exists() { + debug_ext_path + } else if elf_path.exists() { + elf_path + } else { + // No paths exist in this dir, try the next one + continue; + }; + return final_path.to_str().and_then(|x| Some(x.to_string())); + } + None +} + +pub(crate) fn load_debug_info_for_build_id( + build_id: &str, + view: &BinaryView, +) -> (Option<Ref<BinaryView>>, bool) { + let mut settings_query_opts = QueryOptions::new_with_view(view); + let settings = Settings::new(); + if let Some(debug_file_path) = find_local_debug_file_for_build_id(build_id, view) { + return ( + binaryninja::load_with_options( + debug_file_path, + false, + Some("{\"analysis.debugInfo.internal\": false}"), + ), + false, + ); + } else if settings.get_bool_with_opts("network.enableDebuginfod", &mut settings_query_opts) { + return (download_debug_info(build_id, view).ok(), true); + } + (None, false) +} + +pub(crate) fn find_sibling_debug_file(view: &BinaryView) -> Option<String> { + let mut settings_query_opts = QueryOptions::new_with_view(view); + let settings = Settings::new(); + let load_sibling_debug = settings.get_bool_with_opts( + "analysis.debugInfo.loadSiblingDebugFiles", + &mut settings_query_opts, + ); + + if !load_sibling_debug { + return None; + } + + let full_file_path = view.file().filename().to_string(); + + let debug_file = PathBuf::from(format!("{}.debug", full_file_path)); + let dsym_folder = PathBuf::from(format!("{}.dSYM", full_file_path)); + if debug_file.exists() && debug_file.is_file() { + return Some(debug_file.to_string_lossy().to_string()); + } + + if dsym_folder.exists() && dsym_folder.is_dir() { + let filename = Path::new(&full_file_path) + .file_name() + .unwrap_or(OsStr::new("")); + + let dsym_file = dsym_folder.join("Contents/Resources/DWARF/").join(filename); // TODO: should this just pull any file out? Can there be multiple files? + if dsym_file.exists() { + return Some(dsym_file.to_string_lossy().to_string()); + } + } + + None +} + +pub(crate) fn load_sibling_debug_file(view: &BinaryView) -> (Option<Ref<BinaryView>>, bool) { + let Some(debug_file) = find_sibling_debug_file(view) else { + return (None, false); + }; + + let load_settings = match view.default_platform() { + Some(plat) => format!( + "{{\"analysis.debugInfo.internal\": false, \"loader.platform\": \"{}\"}}", + plat.name() + ), + None => "{\"analysis.debugInfo.internal\": false}".to_string(), + }; + + ( + binaryninja::load_with_options(debug_file, false, Some(load_settings)), + false, + ) +} diff --git a/plugins/dwarf/dwarf_import/src/lib.rs b/plugins/dwarf/dwarf_import/src/lib.rs new file mode 100644 index 00000000..f850d3ad --- /dev/null +++ b/plugins/dwarf/dwarf_import/src/lib.rs @@ -0,0 +1,757 @@ +// Copyright 2021-2024 Vector 35 Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod die_handlers; +mod dwarfdebuginfo; +mod functions; +mod helpers; +mod types; + +use std::collections::HashMap; + +use crate::dwarfdebuginfo::{DebugInfoBuilder, DebugInfoBuilderContext}; +use crate::functions::parse_function_entry; +use crate::helpers::{get_attr_die, get_name, get_uid, DieReference}; +use crate::types::parse_variable; + +use binaryninja::binary_view::BinaryViewBase; +use binaryninja::{ + binary_view::{BinaryView, BinaryViewExt}, + debuginfo::{CustomDebugInfoParser, DebugInfo, DebugInfoParser}, + settings::Settings, + template_simplifier::simplify_str_to_str, +}; +use dwarfreader::{ + create_section_reader, get_endian, is_dwo_dwarf, is_non_dwo_dwarf, is_raw_dwo_dwarf, +}; + +use functions::parse_lexical_block; +use gimli::{ + constants, CfaRule, DebuggingInformationEntry, Dwarf, DwarfFileType, Reader, Section, + SectionId, Unit, UnwindContext, UnwindSection, +}; + +use binaryninja::logger::Logger; +use helpers::{get_build_id, load_debug_info_for_build_id}; +use log::{debug, error, warn}; + +trait ReaderType: Reader<Offset = usize> {} +impl<T: Reader<Offset = usize>> ReaderType for T {} + +pub(crate) fn split_progress<'b, F: Fn(usize, usize) -> Result<(), ()> + 'b>( + original_fn: F, + subpart: usize, + subpart_weights: &[f64], +) -> Box<dyn Fn(usize, usize) -> Result<(), ()> + 'b> { + // Normalize weights + let weight_sum: f64 = subpart_weights.iter().sum(); + if weight_sum < 0.0001 { + return Box::new(|_, _| Ok(())); + } + + // Keep a running count of weights for the start + let mut subpart_starts = vec![]; + let mut start = 0f64; + for w in subpart_weights { + subpart_starts.push(start); + start += *w; + } + + let subpart_start = subpart_starts[subpart] / weight_sum; + let weight = subpart_weights[subpart] / weight_sum; + + Box::new(move |cur: usize, max: usize| { + // Just use a large number for easy divisibility + let steps = 1000000f64; + let subpart_size = steps * weight; + let subpart_progress = ((cur as f64) / (max as f64)) * subpart_size; + + original_fn( + (subpart_start * steps + subpart_progress) as usize, + steps as usize, + ) + }) +} + +fn calculate_total_unit_bytes<R: ReaderType>( + dwarf: &Dwarf<R>, + debug_info_builder_context: &mut DebugInfoBuilderContext<R>, +) { + let mut iter = dwarf.units(); + let mut total_size: usize = 0; + while let Ok(Some(header)) = iter.next() { + total_size += header.length_including_self(); + } + debug_info_builder_context.total_unit_size_bytes = total_size; +} + +fn recover_names<R: ReaderType>( + dwarf: &Dwarf<R>, + debug_info_builder_context: &mut DebugInfoBuilderContext<R>, + progress: &dyn Fn(usize, usize) -> Result<(), ()>, +) -> bool { + let mut res = true; + if let Some(sup_dwarf) = dwarf.sup() { + res = recover_names_internal(sup_dwarf, debug_info_builder_context, progress); + } + + if res { + res = recover_names_internal(dwarf, debug_info_builder_context, progress); + } + res +} + +fn recover_names_internal<R: ReaderType>( + dwarf: &Dwarf<R>, + debug_info_builder_context: &mut DebugInfoBuilderContext<R>, + progress: &dyn Fn(usize, usize) -> Result<(), ()>, +) -> bool { + let mut iter = dwarf.units(); + let mut current_byte_offset: usize = 0; + while let Ok(Some(header)) = iter.next() { + let unit_offset = header.offset().as_debug_info_offset().unwrap().0; + let unit = dwarf.unit(header).unwrap(); + let mut namespace_qualifiers: Vec<(isize, String)> = vec![]; + let mut entries = unit.entries(); + let mut depth = 0; + + // The first entry in the unit is the header for the unit + if let Ok(Some((delta_depth, _))) = entries.next_dfs() { + depth += delta_depth; + debug_info_builder_context.total_die_count += 1; + } + + while let Ok(Some((delta_depth, entry))) = entries.next_dfs() { + debug_info_builder_context.total_die_count += 1; + + if (*progress)( + current_byte_offset, + debug_info_builder_context.total_unit_size_bytes, + ) + .is_err() + { + return false; // Parsing canceled + }; + current_byte_offset = unit_offset + entry.offset().0; + + depth += delta_depth; + if depth < 0 { + error!("DWARF information is seriously malformed. Aborting parsing."); + return false; + } + + // TODO : Better module/component support + namespace_qualifiers.retain(|&(entry_depth, _)| entry_depth < depth); + + match entry.tag() { + constants::DW_TAG_namespace => { + fn resolve_namespace_name<R: ReaderType>( + dwarf: &Dwarf<R>, + unit: &Unit<R>, + entry: &DebuggingInformationEntry<R>, + debug_info_builder_context: &DebugInfoBuilderContext<R>, + namespace_qualifiers: &mut Vec<(isize, String)>, + depth: isize, + ) { + if let Some(namespace_qualifier) = + get_name(dwarf, unit, entry, debug_info_builder_context) + { + namespace_qualifiers.push((depth, namespace_qualifier)); + } else if let Some(die_reference) = get_attr_die( + dwarf, + unit, + entry, + debug_info_builder_context, + constants::DW_AT_extension, + ) { + match die_reference { + DieReference::UnitAndOffset((dwarf, entry_unit, entry_offset)) => { + resolve_namespace_name( + dwarf, + entry_unit, + &entry_unit.entry(entry_offset).unwrap(), + debug_info_builder_context, + namespace_qualifiers, + depth, + ) + } + DieReference::Err => { + warn!( + "Failed to fetch DIE when resolving namespace. Debug information may be incomplete." + ); + } + } + } else { + namespace_qualifiers.push((depth, "anonymous_namespace".to_string())); + } + } + + resolve_namespace_name( + dwarf, + &unit, + entry, + debug_info_builder_context, + &mut namespace_qualifiers, + depth, + ); + } + constants::DW_TAG_class_type + | constants::DW_TAG_structure_type + | constants::DW_TAG_union_type => { + if let Some(name) = get_name(dwarf, &unit, entry, debug_info_builder_context) { + namespace_qualifiers.push((depth, name)) + } else { + namespace_qualifiers.push(( + depth, + match entry.tag() { + constants::DW_TAG_class_type => "anonymous_class".to_string(), + constants::DW_TAG_structure_type => { + "anonymous_structure".to_string() + } + constants::DW_TAG_union_type => "anonymous_union".to_string(), + _ => unreachable!(), + }, + )) + } + debug_info_builder_context.set_name( + get_uid(dwarf, &unit, entry), + simplify_str_to_str( + namespace_qualifiers + .iter() + .map(|(_, namespace)| namespace.to_owned()) + .collect::<Vec<String>>() + .join("::"), + ) + .to_string(), + ); + } + constants::DW_TAG_typedef + | constants::DW_TAG_subprogram + | constants::DW_TAG_enumeration_type => { + if let Some(name) = get_name(dwarf, &unit, entry, debug_info_builder_context) { + debug_info_builder_context.set_name( + get_uid(dwarf, &unit, entry), + simplify_str_to_str( + namespace_qualifiers + .iter() + .chain(vec![&(-1, name)].into_iter()) + .map(|(_, namespace)| namespace.to_owned()) + .collect::<Vec<String>>() + .join("::"), + ) + .to_string(), + ); + } + } + _ => { + if let Some(name) = get_name(dwarf, &unit, entry, debug_info_builder_context) { + debug_info_builder_context.set_name(get_uid(dwarf, &unit, entry), name); + } + } + } + } + } + + true +} + +fn parse_unit<R: ReaderType>( + dwarf: &Dwarf<R>, + unit: &Unit<R>, + debug_info_builder_context: &DebugInfoBuilderContext<R>, + debug_info_builder: &mut DebugInfoBuilder, + progress: &dyn Fn(usize, usize) -> Result<(), ()>, + current_die_number: &mut usize, +) { + let mut entries = unit.entries(); + + let mut current_depth: isize = 0; + let mut functions_by_depth: Vec<(Option<usize>, isize)> = vec![]; + let mut lexical_blocks_by_depth: Vec<(iset::IntervalSet<u64>, isize)> = vec![]; + + // Really all we care about as we iterate the entries in a given unit is how they modify state (our perception of the file) + // There's a lot of junk we don't care about in DWARF info, so we choose a couple DIEs and mutate state (add functions (which adds the types it uses) and keep track of what namespace we're in) + while let Ok(Some((depth_delta, entry))) = entries.next_dfs() { + *current_die_number += 1; + if (*progress)( + *current_die_number, + debug_info_builder_context.total_die_count, + ) + .is_err() + { + return; // Parsing canceled + } + + current_depth = current_depth.saturating_add(depth_delta); + + loop { + if let Some((_fn_idx, depth)) = functions_by_depth.last() { + if current_depth <= *depth { + functions_by_depth.pop(); + } else { + break; + } + } else { + break; + } + + if let Some((_lexical_block, depth)) = lexical_blocks_by_depth.last() { + if current_depth <= *depth { + lexical_blocks_by_depth.pop(); + } else { + break; + } + } else { + break; + } + } + + match entry.tag() { + constants::DW_TAG_subprogram => { + let fn_idx = parse_function_entry( + dwarf, + unit, + entry, + debug_info_builder_context, + debug_info_builder, + ); + functions_by_depth.push((fn_idx, current_depth)); + } + constants::DW_TAG_lexical_block => { + if let Some(block_ranges) = parse_lexical_block(dwarf, unit, entry) { + lexical_blocks_by_depth.push((block_ranges, current_depth)); + } + } + constants::DW_TAG_variable => { + let current_fn_idx = functions_by_depth.last().and_then(|x| x.0); + let current_lexical_block = lexical_blocks_by_depth.last().and_then(|x| Some(&x.0)); + parse_variable( + dwarf, + unit, + entry, + debug_info_builder_context, + debug_info_builder, + current_fn_idx, + current_lexical_block, + ) + } + constants::DW_TAG_class_type + | constants::DW_TAG_enumeration_type + | constants::DW_TAG_structure_type + | constants::DW_TAG_union_type + | constants::DW_TAG_typedef => { + // Ensure types are loaded even if they're unused + types::get_type( + dwarf, + unit, + entry, + debug_info_builder_context, + debug_info_builder, + ); + } + _ => (), + } + } +} + +fn parse_unwind_section<R: Reader, U: UnwindSection<R>>( + view: &BinaryView, + unwind_section: U, +) -> gimli::Result<iset::IntervalMap<u64, i64>> +where + <U as UnwindSection<R>>::Offset: std::hash::Hash, +{ + let mut bases = gimli::BaseAddresses::default(); + + if let Some(section) = view + .section_by_name(".eh_frame_hdr") + .or(view.section_by_name("__eh_frame_hdr")) + { + bases = bases.set_eh_frame_hdr(section.start()); + } + + if let Some(section) = view + .section_by_name(".eh_frame") + .or(view.section_by_name("__eh_frame")) + { + bases = bases.set_eh_frame(section.start()); + } else if let Some(section) = view + .section_by_name(".debug_frame") + .or(view.section_by_name("__debug_frame")) + { + bases = bases.set_eh_frame(section.start()); + } + + if let Some(section) = view + .section_by_name(".text") + .or(view.section_by_name("__text")) + { + bases = bases.set_text(section.start()); + } + + if let Some(section) = view + .section_by_name(".got") + .or(view.section_by_name("__got")) + { + bases = bases.set_got(section.start()); + } + + let mut cies = HashMap::new(); + let mut cfa_offsets = iset::IntervalMap::new(); + + let mut entries = unwind_section.entries(&bases); + let mut unwind_context = UnwindContext::new(); + loop { + match entries.next()? { + None => return Ok(cfa_offsets), + Some(gimli::CieOrFde::Cie(_cie)) => { + // TODO: do we want to do anything with standalone CIEs? + } + Some(gimli::CieOrFde::Fde(partial)) => { + let fde = match partial.parse(|_, bases, o| { + cies.entry(o) + .or_insert_with(|| unwind_section.cie_from_offset(bases, o)) + .clone() + }) { + Ok(fde) => fde, + Err(e) => { + error!("Failed to parse FDE: {}", e); + continue; + } + }; + + if fde.len() == 0 { + // This FDE is a terminator + return Ok(cfa_offsets); + } + + if fde.initial_address().overflowing_add(fde.len()).1 { + warn!( + "FDE at offset {:?} exceeds bounds of memory space! {:#x} + length {:#x}", + fde.offset(), + fde.initial_address(), + fde.len() + ); + } else { + // Walk the FDE table rows and store their CFA + let mut fde_table = fde.rows(&unwind_section, &bases, &mut unwind_context)?; + + while let Some(row) = fde_table.next_row()? { + match row.cfa() { + CfaRule::RegisterAndOffset { + register: _, + offset, + } => { + // TODO: we should store offsets by register + if row.start_address() < row.end_address() { + cfa_offsets + .insert(row.start_address()..row.end_address(), *offset); + } else { + debug!( + "Invalid FDE table row addresses: {:#x}..{:#x}", + row.start_address(), + row.end_address() + ); + } + } + CfaRule::Expression(_) => { + debug!("Unhandled CFA expression when determining offset"); + } + }; + } + } + } + } + } +} + +fn get_supplementary_build_id(bv: &BinaryView) -> Option<String> { + let raw_view = bv.raw_view()?; + if let Some(section) = raw_view.section_by_name(".gnu_debugaltlink") { + let start = section.start(); + let len = section.len(); + + if len < 20 { + // Not large enough to hold a build id + return None; + } + + raw_view + .read_vec(start, len) + .splitn(2, |x| *x == 0) + .last() + .map(|a| a.iter().map(|b| format!("{:02x}", b)).collect()) + } else { + None + } +} + +fn parse_dwarf( + _bv: &BinaryView, + debug_bv: &BinaryView, + supplementary_bv: Option<&BinaryView>, + progress: Box<dyn Fn(usize, usize) -> Result<(), ()>>, +) -> Result<DebugInfoBuilder, ()> { + // TODO: warn if no supplementary file and .gnu_debugaltlink section present + + // Determine if this is a DWO + // TODO : Make this more robust...some DWOs follow non-DWO conventions + + // Figure out if it's the given view or the raw view that has the dwarf info in it + let raw_view = &debug_bv.raw_view().ok_or(())?; + let view = if is_dwo_dwarf(debug_bv) || is_non_dwo_dwarf(debug_bv) { + debug_bv + } else { + raw_view + }; + + let dwo_file = is_dwo_dwarf(view) || is_raw_dwo_dwarf(view); + + // gimli setup + let endian = get_endian(view); + let mut section_reader = + |section_id: SectionId| -> _ { create_section_reader(section_id, view, endian, dwo_file) }; + + let mut dwarf = match Dwarf::load(&mut section_reader) { + Ok(x) => x, + Err(e) => { + error!("Failed to load DWARF info: {}", e); + return Err(()); + } + }; + + if dwo_file { + dwarf.file_type = DwarfFileType::Dwo; + } else { + dwarf.file_type = DwarfFileType::Main; + } + + if let Some(sup_bv) = supplementary_bv { + let sup_endian = get_endian(sup_bv); + let sup_dwo_file = is_dwo_dwarf(sup_bv) || is_raw_dwo_dwarf(sup_bv); + let sup_section_reader = |section_id: SectionId| -> _ { + create_section_reader(section_id, sup_bv, sup_endian, sup_dwo_file) + }; + if let Err(e) = dwarf.load_sup(sup_section_reader) { + error!("Failed to load supplementary file: {}", e); + } + } + + let range_data_offsets; + if view.section_by_name(".eh_frame").is_some() || view.section_by_name("__eh_frame").is_some() { + let eh_frame_endian = get_endian(view); + let eh_frame_section_reader = |section_id: SectionId| -> _ { + create_section_reader(section_id, view, eh_frame_endian, dwo_file) + }; + let mut eh_frame = gimli::EhFrame::load(eh_frame_section_reader).unwrap(); + eh_frame.set_address_size(view.address_size() as u8); + range_data_offsets = parse_unwind_section(view, eh_frame) + .map_err(|e| error!("Error parsing .eh_frame: {}", e))?; + } else if view.section_by_name(".debug_frame").is_some() + || view.section_by_name("__debug_frame").is_some() + { + let debug_frame_endian = get_endian(view); + let debug_frame_section_reader = |section_id: SectionId| -> _ { + create_section_reader(section_id, view, debug_frame_endian, dwo_file) + }; + let mut debug_frame = gimli::DebugFrame::load(debug_frame_section_reader).unwrap(); + debug_frame.set_address_size(view.address_size() as u8); + range_data_offsets = parse_unwind_section(view, debug_frame) + .map_err(|e| error!("Error parsing .debug_frame: {}", e))?; + } else { + range_data_offsets = Default::default(); + } + + // Create debug info builder and recover name mapping first + // Since DWARF is stored as a tree with arbitrary implicit edges among leaves, + // it is not possible to correctly track namespaces while you're parsing "in order" without backtracking, + // so we just do it up front + let mut debug_info_builder = DebugInfoBuilder::new(); + debug_info_builder.set_range_data_offsets(range_data_offsets); + + if let Some(mut debug_info_builder_context) = DebugInfoBuilderContext::new(view, &dwarf) { + calculate_total_unit_bytes(&dwarf, &mut debug_info_builder_context); + + let progress_weights = [0.5, 0.5]; + let name_progress = split_progress(&progress, 0, &progress_weights); + let parse_progress = split_progress(&progress, 1, &progress_weights); + + if !recover_names(&dwarf, &mut debug_info_builder_context, &name_progress) + || debug_info_builder_context.total_die_count == 0 + { + return Ok(debug_info_builder); + } + + // Parse all the compilation units + let mut current_die_number = 0; + + for unit in debug_info_builder_context.sup_units() { + parse_unit( + dwarf.sup().unwrap(), + unit, + &debug_info_builder_context, + &mut debug_info_builder, + &parse_progress, + &mut current_die_number, + ); + } + + for unit in debug_info_builder_context.units() { + parse_unit( + &dwarf, + unit, + &debug_info_builder_context, + &mut debug_info_builder, + &parse_progress, + &mut current_die_number, + ); + } + } + + Ok(debug_info_builder) +} + +struct DWARFParser; + +impl CustomDebugInfoParser for DWARFParser { + fn is_valid(&self, view: &BinaryView) -> bool { + if dwarfreader::is_valid(view) || dwarfreader::can_use_debuginfod(view) { + return true; + } + if dwarfreader::has_build_id_section(view) { + if let Ok(build_id) = get_build_id(view) { + if helpers::find_local_debug_file_for_build_id(&build_id, view).is_some() { + return true; + } + } + } + if helpers::find_sibling_debug_file(view).is_some() { + return true; + } + false + } + + fn parse_info( + &self, + debug_info: &mut DebugInfo, + bv: &BinaryView, + debug_file: &BinaryView, + progress: Box<dyn Fn(usize, usize) -> Result<(), ()>>, + ) -> bool { + let (external_file, close_external) = if !dwarfreader::is_valid(bv) { + if let (Some(debug_view), x) = helpers::load_sibling_debug_file(bv) { + (Some(debug_view), x) + } else if let Ok(build_id) = get_build_id(bv) { + load_debug_info_for_build_id(&build_id, bv) + } else { + (None, false) + } + } else { + (None, false) + }; + + let sup_bv = get_supplementary_build_id(external_file.as_deref().unwrap_or(debug_file)) + .and_then(|build_id| { + load_debug_info_for_build_id(&build_id, bv) + .0 + .map(|x| x.raw_view().unwrap()) + }); + + let result = match parse_dwarf( + bv, + external_file.as_deref().unwrap_or(debug_file), + sup_bv.as_deref(), + progress, + ) { + Ok(mut builder) => { + builder.post_process(bv, debug_info).commit_info(debug_info); + true + } + Err(_) => false, + }; + + if let (Some(ext), true) = (external_file, close_external) { + ext.file().close(); + } + + result + } +} + +#[no_mangle] +pub extern "C" fn CorePluginInit() -> bool { + Logger::new("DWARF").init(); + + let settings = Settings::new(); + + settings.register_setting_json( + "network.enableDebuginfod", + r#"{ + "title" : "Enable Debuginfod Support", + "type" : "boolean", + "default" : false, + "description" : "Enable using Debuginfod servers to fetch DWARF debug info for files with a .note.gnu.build-id section.", + "ignore" : [] + }"#, + ); + + settings.register_setting_json( + "network.debuginfodServers", + r#"{ + "title" : "Debuginfod Server URLs", + "type" : "array", + "sorted" : true, + "default" : [], + "description" : "Servers to use for fetching DWARF debug info for files with a .note.gnu.build-id section.", + "ignore" : [] + }"#, + ); + + settings.register_setting_json( + "analysis.debugInfo.enableDebugDirectories", + r#"{ + "title" : "Enable Debug File Directories", + "type" : "boolean", + "default" : true, + "description" : "Enable searching local debug directories for DWARF debug info.", + "ignore" : [] + }"#, + ); + + settings.register_setting_json( + "analysis.debugInfo.debugDirectories", + r#"{ + "title" : "Debug File Directories", + "type" : "array", + "sorted" : true, + "default" : [], + "description" : "Paths to folder containing DWARF debug info stored by build id.", + "ignore" : [] + }"#, + ); + + settings.register_setting_json( + "analysis.debugInfo.loadSiblingDebugFiles", + r#"{ + "title" : "Enable Loading of Sibling Debug Files", + "type" : "boolean", + "default" : true, + "description" : "Enable automatic loading of X.debug and X.dSYM files next to a file named X.", + "ignore" : [] + }"#, + ); + + DebugInfoParser::register("DWARF", DWARFParser {}); + true +} diff --git a/plugins/dwarf/dwarf_import/src/types.rs b/plugins/dwarf/dwarf_import/src/types.rs new file mode 100644 index 00000000..f5d5a444 --- /dev/null +++ b/plugins/dwarf/dwarf_import/src/types.rs @@ -0,0 +1,470 @@ +// Copyright 2021-2024 Vector 35 Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::dwarfdebuginfo::{DebugInfoBuilder, DebugInfoBuilderContext, TypeUID}; +use crate::helpers::*; +use crate::{die_handlers::*, ReaderType}; + +use binaryninja::{ + rc::*, + types::{ + MemberAccess, MemberScope, ReferenceType, StructureBuilder, StructureType, Type, TypeClass, + }, +}; + +use gimli::{constants, AttributeValue, DebuggingInformationEntry, Dwarf, Operation, Unit}; + +use log::{debug, error, warn}; + +pub(crate) fn parse_variable<R: ReaderType>( + dwarf: &Dwarf<R>, + unit: &Unit<R>, + entry: &DebuggingInformationEntry<R>, + debug_info_builder_context: &DebugInfoBuilderContext<R>, + debug_info_builder: &mut DebugInfoBuilder, + function_index: Option<usize>, + lexical_block: Option<&iset::IntervalSet<u64>>, +) { + let full_name = debug_info_builder_context.get_name(dwarf, unit, entry); + let type_uid = get_type( + dwarf, + unit, + entry, + debug_info_builder_context, + debug_info_builder, + ); + + let Ok(Some(attr)) = entry.attr(constants::DW_AT_location) else { + return; + }; + + let AttributeValue::Exprloc(mut expression) = attr.value() else { + return; + }; + + match Operation::parse(&mut expression.0, unit.encoding()) { + Ok(Operation::FrameOffset { offset }) => { + debug_info_builder.add_stack_variable( + function_index, + offset, + full_name, + type_uid, + lexical_block, + ); + } + //Ok(Operation::RegisterOffset { register: _, offset: _, base_type: _ }) => { + // //TODO: look up register by index (binja register indexes don't match processor indexes?) + // //TODO: calculate absolute stack offset + // //TODO: add by absolute offset + //}, + Ok(Operation::Address { address }) => { + if let Some(uid) = type_uid { + debug_info_builder.add_data_variable(address, full_name, uid) + } + } + Ok(Operation::AddressIndex { index }) => { + if let Some(uid) = type_uid { + if let Ok(address) = dwarf.address(unit, index) { + debug_info_builder.add_data_variable(address, full_name, uid) + } else { + warn!("Invalid index into IAT: {}", index.0); + } + } + } + Ok(op) => { + debug!("Unhandled operation type for variable: {:?}", op); + } + Err(e) => error!( + "Error parsing operation type for variable {:?}: {}", + full_name, e + ), + } +} + +fn do_structure_parse<R: ReaderType>( + dwarf: &Dwarf<R>, + structure_type: StructureType, + unit: &Unit<R>, + entry: &DebuggingInformationEntry<R>, + debug_info_builder_context: &DebugInfoBuilderContext<R>, + debug_info_builder: &mut DebugInfoBuilder, +) -> Option<usize> { + // All struct, union, and class types will have: + // *DW_AT_name + // *DW_AT_byte_size or *DW_AT_bit_size + // *DW_AT_declaration + // *DW_AT_signature + // *DW_AT_specification + // ?DW_AT_abstract_origin + // ?DW_AT_accessibility + // ?DW_AT_allocated + // ?DW_AT_associated + // ?DW_AT_data_location + // ?DW_AT_description + // ?DW_AT_start_scope + // ?DW_AT_visibility + // * = Optional + + // Structure/Class/Union _Children_ consist of: + // Data members: + // DW_AT_type + // *DW_AT_name + // *DW_AT_accessibility (default private for classes, public for everything else) + // *DW_AT_mutable + // *DW_AT_data_member_location xor *DW_AT_data_bit_offset (otherwise assume zero) <- there are some deprecations for DWARF 4 + // *DW_AT_byte_size xor DW_AT_bit_size, iff the storage size is different than it usually would be for the given member type + // Function members: + // *DW_AT_accessibility (default private for classes, public for everything else) + // *DW_AT_virtuality (assume false) + // If true: DW_AT_vtable_elem_location + // *DW_AT_explicit (assume false) + // *DW_AT_object_pointer (assume false; for non-static member function; references the formal parameter that has "DW_AT_artificial = true" and represents "self" or "this" (language specified)) + // *DW_AT_specification + // * = Optional + + if let Ok(Some(_)) = entry.attr(constants::DW_AT_declaration) { + return None; + } + + let full_name = if get_name(dwarf, unit, entry, debug_info_builder_context).is_some() { + debug_info_builder_context.get_name(dwarf, unit, entry) + } else { + None + }; + + // Create structure with proper size + let size = get_size_as_u64(entry).unwrap_or(0); + let mut structure_builder = StructureBuilder::new(); + structure_builder + .packed(true) + .width(size) + .structure_type(structure_type); + + // This reference type will be used by any children to grab while we're still building this type + // it will also be how any other types refer to this struct + if let Some(full_name) = &full_name { + let ntr = + Type::named_type_from_type(full_name, &Type::structure(&structure_builder.finalize())); + debug_info_builder.add_type( + get_uid(dwarf, unit, entry), + full_name.to_owned(), + ntr, + false, + ); + } else { + // We _need_ to have initial typedefs or else we can enter infinite parsing loops + // These get overwritten in the last step with the actual type, however, so this + // is either perfectly fine or breaking a bunch of NTRs + let full_name = format!("anonymous_structure_{:x}", get_uid(dwarf, unit, entry)); + let ntr = + Type::named_type_from_type(&full_name, &Type::structure(&structure_builder.finalize())); + debug_info_builder.add_type(get_uid(dwarf, unit, entry), full_name, ntr, false); + } + + // Get all the children and populate + let mut tree = unit.entries_tree(Some(entry.offset())).unwrap(); + let mut children = tree.root().unwrap().children(); + while let Ok(Some(child)) = children.next() { + if child.entry().tag() == constants::DW_TAG_member { + if let Some(child_type_id) = get_type( + dwarf, + unit, + child.entry(), + debug_info_builder_context, + debug_info_builder, + ) { + if let Some(t) = debug_info_builder.get_type(child_type_id) { + let child_type = t.get_type(); + if let Some(child_name) = debug_info_builder_context + .get_name(dwarf, unit, child.entry()) + .map_or( + if child_type.type_class() == TypeClass::StructureTypeClass { + Some("".to_string()) + } else { + None + }, + Some, + ) + { + // TODO : support DW_AT_data_bit_offset for offset as well + if let Ok(Some(raw_struct_offset)) = + child.entry().attr(constants::DW_AT_data_member_location) + { + // TODO : Let this fail; don't unwrap_or_default get_expr_value + let struct_offset = + get_attr_as_u64(&raw_struct_offset).unwrap_or_else(|| { + get_expr_value(unit, raw_struct_offset).unwrap_or_default() + }); + + structure_builder.insert( + child_type.as_ref(), + child_name, + struct_offset, + false, + MemberAccess::NoAccess, // TODO : Resolve actual scopes, if possible + MemberScope::NoScope, + ); + } else { + structure_builder.append( + child_type.as_ref(), + child_name, + MemberAccess::NoAccess, + MemberScope::NoScope, + ); + } + } + } + } + } + } + + let finalized_structure = Type::structure(&structure_builder.finalize()); + if let Some(full_name) = full_name { + debug_info_builder.add_type( + get_uid(dwarf, unit, entry) + 1, // TODO : This is super broke (uid + 1 is not guaranteed to be unique) + full_name, + finalized_structure, + true, + ); + } else { + debug_info_builder.add_type( + get_uid(dwarf, unit, entry), + finalized_structure.to_string(), + finalized_structure, + false, // Don't commit anonymous unions (because I think it'll break things) + ); + } + Some(get_uid(dwarf, unit, entry)) +} + +// This function iterates up through the dependency references, adding all the types along the way until there are no more or stopping at the first one already tracked, then returns the UID of the type of the given DIE +pub(crate) fn get_type<R: ReaderType>( + dwarf: &Dwarf<R>, + unit: &Unit<R>, + entry: &DebuggingInformationEntry<R>, + debug_info_builder_context: &DebugInfoBuilderContext<R>, + debug_info_builder: &mut DebugInfoBuilder, +) -> Option<TypeUID> { + // If this node (and thus all its referenced nodes) has already been processed, just return the offset + let entry_uid = get_uid(dwarf, unit, entry); + if debug_info_builder.contains_type(entry_uid) { + return Some(entry_uid); + } + + // Don't parse types that are just declarations and not definitions + if let Ok(Some(_)) = entry.attr(constants::DW_AT_declaration) { + return None; + } + + let entry_type = if let Some(die_reference) = get_attr_die( + dwarf, + unit, + entry, + debug_info_builder_context, + constants::DW_AT_type, + ) { + // This needs to recurse first (before the early return below) to ensure all sub-types have been parsed + match die_reference { + DieReference::UnitAndOffset((dwarf, entry_unit, entry_offset)) => get_type( + dwarf, + entry_unit, + &entry_unit.entry(entry_offset).unwrap(), + debug_info_builder_context, + debug_info_builder, + ), + DieReference::Err => { + warn!("Failed to fetch DIE when getting type through DW_AT_type. Debug information may be incomplete."); + None + } + } + } else if let Some(die_reference) = get_attr_die( + dwarf, + unit, + entry, + debug_info_builder_context, + constants::DW_AT_abstract_origin, + ) { + // This needs to recurse first (before the early return below) to ensure all sub-types have been parsed + match die_reference { + DieReference::UnitAndOffset((dwarf, entry_unit, entry_offset)) => get_type( + dwarf, + entry_unit, + &entry_unit.entry(entry_offset).unwrap(), + debug_info_builder_context, + debug_info_builder, + ), + DieReference::Err => { + warn!("Failed to fetch DIE when getting type through DW_AT_abstract_origin. Debug information may be incomplete."); + None + } + } + } else { + // This needs to recurse first (before the early return below) to ensure all sub-types have been parsed + match resolve_specification(dwarf, unit, entry, debug_info_builder_context) { + DieReference::UnitAndOffset((dwarf, entry_unit, entry_offset)) + if entry_unit.header.offset() != unit.header.offset() + && entry_offset != entry.offset() => + { + get_type( + dwarf, + entry_unit, + &entry_unit.entry(entry_offset).unwrap(), + debug_info_builder_context, + debug_info_builder, + ) + } + DieReference::UnitAndOffset(_) => None, + DieReference::Err => { + warn!( + "Failed to fetch DIE when getting type. Debug information may be incomplete." + ); + None + } + } + }; + + // If this node (and thus all its referenced nodes) has already been processed, just return the offset + // This check is not redundant because this type might have been processes in the recursive calls above + if debug_info_builder.contains_type(entry_uid) { + return Some(entry_uid); + } + + // Collect the required information to create a type and add it to the type map. Also, add the dependencies of this type to the type's typeinfo + // Create the type, make a TypeInfo for it, and add it to the debug info + let (type_def, mut commit): (Option<Ref<Type>>, bool) = match entry.tag() { + constants::DW_TAG_base_type => ( + handle_base_type(dwarf, unit, entry, debug_info_builder_context), + false, + ), + + constants::DW_TAG_structure_type => { + return do_structure_parse( + dwarf, + StructureType::StructStructureType, + unit, + entry, + debug_info_builder_context, + debug_info_builder, + ) + } + constants::DW_TAG_class_type => { + return do_structure_parse( + dwarf, + StructureType::ClassStructureType, + unit, + entry, + debug_info_builder_context, + debug_info_builder, + ) + } + constants::DW_TAG_union_type => { + return do_structure_parse( + dwarf, + StructureType::UnionStructureType, + unit, + entry, + debug_info_builder_context, + debug_info_builder, + ) + } + + // Enum + constants::DW_TAG_enumeration_type => ( + handle_enum(dwarf, unit, entry, debug_info_builder_context), + true, + ), + + // Basic types + constants::DW_TAG_typedef => { + if let Some(name) = debug_info_builder_context.get_name(dwarf, unit, entry) { + handle_typedef(debug_info_builder, entry_type, &name) + } else { + (None, false) + } + } + constants::DW_TAG_pointer_type => ( + handle_pointer( + entry, + debug_info_builder_context, + debug_info_builder, + entry_type, + ReferenceType::PointerReferenceType, + ), + false, + ), + constants::DW_TAG_reference_type => ( + handle_pointer( + entry, + debug_info_builder_context, + debug_info_builder, + entry_type, + ReferenceType::ReferenceReferenceType, + ), + false, + ), + constants::DW_TAG_rvalue_reference_type => ( + handle_pointer( + entry, + debug_info_builder_context, + debug_info_builder, + entry_type, + ReferenceType::RValueReferenceType, + ), + false, + ), + constants::DW_TAG_array_type => ( + handle_array(unit, entry, debug_info_builder, entry_type), + false, + ), + + // Strange Types + constants::DW_TAG_unspecified_type => (Some(Type::void()), false), + constants::DW_TAG_subroutine_type => ( + handle_function( + dwarf, + unit, + entry, + debug_info_builder_context, + debug_info_builder, + entry_type, + ), + false, + ), + + // Weird types + constants::DW_TAG_const_type => (handle_const(debug_info_builder, entry_type), false), + constants::DW_TAG_volatile_type => (handle_volatile(debug_info_builder, entry_type), true), // TODO : Maybe false here + + // Pass-through everything else! + _ => return entry_type, + }; + + // Wrap our resultant type in a TypeInfo so that the internal DebugInfo class can manage it + if let Some(type_def) = type_def { + let name = if get_name(dwarf, unit, entry, debug_info_builder_context).is_some() { + debug_info_builder_context.get_name(dwarf, unit, entry) + } else { + None + } + .unwrap_or_else(|| { + commit = false; + type_def.to_string() + }); + + debug_info_builder.add_type(entry_uid, name, type_def, commit); + Some(entry_uid) + } else { + None + } +} diff --git a/plugins/dwarf/dwarfdump/Cargo.toml b/plugins/dwarf/dwarfdump/Cargo.toml new file mode 100644 index 00000000..a58e2e33 --- /dev/null +++ b/plugins/dwarf/dwarfdump/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "dwarfdump" +version = "0.1.0" +authors = ["Kyle Martin <kyle@vector35.com>"] +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +dwarfreader = { path = "../shared/" } +binaryninja.workspace = true +binaryninjacore-sys.workspace = true +gimli = "0.31" diff --git a/plugins/dwarf/dwarfdump/README.md b/plugins/dwarf/dwarfdump/README.md new file mode 100644 index 00000000..ae3a193b --- /dev/null +++ b/plugins/dwarf/dwarfdump/README.md @@ -0,0 +1,17 @@ +# DWARF Dump Example + +This is actually a fully-developed plugin, rather than a measly example. + +Two features this does not support are: files in big endian, and .dwo files + +## How to use + +Simply `cargo build --release` in this directory, and copy the `.so` from the target directory to your plugin directory + +### Attribution + +This example makes use of: + - [gimli] ([gimli license] - MIT) + +[gimli license]: https://github.com/gimli-rs/gimli/blob/master/LICENSE-MIT +[gimli]: https://github.com/gimli-rs/gimli diff --git a/plugins/dwarf/dwarfdump/build.rs b/plugins/dwarf/dwarfdump/build.rs new file mode 100644 index 00000000..ed6cec7d --- /dev/null +++ b/plugins/dwarf/dwarfdump/build.rs @@ -0,0 +1,15 @@ +fn main() { + let link_path = std::env::var_os("DEP_BINARYNINJACORE_PATH") + .expect("DEP_BINARYNINJACORE_PATH not specified"); + + println!("cargo::rustc-link-lib=dylib=binaryninjacore"); + println!("cargo::rustc-link-search={}", link_path.to_str().unwrap()); + + #[cfg(not(target_os = "windows"))] + { + println!( + "cargo::rustc-link-arg=-Wl,-rpath,{0},-L{0}", + link_path.to_string_lossy() + ); + } +} diff --git a/plugins/dwarf/dwarfdump/src/lib.rs b/plugins/dwarf/dwarfdump/src/lib.rs new file mode 100644 index 00000000..dabf88fb --- /dev/null +++ b/plugins/dwarf/dwarfdump/src/lib.rs @@ -0,0 +1,334 @@ +// Copyright 2021-2024 Vector 35 Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use binaryninja::{ + binary_view::{BinaryView, BinaryViewExt}, + command::{register_command, Command}, + disassembly::{DisassemblyTextLine, InstructionTextToken, InstructionTextTokenKind}, + flowgraph::{BranchType, EdgeStyle, FlowGraph, FlowGraphNode, FlowGraphOption}, +}; +use dwarfreader::is_valid; + +use binaryninja::disassembly::StringType; +use gimli::{ + AttributeValue::{Encoding, Flag, UnitRef}, + // BigEndian, + DebuggingInformationEntry, + Dwarf, + EntriesTreeNode, + Reader, + ReaderOffset, + SectionId, + Unit, + UnitSectionOffset, +}; + +static PADDING: [&str; 23] = [ + "", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", +]; + +// TODO : This is very much not comprehensive: see https://github.com/gimli-rs/gimli/blob/master/examples/dwarfdump.rs +fn get_info_string<R: Reader>( + _view: &BinaryView, + dwarf: &Dwarf<R>, + unit: &Unit<R>, + die_node: &DebuggingInformationEntry<R>, +) -> Vec<DisassemblyTextLine> { + let mut disassembly_lines: Vec<DisassemblyTextLine> = Vec::with_capacity(10); // This is an estimate so "most" things won't need to resize + + let label_value = match die_node.offset().to_unit_section_offset(unit) { + UnitSectionOffset::DebugInfoOffset(o) => o.0, + UnitSectionOffset::DebugTypesOffset(o) => o.0, + } + .into_u64(); + let label_string = format!("#0x{:08x}", label_value); + disassembly_lines.push(DisassemblyTextLine::new(vec![ + InstructionTextToken::new( + &label_string, + InstructionTextTokenKind::GotoLabel { + target: label_value, + }, + ), + InstructionTextToken::new(":", InstructionTextTokenKind::Text), + ])); + + disassembly_lines.push(DisassemblyTextLine::new(vec![InstructionTextToken::new( + die_node.tag().static_string().unwrap(), + InstructionTextTokenKind::TypeName, // TODO : KeywordToken? + )])); + + let mut attrs = die_node.attrs(); + while let Some(attr) = attrs.next().unwrap() { + let mut attr_line: Vec<InstructionTextToken> = Vec::with_capacity(5); + attr_line.push(InstructionTextToken::new( + " ", + InstructionTextTokenKind::Indentation, + )); + + let len; + if let Some(n) = attr.name().static_string() { + len = n.len(); + attr_line.push(InstructionTextToken::new( + n, + // TODO: Using field name for this is weird. + InstructionTextTokenKind::FieldName { + offset: 0, + type_names: vec![], + }, + )); + } else { + // This is rather unlikely, I think + len = 1; + attr_line.push(InstructionTextToken::new( + "?", + // TODO: Using field name for this is weird. + InstructionTextTokenKind::FieldName { + offset: 0, + type_names: vec![], + }, + )); + } + + // On command line the magic number that looks good is 22, but that's too much whitespace in a basic block, so I chose 18 (22 is the max with the current padding provided) + if len < 18 { + attr_line.push(InstructionTextToken::new( + PADDING[18 - len], + InstructionTextTokenKind::Text, + )); + } + attr_line.push(InstructionTextToken::new( + " = ", + InstructionTextTokenKind::Text, + )); + + if let Ok(Some(addr)) = dwarf.attr_address(unit, attr.value()) { + let addr_string = format!("0x{:08x}", addr); + attr_line.push(InstructionTextToken::new( + &addr_string, + InstructionTextTokenKind::Integer { + value: addr, + size: None, + }, + )); + } else if let Ok(attr_reader) = dwarf.attr_string(unit, attr.value()) { + if let Ok(attr_string) = attr_reader.to_string() { + attr_line.push(InstructionTextToken::new( + attr_string.as_ref(), + InstructionTextTokenKind::String { + ty: StringType::Utf8String, + }, + )); + } else { + attr_line.push(InstructionTextToken::new( + "??", + InstructionTextTokenKind::Text, + )); + } + } else if let Encoding(type_class) = attr.value() { + attr_line.push(InstructionTextToken::new( + type_class.static_string().unwrap(), + InstructionTextTokenKind::TypeName, + )); + } else if let UnitRef(offset) = attr.value() { + let addr = match offset.to_unit_section_offset(unit) { + UnitSectionOffset::DebugInfoOffset(o) => o.0, + UnitSectionOffset::DebugTypesOffset(o) => o.0, + } + .into_u64(); + let addr_string = format!("#0x{:08x}", addr); + attr_line.push(InstructionTextToken::new( + &addr_string, + InstructionTextTokenKind::GotoLabel { target: addr }, + )); + } else if let Flag(true) = attr.value() { + attr_line.push(InstructionTextToken::new( + "true", + InstructionTextTokenKind::Integer { + value: 1, + size: None, + }, + )); + } else if let Flag(false) = attr.value() { + attr_line.push(InstructionTextToken::new( + "false", + InstructionTextTokenKind::Integer { + value: 0, + size: None, + }, + )); + + // Fall-back cases + } else if let Some(value) = attr.u8_value() { + let value_string = format!("{}", value); + attr_line.push(InstructionTextToken::new( + &value_string, + InstructionTextTokenKind::Integer { + value: value as u64, + size: None, + }, + )); + } else if let Some(value) = attr.u16_value() { + let value_string = format!("{}", value); + attr_line.push(InstructionTextToken::new( + &value_string, + InstructionTextTokenKind::Integer { + value: value as u64, + size: None, + }, + )); + } else if let Some(value) = attr.udata_value() { + let value_string = format!("{}", value); + attr_line.push(InstructionTextToken::new( + &value_string, + InstructionTextTokenKind::Integer { value, size: None }, + )); + } else if let Some(value) = attr.sdata_value() { + let value_string = format!("{}", value); + attr_line.push(InstructionTextToken::new( + &value_string, + InstructionTextTokenKind::Integer { + value: value as u64, + size: None, + }, + )); + } else { + let attr_string = format!("{:?}", attr.value()); + attr_line.push(InstructionTextToken::new( + &attr_string, + InstructionTextTokenKind::Text, + )); + } + disassembly_lines.push(DisassemblyTextLine::new(attr_line)); + } + + disassembly_lines +} + +fn process_tree<R: Reader>( + view: &BinaryView, + dwarf: &Dwarf<R>, + unit: &Unit<R>, + graph: &FlowGraph, + graph_parent: &FlowGraphNode, + die_node: EntriesTreeNode<R>, +) { + // Namespaces only - really interesting to look at! + // if (die_node.entry().tag() == constants::DW_TAG_namespace) + // || (die_node.entry().tag() == constants::DW_TAG_class_type) + // || (die_node.entry().tag() == constants::DW_TAG_compile_unit) + // || (die_node.entry().tag() == constants::DW_TAG_subprogram) + // { + let new_node = FlowGraphNode::new(graph); + + let attr_string = get_info_string(view, dwarf, unit, die_node.entry()); + new_node.set_lines(attr_string); + + graph.append(&new_node); + graph_parent.add_outgoing_edge( + BranchType::UnconditionalBranch, + &new_node, + EdgeStyle::default(), + ); + + let mut children = die_node.children(); + while let Some(child) = children.next().unwrap() { + process_tree(view, dwarf, unit, graph, &new_node, child); + } + // } +} + +fn dump_dwarf(bv: &BinaryView) { + let view = if bv.section_by_name(".debug_info").is_some() { + bv.to_owned() + } else { + bv.parent_view().unwrap() + }; + + let graph = FlowGraph::new(); + graph.set_option(FlowGraphOption::FlowGraphUsesBlockHighlights, true); + graph.set_option(FlowGraphOption::FlowGraphUsesInstructionHighlights, true); + + let graph_root = FlowGraphNode::new(&graph); + graph_root.set_lines(["Graph Root".into()]); + graph.append(&graph_root); + + let endian = dwarfreader::get_endian(bv); + let section_reader = |section_id: SectionId| -> _ { + dwarfreader::create_section_reader(section_id, bv, endian, false) + }; + let dwarf = Dwarf::load(§ion_reader).unwrap(); + + let mut iter = dwarf.units(); + while let Some(header) = iter.next().unwrap() { + let unit = dwarf.unit(header).unwrap(); + let mut entries = unit.entries(); + let mut depth = 0; + + if let Some((delta_depth, entry)) = entries.next_dfs().unwrap() { + depth += delta_depth; + assert!(depth >= 0); + + let mut tree = unit.entries_tree(Some(entry.offset())).unwrap(); + let root = tree.root().unwrap(); + + process_tree(&view, &dwarf, &unit, &graph, &graph_root, root); + } + } + + view.show_graph_report("DWARF", &graph); +} + +struct DWARFDump; + +impl Command for DWARFDump { + fn action(&self, view: &BinaryView) { + dump_dwarf(view); + } + + fn valid(&self, view: &BinaryView) -> bool { + is_valid(view) + } +} + +#[no_mangle] +pub extern "C" fn UIPluginInit() -> bool { + register_command( + "DWARF Dump", + "Show embedded DWARF info as a tree structure for you to navigate", + DWARFDump {}, + ); + true +} diff --git a/plugins/dwarf/shared/Cargo.toml b/plugins/dwarf/shared/Cargo.toml new file mode 100644 index 00000000..93ed004d --- /dev/null +++ b/plugins/dwarf/shared/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "dwarfreader" +version = "0.1.0" +authors = ["Kyle Martin <kyle@vector35.com>"] +edition = "2021" + +[dependencies] +binaryninja.workspace = true +binaryninjacore-sys.workspace = true +gimli = "0.31" +zstd = "0.13.2" +thiserror = "1.0" diff --git a/plugins/dwarf/shared/build.rs b/plugins/dwarf/shared/build.rs new file mode 100644 index 00000000..ed6cec7d --- /dev/null +++ b/plugins/dwarf/shared/build.rs @@ -0,0 +1,15 @@ +fn main() { + let link_path = std::env::var_os("DEP_BINARYNINJACORE_PATH") + .expect("DEP_BINARYNINJACORE_PATH not specified"); + + println!("cargo::rustc-link-lib=dylib=binaryninjacore"); + println!("cargo::rustc-link-search={}", link_path.to_str().unwrap()); + + #[cfg(not(target_os = "windows"))] + { + println!( + "cargo::rustc-link-arg=-Wl,-rpath,{0},-L{0}", + link_path.to_string_lossy() + ); + } +} diff --git a/plugins/dwarf/shared/src/lib.rs b/plugins/dwarf/shared/src/lib.rs new file mode 100644 index 00000000..f955852b --- /dev/null +++ b/plugins/dwarf/shared/src/lib.rs @@ -0,0 +1,188 @@ +// Copyright 2021-2024 Vector 35 Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use gimli::{EndianRcSlice, Endianity, RunTimeEndian, SectionId}; + +use binaryninja::{ + binary_view::{BinaryView, BinaryViewBase, BinaryViewExt}, + settings::Settings, + Endianness, +}; + +use binaryninja::settings::QueryOptions; +use std::rc::Rc; +////////////////////// +// Dwarf Validation + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("unknown section compression method {0:#x}")] + UnknownCompressionMethod(u32), + + #[error("{0}")] + GimliError(#[from] gimli::Error), + + #[error("{0}")] + IoError(#[from] std::io::Error), +} + +pub fn is_non_dwo_dwarf(view: &BinaryView) -> bool { + view.section_by_name(".debug_info").is_some() || view.section_by_name("__debug_info").is_some() +} + +pub fn is_dwo_dwarf(view: &BinaryView) -> bool { + view.section_by_name(".debug_info.dwo").is_some() +} + +pub fn is_raw_non_dwo_dwarf(view: &BinaryView) -> bool { + if let Some(raw_view) = view.raw_view() { + raw_view.section_by_name(".debug_info").is_some() + || view.section_by_name("__debug_info").is_some() + } else { + false + } +} + +pub fn is_raw_dwo_dwarf(view: &BinaryView) -> bool { + if let Some(raw_view) = view.raw_view() { + raw_view.section_by_name(".debug_info.dwo").is_some() + } else { + false + } +} + +pub fn can_use_debuginfod(view: &BinaryView) -> bool { + let mut query_options = QueryOptions::new_with_view(view); + has_build_id_section(view) + && Settings::new().get_bool_with_opts("network.enableDebuginfod", &mut query_options) +} + +pub fn has_build_id_section(view: &BinaryView) -> bool { + if let Some(raw_view) = view.raw_view() { + return raw_view.section_by_name(".note.gnu.build-id").is_some(); + } + false +} + +pub fn is_valid(view: &BinaryView) -> bool { + is_non_dwo_dwarf(view) + || is_raw_non_dwo_dwarf(view) + || is_dwo_dwarf(view) + || is_raw_dwo_dwarf(view) +} + +pub fn get_endian(view: &BinaryView) -> RunTimeEndian { + match view.default_endianness() { + Endianness::LittleEndian => RunTimeEndian::Little, + Endianness::BigEndian => RunTimeEndian::Big, + } +} + +pub fn create_section_reader<'a, Endian: 'a + Endianity>( + section_id: SectionId, + view: &'a BinaryView, + endian: Endian, + dwo_file: bool, +) -> Result<EndianRcSlice<Endian>, Error> { + let section_name = if dwo_file && section_id.dwo_name().is_some() { + section_id.dwo_name().unwrap() + } else { + section_id.name() + }; + + if let Some(section) = view.section_by_name(section_name) { + // TODO : This is kinda broke....should add rust wrappers for some of this + if let Some(symbol) = view + .symbols() + .iter() + .find(|symbol| symbol.full_name().as_str() == "__elf_section_headers") + { + if let Some(data_var) = view + .data_variables() + .iter() + .find(|var| var.address == symbol.address()) + { + // TODO : This should eventually be wrapped by some DataView sorta thingy thing, like how python does it + let data_type = &data_var.ty.contents; + let data = view.read_vec(data_var.address, data_type.width() as usize); + let element_type = data_type.element_type().unwrap().contents; + + if let Some(current_section_header) = data + .chunks(element_type.width() as usize) + .find(|section_header| { + if view.address_size() == 4 { + endian.read_u32(§ion_header[16..20]) as u64 == section.start() + } else { + endian.read_u64(§ion_header[24..32]) == section.start() + } + }) + { + let section_flags = if view.address_size() == 4 { + endian.read_u32(¤t_section_header[8..12]) as u64 + } else { + endian.read_u64(¤t_section_header[8..16]) + }; + // If the section has the compressed bit set + if (section_flags & 2048) != 0 { + // Get section, trim header, decompress, return + let compressed_header_size = view.address_size() * 3; + + let offset = section.start() + compressed_header_size as u64; + let len = section.len() - compressed_header_size; + + let ch_type_vec = view.read_vec(section.start(), 4); + let ch_type = endian.read_u32(&ch_type_vec); + + if let Ok(buffer) = view.read_buffer(offset, len) { + match ch_type { + 1 => { + return Ok(EndianRcSlice::new( + buffer.zlib_decompress().get_data().into(), + endian, + )); + } + 2 => { + return Ok(EndianRcSlice::new( + zstd::decode_all(buffer.get_data())?.as_slice().into(), + endian, + )); + } + x => { + return Err(Error::UnknownCompressionMethod(x)); + } + } + } + } + } + } + } + let offset = section.start(); + let len = section.len(); + if len == 0 { + Ok(EndianRcSlice::new(Rc::from([]), endian)) + } else { + Ok(EndianRcSlice::new( + Rc::from(view.read_vec(offset, len).as_slice()), + endian, + )) + } + } else if let Some(section) = view.section_by_name("__".to_string() + §ion_name[1..]) { + Ok(EndianRcSlice::new( + Rc::from(view.read_vec(section.start(), section.len()).as_slice()), + endian, + )) + } else { + Ok(EndianRcSlice::new(Rc::from([]), endian)) + } +} |
