macho-unwind-info-0.4.0/.cargo_vcs_info.json0000644000000001360000000000100143570ustar { "git": { "sha1": "23dc21b25c66c31c084cac4e5ad816df79b2e5c9" }, "path_in_vcs": "" }macho-unwind-info-0.4.0/.gitignore000064400000000000000000000000351046102023000151350ustar 00000000000000/target Cargo.lock .DS_Store macho-unwind-info-0.4.0/Cargo.lock0000644000000116630000000000100123410ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "crc32fast" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if", ] [[package]] name = "derive_more" version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "flate2" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ "crc32fast", "miniz_oxide", ] [[package]] name = "macho-unwind-info" version = "0.4.0" dependencies = [ "object", "thiserror", "zerocopy", "zerocopy-derive", ] [[package]] name = "memchr" version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "miniz_oxide" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", ] [[package]] name = "object" version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "flate2", "memchr", "ruzstd", ] [[package]] name = "proc-macro2" version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] [[package]] name = "ruzstd" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58c4eb8a81997cf040a091d1f7e1938aeab6749d3a0dfa73af43cdc32393483d" dependencies = [ "byteorder", "derive_more", "twox-hash", ] [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "thiserror" version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ "proc-macro2", "quote", "syn 2.0.48", ] [[package]] name = "twox-hash" version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ "cfg-if", "static_assertions", ] [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "zerocopy" version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" dependencies = [ "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", "syn 2.0.48", ] macho-unwind-info-0.4.0/Cargo.toml0000644000000024040000000000100123550ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "macho-unwind-info" version = "0.4.0" authors = ["Markus Stange "] exclude = [ "/.github", "/tests", "/fixtures", ] description = "A parser for Apple's Compact Unwinding Format, which is used in the __unwind_info section of mach-O binaries." readme = "Readme.md" keywords = [ "unwinding", "exception", "apple", "object", "parser", ] categories = ["development-tools::debugging"] license = "MIT/Apache-2.0" repository = "https://github.com/mstange/macho-unwind-info" [[example]] name = "unwindinfodump" [[example]] name = "unwindinfolookup" [dependencies.thiserror] version = "1.0.56" [dependencies.zerocopy] version = "0.7.32" [dependencies.zerocopy-derive] version = "0.7.32" [dev-dependencies.object] version = "0.32.2" macho-unwind-info-0.4.0/Cargo.toml.orig000064400000000000000000000013131046102023000160340ustar 00000000000000[package] name = "macho-unwind-info" version = "0.4.0" edition = "2021" authors = ["Markus Stange "] categories = ["development-tools::debugging"] description = "A parser for Apple's Compact Unwinding Format, which is used in the __unwind_info section of mach-O binaries." keywords = ["unwinding", "exception", "apple", "object", "parser"] repository = "https://github.com/mstange/macho-unwind-info" license = "MIT/Apache-2.0" readme = "Readme.md" exclude = ["/.github", "/tests", "/fixtures"] [dependencies] thiserror = "1.0.56" zerocopy = "0.7.32" zerocopy-derive = "0.7.32" [dev-dependencies] object = "0.32.2" [[example]] name = "unwindinfodump" [[example]] name = "unwindinfolookup" macho-unwind-info-0.4.0/LICENSE-APACHE000064400000000000000000000251371046102023000151030ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. macho-unwind-info-0.4.0/LICENSE-MIT000064400000000000000000000020701046102023000146020ustar 00000000000000Copyright (c) 2018 Markus Stange Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. macho-unwind-info-0.4.0/Readme.md000064400000000000000000000060451046102023000146730ustar 00000000000000[![crates.io page](https://img.shields.io/crates/v/macho-unwind-info.svg)](https://crates.io/crates/macho-unwind-info) [![docs.rs page](https://docs.rs/macho-unwind-info/badge.svg)](https://docs.rs/macho-unwind-info/) # macho-unwind-info A zero-copy parser for the contents of the `__unwind_info` section of a mach-O binary. Quickly look up the unwinding opcode for an address. Then parse the opcode to find out how to recover the return address and the caller frame's register values. This crate is intended to be fast enough to be used in a sampling profiler. Re-parsing from scratch is cheap and can be done on every sample. For the full unwinding experience, both `__unwind_info` and `__eh_frame` may need to be consulted. The two sections are complementary: `__unwind_info` handles the easy cases, and refers to an `__eh_frame` FDE for the hard cases. Conversely, `__eh_frame` only includes FDEs for functions whose unwinding info cannot be represented in `__unwind_info`. On x86 and x86_64, `__unwind_info` can represent most functions regardless of whether they were compiled with framepointers or without. On arm64, compiling without framepointers is strongly discouraged, and `__unwind_info` can only represent functions which have framepointers or which don't need to restore any registers. As a result, if you have an arm64 binary without framepointers (rare!), then the `__unwind_info` basically just acts as an index for `__eh_frame`, similarly to `.eh_frame_hdr` for ELF. In clang's default configuration for arm64, non-leaf functions have framepointers and leaf functions without stored registers on the stack don't have framepointers. For leaf functions, the return address is kept in the `lr` register for the entire duration of the function. And the unwind info lets you discern between these two types of functions ("frame-based" and "frameless"). ## Example ```rust use macho_unwind_info::UnwindInfo; use macho_unwind_info::opcodes::OpcodeX86_64; let unwind_info = UnwindInfo::parse(data)?; if let Some(function) = unwind_info.lookup(0x1234)? { println!("Found function entry covering the address 0x1234:"); let opcode = OpcodeX86_64::parse(function.opcode); println!("0x{:08x}..0x{:08x}: {}", function.start_address, function.end_address, opcode); } ``` ## Command-line usage This repository also contains two CLI executables. You can install them like so: ``` % cargo install --examples macho-unwind-info ``` ## Acknowledgements Thanks a ton to [**@Gankra**](https://github.com/Gankra/) for documenting this format at https://gankra.github.io/blah/compact-unwinding/. ## License Licensed under either of * Apache License, Version 2.0 ([`LICENSE-APACHE`](./LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) * MIT license ([`LICENSE-MIT`](./LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. macho-unwind-info-0.4.0/examples/unwindinfodump.rs000064400000000000000000000034271046102023000204070ustar 00000000000000use std::{fmt::Display, fs::File, io::Read}; use macho_unwind_info::opcodes::{OpcodeArm64, OpcodeX86, OpcodeX86_64}; use macho_unwind_info::UnwindInfo; use object::{Architecture, ObjectSection}; fn main() { let mut args = std::env::args_os().skip(1); if args.len() < 1 { eprintln!("Usage: {} ", std::env::args().next().unwrap()); std::process::exit(1); } let path = args.next().unwrap(); let mut data = Vec::new(); let mut file = File::open(path).unwrap(); file.read_to_end(&mut data).unwrap(); let data = &data[..]; let file = object::File::parse(data).expect("Could not parse object file"); use object::Object; let unwind_info_data_section = file .section_by_name_bytes(b"__unwind_info") .expect("Could not find __unwind_info section"); let data = unwind_info_data_section.data().unwrap(); let arch = file.architecture(); let info = UnwindInfo::parse(data).unwrap(); let address_range = info.address_range(); println!( "Unwind info for address range 0x{:08x}-0x{:08x}", address_range.start, address_range.end ); println!(); let mut function_iter = info.functions(); while let Some(function) = function_iter.next().unwrap() { print_entry(function.start_address, function.opcode, arch); } } fn print_entry(address: u32, opcode: u32, arch: Architecture) { match arch { Architecture::I386 => print_entry_impl(address, OpcodeX86::parse(opcode)), Architecture::X86_64 => print_entry_impl(address, OpcodeX86_64::parse(opcode)), Architecture::Aarch64 => print_entry_impl(address, OpcodeArm64::parse(opcode)), _ => {} } } fn print_entry_impl(address: u32, opcode: impl Display) { println!("0x{:08x}: {}", address, opcode); } macho-unwind-info-0.4.0/examples/unwindinfolookup.rs000064400000000000000000000042201046102023000207430ustar 00000000000000use std::{fmt::Display, fs::File, io::Read}; use macho_unwind_info::opcodes::{OpcodeArm64, OpcodeX86, OpcodeX86_64}; use macho_unwind_info::UnwindInfo; use object::{Architecture, ObjectSection}; fn main() { let mut args = std::env::args().skip(1); if args.len() < 1 { eprintln!("Usage: {} ", std::env::args().next().unwrap()); std::process::exit(1); } let path = args.next().unwrap(); let pc = args.next().unwrap(); let pc: u32 = if let Some(hexstr) = pc.strip_prefix("0x") { u32::from_str_radix(hexstr, 16).unwrap() } else { pc.parse().unwrap() }; let mut data = Vec::new(); let mut file = File::open(path).unwrap(); file.read_to_end(&mut data).unwrap(); let data = &data[..]; let file = object::File::parse(data).expect("Could not parse object file"); use object::Object; let unwind_info_data_section = file .section_by_name_bytes(b"__unwind_info") .expect("Could not find __unwind_info section"); let data = unwind_info_data_section.data().unwrap(); let arch = file.architecture(); let unwind_info = UnwindInfo::parse(data).unwrap(); let function = match unwind_info.lookup(pc) { Ok(Some(f)) => f, Ok(None) => { println!("No entry was found for address 0x{:x}", pc); std::process::exit(1); } Err(e) => { println!( "There was an error when looking up address 0x{:x}: {}", pc, e ); std::process::exit(1); } }; print_entry(function.start_address, function.opcode, arch); } fn print_entry(address: u32, opcode: u32, arch: Architecture) { match arch { Architecture::I386 => print_entry_impl(address, OpcodeX86::parse(opcode)), Architecture::X86_64 => print_entry_impl(address, OpcodeX86_64::parse(opcode)), Architecture::Aarch64 => print_entry_impl(address, OpcodeArm64::parse(opcode)), _ => {} } } fn print_entry_impl(address: u32, opcode: impl Display) { println!( "Found entry with function address 0x{:08x} and opcode {}", address, opcode ); } macho-unwind-info-0.4.0/src/error.rs000064400000000000000000000037511046102023000154430ustar 00000000000000/// The error type used in this crate. #[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)] pub enum Error { /// The data slice was not big enough to read the struct, or we /// were trying to follow an invalid offset to somewhere outside /// of the data bounds. #[error("Read error: {0}")] ReadError(#[from] ReadError), /// Each page has a first_address which is supposed to match the /// start address of its first function entry. If the two addresses /// don't match, then the lookup will fail for addresses which fall /// in the gap between the page start address and the page's first /// function's start address. #[error("The page entry's first_address didn't match the address of its first function")] InvalidPageEntryFirstAddress, /// The page kind was set to an unrecognized value. #[error("Invalid page kind")] InvalidPageKind, /// There is only supposed to be one sentinel page, at the very end /// of the pages list - its first_address gives the end address of /// the unwind info address range. If a sentinel page is encountered /// somewhere else, this error is thrown. #[error("Unexpected sentinel page")] UnexpectedSentinelPage, } /// This error indicates that the data slice was not large enough to /// read the respective item. #[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)] pub enum ReadError { #[error("Could not read CompactUnwindInfoHeader")] Header, #[error("Could not read global opcodes")] GlobalOpcodes, #[error("Could not read pages")] Pages, #[error("Could not read RegularPage")] RegularPage, #[error("Could not read RegularPage functions")] RegularPageFunctions, #[error("Could not read CompressedPage")] CompressedPage, #[error("Could not read CompressedPage functions")] CompressedPageFunctions, #[error("Could not read local opcodes")] LocalOpcodes, #[error("Could not read page kind")] PageKind, } macho-unwind-info-0.4.0/src/lib.rs000064400000000000000000000367611046102023000150670ustar 00000000000000//! A zero-copy parser for the contents of the `__unwind_info` section of a //! mach-O binary. //! //! Quickly look up the unwinding opcode for an address. Then parse the opcode to find //! out how to recover the return address and the caller frame's register values. //! //! This crate is intended to be fast enough to be used in a sampling profiler. //! Re-parsing from scratch is cheap and can be done on every sample. //! //! For the full unwinding experience, both `__unwind_info` and `__eh_frame` may need //! to be consulted. The two sections are complementary: `__unwind_info` handles the //! easy cases, and refers to an `__eh_frame` FDE for the hard cases. Conversely, //! `__eh_frame` only includes FDEs for functions whose unwinding info cannot be //! represented in `__unwind_info`. //! //! On x86 and x86_64, `__unwind_info` can represent most functions regardless of //! whether they were compiled with framepointers or without. //! //! On arm64, compiling without framepointers is strongly discouraged, and //! `__unwind_info` can only represent functions which have framepointers or //! which don't need to restore any registers. As a result, if you have an arm64 //! binary without framepointers (rare!), then the `__unwind_info` basically just //! acts as an index for `__eh_frame`, similarly to `.eh_frame_hdr` for ELF. //! //! In clang's default configuration for arm64, non-leaf functions have framepointers //! and leaf functions without stored registers on the stack don't have framepointers. //! For leaf functions, the return address is kept in the `lr` register for the entire //! duration of the function. And the unwind info lets you discern between these two //! types of functions ("frame-based" and "frameless"). //! //! # Example //! //! ```rust //! use macho_unwind_info::UnwindInfo; //! use macho_unwind_info::opcodes::OpcodeX86_64; //! //! # fn example(data: &[u8]) -> Result<(), macho_unwind_info::Error> { //! let unwind_info = UnwindInfo::parse(data)?; //! //! if let Some(function) = unwind_info.lookup(0x1234)? { //! println!("Found function entry covering the address 0x1234:"); //! let opcode = OpcodeX86_64::parse(function.opcode); //! println!("0x{:08x}..0x{:08x}: {}", function.start_address, function.end_address, opcode); //! } //! # Ok(()) //! # } //! ``` mod error; mod num_display; /// Provides architecture-specific opcode parsing. pub mod opcodes; /// Lower-level structs for interpreting the format data. Can be used if the convenience APIs are too limiting. pub mod raw; mod reader; pub use error::*; use raw::*; /// A parsed representation of the unwind info. /// /// The UnwindInfo contains a list of pages, each of which contain a list of /// function entries. pub struct UnwindInfo<'a> { /// The full __unwind_info section data. data: &'a [u8], /// The list of global opcodes. global_opcodes: &'a [Opcode], /// The list of page entries in this UnwindInfo. pages: &'a [PageEntry], } /// The information about a single function in the UnwindInfo. #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Function { /// The address where this function starts. pub start_address: u32, /// The address where this function ends. Includes the padding at the end of /// the function. In reality, this is the address of the *next* function /// entry, or for the last function this is the address of the sentinel page /// entry. pub end_address: u32, /// The opcode which describes the unwinding information for this function. /// This opcode needs to be parsed in an architecture-specific manner. /// See the [opcodes] module for the facilities to do so. pub opcode: u32, } impl<'a> UnwindInfo<'a> { /// Create an [UnwindInfo] instance which wraps the raw bytes of a mach-O binary's /// `__unwind_info` section. The data can have arbitrary alignment. The parsing done /// in this function is minimal; it's basically just three bounds checks. pub fn parse(data: &'a [u8]) -> Result { let header = CompactUnwindInfoHeader::parse(data)?; let global_opcodes = header.global_opcodes(data)?; let pages = header.pages(data)?; Ok(Self { data, global_opcodes, pages, }) } /// Returns an iterator over all the functions in this UnwindInfo. pub fn functions(&self) -> FunctionIter<'a> { FunctionIter { data: self.data, global_opcodes: self.global_opcodes, pages: self.pages, cur_page: None, } } /// Returns the range of addresses covered by unwind information. pub fn address_range(&self) -> core::ops::Range { if self.pages.is_empty() { return 0..0; } let first_page = self.pages.first().unwrap(); let last_page = self.pages.last().unwrap(); first_page.first_address()..last_page.first_address() } /// Looks up the unwind information for the function that covers the given address. /// Returns `Ok(Some(function))` if a function was found. /// Returns `Ok(None)` if the address was outside of the range of addresses covered /// by the unwind info. /// Returns `Err(error)` if there was a problem with the format of the `__unwind_info` /// data. /// /// This lookup is architecture agnostic. The opcode is returned as a u32. /// To actually perform unwinding, the opcode needs to be parsed in an /// architecture-specific manner. /// /// The design of the compact unwinding format makes this lookup extremely cheap. /// It's just two binary searches: First to find the right page, end then to find /// the right function within a page. The search happens inside the wrapped data, /// with no extra copies. pub fn lookup(&self, pc: u32) -> Result, Error> { let Self { pages, data, global_opcodes, } = self; let page_index = match pages.binary_search_by_key(&pc, PageEntry::first_address) { Ok(i) => i, Err(insertion_index) => { if insertion_index == 0 { return Ok(None); } insertion_index - 1 } }; if page_index == pages.len() - 1 { // We found the sentinel last page, which just marks the end of the range. // So the looked up address is at or after the end address, i.e. outside the // range of addresses covered by this UnwindInfo. return Ok(None); } let page_entry = &pages[page_index]; let next_page_entry = &pages[page_index + 1]; let page_offset = page_entry.page_offset(); match page_entry.page_kind(data)? { consts::PAGE_KIND_REGULAR => { let page = RegularPage::parse(data, page_offset.into())?; let functions = page.functions(data, page_offset)?; let function_index = match functions.binary_search_by_key(&pc, RegularFunctionEntry::address) { Ok(i) => i, Err(insertion_index) => { if insertion_index == 0 { return Err(Error::InvalidPageEntryFirstAddress); } insertion_index - 1 } }; let entry = &functions[function_index]; let fun_address = entry.address(); let next_fun_address = if let Some(next_entry) = functions.get(function_index + 1) { next_entry.address() } else { next_page_entry.first_address() }; Ok(Some(Function { start_address: fun_address, end_address: next_fun_address, opcode: entry.opcode(), })) } consts::PAGE_KIND_COMPRESSED => { let page = CompressedPage::parse(data, page_offset.into())?; let functions = page.functions(data, page_offset)?; let page_address = page_entry.first_address(); let rel_pc = pc - page_address; let function_index = match functions.binary_search_by_key(&rel_pc, |&entry| { CompressedFunctionEntry::new(entry.into()).relative_address() }) { Ok(i) => i, Err(insertion_index) => { if insertion_index == 0 { return Err(Error::InvalidPageEntryFirstAddress); } insertion_index - 1 } }; let entry = CompressedFunctionEntry::new(functions[function_index].into()); let fun_address = page_address + entry.relative_address(); let next_fun_address = if let Some(next_entry) = functions.get(function_index + 1) { let next_entry = CompressedFunctionEntry::new((*next_entry).into()); page_address + next_entry.relative_address() } else { next_page_entry.first_address() }; let opcode_index: usize = entry.opcode_index().into(); let opcode = if opcode_index < global_opcodes.len() { global_opcodes[opcode_index].opcode() } else { let local_opcodes = page.local_opcodes(data, page_offset)?; let local_index = opcode_index - global_opcodes.len(); local_opcodes[local_index].opcode() }; Ok(Some(Function { start_address: fun_address, end_address: next_fun_address, opcode, })) } consts::PAGE_KIND_SENTINEL => { // Only the last page should be a sentinel page, and we've already checked earlier // that we're not in the last page. Err(Error::UnexpectedSentinelPage) } _ => Err(Error::InvalidPageKind), } } } /// An iterator over the functions in an UnwindInfo page. pub struct FunctionIter<'a> { /// The full __unwind_info section data. data: &'a [u8], /// The list of global opcodes. global_opcodes: &'a [Opcode], /// The slice of the remaining to-be-iterated-over pages. pages: &'a [PageEntry], /// The page whose functions we're iterating over at the moment. cur_page: Option>, } /// The current page of the function iterator. /// The functions field is the slice of the remaining to-be-iterated-over functions. #[derive(Clone, Copy)] enum PageWithPartialFunctions<'a> { Regular { next_page_address: u32, functions: &'a [RegularFunctionEntry], }, Compressed { page_address: u32, next_page_address: u32, local_opcodes: &'a [Opcode], functions: &'a [U32], }, } impl<'a> FunctionIter<'a> { #[allow(clippy::should_implement_trait)] pub fn next(&mut self) -> Result, Error> { loop { let cur_page = if let Some(cur_page) = self.cur_page.as_mut() { cur_page } else { let cur_page = match self.next_page()? { Some(page) => page, None => return Ok(None), }; self.cur_page.insert(cur_page) }; match cur_page { PageWithPartialFunctions::Regular { next_page_address, functions, } => { if let Some((entry, remainder)) = functions.split_first() { *functions = remainder; let start_address = entry.address(); let end_address = remainder .first() .map(RegularFunctionEntry::address) .unwrap_or(*next_page_address); return Ok(Some(Function { start_address, end_address, opcode: entry.opcode(), })); } } PageWithPartialFunctions::Compressed { page_address, functions, next_page_address, local_opcodes, } => { if let Some((entry, remainder)) = functions.split_first() { *functions = remainder; let entry = CompressedFunctionEntry::new((*entry).into()); let start_address = *page_address + entry.relative_address(); let end_address = match remainder.first() { Some(next_entry) => { let next_entry = CompressedFunctionEntry::new((*next_entry).into()); *page_address + next_entry.relative_address() } None => *next_page_address, }; let opcode_index: usize = entry.opcode_index().into(); let opcode = if opcode_index < self.global_opcodes.len() { self.global_opcodes[opcode_index].opcode() } else { let local_index = opcode_index - self.global_opcodes.len(); local_opcodes[local_index].opcode() }; return Ok(Some(Function { start_address, end_address, opcode, })); } } } self.cur_page = None; } } fn next_page(&mut self) -> Result>, Error> { let (page_entry, remainder) = match self.pages.split_first() { Some(split) => split, None => return Ok(None), }; self.pages = remainder; let next_page_entry = match remainder.first() { Some(entry) => entry, None => return Ok(None), }; let page_offset = page_entry.page_offset(); let page_address = page_entry.first_address(); let next_page_address = next_page_entry.first_address(); let data = self.data; let cur_page = match page_entry.page_kind(data)? { consts::PAGE_KIND_REGULAR => { let page = RegularPage::parse(data, page_offset.into())?; PageWithPartialFunctions::Regular { functions: page.functions(data, page_offset)?, next_page_address, } } consts::PAGE_KIND_COMPRESSED => { let page = CompressedPage::parse(data, page_offset.into())?; PageWithPartialFunctions::Compressed { page_address, next_page_address, functions: page.functions(data, page_offset)?, local_opcodes: page.local_opcodes(data, page_offset)?, } } consts::PAGE_KIND_SENTINEL => return Err(Error::UnexpectedSentinelPage), _ => return Err(Error::InvalidPageKind), }; Ok(Some(cur_page)) } } macho-unwind-info-0.4.0/src/num_display.rs000064400000000000000000000006531046102023000166340ustar 00000000000000use std::fmt::{Binary, Debug, LowerHex}; pub struct HexNum(pub N); impl Debug for HexNum { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { LowerHex::fmt(&self.0, f) } } pub struct BinNum(pub N); impl Debug for BinNum { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Binary::fmt(&self.0, f) } } macho-unwind-info-0.4.0/src/opcodes/arm64.rs000064400000000000000000000104021046102023000166660ustar 00000000000000use std::fmt::Display; use super::bitfield::OpcodeBitfield; use crate::raw::consts::*; #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum OpcodeArm64 { Null, Frameless { stack_size_in_bytes: u16, }, Dwarf { eh_frame_fde: u32, }, FrameBased { saved_reg_pair_count: u8, // Whether each register pair was pushed d14_and_d15_saved: bool, d12_and_d13_saved: bool, d10_and_d11_saved: bool, d8_and_d9_saved: bool, x27_and_x28_saved: bool, x25_and_x26_saved: bool, x23_and_x24_saved: bool, x21_and_x22_saved: bool, x19_and_x20_saved: bool, }, UnrecognizedKind(u8), } impl OpcodeArm64 { pub fn parse(opcode: u32) -> Self { match OpcodeBitfield::new(opcode).kind() { OPCODE_KIND_NULL => OpcodeArm64::Null, OPCODE_KIND_ARM64_FRAMELESS => OpcodeArm64::Frameless { stack_size_in_bytes: (((opcode >> 12) & 0b1111_1111_1111) as u16) * 16, }, OPCODE_KIND_ARM64_DWARF => OpcodeArm64::Dwarf { eh_frame_fde: (opcode & 0xffffff), }, OPCODE_KIND_ARM64_FRAMEBASED => { let saved_reg_pair_count = (opcode & 0b1_1111_1111).count_ones() as u8; OpcodeArm64::FrameBased { saved_reg_pair_count, d14_and_d15_saved: ((opcode >> 8) & 1) == 1, d12_and_d13_saved: ((opcode >> 7) & 1) == 1, d10_and_d11_saved: ((opcode >> 6) & 1) == 1, d8_and_d9_saved: ((opcode >> 5) & 1) == 1, x27_and_x28_saved: ((opcode >> 4) & 1) == 1, x25_and_x26_saved: ((opcode >> 3) & 1) == 1, x23_and_x24_saved: ((opcode >> 2) & 1) == 1, x21_and_x22_saved: ((opcode >> 1) & 1) == 1, x19_and_x20_saved: (opcode & 1) == 1, } } kind => OpcodeArm64::UnrecognizedKind(kind), } } } impl Display for OpcodeArm64 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { OpcodeArm64::Null => { write!(f, "(uncovered)")?; } OpcodeArm64::Frameless { stack_size_in_bytes, } => { if *stack_size_in_bytes == 0 { write!(f, "CFA=reg31")?; } else { write!(f, "CFA=reg31+{}", stack_size_in_bytes)?; } } OpcodeArm64::Dwarf { eh_frame_fde } => { write!(f, "(check eh_frame FDE 0x{:x})", eh_frame_fde)?; } OpcodeArm64::FrameBased { d14_and_d15_saved, d12_and_d13_saved, d10_and_d11_saved, d8_and_d9_saved, x27_and_x28_saved, x25_and_x26_saved, x23_and_x24_saved, x21_and_x22_saved, x19_and_x20_saved, .. } => { write!(f, "CFA=reg29+16: reg29=[CFA-16], reg30=[CFA-8]")?; let mut offset = 32; let mut next_pair = |pair_saved, a, b| { if pair_saved { let r = write!(f, ", {}=[CFA-{}], {}=[CFA-{}]", a, offset, b, offset + 8); offset += 16; r } else { Ok(()) } }; next_pair(*d14_and_d15_saved, "reg14", "reg15")?; next_pair(*d12_and_d13_saved, "reg12", "reg13")?; next_pair(*d10_and_d11_saved, "reg10", "reg11")?; next_pair(*d8_and_d9_saved, "reg8", "reg9")?; next_pair(*x27_and_x28_saved, "reg27", "reg28")?; next_pair(*x25_and_x26_saved, "reg25", "reg26")?; next_pair(*x23_and_x24_saved, "reg23", "reg24")?; next_pair(*x21_and_x22_saved, "reg21", "reg22")?; next_pair(*x19_and_x20_saved, "reg19", "reg20")?; } OpcodeArm64::UnrecognizedKind(kind) => { write!(f, "!! Unrecognized kind {}", kind)?; } } Ok(()) } } macho-unwind-info-0.4.0/src/opcodes/bitfield.rs000064400000000000000000000030401046102023000175170ustar 00000000000000use crate::num_display::BinNum; use std::fmt::Debug; pub struct OpcodeBitfield(pub u32); impl OpcodeBitfield { pub fn new(value: u32) -> Self { Self(value) } /// Whether this instruction is the start of a function. pub fn is_function_start(&self) -> bool { self.0 >> 31 == 1 } /// Whether there is an lsda entry for this instruction. pub fn has_lsda(&self) -> bool { (self.0 >> 30) & 0b1 == 1 } /// An index into the global personalities array /// (TODO: ignore if has_lsda() == false?) pub fn personality_index(&self) -> u8 { ((self.0 >> 28) & 0b11) as u8 } /// The architecture-specific kind of opcode this is, specifying how to /// interpret the remaining 24 bits of the opcode. pub fn kind(&self) -> u8 { ((self.0 >> 24) & 0b1111) as u8 } /// The architecture-specific remaining 24 bits. pub fn specific_bits(&self) -> u32 { self.0 & 0xffffff } } impl From for OpcodeBitfield { fn from(opcode: u32) -> OpcodeBitfield { OpcodeBitfield::new(opcode) } } impl Debug for OpcodeBitfield { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Opcode") .field("kind", &self.kind()) .field("is_function_start", &self.is_function_start()) .field("has_lsda", &self.has_lsda()) .field("personality_index", &self.personality_index()) .field("specific_bits", &BinNum(self.specific_bits())) .finish() } } macho-unwind-info-0.4.0/src/opcodes/mod.rs000064400000000000000000000002121046102023000165120ustar 00000000000000mod arm64; mod bitfield; mod permutation; mod x86; mod x86_64; pub use arm64::*; pub use bitfield::*; pub use x86::*; pub use x86_64::*; macho-unwind-info-0.4.0/src/opcodes/permutation.rs000064400000000000000000000050341046102023000203110ustar 00000000000000/// Magically unpack up to 6 values from 10 bits. /// /// Background: /// /// Let's start with a simpler example of packing a list of numbers. /// Let's say you want to store 2 values a and b, which can each be 0, 1, or 2. /// You can store this as x = a * 3 + b. Then you can get out (a, b) by doing a /// division by 3 with remainder, because this has the form of n * 3 + (something less than 3) /// /// Similar, for four values, you can use: /// /// ```text /// x = a * 27 + b * 9 + c * 3 + d. /// ^^^^^^^^^^^^^^^^^ == x % 27 /// ^^^^^^^^^ == x % 9 /// ^ == x % 3 /// x == 27 * a + rem27 /// rem27 == 9 * b + rem9 /// rem9 == 3 * c + rem3 /// rem3 = d /// ``` /// /// Written differently: /// `x = d + 3 * (c + 3 * (b + (3 * a)))` /// /// So that was the case for when all digits have the same range (0..3 in this example). /// /// In this function we want to decode a permutation. In a permutation of n items, /// for the first digit we can choose one of n items, for the second digit we can /// choose one of the remaining n - 1 items, for the third one of the remaining n - 2 etc. /// /// We have the choice between 6 registers, so n = 6 in this function. /// Each digit is stored zero-based. So a is in 0..6, b is in 0..5, c in 0..4 etc. /// /// We encode as (a, b, c) as c + 4 * (b + 5 * a) /// [...] pub fn decode_permutation_6(count: u32, mut encoding: u32) -> std::result::Result<[u8; 6], ()> { if count > 6 { return Err(()); } let mut compressed_regindexes = [0; 6]; if count > 4 { compressed_regindexes[4] = encoding % 2; encoding /= 2; } if count > 3 { compressed_regindexes[3] = encoding % 3; encoding /= 3; } if count > 2 { compressed_regindexes[2] = encoding % 4; encoding /= 4; } if count > 1 { compressed_regindexes[1] = encoding % 5; encoding /= 5; } if count > 0 { compressed_regindexes[0] = encoding; } if compressed_regindexes[0] >= 6 { return Err(()); } let mut registers = [0; 6]; let mut used = [false; 6]; for i in 0..count { let compressed_regindex = compressed_regindexes[i as usize]; debug_assert!(compressed_regindex < 6 - i); let uncompressed_regindex = (0..6) .filter(|ri| !used[*ri]) .nth(compressed_regindex as usize) .unwrap(); used[uncompressed_regindex] = true; registers[i as usize] = (uncompressed_regindex + 1) as u8; } Ok(registers) } macho-unwind-info-0.4.0/src/opcodes/x86.rs000064400000000000000000000221071046102023000163670ustar 00000000000000use std::fmt::Display; use super::bitfield::OpcodeBitfield; use super::permutation::decode_permutation_6; use crate::consts::*; #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum RegisterNameX86 { Ebx, Ecx, Edx, Edi, Esi, Ebp, } impl RegisterNameX86 { pub fn parse(n: u8) -> Option { match n { 1 => Some(RegisterNameX86::Ebx), 2 => Some(RegisterNameX86::Ecx), 3 => Some(RegisterNameX86::Edx), 4 => Some(RegisterNameX86::Edi), 5 => Some(RegisterNameX86::Esi), 6 => Some(RegisterNameX86::Ebp), _ => None, } } pub fn dwarf_name(&self) -> &'static str { match self { RegisterNameX86::Ebx => "reg3", RegisterNameX86::Ecx => "reg1", RegisterNameX86::Edx => "reg2", RegisterNameX86::Edi => "reg7", RegisterNameX86::Esi => "reg6", RegisterNameX86::Ebp => "reg5", } } } #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum OpcodeX86 { Null, FrameBased { stack_offset_in_bytes: u16, saved_regs: [Option; 5], }, FramelessImmediate { stack_size_in_bytes: u16, saved_regs: [Option; 6], }, FramelessIndirect { /// Offset from the start of the function into the middle of a `sub` /// instruction, pointing right at the instruction's "immediate" which /// is a u32 value with the offset we need. (NOTE: not divided by anything!) immediate_offset_from_function_start: u8, /// An offset to add to the loaded stack size. /// This allows the stack size to differ slightly from the `sub`, to /// compensate for any function prologue that pushes a bunch of /// pointer-sized registers. This adjust value includes the return /// address on the stack. For example, if the function begins with six push /// instructions, followed by a sub instruction, then stack_adjust_in_bytes /// is 28: 4 bytes for the return address + 6 * 4 for each pushed register. stack_adjust_in_bytes: u8, /// The registers, in the order that they need to be popped in when /// returning / unwinding from this function. (Reverse order from /// function prologue!) /// Can have leading `None`s. saved_regs: [Option; 6], }, Dwarf { eh_frame_fde: u32, }, InvalidFrameless, UnrecognizedKind(u8), } impl OpcodeX86 { pub fn parse(opcode: u32) -> Self { match OpcodeBitfield::new(opcode).kind() { OPCODE_KIND_NULL => OpcodeX86::Null, OPCODE_KIND_X86_FRAMEBASED => OpcodeX86::FrameBased { stack_offset_in_bytes: (((opcode >> 16) & 0xff) as u16) * 4, saved_regs: [ RegisterNameX86::parse(((opcode >> 12) & 0b111) as u8), RegisterNameX86::parse(((opcode >> 9) & 0b111) as u8), RegisterNameX86::parse(((opcode >> 6) & 0b111) as u8), RegisterNameX86::parse(((opcode >> 3) & 0b111) as u8), RegisterNameX86::parse((opcode & 0b111) as u8), ], }, OPCODE_KIND_X86_FRAMELESS_IMMEDIATE => { let stack_size_in_bytes = (((opcode >> 16) & 0xff) as u16) * 4; let register_count = (opcode >> 10) & 0b111; let register_permutation = opcode & 0b11_1111_1111; let saved_registers = match decode_permutation_6(register_count, register_permutation) { Ok(regs) => regs, Err(_) => return OpcodeX86::InvalidFrameless, }; OpcodeX86::FramelessImmediate { stack_size_in_bytes, saved_regs: [ RegisterNameX86::parse(saved_registers[0]), RegisterNameX86::parse(saved_registers[1]), RegisterNameX86::parse(saved_registers[2]), RegisterNameX86::parse(saved_registers[3]), RegisterNameX86::parse(saved_registers[4]), RegisterNameX86::parse(saved_registers[5]), ], } } OPCODE_KIND_X86_FRAMELESS_INDIRECT => { let immediate_offset_from_function_start = (opcode >> 16) as u8; let stack_adjust_in_bytes = ((opcode >> 13) & 0b111) as u8 * 4; let register_count = (opcode >> 10) & 0b111; let register_permutation = opcode & 0b11_1111_1111; let saved_registers = match decode_permutation_6(register_count, register_permutation) { Ok(regs) => regs, Err(_) => return OpcodeX86::InvalidFrameless, }; OpcodeX86::FramelessIndirect { immediate_offset_from_function_start, stack_adjust_in_bytes, saved_regs: [ RegisterNameX86::parse(saved_registers[0]), RegisterNameX86::parse(saved_registers[1]), RegisterNameX86::parse(saved_registers[2]), RegisterNameX86::parse(saved_registers[3]), RegisterNameX86::parse(saved_registers[4]), RegisterNameX86::parse(saved_registers[5]), ], } } OPCODE_KIND_X86_DWARF => OpcodeX86::Dwarf { eh_frame_fde: (opcode & 0xffffff), }, kind => OpcodeX86::UnrecognizedKind(kind), } } } impl Display for OpcodeX86 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { OpcodeX86::Null => { write!(f, "(uncovered)")?; } OpcodeX86::FrameBased { stack_offset_in_bytes, saved_regs, } => { // ebp was set to esp before the saved registers were pushed. // The first pushed register is at ebp - 4 (== CFA - 12), the last at ebp - stack_offset_in_bytes. write!(f, "CFA=reg6+8: reg6=[CFA-8], reg16=[CFA-4]")?; let max_count = (*stack_offset_in_bytes / 4) as usize; let mut offset = *stack_offset_in_bytes + 8; // + 2 for rbp, return address for reg in saved_regs.iter().rev().take(max_count) { if let Some(reg) = reg { write!(f, ", {}=[CFA-{}]", reg.dwarf_name(), offset)?; } offset -= 4; } } OpcodeX86::FramelessImmediate { stack_size_in_bytes, saved_regs, } => { if *stack_size_in_bytes == 0 { write!(f, "CFA=reg7:",)?; } else { write!(f, "CFA=reg7+{}:", *stack_size_in_bytes)?; } write!(f, " reg16=[CFA-4]")?; let mut offset = 2 * 4; for reg in saved_regs.iter().rev().flatten() { write!(f, ", {}=[CFA-{}]", reg.dwarf_name(), offset)?; offset += 4; } } OpcodeX86::FramelessIndirect { immediate_offset_from_function_start, stack_adjust_in_bytes, saved_regs, } => { write!( f, "CFA=[function_start+{}]+{}", immediate_offset_from_function_start, stack_adjust_in_bytes )?; write!(f, " reg16=[CFA-4]")?; let mut offset = 2 * 4; for reg in saved_regs.iter().rev().flatten() { write!(f, ", {}=[CFA-{}]", reg.dwarf_name(), offset)?; offset += 4; } } OpcodeX86::Dwarf { eh_frame_fde } => { write!(f, "(check eh_frame FDE 0x{:x})", eh_frame_fde)?; } OpcodeX86::InvalidFrameless => { write!( f, "!! frameless immediate or indirect with invalid permutation encoding" )?; } OpcodeX86::UnrecognizedKind(kind) => { write!(f, "!! Unrecognized kind {}", kind)?; } } Ok(()) } } #[cfg(test)] mod test { use super::*; #[test] fn test_frameless_indirect() { use RegisterNameX86::*; assert_eq!( OpcodeX86::parse(0x30df800), OpcodeX86::FramelessIndirect { immediate_offset_from_function_start: 13, stack_adjust_in_bytes: 28, saved_regs: [ Some(Ebx), Some(Ecx), Some(Edx), Some(Edi), Some(Esi), Some(Ebp) ] } ) } } macho-unwind-info-0.4.0/src/opcodes/x86_64.rs000064400000000000000000000232061046102023000167010ustar 00000000000000use std::fmt::Display; use super::bitfield::OpcodeBitfield; use super::permutation::decode_permutation_6; use crate::consts::*; #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum RegisterNameX86_64 { Rbx, R12, R13, R14, R15, Rbp, } impl RegisterNameX86_64 { pub fn parse(n: u8) -> Option { match n { 1 => Some(RegisterNameX86_64::Rbx), 2 => Some(RegisterNameX86_64::R12), 3 => Some(RegisterNameX86_64::R13), 4 => Some(RegisterNameX86_64::R14), 5 => Some(RegisterNameX86_64::R15), 6 => Some(RegisterNameX86_64::Rbp), _ => None, } } pub fn dwarf_name(&self) -> &'static str { match self { RegisterNameX86_64::Rbx => "reg3", RegisterNameX86_64::R12 => "reg12", RegisterNameX86_64::R13 => "reg13", RegisterNameX86_64::R14 => "reg14", RegisterNameX86_64::R15 => "reg15", RegisterNameX86_64::Rbp => "reg6", } } } #[derive(Clone, Debug, PartialEq, Eq)] pub enum OpcodeX86_64 { Null, FrameBased { stack_offset_in_bytes: u16, saved_regs: [Option; 5], }, FramelessImmediate { stack_size_in_bytes: u16, saved_regs: [Option; 6], }, FramelessIndirect { /// Offset from the start of the function into the middle of a `sub` /// instruction, pointing right at the instruction's "immediate" which /// is a u32 value with the offset we need. (NOTE: not divided by anything!) /// Example: /// - function_start is 0x1c20 /// - immediate_offset_from_function_start is 13 (= 0xd), /// - there's sub instruction at 0x1c2a: sub rsp, 0xc28. /// This instruction is encoded as 48 81 EC 28 0C 00 00, with the 28 /// byte at 0x1c2d (= 0x1c20 + 13). The immediate is 28 0C 00 00, /// interpreted as a little-endian u32: 0xc28. immediate_offset_from_function_start: u8, /// An offset to add to the loaded stack size. /// This allows the stack size to differ slightly from the `sub`, to /// compensate for any function prologue that pushes a bunch of /// pointer-sized registers. This adjust value includes the return /// address on the stack. For example, if the function begins with six push /// instructions, followed by a sub instruction, then stack_adjust_in_bytes /// is 56: 8 bytes for the return address + 6 * 8 for each pushed register. stack_adjust_in_bytes: u8, /// The registers, in the order that they need to be popped in when /// returning / unwinding from this function. (Reverse order from /// function prologue!) /// Can have leading `None`s. saved_regs: [Option; 6], }, Dwarf { eh_frame_fde: u32, }, InvalidFrameless, UnrecognizedKind(u8), } impl OpcodeX86_64 { pub fn parse(opcode: u32) -> Self { match OpcodeBitfield::new(opcode).kind() { OPCODE_KIND_NULL => OpcodeX86_64::Null, OPCODE_KIND_X86_FRAMEBASED => OpcodeX86_64::FrameBased { stack_offset_in_bytes: (((opcode >> 16) & 0xff) as u16) * 8, saved_regs: [ RegisterNameX86_64::parse(((opcode >> 12) & 0b111) as u8), RegisterNameX86_64::parse(((opcode >> 9) & 0b111) as u8), RegisterNameX86_64::parse(((opcode >> 6) & 0b111) as u8), RegisterNameX86_64::parse(((opcode >> 3) & 0b111) as u8), RegisterNameX86_64::parse((opcode & 0b111) as u8), ], }, OPCODE_KIND_X86_FRAMELESS_IMMEDIATE => { let stack_size_in_bytes = (((opcode >> 16) & 0xff) as u16) * 8; let register_count = (opcode >> 10) & 0b111; let register_permutation = opcode & 0b11_1111_1111; let saved_registers = match decode_permutation_6(register_count, register_permutation) { Ok(regs) => regs, Err(_) => return OpcodeX86_64::InvalidFrameless, }; OpcodeX86_64::FramelessImmediate { stack_size_in_bytes, saved_regs: [ RegisterNameX86_64::parse(saved_registers[0]), RegisterNameX86_64::parse(saved_registers[1]), RegisterNameX86_64::parse(saved_registers[2]), RegisterNameX86_64::parse(saved_registers[3]), RegisterNameX86_64::parse(saved_registers[4]), RegisterNameX86_64::parse(saved_registers[5]), ], } } OPCODE_KIND_X86_FRAMELESS_INDIRECT => { let immediate_offset_from_function_start = (opcode >> 16) as u8; let stack_adjust_in_bytes = ((opcode >> 13) & 0b111) as u8 * 8; let register_count = (opcode >> 10) & 0b111; let register_permutation = opcode & 0b11_1111_1111; let saved_registers = match decode_permutation_6(register_count, register_permutation) { Ok(regs) => regs, Err(_) => return OpcodeX86_64::InvalidFrameless, }; OpcodeX86_64::FramelessIndirect { immediate_offset_from_function_start, stack_adjust_in_bytes, saved_regs: [ RegisterNameX86_64::parse(saved_registers[0]), RegisterNameX86_64::parse(saved_registers[1]), RegisterNameX86_64::parse(saved_registers[2]), RegisterNameX86_64::parse(saved_registers[3]), RegisterNameX86_64::parse(saved_registers[4]), RegisterNameX86_64::parse(saved_registers[5]), ], } } OPCODE_KIND_X86_DWARF => OpcodeX86_64::Dwarf { eh_frame_fde: (opcode & 0xffffff), }, kind => OpcodeX86_64::UnrecognizedKind(kind), } } } impl Display for OpcodeX86_64 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { OpcodeX86_64::Null => { write!(f, "(uncovered)")?; } OpcodeX86_64::FrameBased { stack_offset_in_bytes, saved_regs, } => { // rbp was set to rsp before the saved registers were pushed. // The first pushed register is at rbp - 8 (== CFA - 24), the last at rbp - stack_offset_in_bytes. write!(f, "CFA=reg6+16: reg6=[CFA-16], reg16=[CFA-8]")?; let max_count = (*stack_offset_in_bytes / 8) as usize; let mut offset = *stack_offset_in_bytes + 16; // + 2 for rbp, return address for reg in saved_regs.iter().rev().take(max_count) { if let Some(reg) = reg { write!(f, ", {}=[CFA-{}]", reg.dwarf_name(), offset)?; } offset -= 8; } } OpcodeX86_64::FramelessImmediate { stack_size_in_bytes, saved_regs, } => { if *stack_size_in_bytes == 0 { write!(f, "CFA=reg7:",)?; } else { write!(f, "CFA=reg7+{}:", *stack_size_in_bytes)?; } write!(f, " reg16=[CFA-8]")?; let mut offset = 2 * 8; for reg in saved_regs.iter().rev().flatten() { write!(f, ", {}=[CFA-{}]", reg.dwarf_name(), offset)?; offset += 8; } } OpcodeX86_64::FramelessIndirect { immediate_offset_from_function_start, stack_adjust_in_bytes, saved_regs, } => { write!( f, "CFA=[function_start+{}]+{}", immediate_offset_from_function_start, stack_adjust_in_bytes )?; write!(f, " reg16=[CFA-8]")?; let mut offset = 2 * 8; for reg in saved_regs.iter().rev().flatten() { write!(f, ", {}=[CFA-{}]", reg.dwarf_name(), offset)?; offset += 8; } } OpcodeX86_64::Dwarf { eh_frame_fde } => { write!(f, "(check eh_frame FDE 0x{:x})", eh_frame_fde)?; } OpcodeX86_64::InvalidFrameless => { write!( f, "!! frameless immediate or indirect with invalid permutation encoding" )?; } OpcodeX86_64::UnrecognizedKind(kind) => { write!(f, "!! Unrecognized kind {}", kind)?; } } Ok(()) } } #[cfg(test)] mod test { use super::*; #[test] fn test_frameless_indirect() { use RegisterNameX86_64::*; assert_eq!( OpcodeX86_64::parse(0x30df800), OpcodeX86_64::FramelessIndirect { immediate_offset_from_function_start: 13, stack_adjust_in_bytes: 56, saved_regs: [ Some(Rbx), Some(R12), Some(R13), Some(R14), Some(R15), Some(Rbp) ] } ) } } macho-unwind-info-0.4.0/src/raw/compressed_function.rs000064400000000000000000000025631046102023000211540ustar 00000000000000use crate::num_display::HexNum; use std::fmt::Debug; /// Allows accessing the two packed values from a "compressed" function entry. #[derive(Clone, Copy, PartialEq, Eq)] pub struct CompressedFunctionEntry(pub u32); /// Entries are a u32 that contains two packed values (from high to low): /// * 8 bits: opcode index /// * 24 bits: function address impl CompressedFunctionEntry { /// Wrap the u32. pub fn new(value: u32) -> Self { Self(value) } /// The opcode index. /// * 0..global_opcodes_len => index into global palette /// * global_opcodes_len..255 => index into local palette /// (subtract global_opcodes_len to get the real local index) pub fn opcode_index(&self) -> u8 { (self.0 >> 24) as u8 } /// The function address, relative to the page's first_address. pub fn relative_address(&self) -> u32 { self.0 & 0xffffff } } impl From for CompressedFunctionEntry { fn from(entry: u32) -> CompressedFunctionEntry { CompressedFunctionEntry::new(entry) } } impl Debug for CompressedFunctionEntry { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("CompressedFunctionEntry") .field("opcode_index", &HexNum(self.opcode_index())) .field("relative_address", &HexNum(self.relative_address())) .finish() } } macho-unwind-info-0.4.0/src/raw/consts.rs000064400000000000000000000010541046102023000164060ustar 00000000000000pub const PAGE_KIND_SENTINEL: u32 = 1; // used in the last page, whose first_address is the end address pub const PAGE_KIND_REGULAR: u32 = 2; pub const PAGE_KIND_COMPRESSED: u32 = 3; pub const OPCODE_KIND_NULL: u8 = 0; pub const OPCODE_KIND_X86_FRAMEBASED: u8 = 1; pub const OPCODE_KIND_X86_FRAMELESS_IMMEDIATE: u8 = 2; pub const OPCODE_KIND_X86_FRAMELESS_INDIRECT: u8 = 3; pub const OPCODE_KIND_X86_DWARF: u8 = 4; pub const OPCODE_KIND_ARM64_FRAMELESS: u8 = 2; pub const OPCODE_KIND_ARM64_DWARF: u8 = 3; pub const OPCODE_KIND_ARM64_FRAMEBASED: u8 = 4; macho-unwind-info-0.4.0/src/raw/format.rs000064400000000000000000000076631046102023000164010ustar 00000000000000use std::fmt::Debug; use zerocopy_derive::{FromBytes, FromZeroes, Unaligned}; use super::unaligned::{U16, U32}; // Written with help from https://gankra.github.io/blah/compact-unwinding/ /// The `__unwind_info` header. #[derive(Unaligned, FromZeroes, FromBytes, Debug, Clone, Copy)] #[repr(C)] pub struct CompactUnwindInfoHeader { /// The version. Only version 1 is currently defined pub version: U32, /// The array of U32 global opcodes (offset relative to start of root page). /// /// These may be indexed by "compressed" second-level pages. pub global_opcodes_offset: U32, pub global_opcodes_len: U32, /// The array of U32 global personality codes (offset relative to start of root page). /// /// Personalities define the style of unwinding that an unwinder should use, /// and how to interpret the LSDA functions for a function (see below). pub personalities_offset: U32, pub personalities_len: U32, /// The array of [`PageEntry`]'s describing the second-level pages /// (offset relative to start of root page). pub pages_offset: U32, pub pages_len: U32, // After this point there are several dynamically-sized arrays whose precise // order and positioning don't matter, because they are all accessed using // offsets like the ones above. The arrays are: // global_opcodes: [u32; global_opcodes_len], // personalities: [u32; personalities_len], // pages: [PageEntry; pages_len], // lsdas: [LsdaEntry; unknown_len], } /// One element of the array of pages. #[derive(Unaligned, FromZeroes, FromBytes, Clone, Copy)] #[repr(C)] pub struct PageEntry { /// The first address mapped by this page. /// /// This is useful for binary-searching for the page that can map /// a specific address in the binary (the primary kind of lookup /// performed by an unwinder). pub first_address: U32, /// Offset of the second-level page. /// /// This may point to either a [`RegularPage`] or a [`CompressedPage`]. /// Which it is can be determined by the 32-bit "kind" value that is at /// the start of both layouts. pub page_offset: U32, /// Base offset into the lsdas array that functions in this page will be /// relative to. pub lsda_index_offset: U32, } /// A non-compressed page. #[derive(Unaligned, FromZeroes, FromBytes, Debug, Clone, Copy)] #[repr(C)] pub struct RegularPage { /// Always 2 (use to distinguish from CompressedPage). pub kind: U32, /// The Array of [`RegularFunctionEntry`]'s (offset relative to **start of this page**). pub functions_offset: U16, pub functions_len: U16, } /// A "compressed" page. #[derive(Unaligned, FromZeroes, FromBytes, Debug, Clone, Copy)] #[repr(C)] pub struct CompressedPage { /// Always 3 (use to distinguish from RegularPage). pub kind: U32, /// The array of compressed u32 function entries (offset relative to **start of this page**). /// /// Entries are a u32 that contains two packed values (from highest to lowest bits): /// * 8 bits: opcode index /// * 0..global_opcodes_len => index into global palette /// * global_opcodes_len..255 => index into local palette (subtract global_opcodes_len) /// * 24 bits: instruction address /// * address is relative to this page's first_address! pub functions_offset: U16, pub functions_len: U16, /// The array of u32 local opcodes for this page (offset relative to **start of this page**). pub local_opcodes_offset: U16, pub local_opcodes_len: U16, } /// An opcode. #[derive(Unaligned, FromZeroes, FromBytes, Debug, Clone, Copy)] #[repr(C)] pub struct Opcode(pub U32); /// A function entry from a non-compressed page. #[derive(Unaligned, FromZeroes, FromBytes, Debug, Clone, Copy)] #[repr(C)] pub struct RegularFunctionEntry { /// The address in the binary for this function entry (absolute). pub address: U32, /// The opcode for this address. pub opcode: Opcode, } macho-unwind-info-0.4.0/src/raw/impls.rs000064400000000000000000000114431046102023000162240ustar 00000000000000use std::fmt::Debug; use super::format::{ CompactUnwindInfoHeader, CompressedPage, Opcode, PageEntry, RegularFunctionEntry, RegularPage, }; use super::unaligned::U32; use crate::error::ReadError; use crate::num_display::HexNum; use crate::reader::Reader; type Result = std::result::Result; impl CompactUnwindInfoHeader { pub fn parse(data: &[u8]) -> Result<&Self> { data.read_at::(0) .ok_or(ReadError::Header) } pub fn global_opcodes_offset(&self) -> u32 { self.global_opcodes_offset.into() } pub fn global_opcodes_len(&self) -> u32 { self.global_opcodes_len.into() } pub fn pages_offset(&self) -> u32 { self.pages_offset.into() } pub fn pages_len(&self) -> u32 { self.pages_len.into() } /// Return the list of global opcodes. pub fn global_opcodes<'data>(&self, data: &'data [u8]) -> Result<&'data [Opcode]> { data.read_slice_at::( self.global_opcodes_offset().into(), self.global_opcodes_len() as usize, ) .ok_or(ReadError::GlobalOpcodes) } /// Return the list of pages. pub fn pages<'data>(&self, data: &'data [u8]) -> Result<&'data [PageEntry]> { data.read_slice_at::(self.pages_offset().into(), self.pages_len() as usize) .ok_or(ReadError::Pages) } } impl RegularPage { pub fn parse(data: &[u8], page_offset: u64) -> Result<&Self> { data.read_at::(page_offset) .ok_or(ReadError::RegularPage) } pub fn functions_offset(&self) -> u16 { self.functions_offset.into() } pub fn functions_len(&self) -> u16 { self.functions_len.into() } pub fn functions<'data>( &self, data: &'data [u8], page_offset: u32, ) -> Result<&'data [RegularFunctionEntry]> { let relative_functions_offset = self.functions_offset(); let functions_len: usize = self.functions_len().into(); let functions_offset = page_offset as u64 + relative_functions_offset as u64; data.read_slice_at::(functions_offset, functions_len) .ok_or(ReadError::RegularPageFunctions) } } impl CompressedPage { pub fn parse(data: &[u8], page_offset: u64) -> Result<&Self> { data.read_at::(page_offset) .ok_or(ReadError::CompressedPage) } pub fn functions_offset(&self) -> u16 { self.functions_offset.into() } pub fn functions_len(&self) -> u16 { self.functions_len.into() } pub fn local_opcodes_offset(&self) -> u16 { self.local_opcodes_offset.into() } pub fn local_opcodes_len(&self) -> u16 { self.local_opcodes_len.into() } pub fn functions<'data>(&self, data: &'data [u8], page_offset: u32) -> Result<&'data [U32]> { let relative_functions_offset = self.functions_offset(); let functions_len: usize = self.functions_len().into(); let functions_offset = page_offset as u64 + relative_functions_offset as u64; data.read_slice_at::(functions_offset, functions_len) .ok_or(ReadError::CompressedPageFunctions) } /// Return the list of local opcodes. pub fn local_opcodes<'data>( &self, data: &'data [u8], page_offset: u32, ) -> Result<&'data [Opcode]> { let relative_local_opcodes_offset = self.local_opcodes_offset(); let local_opcodes_len: usize = self.local_opcodes_len().into(); let local_opcodes_offset = page_offset as u64 + relative_local_opcodes_offset as u64; data.read_slice_at::(local_opcodes_offset, local_opcodes_len) .ok_or(ReadError::LocalOpcodes) } } impl Opcode { pub fn opcode(&self) -> u32 { self.0.into() } } impl RegularFunctionEntry { pub fn address(&self) -> u32 { self.address.into() } pub fn opcode(&self) -> u32 { self.opcode.opcode() } } impl PageEntry { pub fn page_offset(&self) -> u32 { self.page_offset.into() } pub fn first_address(&self) -> u32 { self.first_address.into() } pub fn lsda_index_offset(&self) -> u32 { self.lsda_index_offset.into() } pub fn page_kind(&self, data: &[u8]) -> Result { let kind = *data .read_at::(self.page_offset().into()) .ok_or(ReadError::PageKind)?; Ok(kind.into()) } } impl Debug for PageEntry { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("PageEntry") .field("first_address", &HexNum(self.first_address())) .field("page_offset", &HexNum(self.page_offset())) .field("lsda_index_offset", &HexNum(self.lsda_index_offset())) .finish() } } macho-unwind-info-0.4.0/src/raw/mod.rs000064400000000000000000000002311046102023000156500ustar 00000000000000mod compressed_function; pub mod consts; mod format; mod impls; mod unaligned; pub use compressed_function::*; pub use format::*; pub use unaligned::*; macho-unwind-info-0.4.0/src/raw/unaligned.rs000064400000000000000000000021651046102023000170470ustar 00000000000000use std::fmt::Debug; use zerocopy_derive::{FromBytes, FromZeroes, Unaligned}; /// An unaligned little-endian `u32` value. #[derive( Unaligned, FromZeroes, FromBytes, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, )] #[repr(transparent)] pub struct U32([u8; 4]); impl From for U32 { fn from(n: u32) -> Self { U32(n.to_le_bytes()) } } impl From for u32 { fn from(n: U32) -> Self { u32::from_le_bytes(n.0) } } impl Debug for U32 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { u32::fmt(&(*self).into(), f) } } /// An unaligned little-endian `u16` value. #[derive( Unaligned, FromZeroes, FromBytes, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, )] #[repr(transparent)] pub struct U16([u8; 2]); impl From for U16 { fn from(n: u16) -> Self { U16(n.to_le_bytes()) } } impl From for u16 { fn from(n: U16) -> Self { u16::from_le_bytes(n.0) } } impl Debug for U16 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { u16::fmt(&(*self).into(), f) } } macho-unwind-info-0.4.0/src/reader.rs000064400000000000000000000016461046102023000155550ustar 00000000000000use zerocopy::{FromBytes, Ref, Unaligned}; pub trait Reader { fn read_at(&self, offset: u64) -> Option<&T>; fn read_slice_at(&self, offset: u64, len: usize) -> Option<&[T]>; } impl Reader for [u8] { fn read_at(&self, offset: u64) -> Option<&T> { let offset: usize = offset.try_into().ok()?; let end: usize = offset.checked_add(core::mem::size_of::())?; let lv = Ref::<&[u8], T>::new_unaligned(self.get(offset..end)?)?; Some(lv.into_ref()) } fn read_slice_at(&self, offset: u64, len: usize) -> Option<&[T]> { let offset: usize = offset.try_into().ok()?; let end: usize = offset.checked_add(core::mem::size_of::().checked_mul(len)?)?; let lv = Ref::<&[u8], [T]>::new_slice_unaligned(self.get(offset..end)?)?; Some(lv.into_slice()) } }