framehop-0.13.0/.cargo_vcs_info.json0000644000000001360000000000100127160ustar { "git": { "sha1": "0f42d4bd0565f5977fc30cfcf4a9854e2d40a2b6" }, "path_in_vcs": "" }framehop-0.13.0/.gitignore000064400000000000000000000000531046102023000134740ustar 00000000000000/target /big-fixtures Cargo.lock .DS_Store framehop-0.13.0/Cargo.toml0000644000000032660000000000100107230ustar # 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 = "framehop" version = "0.13.0" authors = ["Markus Stange "] exclude = [ "/.github", "/.vscode", "/tests", "/fixtures", "/big-fixtures", ] description = "Stack frame unwinding support for various formats" documentation = "https://docs.rs/framehop/" readme = "Readme.md" keywords = [ "unwind", "stackwalk", "profiling", "debug", ] categories = ["development-tools::debugging"] license = "MIT/Apache-2.0" repository = "https://github.com/mstange/framehop/" [profile.release] debug = 2 [dependencies.arrayvec] version = "0.7.4" default-features = false [dependencies.cfg-if] version = "1.0.0" [dependencies.fallible-iterator] version = "0.3.0" [dependencies.gimli] version = "0.31" features = ["read"] default-features = false [dependencies.macho-unwind-info] version = "0.4.0" optional = true [dependencies.pe-unwind-info] version = "0.2.1" optional = true [dev-dependencies.flate2] version = "1.0.28" [dev-dependencies.itertools] version = "0.13" [dev-dependencies.object] version = "0.36" [features] default = [ "std", "macho", "pe", ] macho = ["macho-unwind-info"] pe = ["pe-unwind-info"] std = [ "arrayvec/std", "gimli/std", ] framehop-0.13.0/Cargo.toml.orig000064400000000000000000000020371046102023000143770ustar 00000000000000[package] name = "framehop" version = "0.13.0" edition = "2021" authors = ["Markus Stange "] categories = ["development-tools::debugging"] description = "Stack frame unwinding support for various formats" keywords = ["unwind", "stackwalk", "profiling", "debug"] license = "MIT/Apache-2.0" readme = "Readme.md" documentation = "https://docs.rs/framehop/" repository = "https://github.com/mstange/framehop/" exclude = ["/.github", "/.vscode", "/tests", "/fixtures", "/big-fixtures"] [dependencies] gimli = { version = "0.31", default-features = false, features = ["read"] } macho-unwind-info = { version = "0.4.0", optional = true } pe-unwind-info = { version = "0.2.1", optional = true } fallible-iterator = "0.3.0" arrayvec = { version = "0.7.4", default-features = false } cfg-if = "1.0.0" [features] default = ["std", "macho", "pe"] macho = ["macho-unwind-info"] pe = ["pe-unwind-info"] std = ["arrayvec/std", "gimli/std"] [dev-dependencies] object = "0.36" flate2 = "1.0.28" itertools = "0.13" [profile.release] debug = true framehop-0.13.0/LICENSE-APACHE000064400000000000000000000251371046102023000134420ustar 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. framehop-0.13.0/LICENSE-MIT000064400000000000000000000020701046102023000131410ustar 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. framehop-0.13.0/Readme.md000064400000000000000000000244021046102023000132270ustar 00000000000000[![crates.io page](https://img.shields.io/crates/v/framehop.svg)](https://crates.io/crates/framehop) [![docs.rs page](https://docs.rs/framehop/badge.svg)](https://docs.rs/framehop/) # framehop Framehop is a stack frame unwinder written in 100% Rust. It produces high quality stacks at high speed, on multiple platforms and architectures, without an expensive pre-processing step for unwind information. This makes it suitable for sampling profilers. It currently supports unwinding x86_64 and aarch64, with unwind information formats commonly used on Windows, macOS, Linux and Android. You give framehop register values, stack memory and unwind data, and framehop produces a list of return addresses. Framehop can be used in the following scenarios: - Live unwinding of a remote process. This is how [`samply`](https://github.com/mstange/samply/) uses it. - Offline unwinding from saved registers and stack bytes, even on a different machine, a different OS, or a different CPU architecture. - Live unwinding inside the same process. This is currently unproven, but should work as long as you can do heap allocation before sampling, in order to allocate a cache and to update the list of modules. The actual unwinding does not require any heap allocation and should work even inside a signal handler, as long as you use `MustNotAllocateDuringUnwind`. As a user of framehop, your responsibilities are the following: - You need to enumerate the modules (libraries) that are loaded in the sampled process ahead of time, or ideally maintain a live list which is updated whenever modules are loaded / unloaded. - You need to provide address ranges and unwind section data for those modules. - When sampling, you provide the register values and a callback to read arbitrary stack memory without segfaulting. - On aarch64, picking the right bitmask to strip pointer authentication bits from return addresses is up to you. - You will need to do symbol resolution yourself, if you want function names. Framehop only produces addresses, it does not do any symbolication. In turn, framehop solves the following problems: - It parses a number of different unwind information formats. At the moment, it supports the following: - Apple's Compact Unwinding Format, in `__unwind_info` (macOS) - DWARF CFI in `.eh_frame` (using `.eh_frame_hdr` as an index, if available) - DWARF CFI in `.debug_frame` - PE unwind info in `.pdata`, `.rdata` and `.xdata` (for Windows x86_64) - It supports correct unwinding even when the program is interrupted inside a function prologue or epilogue. On macOS, it has to analyze assembly instructions in order to do this. - On x86_64 and aarch64, it falls back to frame pointer unwinding if it cannot find unwind information for an address. - It caches the unwind rule for each address in a fixed-size cache, so that repeated unwinding from the same address is even faster. - It generates binary search indexes for unwind information formats which don't have them. Specifically, for `.debug_frame` and for `.eh_frame` without `.eh_frame_hdr`. - It does a reasonable job of detecting the end of the stack, so that you can differentiate between properly terminated stacks and prematurely truncated stacks. Framehop is not suitable for debuggers or to implement exception handling. Debuggers usually need to recover all register values for every frame whereas framehop only cares about return addresses. And exception handling needs the ability to call destructors, which is also a non-goal for framehop. ## Speed Framehop is so fast that stack walking is a miniscule part of sampling in both scenarios where I've tried it. In [this samply example](https://share.firefox.dev/3s6mQKl) of profiling a single-threaded Rust application, walking the stack takes a quarter of the time it take to query macOS for the thread's register values. In [another samply example](https://share.firefox.dev/3ksWaPt) of profiling a Firefox build without frame pointers, the dwarf unwinding takes 4x as long as the querying of the register values, but is still overall cheaper than the cost of thread_suspend + thread_get_state + thread_resume. In [this example of processing a `perf.data` file](https://share.firefox.dev/3vSQOTb), the bottleneck is reading the bytes from disk, rather than stackwalking. [With a warm file cache](https://share.firefox.dev/3Kt6sK1), the cost of stack walking is still comparable to the cost of copying the bytes from the file cache, and most of the stack walking time is spent reading return addresses from the stack bytes. Framehop achieves this speed in the following ways: 1. It only recovers registers which are needed for computing return addresses. On x86_64 that's `rip`, `rsp` and `rbp`, and on aarch64 that's `lr`, `sp` and `fp`. All other registers are not needed - in theory they could be used as inputs to DWARF CFI expressions, but in practice they are not. 2. It uses zero-copy parsing wherever possible. For example, the bytes in `__unwind_info` are only accessed during unwinding, and the binary search happens right inside the original `__unwind_info` memory. For DWARF unwinding, framehop uses the excellent [`gimli` crate](https://github.com/gimli-rs/gimli/), which was written with performance in mind. 3. It uses binary search to find the correct unwind rule in all supported unwind information formats. For formats without an built-in index, it creates an index when the module is added. 4. It caches unwind rules based on address. In practice, the 509-slot cache achieves a hit rate of around 80% on complicated code like Firefox (with the cache being shared across all Firefox processes). When profiling simpler applications, the hit rate is likely much higher. Furthermore, adding a module is fast too because framehop only does minimal up-front parsing and processing - really, the only thing it does is to create the index of FDE offsets for `.eh_frame` / `.debug_frame`. ## Current State and Roadmap Framehop is still a work in progress. Its API is subject to change. The API churn probably won't quieten down at least until we have one or two 32 bit architectures implemented. That said, framehop works remarkably well on the supported platforms, and is definitely worth a try if you can stomach the frequent API breakages. Please file issues if you run into any trouble or have suggestions. Eventually I'd like to use framehop as a replacement for Lul in the Gecko profiler (Firefox's built-in profiler). For that we'll also want to add x86 support (for 32 bit Windows and Linux) and EHABI / EXIDX support (for 32 bit ARM Android). ## Example ```rust use framehop::aarch64::{CacheAarch64, UnwindRegsAarch64, UnwinderAarch64}; use framehop::{ExplicitModuleSectionInfo, FrameAddress, Module}; let mut cache = CacheAarch64::<_>::new(); let mut unwinder = UnwinderAarch64::new(); let module = Module::new( "mybinary".to_string(), 0x1003fc000..0x100634000, 0x1003fc000, ExplicitModuleSectionInfo { base_svma: 0x100000000, text_svma: Some(0x100000b64..0x1001d2d18), text: Some(vec![/* __text */]), stubs_svma: Some(0x1001d2d18..0x1001d309c), stub_helper_svma: Some(0x1001d309c..0x1001d3438), got_svma: Some(0x100238000..0x100238010), unwind_info: Some(vec![/* __unwind_info */]), eh_frame_svma: Some(0x100237f80..0x100237ffc), eh_frame: Some(vec![/* __eh_frame */]), text_segment_svma: Some(0x1003fc000..0x100634000), text_segment: Some(vec![/* __TEXT */]), ..Default::default() }, ); unwinder.add_module(module); let pc = 0x1003fc000 + 0x1292c0; let lr = 0x1003fc000 + 0xe4830; let sp = 0x10; let fp = 0x20; let stack = [ 1, 2, 3, 4, 0x40, 0x1003fc000 + 0x100dc4, 5, 6, 0x70, 0x1003fc000 + 0x12ca28, 7, 8, 9, 10, 0x0, 0x0, ]; let mut read_stack = |addr| stack.get((addr / 8) as usize).cloned().ok_or(()); use framehop::Unwinder; let mut iter = unwinder.iter_frames( pc, UnwindRegsAarch64::new(lr, sp, fp), &mut cache, &mut read_stack, ); let mut frames = Vec::new(); while let Ok(Some(frame)) = iter.next() { frames.push(frame); } assert_eq!( frames, vec![ FrameAddress::from_instruction_pointer(0x1003fc000 + 0x1292c0), FrameAddress::from_return_address(0x1003fc000 + 0x100dc4).unwrap(), FrameAddress::from_return_address(0x1003fc000 + 0x12ca28).unwrap() ] ); ``` ## Recommended Reading and Tools Here's a list of articles I found useful during development: - [Reliable and Fast DWARF-Based Stack Unwinding](https://hal.inria.fr/hal-02297690/document), also available [as a presentation](https://deepspec.org/events/dsw18/zappa-nardelli-deepspec18.pdf). This is **the** unwinding reference document. If want to read just one thing, read this. This article explains the background super clearly, and is very approachable. It shows how assembly and unwind information correspond to each other and has lots of examples that are easy to understand. - [How fast can CFI/EXIDX-based stack unwinding be?](https://blog.mozilla.org/jseward/2013/08/29/how-fast-can-cfiexidx-based-stack-unwinding-be/), by Julian Seward - [Unwinding a Stack by Hand with Frame Pointers and ORC](https://blogs.oracle.com/linux/post/unwinding-stack-frame-pointers-and-orc), by Stephen Brennan - [Aarch64 DWARF register names](https://github.com/ARM-software/abi-aa/blob/main/aadwarf64/aadwarf64.rst#dwarf-register-names) I used these tools very frequently: - [Hopper Disassembler](https://www.hopperapp.com/), to look at assembly code. - `llvm-dwarfdump --eh-frame mylib.so` to display DWARF unwind information. - `llvm-objdump --section-headers mylib.so` to display section information. - `unwindinfodump mylib.dylib` to display compact unwind information. (Install using `cargo install --examples macho-unwind-info`, see [macho-unwind-info](https://github.com/mstange/macho-unwind-info/blob/main/examples/unwindinfodump.rs).) ## 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. framehop-0.13.0/src/aarch64/arch.rs000064400000000000000000000004271046102023000150130ustar 00000000000000use super::unwind_rule::UnwindRuleAarch64; use super::unwindregs::UnwindRegsAarch64; use crate::arch::Arch; /// The Aarch64 CPU architecture. pub struct ArchAarch64; impl Arch for ArchAarch64 { type UnwindRule = UnwindRuleAarch64; type UnwindRegs = UnwindRegsAarch64; } framehop-0.13.0/src/aarch64/cache.rs000064400000000000000000000013761046102023000151450ustar 00000000000000use super::unwind_rule::*; use crate::cache::*; /// The unwinder cache type for [`UnwinderAarch64`](super::UnwinderAarch64). pub struct CacheAarch64( pub Cache, ); impl CacheAarch64 { /// Create a new cache. pub fn new() -> Self { Self(Cache::new()) } } impl CacheAarch64

{ /// Create a new cache. pub fn new_in() -> Self { Self(Cache::new()) } /// Returns a snapshot of the cache usage statistics. pub fn stats(&self) -> CacheStats { self.0.rule_cache.stats() } } impl Default for CacheAarch64

{ fn default() -> Self { Self::new_in() } } framehop-0.13.0/src/aarch64/dwarf.rs000064400000000000000000000212701046102023000152000ustar 00000000000000use gimli::{ AArch64, CfaRule, Encoding, EvaluationStorage, Reader, ReaderOffset, Register, RegisterRule, UnwindContextStorage, UnwindSection, UnwindTableRow, }; use super::{arch::ArchAarch64, unwind_rule::UnwindRuleAarch64, unwindregs::UnwindRegsAarch64}; use crate::unwind_result::UnwindResult; use crate::dwarf::{ eval_cfa_rule, eval_register_rule, ConversionError, DwarfUnwindRegs, DwarfUnwinderError, DwarfUnwinding, }; impl DwarfUnwindRegs for UnwindRegsAarch64 { fn get(&self, register: Register) -> Option { match register { AArch64::SP => Some(self.sp()), AArch64::X29 => Some(self.fp()), AArch64::X30 => Some(self.lr()), _ => None, } } } impl DwarfUnwinding for ArchAarch64 { fn unwind_frame( section: &impl UnwindSection, unwind_info: &UnwindTableRow, encoding: Encoding, regs: &mut Self::UnwindRegs, is_first_frame: bool, read_stack: &mut F, ) -> Result, DwarfUnwinderError> where F: FnMut(u64) -> Result, R: Reader, UCS: UnwindContextStorage, ES: EvaluationStorage, { let cfa_rule = unwind_info.cfa(); let fp_rule = unwind_info.register(AArch64::X29); let lr_rule = unwind_info.register(AArch64::X30); match translate_into_unwind_rule(cfa_rule, &fp_rule, &lr_rule) { Ok(unwind_rule) => return Ok(UnwindResult::ExecRule(unwind_rule)), Err(_err) => { // Could not translate into a cacheable unwind rule. Fall back to the generic path. // eprintln!("Unwind rule translation failed: {:?}", err); } } let cfa = eval_cfa_rule::(section, cfa_rule, encoding, regs) .ok_or(DwarfUnwinderError::CouldNotRecoverCfa)?; let lr = regs.lr(); let fp = regs.fp(); let sp = regs.sp(); let (fp, lr) = if !is_first_frame { if cfa <= sp { return Err(DwarfUnwinderError::StackPointerMovedBackwards); } let fp = eval_register_rule::( section, fp_rule, cfa, encoding, fp, regs, read_stack, ) .ok_or(DwarfUnwinderError::CouldNotRecoverFramePointer)?; let lr = eval_register_rule::( section, lr_rule, cfa, encoding, lr, regs, read_stack, ) .ok_or(DwarfUnwinderError::CouldNotRecoverReturnAddress)?; (fp, lr) } else { // For the first frame, be more lenient when encountering errors. // TODO: Find evidence of what this gives us. I think on macOS the prologue often has Unknown register rules // and we only encounter prologues for the first frame. let fp = eval_register_rule::( section, fp_rule, cfa, encoding, fp, regs, read_stack, ) .unwrap_or(fp); let lr = eval_register_rule::( section, lr_rule, cfa, encoding, lr, regs, read_stack, ) .unwrap_or(lr); (fp, lr) }; regs.set_fp(fp); regs.set_sp(cfa); regs.set_lr(lr); Ok(UnwindResult::Uncacheable(lr)) } fn rule_if_uncovered_by_fde() -> Self::UnwindRule { UnwindRuleAarch64::NoOpIfFirstFrameOtherwiseFp } } fn register_rule_to_cfa_offset( rule: &RegisterRule, ) -> Result, ConversionError> { match *rule { RegisterRule::Undefined | RegisterRule::SameValue => Ok(None), RegisterRule::Offset(offset) => Ok(Some(offset)), _ => Err(ConversionError::RegisterNotStoredRelativeToCfa), } } fn translate_into_unwind_rule( cfa_rule: &CfaRule, fp_rule: &RegisterRule, lr_rule: &RegisterRule, ) -> Result { match cfa_rule { CfaRule::RegisterAndOffset { register, offset } => match *register { AArch64::SP => { let sp_offset_by_16 = u16::try_from(offset / 16).map_err(|_| ConversionError::SpOffsetDoesNotFit)?; let lr_cfa_offset = register_rule_to_cfa_offset(lr_rule)?; let fp_cfa_offset = register_rule_to_cfa_offset(fp_rule)?; match (lr_cfa_offset, fp_cfa_offset) { (None, Some(_)) => Err(ConversionError::RestoringFpButNotLr), (None, None) => { if let RegisterRule::Undefined = lr_rule { // If the return address is undefined, this could have two reasons: // - The column for the return address may have been manually set to "undefined" // using DW_CFA_undefined. This usually means that the function never returns // and can be treated as the root of the stack. // - The column for the return may have been omitted from the DWARF CFI table. // Per spec (at least as of DWARF >= 3), this means that it should be treated // as undefined. But it seems that compilers often do this when they really mean // "same value". // Gimli follows DWARF 3 and does not differentiate between "omitted" and "undefined". Ok( UnwindRuleAarch64::OffsetSpIfFirstFrameOtherwiseStackEndsHere { sp_offset_by_16, }, ) } else { Ok(UnwindRuleAarch64::OffsetSp { sp_offset_by_16 }) } } (Some(lr_cfa_offset), None) => { let lr_storage_offset_from_sp_by_8 = i16::try_from((offset + lr_cfa_offset) / 8) .map_err(|_| ConversionError::LrStorageOffsetDoesNotFit)?; Ok(UnwindRuleAarch64::OffsetSpAndRestoreLr { sp_offset_by_16, lr_storage_offset_from_sp_by_8, }) } (Some(lr_cfa_offset), Some(fp_cfa_offset)) => { let lr_storage_offset_from_sp_by_8 = i16::try_from((offset + lr_cfa_offset) / 8) .map_err(|_| ConversionError::LrStorageOffsetDoesNotFit)?; let fp_storage_offset_from_sp_by_8 = i16::try_from((offset + fp_cfa_offset) / 8) .map_err(|_| ConversionError::FpStorageOffsetDoesNotFit)?; Ok(UnwindRuleAarch64::OffsetSpAndRestoreFpAndLr { sp_offset_by_16, fp_storage_offset_from_sp_by_8, lr_storage_offset_from_sp_by_8, }) } } } AArch64::X29 => { let lr_cfa_offset = register_rule_to_cfa_offset(lr_rule)? .ok_or(ConversionError::FramePointerRuleDoesNotRestoreLr)?; let fp_cfa_offset = register_rule_to_cfa_offset(fp_rule)? .ok_or(ConversionError::FramePointerRuleDoesNotRestoreFp)?; if *offset == 16 && fp_cfa_offset == -16 && lr_cfa_offset == -8 { Ok(UnwindRuleAarch64::UseFramePointer) } else { let sp_offset_from_fp_by_8 = u16::try_from(offset / 8) .map_err(|_| ConversionError::SpOffsetFromFpDoesNotFit)?; let lr_storage_offset_from_fp_by_8 = i16::try_from((offset + lr_cfa_offset) / 8) .map_err(|_| ConversionError::LrStorageOffsetDoesNotFit)?; let fp_storage_offset_from_fp_by_8 = i16::try_from((offset + fp_cfa_offset) / 8) .map_err(|_| ConversionError::FpStorageOffsetDoesNotFit)?; Ok(UnwindRuleAarch64::UseFramepointerWithOffsets { sp_offset_from_fp_by_8, fp_storage_offset_from_fp_by_8, lr_storage_offset_from_fp_by_8, }) } } _ => Err(ConversionError::CfaIsOffsetFromUnknownRegister), }, CfaRule::Expression(_) => Err(ConversionError::CfaIsExpression), } } framehop-0.13.0/src/aarch64/instruction_analysis/epilogue.rs000064400000000000000000000703011046102023000221710ustar 00000000000000use super::super::unwind_rule::UnwindRuleAarch64; struct EpilogueDetectorAarch64 { sp_offset: i32, fp_offset_from_initial_sp: Option, lr_offset_from_initial_sp: Option, } enum EpilogueStepResult { NeedMore, FoundBodyInstruction(UnexpectedInstructionType), FoundReturn, FoundTailCall, CouldBeAuthTailCall, } #[derive(Clone, Debug, PartialEq, Eq)] enum EpilogueResult { ProbablyStillInBody(UnexpectedInstructionType), ReachedFunctionEndWithoutReturn, FoundReturnOrTailCall { sp_offset: i32, fp_offset_from_initial_sp: Option, lr_offset_from_initial_sp: Option, }, } #[derive(Clone, Debug, PartialEq, Eq)] enum UnexpectedInstructionType { LoadOfWrongSize, LoadReferenceRegisterNotSp, AddSubNotOperatingOnSp, AutibspNotFollowedByExpectedTailCall, BranchWithUnadjustedStackPointer, Unknown, } #[derive(Clone, Debug, PartialEq, Eq)] enum EpilogueInstructionType { NotExpectedInEpilogue, CouldBeTailCall { /// If auth tail call, the offset in bytes where the autibsp would be. /// If regular tail call, we just check if the previous instruction /// adjusts the stack pointer. offset_of_expected_autibsp: u8, }, CouldBePartOfAuthTailCall { /// In bytes offset_of_expected_autibsp: u8, }, VeryLikelyPartOfEpilogue, } impl EpilogueDetectorAarch64 { pub fn new() -> Self { Self { sp_offset: 0, fp_offset_from_initial_sp: None, lr_offset_from_initial_sp: None, } } pub fn analyze_slice(&mut self, function_bytes: &[u8], pc_offset: usize) -> EpilogueResult { let mut bytes = &function_bytes[pc_offset..]; if bytes.len() < 4 { return EpilogueResult::ReachedFunctionEndWithoutReturn; } let mut word = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]); bytes = &bytes[4..]; match Self::analyze_instruction(word) { EpilogueInstructionType::NotExpectedInEpilogue => { return EpilogueResult::ProbablyStillInBody(UnexpectedInstructionType::Unknown) } EpilogueInstructionType::CouldBeTailCall { offset_of_expected_autibsp, } => { if pc_offset >= offset_of_expected_autibsp as usize { let auth_tail_call_bytes = &function_bytes[pc_offset - offset_of_expected_autibsp as usize..]; if auth_tail_call_bytes[0..4] == [0xff, 0x23, 0x03, 0xd5] && Self::is_auth_tail_call(&auth_tail_call_bytes[4..]) { return EpilogueResult::FoundReturnOrTailCall { sp_offset: 0, fp_offset_from_initial_sp: None, lr_offset_from_initial_sp: None, }; } } if pc_offset >= 4 { let prev_b = &function_bytes[pc_offset - 4..pc_offset]; let prev_word = u32::from_le_bytes([prev_b[0], prev_b[1], prev_b[2], prev_b[3]]); if Self::instruction_adjusts_stack_pointer(prev_word) { return EpilogueResult::FoundReturnOrTailCall { sp_offset: 0, fp_offset_from_initial_sp: None, lr_offset_from_initial_sp: None, }; } } return EpilogueResult::ProbablyStillInBody(UnexpectedInstructionType::Unknown); } EpilogueInstructionType::CouldBePartOfAuthTailCall { offset_of_expected_autibsp, } => { if pc_offset >= offset_of_expected_autibsp as usize { let auth_tail_call_bytes = &function_bytes[pc_offset - offset_of_expected_autibsp as usize..]; if auth_tail_call_bytes[0..4] == [0xff, 0x23, 0x03, 0xd5] && Self::is_auth_tail_call(&auth_tail_call_bytes[4..]) { return EpilogueResult::FoundReturnOrTailCall { sp_offset: 0, fp_offset_from_initial_sp: None, lr_offset_from_initial_sp: None, }; } } return EpilogueResult::ProbablyStillInBody(UnexpectedInstructionType::Unknown); } EpilogueInstructionType::VeryLikelyPartOfEpilogue => {} } loop { match self.step_instruction(word) { EpilogueStepResult::NeedMore => { if bytes.len() < 4 { return EpilogueResult::ReachedFunctionEndWithoutReturn; } word = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]); bytes = &bytes[4..]; continue; } EpilogueStepResult::FoundBodyInstruction(uit) => { return EpilogueResult::ProbablyStillInBody(uit); } EpilogueStepResult::FoundReturn | EpilogueStepResult::FoundTailCall => {} EpilogueStepResult::CouldBeAuthTailCall => { if !Self::is_auth_tail_call(bytes) { return EpilogueResult::ProbablyStillInBody( UnexpectedInstructionType::AutibspNotFollowedByExpectedTailCall, ); } } } return EpilogueResult::FoundReturnOrTailCall { sp_offset: self.sp_offset, fp_offset_from_initial_sp: self.fp_offset_from_initial_sp, lr_offset_from_initial_sp: self.lr_offset_from_initial_sp, }; } } fn instruction_adjusts_stack_pointer(word: u32) -> bool { // Detect load from sp-relative offset with writeback. if (word >> 22) & 0b1011111011 == 0b1010100011 && (word >> 5) & 0b11111 == 31 { return true; } // Detect sub sp, sp, 0xXXXX if (word >> 23) & 0b111111111 == 0b100100010 && word & 0b11111 == 31 && (word >> 5) & 0b11111 == 31 { return true; } false } fn is_auth_tail_call(bytes_after_autibsp: &[u8]) -> bool { // libsystem_malloc.dylib contains over a hundred of these. // At the end of the function, after restoring the registers from the stack, // there's an autibsp instruction, followed by some check (not sure what it // does), and then a tail call. These instructions should all be counted as // part of the epilogue; returning at this point is just "follow lr" instead // of "use the frame pointer". // // 180139058 ff 23 03 d5 autibsp // // 18013905c d0 07 1e ca eor x16, lr, lr, lsl #1 // 180139060 50 00 f0 b6 tbz x16, 0x3e, $+0x8 // 180139064 20 8e 38 d4 brk #0xc471 ; "breakpoint trap" // // and then a tail call, of one of these forms: // // 180139068 13 00 00 14 b some_outside_function // // 18013a364 f0 36 88 d2 mov x16, #0xXXXX // 18013a368 70 08 1f d7 braa xX, x16 // if bytes_after_autibsp.len() < 16 { return false; } let eor_tbz_brk = &bytes_after_autibsp[..12]; if eor_tbz_brk != [ 0xd0, 0x07, 0x1e, 0xca, 0x50, 0x00, 0xf0, 0xb6, 0x20, 0x8e, 0x38, 0xd4, ] { return false; } let first_tail_call_instruction_opcode = u32::from_le_bytes([ bytes_after_autibsp[12], bytes_after_autibsp[13], bytes_after_autibsp[14], bytes_after_autibsp[15], ]); let bits_26_to_32 = first_tail_call_instruction_opcode >> 26; if bits_26_to_32 == 0b000101 { // This is a `b` instruction. We've found the tail call. return true; } // If we get here, it's either not a recognized instruction sequence, // or the tail call is of the form `mov x16, #0xXXXX`, `braa xX, x16`. if bytes_after_autibsp.len() < 20 { return false; } let bits_23_to_32 = first_tail_call_instruction_opcode >> 23; let is_64_mov = (bits_23_to_32 & 0b111000111) == 0b110000101; let result_reg = first_tail_call_instruction_opcode & 0b11111; if !is_64_mov || result_reg != 16 { return false; } let braa_opcode = u32::from_le_bytes([ bytes_after_autibsp[16], bytes_after_autibsp[17], bytes_after_autibsp[18], bytes_after_autibsp[19], ]); (braa_opcode & 0xff_ff_fc_00) == 0xd7_1f_08_00 && (braa_opcode & 0b11111) == 16 } pub fn analyze_instruction(word: u32) -> EpilogueInstructionType { // Detect ret and retab if word == 0xd65f03c0 || word == 0xd65f0fff { return EpilogueInstructionType::VeryLikelyPartOfEpilogue; } // Detect autibsp if word == 0xd50323ff { return EpilogueInstructionType::CouldBePartOfAuthTailCall { offset_of_expected_autibsp: 0, }; } // Detect `eor x16, lr, lr, lsl #1` if word == 0xca1e07d0 { return EpilogueInstructionType::CouldBePartOfAuthTailCall { offset_of_expected_autibsp: 4, }; } // Detect `tbz x16, 0x3e, $+0x8` if word == 0xb6f00050 { return EpilogueInstructionType::CouldBePartOfAuthTailCall { offset_of_expected_autibsp: 8, }; } // Detect `brk #0xc471` if word == 0xd4388e20 { return EpilogueInstructionType::CouldBePartOfAuthTailCall { offset_of_expected_autibsp: 12, }; } // Detect `b` and `br xX` if (word >> 26) == 0b000101 || word & 0xff_ff_fc_1f == 0xd6_1f_00_00 { // This could be a branch with a target inside this function, or // a tail call outside of this function. return EpilogueInstructionType::CouldBeTailCall { offset_of_expected_autibsp: 16, }; } // Detect `mov x16, #0xXXXX` if (word >> 23) & 0b111000111 == 0b110000101 && word & 0b11111 == 16 { return EpilogueInstructionType::CouldBePartOfAuthTailCall { offset_of_expected_autibsp: 16, }; } // Detect `braa xX, x16` if word & 0xff_ff_fc_00 == 0xd7_1f_08_00 && word & 0b11111 == 16 { return EpilogueInstructionType::CouldBePartOfAuthTailCall { offset_of_expected_autibsp: 20, }; } if (word >> 22) & 0b1011111001 == 0b1010100001 { // Section C3.3, Loads and stores. // but only loads that are commonly seen in prologues / epilogues (bits 29 and 31 are set) let writeback_bits = (word >> 23) & 0b11; if writeback_bits == 0b00 { // Not 64-bit load. return EpilogueInstructionType::NotExpectedInEpilogue; } let reference_reg = ((word >> 5) & 0b11111) as u16; if reference_reg != 31 { return EpilogueInstructionType::NotExpectedInEpilogue; } return EpilogueInstructionType::VeryLikelyPartOfEpilogue; } if (word >> 23) & 0b111111111 == 0b100100010 { // Section C3.4, Data processing - immediate // unsigned add imm, size class X (8 bytes) let result_reg = (word & 0b11111) as u16; let input_reg = ((word >> 5) & 0b11111) as u16; if result_reg != 31 || input_reg != 31 { return EpilogueInstructionType::NotExpectedInEpilogue; } return EpilogueInstructionType::VeryLikelyPartOfEpilogue; } EpilogueInstructionType::NotExpectedInEpilogue } pub fn step_instruction(&mut self, word: u32) -> EpilogueStepResult { // Detect ret and retab if word == 0xd65f03c0 || word == 0xd65f0fff { return EpilogueStepResult::FoundReturn; } // Detect autibsp if word == 0xd50323ff { return EpilogueStepResult::CouldBeAuthTailCall; } // Detect b if (word >> 26) == 0b000101 { // This could be a branch with a target inside this function, or // a tail call outside of this function. // Let's use the following heuristic: If this instruction is followed // by valid epilogue instructions which adjusted the stack pointer, then // we treat it as a tail call. if self.sp_offset != 0 { return EpilogueStepResult::FoundTailCall; } return EpilogueStepResult::FoundBodyInstruction( UnexpectedInstructionType::BranchWithUnadjustedStackPointer, ); } if (word >> 22) & 0b1011111001 == 0b1010100001 { // Section C3.3, Loads and stores. // but only those that are commonly seen in prologues / epilogues (bits 29 and 31 are set) let writeback_bits = (word >> 23) & 0b11; if writeback_bits == 0b00 { // Not 64-bit load/store. return EpilogueStepResult::FoundBodyInstruction( UnexpectedInstructionType::LoadOfWrongSize, ); } let reference_reg = ((word >> 5) & 0b11111) as u16; if reference_reg != 31 { return EpilogueStepResult::FoundBodyInstruction( UnexpectedInstructionType::LoadReferenceRegisterNotSp, ); } let is_preindexed_writeback = writeback_bits == 0b11; // TODO: are there preindexed loads? What do they mean? let is_postindexed_writeback = writeback_bits == 0b01; let imm7 = (((((word >> 15) & 0b1111111) as i16) << 9) >> 6) as i32; let reg_loc = if is_postindexed_writeback { self.sp_offset } else { self.sp_offset + imm7 }; let pair_reg_1 = (word & 0b11111) as u16; if pair_reg_1 == 29 { self.fp_offset_from_initial_sp = Some(reg_loc); } else if pair_reg_1 == 30 { self.lr_offset_from_initial_sp = Some(reg_loc); } let pair_reg_2 = ((word >> 10) & 0b11111) as u16; if pair_reg_2 == 29 { self.fp_offset_from_initial_sp = Some(reg_loc + 8); } else if pair_reg_2 == 30 { self.lr_offset_from_initial_sp = Some(reg_loc + 8); } if is_preindexed_writeback || is_postindexed_writeback { self.sp_offset += imm7; } return EpilogueStepResult::NeedMore; } if (word >> 23) & 0b111111111 == 0b100100010 { // Section C3.4, Data processing - immediate // unsigned add imm, size class X (8 bytes) let result_reg = (word & 0b11111) as u16; let input_reg = ((word >> 5) & 0b11111) as u16; if result_reg != 31 || input_reg != 31 { return EpilogueStepResult::FoundBodyInstruction( UnexpectedInstructionType::AddSubNotOperatingOnSp, ); } let mut imm12 = ((word >> 10) & 0b111111111111) as i32; let shift_immediate_by_12 = ((word >> 22) & 0b1) == 0b1; if shift_immediate_by_12 { imm12 <<= 12 } self.sp_offset += imm12; return EpilogueStepResult::NeedMore; } EpilogueStepResult::FoundBodyInstruction(UnexpectedInstructionType::Unknown) } } pub fn unwind_rule_from_detected_epilogue( bytes: &[u8], pc_offset: usize, ) -> Option { let mut detector = EpilogueDetectorAarch64::new(); match detector.analyze_slice(bytes, pc_offset) { EpilogueResult::ProbablyStillInBody(_) | EpilogueResult::ReachedFunctionEndWithoutReturn => None, EpilogueResult::FoundReturnOrTailCall { sp_offset, fp_offset_from_initial_sp, lr_offset_from_initial_sp, } => { let sp_offset_by_16 = u16::try_from(sp_offset / 16).ok()?; let rule = match (fp_offset_from_initial_sp, lr_offset_from_initial_sp) { (None, None) if sp_offset_by_16 == 0 => UnwindRuleAarch64::NoOp, (None, None) => UnwindRuleAarch64::OffsetSp { sp_offset_by_16 }, (None, Some(lr_offset)) => UnwindRuleAarch64::OffsetSpAndRestoreLr { sp_offset_by_16, lr_storage_offset_from_sp_by_8: i16::try_from(lr_offset / 8).ok()?, }, (Some(_), None) => return None, (Some(fp_offset), Some(lr_offset)) => { UnwindRuleAarch64::OffsetSpAndRestoreFpAndLr { sp_offset_by_16, fp_storage_offset_from_sp_by_8: i16::try_from(fp_offset / 8).ok()?, lr_storage_offset_from_sp_by_8: i16::try_from(lr_offset / 8).ok()?, } } }; Some(rule) } } } #[cfg(test)] mod test { use super::*; #[test] fn test_epilogue_1() { // 1000e0d18 fd 7b 44 a9 ldp fp, lr, [sp, #0x40] // 1000e0d1c f4 4f 43 a9 ldp x20, x19, [sp, #0x30] // 1000e0d20 f6 57 42 a9 ldp x22, x21, [sp, #0x20] // 1000e0d24 ff 43 01 91 add sp, sp, #0x50 // 1000e0d28 c0 03 5f d6 ret let bytes = &[ 0xfd, 0x7b, 0x44, 0xa9, 0xf4, 0x4f, 0x43, 0xa9, 0xf6, 0x57, 0x42, 0xa9, 0xff, 0x43, 0x01, 0x91, 0xc0, 0x03, 0x5f, 0xd6, ]; assert_eq!( unwind_rule_from_detected_epilogue(bytes, 0), Some(UnwindRuleAarch64::OffsetSpAndRestoreFpAndLr { sp_offset_by_16: 5, fp_storage_offset_from_sp_by_8: 8, lr_storage_offset_from_sp_by_8: 9, }) ); assert_eq!( unwind_rule_from_detected_epilogue(bytes, 4), Some(UnwindRuleAarch64::OffsetSp { sp_offset_by_16: 5 }) ); assert_eq!( unwind_rule_from_detected_epilogue(bytes, 8), Some(UnwindRuleAarch64::OffsetSp { sp_offset_by_16: 5 }) ); assert_eq!( unwind_rule_from_detected_epilogue(bytes, 12), Some(UnwindRuleAarch64::OffsetSp { sp_offset_by_16: 5 }) ); assert_eq!( unwind_rule_from_detected_epilogue(bytes, 16), Some(UnwindRuleAarch64::NoOp) ); assert_eq!(unwind_rule_from_detected_epilogue(bytes, 20), None); } #[test] fn test_epilogue_with_retab() { // _malloc_zone_realloc epilogue // 18012466c e0 03 16 aa mov x0,x22 // 180124670 fd 7b 43 a9 ldp x29=>local_10,x30,[sp, #0x30] // 180124674 f4 4f 42 a9 ldp x20,x19,[sp, #local_20] // 180124678 f6 57 41 a9 ldp x22,x21,[sp, #local_30] // 18012467c f8 5f c4 a8 ldp x24,x23,[sp], #0x40 // 180124680 ff 0f 5f d6 retab // 180124684 a0 01 80 52 mov w0,#0xd // 180124688 20 60 a6 72 movk w0,#0x3301, LSL #16 let bytes = &[ 0xe0, 0x03, 0x16, 0xaa, 0xfd, 0x7b, 0x43, 0xa9, 0xf4, 0x4f, 0x42, 0xa9, 0xf6, 0x57, 0x41, 0xa9, 0xf8, 0x5f, 0xc4, 0xa8, 0xff, 0x0f, 0x5f, 0xd6, 0xa0, 0x01, 0x80, 0x52, 0x20, 0x60, 0xa6, 0x72, ]; assert_eq!(unwind_rule_from_detected_epilogue(bytes, 0), None); assert_eq!( unwind_rule_from_detected_epilogue(bytes, 4), Some(UnwindRuleAarch64::OffsetSpAndRestoreFpAndLr { sp_offset_by_16: 4, fp_storage_offset_from_sp_by_8: 6, lr_storage_offset_from_sp_by_8: 7 }) ); assert_eq!( unwind_rule_from_detected_epilogue(bytes, 8), Some(UnwindRuleAarch64::OffsetSp { sp_offset_by_16: 4 }) ); assert_eq!( unwind_rule_from_detected_epilogue(bytes, 12), Some(UnwindRuleAarch64::OffsetSp { sp_offset_by_16: 4 }) ); assert_eq!( unwind_rule_from_detected_epilogue(bytes, 16), Some(UnwindRuleAarch64::OffsetSp { sp_offset_by_16: 4 }) ); assert_eq!( unwind_rule_from_detected_epilogue(bytes, 20), Some(UnwindRuleAarch64::NoOp) ); assert_eq!(unwind_rule_from_detected_epilogue(bytes, 24), None); } #[test] fn test_epilogue_with_retab_2() { // _tiny_free_list_add_ptr: // ... // 18013e114 28 01 00 79 strh w8, [x9] // 18013e118 fd 7b c1 a8 ldp fp, lr, [sp], #0x10 // 18013e11c ff 0f 5f d6 retab // 18013e120 e2 03 08 aa mov x2, x8 // 18013e124 38 76 00 94 bl _free_list_checksum_botch // ... let bytes = &[ 0x28, 0x01, 0x00, 0x79, 0xfd, 0x7b, 0xc1, 0xa8, 0xff, 0x0f, 0x5f, 0xd6, 0xe2, 0x03, 0x08, 0xaa, 0x38, 0x76, 0x00, 0x94, ]; assert_eq!(unwind_rule_from_detected_epilogue(bytes, 0), None); assert_eq!( unwind_rule_from_detected_epilogue(bytes, 4), Some(UnwindRuleAarch64::OffsetSpAndRestoreFpAndLr { sp_offset_by_16: 1, fp_storage_offset_from_sp_by_8: 0, lr_storage_offset_from_sp_by_8: 1 }) ); assert_eq!( unwind_rule_from_detected_epilogue(bytes, 8), Some(UnwindRuleAarch64::NoOp) ); assert_eq!(unwind_rule_from_detected_epilogue(bytes, 12), None); assert_eq!(unwind_rule_from_detected_epilogue(bytes, 16), None); } #[test] fn test_epilogue_with_regular_tail_call() { // (in rustup) __ZN126_$LT$$LT$toml..value..Value$u20$as$u20$serde..de..Deserialize$GT$..deserialize..ValueVisitor$u20$as$u20$serde..de..Visitor$GT$9visit_map17h0afd4b269ef00eebE // ... // 1002566b4 fc 6f c6 a8 ldp x28, x27, [sp], #0x60 // 1002566b8 bc ba ff 17 b __ZN4core3ptr41drop_in_place$LT$toml..de..MapVisitor$GT$17hd4556de1a4edab42E // ... let bytes = &[0xfc, 0x6f, 0xc6, 0xa8, 0xbc, 0xba, 0xff, 0x17]; assert_eq!( unwind_rule_from_detected_epilogue(bytes, 0), Some(UnwindRuleAarch64::OffsetSp { sp_offset_by_16: 6 }) ); } // This test fails at the moment. #[test] fn test_epilogue_with_register_tail_call() { // This test requires lookbehind in the epilogue detection. // We want to detect the `br` as a tail call. We should do this // based on the fact that the previous instruction adjusted the // stack pointer. // // (in rustup) __ZN4core3fmt9Formatter3pad17h3f40041e7f99f180E // ... // 1000500bc fa 67 c5 a8 ldp x26, x25, [sp], #0x50 // 1000500c0 60 00 1f d6 br x3 // ... let bytes = &[0xfa, 0x67, 0xc5, 0xa8, 0x60, 0x00, 0x1f, 0xd6]; assert_eq!( unwind_rule_from_detected_epilogue(bytes, 4), Some(UnwindRuleAarch64::NoOp) ); } #[test] fn test_epilogue_with_auth_tail_call() { // _nanov2_free_definite_size // ... // 180139048 e1 03 13 aa mov x1, x19 // 18013904c fd 7b 42 a9 ldp fp, lr, [sp, #0x20] // 180139050 f4 4f 41 a9 ldp x20, x19, [sp, #0x10] // 180139054 f6 57 c3 a8 ldp x22, x21, [sp], #0x30 // 180139058 ff 23 03 d5 autibsp // 18013905c d0 07 1e ca eor x16, lr, lr, lsl #1 // 180139060 50 00 f0 b6 tbz x16, 0x3e, loc_180139068 // 180139064 20 8e 38 d4 brk #0xc471 // loc_180139068: // 180139068 13 00 00 14 b _nanov2_free_to_block // loc_18013906c: // 18013906c a0 16 78 f9 ldr x0, [x21, #0x7028] // 180139070 03 3c 40 f9 ldr x3, [x0, #0x78] // ... let bytes = &[ 0xe1, 0x03, 0x13, 0xaa, 0xfd, 0x7b, 0x42, 0xa9, 0xf4, 0x4f, 0x41, 0xa9, 0xf6, 0x57, 0xc3, 0xa8, 0xff, 0x23, 0x03, 0xd5, 0xd0, 0x07, 0x1e, 0xca, 0x50, 0x00, 0xf0, 0xb6, 0x20, 0x8e, 0x38, 0xd4, 0x13, 0x00, 0x00, 0x14, 0xa0, 0x16, 0x78, 0xf9, 0x03, 0x3c, 0x40, 0xf9, ]; assert_eq!(unwind_rule_from_detected_epilogue(bytes, 0), None); assert_eq!( unwind_rule_from_detected_epilogue(bytes, 4), Some(UnwindRuleAarch64::OffsetSpAndRestoreFpAndLr { sp_offset_by_16: 3, fp_storage_offset_from_sp_by_8: 4, lr_storage_offset_from_sp_by_8: 5 }) ); assert_eq!( unwind_rule_from_detected_epilogue(bytes, 8), Some(UnwindRuleAarch64::OffsetSp { sp_offset_by_16: 3 }) ); assert_eq!( unwind_rule_from_detected_epilogue(bytes, 12), Some(UnwindRuleAarch64::OffsetSp { sp_offset_by_16: 3 }) ); assert_eq!( unwind_rule_from_detected_epilogue(bytes, 16), Some(UnwindRuleAarch64::NoOp) ); assert_eq!( unwind_rule_from_detected_epilogue(bytes, 20), Some(UnwindRuleAarch64::NoOp) ); assert_eq!( unwind_rule_from_detected_epilogue(bytes, 24), Some(UnwindRuleAarch64::NoOp) ); assert_eq!( unwind_rule_from_detected_epilogue(bytes, 28), Some(UnwindRuleAarch64::NoOp) ); } #[test] fn test_epilogue_with_auth_tail_call_2() { // _malloc_zone_claimed_addres // ... // 1801457ac e1 03 13 aa mov x1, x19 // 1801457b0 fd 7b 41 a9 ldp fp, lr, [sp, #0x10] // 1801457b4 f4 4f c2 a8 ldp x20, x19, [sp], #0x20 // 1801457b8 ff 23 03 d5 autibsp // 1801457bc d0 07 1e ca eor x16, lr, lr, lsl #1 // 1801457c0 50 00 f0 b6 tbz x16, 0x3e, loc_1801457c8 // 1801457c4 20 8e 38 d4 brk #0xc471 // loc_1801457c8: // 1801457c8 f0 77 9c d2 mov x16, #0xe3bf // 1801457cc 50 08 1f d7 braa x2, x16 // ... let bytes = &[ 0xe1, 0x03, 0x13, 0xaa, 0xfd, 0x7b, 0x41, 0xa9, 0xf4, 0x4f, 0xc2, 0xa8, 0xff, 0x23, 0x03, 0xd5, 0xd0, 0x07, 0x1e, 0xca, 0x50, 0x00, 0xf0, 0xb6, 0x20, 0x8e, 0x38, 0xd4, 0xf0, 0x77, 0x9c, 0xd2, 0x50, 0x08, 0x1f, 0xd7, ]; assert_eq!(unwind_rule_from_detected_epilogue(bytes, 0), None); assert_eq!( unwind_rule_from_detected_epilogue(bytes, 4), Some(UnwindRuleAarch64::OffsetSpAndRestoreFpAndLr { sp_offset_by_16: 2, fp_storage_offset_from_sp_by_8: 2, lr_storage_offset_from_sp_by_8: 3 }) ); assert_eq!( unwind_rule_from_detected_epilogue(bytes, 8), Some(UnwindRuleAarch64::OffsetSp { sp_offset_by_16: 2 }) ); assert_eq!( unwind_rule_from_detected_epilogue(bytes, 12), Some(UnwindRuleAarch64::NoOp) ); assert_eq!( unwind_rule_from_detected_epilogue(bytes, 16), Some(UnwindRuleAarch64::NoOp) ); assert_eq!( unwind_rule_from_detected_epilogue(bytes, 20), Some(UnwindRuleAarch64::NoOp) ); assert_eq!( unwind_rule_from_detected_epilogue(bytes, 24), Some(UnwindRuleAarch64::NoOp) ); assert_eq!( unwind_rule_from_detected_epilogue(bytes, 28), Some(UnwindRuleAarch64::NoOp) ); } } framehop-0.13.0/src/aarch64/instruction_analysis/mod.rs000064400000000000000000000013471046102023000211430ustar 00000000000000use super::arch::ArchAarch64; use crate::instruction_analysis::InstructionAnalysis; mod epilogue; mod prologue; use epilogue::unwind_rule_from_detected_epilogue; use prologue::unwind_rule_from_detected_prologue; impl InstructionAnalysis for ArchAarch64 { fn rule_from_prologue_analysis( text_bytes: &[u8], pc_offset: usize, ) -> Option { let (slice_from_start, slice_to_end) = text_bytes.split_at(pc_offset); unwind_rule_from_detected_prologue(slice_from_start, slice_to_end) } fn rule_from_epilogue_analysis( text_bytes: &[u8], pc_offset: usize, ) -> Option { unwind_rule_from_detected_epilogue(text_bytes, pc_offset) } } framehop-0.13.0/src/aarch64/instruction_analysis/prologue.rs000064400000000000000000000403501046102023000222150ustar 00000000000000use super::super::unwind_rule::UnwindRuleAarch64; struct PrologueDetectorAarch64 { sp_offset: i32, } #[derive(Clone, Debug, PartialEq, Eq)] enum PrologueStepResult { UnexpectedInstruction(UnexpectedInstructionType), ValidPrologueInstruction, } #[derive(Clone, Debug, PartialEq, Eq)] enum PrologueResult { ProbablyAlreadyInBody(UnexpectedInstructionType), FoundFunctionStart { sp_offset: i32 }, } #[derive(Clone, Debug, PartialEq, Eq)] enum PrologueInstructionType { NotExpectedInPrologue, CouldBePartOfPrologueIfThereIsAlsoAStackPointerSub, VeryLikelyPartOfPrologue, } #[derive(Clone, Debug, PartialEq, Eq)] enum UnexpectedInstructionType { StoreOfWrongSize, StoreReferenceRegisterNotSp, AddSubNotOperatingOnSp, NoNextInstruction, NoStackPointerSubBeforeStore, Unknown, } impl PrologueDetectorAarch64 { pub fn new() -> Self { Self { sp_offset: 0 } } pub fn analyze_slices( &mut self, slice_from_start: &[u8], slice_to_end: &[u8], ) -> PrologueResult { // There are at least two options of what we could do here: // - We could walk forwards from the function start to the instruction pointer. // - We could walk backwards from the instruction pointer to the function start. // Walking backwards is fine on arm64 because instructions are fixed size. // Walking forwards requires that we have a useful function start address. // // Unfortunately, we can't rely on having a useful function start address. // We get the funcion start address from the __unwind_info, which often collapses // consecutive functions with the same unwind rules into a single entry, discarding // the original function start addresses. // Concretely, this means that `slice_from_start` may start much earlier than the // current function. // // So we walk backwards. We first check the next instruction, and then // go backwards from the instruction pointer to the function start. // If the instruction we're about to execute is one that we'd expect to find in a prologue, // then we assume that we're in a prologue. Then we single-step backwards until we // either run out of instructions (which means we've definitely hit the start of the // function), or until we find an instruction that we would not expect in a prologue. // At that point we guess that this instruction must be belonging to the previous // function, and that we've succesfully found the start of the current function. if slice_to_end.len() < 4 { return PrologueResult::ProbablyAlreadyInBody( UnexpectedInstructionType::NoNextInstruction, ); } let next_instruction = u32::from_le_bytes([ slice_to_end[0], slice_to_end[1], slice_to_end[2], slice_to_end[3], ]); let next_instruction_type = Self::analyze_prologue_instruction_type(next_instruction); if next_instruction_type == PrologueInstructionType::NotExpectedInPrologue { return PrologueResult::ProbablyAlreadyInBody(UnexpectedInstructionType::Unknown); } let instructions = slice_from_start .chunks_exact(4) .map(|c| u32::from_le_bytes([c[0], c[1], c[2], c[3]])) .rev(); for instruction in instructions { if let PrologueStepResult::UnexpectedInstruction(_) = self.reverse_step_instruction(instruction) { break; } } if next_instruction_type == PrologueInstructionType::CouldBePartOfPrologueIfThereIsAlsoAStackPointerSub && self.sp_offset == 0 { return PrologueResult::ProbablyAlreadyInBody( UnexpectedInstructionType::NoStackPointerSubBeforeStore, ); } PrologueResult::FoundFunctionStart { sp_offset: self.sp_offset, } } /// Check if the instruction indicates that we're likely in a prologue. pub fn analyze_prologue_instruction_type(word: u32) -> PrologueInstructionType { // Detect pacibsp (verify stack pointer authentication) and `mov x29, sp`. if word == 0xd503237f || word == 0x910003fd { return PrologueInstructionType::VeryLikelyPartOfPrologue; } let bits_22_to_32 = word >> 22; // Detect stores of register pairs to the stack. if bits_22_to_32 & 0b1011111001 == 0b1010100000 { // Section C3.3, Loads and stores. // Only stores that are commonly seen in prologues (bits 22, 29 and 31 are set) let writeback_bits = bits_22_to_32 & 0b110; let reference_reg = ((word >> 5) & 0b11111) as u16; if writeback_bits == 0b000 || reference_reg != 31 { return PrologueInstructionType::NotExpectedInPrologue; } // We are storing a register pair to the stack. This is something that // can happen in a prologue but it can also happen in the body of a // function. if writeback_bits == 0b100 { // No writeback. return PrologueInstructionType::CouldBePartOfPrologueIfThereIsAlsoAStackPointerSub; } return PrologueInstructionType::VeryLikelyPartOfPrologue; } // Detect sub instructions operating on the stack pointer. // Detect `add fp, sp, #0xXX` instructions if bits_22_to_32 & 0b1011111110 == 0b1001000100 { // Section C3.4, Data processing - immediate // unsigned add / sub imm, size class X (8 bytes) let result_reg = (word & 0b11111) as u16; let input_reg = ((word >> 5) & 0b11111) as u16; let is_sub = ((word >> 30) & 0b1) == 0b1; let expected_result_reg = if is_sub { 31 } else { 29 }; if input_reg != 31 || result_reg != expected_result_reg { return PrologueInstructionType::NotExpectedInPrologue; } return PrologueInstructionType::VeryLikelyPartOfPrologue; } PrologueInstructionType::NotExpectedInPrologue } /// Step backwards over one (already executed) instruction. pub fn reverse_step_instruction(&mut self, word: u32) -> PrologueStepResult { // Detect pacibsp (verify stack pointer authentication) if word == 0xd503237f { return PrologueStepResult::ValidPrologueInstruction; } // Detect stores of register pairs to the stack. if (word >> 22) & 0b1011111001 == 0b1010100000 { // Section C3.3, Loads and stores. // but only those that are commonly seen in prologues / prologues (bits 29 and 31 are set) let writeback_bits = (word >> 23) & 0b11; if writeback_bits == 0b00 { // Not 64-bit load/store. return PrologueStepResult::UnexpectedInstruction( UnexpectedInstructionType::StoreOfWrongSize, ); } let reference_reg = ((word >> 5) & 0b11111) as u16; if reference_reg != 31 { return PrologueStepResult::UnexpectedInstruction( UnexpectedInstructionType::StoreReferenceRegisterNotSp, ); } let is_preindexed_writeback = writeback_bits == 0b11; let is_postindexed_writeback = writeback_bits == 0b01; // TODO: are there postindexed stores? What do they mean? if is_preindexed_writeback || is_postindexed_writeback { let imm7 = (((((word >> 15) & 0b1111111) as i16) << 9) >> 6) as i32; self.sp_offset -= imm7; // - to undo the instruction } return PrologueStepResult::ValidPrologueInstruction; } // Detect sub instructions operating on the stack pointer. if (word >> 23) & 0b111111111 == 0b110100010 { // Section C3.4, Data processing - immediate // unsigned sub imm, size class X (8 bytes) let result_reg = (word & 0b11111) as u16; let input_reg = ((word >> 5) & 0b11111) as u16; if result_reg != 31 || input_reg != 31 { return PrologueStepResult::UnexpectedInstruction( UnexpectedInstructionType::AddSubNotOperatingOnSp, ); } let mut imm12 = ((word >> 10) & 0b111111111111) as i32; let shift_immediate_by_12 = ((word >> 22) & 0b1) == 0b1; if shift_immediate_by_12 { imm12 <<= 12 } self.sp_offset += imm12; // + to undo the sub instruction return PrologueStepResult::ValidPrologueInstruction; } PrologueStepResult::UnexpectedInstruction(UnexpectedInstructionType::Unknown) } } pub fn unwind_rule_from_detected_prologue( slice_from_start: &[u8], slice_to_end: &[u8], ) -> Option { let mut detector = PrologueDetectorAarch64::new(); match detector.analyze_slices(slice_from_start, slice_to_end) { PrologueResult::ProbablyAlreadyInBody(_) => None, PrologueResult::FoundFunctionStart { sp_offset } => { let sp_offset_by_16 = u16::try_from(sp_offset / 16).ok()?; let rule = if sp_offset_by_16 == 0 { UnwindRuleAarch64::NoOp } else { UnwindRuleAarch64::OffsetSp { sp_offset_by_16 } }; Some(rule) } } } #[cfg(test)] mod test { use super::*; #[test] fn test_prologue_1() { // gimli::read::unit::parse_attribute // 1000dfeb8 ff 43 01 d1 sub sp, sp, #0x50 // 1000dfebc f6 57 02 a9 stp x22, x21, [sp, #local_30] // 1000dfec0 f4 4f 03 a9 stp x20, x19, [sp, #local_20] // 1000dfec4 fd 7b 04 a9 stp x29, x30, [sp, #local_10] // 1000dfec8 fd 03 01 91 add x29, sp, #0x40 // 1000dfecc f4 03 04 aa mov x20, x4 // 1000dfed0 f5 03 01 aa mov x21, x1 let bytes = &[ 0xff, 0x43, 0x01, 0xd1, 0xf6, 0x57, 0x02, 0xa9, 0xf4, 0x4f, 0x03, 0xa9, 0xfd, 0x7b, 0x04, 0xa9, 0xfd, 0x03, 0x01, 0x91, 0xf4, 0x03, 0x04, 0xaa, 0xf5, 0x03, 0x01, 0xaa, ]; assert_eq!( unwind_rule_from_detected_prologue(&bytes[..0], &bytes[0..]), Some(UnwindRuleAarch64::NoOp) ); assert_eq!( unwind_rule_from_detected_prologue(&bytes[..4], &bytes[4..]), Some(UnwindRuleAarch64::OffsetSp { sp_offset_by_16: 5 }) ); assert_eq!( unwind_rule_from_detected_prologue(&bytes[..8], &bytes[8..]), Some(UnwindRuleAarch64::OffsetSp { sp_offset_by_16: 5 }) ); assert_eq!( unwind_rule_from_detected_prologue(&bytes[..12], &bytes[12..]), Some(UnwindRuleAarch64::OffsetSp { sp_offset_by_16: 5 }) ); assert_eq!( unwind_rule_from_detected_prologue(&bytes[..16], &bytes[16..]), Some(UnwindRuleAarch64::OffsetSp { sp_offset_by_16: 5 }) ); assert_eq!( unwind_rule_from_detected_prologue(&bytes[..20], &bytes[20..]), None ); assert_eq!( unwind_rule_from_detected_prologue(&bytes[..24], &bytes[24..]), None ); assert_eq!( unwind_rule_from_detected_prologue(&bytes[..28], &bytes[28..]), None ); } #[test] fn test_prologue_with_pacibsp() { // 1801245c4 08 58 29 b8 str w8,[x0, w9, UXTW #0x2] // 1801245c8 c0 03 5f d6 ret // _malloc_zone_realloc // 1801245cc 7f 23 03 d5 pacibsp // 1801245d0 f8 5f bc a9 stp x24,x23,[sp, #local_40]! // 1801245d4 f6 57 01 a9 stp x22,x21,[sp, #local_30] // 1801245d8 f4 4f 02 a9 stp x20,x19,[sp, #local_20] // 1801245dc fd 7b 03 a9 stp x29,x30,[sp, #local_10] // 1801245e0 fd c3 00 91 add x29,sp,#0x30 // 1801245e4 f3 03 02 aa mov x19,x2 // 1801245e8 f4 03 01 aa mov x20,x1 let bytes = &[ 0x08, 0x58, 0x29, 0xb8, 0xc0, 0x03, 0x5f, 0xd6, 0x7f, 0x23, 0x03, 0xd5, 0xf8, 0x5f, 0xbc, 0xa9, 0xf6, 0x57, 0x01, 0xa9, 0xf4, 0x4f, 0x02, 0xa9, 0xfd, 0x7b, 0x03, 0xa9, 0xfd, 0xc3, 0x00, 0x91, 0xf3, 0x03, 0x02, 0xaa, 0xf4, 0x03, 0x01, 0xaa, ]; assert_eq!( unwind_rule_from_detected_prologue(&bytes[..0], &bytes[0..]), None ); assert_eq!( unwind_rule_from_detected_prologue(&bytes[..4], &bytes[4..]), None ); assert_eq!( unwind_rule_from_detected_prologue(&bytes[..8], &bytes[8..]), Some(UnwindRuleAarch64::NoOp) ); assert_eq!( unwind_rule_from_detected_prologue(&bytes[..12], &bytes[12..]), Some(UnwindRuleAarch64::NoOp) ); assert_eq!( unwind_rule_from_detected_prologue(&bytes[..16], &bytes[16..]), Some(UnwindRuleAarch64::OffsetSp { sp_offset_by_16: 4 }) ); assert_eq!( unwind_rule_from_detected_prologue(&bytes[..20], &bytes[20..]), Some(UnwindRuleAarch64::OffsetSp { sp_offset_by_16: 4 }) ); assert_eq!( unwind_rule_from_detected_prologue(&bytes[..24], &bytes[24..]), Some(UnwindRuleAarch64::OffsetSp { sp_offset_by_16: 4 }) ); assert_eq!( unwind_rule_from_detected_prologue(&bytes[..28], &bytes[28..]), Some(UnwindRuleAarch64::OffsetSp { sp_offset_by_16: 4 }) ); assert_eq!( unwind_rule_from_detected_prologue(&bytes[..32], &bytes[32..]), None ); } #[test] fn test_prologue_with_mov_fp_sp() { // _tiny_free_list_add_ptr // 180126e94 7f 23 03 d5 pacibsp // 180126e98 fd 7b bf a9 stp x29,x30,[sp, #local_10]! // 180126e9c fd 03 00 91 mov x29,sp // 180126ea0 68 04 00 51 sub w8,w3,#0x1 let bytes = &[ 0x7f, 0x23, 0x03, 0xd5, 0xfd, 0x7b, 0xbf, 0xa9, 0xfd, 0x03, 0x00, 0x91, 0x68, 0x04, 0x00, 0x51, ]; assert_eq!( unwind_rule_from_detected_prologue(&bytes[..0], &bytes[0..]), Some(UnwindRuleAarch64::NoOp) ); assert_eq!( unwind_rule_from_detected_prologue(&bytes[..4], &bytes[4..]), Some(UnwindRuleAarch64::NoOp) ); assert_eq!( unwind_rule_from_detected_prologue(&bytes[..8], &bytes[8..]), Some(UnwindRuleAarch64::OffsetSp { sp_offset_by_16: 1 }) ); assert_eq!( unwind_rule_from_detected_prologue(&bytes[..12], &bytes[12..]), None ); } #[test] fn test_no_prologue_despite_stack_store() { // We're in the middle of a function and are storing something to the stack. // But this is not a prologue, so it shouldn't be detected as one. // // 1004073d0 e8 17 00 f9 str x8,[sp, #0x28] // 1004073d4 03 00 00 14 b LAB_1004073e0 // 1004073d8 ff ff 01 a9 stp xzr,xzr,[sp, #0x18] ; <-- stores the pair xzr, xzr on the stack // 1004073dc ff 17 00 f9 str xzr,[sp, #0x28] // 1004073e0 e0 03 00 91 mov x0,sp let bytes = &[ 0xe8, 0x17, 0x00, 0xf9, 0x03, 0x00, 0x00, 0x14, 0xff, 0xff, 0x01, 0xa9, 0xff, 0x17, 0x00, 0xf9, 0xe0, 0x03, 0x00, 0x91, ]; assert_eq!( unwind_rule_from_detected_prologue(&bytes[..0], &bytes[0..]), None ); assert_eq!( unwind_rule_from_detected_prologue(&bytes[..4], &bytes[4..]), None ); assert_eq!( unwind_rule_from_detected_prologue(&bytes[..8], &bytes[8..]), None ); assert_eq!( unwind_rule_from_detected_prologue(&bytes[..12], &bytes[12..]), None ); assert_eq!( unwind_rule_from_detected_prologue(&bytes[..16], &bytes[16..]), None ); } } framehop-0.13.0/src/aarch64/macho.rs000064400000000000000000000104301046102023000151600ustar 00000000000000use super::arch::ArchAarch64; use super::unwind_rule::UnwindRuleAarch64; use crate::instruction_analysis::InstructionAnalysis; use crate::macho::{CompactUnwindInfoUnwinderError, CompactUnwindInfoUnwinding, CuiUnwindResult}; use macho_unwind_info::opcodes::OpcodeArm64; use macho_unwind_info::Function; impl CompactUnwindInfoUnwinding for ArchAarch64 { fn unwind_frame( function: Function, is_first_frame: bool, address_offset_within_function: usize, function_bytes: Option<&[u8]>, ) -> Result, CompactUnwindInfoUnwinderError> { let opcode = OpcodeArm64::parse(function.opcode); if is_first_frame { if opcode == OpcodeArm64::Null { return Ok(CuiUnwindResult::ExecRule(UnwindRuleAarch64::NoOp)); } // The pc might be in a prologue or an epilogue. The compact unwind info format ignores // prologues and epilogues; the opcodes only describe the function body. So we do some // instruction analysis to check for prologues and epilogues. if let Some(function_bytes) = function_bytes { if let Some(rule) = Self::rule_from_instruction_analysis( function_bytes, address_offset_within_function, ) { // We are inside a prologue / epilogue. Ignore the opcode and use the rule from // instruction analysis. return Ok(CuiUnwindResult::ExecRule(rule)); } } } // At this point we know with high certainty that we are in a function body. let r = match opcode { OpcodeArm64::Null => { return Err(CompactUnwindInfoUnwinderError::FunctionHasNoInfo); } OpcodeArm64::Frameless { stack_size_in_bytes, } => { if is_first_frame { if stack_size_in_bytes == 0 { CuiUnwindResult::ExecRule(UnwindRuleAarch64::NoOp) } else { CuiUnwindResult::ExecRule(UnwindRuleAarch64::OffsetSp { sp_offset_by_16: stack_size_in_bytes / 16, }) } } else { return Err(CompactUnwindInfoUnwinderError::CallerCannotBeFrameless); } } OpcodeArm64::Dwarf { eh_frame_fde } => CuiUnwindResult::NeedDwarf(eh_frame_fde), OpcodeArm64::FrameBased { .. } => { CuiUnwindResult::ExecRule(UnwindRuleAarch64::UseFramePointer) } OpcodeArm64::UnrecognizedKind(kind) => { return Err(CompactUnwindInfoUnwinderError::BadOpcodeKind(kind)) } }; Ok(r) } fn rule_for_stub_helper( offset: u32, ) -> Result, CompactUnwindInfoUnwinderError> { // shared: // +0x0 1d309c B1 94 48 10 adr x17, #0x100264330 // +0x4 1d30a0 1F 20 03 D5 nop // +0x8 1d30a4 F0 47 BF A9 stp x16, x17, [sp, #-0x10]! // +0xc 1d30a8 1F 20 03 D5 nop // +0x10 1d30ac F0 7A 32 58 ldr x16, #dyld_stub_binder_100238008 // +0x14 1d30b0 00 02 1F D6 br x16 // first stub: // +0x18 1d30b4 50 00 00 18 ldr w16, =0x1800005000000000 // +0x1c 1d30b8 F9 FF FF 17 b 0x1001d309c // +0x20 1d30bc 00 00 00 00 (padding) // second stub: // +0x24 1d30c0 50 00 00 18 ldr w16, =0x1800005000000012 // +0x28 1d30c4 F6 FF FF 17 b 0x1001d309c // +0x2c 1d30c8 00 00 00 00 (padding) let rule = if offset < 0xc { // Stack pointer hasn't been touched, just follow lr UnwindRuleAarch64::NoOp } else if offset < 0x18 { // Add 0x10 to the stack pointer and follow lr UnwindRuleAarch64::OffsetSp { sp_offset_by_16: 1 } } else { // Stack pointer hasn't been touched, just follow lr UnwindRuleAarch64::NoOp }; Ok(CuiUnwindResult::ExecRule(rule)) } } framehop-0.13.0/src/aarch64/mod.rs000064400000000000000000000004251046102023000146530ustar 00000000000000mod arch; mod cache; mod dwarf; mod instruction_analysis; #[cfg(feature = "macho")] mod macho; #[cfg(feature = "pe")] mod pe; mod unwind_rule; mod unwinder; mod unwindregs; pub use arch::*; pub use cache::*; pub use unwind_rule::*; pub use unwinder::*; pub use unwindregs::*; framehop-0.13.0/src/aarch64/pe.rs000064400000000000000000000010731046102023000145000ustar 00000000000000use super::arch::ArchAarch64; use crate::pe::{PeSections, PeUnwinderError, PeUnwinding}; use crate::unwind_result::UnwindResult; impl PeUnwinding for ArchAarch64 { fn unwind_frame( _sections: PeSections, _address: u32, _regs: &mut Self::UnwindRegs, _is_first_frame: bool, _read_stack: &mut F, ) -> Result, PeUnwinderError> where F: FnMut(u64) -> Result, D: core::ops::Deref, { Err(PeUnwinderError::Aarch64Unsupported) } } framehop-0.13.0/src/aarch64/unwind_rule.rs000064400000000000000000000306771046102023000164430ustar 00000000000000use super::unwindregs::UnwindRegsAarch64; use crate::add_signed::checked_add_signed; use crate::error::Error; use crate::unwind_rule::UnwindRule; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum UnwindRuleAarch64 { /// (sp, fp, lr) = (sp, fp, lr) /// Only possible for the first frame. Subsequent frames must get the /// return address from somewhere other than the lr register to avoid /// infinite loops. NoOp, /// (sp, fp, lr) = if is_first_frame (sp, fp, lr) else (fp + 16, *fp, *(fp + 8)) /// Used as a fallback rule. NoOpIfFirstFrameOtherwiseFp, /// (sp, fp, lr) = (sp + 16x, fp, lr) /// Only possible for the first frame. Subsequent frames must get the /// return address from somewhere other than the lr register to avoid /// infinite loops. OffsetSp { sp_offset_by_16: u16 }, /// (sp, fp, lr) = (sp + 16x, fp, lr) if is_first_frame /// This rule reflects an ambiguity in DWARF CFI information. When the /// return address is "undefined" because it was omitted, it could mean /// "same value", but this is only allowed for the first frame. OffsetSpIfFirstFrameOtherwiseStackEndsHere { sp_offset_by_16: u16 }, /// (sp, fp, lr) = (sp + 16x, fp, *(sp + 8y)) OffsetSpAndRestoreLr { sp_offset_by_16: u16, lr_storage_offset_from_sp_by_8: i16, }, /// (sp, fp, lr) = (sp + 16x, *(sp + 8y), *(sp + 8z)) OffsetSpAndRestoreFpAndLr { sp_offset_by_16: u16, fp_storage_offset_from_sp_by_8: i16, lr_storage_offset_from_sp_by_8: i16, }, /// (sp, fp, lr) = (fp + 16, *fp, *(fp + 8)) UseFramePointer, /// (sp, fp, lr) = (fp + 8x, *(fp + 8y), *(fp + 8z)) UseFramepointerWithOffsets { sp_offset_from_fp_by_8: u16, fp_storage_offset_from_fp_by_8: i16, lr_storage_offset_from_fp_by_8: i16, }, } impl UnwindRule for UnwindRuleAarch64 { type UnwindRegs = UnwindRegsAarch64; fn rule_for_stub_functions() -> Self { UnwindRuleAarch64::NoOp } fn rule_for_function_start() -> Self { UnwindRuleAarch64::NoOp } fn fallback_rule() -> Self { UnwindRuleAarch64::UseFramePointer } fn exec( self, is_first_frame: bool, regs: &mut UnwindRegsAarch64, read_stack: &mut F, ) -> Result, Error> where F: FnMut(u64) -> Result, { let lr = regs.lr(); let sp = regs.sp(); let fp = regs.fp(); let (new_lr, new_sp, new_fp) = match self { UnwindRuleAarch64::NoOp => { if !is_first_frame { return Err(Error::DidNotAdvance); } (lr, sp, fp) } UnwindRuleAarch64::NoOpIfFirstFrameOtherwiseFp => { if is_first_frame { (lr, sp, fp) } else { let fp = regs.fp(); let new_sp = fp.checked_add(16).ok_or(Error::IntegerOverflow)?; let new_lr = read_stack(fp + 8).map_err(|_| Error::CouldNotReadStack(fp + 8))?; let new_fp = read_stack(fp).map_err(|_| Error::CouldNotReadStack(fp))?; if new_sp <= sp { return Err(Error::FramepointerUnwindingMovedBackwards); } (new_lr, new_sp, new_fp) } } UnwindRuleAarch64::OffsetSpIfFirstFrameOtherwiseStackEndsHere { sp_offset_by_16 } => { if !is_first_frame { return Ok(None); } let sp_offset = u64::from(sp_offset_by_16) * 16; let new_sp = sp.checked_add(sp_offset).ok_or(Error::IntegerOverflow)?; (lr, new_sp, fp) } UnwindRuleAarch64::OffsetSp { sp_offset_by_16 } => { if !is_first_frame { return Err(Error::DidNotAdvance); } let sp_offset = u64::from(sp_offset_by_16) * 16; let new_sp = sp.checked_add(sp_offset).ok_or(Error::IntegerOverflow)?; (lr, new_sp, fp) } UnwindRuleAarch64::OffsetSpAndRestoreLr { sp_offset_by_16, lr_storage_offset_from_sp_by_8, } => { let sp_offset = u64::from(sp_offset_by_16) * 16; let new_sp = sp.checked_add(sp_offset).ok_or(Error::IntegerOverflow)?; let lr_storage_offset = i64::from(lr_storage_offset_from_sp_by_8) * 8; let lr_location = checked_add_signed(sp, lr_storage_offset).ok_or(Error::IntegerOverflow)?; let new_lr = read_stack(lr_location).map_err(|_| Error::CouldNotReadStack(lr_location))?; (new_lr, new_sp, fp) } UnwindRuleAarch64::OffsetSpAndRestoreFpAndLr { sp_offset_by_16, fp_storage_offset_from_sp_by_8, lr_storage_offset_from_sp_by_8, } => { let sp_offset = u64::from(sp_offset_by_16) * 16; let new_sp = sp.checked_add(sp_offset).ok_or(Error::IntegerOverflow)?; let lr_storage_offset = i64::from(lr_storage_offset_from_sp_by_8) * 8; let lr_location = checked_add_signed(sp, lr_storage_offset).ok_or(Error::IntegerOverflow)?; let new_lr = read_stack(lr_location).map_err(|_| Error::CouldNotReadStack(lr_location))?; let fp_storage_offset = i64::from(fp_storage_offset_from_sp_by_8) * 8; let fp_location = checked_add_signed(sp, fp_storage_offset).ok_or(Error::IntegerOverflow)?; let new_fp = read_stack(fp_location).map_err(|_| Error::CouldNotReadStack(fp_location))?; (new_lr, new_sp, new_fp) } UnwindRuleAarch64::UseFramePointer => { // Do a frame pointer stack walk. Frame-based aarch64 functions store the caller's fp and lr // on the stack and then set fp to the address where the caller's fp is stored. // // Function prologue example (this one also stores x19, x20, x21 and x22): // stp x22, x21, [sp, #-0x30]! ; subtracts 0x30 from sp, and then stores (x22, x21) at sp // stp x20, x19, [sp, #0x10] ; stores (x20, x19) at sp + 0x10 (== original sp - 0x20) // stp fp, lr, [sp, #0x20] ; stores (fp, lr) at sp + 0x20 (== original sp - 0x10) // add fp, sp, #0x20 ; sets fp to the address where the old fp is stored on the stack // // Function epilogue: // ldp fp, lr, [sp, #0x20] ; restores fp and lr from the stack // ldp x20, x19, [sp, #0x10] ; restores x20 and x19 // ldp x22, x21, [sp], #0x30 ; restores x22 and x21, and then adds 0x30 to sp // ret ; follows lr to jump back to the caller // // Functions are called with bl ("branch with link"); bl puts the return address into the lr register. // When a function reaches its end, ret reads the return address from lr and jumps to it. // On aarch64, the stack pointer is always aligned to 16 bytes, and registers are usually written // to and read from the stack in pairs. // In frame-based functions, fp and lr are placed next to each other on the stack. // So when a function is called, we have the following stack layout: // // [... rest of the stack] // ^ sp ^ fp // bl some_function ; jumps to the function and sets lr = return address // [... rest of the stack] // ^ sp ^ fp // adjust stack ptr, write some registers, and write fp and lr // [more saved regs] [caller's frame pointer] [return address] [... rest of the stack] // ^ sp ^ fp // add fp, sp, #0x20 ; sets fp to where the caller's fp is now stored // [more saved regs] [caller's frame pointer] [return address] [... rest of the stack] // ^ sp ^ fp // ; can execute bl and overwrite lr with a new value // ... [more saved regs] [caller's frame pointer] [return address] [... rest of the stack] // ^ sp ^ fp // // So: *fp is the caller's frame pointer, and *(fp + 8) is the return address. let fp = regs.fp(); let new_sp = fp.checked_add(16).ok_or(Error::IntegerOverflow)?; let new_lr = read_stack(fp + 8).map_err(|_| Error::CouldNotReadStack(fp + 8))?; let new_fp = read_stack(fp).map_err(|_| Error::CouldNotReadStack(fp))?; if new_fp == 0 { return Ok(None); } if new_fp <= fp || new_sp <= sp { return Err(Error::FramepointerUnwindingMovedBackwards); } (new_lr, new_sp, new_fp) } UnwindRuleAarch64::UseFramepointerWithOffsets { sp_offset_from_fp_by_8, fp_storage_offset_from_fp_by_8, lr_storage_offset_from_fp_by_8, } => { let sp_offset_from_fp = u64::from(sp_offset_from_fp_by_8) * 8; let new_sp = fp .checked_add(sp_offset_from_fp) .ok_or(Error::IntegerOverflow)?; let lr_storage_offset = i64::from(lr_storage_offset_from_fp_by_8) * 8; let lr_location = checked_add_signed(fp, lr_storage_offset).ok_or(Error::IntegerOverflow)?; let new_lr = read_stack(lr_location).map_err(|_| Error::CouldNotReadStack(lr_location))?; let fp_storage_offset = i64::from(fp_storage_offset_from_fp_by_8) * 8; let fp_location = checked_add_signed(fp, fp_storage_offset).ok_or(Error::IntegerOverflow)?; let new_fp = read_stack(fp_location).map_err(|_| Error::CouldNotReadStack(fp_location))?; if new_fp == 0 { return Ok(None); } if new_fp <= fp || new_sp <= sp { return Err(Error::FramepointerUnwindingMovedBackwards); } (new_lr, new_sp, new_fp) } }; let return_address = regs.lr_mask().strip_ptr_auth(new_lr); if return_address == 0 { return Ok(None); } if !is_first_frame && new_sp == sp { return Err(Error::DidNotAdvance); } regs.set_lr(new_lr); regs.set_sp(new_sp); regs.set_fp(new_fp); Ok(Some(return_address)) } } #[cfg(test)] mod test { use super::*; #[test] fn test_basic() { let stack = [ 1, 2, 3, 4, 0x40, 0x100200, 5, 6, 0x70, 0x100100, 7, 8, 9, 10, 0x0, 0x0, ]; let mut read_stack = |addr| Ok(stack[(addr / 8) as usize]); let mut regs = UnwindRegsAarch64::new(0x100300, 0x10, 0x20); let res = UnwindRuleAarch64::NoOp.exec(true, &mut regs, &mut read_stack); assert_eq!(res, Ok(Some(0x100300))); assert_eq!(regs.sp(), 0x10); let res = UnwindRuleAarch64::UseFramePointer.exec(false, &mut regs, &mut read_stack); assert_eq!(res, Ok(Some(0x100200))); assert_eq!(regs.sp(), 0x30); assert_eq!(regs.fp(), 0x40); let res = UnwindRuleAarch64::UseFramePointer.exec(false, &mut regs, &mut read_stack); assert_eq!(res, Ok(Some(0x100100))); assert_eq!(regs.sp(), 0x50); assert_eq!(regs.fp(), 0x70); let res = UnwindRuleAarch64::UseFramePointer.exec(false, &mut regs, &mut read_stack); assert_eq!(res, Ok(None)); } } framehop-0.13.0/src/aarch64/unwinder.rs000064400000000000000000000033551046102023000157340ustar 00000000000000use core::ops::Deref; use crate::{ unwinder::UnwinderInternal, AllocationPolicy, Error, FrameAddress, MayAllocateDuringUnwind, Module, Unwinder, }; use super::{ArchAarch64, CacheAarch64, UnwindRegsAarch64}; /// The unwinder for the Aarch64 CPU architecture. Use the [`Unwinder`] trait for unwinding. /// /// Type arguments: /// /// - `D`: The type for unwind section data in the modules. See [`Module`]. /// - `P`: The [`AllocationPolicy`]. pub struct UnwinderAarch64(UnwinderInternal); impl Default for UnwinderAarch64 { fn default() -> Self { Self::new() } } impl Clone for UnwinderAarch64 { fn clone(&self) -> Self { Self(self.0.clone()) } } impl UnwinderAarch64 { /// Create an unwinder for a process. pub fn new() -> Self { Self(UnwinderInternal::new()) } } impl, P: AllocationPolicy> Unwinder for UnwinderAarch64 { type UnwindRegs = UnwindRegsAarch64; type Cache = CacheAarch64

; type Module = Module; fn add_module(&mut self, module: Module) { self.0.add_module(module); } fn remove_module(&mut self, module_address_range_start: u64) { self.0.remove_module(module_address_range_start); } fn max_known_code_address(&self) -> u64 { self.0.max_known_code_address() } fn unwind_frame( &self, address: FrameAddress, regs: &mut UnwindRegsAarch64, cache: &mut CacheAarch64

, read_stack: &mut F, ) -> Result, Error> where F: FnMut(u64) -> Result, { self.0.unwind_frame(address, regs, &mut cache.0, read_stack) } } framehop-0.13.0/src/aarch64/unwindregs.rs000064400000000000000000000125631046102023000162670ustar 00000000000000use core::fmt::Debug; use crate::display_utils::HexNum; /// The registers used for unwinding on Aarch64. We only need lr (x30), sp (x31), /// and fp (x29). /// /// We also have a [`PtrAuthMask`] which allows stripping off the pointer authentication /// hash bits from the return address when unwinding through libraries which use pointer /// authentication, e.g. in system libraries on macOS. #[derive(Clone, Copy, PartialEq, Eq)] pub struct UnwindRegsAarch64 { lr_mask: PtrAuthMask, lr: u64, sp: u64, fp: u64, } /// Aarch64 CPUs support special instructions which interpret pointers as pair /// of the pointer address and an encrypted hash: The address is stored in the /// lower bits and the hash in the high bits. These are called "authenticated" /// pointers. Special instructions exist to verify pointers before dereferencing /// them. /// /// Return address can be such authenticated pointers. To return to an /// authenticated return address, the "retab" instruction is used instead of /// the regular "ret" instruction. /// /// Stack walkers need to strip the encrypted hash from return addresses because /// they need the raw code address. /// /// On macOS arm64, system libraries compiled with the arm64e target use pointer /// pointer authentication for return addresses. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct PtrAuthMask(pub u64); impl PtrAuthMask { /// Create a no-op mask which treats all bits of the pointer as address bits, /// so no bits are stripped. pub fn new_no_strip() -> Self { Self(u64::MAX) } /// Create a mask for 24 bits hash + 40 bits pointer. This appears to be /// what macOS arm64e uses. It is unclear whether we can rely on this or /// whether it can change. /// /// On macOS arm64, this mask can be applied to both authenticated pointers /// and to non-authenticated pointers without data loss; non-authenticated /// don't appear to use the top 24 bits (they're always zero). pub fn new_24_40() -> Self { Self(u64::MAX >> 24) } /// Deduce a mask based on the highest known address. The leading zero bits /// in this address will be reserved for the hash. pub fn from_max_known_address(address: u64) -> Self { Self(u64::MAX >> address.leading_zeros()) } /// Apply the mask to the given pointer. #[inline(always)] pub fn strip_ptr_auth(&self, ptr: u64) -> u64 { ptr & self.0 } } impl UnwindRegsAarch64 { /// Create a set of unwind register values and do not apply any pointer /// authentication stripping. pub fn new(lr: u64, sp: u64, fp: u64) -> Self { Self { lr_mask: PtrAuthMask::new_no_strip(), lr, sp, fp, } } /// Create a set of unwind register values with the given mask for return /// address pointer authentication stripping. pub fn new_with_ptr_auth_mask( code_ptr_auth_mask: PtrAuthMask, lr: u64, sp: u64, fp: u64, ) -> Self { Self { lr_mask: code_ptr_auth_mask, lr: code_ptr_auth_mask.strip_ptr_auth(lr), sp, fp, } } /// Get the [`PtrAuthMask`] which we apply to the `lr` value. #[inline(always)] pub fn lr_mask(&self) -> PtrAuthMask { self.lr_mask } /// Get the stack pointer value. #[inline(always)] pub fn sp(&self) -> u64 { self.sp } /// Set the stack pointer value. #[inline(always)] pub fn set_sp(&mut self, sp: u64) { self.sp = sp } /// Get the frame pointer value (x29). #[inline(always)] pub fn fp(&self) -> u64 { self.fp } /// Set the frame pointer value (x29). #[inline(always)] pub fn set_fp(&mut self, fp: u64) { self.fp = fp } /// Get the lr register value. #[inline(always)] pub fn lr(&self) -> u64 { self.lr } /// Set the lr register value. #[inline(always)] pub fn set_lr(&mut self, lr: u64) { self.lr = self.lr_mask.strip_ptr_auth(lr) } } impl Debug for UnwindRegsAarch64 { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("UnwindRegsAarch64") .field("lr", &HexNum(self.lr)) .field("sp", &HexNum(self.sp)) .field("fp", &HexNum(self.fp)) .finish() } } #[cfg(test)] mod test { use crate::aarch64::PtrAuthMask; #[test] fn test() { assert_eq!(PtrAuthMask::new_24_40().0, u64::MAX >> 24); assert_eq!(PtrAuthMask::new_24_40().0, (1 << 40) - 1); assert_eq!( PtrAuthMask::from_max_known_address(0x0000aaaab54f7000).0, 0x0000ffffffffffff ); assert_eq!( PtrAuthMask::from_max_known_address(0x0000ffffa3206000).0, 0x0000ffffffffffff ); assert_eq!( PtrAuthMask::from_max_known_address(0xffffffffc05a9000).0, 0xffffffffffffffff ); assert_eq!( PtrAuthMask::from_max_known_address(0x000055ba9f07e000).0, 0x00007fffffffffff ); assert_eq!( PtrAuthMask::from_max_known_address(0x00007f76b8019000).0, 0x00007fffffffffff ); assert_eq!( PtrAuthMask::from_max_known_address(0x000000022a3ccff7).0, 0x00000003ffffffff ); } } framehop-0.13.0/src/add_signed.rs000064400000000000000000000070721046102023000147320ustar 00000000000000/// Add a signed integer to this unsigned integer, with wrapping. #[allow(unused)] pub fn wrapping_add_signed(lhs: T, rhs: T::Signed) -> T { lhs.wrapping_add_signed(rhs) } /// Add a signed integer to this unsigned integer, but only if doing so /// does not cause underflow / overflow. pub fn checked_add_signed(lhs: T, rhs: T::Signed) -> Option { lhs.checked_add_signed(rhs) } /// A trait which adds method to unsigned integers which allow checked and /// wrapping addition of the corresponding signed integer type. /// Unfortunately, these methods conflict with the proposed standard rust /// methods, so this trait isn't actually usable without risking build /// errors once these methods are stabilized. /// https://github.com/rust-lang/rust/issues/87840 pub trait AddSigned: Sized { type Signed; /// Add a signed integer to this unsigned integer, with wrapping. fn wrapping_add_signed(self, rhs: Self::Signed) -> Self; /// Add a signed integer to this unsigned integer, but only if doing so /// does not cause underflow / overflow. fn checked_add_signed(self, rhs: Self::Signed) -> Option; } impl AddSigned for u64 { type Signed = i64; fn wrapping_add_signed(self, rhs: i64) -> u64 { self.wrapping_add(rhs as u64) } fn checked_add_signed(self, rhs: i64) -> Option { let res = AddSigned::wrapping_add_signed(self, rhs); if (rhs >= 0 && res >= self) || (rhs < 0 && res < self) { Some(res) } else { None } } } impl AddSigned for u32 { type Signed = i32; fn wrapping_add_signed(self, rhs: i32) -> u32 { self.wrapping_add(rhs as u32) } fn checked_add_signed(self, rhs: i32) -> Option { let res = AddSigned::wrapping_add_signed(self, rhs); if (rhs >= 0 && res >= self) || (rhs < 0 && res < self) { Some(res) } else { None } } } #[cfg(test)] mod test { use super::{checked_add_signed, wrapping_add_signed}; #[test] fn test_wrapping() { assert_eq!(wrapping_add_signed(1, 2), 3u64); assert_eq!(wrapping_add_signed(2, 1), 3u64); assert_eq!(wrapping_add_signed(5, -4), 1u64); assert_eq!(wrapping_add_signed(5, -5), 0u64); assert_eq!(wrapping_add_signed(u64::MAX - 5, 3), u64::MAX - 2); assert_eq!(wrapping_add_signed(u64::MAX - 5, 5), u64::MAX); assert_eq!(wrapping_add_signed(u64::MAX - 5, -5), u64::MAX - 10); assert_eq!(wrapping_add_signed(1, -2), u64::MAX); assert_eq!(wrapping_add_signed(2, -4), u64::MAX - 1); assert_eq!(wrapping_add_signed(u64::MAX, 1), 0); assert_eq!(wrapping_add_signed(u64::MAX - 5, 6), 0); assert_eq!(wrapping_add_signed(u64::MAX - 5, 9), 3); } #[test] fn test_checked() { assert_eq!(checked_add_signed(1, 2), Some(3u64)); assert_eq!(checked_add_signed(2, 1), Some(3u64)); assert_eq!(checked_add_signed(5, -4), Some(1u64)); assert_eq!(checked_add_signed(5, -5), Some(0u64)); assert_eq!(checked_add_signed(u64::MAX - 5, 3), Some(u64::MAX - 2)); assert_eq!(checked_add_signed(u64::MAX - 5, 5), Some(u64::MAX)); assert_eq!(checked_add_signed(u64::MAX - 5, -5), Some(u64::MAX - 10)); assert_eq!(checked_add_signed(1u64, -2), None); assert_eq!(checked_add_signed(2u64, -4), None); assert_eq!(checked_add_signed(u64::MAX, 1), None); assert_eq!(checked_add_signed(u64::MAX - 5, 6), None); assert_eq!(checked_add_signed(u64::MAX - 5, 9), None); } } framehop-0.13.0/src/arch.rs000064400000000000000000000002151046102023000135560ustar 00000000000000use crate::unwind_rule::UnwindRule; pub trait Arch { type UnwindRegs; type UnwindRule: UnwindRule; } framehop-0.13.0/src/cache.rs000064400000000000000000000062071046102023000137130ustar 00000000000000use alloc::boxed::Box; use crate::{rule_cache::RuleCache, unwind_rule::UnwindRule}; pub use crate::rule_cache::CacheStats; /// A trait which lets you opt into allocation-free unwinding. The two implementations of /// this trait are [`MustNotAllocateDuringUnwind`] and [`MayAllocateDuringUnwind`]. pub trait AllocationPolicy { type GimliUnwindContextStorage: gimli::UnwindContextStorage; type GimliEvaluationStorage: gimli::EvaluationStorage; } /// Require allocation-free unwinding. This is one of the two [`AllocationPolicy`] /// implementations. /// /// Using this means that the unwinder cache takes up more memory, because it preallocates /// space for DWARF CFI unwind table row evaluation and for DWARF CFI expression evaluation. /// And because those preallocations are of a fixed size, it is possible that this fixed /// size is not large enough for certain DWARF unwinding tasks. pub struct MustNotAllocateDuringUnwind; /// This is only used in the implementation of [MustNotAllocateDuringUnwind] and /// is not intended to be used by the outside world. #[doc(hidden)] pub struct StoreOnStack; impl gimli::UnwindContextStorage for StoreOnStack { type Rules = [(gimli::Register, gimli::RegisterRule); 192]; type Stack = [gimli::UnwindTableRow; 4]; } impl gimli::EvaluationStorage for StoreOnStack { type Stack = [gimli::Value; 64]; type ExpressionStack = [(R, R); 4]; type Result = [gimli::Piece; 1]; } impl AllocationPolicy for MustNotAllocateDuringUnwind { type GimliUnwindContextStorage = StoreOnStack; type GimliEvaluationStorage = StoreOnStack; } /// Allow allocation during unwinding. This is one of the two [`AllocationPolicy`] /// implementations. /// /// This is the preferred policy because it saves memory and places no limitations on /// DWARF CFI evaluation. pub struct MayAllocateDuringUnwind; impl AllocationPolicy for MayAllocateDuringUnwind { type GimliUnwindContextStorage = gimli::StoreOnHeap; type GimliEvaluationStorage = gimli::StoreOnHeap; } /// The unwinder cache. This needs to be created upfront before unwinding. During /// unwinding, the unwinder needs exclusive access to this cache. /// /// A single unwinder cache can be used with multiple unwinders alternatingly. /// /// The cache stores unwind rules for addresses it has seen before, and it stores the /// unwind context which gimli needs for DWARF CFI evaluation. pub struct Cache { pub(crate) gimli_unwind_context: Box>>, pub(crate) rule_cache: RuleCache, } impl Cache { pub fn new() -> Self { Self { gimli_unwind_context: Box::new(gimli::UnwindContext::new_in()), rule_cache: RuleCache::new(), } } } impl Default for Cache { fn default() -> Self { Self::new() } } framehop-0.13.0/src/code_address.rs000064400000000000000000000060141046102023000152630ustar 00000000000000use core::num::NonZeroU64; /// An absolute code address for a stack frame. Can either be taken directly from the /// instruction pointer ("program counter"), or from a return address. /// /// These addresses are "AVMAs", i.e. Actual Virtual Memory Addresses, i.e. addresses /// in the virtual memory of the profiled process. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum FrameAddress { /// This address is the instruction pointer / program counter. This is what unwinding /// starts with. InstructionPointer(u64), /// This is a return address, i.e. the address to which the CPU will jump to when /// returning from a function. This is the address of the instruction *after* the /// call instruction. /// /// Unwinding produces a list of return addresses. ReturnAddress(NonZeroU64), } impl FrameAddress { /// Create a [`FrameAddress::InstructionPointer`]. pub fn from_instruction_pointer(ip: u64) -> Self { FrameAddress::InstructionPointer(ip) } /// Create a [`FrameAddress::ReturnAddress`]. This returns `None` if the given /// address is zero. pub fn from_return_address(return_address: u64) -> Option { Some(FrameAddress::ReturnAddress(NonZeroU64::new( return_address, )?)) } /// The raw address (AVMA). pub fn address(self) -> u64 { match self { FrameAddress::InstructionPointer(address) => address, FrameAddress::ReturnAddress(address) => address.into(), } } /// The address (AVMA) that should be used for lookup. /// /// If this address is taken directly from the instruction pointer, then the lookup /// address is just the raw address. /// /// If this address is a return address, then the lookup address is that address **minus /// one byte**. This adjusted address will point inside the call instruction. This /// subtraction of one byte is needed if you want to look up unwind information or /// debug information, because you usually want the information for the call, not for /// the next instruction after the call. /// /// Furthermore, this distinction matters if a function calls a noreturn function as /// the last thing it does: If the call is the final instruction of the function, then /// the return address will point *after* the function, into the *next* function. /// If, during unwinding, you look up unwind information for that next function, you'd /// get incorrect unwinding. /// This has been observed in practice with `+[NSThread exit]`. pub fn address_for_lookup(self) -> u64 { match self { FrameAddress::InstructionPointer(address) => address, FrameAddress::ReturnAddress(address) => u64::from(address) - 1, } } /// Returns whether this address is a return address. pub fn is_return_address(self) -> bool { match self { FrameAddress::InstructionPointer(_) => false, FrameAddress::ReturnAddress(_) => true, } } } framehop-0.13.0/src/display_utils.rs000064400000000000000000000006601046102023000155320ustar 00000000000000use core::fmt::{Binary, Debug, LowerHex}; pub struct HexNum(pub N); impl Debug for HexNum { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { LowerHex::fmt(&self.0, f) } } pub struct BinNum(pub N); impl Debug for BinNum { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { Binary::fmt(&self.0, f) } } framehop-0.13.0/src/dwarf.rs000064400000000000000000000405061046102023000137530ustar 00000000000000use core::marker::PhantomData; use alloc::vec::Vec; use gimli::{ CfaRule, CieOrFde, DebugFrame, EhFrame, EhFrameHdr, Encoding, EndianSlice, Evaluation, EvaluationResult, EvaluationStorage, Expression, LittleEndian, Location, ParsedEhFrameHdr, Reader, ReaderOffset, Register, RegisterRule, UnwindContext, UnwindContextStorage, UnwindOffset, UnwindSection, UnwindTableRow, Value, }; pub(crate) use gimli::BaseAddresses; use crate::{arch::Arch, unwind_result::UnwindResult, ModuleSectionInfo}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DwarfUnwinderError { FdeFromOffsetFailed(gimli::Error), UnwindInfoForAddressFailed(gimli::Error), StackPointerMovedBackwards, DidNotAdvance, CouldNotRecoverCfa, CouldNotRecoverReturnAddress, CouldNotRecoverFramePointer, } impl core::fmt::Display for DwarfUnwinderError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { Self::FdeFromOffsetFailed(err) => { write!(f, "Could not get the FDE for the supplied offset: {err}") } Self::UnwindInfoForAddressFailed(err) => write!( f, "Could not find DWARF unwind info for the requested address: {err}" ), Self::StackPointerMovedBackwards => write!(f, "Stack pointer moved backwards"), Self::DidNotAdvance => write!(f, "Did not advance"), Self::CouldNotRecoverCfa => write!(f, "Could not recover the CFA"), Self::CouldNotRecoverReturnAddress => write!(f, "Could not recover the return address"), Self::CouldNotRecoverFramePointer => write!(f, "Could not recover the frame pointer"), } } } #[cfg(feature = "std")] impl std::error::Error for DwarfUnwinderError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Self::FdeFromOffsetFailed(e) => Some(e), Self::UnwindInfoForAddressFailed(e) => Some(e), _ => None, } } } #[derive(Clone, Debug)] pub enum ConversionError { CfaIsExpression, CfaIsOffsetFromUnknownRegister, ReturnAddressRuleWithUnexpectedOffset, ReturnAddressRuleWasWeird, SpOffsetDoesNotFit, RegisterNotStoredRelativeToCfa, RestoringFpButNotLr, LrStorageOffsetDoesNotFit, FpStorageOffsetDoesNotFit, SpOffsetFromFpDoesNotFit, FramePointerRuleDoesNotRestoreLr, FramePointerRuleDoesNotRestoreFp, FramePointerRuleDoesNotRestoreBp, FramePointerRuleHasStrangeBpOffset, } pub trait DwarfUnwinding: Arch { fn unwind_frame( section: &impl UnwindSection, unwind_info: &UnwindTableRow, encoding: Encoding, regs: &mut Self::UnwindRegs, is_first_frame: bool, read_stack: &mut F, ) -> Result, DwarfUnwinderError> where F: FnMut(u64) -> Result, R: Reader, UCS: UnwindContextStorage, ES: EvaluationStorage; fn rule_if_uncovered_by_fde() -> Self::UnwindRule; } pub enum UnwindSectionType { EhFrame, DebugFrame, } pub struct DwarfUnwinder<'a, R, A, UCS> where R: Reader, A: DwarfUnwinding, UCS: UnwindContextStorage, { unwind_section_data: R, unwind_section_type: UnwindSectionType, eh_frame_hdr: Option>>, unwind_context: &'a mut UnwindContext, base_svma: u64, bases: BaseAddresses, _arch: PhantomData, } impl<'a, R, A, UCS> DwarfUnwinder<'a, R, A, UCS> where R: Reader, A: DwarfUnwinding, UCS: UnwindContextStorage, { pub fn new( unwind_section_data: R, unwind_section_type: UnwindSectionType, eh_frame_hdr_data: Option<&'a [u8]>, unwind_context: &'a mut UnwindContext, bases: BaseAddresses, base_svma: u64, ) -> Self { let eh_frame_hdr = match eh_frame_hdr_data { Some(eh_frame_hdr_data) => { let hdr = EhFrameHdr::new(eh_frame_hdr_data, unwind_section_data.endian()); match hdr.parse(&bases, 8) { Ok(hdr) => Some(hdr), Err(_) => None, } } None => None, }; Self { unwind_section_data, unwind_section_type, eh_frame_hdr, unwind_context, bases, base_svma, _arch: PhantomData, } } pub fn get_fde_offset_for_relative_address(&self, rel_lookup_address: u32) -> Option { let lookup_svma = self.base_svma + rel_lookup_address as u64; let eh_frame_hdr = self.eh_frame_hdr.as_ref()?; let table = eh_frame_hdr.table()?; let fde_ptr = table.lookup(lookup_svma, &self.bases).ok()?; let fde_offset = table.pointer_to_offset(fde_ptr).ok()?; fde_offset.0.into_u64().try_into().ok() } pub fn unwind_frame_with_fde( &mut self, regs: &mut A::UnwindRegs, is_first_frame: bool, rel_lookup_address: u32, fde_offset: u32, read_stack: &mut F, ) -> Result, DwarfUnwinderError> where F: FnMut(u64) -> Result, ES: EvaluationStorage, { let lookup_svma = self.base_svma + rel_lookup_address as u64; let unwind_section_data = self.unwind_section_data.clone(); match self.unwind_section_type { UnwindSectionType::EhFrame => { let mut eh_frame = EhFrame::from(unwind_section_data); eh_frame.set_address_size(8); let unwind_info = self.unwind_info_for_fde(&eh_frame, lookup_svma, fde_offset); if let Err(DwarfUnwinderError::UnwindInfoForAddressFailed(_)) = unwind_info { return Ok(UnwindResult::ExecRule(A::rule_if_uncovered_by_fde())); } let (unwind_info, encoding) = unwind_info?; A::unwind_frame::( &eh_frame, unwind_info, encoding, regs, is_first_frame, read_stack, ) } UnwindSectionType::DebugFrame => { let mut debug_frame = DebugFrame::from(unwind_section_data); debug_frame.set_address_size(8); let unwind_info = self.unwind_info_for_fde(&debug_frame, lookup_svma, fde_offset); if let Err(DwarfUnwinderError::UnwindInfoForAddressFailed(_)) = unwind_info { return Ok(UnwindResult::ExecRule(A::rule_if_uncovered_by_fde())); } let (unwind_info, encoding) = unwind_info?; A::unwind_frame::( &debug_frame, unwind_info, encoding, regs, is_first_frame, read_stack, ) } } } fn unwind_info_for_fde>( &mut self, unwind_section: &US, lookup_svma: u64, fde_offset: u32, ) -> Result<(&UnwindTableRow, Encoding), DwarfUnwinderError> { let fde = unwind_section.fde_from_offset( &self.bases, US::Offset::from(R::Offset::from_u32(fde_offset)), US::cie_from_offset, ); let fde = fde.map_err(DwarfUnwinderError::FdeFromOffsetFailed)?; let encoding = fde.cie().encoding(); let unwind_info: &UnwindTableRow<_, _> = fde .unwind_info_for_address( unwind_section, &self.bases, self.unwind_context, lookup_svma, ) .map_err(DwarfUnwinderError::UnwindInfoForAddressFailed)?; Ok((unwind_info, encoding)) } } pub(crate) fn base_addresses_for_sections( section_info: &mut impl ModuleSectionInfo, ) -> BaseAddresses { let mut start_addr = |names: &[&[u8]]| -> u64 { names .iter() .find_map(|name| section_info.section_svma_range(name)) .map(|r| r.start) .unwrap_or_default() }; BaseAddresses::default() .set_eh_frame(start_addr(&[b"__eh_frame", b".eh_frame"])) .set_eh_frame_hdr(start_addr(&[b"__eh_frame_hdr", b".eh_frame_hdr"])) .set_text(start_addr(&[b"__text", b".text"])) .set_got(start_addr(&[b"__got", b".got"])) } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DwarfCfiIndexError { Gimli(gimli::Error), CouldNotSubtractBaseAddress, RelativeAddressTooBig, FdeOffsetTooBig, } impl core::fmt::Display for DwarfCfiIndexError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { Self::Gimli(e) => write!(f, "EhFrame processing failed: {e}"), Self::CouldNotSubtractBaseAddress => { write!(f, "Could not subtract base address to create relative pc") } Self::RelativeAddressTooBig => write!(f, "Relative address did not fit into u32"), Self::FdeOffsetTooBig => write!(f, "FDE offset did not fit into u32"), } } } impl From for DwarfCfiIndexError { fn from(e: gimli::Error) -> Self { Self::Gimli(e) } } #[cfg(feature = "std")] impl std::error::Error for DwarfCfiIndexError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Self::Gimli(e) => Some(e), _ => None, } } } /// A binary search table for eh_frame FDEs. We generate this whenever a module /// without eh_frame_hdr is added. pub struct DwarfCfiIndex { /// Contains the initial address for every FDE, relative to the base address. /// This vector is sorted so that it can be used for binary search. /// It has the same length as `fde_offsets`. sorted_fde_pc_starts: Vec, /// Contains the FDE offset for every FDE. The FDE at offset `fde_offsets[i]` /// has a PC range which starts at `sorted_fde_pc_starts[i]`. fde_offsets: Vec, } impl DwarfCfiIndex { pub fn try_new( unwind_section: US, bases: BaseAddresses, base_svma: u64, ) -> Result where R: Reader, R::Offset: TryInto, US: UnwindSection, { let mut fde_pc_and_offset = Vec::new(); let mut cur_cie = None; let mut entries_iter = unwind_section.entries(&bases); while let Some(entry) = entries_iter.next()? { let fde = match entry { CieOrFde::Cie(cie) => { cur_cie = Some(cie); continue; } CieOrFde::Fde(partial_fde) => { partial_fde.parse(|unwind_section, bases, cie_offset| { if let Some(cie) = &cur_cie { if cie.offset() == >::into(cie_offset) { return Ok(cie.clone()); } } let cie = unwind_section.cie_from_offset(bases, cie_offset); if let Ok(cie) = &cie { cur_cie = Some(cie.clone()); } cie })? } }; let pc = fde.initial_address(); let relative_pc = pc .checked_sub(base_svma) .ok_or(DwarfCfiIndexError::CouldNotSubtractBaseAddress)?; let relative_pc = u32::try_from(relative_pc) .map_err(|_| DwarfCfiIndexError::RelativeAddressTooBig)?; let fde_offset = >::try_into(fde.offset()) .map_err(|_| DwarfCfiIndexError::FdeOffsetTooBig)?; fde_pc_and_offset.push((relative_pc, fde_offset)); } fde_pc_and_offset.sort_by_key(|(pc, _)| *pc); let sorted_fde_pc_starts = fde_pc_and_offset.iter().map(|(pc, _)| *pc).collect(); let fde_offsets = fde_pc_and_offset.into_iter().map(|(_, fde)| fde).collect(); Ok(Self { sorted_fde_pc_starts, fde_offsets, }) } pub fn try_new_eh_frame( eh_frame_data: &[u8], section_info: &mut impl ModuleSectionInfo, ) -> Result { let bases = base_addresses_for_sections(section_info); let mut eh_frame = EhFrame::from(EndianSlice::new(eh_frame_data, LittleEndian)); eh_frame.set_address_size(8); Self::try_new(eh_frame, bases, section_info.base_svma()) } pub fn try_new_debug_frame( debug_frame_data: &[u8], section_info: &mut impl ModuleSectionInfo, ) -> Result { let bases = base_addresses_for_sections(section_info); let mut debug_frame = DebugFrame::from(EndianSlice::new(debug_frame_data, LittleEndian)); debug_frame.set_address_size(8); Self::try_new(debug_frame, bases, section_info.base_svma()) } pub fn fde_offset_for_relative_address(&self, rel_lookup_address: u32) -> Option { let i = match self.sorted_fde_pc_starts.binary_search(&rel_lookup_address) { Err(0) => return None, Ok(i) => i, Err(i) => i - 1, }; Some(self.fde_offsets[i]) } } pub trait DwarfUnwindRegs { fn get(&self, register: Register) -> Option; } pub fn eval_cfa_rule>( section: &impl UnwindSection, rule: &CfaRule, encoding: Encoding, regs: &UR, ) -> Option { match rule { CfaRule::RegisterAndOffset { register, offset } => { let val = regs.get(*register)?; u64::try_from(i64::try_from(val).ok()?.checked_add(*offset)?).ok() } CfaRule::Expression(expr) => { let expr = expr.get(section).ok()?; eval_expr::(expr, encoding, regs) } } } fn eval_expr>( expr: Expression, encoding: Encoding, regs: &UR, ) -> Option { let mut eval = Evaluation::::new_in(expr.0, encoding); let mut result = eval.evaluate().ok()?; loop { match result { EvaluationResult::Complete => break, EvaluationResult::RequiresRegister { register, .. } => { let value = regs.get(register)?; result = eval.resume_with_register(Value::Generic(value as _)).ok()?; } _ => return None, } } let x = &eval.as_result().last()?.location; if let Location::Address { address } = x { Some(*address) } else { None } } pub fn eval_register_rule( section: &impl UnwindSection, rule: RegisterRule, cfa: u64, encoding: Encoding, val: u64, regs: &UR, read_stack: &mut F, ) -> Option where R: Reader, F: FnMut(u64) -> Result, UR: DwarfUnwindRegs, S: EvaluationStorage, { match rule { RegisterRule::Undefined => None, RegisterRule::SameValue => Some(val), RegisterRule::Offset(offset) => { let cfa_plus_offset = u64::try_from(i64::try_from(cfa).ok()?.checked_add(offset)?).ok()?; read_stack(cfa_plus_offset).ok() } RegisterRule::ValOffset(offset) => { u64::try_from(i64::try_from(cfa).ok()?.checked_add(offset)?).ok() } RegisterRule::Register(register) => regs.get(register), RegisterRule::Expression(expr) => { let expr = expr.get(section).ok()?; let val = eval_expr::(expr, encoding, regs)?; read_stack(val).ok() } RegisterRule::ValExpression(expr) => { let expr = expr.get(section).ok()?; eval_expr::(expr, encoding, regs) } RegisterRule::Architectural => { // Unimplemented // TODO: Find out what the architectural rules for x86_64 and for aarch64 are, if any. None } _ => None, } } framehop-0.13.0/src/error.rs000064400000000000000000000073031046102023000137770ustar 00000000000000use crate::dwarf::DwarfUnwinderError; #[cfg(feature = "macho")] use crate::macho::CompactUnwindInfoUnwinderError; #[cfg(feature = "pe")] use crate::pe::PeUnwinderError; /// The error type used in this crate. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Error { CouldNotReadStack(u64), FramepointerUnwindingMovedBackwards, DidNotAdvance, IntegerOverflow, ReturnAddressIsNull, } impl core::fmt::Display for Error { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { Self::CouldNotReadStack(addr) => write!(f, "Could not read stack memory at 0x{addr:x}"), Self::FramepointerUnwindingMovedBackwards => { write!(f, "Frame pointer unwinding moved backwards") } Self::DidNotAdvance => write!( f, "Neither the code address nor the stack pointer changed, would loop" ), Self::IntegerOverflow => write!(f, "Unwinding caused integer overflow"), Self::ReturnAddressIsNull => write!(f, "Return address is null"), } } } #[cfg(feature = "std")] impl std::error::Error for Error {} #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum UnwinderError { #[cfg(feature = "macho")] CompactUnwindInfo(CompactUnwindInfoUnwinderError), Dwarf(DwarfUnwinderError), #[cfg(feature = "pe")] Pe(PeUnwinderError), #[cfg(feature = "macho")] NoDwarfData, NoModuleUnwindData, EhFrameHdrCouldNotFindAddress, DwarfCfiIndexCouldNotFindAddress, } impl core::fmt::Display for UnwinderError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { #[cfg(feature = "macho")] Self::CompactUnwindInfo(err) => { write!(f, "Compact Unwind Info unwinding failed: {err}") } Self::Dwarf(err) => write!(f, "DWARF unwinding failed: {err}"), #[cfg(feature = "pe")] Self::Pe(err) => write!(f, "PE unwinding failed: {err}"), #[cfg(feature = "macho")] Self::NoDwarfData => write!( f, "__unwind_info referred to DWARF FDE but we do not have __eh_frame data" ), Self::NoModuleUnwindData => { write!(f, "No unwind data for the module containing the address") } Self::EhFrameHdrCouldNotFindAddress => write!( f, ".eh_frame_hdr was not successful in looking up the address in the table" ), Self::DwarfCfiIndexCouldNotFindAddress => write!( f, "Failed to look up the address in the DwarfCfiIndex search table" ), } } } impl From for UnwinderError { fn from(e: DwarfUnwinderError) -> Self { Self::Dwarf(e) } } #[cfg(feature = "pe")] impl From for UnwinderError { fn from(e: PeUnwinderError) -> Self { Self::Pe(e) } } #[cfg(feature = "macho")] impl From for UnwinderError { fn from(e: CompactUnwindInfoUnwinderError) -> Self { match e { CompactUnwindInfoUnwinderError::BadDwarfUnwinding(e) => UnwinderError::Dwarf(e), e => UnwinderError::CompactUnwindInfo(e), } } } #[cfg(feature = "std")] impl std::error::Error for UnwinderError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { #[cfg(feature = "macho")] Self::CompactUnwindInfo(e) => Some(e), Self::Dwarf(e) => Some(e), #[cfg(feature = "pe")] Self::Pe(e) => Some(e), _ => None, } } } framehop-0.13.0/src/instruction_analysis.rs000064400000000000000000000013351046102023000171310ustar 00000000000000use crate::arch::Arch; pub trait InstructionAnalysis: Arch { /// Caller guarantees pc_offset <= text_bytes.len() fn rule_from_prologue_analysis(text_bytes: &[u8], pc_offset: usize) -> Option; /// Caller guarantees pc_offset <= text_bytes.len() fn rule_from_epilogue_analysis(text_bytes: &[u8], pc_offset: usize) -> Option; /// Caller guarantees pc_offset <= text_bytes.len() fn rule_from_instruction_analysis( text_bytes: &[u8], pc_offset: usize, ) -> Option { Self::rule_from_prologue_analysis(text_bytes, pc_offset) .or_else(|| Self::rule_from_epilogue_analysis(text_bytes, pc_offset)) } } framehop-0.13.0/src/lib.rs000064400000000000000000000207251046102023000134170ustar 00000000000000//! # framehop //! //! Framehop is a stack frame unwinder written in 100% Rust. It produces high quality stacks at high speed, on multiple platforms and architectures, without an expensive pre-processing step for unwind information. This makes it suitable for sampling profilers. //! //! It currently supports unwinding x86_64 and aarch64, with unwind information formats commonly used on Windows, macOS, Linux and Android. //! //! You give framehop register values, stack memory and unwind data, and framehop produces a list of return addresses. //! //! Framehop can be used in the following scenarios: //! //! - Live unwinding of a remote process. This is how [`samply`](https://github.com/mstange/samply/) uses it. //! - Offline unwinding from saved registers and stack bytes, even on a different machine, a different OS, or a different CPU architecture. //! - Live unwinding inside the same process. This is currently unproven, but should work as long as you can do heap allocation before sampling, in order to allocate a cache and to update the list of modules. The actual unwinding does not require any heap allocation and should work even inside a signal handler, as long as you use `MustNotAllocateDuringUnwind`. //! //! As a user of framehop, your responsibilities are the following: //! //! - You need to enumerate the modules (libraries) that are loaded in the sampled process ahead of time, or ideally maintain a live list which is updated whenever modules are loaded / unloaded. //! - You need to provide address ranges and unwind section data for those modules. //! - When sampling, you provide the register values and a callback to read arbitrary stack memory without segfaulting. //! - On aarch64, picking the right bitmask to strip pointer authentication bits from return addresses is up to you. //! - You will need to do symbol resolution yourself, if you want function names. Framehop only produces addresses, it does not do any symbolication. //! //! In turn, framehop solves the following problems: //! //! - It parses a number of different unwind information formats. At the moment, it supports the following: //! - Apple's Compact Unwinding Format, in `__unwind_info` (macOS) //! - DWARF CFI in `.eh_frame` (using `.eh_frame_hdr` as an index, if available) //! - DWARF CFI in `.debug_frame` //! - PE unwind info in `.pdata`, `.rdata` and `.xdata` (for Windows x86_64) //! - It supports correct unwinding even when the program is interrupted inside a function prologue or epilogue. On macOS, it has to analyze assembly instructions in order to do this. //! - On x86_64 and aarch64, it falls back to frame pointer unwinding if it cannot find unwind information for an address. //! - It caches the unwind rule for each address in a fixed-size cache, so that repeated unwinding from the same address is even faster. //! - It generates binary search indexes for unwind information formats which don't have them. Specifically, for `.debug_frame` and for `.eh_frame` without `.eh_frame_hdr`. //! - It does a reasonable job of detecting the end of the stack, so that you can differentiate between properly terminated stacks and prematurely truncated stacks. //! //! Framehop is not suitable for debuggers or to implement exception handling. Debuggers usually need to recover all register values for every frame whereas framehop only cares about return addresses. And exception handling needs the ability to call destructors, which is also a non-goal for framehop. //! //! ## Speed //! //! Framehop achieves high speed in the following ways: //! //! 1. It only recovers registers which are needed for computing return addresses. On x86_64 that's `rip`, `rsp` and `rbp`, and on aarch64 that's `lr`, `sp` and `fp`. All other registers are not needed - in theory they could be used as inputs to DWARF CFI expressions, but in practice they are not. //! 2. It uses zero-copy parsing wherever possible. For example, the bytes in `__unwind_info` are only accessed during unwinding, and the binary search happens right inside the original `__unwind_info` memory. For DWARF unwinding, framehop uses the excellent [`gimli` crate](https://github.com/gimli-rs/gimli/), which was written with performance in mind. //! 3. It uses binary search to find the correct unwind rule in all supported unwind information formats. For formats without an built-in index, it creates an index when the module is added. //! 4. It caches unwind rules based on address. In practice, the 509-slot cache achieves a hit rate of around 80% on complicated code like Firefox (with the cache being shared across all Firefox processes). When profiling simpler applications, the hit rate is likely much higher. //! //! Furthermore, adding a module is fast too because framehop only does minimal up-front parsing and processing - really, the only thing it does is to create the index of FDE offsets for `.eh_frame` / `.debug_frame`. //! //! ## Example //! //! ``` //! use core::ops::Range; //! use framehop::aarch64::{CacheAarch64, UnwindRegsAarch64, UnwinderAarch64}; //! use framehop::{ExplicitModuleSectionInfo, FrameAddress, Module}; //! //! let mut cache = CacheAarch64::<_>::new(); //! let mut unwinder = UnwinderAarch64::new(); //! //! let module = Module::new( //! "mybinary".to_string(), //! 0x1003fc000..0x100634000, //! 0x1003fc000, //! ExplicitModuleSectionInfo { //! base_svma: 0x100000000, //! text_svma: Some(0x100000b64..0x1001d2d18), //! text: Some(vec![/* __text */]), //! stubs_svma: Some(0x1001d2d18..0x1001d309c), //! stub_helper_svma: Some(0x1001d309c..0x1001d3438), //! got_svma: Some(0x100238000..0x100238010), //! unwind_info: Some(vec![/* __unwind_info */]), //! eh_frame_svma: Some(0x100237f80..0x100237ffc), //! eh_frame: Some(vec![/* __eh_frame */]), //! text_segment_svma: Some(0x1003fc000..0x100634000), //! text_segment: Some(vec![/* __TEXT */]), //! ..Default::default() //! }, //! ); //! unwinder.add_module(module); //! //! let pc = 0x1003fc000 + 0x1292c0; //! let lr = 0x1003fc000 + 0xe4830; //! let sp = 0x10; //! let fp = 0x20; //! let stack = [ //! 1, 2, 3, 4, 0x40, 0x1003fc000 + 0x100dc4, //! 5, 6, 0x70, 0x1003fc000 + 0x12ca28, //! 7, 8, 9, 10, 0x0, 0x0, //! ]; //! let mut read_stack = |addr| stack.get((addr / 8) as usize).cloned().ok_or(()); //! //! use framehop::Unwinder; //! let mut iter = unwinder.iter_frames( //! pc, //! UnwindRegsAarch64::new(lr, sp, fp), //! &mut cache, //! &mut read_stack, //! ); //! //! let mut frames = Vec::new(); //! while let Ok(Some(frame)) = iter.next() { //! frames.push(frame); //! } //! //! assert_eq!( //! frames, //! vec![ //! FrameAddress::from_instruction_pointer(0x1003fc000 + 0x1292c0), //! FrameAddress::from_return_address(0x1003fc000 + 0x100dc4).unwrap(), //! FrameAddress::from_return_address(0x1003fc000 + 0x12ca28).unwrap() //! ] //! ); //! ``` #![cfg_attr(not(feature = "std"), no_std)] extern crate alloc; mod add_signed; mod arch; mod cache; mod code_address; mod display_utils; mod dwarf; mod error; mod instruction_analysis; #[cfg(feature = "macho")] mod macho; #[cfg(feature = "pe")] mod pe; mod rule_cache; mod unwind_result; mod unwind_rule; mod unwinder; /// Types for unwinding on the aarch64 CPU architecture. pub mod aarch64; /// Types for unwinding on the x86_64 CPU architecture. pub mod x86_64; pub use cache::{AllocationPolicy, MayAllocateDuringUnwind, MustNotAllocateDuringUnwind}; pub use code_address::FrameAddress; pub use error::Error; pub use rule_cache::CacheStats; pub use unwinder::{ ExplicitModuleSectionInfo, Module, ModuleSectionInfo, UnwindIterator, Unwinder, }; /// The unwinder cache for the native CPU architecture. #[cfg(target_arch = "aarch64")] pub type CacheNative

= aarch64::CacheAarch64

; /// The unwind registers type for the native CPU architecture. #[cfg(target_arch = "aarch64")] pub type UnwindRegsNative = aarch64::UnwindRegsAarch64; /// The unwinder type for the native CPU architecture. #[cfg(target_arch = "aarch64")] pub type UnwinderNative = aarch64::UnwinderAarch64; /// The unwinder cache for the native CPU architecture. #[cfg(target_arch = "x86_64")] pub type CacheNative

= x86_64::CacheX86_64

; /// The unwind registers type for the native CPU architecture. #[cfg(target_arch = "x86_64")] pub type UnwindRegsNative = x86_64::UnwindRegsX86_64; /// The unwinder type for the native CPU architecture. #[cfg(target_arch = "x86_64")] pub type UnwinderNative = x86_64::UnwinderX86_64; framehop-0.13.0/src/macho.rs000064400000000000000000000200741046102023000137350ustar 00000000000000use core::marker::PhantomData; use crate::dwarf::DwarfUnwinderError; use crate::{arch::Arch, unwind_rule::UnwindRule}; use macho_unwind_info::UnwindInfo; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CompactUnwindInfoUnwinderError { BadFormat(macho_unwind_info::Error), AddressOutsideRange(u32), CallerCannotBeFrameless, FunctionHasNoInfo, BpOffsetDoesNotFit, BadOpcodeKind(u8), BadDwarfUnwinding(DwarfUnwinderError), NoTextBytesToLookUpIndirectStackOffset, IndirectStackOffsetOutOfBounds, StackAdjustOverflow, StackSizeDoesNotFit, StubFunctionCannotBeCaller, InvalidFrameless, } impl core::fmt::Display for CompactUnwindInfoUnwinderError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { Self::BadFormat(err) => write!(f, "Bad __unwind_info format: {err}"), Self::AddressOutsideRange(addr) => write!(f, "Address 0x{addr:x} outside of the range covered by __unwind_info"), Self::CallerCannotBeFrameless => write!(f, "Encountered a non-leaf function which was marked as frameless."), Self::FunctionHasNoInfo => write!(f, "No unwind info (null opcode) for this function in __unwind_info"), Self::BpOffsetDoesNotFit => write!(f, "rbp offset from the stack pointer divided by 8 does not fit into i16"), Self::BadOpcodeKind(kind) => write!(f, "Unrecognized __unwind_info opcode kind {kind}"), Self::BadDwarfUnwinding(err) => write!(f, "DWARF unwinding failed: {err}"), Self::NoTextBytesToLookUpIndirectStackOffset => write!(f, "Don't have the function bytes to look up the offset for frameless function with indirect stack offset"), Self::IndirectStackOffsetOutOfBounds => write!(f, "Stack offset not found inside the bounds of the text bytes"), Self::StackAdjustOverflow => write!(f, "Stack adjust addition overflowed"), Self::StackSizeDoesNotFit => write!(f, "Stack size does not fit into the rule representation"), Self::StubFunctionCannotBeCaller => write!(f, "A caller had its address in the __stubs section"), Self::InvalidFrameless => write!(f, "Encountered invalid unwind entry"), } } } impl From for CompactUnwindInfoUnwinderError { fn from(e: macho_unwind_info::Error) -> Self { Self::BadFormat(e) } } impl From for CompactUnwindInfoUnwinderError { fn from(e: DwarfUnwinderError) -> Self { Self::BadDwarfUnwinding(e) } } #[cfg(feature = "std")] impl std::error::Error for CompactUnwindInfoUnwinderError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Self::BadFormat(e) => Some(e), Self::BadDwarfUnwinding(e) => Some(e), _ => None, } } } #[derive(Clone, Debug)] pub enum CuiUnwindResult { ExecRule(R), NeedDwarf(u32), } pub trait CompactUnwindInfoUnwinding: Arch { fn unwind_frame( function: macho_unwind_info::Function, is_first_frame: bool, address_offset_within_function: usize, function_bytes: Option<&[u8]>, ) -> Result, CompactUnwindInfoUnwinderError>; fn rule_for_stub_helper( offset: u32, ) -> Result, CompactUnwindInfoUnwinderError>; } #[derive(Clone, Copy)] pub struct TextBytes<'a> { offset_from_base_address: u32, bytes: &'a [u8], } impl<'a> TextBytes<'a> { pub fn new(offset_from_base_address: u32, bytes: &'a [u8]) -> Self { Self { offset_from_base_address, bytes, } } } pub struct CompactUnwindInfoUnwinder<'a, A: CompactUnwindInfoUnwinding> { unwind_info_data: &'a [u8], text_bytes: Option>, stubs_range: (u32, u32), stub_helper_range: (u32, u32), _arch: PhantomData, } impl<'a, A: CompactUnwindInfoUnwinding> CompactUnwindInfoUnwinder<'a, A> { pub fn new( unwind_info_data: &'a [u8], text_bytes: Option>, stubs_range: (u32, u32), stub_helper_range: (u32, u32), ) -> Self { Self { unwind_info_data, text_bytes, stubs_range, stub_helper_range, _arch: PhantomData, } } pub fn function_for_address( &self, address: u32, ) -> Result { let unwind_info = UnwindInfo::parse(self.unwind_info_data) .map_err(CompactUnwindInfoUnwinderError::BadFormat)?; let function = unwind_info .lookup(address) .map_err(CompactUnwindInfoUnwinderError::BadFormat)?; function.ok_or(CompactUnwindInfoUnwinderError::AddressOutsideRange(address)) } pub fn unwind_frame( &mut self, rel_lookup_address: u32, is_first_frame: bool, ) -> Result, CompactUnwindInfoUnwinderError> { // Exclude __stubs and __stub_helper sections. The __unwind_info does not describe those // sections. These sections need to be manually excluded because the addresses in // __unwind_info can be both before and after the stubs/stub_helper sections, if there is // both a __text and a text_env section. if self.stubs_range.0 <= rel_lookup_address && rel_lookup_address < self.stubs_range.1 { if !is_first_frame { return Err(CompactUnwindInfoUnwinderError::StubFunctionCannotBeCaller); } // All stub functions are frameless. return Ok(CuiUnwindResult::ExecRule( A::UnwindRule::rule_for_stub_functions(), )); } if self.stub_helper_range.0 <= rel_lookup_address && rel_lookup_address < self.stub_helper_range.1 { if !is_first_frame { return Err(CompactUnwindInfoUnwinderError::StubFunctionCannotBeCaller); } let lookup_address_relative_to_section = rel_lookup_address - self.stub_helper_range.0; return ::rule_for_stub_helper( lookup_address_relative_to_section, ); } let function = match self.function_for_address(rel_lookup_address) { Ok(f) => f, Err(CompactUnwindInfoUnwinderError::AddressOutsideRange(_)) if is_first_frame => { // pc is falling into this module's address range, but it's not covered by __unwind_info. // This could mean that we're inside a stub function, in the __stubs section. // All stub functions are frameless. // TODO: Obtain the actual __stubs address range and do better checking here. return Ok(CuiUnwindResult::ExecRule( A::UnwindRule::rule_for_stub_functions(), )); } Err(err) => return Err(err), }; if is_first_frame && rel_lookup_address == function.start_address { return Ok(CuiUnwindResult::ExecRule( A::UnwindRule::rule_for_function_start(), )); } let address_offset_within_function = usize::try_from(rel_lookup_address - function.start_address).unwrap(); let function_bytes = self.text_bytes.and_then(|text_bytes| { let TextBytes { offset_from_base_address, bytes, } = text_bytes; let function_start_relative_to_text = function .start_address .checked_sub(offset_from_base_address)? as usize; let function_end_relative_to_text = function.end_address.checked_sub(offset_from_base_address)? as usize; bytes.get(function_start_relative_to_text..function_end_relative_to_text) }); ::unwind_frame( function, is_first_frame, address_offset_within_function, function_bytes, ) } } framehop-0.13.0/src/pe.rs000064400000000000000000000062211046102023000132500ustar 00000000000000use crate::{arch::Arch, unwind_result::UnwindResult}; use core::ops::Range; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum PeUnwinderError { MissingUnwindInfoData(u32), MissingInstructionData(u32), MissingStackData(Option), UnwindInfoParseError, Aarch64Unsupported, } impl core::fmt::Display for PeUnwinderError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { Self::MissingUnwindInfoData(rva) => { write!(f, "failed to read unwind info memory at RVA {rva:x}") } Self::MissingInstructionData(rva) => { write!(f, "failed to read instruction memory at RVA {rva:x}") } Self::MissingStackData(addr) => { write!(f, "failed to read stack")?; if let Some(addr) = addr { write!(f, " at address {addr:x}")?; } Ok(()) } Self::UnwindInfoParseError => write!(f, "failed to parse UnwindInfo"), Self::Aarch64Unsupported => write!(f, "AArch64 is not yet supported"), } } } #[cfg(feature = "std")] impl std::error::Error for PeUnwinderError {} /// Data and the related RVA range within the binary. /// /// This is only used by PE unwinding. /// /// Type arguments: /// - `D`: The type for unwind section data. This allows carrying owned data on the /// module, e.g. `Vec`. But it could also be a wrapper around mapped memory from /// a file or a different process, for example. It just needs to provide a slice of /// bytes via its `Deref` implementation. pub struct DataAtRvaRange { pub data: D, pub rva_range: Range, } pub struct PeSections<'a, D> { pub pdata: &'a D, pub rdata: Option<&'a DataAtRvaRange>, pub xdata: Option<&'a DataAtRvaRange>, pub text: Option<&'a DataAtRvaRange>, } impl<'a, D> PeSections<'a, D> where D: core::ops::Deref, { pub fn unwind_info_memory_at_rva(&self, rva: u32) -> Result<&'a [u8], PeUnwinderError> { [&self.rdata, &self.xdata] .into_iter() .find_map(|o| o.and_then(|m| memory_at_rva(m, rva))) .ok_or(PeUnwinderError::MissingUnwindInfoData(rva)) } pub fn text_memory_at_rva(&self, rva: u32) -> Result<&'a [u8], PeUnwinderError> { self.text .and_then(|m| memory_at_rva(m, rva)) .ok_or(PeUnwinderError::MissingInstructionData(rva)) } } fn memory_at_rva>( DataAtRvaRange { data, rva_range }: &DataAtRvaRange, address: u32, ) -> Option<&[u8]> { if rva_range.contains(&address) { let offset = address - rva_range.start; Some(&data[(offset as usize)..]) } else { None } } pub trait PeUnwinding: Arch { fn unwind_frame( sections: PeSections, address: u32, regs: &mut Self::UnwindRegs, is_first_frame: bool, read_stack: &mut F, ) -> Result, PeUnwinderError> where F: FnMut(u64) -> Result, D: core::ops::Deref; } framehop-0.13.0/src/rule_cache.rs000064400000000000000000000100651046102023000147370ustar 00000000000000use alloc::boxed::Box; use crate::unwind_rule::UnwindRule; const CACHE_ENTRY_COUNT: usize = 509; pub struct RuleCache { entries: Box<[Option>; CACHE_ENTRY_COUNT]>, stats: CacheStats, } impl RuleCache { pub fn new() -> Self { Self { entries: Box::new([None; CACHE_ENTRY_COUNT]), stats: CacheStats::new(), } } pub fn lookup(&mut self, address: u64, modules_generation: u16) -> CacheResult { let slot = (address % (CACHE_ENTRY_COUNT as u64)) as u16; match &self.entries[slot as usize] { None => { self.stats.miss_empty_slot_count += 1; } Some(entry) => { if entry.modules_generation == modules_generation { if entry.address == address { self.stats.hit_count += 1; return CacheResult::Hit(entry.unwind_rule); } else { self.stats.miss_wrong_address_count += 1; } } else { self.stats.miss_wrong_modules_count += 1; } } } CacheResult::Miss(CacheHandle { slot, address, modules_generation, }) } pub fn insert(&mut self, handle: CacheHandle, unwind_rule: R) { let CacheHandle { slot, address, modules_generation, } = handle; self.entries[slot as usize] = Some(CacheEntry { address, modules_generation, unwind_rule, }); } /// Returns a snapshot of the cache usage statistics. pub fn stats(&self) -> CacheStats { self.stats } } pub enum CacheResult { Miss(CacheHandle), Hit(R), } pub struct CacheHandle { slot: u16, address: u64, modules_generation: u16, } const _: () = assert!( CACHE_ENTRY_COUNT as u64 <= u16::MAX as u64, "u16 should be sufficient to store the cache slot index" ); #[derive(Clone, Copy, Debug)] struct CacheEntry { address: u64, modules_generation: u16, unwind_rule: R, } /// Statistics about the effectiveness of the rule cache. #[derive(Default, Debug, Clone, Copy)] pub struct CacheStats { /// The number of successful cache hits. pub hit_count: u64, /// The number of cache misses that were due to an empty slot. pub miss_empty_slot_count: u64, /// The number of cache misses that were due to a filled slot whose module /// generation didn't match the unwinder's current module generation. /// (This means that either the unwinder's modules have changed since the /// rule in this slot was stored, or the same cache is used with multiple /// unwinders and the unwinders are stomping on each other's cache slots.) pub miss_wrong_modules_count: u64, /// The number of cache misses that were due to cache slot collisions of /// different addresses. pub miss_wrong_address_count: u64, } impl CacheStats { /// Create a new instance. pub fn new() -> Self { Default::default() } /// The number of total lookups. pub fn total(&self) -> u64 { self.hits() + self.misses() } /// The number of total hits. pub fn hits(&self) -> u64 { self.hit_count } /// The number of total misses. pub fn misses(&self) -> u64 { self.miss_empty_slot_count + self.miss_wrong_modules_count + self.miss_wrong_address_count } } #[cfg(test)] mod tests { use crate::{aarch64::UnwindRuleAarch64, x86_64::UnwindRuleX86_64}; use super::*; // Ensure that the size of Option> doesn't change by accident. #[test] fn test_cache_entry_size() { assert_eq!( core::mem::size_of::>>(), 16 ); assert_eq!( core::mem::size_of::>>(), 24 // <-- larger than we'd like ); } } framehop-0.13.0/src/unwind_result.rs000064400000000000000000000001341046102023000155430ustar 00000000000000#[derive(Debug, Clone)] pub enum UnwindResult { ExecRule(R), Uncacheable(u64), } framehop-0.13.0/src/unwind_rule.rs000064400000000000000000000006541046102023000152030ustar 00000000000000use crate::error::Error; pub trait UnwindRule: Copy + core::fmt::Debug { type UnwindRegs; fn exec( self, is_first_frame: bool, regs: &mut Self::UnwindRegs, read_stack: &mut F, ) -> Result, Error> where F: FnMut(u64) -> Result; fn rule_for_stub_functions() -> Self; fn rule_for_function_start() -> Self; fn fallback_rule() -> Self; } framehop-0.13.0/src/unwinder.rs000064400000000000000000001141711046102023000145030ustar 00000000000000use alloc::string::String; use alloc::sync::Arc; use alloc::vec::Vec; use fallible_iterator::FallibleIterator; use gimli::{EndianSlice, LittleEndian}; use crate::arch::Arch; use crate::cache::{AllocationPolicy, Cache}; use crate::dwarf::{DwarfCfiIndex, DwarfUnwinder, DwarfUnwinding, UnwindSectionType}; use crate::error::{Error, UnwinderError}; use crate::instruction_analysis::InstructionAnalysis; #[cfg(feature = "macho")] use crate::macho::{ CompactUnwindInfoUnwinder, CompactUnwindInfoUnwinding, CuiUnwindResult, TextBytes, }; #[cfg(feature = "pe")] use crate::pe::{DataAtRvaRange, PeUnwinding}; use crate::rule_cache::CacheResult; use crate::unwind_result::UnwindResult; use crate::unwind_rule::UnwindRule; use crate::FrameAddress; use core::marker::PhantomData; use core::ops::{Deref, Range}; use core::sync::atomic::{AtomicU16, Ordering}; /// Unwinder is the trait that each CPU architecture's concrete unwinder type implements. /// This trait's methods are what let you do the actual unwinding. pub trait Unwinder: Clone { /// The unwind registers type for the targeted CPU architecture. type UnwindRegs; /// The unwind cache for the targeted CPU architecture. /// This is an associated type because the cache stores unwind rules, whose concrete /// type depends on the CPU arch, and because the cache can support different allocation /// policies. type Cache; /// The module type. This is an associated type because the concrete type varies /// depending on the type you use to give the module access to the unwind section data. type Module; /// Add a module that's loaded in the profiled process. This is how you provide unwind /// information and address ranges. /// /// This should be called whenever a new module is loaded into the process. fn add_module(&mut self, module: Self::Module); /// Remove a module that was added before using `add_module`, keyed by the start /// address of that module's address range. If no match is found, the call is ignored. /// This should be called whenever a module is unloaded from the process. fn remove_module(&mut self, module_avma_range_start: u64); /// Returns the highest code address that is known in this process based on the module /// address ranges. Returns 0 if no modules have been added. /// /// This method can be used together with /// [`PtrAuthMask::from_max_known_address`](crate::aarch64::PtrAuthMask::from_max_known_address) /// to make an educated guess at a pointer authentication mask for Aarch64 return addresses. fn max_known_code_address(&self) -> u64; /// Unwind a single frame, to recover return address and caller register values. /// This is the main entry point for unwinding. fn unwind_frame( &self, address: FrameAddress, regs: &mut Self::UnwindRegs, cache: &mut Self::Cache, read_stack: &mut F, ) -> Result, Error> where F: FnMut(u64) -> Result; /// Return an iterator that unwinds frame by frame until the end of the stack is found. fn iter_frames<'u, 'c, 'r, F>( &'u self, pc: u64, regs: Self::UnwindRegs, cache: &'c mut Self::Cache, read_stack: &'r mut F, ) -> UnwindIterator<'u, 'c, 'r, Self, F> where F: FnMut(u64) -> Result, { UnwindIterator::new(self, pc, regs, cache, read_stack) } } /// An iterator for unwinding the entire stack, starting from the initial register values. /// /// The first yielded frame is the instruction pointer. Subsequent addresses are return /// addresses. /// /// This iterator attempts to detect if stack unwinding completed successfully, or if the /// stack was truncated prematurely. If it thinks that it successfully found the root /// function, it will complete with `Ok(None)`, otherwise it will complete with `Err(...)`. /// However, the detection does not work in all cases, so you should expect `Err(...)` to /// be returned even during normal operation. As a result, it is not recommended to use /// this iterator as a `FallibleIterator`, because you might lose the entire stack if the /// last iteration returns `Err(...)`. /// /// Lifetimes: /// /// - `'u`: The lifetime of the [`Unwinder`]. /// - `'c`: The lifetime of the unwinder cache. /// - `'r`: The lifetime of the exclusive access to the `read_stack` callback. pub struct UnwindIterator<'u, 'c, 'r, U: Unwinder + ?Sized, F: FnMut(u64) -> Result> { unwinder: &'u U, state: UnwindIteratorState, regs: U::UnwindRegs, cache: &'c mut U::Cache, read_stack: &'r mut F, } enum UnwindIteratorState { Initial(u64), Unwinding(FrameAddress), Done, } impl<'u, 'c, 'r, U: Unwinder + ?Sized, F: FnMut(u64) -> Result> UnwindIterator<'u, 'c, 'r, U, F> { /// Create a new iterator. You'd usually use [`Unwinder::iter_frames`] instead. pub fn new( unwinder: &'u U, pc: u64, regs: U::UnwindRegs, cache: &'c mut U::Cache, read_stack: &'r mut F, ) -> Self { Self { unwinder, state: UnwindIteratorState::Initial(pc), regs, cache, read_stack, } } } impl<'u, 'c, 'r, U: Unwinder + ?Sized, F: FnMut(u64) -> Result> UnwindIterator<'u, 'c, 'r, U, F> { /// Yield the next frame in the stack. /// /// The first frame is `Ok(Some(FrameAddress::InstructionPointer(...)))`. /// Subsequent frames are `Ok(Some(FrameAddress::ReturnAddress(...)))`. /// /// If a root function has been reached, this iterator completes with `Ok(None)`. /// Otherwise it completes with `Err(...)`, usually indicating that a certain stack /// address could not be read. #[allow(clippy::should_implement_trait)] pub fn next(&mut self) -> Result, Error> { let next = match self.state { UnwindIteratorState::Initial(pc) => { self.state = UnwindIteratorState::Unwinding(FrameAddress::InstructionPointer(pc)); return Ok(Some(FrameAddress::InstructionPointer(pc))); } UnwindIteratorState::Unwinding(address) => { self.unwinder .unwind_frame(address, &mut self.regs, self.cache, self.read_stack)? } UnwindIteratorState::Done => return Ok(None), }; match next { Some(return_address) => { let return_address = FrameAddress::from_return_address(return_address) .ok_or(Error::ReturnAddressIsNull)?; self.state = UnwindIteratorState::Unwinding(return_address); Ok(Some(return_address)) } None => { self.state = UnwindIteratorState::Done; Ok(None) } } } } impl<'u, 'c, 'r, U: Unwinder + ?Sized, F: FnMut(u64) -> Result> FallibleIterator for UnwindIterator<'u, 'c, 'r, U, F> { type Item = FrameAddress; type Error = Error; fn next(&mut self) -> Result, Error> { self.next() } } /// This global generation counter makes it so that the cache can be shared /// between multiple unwinders. /// This is a u16, so if you make it wrap around by adding / removing modules /// more than 65535 times, then you risk collisions in the cache; meaning: /// unwinding might not work properly if an old unwind rule was found in the /// cache for the same address and the same (pre-wraparound) modules_generation. static GLOBAL_MODULES_GENERATION: AtomicU16 = AtomicU16::new(0); fn next_global_modules_generation() -> u16 { GLOBAL_MODULES_GENERATION.fetch_add(1, Ordering::Relaxed) } cfg_if::cfg_if! { if #[cfg(all(feature = "macho", feature = "pe"))] { pub trait Unwinding: Arch + DwarfUnwinding + InstructionAnalysis + CompactUnwindInfoUnwinding + PeUnwinding {} impl Unwinding for T {} } else if #[cfg(feature = "macho")] { pub trait Unwinding: Arch + DwarfUnwinding + InstructionAnalysis + CompactUnwindInfoUnwinding {} impl Unwinding for T {} } else if #[cfg(feature = "pe")] { pub trait Unwinding: Arch + DwarfUnwinding + InstructionAnalysis + PeUnwinding {} impl Unwinding for T {} } else { pub trait Unwinding: Arch + DwarfUnwinding + InstructionAnalysis {} impl Unwinding for T {} } } pub struct UnwinderInternal { /// sorted by avma_range.start modules: Vec>, /// Incremented every time modules is changed. modules_generation: u16, _arch: PhantomData, _allocation_policy: PhantomData

, } impl Default for UnwinderInternal { fn default() -> Self { Self::new() } } impl Clone for UnwinderInternal { fn clone(&self) -> Self { Self { modules: self.modules.clone(), modules_generation: self.modules_generation, _arch: PhantomData, _allocation_policy: PhantomData, } } } impl UnwinderInternal { pub fn new() -> Self { Self { modules: Vec::new(), modules_generation: next_global_modules_generation(), _arch: PhantomData, _allocation_policy: PhantomData, } } } impl, A: Unwinding, P: AllocationPolicy> UnwinderInternal { pub fn add_module(&mut self, module: Module) { let insertion_index = match self .modules .binary_search_by_key(&module.avma_range.start, |module| module.avma_range.start) { Ok(i) => { #[cfg(feature = "std")] eprintln!( "Now we have two modules at the same start address 0x{:x}. This can't be good.", module.avma_range.start ); i } Err(i) => i, }; self.modules.insert(insertion_index, module); self.modules_generation = next_global_modules_generation(); } pub fn remove_module(&mut self, module_address_range_start: u64) { if let Ok(index) = self .modules .binary_search_by_key(&module_address_range_start, |module| { module.avma_range.start }) { self.modules.remove(index); self.modules_generation = next_global_modules_generation(); }; } pub fn max_known_code_address(&self) -> u64 { self.modules.last().map_or(0, |m| m.avma_range.end) } fn find_module_for_address(&self, address: u64) -> Option<(usize, u32)> { let (module_index, module) = match self .modules .binary_search_by_key(&address, |m| m.avma_range.start) { Ok(i) => (i, &self.modules[i]), Err(insertion_index) => { if insertion_index == 0 { // address is before first known module return None; } let i = insertion_index - 1; let module = &self.modules[i]; if module.avma_range.end <= address { // address is after this module return None; } (i, module) } }; if address < module.base_avma { // Invalid base address return None; } let relative_address = u32::try_from(address - module.base_avma).ok()?; Some((module_index, relative_address)) } fn with_cache( &self, address: FrameAddress, regs: &mut A::UnwindRegs, cache: &mut Cache, read_stack: &mut F, callback: G, ) -> Result, Error> where F: FnMut(u64) -> Result, G: FnOnce( &Module, FrameAddress, u32, &mut A::UnwindRegs, &mut Cache, &mut F, ) -> Result, UnwinderError>, { let lookup_address = address.address_for_lookup(); let is_first_frame = !address.is_return_address(); let cache_handle = match cache .rule_cache .lookup(lookup_address, self.modules_generation) { CacheResult::Hit(unwind_rule) => { return unwind_rule.exec(is_first_frame, regs, read_stack); } CacheResult::Miss(handle) => handle, }; let unwind_rule = match self.find_module_for_address(lookup_address) { None => A::UnwindRule::fallback_rule(), Some((module_index, relative_lookup_address)) => { let module = &self.modules[module_index]; match callback( module, address, relative_lookup_address, regs, cache, read_stack, ) { Ok(UnwindResult::ExecRule(rule)) => rule, Ok(UnwindResult::Uncacheable(return_address)) => { return Ok(Some(return_address)) } Err(_err) => { // eprintln!("Unwinder error: {}", err); A::UnwindRule::fallback_rule() } } } }; cache.rule_cache.insert(cache_handle, unwind_rule); unwind_rule.exec(is_first_frame, regs, read_stack) } pub fn unwind_frame( &self, address: FrameAddress, regs: &mut A::UnwindRegs, cache: &mut Cache, read_stack: &mut F, ) -> Result, Error> where F: FnMut(u64) -> Result, { self.with_cache(address, regs, cache, read_stack, Self::unwind_frame_impl) } fn unwind_frame_impl( module: &Module, address: FrameAddress, rel_lookup_address: u32, regs: &mut A::UnwindRegs, cache: &mut Cache, read_stack: &mut F, ) -> Result, UnwinderError> where F: FnMut(u64) -> Result, { let is_first_frame = !address.is_return_address(); let unwind_result = match &*module.unwind_data { #[cfg(feature = "macho")] ModuleUnwindDataInternal::CompactUnwindInfoAndEhFrame { unwind_info, eh_frame, stubs_svma: stubs, stub_helper_svma: stub_helper, base_addresses, text_data, } => { // eprintln!("unwinding with cui and eh_frame in module {}", module.name); let text_bytes = text_data.as_ref().and_then(|data| { let offset_from_base = u32::try_from(data.svma_range.start.checked_sub(module.base_svma)?).ok()?; Some(TextBytes::new(offset_from_base, &data.bytes[..])) }); let stubs_range = if let Some(stubs_range) = stubs { ( (stubs_range.start - module.base_svma) as u32, (stubs_range.end - module.base_svma) as u32, ) } else { (0, 0) }; let stub_helper_range = if let Some(stub_helper_range) = stub_helper { ( (stub_helper_range.start - module.base_svma) as u32, (stub_helper_range.end - module.base_svma) as u32, ) } else { (0, 0) }; let mut unwinder = CompactUnwindInfoUnwinder::::new( &unwind_info[..], text_bytes, stubs_range, stub_helper_range, ); let unwind_result = unwinder.unwind_frame(rel_lookup_address, is_first_frame)?; match unwind_result { CuiUnwindResult::ExecRule(rule) => UnwindResult::ExecRule(rule), CuiUnwindResult::NeedDwarf(fde_offset) => { let eh_frame_data = eh_frame.as_deref().ok_or(UnwinderError::NoDwarfData)?; let mut dwarf_unwinder = DwarfUnwinder::<_, A, _>::new( EndianSlice::new(eh_frame_data, LittleEndian), UnwindSectionType::EhFrame, None, &mut cache.gimli_unwind_context, base_addresses.clone(), module.base_svma, ); dwarf_unwinder.unwind_frame_with_fde::<_, P::GimliEvaluationStorage<_>>( regs, is_first_frame, rel_lookup_address, fde_offset, read_stack, )? } } } ModuleUnwindDataInternal::EhFrameHdrAndEhFrame { eh_frame_hdr, eh_frame, base_addresses, } => { let eh_frame_hdr_data = &eh_frame_hdr[..]; let mut dwarf_unwinder = DwarfUnwinder::<_, A, _>::new( EndianSlice::new(eh_frame, LittleEndian), UnwindSectionType::EhFrame, Some(eh_frame_hdr_data), &mut cache.gimli_unwind_context, base_addresses.clone(), module.base_svma, ); let fde_offset = dwarf_unwinder .get_fde_offset_for_relative_address(rel_lookup_address) .ok_or(UnwinderError::EhFrameHdrCouldNotFindAddress)?; dwarf_unwinder.unwind_frame_with_fde::<_, P::GimliEvaluationStorage<_>>( regs, is_first_frame, rel_lookup_address, fde_offset, read_stack, )? } ModuleUnwindDataInternal::DwarfCfiIndexAndEhFrame { index, eh_frame, base_addresses, } => { let mut dwarf_unwinder = DwarfUnwinder::<_, A, _>::new( EndianSlice::new(eh_frame, LittleEndian), UnwindSectionType::EhFrame, None, &mut cache.gimli_unwind_context, base_addresses.clone(), module.base_svma, ); let fde_offset = index .fde_offset_for_relative_address(rel_lookup_address) .ok_or(UnwinderError::DwarfCfiIndexCouldNotFindAddress)?; dwarf_unwinder.unwind_frame_with_fde::<_, P::GimliEvaluationStorage<_>>( regs, is_first_frame, rel_lookup_address, fde_offset, read_stack, )? } ModuleUnwindDataInternal::DwarfCfiIndexAndDebugFrame { index, debug_frame, base_addresses, } => { let mut dwarf_unwinder = DwarfUnwinder::<_, A, _>::new( EndianSlice::new(debug_frame, LittleEndian), UnwindSectionType::DebugFrame, None, &mut cache.gimli_unwind_context, base_addresses.clone(), module.base_svma, ); let fde_offset = index .fde_offset_for_relative_address(rel_lookup_address) .ok_or(UnwinderError::DwarfCfiIndexCouldNotFindAddress)?; dwarf_unwinder.unwind_frame_with_fde::<_, P::GimliEvaluationStorage<_>>( regs, is_first_frame, rel_lookup_address, fde_offset, read_stack, )? } #[cfg(feature = "pe")] ModuleUnwindDataInternal::PeUnwindInfo { pdata, rdata, xdata, text, } => ::unwind_frame( crate::pe::PeSections { pdata, rdata: rdata.as_ref(), xdata: xdata.as_ref(), text: text.as_ref(), }, rel_lookup_address, regs, is_first_frame, read_stack, )?, ModuleUnwindDataInternal::None => return Err(UnwinderError::NoModuleUnwindData), }; Ok(unwind_result) } } /// The unwind data that should be used when unwinding addresses inside this module. /// Unwind data describes how to recover register values of the caller frame. /// /// The type of unwind information you use depends on the platform and what's available /// in the binary. /// /// Type arguments: /// /// - `D`: The type for unwind section data. This allows carrying owned data on the /// module, e.g. `Vec`. But it could also be a wrapper around mapped memory from /// a file or a different process, for example. It just needs to provide a slice of /// bytes via its `Deref` implementation. enum ModuleUnwindDataInternal { /// Used on macOS, with mach-O binaries. Compact unwind info is in the `__unwind_info` /// section and is sometimes supplemented with DWARF CFI information in the `__eh_frame` /// section. `__stubs` and `__stub_helper` ranges are used by the unwinder. #[cfg(feature = "macho")] CompactUnwindInfoAndEhFrame { unwind_info: D, eh_frame: Option, stubs_svma: Option>, stub_helper_svma: Option>, base_addresses: crate::dwarf::BaseAddresses, text_data: Option>, }, /// Used with ELF binaries (Linux and friends), in the `.eh_frame_hdr` and `.eh_frame` /// sections. Contains an index and DWARF CFI. EhFrameHdrAndEhFrame { eh_frame_hdr: D, eh_frame: D, base_addresses: crate::dwarf::BaseAddresses, }, /// Used with ELF binaries (Linux and friends), in the `.eh_frame` section. Contains /// DWARF CFI. We create a binary index for the FDEs when a module with this unwind /// data type is added. DwarfCfiIndexAndEhFrame { index: DwarfCfiIndex, eh_frame: D, base_addresses: crate::dwarf::BaseAddresses, }, /// Used with ELF binaries (Linux and friends), in the `.debug_frame` section. Contains /// DWARF CFI. We create a binary index for the FDEs when a module with this unwind /// data type is added. DwarfCfiIndexAndDebugFrame { index: DwarfCfiIndex, debug_frame: D, base_addresses: crate::dwarf::BaseAddresses, }, /// Used with PE binaries (Windows). #[cfg(feature = "pe")] PeUnwindInfo { pdata: D, rdata: Option>, xdata: Option>, text: Option>, }, /// No unwind information is used. Unwinding in this module will use a fallback rule /// (usually frame pointer unwinding). None, } impl> ModuleUnwindDataInternal { fn new(section_info: &mut impl ModuleSectionInfo) -> Self { use crate::dwarf::base_addresses_for_sections; #[cfg(feature = "macho")] if let Some(unwind_info) = section_info.section_data(b"__unwind_info") { let eh_frame = section_info.section_data(b"__eh_frame"); let stubs = section_info.section_svma_range(b"__stubs"); let stub_helper = section_info.section_svma_range(b"__stub_helper"); // Get the bytes of the executable code (instructions). // // In mach-O objects, executable code is stored in the `__TEXT` segment, which contains // multiple executable sections such as `__text`, `__stubs`, and `__stub_helper`. If we // don't have the full `__TEXT` segment contents, we can fall back to the contents of // just the `__text` section. let text_data = if let (Some(bytes), Some(svma_range)) = ( section_info.segment_data(b"__TEXT"), section_info.segment_svma_range(b"__TEXT"), ) { Some(TextByteData { bytes, svma_range }) } else if let (Some(bytes), Some(svma_range)) = ( section_info.section_data(b"__text"), section_info.section_svma_range(b"__text"), ) { Some(TextByteData { bytes, svma_range }) } else { None }; return ModuleUnwindDataInternal::CompactUnwindInfoAndEhFrame { unwind_info, eh_frame, stubs_svma: stubs, stub_helper_svma: stub_helper, base_addresses: base_addresses_for_sections(section_info), text_data, }; } #[cfg(feature = "pe")] if let Some(pdata) = section_info.section_data(b".pdata") { let mut range_and_data = |name| { let rva_range = section_info.section_svma_range(name).and_then(|range| { Some(Range { start: (range.start - section_info.base_svma()).try_into().ok()?, end: (range.end - section_info.base_svma()).try_into().ok()?, }) })?; let data = section_info.section_data(name)?; Some(DataAtRvaRange { data, rva_range }) }; return ModuleUnwindDataInternal::PeUnwindInfo { pdata, rdata: range_and_data(b".rdata"), xdata: range_and_data(b".xdata"), text: range_and_data(b".text"), }; } if let Some(eh_frame) = section_info .section_data(b".eh_frame") .or_else(|| section_info.section_data(b"__eh_frame")) { if let Some(eh_frame_hdr) = section_info .section_data(b".eh_frame_hdr") .or_else(|| section_info.section_data(b"__eh_frame_hdr")) { ModuleUnwindDataInternal::EhFrameHdrAndEhFrame { eh_frame_hdr, eh_frame, base_addresses: base_addresses_for_sections(section_info), } } else { match DwarfCfiIndex::try_new_eh_frame(&eh_frame, section_info) { Ok(index) => ModuleUnwindDataInternal::DwarfCfiIndexAndEhFrame { index, eh_frame, base_addresses: base_addresses_for_sections(section_info), }, Err(_) => ModuleUnwindDataInternal::None, } } } else if let Some(debug_frame) = section_info.section_data(b".debug_frame") { match DwarfCfiIndex::try_new_debug_frame(&debug_frame, section_info) { Ok(index) => ModuleUnwindDataInternal::DwarfCfiIndexAndDebugFrame { index, debug_frame, base_addresses: base_addresses_for_sections(section_info), }, Err(_) => ModuleUnwindDataInternal::None, } } else { ModuleUnwindDataInternal::None } } } /// Used to supply raw instruction bytes to the unwinder, which uses it to analyze /// instructions in order to provide high quality unwinding inside function prologues and /// epilogues. /// /// This is only needed on macOS, because mach-O `__unwind_info` and `__eh_frame` only /// cares about accuracy in function bodies, not in function prologues and epilogues. /// /// On Linux, compilers produce `.eh_frame` and `.debug_frame` which provides correct /// unwind information for all instructions including those in function prologues and /// epilogues, so instruction analysis is not needed. /// /// Type arguments: /// /// - `D`: The type for unwind section data. This allows carrying owned data on the /// module, e.g. `Vec`. But it could also be a wrapper around mapped memory from /// a file or a different process, for example. It just needs to provide a slice of /// bytes via its `Deref` implementation. #[cfg(feature = "macho")] struct TextByteData { pub bytes: D, pub svma_range: Range, } /// Information about a module that is loaded in a process. You might know this under a /// different name, for example: (Shared) library, binary image, DSO ("Dynamic shared object") /// /// The unwinder needs to have an up-to-date list of modules so that it can match an /// absolute address to the right module, and so that it can find that module's unwind /// information. /// /// Type arguments: /// /// - `D`: The type for unwind section data. This allows carrying owned data on the /// module, e.g. `Vec`. But it could also be a wrapper around mapped memory from /// a file or a different process, for example. It just needs to provide a slice of /// bytes via its `Deref` implementation. pub struct Module { /// The name or file path of the module. Unused, it's just there for easier debugging. #[allow(unused)] name: String, /// The address range where this module is mapped into the process. avma_range: Range, /// The base address of this module, in the process's address space. On Linux, the base /// address can sometimes be different from the start address of the mapped range. base_avma: u64, /// The base address of this module, according to the module. base_svma: u64, /// The unwind data that should be used for unwinding addresses from this module. unwind_data: Arc>, } impl Clone for Module { fn clone(&self) -> Self { Self { name: self.name.clone(), avma_range: self.avma_range.clone(), base_avma: self.base_avma, base_svma: self.base_svma, unwind_data: self.unwind_data.clone(), } } } /// Information about a module's sections (and segments). /// /// This trait is used as an interface to module information, and each function with `&mut self` is /// called at most once with a particular argument (e.g., `section_data(b".text")` will be called /// at most once, so it can move data out of the underlying type if desired). /// /// Type arguments: /// /// - `D`: The type for section data. This allows carrying owned data on the module, e.g. /// `Vec`. But it could also be a wrapper around mapped memory from a file or a different /// process, for example. pub trait ModuleSectionInfo { /// Return the base address stated in the module. /// /// For mach-O objects, this is the vmaddr of the __TEXT segment. For ELF objects, this is /// zero. For PE objects, this is the image base address. /// /// This is used to convert between SVMAs and relative addresses. fn base_svma(&self) -> u64; /// Get the given section's memory range, as stated in the module. fn section_svma_range(&mut self, name: &[u8]) -> Option>; /// Get the given section's data. This will only be called once per section. fn section_data(&mut self, name: &[u8]) -> Option; /// Get the given segment's memory range, as stated in the module. fn segment_svma_range(&mut self, _name: &[u8]) -> Option> { None } /// Get the given segment's data. This will only be called once per segment. fn segment_data(&mut self, _name: &[u8]) -> Option { None } } /// Explicit addresses and data of various sections in the module. This implements /// the `ModuleSectionInfo` trait. /// /// Unless otherwise stated, these are SVMAs, "stated virtual memory addresses", i.e. addresses as /// stated in the object, as opposed to AVMAs, "actual virtual memory addresses", i.e. addresses in /// the virtual memory of the profiled process. /// /// Code addresses inside a module's unwind information are usually written down as SVMAs, /// or as relative addresses. For example, DWARF CFI can have code addresses expressed as /// relative-to-.text addresses or as absolute SVMAs. And mach-O compact unwind info /// contains addresses relative to the image base address. #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct ExplicitModuleSectionInfo { /// The image base address, as stated in the object. For mach-O objects, this is the /// vmaddr of the `__TEXT` segment. For ELF objects, this is zero. /// /// This is used to convert between SVMAs and relative addresses. pub base_svma: u64, /// The address range of the `__text` or `.text` section. This is where most of the compiled /// code is stored. /// /// This is used to detect whether we need to do instruction analysis for an address. pub text_svma: Option>, /// The data of the `__text` or `.text` section. This is where most of the compiled code is /// stored. For mach-O binaries, this does not need to be supplied if `text_segment` is supplied. /// /// This is used to handle function prologues and epilogues in some cases. pub text: Option, /// The address range of the mach-O `__stubs` section. Contains small pieces of /// executable code for calling imported functions. Code inside this section is not /// covered by the unwind information in `__unwind_info`. /// /// This is used to exclude addresses in this section from incorrectly applying /// `__unwind_info` opcodes. It is also used to infer unwind rules for the known /// structure of stub functions. pub stubs_svma: Option>, /// The address range of the mach-O `__stub_helper` section. Contains small pieces of /// executable code for calling imported functions. Code inside this section is not /// covered by the unwind information in `__unwind_info`. /// /// This is used to exclude addresses in this section from incorrectly applying /// `__unwind_info` opcodes. It is also used to infer unwind rules for the known /// structure of stub helper /// functions. pub stub_helper_svma: Option>, /// The address range of the `.got` section (Global Offset Table). This is used /// during DWARF CFI processing, to resolve got-relative addresses. pub got_svma: Option>, /// The data of the `__unwind_info` section of mach-O binaries. pub unwind_info: Option, /// The address range of the `__eh_frame` or `.eh_frame` section. This is used during DWARF CFI /// processing, to resolve eh_frame-relative addresses. pub eh_frame_svma: Option>, /// The data of the `__eh_frame` or `.eh_frame` section. This is used during DWARF CFI /// processing, to resolve eh_frame-relative addresses. pub eh_frame: Option, /// The address range of the `.eh_frame_hdr` section. This is used during DWARF CFI processing, /// to resolve eh_frame_hdr-relative addresses. pub eh_frame_hdr_svma: Option>, /// The data of the `.eh_frame_hdr` section. This is used during DWARF CFI processing, to /// resolve eh_frame_hdr-relative addresses. pub eh_frame_hdr: Option, /// The data of the `.debug_frame` section. The related address range is not needed. pub debug_frame: Option, /// The address range of the `__TEXT` segment of mach-O binaries, if available. pub text_segment_svma: Option>, /// The data of the `__TEXT` segment of mach-O binaries, if available. pub text_segment: Option, } impl ModuleSectionInfo for ExplicitModuleSectionInfo where D: Deref, { fn base_svma(&self) -> u64 { self.base_svma } fn section_svma_range(&mut self, name: &[u8]) -> Option> { match name { b"__text" | b".text" => self.text_svma.clone(), b"__stubs" => self.stubs_svma.clone(), b"__stub_helper" => self.stub_helper_svma.clone(), b"__eh_frame" | b".eh_frame" => self.eh_frame_svma.clone(), b"__eh_frame_hdr" | b".eh_frame_hdr" => self.eh_frame_hdr_svma.clone(), b"__got" | b".got" => self.got_svma.clone(), _ => None, } } fn section_data(&mut self, name: &[u8]) -> Option { match name { b"__text" | b".text" => self.text.take(), b"__unwind_info" => self.unwind_info.take(), b"__eh_frame" | b".eh_frame" => self.eh_frame.take(), b"__eh_frame_hdr" | b".eh_frame_hdr" => self.eh_frame_hdr.take(), b"__debug_frame" | b".debug_frame" => self.debug_frame.take(), _ => None, } } fn segment_svma_range(&mut self, name: &[u8]) -> Option> { match name { b"__TEXT" => self.text_segment_svma.clone(), _ => None, } } fn segment_data(&mut self, name: &[u8]) -> Option { match name { b"__TEXT" => self.text_segment.take(), _ => None, } } } impl> Module { pub fn new( name: String, avma_range: core::ops::Range, base_avma: u64, mut section_info: impl ModuleSectionInfo, ) -> Self { let unwind_data = ModuleUnwindDataInternal::new(&mut section_info); Self { name, avma_range, base_avma, base_svma: section_info.base_svma(), unwind_data: Arc::new(unwind_data), } } pub fn avma_range(&self) -> core::ops::Range { self.avma_range.clone() } pub fn base_avma(&self) -> u64 { self.base_avma } pub fn name(&self) -> &str { &self.name } } framehop-0.13.0/src/x86_64/arch.rs000064400000000000000000000004201046102023000145120ustar 00000000000000use super::unwind_rule::UnwindRuleX86_64; use super::unwindregs::UnwindRegsX86_64; use crate::arch::Arch; /// The x86_64 CPU architecture. pub struct ArchX86_64; impl Arch for ArchX86_64 { type UnwindRule = UnwindRuleX86_64; type UnwindRegs = UnwindRegsX86_64; } framehop-0.13.0/src/x86_64/cache.rs000064400000000000000000000013671046102023000146530ustar 00000000000000use super::unwind_rule::*; use crate::cache::*; /// The unwinder cache type for [`UnwinderX86_64`](super::UnwinderX86_64). pub struct CacheX86_64( pub Cache, ); impl CacheX86_64 { /// Create a new cache. pub fn new() -> Self { Self(Cache::new()) } } impl CacheX86_64

{ /// Create a new cache. pub fn new_in() -> Self { Self(Cache::new()) } /// Returns a snapshot of the cache usage statistics. pub fn stats(&self) -> CacheStats { self.0.rule_cache.stats() } } impl Default for CacheX86_64

{ fn default() -> Self { Self::new_in() } } framehop-0.13.0/src/x86_64/dwarf.rs000064400000000000000000000143311046102023000147060ustar 00000000000000use gimli::{ CfaRule, Encoding, EvaluationStorage, Reader, ReaderOffset, Register, RegisterRule, UnwindContextStorage, UnwindSection, UnwindTableRow, X86_64, }; use super::{arch::ArchX86_64, unwind_rule::UnwindRuleX86_64, unwindregs::UnwindRegsX86_64}; use crate::dwarf::{ eval_cfa_rule, eval_register_rule, ConversionError, DwarfUnwindRegs, DwarfUnwinderError, DwarfUnwinding, }; use crate::unwind_result::UnwindResult; impl DwarfUnwindRegs for UnwindRegsX86_64 { fn get(&self, register: Register) -> Option { match register { X86_64::RA => Some(self.ip()), X86_64::RSP => Some(self.sp()), X86_64::RBP => Some(self.bp()), _ => None, } } } impl DwarfUnwinding for ArchX86_64 { fn unwind_frame( section: &impl UnwindSection, unwind_info: &UnwindTableRow, encoding: Encoding, regs: &mut Self::UnwindRegs, is_first_frame: bool, read_stack: &mut F, ) -> Result, DwarfUnwinderError> where F: FnMut(u64) -> Result, R: Reader, UCS: UnwindContextStorage, ES: EvaluationStorage, { let cfa_rule = unwind_info.cfa(); let bp_rule = unwind_info.register(X86_64::RBP); let ra_rule = unwind_info.register(X86_64::RA); match translate_into_unwind_rule(cfa_rule, &bp_rule, &ra_rule) { Ok(unwind_rule) => return Ok(UnwindResult::ExecRule(unwind_rule)), Err(_err) => { // Could not translate into a cacheable unwind rule. Fall back to the generic path. // eprintln!("Unwind rule translation failed: {:?}", err); } } let cfa = eval_cfa_rule::(section, cfa_rule, encoding, regs) .ok_or(DwarfUnwinderError::CouldNotRecoverCfa)?; let ip = regs.ip(); let bp = regs.bp(); let sp = regs.sp(); let new_bp = eval_register_rule::( section, bp_rule, cfa, encoding, bp, regs, read_stack, ) .unwrap_or(bp); let return_address = match eval_register_rule::( section, ra_rule, cfa, encoding, ip, regs, read_stack, ) { Some(ra) => ra, None => { read_stack(cfa - 8).map_err(|_| DwarfUnwinderError::CouldNotRecoverReturnAddress)? } }; if cfa == sp && return_address == ip { return Err(DwarfUnwinderError::DidNotAdvance); } if !is_first_frame && cfa < regs.sp() { return Err(DwarfUnwinderError::StackPointerMovedBackwards); } regs.set_ip(return_address); regs.set_bp(new_bp); regs.set_sp(cfa); Ok(UnwindResult::Uncacheable(return_address)) } fn rule_if_uncovered_by_fde() -> Self::UnwindRule { UnwindRuleX86_64::JustReturnIfFirstFrameOtherwiseFp } } fn register_rule_to_cfa_offset( rule: &RegisterRule, ) -> Result, ConversionError> { match *rule { RegisterRule::Undefined | RegisterRule::SameValue => Ok(None), RegisterRule::Offset(offset) => Ok(Some(offset)), _ => Err(ConversionError::RegisterNotStoredRelativeToCfa), } } fn translate_into_unwind_rule( cfa_rule: &CfaRule, bp_rule: &RegisterRule, ra_rule: &RegisterRule, ) -> Result { match ra_rule { RegisterRule::Undefined => { // No return address. This means that we've reached the end of the stack. return Ok(UnwindRuleX86_64::EndOfStack); } RegisterRule::Offset(offset) if *offset == -8 => { // This is normal case. Return address is [CFA-8]. } RegisterRule::Offset(_) => { // Unsupported, will have to use the slow path. return Err(ConversionError::ReturnAddressRuleWithUnexpectedOffset); } _ => { // Unsupported, will have to use the slow path. return Err(ConversionError::ReturnAddressRuleWasWeird); } } match cfa_rule { CfaRule::RegisterAndOffset { register, offset } => match *register { X86_64::RSP => { let sp_offset_by_8 = u16::try_from(offset / 8).map_err(|_| ConversionError::SpOffsetDoesNotFit)?; let fp_cfa_offset = register_rule_to_cfa_offset(bp_rule)?; match fp_cfa_offset { None => Ok(UnwindRuleX86_64::OffsetSp { sp_offset_by_8 }), Some(bp_cfa_offset) => { let bp_storage_offset_from_sp_by_8 = i16::try_from((offset + bp_cfa_offset) / 8) .map_err(|_| ConversionError::FpStorageOffsetDoesNotFit)?; Ok(UnwindRuleX86_64::OffsetSpAndRestoreBp { sp_offset_by_8, bp_storage_offset_from_sp_by_8, }) } } } X86_64::RBP => { let bp_cfa_offset = register_rule_to_cfa_offset(bp_rule)? .ok_or(ConversionError::FramePointerRuleDoesNotRestoreBp)?; if *offset == 16 && bp_cfa_offset == -16 { Ok(UnwindRuleX86_64::UseFramePointer) } else { // TODO: Maybe handle this case. This case has been observed in _ffi_call_unix64, // which has the following unwind table: // // 00000060 00000024 0000001c FDE cie=00000048 pc=000de548...000de6a6 // 0xde548: CFA=reg7+8: reg16=[CFA-8] // 0xde562: CFA=reg6+32: reg6=[CFA-16], reg16=[CFA-8] // 0xde5ad: CFA=reg7+8: reg16=[CFA-8] // 0xde668: CFA=reg7+8: reg6=[CFA-16], reg16=[CFA-8] Err(ConversionError::FramePointerRuleHasStrangeBpOffset) } } _ => Err(ConversionError::CfaIsOffsetFromUnknownRegister), }, CfaRule::Expression(_) => Err(ConversionError::CfaIsExpression), } } framehop-0.13.0/src/x86_64/instruction_analysis/epilogue.rs000064400000000000000000000061301046102023000216760ustar 00000000000000use super::super::unwind_rule::UnwindRuleX86_64; pub fn unwind_rule_from_detected_epilogue( text_bytes: &[u8], pc_offset: usize, ) -> Option { let (slice_from_start, slice_to_end) = text_bytes.split_at(pc_offset); let mut sp_offset_by_8 = 0; let mut bp_offset_by_8 = None; let mut bytes = slice_to_end; loop { if bytes.is_empty() { return None; } // Detect ret if bytes[0] == 0xc3 { break; } // Detect jmp if bytes[0] == 0xeb || bytes[0] == 0xe9 || bytes[0] == 0xff { // This could be a tail call, or just a regular jump inside the current function. // Ideally, we would check whether the jump target is inside this function. // But this would require having an accurate idea of where the current function // starts and ends. // For now, we instead use the following heuristic: Any jmp that directly follows // a `pop` instruction is treated as a tail call. if sp_offset_by_8 != 0 { // We have detected a pop in the previous loop iteration. break; } // This must be the first iteration. Look backwards. if let Some(potential_pop_byte) = slice_from_start.last() { // Get the previous byte. We have no idea how long the previous instruction // is, so we might be looking at a random last byte of a wider instruction. // Let's just pray that this is not the case. if potential_pop_byte & 0xf8 == 0x58 { // Assuming we haven't just misinterpreted the last byte of a wider // instruction, this is a `pop rXX`. break; } } return None; } // Detect pop rbp if bytes[0] == 0x5d { bp_offset_by_8 = Some(sp_offset_by_8 as i16); sp_offset_by_8 += 1; bytes = &bytes[1..]; continue; } // Detect pop rXX if (0x58..=0x5f).contains(&bytes[0]) { sp_offset_by_8 += 1; bytes = &bytes[1..]; continue; } // Detect pop rXX with prefix if bytes.len() >= 2 && bytes[0] & 0xfe == 0x40 && bytes[1] & 0xf8 == 0x58 { sp_offset_by_8 += 1; bytes = &bytes[2..]; continue; } // Unexpected instruction. // This probably means that we weren't in an epilogue after all. return None; } // We've found the return or the tail call. let rule = if sp_offset_by_8 == 0 { UnwindRuleX86_64::JustReturn } else { sp_offset_by_8 += 1; // Add one for popping the return address. if let Some(bp_storage_offset_from_sp_by_8) = bp_offset_by_8 { UnwindRuleX86_64::OffsetSpAndRestoreBp { sp_offset_by_8, bp_storage_offset_from_sp_by_8, } } else { UnwindRuleX86_64::OffsetSp { sp_offset_by_8 } } }; Some(rule) } framehop-0.13.0/src/x86_64/instruction_analysis/mod.rs000064400000000000000000000012151046102023000206430ustar 00000000000000use super::arch::ArchX86_64; use crate::instruction_analysis::InstructionAnalysis; mod epilogue; mod prologue; use epilogue::unwind_rule_from_detected_epilogue; use prologue::unwind_rule_from_detected_prologue; impl InstructionAnalysis for ArchX86_64 { fn rule_from_prologue_analysis( text_bytes: &[u8], pc_offset: usize, ) -> Option { unwind_rule_from_detected_prologue(text_bytes, pc_offset) } fn rule_from_epilogue_analysis( text_bytes: &[u8], pc_offset: usize, ) -> Option { unwind_rule_from_detected_epilogue(text_bytes, pc_offset) } } framehop-0.13.0/src/x86_64/instruction_analysis/prologue.rs000064400000000000000000000062501046102023000217240ustar 00000000000000use super::super::unwind_rule::UnwindRuleX86_64; pub fn unwind_rule_from_detected_prologue( text_bytes: &[u8], pc_offset: usize, ) -> Option { let (slice_from_start, slice_to_end) = text_bytes.split_at(pc_offset); if !is_next_instruction_expected_in_prologue(slice_to_end) { return None; } // We're in a prologue. Find the current stack depth of this frame by // walking backwards. This is risky business, because x86 is a variable // length encoding so you never know what you're looking at if you look // backwards. // Let's do it anyway and hope our heuristics are good enough so that // they work in more cases than they fail in. let mut cursor = slice_from_start.len(); let mut sp_offset_by_8 = 0; loop { if cursor >= 4 { // Detect push rbp; mov rbp, rsp [0x55, 0x48 0x89 0xe5] if slice_from_start[cursor - 4..cursor] == [0x55, 0x48, 0x89, 0xe5] { return Some(UnwindRuleX86_64::UseFramePointer); } } if cursor >= 1 { // Detect push rXX with optional prefix let byte = slice_from_start[cursor - 1]; if byte & 0xf8 == 0x50 { sp_offset_by_8 += 1; cursor -= 1; // Consume prefix, if present if cursor >= 1 && slice_from_start[cursor - 1] & 0xfe == 0x40 { cursor -= 1; } continue; } } break; } sp_offset_by_8 += 1; // Add one for popping the return address. Some(UnwindRuleX86_64::OffsetSp { sp_offset_by_8 }) } fn is_next_instruction_expected_in_prologue(bytes: &[u8]) -> bool { if bytes.len() < 4 { return false; } // Detect push rXX if bytes[0] & 0xf8 == 0x50 { return true; } // Detect push rXX with prefix if bytes[0] & 0xfe == 0x40 && bytes[1] & 0xf8 == 0x50 { return true; } // Detect sub rsp, 0xXX (8-bit immediate operand) if bytes[0..2] == [0x83, 0xec] { return true; } // Detect sub rsp, 0xXX with prefix (8-bit immediate operand) if bytes[0..3] == [0x48, 0x83, 0xec] { return true; } // Detect sub rsp, 0xXX (32-bit immediate operand) if bytes[0..2] == [0x81, 0xec] { return true; } // Detect sub rsp, 0xXX with prefix (32-bit immediate operand) if bytes[0..3] == [0x48, 0x81, 0xec] { return true; } // Detect mov rbp, rsp [0x48 0x89 0xe5] if bytes[0..3] == [0x48, 0x89, 0xe5] { return true; } false } // TODO: Write tests for different "sub" types // 4e88e40 41 57 push r15 // 4e88e42 41 56 push r14 // 4e88e44 53 push rbx // 4e88e45 48 81 EC 80 00 00 00 sub rsp, 0x80 // 4e88e4c 48 89 F3 mov rbx, rsi // // // 4423f9 55 push rbp // 4423fa 48 89 E5 mov rbp, rsp // 4423fd 41 57 push r15 // 4423ff 41 56 push r14 // 442401 41 55 push r13 // 442403 41 54 push r12 // 442405 53 push rbx // 442406 48 83 EC 18 sub rsp, 0x18 // 44240a 48 8B 07 mov rax, qword [rdi] framehop-0.13.0/src/x86_64/macho.rs000064400000000000000000000200011046102023000146610ustar 00000000000000use super::arch::ArchX86_64; use super::unwind_rule::UnwindRuleX86_64; use crate::instruction_analysis::InstructionAnalysis; use crate::macho::{CompactUnwindInfoUnwinderError, CompactUnwindInfoUnwinding, CuiUnwindResult}; use macho_unwind_info::opcodes::{OpcodeX86_64, RegisterNameX86_64}; use macho_unwind_info::Function; impl CompactUnwindInfoUnwinding for ArchX86_64 { fn unwind_frame( function: Function, is_first_frame: bool, address_offset_within_function: usize, function_bytes: Option<&[u8]>, ) -> Result, CompactUnwindInfoUnwinderError> { let opcode = OpcodeX86_64::parse(function.opcode); if is_first_frame { // The pc might be in a prologue or an epilogue. The compact unwind info format ignores // prologues and epilogues; the opcodes only describe the function body. So we do some // instruction analysis to check for prologues and epilogues. if let Some(function_bytes) = function_bytes { if let Some(rule) = Self::rule_from_instruction_analysis( function_bytes, address_offset_within_function, ) { // We are inside a prologue / epilogue. Ignore the opcode and use the rule from // instruction analysis. return Ok(CuiUnwindResult::ExecRule(rule)); } if opcode == OpcodeX86_64::Null && function_bytes.starts_with(&[0x55, 0x48, 0x89, 0xe5]) { // The function is uncovered but it has a `push rbp; mov rbp, rsp` prologue. return Ok(CuiUnwindResult::ExecRule(UnwindRuleX86_64::UseFramePointer)); } } if opcode == OpcodeX86_64::Null { return Ok(CuiUnwindResult::ExecRule(UnwindRuleX86_64::JustReturn)); } } // At this point we know with high certainty that we are in a function body. let r = match opcode { OpcodeX86_64::Null => { return Err(CompactUnwindInfoUnwinderError::FunctionHasNoInfo); } OpcodeX86_64::FramelessImmediate { stack_size_in_bytes, saved_regs, } => { if stack_size_in_bytes == 8 { CuiUnwindResult::ExecRule(UnwindRuleX86_64::JustReturn) } else { let bp_positon_from_outside = saved_regs .iter() .rev() .flatten() .position(|r| *r == RegisterNameX86_64::Rbp); match bp_positon_from_outside { Some(pos) => { let bp_offset_from_sp = stack_size_in_bytes as i32 - 2 * 8 - pos as i32 * 8; let bp_storage_offset_from_sp_by_8 = i16::try_from(bp_offset_from_sp / 8).map_err(|_| { CompactUnwindInfoUnwinderError::BpOffsetDoesNotFit })?; CuiUnwindResult::ExecRule(UnwindRuleX86_64::OffsetSpAndRestoreBp { sp_offset_by_8: stack_size_in_bytes / 8, bp_storage_offset_from_sp_by_8, }) } None => CuiUnwindResult::ExecRule(UnwindRuleX86_64::OffsetSp { sp_offset_by_8: stack_size_in_bytes / 8, }), } } } OpcodeX86_64::FramelessIndirect { immediate_offset_from_function_start, stack_adjust_in_bytes, saved_regs, } => { let function_bytes = function_bytes.ok_or( CompactUnwindInfoUnwinderError::NoTextBytesToLookUpIndirectStackOffset, )?; let sub_immediate_bytes = function_bytes .get( immediate_offset_from_function_start as usize ..immediate_offset_from_function_start as usize + 4, ) .ok_or(CompactUnwindInfoUnwinderError::IndirectStackOffsetOutOfBounds)?; let sub_immediate = u32::from_le_bytes([ sub_immediate_bytes[0], sub_immediate_bytes[1], sub_immediate_bytes[2], sub_immediate_bytes[3], ]); let stack_size_in_bytes = sub_immediate .checked_add(stack_adjust_in_bytes.into()) .ok_or(CompactUnwindInfoUnwinderError::StackAdjustOverflow)?; let sp_offset_by_8 = u16::try_from(stack_size_in_bytes / 8) .map_err(|_| CompactUnwindInfoUnwinderError::StackSizeDoesNotFit)?; let bp_positon_from_outside = saved_regs .iter() .rev() .flatten() .position(|r| *r == RegisterNameX86_64::Rbp); match bp_positon_from_outside { Some(pos) => { let bp_offset_from_sp = stack_size_in_bytes as i32 - 2 * 8 - pos as i32 * 8; let bp_storage_offset_from_sp_by_8 = i16::try_from(bp_offset_from_sp / 8) .map_err(|_| CompactUnwindInfoUnwinderError::BpOffsetDoesNotFit)?; CuiUnwindResult::ExecRule(UnwindRuleX86_64::OffsetSpAndRestoreBp { sp_offset_by_8, bp_storage_offset_from_sp_by_8, }) } None => { CuiUnwindResult::ExecRule(UnwindRuleX86_64::OffsetSp { sp_offset_by_8 }) } } } OpcodeX86_64::Dwarf { eh_frame_fde } => CuiUnwindResult::NeedDwarf(eh_frame_fde), OpcodeX86_64::FrameBased { .. } => { CuiUnwindResult::ExecRule(UnwindRuleX86_64::UseFramePointer) } OpcodeX86_64::UnrecognizedKind(kind) => { return Err(CompactUnwindInfoUnwinderError::BadOpcodeKind(kind)) } OpcodeX86_64::InvalidFrameless => { return Err(CompactUnwindInfoUnwinderError::InvalidFrameless) } }; Ok(r) } fn rule_for_stub_helper( offset: u32, ) -> Result, CompactUnwindInfoUnwinderError> { // shared: // +0x0 235cc4 4C 8D 1D 3D 03 04 00 lea r11, qword [dyld_stub_binder_276000+8] // +0x7 235ccb 41 53 push r11 // +0x9 235ccd FF 25 2D 03 04 00 jmp qword [dyld_stub_binder_276000] ; tail call // +0xf 235cd3 90 nop // first stub: // +0x10 235cd4 68 F1 61 00 00 push 0x61f1 // +0x15 235cd9 E9 E6 FF FF FF jmp 0x235cc4 ; jump to shared // second stub: // +0x1a 235cde 68 38 62 00 00 push 0x6238 // +0x1f 235ce3 E9 DC FF FF FF jmp 0x235cc4 ; jump to shared let rule = if offset < 0x7 { // pop 1 and return UnwindRuleX86_64::OffsetSp { sp_offset_by_8: 2 } } else if offset < 0x10 { // pop 2 and return UnwindRuleX86_64::OffsetSp { sp_offset_by_8: 3 } } else { let offset_after_shared = offset - 0x10; let offset_within_stub = offset_after_shared % 10; if offset_within_stub < 5 { UnwindRuleX86_64::JustReturn // just return } else { // pop 1 and return UnwindRuleX86_64::OffsetSp { sp_offset_by_8: 2 } } }; Ok(CuiUnwindResult::ExecRule(rule)) } } framehop-0.13.0/src/x86_64/mod.rs000064400000000000000000000004541046102023000143630ustar 00000000000000mod arch; mod cache; mod dwarf; mod instruction_analysis; #[cfg(feature = "macho")] mod macho; #[cfg(feature = "pe")] mod pe; mod register_ordering; mod unwind_rule; mod unwinder; mod unwindregs; pub use arch::*; pub use cache::*; pub use unwind_rule::*; pub use unwinder::*; pub use unwindregs::*; framehop-0.13.0/src/x86_64/pe.rs000064400000000000000000000204161046102023000142100ustar 00000000000000use super::{ arch::ArchX86_64, unwind_rule::{OffsetOrPop, UnwindRuleX86_64}, unwindregs::Reg, }; use crate::arch::Arch; use crate::pe::{PeSections, PeUnwinderError, PeUnwinding}; use crate::unwind_result::UnwindResult; use core::ops::ControlFlow; use alloc::vec::Vec; use pe_unwind_info::x86_64::{ FunctionEpilogInstruction, FunctionTableEntries, Register, UnwindInfo, UnwindInfoTrailer, UnwindOperation, UnwindState, }; struct State<'a, F> { regs: &'a mut ::UnwindRegs, read_stack: &'a mut F, } impl UnwindState for State<'_, F> where F: FnMut(u64) -> Result, { fn read_register(&mut self, register: Register) -> u64 { self.regs.get(convert_pe_register(register)) } fn read_stack(&mut self, addr: u64) -> Option { (self.read_stack)(addr).ok() } fn write_register(&mut self, register: Register, value: u64) { self.regs.set(convert_pe_register(register), value) } fn write_xmm_register(&mut self, _register: pe_unwind_info::x86_64::XmmRegister, _value: u128) { // Ignore } } fn convert_pe_register(r: Register) -> Reg { match r { Register::RAX => Reg::RAX, Register::RCX => Reg::RCX, Register::RDX => Reg::RDX, Register::RBX => Reg::RBX, Register::RSP => Reg::RSP, Register::RBP => Reg::RBP, Register::RSI => Reg::RSI, Register::RDI => Reg::RDI, Register::R8 => Reg::R8, Register::R9 => Reg::R9, Register::R10 => Reg::R10, Register::R11 => Reg::R11, Register::R12 => Reg::R12, Register::R13 => Reg::R13, Register::R14 => Reg::R14, Register::R15 => Reg::R15, } } impl From<&'_ FunctionEpilogInstruction> for OffsetOrPop { fn from(value: &'_ FunctionEpilogInstruction) -> Self { match value { FunctionEpilogInstruction::AddSP(offset) => { if let Ok(v) = (offset / 8).try_into() { OffsetOrPop::OffsetBy8(v) } else { OffsetOrPop::None } } FunctionEpilogInstruction::Pop(reg) => OffsetOrPop::Pop(convert_pe_register(*reg)), _ => OffsetOrPop::None, } } } impl From<&'_ UnwindOperation> for OffsetOrPop { fn from(value: &'_ UnwindOperation) -> Self { match value { UnwindOperation::UnStackAlloc(offset) => { if let Ok(v) = (offset / 8).try_into() { OffsetOrPop::OffsetBy8(v) } else { OffsetOrPop::None } } UnwindOperation::PopNonVolatile(reg) => OffsetOrPop::Pop(convert_pe_register(*reg)), _ => OffsetOrPop::None, } } } impl PeUnwinding for ArchX86_64 { fn unwind_frame( sections: PeSections, address: u32, regs: &mut Self::UnwindRegs, is_first_frame: bool, read_stack: &mut F, ) -> Result, PeUnwinderError> where F: FnMut(u64) -> Result, D: core::ops::Deref, { let entries = FunctionTableEntries::parse(sections.pdata); let Some(function) = entries.lookup(address) else { return Ok(UnwindResult::ExecRule(UnwindRuleX86_64::JustReturn)); }; let read_stack_err = |read_stack: &mut F, addr| { read_stack(addr).map_err(|()| PeUnwinderError::MissingStackData(Some(addr))) }; let unwind_info_address = function.unwind_info_address.get(); let unwind_info = UnwindInfo::parse(sections.unwind_info_memory_at_rva(unwind_info_address)?) .ok_or(PeUnwinderError::UnwindInfoParseError)?; if is_first_frame { // Check whether the address is in the function epilog. If so, we need to // simulate the remaining epilog instructions (unwind codes don't account for // unwinding from the epilog). We only need to check this for the first unwind info (if // there are chained infos). let bytes = (function.end_address.get() - address) as usize; let instruction = §ions.text_memory_at_rva(address)?[..bytes]; if let Ok(epilog_instructions) = FunctionEpilogInstruction::parse_sequence(instruction, unwind_info.frame_register()) { // If the epilog is an optional AddSP followed by Pops, we can return a cache // rule. if let Some(rule) = UnwindRuleX86_64::for_sequence_of_offset_or_pop(epilog_instructions.iter()) { return Ok(UnwindResult::ExecRule(rule)); } for instruction in epilog_instructions.iter() { match instruction { FunctionEpilogInstruction::AddSP(offset) => { let rsp = regs.get(Reg::RSP); regs.set(Reg::RSP, rsp + *offset as u64); } FunctionEpilogInstruction::AddSPFromFP(offset) => { let fp = unwind_info .frame_register() .expect("invalid fp register offset"); let fp = convert_pe_register(fp); let fp = regs.get(fp); regs.set(Reg::RSP, fp + *offset as u64); } FunctionEpilogInstruction::Pop(reg) => { let rsp = regs.get(Reg::RSP); let val = read_stack_err(read_stack, rsp)?; regs.set(convert_pe_register(*reg), val); regs.set(Reg::RSP, rsp + 8); } } } let rsp = regs.get(Reg::RSP); let ra = read_stack_err(read_stack, rsp)?; regs.set(Reg::RSP, rsp + 8); return Ok(UnwindResult::Uncacheable(ra)); } } // Get all chained UnwindInfo and resolve errors when collecting. let chained_info = core::iter::successors(Some(Ok(unwind_info)), |info| { let Ok(info) = info else { return None; }; if let Some(UnwindInfoTrailer::ChainedUnwindInfo { chained }) = info.trailer() { let unwind_info_address = chained.unwind_info_address.get(); Some( sections .unwind_info_memory_at_rva(unwind_info_address) .and_then(|data| { UnwindInfo::parse(data).ok_or(PeUnwinderError::UnwindInfoParseError) }), ) } else { None } }) .collect::, _>>()?; // Get all operations across chained UnwindInfo. The first should be filtered to only those // operations which are before the offset in the function. let offset = address - function.begin_address.get(); let operations = chained_info.into_iter().enumerate().flat_map(|(i, info)| { info.unwind_operations() .skip_while(move |(o, _)| i == 0 && *o as u32 > offset) .map(|(_, op)| op) }); // We need to collect operations to first check (without losing ownership) whether an // unwind rule can be returned. let operations = operations.collect::>(); if let Some(rule) = UnwindRuleX86_64::for_sequence_of_offset_or_pop(operations.iter()) { return Ok(UnwindResult::ExecRule(rule)); } // Resolve operations to get the return address. let mut state = State { regs, read_stack }; for op in operations { if let ControlFlow::Break(ra) = unwind_info .resolve_operation(&mut state, &op) .ok_or(PeUnwinderError::MissingStackData(None))? { return Ok(UnwindResult::Uncacheable(ra)); } } let rsp = regs.get(Reg::RSP); let ra = read_stack_err(read_stack, rsp)?; regs.set(Reg::RSP, rsp + 8); Ok(UnwindResult::Uncacheable(ra)) } } framehop-0.13.0/src/x86_64/register_ordering.rs000064400000000000000000000050661046102023000173250ustar 00000000000000use super::unwindregs::Reg; use arrayvec::ArrayVec; const ENCODE_REGISTERS: [Reg; 8] = [ Reg::RBX, Reg::RBP, Reg::RDI, Reg::RSI, Reg::R12, Reg::R13, Reg::R14, Reg::R15, ]; pub fn decode(count: u8, encoded_ordering: u16) -> ArrayVec { let mut regs: ArrayVec = ENCODE_REGISTERS.into(); let mut r = encoded_ordering; let mut n: u16 = 8; while r != 0 { let index = r % n; if index != 0 { regs[(8 - n as usize)..].swap(index as usize, 0); } r /= n; n -= 1; } regs.truncate(count as usize); regs } pub fn encode(registers: &[Reg]) -> Option<(u8, u16)> { if registers.len() > ENCODE_REGISTERS.len() { return None; } let count = registers.len() as u8; let mut r: u16 = 0; let mut reg_order: ArrayVec = ENCODE_REGISTERS.into(); let mut scale: u16 = 1; for (i, reg) in registers.iter().enumerate() { let index = reg_order[i..].iter().position(|r| r == reg)?; if index as u16 != 0 { reg_order[i..].swap(index, 0); } r += index as u16 * scale; scale *= 8 - i as u16; } Some((count, r)) } #[cfg(test)] mod test { use super::*; #[test] fn unhandled_orderings() { use super::Reg::*; assert_eq!(encode(&[RAX]), None, "RAX is a volatile register, i.e. not a callee-save register, so it does not need to be restored during epilogs and is not covered by the encoding."); assert_eq!(encode(&[RSI, RSI]), None, "Valid register orderings only contain each register (at most) once, so there is no encoding for a sequence with repeated registers."); } #[test] fn roundtrip_all() { // Test all possible register orderings. // That is, for all permutations of length 0 to 8 of the ENCODE_REGISTERS array, check that // the register ordering rountrips successfully through encoding and decoding. use itertools::Itertools; for permutation in (0..=8).flat_map(|k| ENCODE_REGISTERS.iter().cloned().permutations(k)) { let permutation = permutation.as_slice(); let encoding = encode(permutation); if let Some((count, encoded)) = encoding { assert_eq!( decode(count, encoded).as_slice(), permutation, "Register permutation should roundtrip correctly", ); } else { panic!("Register permutation failed to encode: {permutation:?}"); } } } } framehop-0.13.0/src/x86_64/unwind_rule.rs000064400000000000000000000327211046102023000161410ustar 00000000000000use super::register_ordering; use super::unwindregs::{Reg, UnwindRegsX86_64}; use crate::add_signed::checked_add_signed; use crate::error::Error; use crate::unwind_rule::UnwindRule; use arrayvec::ArrayVec; /// For all of these: return address is *(new_sp - 8) #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum UnwindRuleX86_64 { EndOfStack, /// (sp, bp) = (sp + 8, bp) JustReturn, /// (sp, bp) = if is_first_frame (sp + 8, bp) else (bp + 16, *bp) JustReturnIfFirstFrameOtherwiseFp, /// (sp, bp) = (sp + 8x, bp) OffsetSp { sp_offset_by_8: u16, }, /// (sp, bp) = (sp + 8x, *(sp + 8y)) OffsetSpAndRestoreBp { sp_offset_by_8: u16, bp_storage_offset_from_sp_by_8: i16, }, /// (sp, bp) = (bp + 16, *bp) UseFramePointer, /// (sp, ...) = (sp + 8 * (offset + register count), ... popped according to encoded ordering) /// This supports the common case of pushed callee-saved registers followed by a stack /// allocation. Up to 8 registers can be stored, which covers all callee-saved registers (aside /// from RSP which is implicit). /// /// The registers are stored in a separate compressed ordering to facilitate restoring register /// values if desired. If not for this we could simply store the total offset. OffsetSpAndPopRegisters { /// The additional stack pointer offset to undo before popping the registers, divided by 8 bytes. sp_offset_by_8: u16, /// The number of registers to pop from the stack. register_count: u8, /// An encoded ordering of the callee-save registers to pop from the stack, see register_ordering. encoded_registers_to_pop: u16, }, } pub enum OffsetOrPop { None, OffsetBy8(u16), Pop(Reg), } impl UnwindRuleX86_64 { /// Get the rule which represents the given operations, if possible. pub fn for_sequence_of_offset_or_pop(iter: I) -> Option where I: Iterator, T: Into, { let mut iter = iter.map(Into::into).peekable(); let sp_offset_by_8 = if let Some(&OffsetOrPop::OffsetBy8(offset)) = iter.peek() { iter.next(); offset } else { 0 }; let mut regs = ArrayVec::::new(); for i in iter { if let OffsetOrPop::Pop(reg) = i { // If try_push errors we've exceeded the number of supported registers: there's no // way to encode these operations as an unwind rule. regs.try_push(reg).ok()?; } else { return None; } } if regs.is_empty() && sp_offset_by_8 == 0 { Some(Self::JustReturn) } else { let (register_count, encoded_registers_to_pop) = register_ordering::encode(®s)?; Some(Self::OffsetSpAndPopRegisters { sp_offset_by_8, register_count, encoded_registers_to_pop, }) } } } impl UnwindRule for UnwindRuleX86_64 { type UnwindRegs = UnwindRegsX86_64; fn rule_for_stub_functions() -> Self { UnwindRuleX86_64::JustReturn } fn rule_for_function_start() -> Self { UnwindRuleX86_64::JustReturn } fn fallback_rule() -> Self { UnwindRuleX86_64::UseFramePointer } fn exec( self, is_first_frame: bool, regs: &mut UnwindRegsX86_64, read_stack: &mut F, ) -> Result, Error> where F: FnMut(u64) -> Result, { let sp = regs.sp(); let (new_sp, new_bp) = match self { UnwindRuleX86_64::EndOfStack => return Ok(None), UnwindRuleX86_64::JustReturn => { let new_sp = sp.checked_add(8).ok_or(Error::IntegerOverflow)?; (new_sp, regs.bp()) } UnwindRuleX86_64::JustReturnIfFirstFrameOtherwiseFp => { if is_first_frame { let new_sp = sp.checked_add(8).ok_or(Error::IntegerOverflow)?; (new_sp, regs.bp()) } else { let sp = regs.sp(); let bp = regs.bp(); let new_sp = bp.checked_add(16).ok_or(Error::IntegerOverflow)?; if new_sp <= sp { return Err(Error::FramepointerUnwindingMovedBackwards); } let new_bp = read_stack(bp).map_err(|_| Error::CouldNotReadStack(bp))?; (new_sp, new_bp) } } UnwindRuleX86_64::OffsetSp { sp_offset_by_8 } => { let sp_offset = u64::from(sp_offset_by_8) * 8; let new_sp = sp.checked_add(sp_offset).ok_or(Error::IntegerOverflow)?; (new_sp, regs.bp()) } UnwindRuleX86_64::OffsetSpAndRestoreBp { sp_offset_by_8, bp_storage_offset_from_sp_by_8, } => { let sp_offset = u64::from(sp_offset_by_8) * 8; let new_sp = sp.checked_add(sp_offset).ok_or(Error::IntegerOverflow)?; let bp_storage_offset_from_sp = i64::from(bp_storage_offset_from_sp_by_8) * 8; let bp_location = checked_add_signed(sp, bp_storage_offset_from_sp) .ok_or(Error::IntegerOverflow)?; let new_bp = match read_stack(bp_location) { Ok(new_bp) => new_bp, Err(()) if is_first_frame && bp_location < sp => { // Ignore errors when reading beyond the stack pointer in the first frame. // These negative offsets are sometimes seen in x86_64 epilogues, where // a bunch of registers are popped one after the other, and the compiler // doesn't always set the already-popped register to "unchanged" (because // doing so would take up extra space in the dwarf information). // read_stack may legitimately refuse to read beyond the stack pointer, // for example when the stack bytes are coming from a linux perf event // sample record, where the ustack bytes are copied starting from sp. regs.bp() } Err(()) => return Err(Error::CouldNotReadStack(bp_location)), }; (new_sp, new_bp) } UnwindRuleX86_64::UseFramePointer => { // Do a frame pointer stack walk. Code that is compiled with frame pointers // has the following function prologues and epilogues: // // Function prologue: // pushq %rbp // movq %rsp, %rbp // // Function epilogue: // popq %rbp // ret // // Functions are called with callq; callq pushes the return address onto the stack. // When a function reaches its end, ret pops the return address from the stack and jumps to it. // So when a function is called, we have the following stack layout: // // [... rest of the stack] // ^ rsp ^ rbp // callq some_function // [return address] [... rest of the stack] // ^ rsp ^ rbp // pushq %rbp // [caller's frame pointer] [return address] [... rest of the stack] // ^ rsp ^ rbp // movq %rsp, %rbp // [caller's frame pointer] [return address] [... rest of the stack] // ^ rsp, rbp // // [... more stack] [caller's frame pointer] [return address] [... rest of the stack] // ^ rsp ^ rbp // // So: *rbp is the caller's frame pointer, and *(rbp + 8) is the return address. // // Or, in other words, the following linked list is built up on the stack: // #[repr(C)] // struct CallFrameInfo { // previous: *const CallFrameInfo, // return_address: *const c_void, // } // and rbp is a *const CallFrameInfo. let sp = regs.sp(); let bp = regs.bp(); if bp == 0 { return Ok(None); } let new_sp = bp.checked_add(16).ok_or(Error::IntegerOverflow)?; if new_sp <= sp { return Err(Error::FramepointerUnwindingMovedBackwards); } let new_bp = read_stack(bp).map_err(|_| Error::CouldNotReadStack(bp))?; // new_bp is the caller's bp. If the caller uses frame pointers, then bp should be // a valid frame pointer and we could do a coherency check on new_bp to make sure // it's moving in the right direction. But if the caller is using bp as a general // purpose register, then any value (including zero) would be a valid value. // At this point we don't know how the caller uses bp, so we leave new_bp unchecked. (new_sp, new_bp) } UnwindRuleX86_64::OffsetSpAndPopRegisters { sp_offset_by_8, register_count, encoded_registers_to_pop, } => { let sp = regs.sp(); let mut sp = sp .checked_add(sp_offset_by_8 as u64 * 8) .ok_or(Error::IntegerOverflow)?; for reg in register_ordering::decode(register_count, encoded_registers_to_pop) { let value = read_stack(sp).map_err(|_| Error::CouldNotReadStack(sp))?; sp = sp.checked_add(8).ok_or(Error::IntegerOverflow)?; regs.set(reg, value); } (sp.checked_add(8).ok_or(Error::IntegerOverflow)?, regs.bp()) } }; let return_address = read_stack(new_sp - 8).map_err(|_| Error::CouldNotReadStack(new_sp - 8))?; if return_address == 0 { return Ok(None); } if new_sp == sp && return_address == regs.ip() { return Err(Error::DidNotAdvance); } regs.set_ip(return_address); regs.set_sp(new_sp); regs.set_bp(new_bp); Ok(Some(return_address)) } } #[cfg(test)] mod test { use super::*; #[test] fn test_basic() { let stack = [ 1, 2, 0x100300, 4, 0x40, 0x100200, 5, 6, 0x70, 0x100100, 7, 8, 9, 10, 0x0, 0x0, ]; let mut read_stack = |addr| Ok(stack[(addr / 8) as usize]); let mut regs = UnwindRegsX86_64::new(0x100400, 0x10, 0x20); let res = UnwindRuleX86_64::OffsetSp { sp_offset_by_8: 1 }.exec(true, &mut regs, &mut read_stack); assert_eq!(res, Ok(Some(0x100300))); assert_eq!(regs.ip(), 0x100300); assert_eq!(regs.sp(), 0x18); assert_eq!(regs.bp(), 0x20); let res = UnwindRuleX86_64::UseFramePointer.exec(true, &mut regs, &mut read_stack); assert_eq!(res, Ok(Some(0x100200))); assert_eq!(regs.ip(), 0x100200); assert_eq!(regs.sp(), 0x30); assert_eq!(regs.bp(), 0x40); let res = UnwindRuleX86_64::UseFramePointer.exec(false, &mut regs, &mut read_stack); assert_eq!(res, Ok(Some(0x100100))); assert_eq!(regs.ip(), 0x100100); assert_eq!(regs.sp(), 0x50); assert_eq!(regs.bp(), 0x70); let res = UnwindRuleX86_64::UseFramePointer.exec(false, &mut regs, &mut read_stack); assert_eq!(res, Ok(None)); } #[test] fn test_overflow() { // This test makes sure that debug builds don't panic when trying to use frame pointer // unwinding on code that was using the bp register as a general-purpose register and // storing -1 in it. -1 is u64::MAX, so an unchecked add panics in debug builds. let stack = [ 1, 2, 0x100300, 4, 0x40, 0x100200, 5, 6, 0x70, 0x100100, 7, 8, 9, 10, 0x0, 0x0, ]; let mut read_stack = |addr| Ok(stack[(addr / 8) as usize]); let mut regs = UnwindRegsX86_64::new(0x100400, u64::MAX / 8 * 8, u64::MAX); let res = UnwindRuleX86_64::JustReturn.exec(true, &mut regs, &mut read_stack); assert_eq!(res, Err(Error::IntegerOverflow)); let res = UnwindRuleX86_64::OffsetSp { sp_offset_by_8: 1 }.exec(true, &mut regs, &mut read_stack); assert_eq!(res, Err(Error::IntegerOverflow)); let res = UnwindRuleX86_64::OffsetSpAndRestoreBp { sp_offset_by_8: 1, bp_storage_offset_from_sp_by_8: 2, } .exec(true, &mut regs, &mut read_stack); assert_eq!(res, Err(Error::IntegerOverflow)); let res = UnwindRuleX86_64::UseFramePointer.exec(true, &mut regs, &mut read_stack); assert_eq!(res, Err(Error::IntegerOverflow)); } } framehop-0.13.0/src/x86_64/unwinder.rs000064400000000000000000000035061046102023000154400ustar 00000000000000use core::ops::Deref; use super::arch::ArchX86_64; use super::cache::CacheX86_64; use super::unwindregs::UnwindRegsX86_64; use crate::cache::{AllocationPolicy, MayAllocateDuringUnwind}; use crate::error::Error; use crate::unwinder::UnwinderInternal; use crate::unwinder::{Module, Unwinder}; use crate::FrameAddress; /// The unwinder for the x86_64 CPU architecture. Use the [`Unwinder`] trait for unwinding. /// /// Type arguments: /// /// - `D`: The type for unwind section data in the modules. See [`Module`]. /// - `P`: The [`AllocationPolicy`]. pub struct UnwinderX86_64(UnwinderInternal); impl Default for UnwinderX86_64 { fn default() -> Self { Self::new() } } impl Clone for UnwinderX86_64 { fn clone(&self) -> Self { Self(self.0.clone()) } } impl UnwinderX86_64 { /// Create an unwinder for a process. pub fn new() -> Self { Self(UnwinderInternal::new()) } } impl, P: AllocationPolicy> Unwinder for UnwinderX86_64 { type UnwindRegs = UnwindRegsX86_64; type Cache = CacheX86_64

; type Module = Module; fn add_module(&mut self, module: Module) { self.0.add_module(module); } fn remove_module(&mut self, module_address_range_start: u64) { self.0.remove_module(module_address_range_start); } fn max_known_code_address(&self) -> u64 { self.0.max_known_code_address() } fn unwind_frame( &self, address: FrameAddress, regs: &mut UnwindRegsX86_64, cache: &mut CacheX86_64

, read_stack: &mut F, ) -> Result, Error> where F: FnMut(u64) -> Result, { self.0.unwind_frame(address, regs, &mut cache.0, read_stack) } } framehop-0.13.0/src/x86_64/unwindregs.rs000064400000000000000000000046741046102023000160010ustar 00000000000000use core::fmt::Debug; use crate::display_utils::HexNum; #[derive(Clone, Copy, PartialEq, Eq)] pub struct UnwindRegsX86_64 { ip: u64, regs: [u64; 16], } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(u8)] pub enum Reg { RAX, RDX, RCX, RBX, RSI, RDI, RBP, RSP, R8, R9, R10, R11, R12, R13, R14, R15, } impl UnwindRegsX86_64 { pub fn new(ip: u64, sp: u64, bp: u64) -> Self { let mut r = Self { ip, regs: Default::default(), }; r.set_sp(sp); r.set_bp(bp); r } #[inline(always)] pub fn get(&self, reg: Reg) -> u64 { self.regs[reg as usize] } #[inline(always)] pub fn set(&mut self, reg: Reg, value: u64) { self.regs[reg as usize] = value; } #[inline(always)] pub fn ip(&self) -> u64 { self.ip } #[inline(always)] pub fn set_ip(&mut self, ip: u64) { self.ip = ip } #[inline(always)] pub fn sp(&self) -> u64 { self.get(Reg::RSP) } #[inline(always)] pub fn set_sp(&mut self, sp: u64) { self.set(Reg::RSP, sp) } #[inline(always)] pub fn bp(&self) -> u64 { self.get(Reg::RBP) } #[inline(always)] pub fn set_bp(&mut self, bp: u64) { self.set(Reg::RBP, bp) } } impl Debug for UnwindRegsX86_64 { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("UnwindRegsX86_64") .field("ip", &HexNum(self.ip())) .field("rax", &HexNum(self.get(Reg::RAX))) .field("rdx", &HexNum(self.get(Reg::RDX))) .field("rcx", &HexNum(self.get(Reg::RCX))) .field("rbx", &HexNum(self.get(Reg::RBX))) .field("rsi", &HexNum(self.get(Reg::RSI))) .field("rdi", &HexNum(self.get(Reg::RDI))) .field("rbp", &HexNum(self.get(Reg::RBP))) .field("rsp", &HexNum(self.get(Reg::RSP))) .field("r8", &HexNum(self.get(Reg::R8))) .field("r9", &HexNum(self.get(Reg::R9))) .field("r10", &HexNum(self.get(Reg::R10))) .field("r11", &HexNum(self.get(Reg::R11))) .field("r12", &HexNum(self.get(Reg::R12))) .field("r13", &HexNum(self.get(Reg::R13))) .field("r14", &HexNum(self.get(Reg::R14))) .field("r15", &HexNum(self.get(Reg::R15))) .finish() } }