├── .cirrus.yml ├── .github ├── FUNDING.yml └── workflows │ └── build.yml ├── .gitignore ├── CONTRIBUTING.md ├── Cargo.toml ├── Documentation ├── Design.md ├── Linux.md ├── MacOS.md ├── README.md └── Resources.md ├── Info.plist ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── code_of_conduct.md ├── examples ├── gui.rs ├── inject_dylib.clif ├── inject_hello_world.clif └── repl.rs ├── fonts ├── FiraMono-Regular.ttf └── LICENSE-FiraMono ├── headcrab_inject ├── Cargo.toml ├── src │ ├── lib.rs │ ├── memory.rs │ └── module.rs └── tests │ ├── inject_abort.rs │ ├── test_utils.rs │ └── testees ├── hello_dylib.rs ├── repl_tools ├── Cargo.toml └── src │ └── lib.rs ├── src ├── lib.rs ├── symbol.rs ├── symbol │ ├── dwarf_utils.rs │ ├── frame.rs │ ├── relocate.rs │ ├── source.rs │ ├── sym.rs │ └── unwind.rs ├── target.rs └── target │ ├── linux.rs │ ├── linux │ ├── hardware_breakpoint.rs │ ├── memory.rs │ ├── readmem.rs │ ├── software_breakpoint.rs │ └── writemem.rs │ ├── macos.rs │ ├── macos │ ├── readmem.rs │ ├── vmmap.rs │ └── writemem.rs │ ├── registers.rs │ ├── thread.rs │ ├── unix.rs │ └── windows.rs └── tests ├── attach_readmem.rs ├── disassemble.rs ├── fixed_breakpoint.rs ├── hardware_breakpoint.rs ├── read_locals.rs ├── readmem.rs ├── readregs.rs ├── runtime_breakpoint.rs ├── source.rs ├── syscall.rs ├── test_utils.rs ├── testees ├── .gitignore ├── Makefile ├── hello.rs ├── hw_breakpoint.rs ├── known_asm.S ├── longer_hello.rs └── loop.rs └── unwind_stack.rs /.cirrus.yml: -------------------------------------------------------------------------------- 1 | task: 2 | name: stable x86_64-unknown-freebsd-12 3 | freebsd_instance: 4 | image: freebsd-12-1-release-amd64 5 | setup_script: 6 | - pkg install -y curl 7 | - curl https://sh.rustup.rs -sSf --output rustup.sh 8 | - sh rustup.sh --default-toolchain stable -y --profile=minimal 9 | - . $HOME/.cargo/env 10 | - rustup default stable 11 | - rustup component add clippy 12 | - rustup component add rustfmt 13 | test_script: 14 | - . $HOME/.cargo/env 15 | - cargo fmt --all -- --check 16 | - make -C tests/testees 17 | - cargo clippy 18 | - cargo test 19 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | open_collective: headcrab 2 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | matrix: 18 | os: [ubuntu-latest, windows-latest] 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Install Linux libraries 23 | if: matrix.os == 'ubuntu-latest' 24 | run: sudo apt-get install libxxf86vm-dev libxcb-shape0-dev libxcb-xfixes0-dev 25 | - name: Format 26 | run: cargo fmt --all -- --check 27 | - name: Clippy 28 | run: cargo clippy 29 | - name: Build 30 | run: cargo build --verbose 31 | - name: Run tests 32 | run: cargo test --verbose 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.*~ 2 | *~ 3 | *.dSYM 4 | /target 5 | Cargo.lock 6 | .vscode/ 7 | .idea/ 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Headcrab 2 | 3 | Welcome to Headcrab! This is a large project with a huge scope, and there are many things you can help us with. 4 | Documentation, code understandability, and contributor guides are amongs our top priorities. 5 | 6 | You can start by taking a look at the [list of open issues] on GitHub. 7 | However, because the project is young, not everything might be reflected in the issues descriptions, documentation, or code, 8 | and if you think something is missing, please let us know! 9 | 10 | If you are interested in working on an open issue, please leave a comment and we will assign you to it. 11 | This will help other contributors to see if this issue is already being worked on by someone else. 12 | 13 | If you want to work on something new and it's not listed on our issue tracker, feel free to create a new issue! 14 | We encourage you to do this before you put your time to work on larger issues. This way, we can make sure that no work will be duplicated. 15 | 16 | [list of open issues]: https://github.com/headcrab-rs/headcrab/issues 17 | 18 | ## Mentoring 19 | 20 | If you are new to debuggers, systems programming, or Rust, we are happy to provide guidance and help you start contributing. 21 | One of the main goals of this project is to create a tool that can be used for education, and we value feedback and involvement 22 | from people with diverse backgrounds. You can find a list of recommended resources in [our documentation](/Documentation/Resources.md). 23 | 24 | We have a list of issues tagged as "[good first issue](https://github.com/headcrab-rs/headcrab/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)" 25 | which can be a good start. If you think that an issue's description is not clear enough, we encourage you to ask questions in comments. 26 | And remember, there is no such thing as a dumb question! 27 | 28 | Lastly, we have a dedicated [#learning](https://headcrab.zulipchat.com/#narrow/stream/248039-learning) stream on our Zulip chat where you can ask 29 | questions and find more educational resources. 30 | 31 | ## Coding Guidelines 32 | 33 | Currently, Headcrab is intended to work with the current stable version of Rust, but some components might require using `nightly` in the future. 34 | 35 | When implementing modules, please prefer using module_name.rs over module_name/mod.rs. This avoids having lots of file with identical names in the source tree. 36 | 37 | We follow common Rust conventions and use default `rustfmt` format settings. Before submitting a pull request, please format it locally by running the following command: 38 | 39 | ``` 40 | $ cargo fmt 41 | ``` 42 | 43 | You can read more about [rustfmt online](https://github.com/rust-lang/rustfmt). 44 | 45 | ## Funding 46 | 47 | If you are interested in supporting the project financially, we have an [OpenCollective](https://opencollective.com/headcrab/) organisation. 48 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "headcrab" 3 | version = "0.2.0" 4 | authors = ["Headcrab Contributors"] 5 | edition = "2018" 6 | description = "A modern Rust debugging library." 7 | repository = "https://github.com/headcrab-rs/headcrab/" 8 | license = "MIT OR Apache-2.0" 9 | categories = ["development-tools::debugging"] 10 | documentation = "https://docs.rs/headcrab" 11 | homepage = "https://headcrab.rs/" 12 | readme = "README.md" 13 | 14 | [dependencies] 15 | lazy_static = "1.4.0" 16 | object = "0.20" 17 | gimli = "0.26.1" 18 | capstone = "0.7.0" 19 | addr2line = "0.17.0" 20 | syntect = {version = "4.4.0", optional = true} 21 | 22 | # Dependencies specific to macOS & Linux 23 | [target.'cfg(unix)'.dependencies] 24 | memmap = "0.7.0" 25 | nix = "0.17.0" 26 | libproc = "0.7.2" 27 | libc = "0.2.72" 28 | 29 | # Dependencies specific to macOS 30 | [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] 31 | mach = "0.3" 32 | security-framework-sys = "1.0" 33 | 34 | # Dependencies specific to Linux 35 | [target.'cfg(target_os="linux")'.dependencies] 36 | procfs = "0.9.0" 37 | 38 | [target.'cfg(target_os="windows")'.dependencies] 39 | winapi = { version = "0.3.9", features = ["winuser","processthreadsapi","winbase","minwinbase","debugapi","winnt","memoryapi","dbghelp"] } 40 | 41 | [target.'cfg(target_os = "linux")'.dev-dependencies] 42 | headcrab_inject = { path = "./headcrab_inject" } 43 | imgui = "0.5.0" 44 | imgui-glium-renderer = "0.5.0" 45 | imgui-winit-support = "0.5.0" 46 | glium = "0.27.0" 47 | clipboard = "0.5.0" 48 | 49 | [dev-dependencies] 50 | rustyline = "6.2.0" 51 | repl_tools = { path = "./repl_tools" } 52 | 53 | [features] 54 | syntax-highlighting = ["syntect"] 55 | 56 | [workspace] 57 | -------------------------------------------------------------------------------- /Documentation/Design.md: -------------------------------------------------------------------------------- 1 | # High-level Design & Plan 2 | 3 | ## Phase 1 4 | 5 | Creating a new debugger is not an easy task and requires a lot of work to cover different target platforms and edge cases. 6 | 7 | Luckily, we don't need all that to create a useful tool that can provide a lot of value to developers, and at this stage 8 | we focus mainly on creating a framework for smaller tools that can solve specific tasks rather than on creating a full-fledged 9 | debugger with command line, UI, IDE/editor integration and so forth. 10 | 11 | This framework will be comprised of some fundamental building blocks which can be used to interactively probe another process 12 | or a core dump file. This also does not necessarily mean that a debuggee process has to be stopped to observe its behaviour, 13 | as some debug techniques work with running processes. 14 | 15 | For the phase 1, we also limit our scope to 2 targets: macOS and Linux on x86_64. The purpose of having two targets rather than 16 | just one is to make sure that any design choices that will be made during the development phase will account for the modular project 17 | structure & the need to support multiple target platforms at later stages. 18 | 19 | Phase 1 will be considered done when we will have these basics available to users in form of a library: 20 | 21 | ### Target layer 22 | 23 | - Reading and writing memory of a debuggee process. 24 | - Primitive types (e.g.: i8, u64, double). 25 | - Collections (arrays, slices, `std::vec::Vec`). 26 | - Structures. 27 | - Enums. 28 | - Getting and setting values of hardware registers. 29 | - Obtaining backtraces. 30 | - Setting breakpoints at specified addresses (step-in, step-out, and step-over are optional). 31 | - Getting info about threads and reading/writing to the thread-local storage. 32 | 33 | ### Symbolication layer 34 | 35 | - Reading DWARF debug info from binary executables: 36 | - Function addresses. 37 | - Variable addresses (including static and thread-local vars). 38 | - Structures & enums information. 39 | - Support source maps (mapping memory addresses to source code). 40 | 41 | ## Phase 2 and next stages 42 | 43 | Next stage goals for the project is to provide more complete debugging experience, more integrations, and more supported platforms. 44 | 45 | - One of the main targets is a JSON-RPC API to communicate with the debugger core. This will allow us to start building UIs for the debugger 46 | in form of command-line, desktop, or web apps. 47 | 48 | - We will also need to support the GDB Remote Serial Protocol as one of the targets to integrate with other debugger cores (such as 49 | [mozilla rr](https://rr-project.org/) for deterministic debugging). 50 | 51 | - This is also a stage where we can begin experiments with the Rust compiler integration for an expression parser. 52 | 53 | ## Components and Design 54 | 55 | The project is split into several loosely coupled major components: 56 | 57 | - **Target**. This is the component that interacts with the operating system. It provides an abstract uniform interface for operations such as memory reading & writing, breakpoints (both internal and user-level), step-in, step-over, and step-out operations, hardware registers, backtraces, threads, and so forth. Targets are supposed to be pluggable components and one platform can support multiple targets (e.g., we can have a native target, a GDB serial protocol, and the core dump target supported for Linux). 58 | 59 | - **Symbolication**. This component translates human-readable symbols into addresses recognised by the machine and the other way around. There could be multiple symbol sources: they can be read from the DWARF debug information, supplied by the compiler, or read from some other source. Currently, we only aim for providing Rust symbolication component. 60 | 61 | - **Integration/API**. At this level, we can combine multiple techniques provided by the target & symbolication layers to achieve some interesting effects on the higher level. For example, an expression parser would belong here: it can parse symbols from an expression, find their addresses using the symbolication API, and read the actual values using the target API. This is also where we provide a JSON-RPC API to integrate with other tools such as code editors or IDEs. 62 | 63 | - **Command line/UI**. A general-purpose user interface is out of scope of the Headcrab project (which is a debugger library or framework as opposed to a universal debugger tool). The UI can be provided by an existing code editor or IDE, or it can be implemented as part of another project that would interface with Headcrab's API. 64 | 65 | Please keep in mind that the current state of the code base does not always reflect the design outlined here. If you find that something is missing and it's not listed on our issue tracker, please let us know! 66 | 67 | ### Target 68 | 69 | Concrete targets should implement traits defined as part of the Target API. They are conditionally-compiled, and can be flexibly configured: for example, if you're building a debugger tool only to work with core dumps, you should be able to build Headcrab only with this target enabled. By default, all targets that a given platform supports should be enabled. 70 | 71 | The feature set that we intend to cover (please keep in mind that the API is loosely defined and is prone to change): 72 | 73 | - `Target::read(&self) -> ReadMemory` - returns a trait implementation (`ReadMemory`, see below) that can be used to read memory from a debuggee process. The trait can be implemented using different strategies: e.g., on Linux we can use `process_vm_read(2)` in the majority of cases, but `ptrace(2)` might be required to cover some edge cases (e.g., when a memory page is read-protected). The concrete memory reading strategy (or a combination of them) should be chosen by the implementor, and a user should not be aware of it. 74 | 75 | - `ReadMemory::read(mut self, val: &mut T, remote_address: usize)` - reads a value `T` from the debuggee process at the given remote address. This function can be called multiple times: it builds a sequence of operations that can be executed with a single call. For non-primitive types, [`std::mem::MaybeUninit`](https://doc.rust-lang.org/stable/std/mem/union.MaybeUninit.html) can be used to safely work with uninitialized values. The lifetime of `ReadMemory` should not exceed the lifetime of `&mut T` references it contains. 76 | 77 | - `ReadMemory::apply(self) -> Vec>` - executes the read operation and returns a list of addresses where the read operation has failed (the address can be contained within the `ReadError` type). This function is not required to be atomic and values can be read partially. 78 | 79 | - `Target::write(&mut self) -> WriteMemory` - returns a trait implementation (`WriteMemory`, see below) that can be used to write memory to a debuggee process. The implementation should account for the possibility of page write protections. For example, on Linux `ptrace(2)` can be used to rewrite even protected memory pages, albeit inefficiently, and a concrete strategy should be chosen on a case-by-case basis without a user's knowledge. 80 | 81 | - `WriteMemory::write(mut self, val: &mut T, remote_address: usize)` - writes a value `T` to the debuggee process at the given remote address. This function can be called multiple times: it builds a sequence of operations that can be executed with a single call. Type `T` should be initialised and the lifetime of `WriteMemory` should not exceed the lifetime of `&mut T` references it contains. 82 | 83 | - `WriteMemory::apply(mut self) -> Vec>` - executes the write operation and returns a list of addresses where the write operation has failed (the address can be contained within the `WriteError` type). This operation is not required to be atomic and values can be written partially. 84 | 85 | - `Target::next_event(&self) -> Option` - blocks & waits for a next debug event (such as `DebugEvent::BreakpointHit`) to occur. 86 | 87 | - `Target::breakpoints(&mut self) -> &mut BreakpointsRef` - provides a view into a set of target breakpoints. It can be used to set, disable, and remove breakpoints. Internally, the `BreakpointsRef` struct can use `WriteMemory` to write the corresponding interrupt instructions, and this implementation can be shared across many target implementations. 88 | 89 | - `enum Breakpoint { Regular(usize), Conditional { addr: usize, cond: Box bool> } }` - the function in the conditional breakpoint will be executed each time the breakpoint is hit. It should return a boolean indicating whether a condition is met or not. 90 | 91 | - `BreakpointsRef::set(&mut self, bps: &[Breakpoint]) -> Result<(), Vec>` - sets a list of breakpoints at the provided addresses. In the case of error, returns a list of breakpoints that weren't set along with the error descriptions. 92 | 93 | - `BreakpointsRef::get_all(&self) -> &[Breakpoint]` - returns a list of all set breakpoints. 94 | 95 | - `BreakpointsRef::find_by_addr(&self, remote_addr: usize) -> &[Breakpoint]` - returns a list of all set breakpoints at the given address. 96 | 97 | - `BreakpointsRef::disable(&mut self, remote_addrs: &[usize]) -> Result<(), Vec>` - temporarily disable all breakpoints at the given addresses. 98 | 99 | - `BreakpointsRef::enable(&mut self, remote_addrs: &[usize]) -> Result<(), Vec>` - re-enable a previously disabled breakpoints at the giveen addresses. 100 | 101 | - `BreakpointsRef::remove(&mut self, remote_addrs: &[usize]) -> Result<(), Vec>` - remove all breakpoints at the given addresses. 102 | 103 | ### Symbolication 104 | 105 | _This section will be expanded in the future._ 106 | -------------------------------------------------------------------------------- /Documentation/Linux.md: -------------------------------------------------------------------------------- 1 | # Headcrab Linux Target Implementation Details 2 | 3 | ## Reading & writing memory 4 | 5 | We use [`process_vm_readv(2)`](https://man7.org/linux/man-pages/man2/process_vm_readv.2.html) and 6 | [`process_vm_writev(2)`](https://man7.org/linux/man-pages/man2/process_vm_writev.2.html) system calls to work with debuggee's memory. 7 | These system calls were added in Linux 3.2 and allow to read & write debuggee's memory at multiple locations with a single context switch, 8 | which is important for the debugger's peformance. 9 | -------------------------------------------------------------------------------- /Documentation/MacOS.md: -------------------------------------------------------------------------------- 1 | # Running Headcrab on macOS 2 | 3 | macOS has additional security measures and doesn't allow to manipulate other processes unless you have permissions 4 | to do so. 5 | 6 | Add linker options to `.cargo/config`: 7 | 8 | [build] 9 | rustflags = ["-C", "link-args=-sectcreate __TEXT __info_plist Info.plist"] 10 | 11 | You can check if the section has been added to the binary by running 12 | 13 | $ codesign -dvvv 2>&1 | grep Info.plist 14 | 15 | ## Address space layout randomization 16 | 17 | ASLR can be disabled with an undocumented attribute for `posix_spawn`: `_POSIX_SPAWN_DISABLE_ASLR` (0x0100) 18 | -------------------------------------------------------------------------------- /Documentation/README.md: -------------------------------------------------------------------------------- 1 | # Headcrab Documentation 2 | 3 | - [High-level design & plan](Design.md) 4 | - [Recommended reading](Resources.md) 5 | - [Linux implementation details](Linux.md) 6 | - [macOS implementation details](MacOS.md) 7 | -------------------------------------------------------------------------------- /Documentation/Resources.md: -------------------------------------------------------------------------------- 1 | # Recommended reading 2 | 3 | ## Books 4 | 5 | - "[GDB Internals](https://sourceware.org/gdb/wiki/Internals)" (also available in [PDF](https://www.sourceware.org/gdb/5/onlinedocs/gdbint.pdf)), Gilmore, J., Shebs S. 6 | 7 | This document describes internals of the GNU debugger, with details about key algorithms and the overall architecture. 8 | 9 | - "[The Linux Programming Interface](https://man7.org/tlpi/)", Kerrisk, M. (2010), ISBN 978-1-59327-220-3 10 | 11 | The encyclopedia of Linux APIs. This is the best resource on using features provided by Linux to the fullest. 12 | 13 | - "[UNIX Internals: The New Frontiers](https://openlibrary.org/books/OL792642M/UNIX_internals)", Vahalia, U. (1996), ISBN 9780131019089 14 | 15 | While this book is slightly dated, and not strictly related to the topic of debuggers, it's an excellent introduction to *nix and operating systems internals. 16 | Chapters on processes, signals, and virtual memory are still very much relevant today. 17 | 18 | - "[How Debuggers Work: Algorithms, Data Structures, and Architecture](https://openlibrary.org/books/OL972343M/How_Debuggers_work)", Rosenberg, J.B. (1996), ISBN 9780471149668 19 | 20 | Despite the old age, many fundamental principles and algorithms described in this book remain applicable to this day. 21 | One major omission is that [DWARF](http://dwarfstd.org/), the standard debug info representation, is not covered. 22 | Sections about legacy platforms like OS/2, DOS, and 16-bit Windows can be skipped entirely. 23 | 24 | - "[Modular Debugger, mdb](https://illumos.org/books/mdb/concepts-1.html#concepts-1)" (also available in [PDF](https://illumos.org/books/mdb/mdb-print.pdf)), 25 | 26 | This book describes the illumos Modular Debugger (MDB), which is a general purpose debugging tool for the illumos operating system. 27 | It has many interesting features such as extensibility and [modular architecture](https://illumos.org/books/mdb/api-5.html#api-5). 28 | 29 | ## Blogs and articles 30 | 31 | - "[Debugging Support in the Rust compiler](https://rustc-dev-guide.rust-lang.org/debugging-support-in-rustc.html)", an article from the Rust compiler dev guide describing the current state of debugging support in the Rust compiler. 32 | 33 | - "[Writing a Linux Debugger](https://blog.tartanllama.xyz/writing-a-linux-debugger-setup/)", a series of blog posts by Sy Brand. 34 | 35 | - "[How Debuggers Work](https://eli.thegreenplace.net/2011/01/23/how-debuggers-work-part-1)", a series of blog posts by Eli Bendersky. 36 | 37 | - "[Your Debugger Sucks](https://robert.ocallahan.org/2019/11/your-debugger-sucks.html)", a blog post about the current debugging experience and how it should be improved. 38 | 39 | ## Specifications 40 | 41 | - [DWARF Debugging Format Standard](http://dwarfstd.org/Download.php). DWARF is the standard most of the compilers & debuggers use (including Rust). 42 | 43 | - [Debug Adapter Protocol](https://microsoft.github.io/debug-adapter-protocol/specification). A specification for a standard that provides interoperability between debuggers and code editors/IDEs. 44 | -------------------------------------------------------------------------------- /Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | CFBundleDevelopmentRegion 10 | English 11 | 12 | CFBundleIdentifier 13 | com.apple.taskforpid 14 | 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | 18 | CFBundleName 19 | taskforpid 20 | 21 | CFBundleVersion 22 | 1.0 23 | 24 | SecTaskAccess 25 | 26 | allowed 27 | debug 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright 2020 Headcrab Contributors 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Headcrab 2 | 3 | [![project chat](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg)](https://headcrab.zulipchat.com) [![Build Status](https://travis-ci.org/headcrab-rs/headcrab.svg?branch=master)](https://travis-ci.org/headcrab-rs/headcrab) [![Build Status](https://api.cirrus-ci.com/github/headcrab-rs/headcrab.svg?task=stable%20x86_64-unknown-freebsd-12)](https://cirrus-ci.com/github/headcrab-rs/headcrab) ![windows](https://github.com/headcrab-rs/headcrab/workflows/windows/badge.svg?branch=master) [![Financial Contributors on Open Collective](https://opencollective.com/headcrab/all/badge.svg?label=financial+contributors)](https://opencollective.com/headcrab) 4 | 5 | [**Contributing**](CONTRIBUTING.md) | [**Documentation**](Documentation) | [**Chat**](https://headcrab.zulipchat.com) | [**Website**](https://headcrab.rs) 6 | 7 | A modern Rust debugging library. 8 | 9 | ## Goals 10 | 11 | This project's goal is to provide a modern debugger library for Rust so that you could build custom debuggers specific for your application. It will be developed with modern operating systems and platforms in mind. 12 | 13 | - [List of Phase 1 goals](https://github.com/headcrab-rs/headcrab/blob/master/Documentation/Design.md#phase-1) 14 | - [List of Phase 1 open issues](https://github.com/headcrab-rs/headcrab/milestone/1) 15 | 16 | ## Using Headcrab 17 | 18 | Currently, Headcrab supports Linux x86_64 as the primary target. 19 | It's intended to be used as a library, but at this time it's not production-ready and the API stability is not guaranteed. 20 | 21 | You can try some example applications. E.g., a command line interface to some of the exposed functions: 22 | 23 | ``` 24 | cargo run --example repl 25 | ``` 26 | 27 | [![asciicast](https://asciinema.org/a/356800.svg)](https://asciinema.org/a/356800) 28 | 29 | ## Contributing 30 | 31 | This project exists thanks to all the people who contribute. 32 | 33 | Please refer to the "[Contributing to Headcrab](CONTRIBUTING.md)" document for more information about how you can help the project. 34 | You can also join the community chat at https://headcrab.zulipchat.com 35 | 36 | Unless you explicitly state otherwise, any contribution intentionally submitted 37 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any 38 | additional terms or conditions. 39 | 40 | ### Code contributors 41 | 42 | 43 | 44 | ### Financial Contributors 45 | 46 | Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/headcrab/contribute)] 47 | 48 | #### Individuals 49 | 50 | 51 | 52 | #### Sponsors 53 | 54 | Support this project with your organization. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/headcrab/contribute)] 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | ## Long-term goals 68 | 69 | - Rust expression parser (reusing MIR and other components from the Rust compiler). 70 | - Read complex data structures. 71 | - Make symbolication reusable for eBPF and dynamic tracing. 72 | - JSON-RPC and support for [Debug Adapter Protocol](https://microsoft.github.io/debug-adapter-protocol/). 73 | - Command-line interface. 74 | - Integrate with rr for reverse debugging. 75 | - Collaborative debugging. 76 | - Use the [LLVM DExTer](https://github.com/llvm/llvm-project/tree/master/debuginfo-tests/dexter) to improve user experience. 77 | - Support more platforms and operating systems (Illumos, FreeBSD, OpenBSD, Windows). 78 | 79 | ## License 80 | 81 | Licensed under either of 82 | 83 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 84 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 85 | 86 | at your option. 87 | -------------------------------------------------------------------------------- /code_of_conduct.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, religion, or sexual identity 11 | and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the 27 | overall community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or 32 | advances of any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email 36 | address, without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at nikita.baksalyar@gmail.com 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /examples/inject_dylib.clif: -------------------------------------------------------------------------------- 1 | function u0:2() system_v { 2 | gv0 = symbol u1:0 ; dylib file name 3 | gv1 = symbol u1:1 ; __headcrab_command 4 | sig0 = (i64, i32) -> i64 system_v ; fn(filename: *const c_char, flag: c_int) -> *mut c_void 5 | sig1 = (i64, i64) -> i64 system_v ; fn(handle: *mut c_void, symbol: *const c_char) -> *mut c_void 6 | sig2 = () system_v ; signature for __headcrab_command 7 | fn0 = u0:0 sig0 ; dlopen 8 | fn1 = u0:1 sig1 ; dlsym 9 | 10 | block0: 11 | v0 = global_value.i64 gv0 12 | v1 = iconst.i32 0x2 ; RTLD_NOW 13 | v2 = call fn0(v0, v1) 14 | v3 = global_value.i64 gv1 15 | v4 = call fn1(v2, v3) 16 | call_indirect sig2, v4() 17 | return 18 | } 19 | -------------------------------------------------------------------------------- /examples/inject_hello_world.clif: -------------------------------------------------------------------------------- 1 | ; declare: func0 puts 2 | ; define: data0 "Hello World from injected code!\0" 3 | 4 | function u0:1() system_v { 5 | gv0 = symbol u1:0 6 | sig0 = (i64) system_v 7 | fn0 = u0:0 sig0 8 | 9 | block0: 10 | v0 = global_value.i64 gv0 11 | call fn0(v0) 12 | return 13 | } 14 | 15 | function u0:2() system_v { 16 | sig0 = () system_v 17 | fn0 = u0:1 sig0 18 | 19 | block0: 20 | call fn0() 21 | return 22 | } 23 | 24 | ; run: func2 25 | -------------------------------------------------------------------------------- /fonts/FiraMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/headcrab-rs/headcrab/5a420c5da3f51196cd991680bb2b3fded6ce7033/fonts/FiraMono-Regular.ttf -------------------------------------------------------------------------------- /fonts/LICENSE-FiraMono: -------------------------------------------------------------------------------- 1 | Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A. 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /headcrab_inject/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "headcrab_inject" 3 | version = "0.2.0" 4 | authors = ["Headcrab Contributors"] 5 | edition = "2018" 6 | description = "A code injection plugin for Headcrab." 7 | repository = "https://github.com/headcrab-rs/headcrab/" 8 | license = "MIT OR Apache-2.0" 9 | categories = ["development-tools::debugging"] 10 | documentation = "https://docs.rs/headcrab_jit" 11 | homepage = "https://headcrab.rs/" 12 | readme = "../README.md" 13 | 14 | [dependencies] 15 | headcrab = { version = "0.2.0", path = "../" } 16 | cranelift-codegen = "0.82.3" 17 | cranelift-reader = "0.82.3" 18 | cranelift-module = "0.82.3" 19 | libc = "0.2.76" 20 | target-lexicon = "0.10.0" 21 | 22 | [target.'cfg(unix)'.dev-dependencies] 23 | nix = "0.17.0" 24 | -------------------------------------------------------------------------------- /headcrab_inject/src/lib.rs: -------------------------------------------------------------------------------- 1 | // FIXME make this work on other systems too. 2 | #![cfg(all(target_arch = "x86_64", target_os = "linux"))] 3 | 4 | use cranelift_codegen::{ 5 | isa::{self, TargetIsa}, 6 | settings::{self, Configurable}, 7 | }; 8 | use headcrab::CrabResult; 9 | 10 | use headcrab::target::LinuxTarget; 11 | 12 | mod memory; 13 | mod module; 14 | 15 | pub use cranelift_codegen::Context; 16 | pub use cranelift_module::{DataId, FuncId, FuncOrDataId}; 17 | pub use cranelift_reader::parse_functions; 18 | pub use memory::Memory; 19 | pub use module::InjectionModule; 20 | 21 | const EXECUTABLE_DATA_ALIGNMENT: u64 = 0x10; 22 | const WRITABLE_DATA_ALIGNMENT: u64 = 0x8; 23 | const READONLY_DATA_ALIGNMENT: u64 = 0x1; 24 | 25 | pub fn target_isa() -> Box { 26 | let mut flag_builder = settings::builder(); 27 | flag_builder.set("use_colocated_libcalls", "false").unwrap(); 28 | let flags = settings::Flags::new(flag_builder); 29 | 30 | // TODO: better error handling 31 | isa::lookup("x86_64".parse().unwrap()) 32 | .unwrap() 33 | .finish(flags) 34 | .unwrap() 35 | } 36 | 37 | fn parse_func_or_data(s: &str) -> FuncOrDataId { 38 | let (kind, index) = s.split_at(4); 39 | let index: u32 = index.parse().unwrap(); 40 | 41 | match kind { 42 | "func" => FuncOrDataId::Func(FuncId::from_u32(index)), 43 | "data" => FuncOrDataId::Data(DataId::from_u32(index)), 44 | _ => panic!("`Unknown kind {}`", kind), 45 | } 46 | } 47 | 48 | pub fn inject_clif_code( 49 | inj_module: &mut InjectionModule, 50 | lookup_symbol: &dyn Fn(&str) -> u64, 51 | code: &str, 52 | ) -> CrabResult { 53 | let mut run_function = None; 54 | 55 | for line in code.lines() { 56 | let line = line.trim(); 57 | if !line.starts_with(';') { 58 | continue; 59 | } 60 | let line = line.trim_start_matches(';').trim_start(); 61 | let (directive, content) = line.split_at(line.find(':').unwrap_or(line.len())); 62 | let content = content[1..].trim_start(); 63 | 64 | match directive { 65 | "declare" => { 66 | let (id, content) = content.split_at(content.find(' ').unwrap_or(content.len())); 67 | let content = content.trim_start(); 68 | match parse_func_or_data(id) { 69 | FuncOrDataId::Func(func_id) => { 70 | inj_module.define_function(func_id, lookup_symbol(content)); 71 | } 72 | FuncOrDataId::Data(data_id) => { 73 | inj_module.define_data_object(data_id, lookup_symbol(content)); 74 | } 75 | } 76 | } 77 | "define" => { 78 | let (id, content) = content.split_at(content.find(' ').unwrap_or(content.len())); 79 | let content = content.trim_start(); 80 | match parse_func_or_data(id) { 81 | FuncOrDataId::Data(data_id) => { 82 | if content.starts_with('"') { 83 | let content = content 84 | .trim_matches('"') 85 | .replace("\\n", "\n") 86 | .replace("\\0", "\0"); 87 | inj_module 88 | .define_data_object_with_bytes(data_id, content.as_bytes())?; 89 | } else { 90 | todo!(); 91 | } 92 | } 93 | FuncOrDataId::Func(func_id) => { 94 | panic!("Please use `function u0:{}()` instead", func_id.as_u32()); 95 | } 96 | } 97 | } 98 | "run" => { 99 | assert!(run_function.is_none()); 100 | match parse_func_or_data(content) { 101 | FuncOrDataId::Func(func_id) => run_function = Some(func_id), 102 | FuncOrDataId::Data(_) => panic!("Can't execute data object"), 103 | } 104 | } 105 | _ => panic!("Unknown directive `{}`", directive), 106 | } 107 | } 108 | 109 | let mut flag_builder = settings::builder(); 110 | flag_builder.set("use_colocated_libcalls", "false").unwrap(); 111 | let flags = settings::Flags::new(flag_builder); 112 | let isa = isa::lookup("x86_64".parse().unwrap()) 113 | .unwrap() 114 | .finish(flags) 115 | .unwrap(); 116 | 117 | let functions = cranelift_reader::parse_functions(code).unwrap(); 118 | let mut ctx = cranelift_codegen::Context::new(); 119 | for func in functions { 120 | ctx.clear(); 121 | ctx.func = func; 122 | inj_module.compile_clif_code(&*isa, &mut ctx)?; 123 | } 124 | 125 | let run_function = inj_module.lookup_function(run_function.expect("Missing `run` directive")); 126 | 127 | Ok(run_function) 128 | } 129 | 130 | pub struct InjectionContext<'a> { 131 | target: &'a LinuxTarget, 132 | code: Memory, 133 | readonly: Memory, 134 | readwrite: Memory, 135 | } 136 | 137 | impl<'a> InjectionContext<'a> { 138 | pub fn new(target: &'a LinuxTarget) -> Self { 139 | Self { 140 | target, 141 | code: Memory::new_executable(), 142 | readonly: Memory::new_readonly(), 143 | readwrite: Memory::new_writable(), 144 | } 145 | } 146 | 147 | pub fn target(&self) -> &'a LinuxTarget { 148 | self.target 149 | } 150 | 151 | pub fn allocate_code(&mut self, size: u64, align: Option) -> CrabResult { 152 | self.code.allocate( 153 | self.target, 154 | size, 155 | align.unwrap_or(EXECUTABLE_DATA_ALIGNMENT), 156 | ) 157 | } 158 | 159 | pub fn allocate_readonly(&mut self, size: u64, align: Option) -> CrabResult { 160 | self.readonly 161 | .allocate(self.target, size, align.unwrap_or(READONLY_DATA_ALIGNMENT)) 162 | } 163 | 164 | pub fn allocate_readwrite(&mut self, size: u64, align: Option) -> CrabResult { 165 | self.readwrite 166 | .allocate(self.target, size, align.unwrap_or(WRITABLE_DATA_ALIGNMENT)) 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /headcrab_inject/src/memory.rs: -------------------------------------------------------------------------------- 1 | // Based on https://github.com/bytecodealliance/wasmtime/blob/48fab12142c971405ab1c56fdceadf78bc2bbff4/cranelift/simplejit/src/memory.rs 2 | 3 | use std::{mem, ptr}; 4 | 5 | use headcrab::{target::LinuxTarget, CrabResult}; 6 | 7 | /// Round `size` up to the nearest multiple of `page_size`. 8 | fn round_up_to_page_size(size: u64, page_size: u64) -> u64 { 9 | (size + (page_size - 1)) & !(page_size - 1) 10 | } 11 | 12 | /// A simple struct consisting of a pointer and length. 13 | struct PtrLen { 14 | ptr: u64, 15 | len: u64, 16 | } 17 | 18 | impl PtrLen { 19 | /// Create a new empty `PtrLen`. 20 | fn new() -> Self { 21 | Self { ptr: 0, len: 0 } 22 | } 23 | 24 | /// Create a new `PtrLen` pointing to at least `size` bytes of memory, 25 | /// suitably sized and aligned for memory protection. 26 | fn with_size(target: &LinuxTarget, size: u64, prot: libc::c_int) -> CrabResult { 27 | let page_size = *headcrab::target::PAGE_SIZE as u64; 28 | let alloc_size = round_up_to_page_size(size, page_size); 29 | let ptr = target.mmap( 30 | ptr::null_mut(), 31 | alloc_size as usize, 32 | prot, 33 | libc::MAP_ANONYMOUS | libc::MAP_PRIVATE, 34 | 0, 35 | 0, 36 | )?; 37 | Ok(Self { 38 | ptr, 39 | len: alloc_size, 40 | }) 41 | } 42 | } 43 | 44 | impl Drop for PtrLen { 45 | fn drop(&mut self) { 46 | if self.ptr != 0 { 47 | todo!("unmap") 48 | } 49 | } 50 | } 51 | 52 | /// JIT memory manager. This manages pages of suitably aligned and 53 | /// accessible memory. Memory will be leaked by default to have 54 | /// function pointers remain valid for the remainder of the 55 | /// program's life. 56 | pub struct Memory { 57 | prot: libc::c_int, 58 | allocations: Vec, 59 | current: PtrLen, 60 | position: u64, 61 | } 62 | 63 | impl Memory { 64 | pub fn new_executable() -> Self { 65 | Self { 66 | prot: libc::PROT_READ | libc::PROT_EXEC, 67 | allocations: Vec::new(), 68 | current: PtrLen::new(), 69 | position: 0, 70 | } 71 | } 72 | 73 | pub fn new_readonly() -> Self { 74 | Self { 75 | prot: libc::PROT_READ, 76 | allocations: Vec::new(), 77 | current: PtrLen::new(), 78 | position: 0, 79 | } 80 | } 81 | 82 | pub fn new_writable() -> Self { 83 | Self { 84 | prot: libc::PROT_READ | libc::PROT_WRITE, 85 | allocations: Vec::new(), 86 | current: PtrLen::new(), 87 | position: 0, 88 | } 89 | } 90 | 91 | pub fn allocate(&mut self, target: &LinuxTarget, size: u64, align: u64) -> CrabResult { 92 | if self.position % align != 0 { 93 | self.position += align - self.position % align; 94 | debug_assert!(self.position % align as u64 == 0); 95 | } 96 | 97 | if size <= self.current.len - self.position { 98 | // TODO: Ensure overflow is not possible. 99 | let ptr = self.current.ptr + self.position; 100 | self.position += size; 101 | return Ok(ptr); 102 | } 103 | 104 | // Finish current 105 | self.allocations 106 | .push(mem::replace(&mut self.current, PtrLen::new())); 107 | self.position = 0; 108 | 109 | // TODO: Allocate more at a time. 110 | self.current = PtrLen::with_size(target, size, self.prot)?; 111 | self.position = size; 112 | Ok(self.current.ptr) 113 | } 114 | 115 | /// Frees all allocated memory regions that would be leaked otherwise. 116 | /// Likely to invalidate existing function pointers, causing unsafety. 117 | pub unsafe fn free_memory(&mut self) { 118 | self.allocations.clear(); 119 | } 120 | } 121 | 122 | impl Drop for Memory { 123 | fn drop(&mut self) { 124 | // leak memory to guarantee validity of function pointers 125 | mem::take(&mut self.allocations) 126 | .into_iter() 127 | .for_each(mem::forget); 128 | mem::forget(mem::replace(&mut self.current, PtrLen::new())); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /headcrab_inject/src/module.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use cranelift_codegen::{binemit, ir, isa::TargetIsa, Context}; 4 | use cranelift_module::{DataId, FuncId}; 5 | use headcrab::{target::LinuxTarget, CrabResult}; 6 | 7 | use crate::InjectionContext; 8 | 9 | // FIXME unmap memory when done 10 | pub struct InjectionModule<'a> { 11 | inj_ctx: InjectionContext<'a>, 12 | functions: HashMap, 13 | data_objects: HashMap, 14 | breakpoint_trap: u64, 15 | } 16 | 17 | impl<'a> InjectionModule<'a> { 18 | pub fn new(target: &'a LinuxTarget) -> CrabResult { 19 | let mut inj_module = Self { 20 | inj_ctx: InjectionContext::new(target), 21 | functions: HashMap::new(), 22 | data_objects: HashMap::new(), 23 | breakpoint_trap: 0, 24 | }; 25 | 26 | inj_module.breakpoint_trap = inj_module.inj_ctx.allocate_code(1, None)?; 27 | inj_module 28 | .target() 29 | .write() 30 | .write(&0xcc, inj_module.breakpoint_trap as usize) 31 | .apply()?; 32 | 33 | Ok(inj_module) 34 | } 35 | 36 | pub fn inj_ctx(&mut self) -> &mut InjectionContext<'a> { 37 | &mut self.inj_ctx 38 | } 39 | 40 | pub fn target(&self) -> &LinuxTarget { 41 | self.inj_ctx.target() 42 | } 43 | 44 | pub fn breakpoint_trap(&self) -> u64 { 45 | self.breakpoint_trap 46 | } 47 | 48 | /// Allocate a new stack and return the bottom of the stack. 49 | pub fn new_stack(&mut self, size: u64) -> CrabResult { 50 | let stack = self.inj_ctx.allocate_readwrite(size, Some(16))?; 51 | 52 | // Ensure that we hit a breakpoint trap when returning from the injected function. 53 | self.target() 54 | .write() 55 | .write( 56 | &(self.breakpoint_trap() as usize), 57 | stack as usize + size as usize - std::mem::size_of::(), 58 | ) 59 | .apply()?; 60 | 61 | // Stack grows downwards on x86_64 62 | Ok(stack + size - std::mem::size_of::() as u64) 63 | } 64 | 65 | pub fn define_function(&mut self, func_id: FuncId, addr: u64) { 66 | assert!(self.functions.insert(func_id, addr).is_none()); 67 | } 68 | 69 | pub fn lookup_function(&self, func_id: FuncId) -> u64 { 70 | self.functions[&func_id] 71 | } 72 | 73 | pub fn define_data_object(&mut self, data_id: DataId, addr: u64) { 74 | assert!(self.data_objects.insert(data_id, addr).is_none()); 75 | } 76 | 77 | pub fn lookup_data_object(&self, data_id: DataId) -> u64 { 78 | self.data_objects[&data_id] 79 | } 80 | 81 | pub fn define_data_object_with_bytes( 82 | &mut self, 83 | data_id: DataId, 84 | bytes: &[u8], 85 | ) -> CrabResult<()> { 86 | let alloc = self.inj_ctx.allocate_readonly(bytes.len() as u64, None)?; 87 | self.target() 88 | .write() 89 | .write_slice(bytes, alloc as usize) 90 | .apply()?; 91 | self.define_data_object(data_id, alloc); 92 | 93 | Ok(()) 94 | } 95 | 96 | pub fn compile_clif_code(&mut self, isa: &dyn TargetIsa, ctx: &mut Context) -> CrabResult<()> { 97 | let mut code_mem = Vec::new(); 98 | 99 | ctx.compile_and_emit(isa, &mut code_mem).unwrap(); 100 | 101 | let code_region = self.inj_ctx.allocate_code(code_mem.len() as u64, None)?; 102 | 103 | for reloc_entry in ctx.mach_compile_result.as_ref().unwrap().buffer.relocs() { 104 | let sym = match reloc_entry.name { 105 | ir::ExternalName::User { 106 | namespace: 0, 107 | index, 108 | } => self.lookup_function(FuncId::from_u32(index)), 109 | ir::ExternalName::User { 110 | namespace: 1, 111 | index, 112 | } => self.lookup_data_object(DataId::from_u32(index)), 113 | _ => todo!("{:?}", reloc_entry.name), 114 | }; 115 | match reloc_entry.kind { 116 | binemit::Reloc::Abs8 => { 117 | code_mem[reloc_entry.offset as usize..reloc_entry.offset as usize + 8] 118 | .copy_from_slice(&u64::to_ne_bytes( 119 | (sym as i64 + reloc_entry.addend) as u64, 120 | )); 121 | } 122 | _ => todo!("reloc kind for {:?}", reloc_entry), 123 | } 124 | } 125 | 126 | self.target() 127 | .write() 128 | .write_slice(&code_mem, code_region as usize) 129 | .apply()?; 130 | 131 | self.define_function( 132 | match ctx.func.name { 133 | ir::ExternalName::User { namespace, index } => { 134 | assert_eq!(namespace, 0); 135 | FuncId::from_u32(index) 136 | } 137 | ir::ExternalName::TestCase { 138 | length: _, 139 | ascii: _, 140 | } => todo!(), 141 | ir::ExternalName::LibCall(_) => panic!("Can't define libcall"), 142 | }, 143 | code_region, 144 | ); 145 | 146 | Ok(()) 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /headcrab_inject/tests/inject_abort.rs: -------------------------------------------------------------------------------- 1 | //! Tests we can inject code in a process calling `abort()`. 2 | 3 | mod test_utils; 4 | 5 | #[cfg(target_os = "linux")] 6 | use cranelift_module::FuncId; 7 | #[cfg(target_os = "linux")] 8 | use headcrab::{ 9 | symbol::RelocatedDwarf, 10 | target::{Registers, UnixTarget}, 11 | }; 12 | #[cfg(target_os = "linux")] 13 | use headcrab_inject::InjectionModule; 14 | #[cfg(target_os = "linux")] 15 | use nix::sys::{signal::Signal, wait::WaitStatus}; 16 | 17 | static BIN_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/testees/hello"); 18 | 19 | // FIXME: Running this test just for linux because of privileges issue on macOS. Enable for everything after fixing. 20 | #[cfg(target_os = "linux")] 21 | #[test] 22 | fn inject_abort() -> headcrab::CrabResult<()> { 23 | test_utils::ensure_testees(); 24 | 25 | let target = test_utils::launch(BIN_PATH); 26 | 27 | let mut debuginfo = RelocatedDwarf::from_maps(&target.memory_maps()?)?; 28 | 29 | test_utils::patch_breakpoint(&target, &debuginfo); 30 | 31 | target.unpause()?; 32 | 33 | debuginfo = RelocatedDwarf::from_maps(&target.memory_maps()?)?; 34 | let mut inj_module = InjectionModule::new(&target)?; 35 | let abort_function = debuginfo.get_symbol_address("abort").unwrap() as u64; 36 | println!("exit fn ptr: {:016x}", abort_function); 37 | inj_module.define_function(FuncId::from_u32(0), abort_function); 38 | 39 | let isa = headcrab_inject::target_isa(); 40 | 41 | let functions = cranelift_reader::parse_functions( 42 | r#" 43 | function u0:1() system_v { 44 | sig0 = () system_v 45 | fn0 = u0:0 sig0 46 | 47 | block0: 48 | call fn0() 49 | return 50 | }"#, 51 | ) 52 | .unwrap(); 53 | let mut ctx = cranelift_codegen::Context::new(); 54 | for func in functions { 55 | ctx.clear(); 56 | ctx.func = func; 57 | inj_module.compile_clif_code(&*isa, &mut ctx)?; 58 | } 59 | 60 | let run_function = inj_module.lookup_function(FuncId::from_u32(1)); 61 | let stack_region = inj_module.inj_ctx().allocate_readwrite(0x1000, Some(16))?; 62 | println!( 63 | "run function: 0x{:016x} stack: 0x{:016x}", 64 | run_function, stack_region 65 | ); 66 | 67 | let orig_regs = inj_module.target().main_thread()?.read_regs()?; 68 | println!("orig rip: {:016x}", orig_regs.ip()); 69 | 70 | let mut regs = orig_regs.clone(); 71 | regs.set_ip(run_function); 72 | regs.set_sp(stack_region + 0x1000); 73 | 74 | inj_module.target().main_thread()?.write_regs(regs)?; 75 | 76 | let res = inj_module.target().unpause()?; 77 | if let WaitStatus::Stopped(_, Signal::SIGABRT) = res { 78 | } else { 79 | println!("rip: {:016x}", inj_module.target().read_regs()?.ip()); 80 | panic!("{:?}", res); 81 | } 82 | 83 | Ok(()) 84 | } 85 | -------------------------------------------------------------------------------- /headcrab_inject/tests/test_utils.rs: -------------------------------------------------------------------------------- 1 | ../../tests/test_utils.rs -------------------------------------------------------------------------------- /headcrab_inject/tests/testees: -------------------------------------------------------------------------------- 1 | ../../tests/testees -------------------------------------------------------------------------------- /hello_dylib.rs: -------------------------------------------------------------------------------- 1 | #![crate_type = "cdylib"] 2 | #![no_std] 3 | 4 | extern "C" { 5 | fn puts(_: *const u8); 6 | } 7 | 8 | #[panic_handler] 9 | fn panic_handler(_: &core::panic::PanicInfo) -> ! { 10 | loop {} 11 | } 12 | 13 | #[no_mangle] 14 | unsafe extern "C" fn __headcrab_command() { 15 | puts("Hello World from dylib!\0" as *const str as *const u8); 16 | } 17 | -------------------------------------------------------------------------------- /repl_tools/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "repl_tools" 3 | version = "0.1.0" 4 | authors = ["Headcrab Contributors"] 5 | edition = "2018" 6 | repository = "https://github.com/headcrab-rs/headcrab/" 7 | license = "MIT OR Apache-2.0" 8 | 9 | [dependencies] 10 | rustyline = "6.2.0" 11 | -------------------------------------------------------------------------------- /repl_tools/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | borrow::Cow, 3 | error::Error, 4 | fmt, 5 | marker::PhantomData, 6 | path::{Path, PathBuf}, 7 | }; 8 | 9 | use rustyline::{ 10 | completion::{Completer, FilenameCompleter, Pair}, 11 | highlight::Highlighter, 12 | hint::Hinter, 13 | validate::Validator, 14 | Helper, 15 | }; 16 | 17 | #[doc(hidden)] 18 | pub use rustyline as __rustyline; 19 | 20 | pub trait HighlightAndComplete: Sized { 21 | type Error: Error; 22 | 23 | fn from_str(line: &str) -> Result; 24 | fn highlight(line: &str) -> Cow; 25 | fn complete( 26 | line: &str, 27 | pos: usize, 28 | ctx: &rustyline::Context<'_>, 29 | ) -> rustyline::Result<(usize, Vec)>; 30 | } 31 | 32 | pub struct MakeHelper { 33 | pub color: bool, 34 | _marker: PhantomData, 35 | } 36 | 37 | impl MakeHelper { 38 | pub fn new(color: bool) -> Self { 39 | Self { 40 | color, 41 | _marker: PhantomData, 42 | } 43 | } 44 | } 45 | 46 | impl Helper for MakeHelper {} 47 | 48 | impl Validator for MakeHelper {} 49 | 50 | impl Hinter for MakeHelper {} 51 | 52 | impl Highlighter for MakeHelper { 53 | fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> { 54 | if self.color { 55 | T::highlight(line) 56 | } else { 57 | line.into() 58 | } 59 | } 60 | 61 | fn highlight_prompt<'b, 's: 'b, 'p: 'b>( 62 | &'s self, 63 | prompt: &'p str, 64 | _default: bool, 65 | ) -> std::borrow::Cow<'b, str> { 66 | if self.color { 67 | format!("\x1b[90m{}\x1b[0m", prompt).into() 68 | } else { 69 | prompt.into() 70 | } 71 | } 72 | 73 | fn highlight_char(&self, _line: &str, _pos: usize) -> bool { 74 | self.color 75 | } 76 | } 77 | 78 | impl Completer for MakeHelper { 79 | type Candidate = Pair; 80 | 81 | fn complete( 82 | &self, 83 | line: &str, 84 | pos: usize, 85 | ctx: &rustyline::Context<'_>, 86 | ) -> rustyline::Result<(usize, Vec)> { 87 | T::complete(line, pos, ctx) 88 | } 89 | } 90 | 91 | #[derive(Debug)] 92 | pub struct NoArgExpectedError(String); 93 | 94 | impl fmt::Display for NoArgExpectedError { 95 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 96 | write!(f, "No arguments were expected, found `{}`", self.0) 97 | } 98 | } 99 | 100 | impl Error for NoArgExpectedError {} 101 | 102 | impl HighlightAndComplete for () { 103 | type Error = NoArgExpectedError; 104 | 105 | fn from_str(line: &str) -> Result { 106 | if line.trim() == "" { 107 | Ok(()) 108 | } else { 109 | Err(NoArgExpectedError(line.trim().to_owned())) 110 | } 111 | } 112 | 113 | fn highlight(line: &str) -> Cow { 114 | format!("\x1b[91m{}\x1b[0m", line).into() 115 | } 116 | 117 | fn complete( 118 | line: &str, 119 | pos: usize, 120 | ctx: &rustyline::Context<'_>, 121 | ) -> rustyline::Result<(usize, Vec)> { 122 | let _ = (line, pos, ctx); 123 | Ok((0, vec![])) 124 | } 125 | } 126 | 127 | #[derive(Debug)] 128 | pub enum VoidError {} 129 | 130 | impl fmt::Display for VoidError { 131 | fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { 132 | match *self {} 133 | } 134 | } 135 | 136 | impl Error for VoidError {} 137 | 138 | impl HighlightAndComplete for String { 139 | type Error = VoidError; 140 | 141 | fn from_str(line: &str) -> Result { 142 | Ok(line.trim().to_owned()) 143 | } 144 | 145 | fn highlight(line: &str) -> Cow { 146 | line.into() 147 | } 148 | 149 | fn complete( 150 | line: &str, 151 | pos: usize, 152 | ctx: &rustyline::Context<'_>, 153 | ) -> rustyline::Result<(usize, Vec)> { 154 | let _ = (line, pos, ctx); 155 | Ok((0, vec![])) 156 | } 157 | } 158 | 159 | pub struct FileNameArgument(PathBuf); 160 | 161 | impl HighlightAndComplete for PathBuf { 162 | type Error = VoidError; 163 | 164 | fn from_str(line: &str) -> Result { 165 | Ok(Path::new(line.trim()).to_owned()) 166 | } 167 | 168 | fn highlight(line: &str) -> Cow { 169 | let path = std::path::Path::new(line.trim()); 170 | if path.is_file() { 171 | // FIXME better colors 172 | format!("\x1b[96m{}\x1b[0m", line).into() 173 | } else if path.exists() { 174 | format!("\x1b[95m{}\x1b[0m", line).into() 175 | } else { 176 | line.into() 177 | } 178 | } 179 | 180 | fn complete( 181 | line: &str, 182 | pos: usize, 183 | _ctx: &__rustyline::Context<'_>, 184 | ) -> __rustyline::Result<(usize, Vec)> { 185 | FilenameCompleter::new().complete_path(line, pos) 186 | } 187 | } 188 | 189 | #[macro_export] 190 | macro_rules! define_repl_cmds { 191 | (enum $command:ident { 192 | err = $error_ty:ident; 193 | $( 194 | #[doc = $doc:literal] 195 | $cmd:ident$(|$alias:ident)*: $argument:ty, 196 | )* 197 | }) => { 198 | enum $command { 199 | $( 200 | $cmd($argument), 201 | )* 202 | } 203 | 204 | #[derive(Debug)] 205 | enum $error_ty { 206 | UnknownCommand(String), 207 | $( 208 | $cmd(<$argument as $crate::HighlightAndComplete>::Error), 209 | )* 210 | } 211 | 212 | impl std::fmt::Display for $error_ty { 213 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 214 | match self { 215 | $error_ty::UnknownCommand(cmd) => write!(f, "Unknown command `{}`", cmd), 216 | $( 217 | $error_ty::$cmd(err) => write!(f, "{}: {}", stringify!($cmd).to_lowercase(), err), 218 | )* 219 | } 220 | } 221 | } 222 | 223 | impl std::error::Error for $error_ty {} 224 | 225 | impl $command { 226 | fn print_help(mut w: impl std::io::Write, color: bool) -> std::io::Result<()> { 227 | $( 228 | if color { 229 | write!(w, "\x1b[1m{}\x1b[0m -- ", concat!(stringify!($cmd $(|$alias)*)).to_lowercase())?; 230 | } else { 231 | write!(w, "{} -- ", concat!(stringify!($cmd $(|$alias)*)).to_lowercase())?; 232 | } 233 | writeln!(w, "{}", $doc.trim())?; 234 | )* 235 | Ok(()) 236 | } 237 | } 238 | 239 | impl $crate::HighlightAndComplete for $command { 240 | type Error = $error_ty; 241 | 242 | fn from_str(line: &str) -> Result { 243 | let cmd_len = line.find(' ').unwrap_or(line.len()); 244 | let (chosen_cmd, rest) = line.split_at(cmd_len); 245 | 246 | $( 247 | if [stringify!($cmd) $(,stringify!($alias))*][..].iter().any(|cmd| cmd.eq_ignore_ascii_case(chosen_cmd)) { 248 | return match <$argument as $crate::HighlightAndComplete>::from_str(rest) { 249 | Ok(cmd) => Ok(Self::$cmd(cmd)), 250 | Err(err) => Err($error_ty::$cmd(err)), 251 | }; 252 | } 253 | )* 254 | 255 | Err($error_ty::UnknownCommand(chosen_cmd.to_owned())) 256 | } 257 | 258 | fn highlight<'l>(line: &'l str) -> std::borrow::Cow<'l, str> { 259 | let cmd_len = line.find(' ').unwrap_or(line.len()); 260 | let (chosen_cmd, rest) = line.split_at(cmd_len); 261 | $( 262 | if [stringify!($cmd) $(,stringify!($alias))*][..].iter().any(|cmd| cmd.eq_ignore_ascii_case(chosen_cmd)) { 263 | let highlighted_argument = 264 | <$argument as $crate::HighlightAndComplete>::highlight(rest); 265 | return format!("\x1b[93m{}\x1b[0m{}", chosen_cmd, highlighted_argument).into(); 266 | } 267 | )* 268 | line.into() 269 | } 270 | 271 | fn complete( 272 | line: &str, 273 | pos: usize, 274 | ctx: &$crate::__rustyline::Context<'_>, 275 | ) -> $crate::__rustyline::Result<(usize, Vec<$crate::__rustyline::completion::Pair>)> { 276 | let cmd_len = line.find(' ').unwrap_or(line.len()); 277 | if pos <= cmd_len { 278 | let candidates = [$(stringify!($cmd).to_lowercase() $(,stringify!($alias).to_lowercase())*),*][..] 279 | .iter() 280 | .filter(|cmd| cmd.starts_with(line)) 281 | .map(|cmd| $crate::__rustyline::completion::Pair { 282 | display: cmd.to_owned(), 283 | replacement: cmd.to_owned() + " ", 284 | }) 285 | .collect::>(); 286 | 287 | let _ = (line, pos, ctx); 288 | return Ok((0, candidates)); 289 | } 290 | 291 | let (chosen_cmd, rest) = line.split_at(cmd_len); 292 | $( 293 | if [stringify!($cmd) $(,stringify!($alias))*][..].iter().copied().any(|cmd| cmd.eq_ignore_ascii_case(chosen_cmd)) { 294 | let pos = pos - cmd_len; 295 | let (at, completions) = 296 | <$argument as $crate::HighlightAndComplete>::complete(rest, pos, ctx)?; 297 | return Ok((at + cmd_len, completions)); 298 | } 299 | )* 300 | 301 | Ok((0, vec![])) 302 | } 303 | } 304 | }; 305 | } 306 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Headcrab, a modern Rust debugging library. 2 | 3 | pub type CrabResult = Result>; 4 | 5 | /// Functions to work with target processes: reading & writing memory, process control functions, etc. 6 | pub mod target; 7 | 8 | /// Symbolication layer. 9 | #[cfg(unix)] 10 | pub mod symbol; 11 | -------------------------------------------------------------------------------- /src/symbol.rs: -------------------------------------------------------------------------------- 1 | //! This module provides a naive implementation of symbolication for the time being. 2 | //! It should be expanded to support multiple data sources. 3 | 4 | #[macro_use] 5 | pub mod dwarf_utils; 6 | pub mod unwind; 7 | 8 | use gimli::read::{EvaluationResult, Reader as _}; 9 | use object::{ 10 | read::{Object, ObjectSection, ObjectSegment}, 11 | SymbolKind, 12 | }; 13 | use std::{ 14 | borrow::Cow, 15 | collections::{BTreeMap, HashMap}, 16 | fs::File, 17 | path::Path, 18 | }; 19 | pub use sym::Symbol; 20 | 21 | mod frame; 22 | mod relocate; 23 | mod source; 24 | mod sym; 25 | 26 | use crate::CrabResult; 27 | pub use frame::{Frame, FrameIter, Local, LocalValue, PrimitiveValue}; 28 | pub use relocate::RelocatedDwarf; 29 | pub use source::{DisassemblySource, Snippet}; 30 | 31 | mod rc_cow { 32 | use std::rc::Rc; 33 | 34 | // Avoid private-in-public error by making this public. 35 | #[derive(Debug)] 36 | pub enum RcCow<'a, T: ?Sized> { 37 | Owned(Rc), 38 | Borrowed(&'a T), 39 | } 40 | } 41 | 42 | use rc_cow::RcCow; 43 | 44 | impl Clone for RcCow<'_, T> { 45 | fn clone(&self) -> Self { 46 | match self { 47 | RcCow::Owned(rc) => RcCow::Owned(rc.clone()), 48 | RcCow::Borrowed(slice) => RcCow::Borrowed(&**slice), 49 | } 50 | } 51 | } 52 | 53 | impl std::ops::Deref for RcCow<'_, T> { 54 | type Target = T; 55 | 56 | fn deref(&self) -> &Self::Target { 57 | match self { 58 | RcCow::Owned(rc) => &**rc, 59 | RcCow::Borrowed(slice) => &**slice, 60 | } 61 | } 62 | } 63 | 64 | unsafe impl gimli::StableDeref for RcCow<'_, T> {} 65 | unsafe impl gimli::CloneStableDeref for RcCow<'_, T> {} 66 | 67 | pub type Reader<'a> = gimli::EndianReader>; 68 | 69 | pub struct ParsedDwarf<'a> { 70 | object: object::File<'a>, 71 | addr2line: addr2line::Context>, 72 | vars: BTreeMap>, gimli::Expression>)>, 73 | symbols: Vec>, 74 | symbol_names: HashMap, 75 | } 76 | 77 | impl<'a> ParsedDwarf<'a> { 78 | pub fn new(bytes: &'a [u8]) -> CrabResult> { 79 | // This is completely inefficient and hackish code, but currently it serves the only 80 | // purpose of getting addresses of static variables. 81 | // TODO: this will be reworked in a more complete symbolication framework. 82 | 83 | let object = object::File::parse(bytes)?; 84 | 85 | let endian = if object.is_little_endian() { 86 | gimli::RunTimeEndian::Little 87 | } else { 88 | gimli::RunTimeEndian::Big 89 | }; 90 | 91 | // This can be also processed in parallel. 92 | let loader = |id: gimli::SectionId| -> Result { 93 | match object.section_by_name(id.name()) { 94 | Some(ref section) => { 95 | let data = section 96 | .uncompressed_data() 97 | .unwrap_or(Cow::Borrowed(&[][..])); 98 | let data = match data { 99 | Cow::Owned(vec) => RcCow::Owned(vec.into()), 100 | Cow::Borrowed(slice) => RcCow::Borrowed(slice), 101 | }; 102 | Ok(gimli::EndianReader::new(data, endian)) 103 | } 104 | None => Ok(gimli::EndianReader::new(RcCow::Borrowed(&[][..]), endian)), 105 | } 106 | }; 107 | 108 | // Create `EndianSlice`s for all of the sections. 109 | let dwarf = gimli::Dwarf::load(loader)?; 110 | 111 | let addr2line = addr2line::Context::from_dwarf(dwarf)?; 112 | let dwarf = addr2line.dwarf(); 113 | 114 | let mut units = dwarf.units(); 115 | 116 | let mut vars = BTreeMap::new(); 117 | while let Some(header) = units.next()? { 118 | let unit = dwarf.unit(header.clone())?; 119 | let mut entries = unit.entries(); 120 | while let Some((_, entry)) = entries.next_dfs()? { 121 | if entry.tag() == gimli::DW_TAG_variable { 122 | let name = dwarf_attr!(str(dwarf, unit) entry.DW_AT_name || continue) 123 | .to_string()? 124 | .into_owned(); 125 | if let Some(expr) = 126 | dwarf_attr!(entry.DW_AT_location || continue).exprloc_value() 127 | { 128 | vars.insert(name, (header.clone(), expr)); 129 | } 130 | } 131 | } 132 | } 133 | 134 | let mut symbols: Vec<_> = object 135 | .symbols() 136 | .chain(object.dynamic_symbols()) 137 | .map(|(_, sym)| sym) 138 | .filter(|symbol| { 139 | // Copied from `object::read::SymbolMap::filter` 140 | match symbol.kind() { 141 | SymbolKind::Unknown | SymbolKind::Text | SymbolKind::Data => {} 142 | SymbolKind::Null 143 | | SymbolKind::Section 144 | | SymbolKind::File 145 | | SymbolKind::Label 146 | | SymbolKind::Tls => { 147 | return false; 148 | } 149 | } 150 | !symbol.is_undefined() && symbol.section() != object::SymbolSection::Common 151 | }) 152 | .map(Into::into) 153 | .collect(); 154 | symbols.sort_by_key(|sym: &Symbol| sym.address()); 155 | 156 | let mut symbol_names = HashMap::new(); 157 | for sym in &symbols { 158 | if let Some(name) = sym.demangled_name() { 159 | symbol_names.insert(name.to_string(), sym.address() as usize); 160 | } 161 | } 162 | 163 | Ok(ParsedDwarf { 164 | object, 165 | addr2line, 166 | vars, 167 | symbols, 168 | symbol_names, 169 | }) 170 | } 171 | 172 | pub fn get_symbol_address(&self, name: &str) -> Option { 173 | self.symbol_names.get(name).copied() 174 | } 175 | 176 | pub fn get_address_symbol(&self, addr: usize) -> Option> { 177 | let index = match self 178 | .symbols 179 | .binary_search_by(|sym| sym.address().cmp(&(addr as u64))) 180 | { 181 | // Found an exact match. 182 | Ok(index) => index, 183 | // Address before the first symbol. 184 | Err(0) => return None, 185 | // Address between two symbols. `index` is the index of the later of the two. 186 | Err(index) => index - 1, 187 | }; 188 | let symbol = &self.symbols[index]; 189 | if self.symbols.get(index + 1).map(|sym| sym.address()) <= Some(addr as u64) { 190 | return None; 191 | } 192 | Some(symbol.clone()) 193 | 194 | // FIXME `size` is wrong in some cases. Once this is solved use the following instead. 195 | /* 196 | let sym = symbols.binary_search_by(|sym| { 197 | use std::cmp::Ordering; 198 | if svma < Svma(sym.address()) { 199 | Ordering::Greater 200 | } else if svma < Svma(sym.address() + sym.size()) { 201 | Ordering::Equal 202 | } else { 203 | Ordering::Less 204 | } 205 | }).ok().and_then(|idx| symbols.get(idx)); 206 | */ 207 | } 208 | 209 | pub fn get_var_address(&self, name: &str) -> CrabResult> { 210 | if let Some((unit_header, expr)) = self.vars.get(name) { 211 | let unit = self.addr2line.dwarf().unit(unit_header.clone())?; 212 | let mut eval = expr.clone().evaluation(unit.encoding()); 213 | match eval.evaluate()? { 214 | EvaluationResult::RequiresRelocatedAddress(reloc_addr) => { 215 | return Ok(Some(reloc_addr as usize)); 216 | } 217 | _ev_res => {} // do nothing for now 218 | } 219 | } 220 | Ok(None) 221 | } 222 | 223 | pub fn get_addr_frames(&'a self, addr: usize) -> CrabResult> { 224 | Ok(FrameIter { 225 | dwarf: self.addr2line.dwarf(), 226 | unit: self.addr2line.find_dwarf_unit(addr as u64), 227 | iter: self.addr2line.find_frames(addr as u64)?, 228 | }) 229 | } 230 | } 231 | 232 | mod inner { 233 | use super::*; 234 | use std::mem::{self, ManuallyDrop}; 235 | 236 | pub struct Dwarf { 237 | _mmap: memmap::Mmap, 238 | parsed: ManuallyDrop>, 239 | } 240 | 241 | impl Dwarf { 242 | // todo: impl loader struct instead of taking 'path' as an argument. 243 | // It will be required to e.g. load coredumps, or external debug info, or to 244 | // communicate with rustc/lang servers. 245 | pub fn new>(path: P) -> CrabResult { 246 | // Load ELF/Mach-O object file 247 | let file = File::open(path)?; 248 | 249 | // Safety: Not really, this assumes that the backing file will not be truncated or 250 | // written to while it is used by us. 251 | let mmap = unsafe { memmap::Mmap::map(&file)? }; 252 | 253 | let parsed = ManuallyDrop::new(ParsedDwarf::new(&*mmap)?); 254 | 255 | // Safety: `parsed` doesn't outlive `mmap`, from which it borrows, because no reference 256 | // to `parsed` can be obtained without the lifetime being shortened to be smaller than 257 | // the `Dwarf` that contains both `parsed` and the `mmap` it borrows from. 258 | let parsed = unsafe { 259 | mem::transmute::>, ManuallyDrop>>( 260 | parsed, 261 | ) 262 | }; 263 | 264 | Ok(Dwarf { 265 | _mmap: mmap, 266 | parsed, 267 | }) 268 | } 269 | 270 | pub fn rent(&self, f: impl for<'a> FnOnce(&'a ParsedDwarf<'a>) -> T) -> T { 271 | // Safety: `ParsedDwarf` is invariant in `'a`. This means that `ParsedDwarf<'static>` 272 | // can't safely turn into `ParsedDwarf<'a>`, as otherwise it would be possible to put a 273 | // short living reference into `ParsedDwarf`. However because of the `for<'a>` on 274 | // `FnOnce`, this is prevented, so the transmute here is safe. 275 | f(unsafe { mem::transmute::<&ParsedDwarf<'static>, &ParsedDwarf<'_>>(&*self.parsed) }) 276 | } 277 | } 278 | 279 | impl Drop for Dwarf { 280 | fn drop(&mut self) { 281 | unsafe { 282 | ManuallyDrop::drop(&mut self.parsed); 283 | } 284 | } 285 | } 286 | } 287 | 288 | pub use inner::Dwarf; 289 | 290 | impl Dwarf { 291 | pub fn get_symbol_address(&self, name: &str) -> Option { 292 | self.rent(|parsed| parsed.get_symbol_address(name)) 293 | } 294 | 295 | pub fn get_address_symbol_name(&self, addr: usize) -> Option { 296 | self.rent(|parsed| Some(parsed.get_address_symbol(addr)?.name()?.to_string())) 297 | } 298 | 299 | pub fn get_address_demangled_name(&self, addr: usize) -> Option { 300 | self.rent(|parsed| { 301 | Some( 302 | parsed 303 | .get_address_symbol(addr)? 304 | .demangled_name()? 305 | .to_string(), 306 | ) 307 | }) 308 | } 309 | 310 | pub fn get_address_symbol_kind(&self, addr: usize) -> Option { 311 | self.rent(|parsed| Some(parsed.get_address_symbol(addr)?.kind())) 312 | } 313 | 314 | pub fn get_var_address(&self, name: &str) -> CrabResult> { 315 | self.rent(|parsed| parsed.get_var_address(name)) 316 | } 317 | 318 | pub fn with_addr_frames FnOnce(usize, FrameIter<'a>) -> CrabResult>( 319 | &self, 320 | addr: usize, 321 | f: F, 322 | ) -> CrabResult { 323 | self.rent(|parsed| f(addr, parsed.get_addr_frames(addr)?)) 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /src/symbol/dwarf_utils.rs: -------------------------------------------------------------------------------- 1 | use gimli::{DebuggingInformationEntry, Dwarf, Unit, UnitOffset, ValueType}; 2 | 3 | use super::Reader; 4 | use crate::CrabResult; 5 | 6 | macro_rules! dwarf_attr { 7 | (str($dwarf:ident,$unit:ident) $entry:ident.$name:ident || $missing:ident) => { 8 | if let Some(attr) = $entry.attr(gimli::$name)? { 9 | dwarf_attr_exists_action_action!($missing, $dwarf.attr_string(&$unit, attr.value())?) 10 | } else { 11 | dwarf_attr_missing_action!($missing, $name) 12 | } 13 | }; 14 | (unit_ref $entry:ident.$name:ident || $missing:ident) => { 15 | if let Some(attr) = $entry.attr(gimli::$name)? { 16 | match attr.value() { 17 | gimli::AttributeValue::UnitRef(unit_ref) => { 18 | dwarf_attr_exists_action_action!($missing, unit_ref) 19 | } 20 | val => { 21 | return Err(format!( 22 | "`{:?}` is not a valid value for a {} attribute", 23 | val, 24 | gimli::$name, 25 | ) 26 | .into()) 27 | } 28 | } 29 | } else { 30 | dwarf_attr_missing_action!($missing, $name) 31 | } 32 | }; 33 | (udata $entry:ident.$name:ident || $missing:ident) => { 34 | if let Some(attr) = $entry.attr(gimli::$name)? { 35 | dwarf_attr_exists_action_action!( 36 | $missing, 37 | attr.udata_value() 38 | .ok_or(concat!("invalid value for", stringify!($name)))? 39 | ) 40 | } else { 41 | dwarf_attr_missing_action!($missing, $name) 42 | } 43 | }; 44 | (encoding $entry:ident.$name:ident || $missing:ident) => { 45 | if let Some(attr) = $entry.attr(gimli::$name)? { 46 | match attr.value() { 47 | gimli::AttributeValue::Encoding(encoding) => { 48 | dwarf_attr_exists_action_action!($missing, encoding) 49 | } 50 | encoding => { 51 | return Err( 52 | format!("invalid value for {}: {:?}", gimli::$name, encoding).into(), 53 | ) 54 | } 55 | } 56 | } else { 57 | dwarf_attr_missing_action!($missing, $name) 58 | } 59 | }; 60 | ($entry:ident.$name:ident || $missing:ident) => { 61 | if let Some(attr) = $entry.attr(gimli::$name)? { 62 | dwarf_attr_exists_action_action!($missing, attr) 63 | } else { 64 | dwarf_attr_missing_action!($missing, $name) 65 | } 66 | }; 67 | } 68 | 69 | macro_rules! dwarf_attr_exists_action_action { 70 | (continue, $val:expr) => { 71 | $val 72 | }; 73 | (error, $val:expr) => { 74 | $val 75 | }; 76 | (None, $val:expr) => { 77 | Some($val) 78 | }; 79 | } 80 | 81 | macro_rules! dwarf_attr_missing_action { 82 | (continue, $name:ident) => { 83 | continue 84 | }; 85 | (error, $name:ident) => { 86 | return Err(concat!("missing ", stringify!($name), " attribute").into()) 87 | }; 88 | (None, $name:ident) => { 89 | None 90 | }; 91 | } 92 | 93 | pub fn in_range( 94 | dwarf: &Dwarf, 95 | unit: &Unit, 96 | entry: Option<&DebuggingInformationEntry>, 97 | addr: u64, 98 | ) -> gimli::Result { 99 | if let Some(entry) = entry { 100 | let mut ranges = dwarf.die_ranges(unit, entry)?; 101 | while let Some(range) = ranges.next()? { 102 | if range.begin <= addr && range.end > addr { 103 | return Ok(true); 104 | } 105 | } 106 | } else { 107 | let mut ranges = dwarf.unit_ranges(unit)?; 108 | while let Some(range) = ranges.next()? { 109 | if range.begin <= addr && range.end > addr { 110 | return Ok(true); 111 | } 112 | } 113 | } 114 | 115 | Ok(false) 116 | } 117 | 118 | pub enum SearchAction { 119 | #[allow(dead_code)] // FIXME 120 | Found(T), 121 | VisitChildren, 122 | SkipChildren, 123 | } 124 | 125 | pub fn search_tree<'a, 'dwarf, 'unit: 'dwarf, T, E: From>( 126 | unit: &'unit Unit>, 127 | offset: Option, 128 | mut f: impl FnMut( 129 | DebuggingInformationEntry<'dwarf, 'unit, Reader<'a>>, 130 | usize, 131 | ) -> Result, E>, 132 | ) -> Result, E> { 133 | fn process_tree<'a, 'dwarf, 'unit: 'dwarf, T, E: From>( 134 | unit: &Unit>, 135 | node: gimli::EntriesTreeNode<'dwarf, 'unit, '_, Reader<'a>>, 136 | indent: usize, 137 | f: &mut impl FnMut( 138 | DebuggingInformationEntry<'dwarf, 'unit, Reader<'a>>, 139 | usize, 140 | ) -> Result, E>, 141 | ) -> Result, E> { 142 | let entry = node.entry().clone(); 143 | 144 | match f(entry, indent)? { 145 | SearchAction::Found(val) => Ok(Some(val)), 146 | SearchAction::VisitChildren => { 147 | let mut children = node.children(); 148 | while let Some(child) = children.next()? { 149 | // Recursively process a child. 150 | if let Some(val) = process_tree(unit, child, indent + 1, f)? { 151 | return Ok(Some(val)); 152 | } 153 | } 154 | Ok(None) 155 | } 156 | SearchAction::SkipChildren => Ok(None), 157 | } 158 | } 159 | 160 | let mut entries_tree = unit.entries_tree(offset)?; 161 | process_tree(unit, entries_tree.root()?, 0, &mut f) 162 | } 163 | 164 | pub trait EvalContext { 165 | fn frame_base(&self) -> u64; 166 | fn register(&self, register: gimli::Register, base_type: ValueType) -> gimli::Value; 167 | fn memory( 168 | &self, 169 | address: u64, 170 | size: u8, 171 | address_space: Option, 172 | base_type: ValueType, 173 | ) -> gimli::Value; 174 | } 175 | 176 | fn value_type_from_base_type( 177 | unit: &Unit>, 178 | base_type: UnitOffset, 179 | ) -> CrabResult { 180 | if base_type.0 == 0 { 181 | Ok(ValueType::Generic) 182 | } else { 183 | let base_type_die = unit.entry(base_type)?; 184 | Ok(ValueType::from_entry(&base_type_die)?.ok_or_else(|| "not a base type".to_owned())?) 185 | } 186 | } 187 | 188 | pub fn evaluate_expression<'a>( 189 | unit: &gimli::Unit>, 190 | expr: gimli::Expression>, 191 | eval_ctx: &impl EvalContext, 192 | ) -> CrabResult>>> { 193 | let mut eval = expr.evaluation(unit.encoding()); 194 | let mut res = eval.evaluate()?; 195 | loop { 196 | match res { 197 | gimli::EvaluationResult::Complete => { 198 | return Ok(eval.result()); 199 | } 200 | gimli::EvaluationResult::RequiresFrameBase => { 201 | res = eval.resume_with_frame_base(eval_ctx.frame_base())?; 202 | } 203 | gimli::EvaluationResult::RequiresRegister { 204 | register, 205 | base_type, 206 | } => { 207 | let ty = value_type_from_base_type(unit, base_type)?; 208 | res = eval.resume_with_register(eval_ctx.register(register, ty))?; 209 | } 210 | gimli::EvaluationResult::RequiresMemory { 211 | address, 212 | size, 213 | space, 214 | base_type, 215 | } => { 216 | let ty = value_type_from_base_type(unit, base_type)?; 217 | res = eval.resume_with_memory(eval_ctx.memory(address, size, space, ty))?; 218 | } 219 | gimli::EvaluationResult::RequiresTls(_addr) => todo!("TLS"), 220 | gimli::EvaluationResult::RequiresCallFrameCfa => todo!("CFA"), 221 | gimli::EvaluationResult::RequiresAtLocation(_expr_ref) => todo!("at location"), 222 | gimli::EvaluationResult::RequiresEntryValue(_expr) => { 223 | // FIXME implement this 224 | // See https://github.com/llvm/llvm-project/blob/895337647896edefda244c7afc4b71eab41ff850/lldb/source/Expression/DWARFExpression.cpp#L645-L687 225 | todo!("entry value"); 226 | } 227 | gimli::EvaluationResult::RequiresParameterRef(_param_ref) => todo!("parameter ref"), 228 | gimli::EvaluationResult::RequiresRelocatedAddress(_addr) => todo!("relocated address"), 229 | gimli::EvaluationResult::RequiresIndexedAddress { 230 | index: _, 231 | relocate: _, 232 | } => todo!("indexed address"), 233 | gimli::EvaluationResult::RequiresBaseType(_base_type) => todo!("base type"), 234 | } 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/symbol/relocate.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub struct RelocatedDwarf(Vec); 4 | 5 | struct RelocatedDwarfEntry { 6 | address_range: (u64, u64), 7 | file_range: (u64, u64), 8 | bias: u64, 9 | dwarf: Dwarf, 10 | } 11 | 12 | impl std::fmt::Debug for RelocatedDwarfEntry { 13 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 14 | f.debug_struct("RelocatedDwarfEntry") 15 | .field( 16 | "address_range", 17 | &format!( 18 | "{:016x}..{:016x}", 19 | self.address_range.0, self.address_range.1 20 | ), 21 | ) 22 | .field( 23 | "file_range", 24 | &format!("{:016x}..{:016x}", self.file_range.0, self.file_range.1), 25 | ) 26 | .field("bias", &format!("{:016x}", self.bias)) 27 | .field( 28 | "stated_range", 29 | &format!( 30 | "{:016x}..{:016x}", 31 | self.address_range.0 - self.bias, 32 | self.address_range.1 - self.bias 33 | ), 34 | ) 35 | .finish() 36 | } 37 | } 38 | 39 | impl RelocatedDwarfEntry { 40 | fn from_file_and_offset(address: (u64, u64), file: &Path, offset: u64) -> CrabResult { 41 | match Dwarf::new(file) { 42 | Ok(dwarf) => { 43 | let (file_range, stated_address) = dwarf 44 | .rent(|parsed| { 45 | let object: &object::File = &parsed.object; 46 | object.segments().find_map(|segment: object::Segment| { 47 | // Sometimes the offset is just before the start file offset of the segment. 48 | if offset <= segment.file_range().0 + segment.file_range().1 { 49 | Some(( 50 | segment.file_range(), 51 | segment.address() - segment.file_range().0 + offset, 52 | )) 53 | } else { 54 | None 55 | } 56 | }) 57 | }) 58 | .ok_or_else(|| { 59 | format!( 60 | "Couldn't find segment for `{}`+0x{:x}", 61 | file.display(), 62 | offset 63 | ) 64 | })?; 65 | Ok(RelocatedDwarfEntry { 66 | address_range: address, 67 | file_range, 68 | bias: address.0 - stated_address, 69 | dwarf, 70 | }) 71 | } 72 | Err(err) => Err(err), 73 | } 74 | } 75 | } 76 | 77 | impl RelocatedDwarf { 78 | pub fn from_maps(maps: &[crate::target::MemoryMap]) -> CrabResult { 79 | let vec: Result, _> = maps 80 | .iter() 81 | .filter_map(|map| { 82 | map.backing_file.as_ref().map(|&(ref file, offset)| { 83 | RelocatedDwarfEntry::from_file_and_offset(map.address, file, offset) 84 | }) 85 | }) 86 | .collect(); 87 | let vec = vec?; 88 | Ok(RelocatedDwarf(vec)) 89 | } 90 | 91 | pub fn get_symbol_address(&self, name: &str) -> Option { 92 | for entry in &self.0 { 93 | if let Some(addr) = entry.dwarf.get_symbol_address(name) { 94 | if addr as u64 + entry.bias >= entry.address_range.0 + entry.address_range.1 { 95 | continue; 96 | } 97 | return Some(addr + entry.bias as usize); 98 | } 99 | } 100 | None 101 | } 102 | 103 | pub fn get_address_symbol_name(&self, addr: usize) -> Option { 104 | for entry in &self.0 { 105 | if (addr as u64) < entry.address_range.0 106 | || addr as u64 >= entry.address_range.0 + entry.address_range.1 107 | { 108 | continue; 109 | } 110 | return entry 111 | .dwarf 112 | .get_address_symbol_name(addr - entry.bias as usize); 113 | } 114 | None 115 | } 116 | 117 | pub fn get_address_demangled_name(&self, addr: usize) -> Option { 118 | for entry in &self.0 { 119 | if (addr as u64) < entry.address_range.0 120 | || addr as u64 >= entry.address_range.0 + entry.address_range.1 121 | { 122 | continue; 123 | } 124 | return entry 125 | .dwarf 126 | .get_address_demangled_name(addr - entry.bias as usize); 127 | } 128 | None 129 | } 130 | 131 | pub fn get_address_symbol_kind(&self, addr: usize) -> Option { 132 | for entry in &self.0 { 133 | if (addr as u64) < entry.address_range.0 134 | || addr as u64 >= entry.address_range.0 + entry.address_range.1 135 | { 136 | continue; 137 | } 138 | return entry 139 | .dwarf 140 | .get_address_symbol_kind(addr - entry.bias as usize); 141 | } 142 | None 143 | } 144 | 145 | pub fn get_var_address(&self, name: &str) -> CrabResult> { 146 | for entry in &self.0 { 147 | if let Some(addr) = entry.dwarf.get_var_address(name)? { 148 | if addr as u64 + entry.bias >= entry.address_range.0 + entry.address_range.1 { 149 | continue; 150 | } 151 | return Ok(Some(addr + entry.bias as usize)); 152 | } 153 | } 154 | Ok(None) 155 | } 156 | 157 | pub fn source_location(&self, addr: usize) -> CrabResult> { 158 | for entry in &self.0 { 159 | if (addr as u64) < entry.address_range.0 160 | || addr as u64 >= entry.address_range.0 + entry.address_range.1 161 | { 162 | continue; 163 | } 164 | return Ok(Some( 165 | entry.dwarf.source_location(addr - entry.bias as usize)?, 166 | )); 167 | } 168 | Ok(None) 169 | } 170 | 171 | pub fn source_snippet(&self, addr: usize) -> CrabResult> { 172 | for entry in &self.0 { 173 | if (addr as u64) < entry.address_range.0 174 | || addr as u64 >= entry.address_range.0 + entry.address_range.1 175 | { 176 | continue; 177 | } 178 | return Ok(Some( 179 | entry.dwarf.source_snippet(addr - entry.bias as usize)?, 180 | )); 181 | } 182 | Ok(None) 183 | } 184 | 185 | pub fn with_addr_frames FnOnce(usize, FrameIter<'a>) -> CrabResult>( 186 | &self, 187 | addr: usize, 188 | f: F, 189 | ) -> CrabResult> { 190 | for entry in &self.0 { 191 | if (addr as u64) < entry.address_range.0 || addr as u64 >= entry.address_range.1 { 192 | continue; 193 | } 194 | return Ok(Some( 195 | entry 196 | .dwarf 197 | .with_addr_frames(addr - entry.bias as usize, f)?, 198 | )); 199 | } 200 | Ok(None) 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/symbol/source.rs: -------------------------------------------------------------------------------- 1 | use capstone::Capstone; 2 | 3 | use std::fs::File; 4 | use std::io::{prelude::*, BufReader}; 5 | 6 | use crate::CrabResult; 7 | 8 | pub struct DisassemblySource(Capstone); 9 | 10 | impl Default for DisassemblySource { 11 | fn default() -> Self { 12 | use capstone::arch::{BuildsCapstone, BuildsCapstoneSyntax}; 13 | 14 | let cs = Capstone::new() 15 | .x86() 16 | .mode(capstone::arch::x86::ArchMode::Mode64) 17 | .syntax(capstone::arch::x86::ArchSyntax::Att) 18 | .detail(true) 19 | .build() 20 | .unwrap(); 21 | 22 | DisassemblySource(cs) 23 | } 24 | } 25 | 26 | impl DisassemblySource { 27 | pub fn source_snippet( 28 | &self, 29 | bytes: &[u8], 30 | addr: u64, 31 | show_address: bool, 32 | ) -> CrabResult { 33 | use std::fmt::Write; 34 | 35 | let mut fmt = String::new(); 36 | 37 | for insn in self.0.disasm_all(bytes, addr).unwrap().iter() { 38 | if show_address { 39 | write!(fmt, "0x{:016x}: ", insn.address()).unwrap(); 40 | } 41 | if let Some(mnemonic) = insn.mnemonic() { 42 | write!(fmt, "{} ", mnemonic).unwrap(); 43 | if let Some(op_str) = insn.op_str() { 44 | writeln!(fmt, "{}", op_str).unwrap(); 45 | } else { 46 | fmt.push('\n'); 47 | } 48 | } 49 | } 50 | 51 | Ok(fmt) 52 | } 53 | } 54 | 55 | impl super::Dwarf { 56 | pub fn source_location(&self, addr: usize) -> CrabResult<(String, u64, u64)> { 57 | self.rent(|parsed| { 58 | let addr2line: &addr2line::Context<_> = &parsed.addr2line; 59 | println!("{:08x}", addr); 60 | assert!(addr2line.find_dwarf_unit(addr as u64).is_some()); 61 | let location = addr2line 62 | .find_location(addr as u64)? 63 | .ok_or_else(|| "source location not found".to_string())?; 64 | Ok(( 65 | location 66 | .file 67 | .ok_or_else(|| "Unknown file".to_string())? 68 | .to_string(), 69 | location.line.unwrap_or(0) as u64, 70 | location.column.unwrap_or(0) as u64, 71 | )) 72 | }) 73 | } 74 | 75 | pub fn source_snippet(&self, addr: usize) -> CrabResult { 76 | let (file, line, _column) = self.source_location(addr)?; 77 | let file = std::fs::read_to_string(file)?; 78 | Ok(file 79 | .lines() 80 | .nth(line as usize) 81 | .ok_or_else(|| "Line not found".to_string())? 82 | .to_string()) 83 | } 84 | } 85 | 86 | /// A line in a source code is represented as a line number and the string. 87 | #[derive(Debug)] 88 | struct SourceLine { 89 | line_no: usize, 90 | line_str: String, 91 | } 92 | 93 | #[derive(Debug)] 94 | /// This represents a snippet of source code. The snippet usually consists of a key line and some 95 | /// lines of context around it. 96 | pub struct Snippet { 97 | /// The full path to the file, whose snippet we capture. 98 | file_path: String, 99 | /// The name of the function containing the breakpoint. 100 | fn_name: String, 101 | /// The lines from the source file that is part of the snippet. Lines are a tuple of line 102 | /// number and the line string 103 | lines: Vec, 104 | /// The index of the key line in the `lines` vector. If there is no specified key, then it will 105 | /// be 0. 106 | key_line_idx: usize, 107 | /// The column containing the symbol we are interested in. 108 | key_column_idx: usize, 109 | } 110 | 111 | impl Snippet { 112 | /// This creates a source code snippet given the source file and the key line and the 113 | /// surrounding line count as context. 114 | pub fn from_file( 115 | file_path: &str, 116 | fn_name: String, 117 | line_no: usize, 118 | lines_as_context: usize, 119 | column: usize, 120 | ) -> CrabResult { 121 | if line_no == 0 { 122 | return Err("Line numbers should start at 1.".into()); 123 | } 124 | let file = File::open(file_path)?; 125 | let reader = BufReader::new(file); 126 | let mut lines = vec![]; 127 | 128 | // The line number from the debuginfo starts at 1 but the one for iterator gives by 129 | // `reader.lines()` starts at 0. 130 | let key = line_no - 1; 131 | let start = if key > lines_as_context { 132 | key - lines_as_context 133 | } else { 134 | 0 135 | }; 136 | let end = key + lines_as_context; 137 | for (i, line) in reader.lines().enumerate().skip(start) { 138 | if i <= end { 139 | lines.push(SourceLine { 140 | line_no: i + 1, 141 | line_str: line?, 142 | }); 143 | } else { 144 | // If we have printed the asked for line and the context around it, 145 | // There is no point in iterating through the whole file. So, break 146 | // out of the loop and make an early return. 147 | break; 148 | } 149 | } 150 | Ok(Snippet { 151 | file_path: file_path.to_string(), 152 | fn_name, 153 | lines, 154 | key_line_idx: lines_as_context, 155 | key_column_idx: column - 1, 156 | }) 157 | } 158 | 159 | #[cfg(not(feature = "syntax-highlighting"))] 160 | pub fn highlight(&self) { 161 | eprintln!( 162 | "\x1b[96m{}::\x1b[37m{}()\x1b[0m\n", 163 | self.file_path, self.fn_name 164 | ); 165 | for (idx, SourceLine { line_no, line_str }) in self.lines.iter().enumerate() { 166 | let line_marker = if idx == self.key_line_idx { 167 | "\x1b[91m>\x1b[0m" 168 | } else { 169 | " \x1b[2m" 170 | }; 171 | 172 | eprintln!("{} {:>6} | {}", line_marker, *line_no, line_str); 173 | if idx == self.key_line_idx && self.key_column_idx != 0 { 174 | eprintln!( 175 | " | {:width$}\x1b[91m^\x1b[0m", 176 | " ", 177 | width = self.key_column_idx 178 | ); 179 | } 180 | } 181 | } 182 | } 183 | 184 | /// This module has methods to syntax highlighting of the code snippet. 185 | /// This is an adaptation of [this](https://github.com/bjorn3/pretty_backtrace/blob/c8a19b94f369a2edd9d58b07eb4310d2e0a2c991/src/display_frame.rs#L103-L113). 186 | #[cfg(feature = "syntax-highlighting")] 187 | mod pretty { 188 | use super::{Snippet, SourceLine}; 189 | use syntect::{ 190 | easy::HighlightLines, 191 | highlighting::{Style, ThemeSet}, 192 | parsing::SyntaxSet, 193 | }; 194 | 195 | lazy_static::lazy_static! { 196 | static ref SYNTAX_SET: SyntaxSet = SyntaxSet::load_defaults_newlines(); 197 | static ref THEME_SET: ThemeSet = ThemeSet::load_defaults(); 198 | } 199 | impl Snippet { 200 | /// Syntax highlight the snippet and print it. 201 | pub fn highlight(&self) { 202 | let t = &THEME_SET.themes["Solarized (dark)"]; 203 | let step_hi = 6; 204 | let step_lo = 2; 205 | let mut h = HighlightLines::new( 206 | &SYNTAX_SET 207 | .find_syntax_by_extension("rs") 208 | .unwrap() 209 | .to_owned(), 210 | t, 211 | ); 212 | 213 | // print the filename and the function name. 214 | eprintln!( 215 | "\x1b[96m{}::\x1b[37m{}()\x1b[0m\n", 216 | self.file_path, self.fn_name 217 | ); 218 | 219 | for (idx, SourceLine { line_no, line_str }) in self.lines.iter().enumerate() { 220 | let (line_marker, step) = if idx == self.key_line_idx { 221 | ("\x1b[91m>\x1b[0m", &step_lo) 222 | } else { 223 | (" \x1b[2m", &step_hi) 224 | }; 225 | let hl_line = h.highlight(line_str, &SYNTAX_SET); 226 | eprintln!( 227 | "{} {:>6} | {}", 228 | line_marker, 229 | *line_no, 230 | as_16_bit_terminal_escaped(&hl_line[..], step) 231 | ); 232 | if idx == self.key_line_idx && self.key_column_idx != 0 { 233 | eprintln!( 234 | " | {:width$}\x1b[91m^\x1b[0m", 235 | " ", 236 | width = self.key_column_idx 237 | ); 238 | } 239 | } 240 | } 241 | } 242 | 243 | fn as_16_bit_terminal_escaped(v: &[(Style, &str)], step: &u8) -> String { 244 | use std::fmt::Write; 245 | let mut s: String = String::new(); 246 | let factor = 256 / *step as u16; 247 | let factor_u8 = factor as u8; 248 | let step_squared = *step * *step; 249 | for &(ref style, text) in v.iter() { 250 | // 256/6 = 42 251 | write!( 252 | s, 253 | "\x1b[38;5;{}m{}", 254 | 16u8 + step_squared * (style.foreground.r / factor_u8) 255 | + *step as u8 * (style.foreground.g / factor_u8) 256 | + (style.foreground.b / factor_u8), 257 | text 258 | ) 259 | .unwrap(); 260 | } 261 | s.push_str("\x1b[0m"); 262 | s 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /src/symbol/sym.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of a symbol table entry that will automatically 2 | //! demangle rustc names. 3 | 4 | use addr2line::demangle_auto; 5 | use std::borrow::Cow; 6 | use std::ops::{Deref, DerefMut}; 7 | 8 | /// A symbol table entry. 9 | #[derive(Clone, Debug)] 10 | pub struct Symbol<'data> { 11 | demangled_name: Option, 12 | symbol: object::Symbol<'data>, 13 | } 14 | 15 | impl<'data> Symbol<'data> { 16 | /// Returns the demangled name if this symbol has a name. 17 | #[inline] 18 | pub fn demangled_name(&'data self) -> Option<&'data str> { 19 | // TODO: Avoid this allocation in every call. (lifetime errors) 20 | self.demangled_name.as_deref() 21 | } 22 | } 23 | 24 | impl<'data> From> for Symbol<'data> { 25 | fn from(symbol: object::Symbol<'data>) -> Self { 26 | let demangled_name = symbol 27 | .name() 28 | .map(|name| demangle_auto(Cow::Borrowed(name), None).to_string()); 29 | Symbol { 30 | symbol, 31 | demangled_name, 32 | } 33 | } 34 | } 35 | 36 | impl<'data> Deref for Symbol<'data> { 37 | type Target = object::Symbol<'data>; 38 | 39 | fn deref(&self) -> &Self::Target { 40 | &self.symbol 41 | } 42 | } 43 | 44 | impl DerefMut for Symbol<'_> { 45 | fn deref_mut(&mut self) -> &mut Self::Target { 46 | &mut self.symbol 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/symbol/unwind.rs: -------------------------------------------------------------------------------- 1 | /// This is a naive stack unwinder that returns all words that could be interpreted as return 2 | /// address for a known function. 3 | pub fn naive_unwinder<'a>( 4 | debuginfo: &'a super::RelocatedDwarf, 5 | stack: &'a [usize], 6 | rip: usize, 7 | ) -> impl Iterator + 'a { 8 | std::iter::once(rip).chain(stack.iter().cloned().filter(move |addr| { 9 | debuginfo.get_address_symbol_kind(*addr) == Some(object::SymbolKind::Text) 10 | })) 11 | } 12 | 13 | struct FramePointerUnwinder<'a> { 14 | stack: &'a [usize], 15 | stack_offset: usize, 16 | rbp: usize, 17 | } 18 | 19 | impl Iterator for FramePointerUnwinder<'_> { 20 | type Item = usize; 21 | 22 | fn next(&mut self) -> Option { 23 | if self.rbp >= self.stack_offset 24 | && self.rbp < self.stack_offset + self.stack.len() * std::mem::size_of::() 25 | { 26 | let ip = self.stack[(self.rbp - self.stack_offset) / std::mem::size_of::() + 1]; 27 | self.rbp = self.stack[(self.rbp - self.stack_offset) / std::mem::size_of::()]; 28 | Some(ip) 29 | } else { 30 | None 31 | } 32 | } 33 | } 34 | 35 | /// This is a frame pointer based unwinder. It expects the frame pointer to form a linked list. 36 | /// May require `-Cforce-frame-pointers=yes`. 37 | pub fn frame_pointer_unwinder<'a>( 38 | _debuginfo: &'a super::RelocatedDwarf, 39 | stack: &'a [usize], 40 | rip: usize, 41 | stack_offset: usize, 42 | rbp: usize, 43 | ) -> impl Iterator + 'a { 44 | std::iter::once(rip).chain(FramePointerUnwinder { 45 | stack, 46 | stack_offset, 47 | rbp, 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /src/target.rs: -------------------------------------------------------------------------------- 1 | #[cfg(unix)] 2 | mod unix; 3 | #[cfg(unix)] 4 | pub use unix::*; 5 | 6 | #[cfg(target_os = "linux")] 7 | mod linux; 8 | #[cfg(target_os = "linux")] 9 | pub use linux::*; 10 | 11 | #[cfg(target_os = "macos")] 12 | mod macos; 13 | #[cfg(target_os = "macos")] 14 | pub use macos::*; 15 | 16 | #[cfg(target_os = "windows")] 17 | mod windows; 18 | #[cfg(target_os = "windows")] 19 | pub use windows::*; 20 | 21 | mod registers; 22 | mod thread; 23 | 24 | pub use registers::Registers; 25 | pub use thread::Thread; 26 | 27 | #[derive(Debug)] 28 | pub struct MemoryMap { 29 | /// Start and end range of the mapped memory. 30 | pub address: (u64, u64), 31 | /// The file and file offset backing the mapped memory if any. 32 | pub backing_file: Option<(std::path::PathBuf, u64)>, 33 | 34 | /// Is mapped memory readable. 35 | pub is_readable: bool, 36 | /// Is mapped memory writable. 37 | pub is_writable: bool, 38 | /// Is mapped memory executable. 39 | pub is_executable: bool, 40 | /// Is mapped memory private. 41 | pub is_private: bool, 42 | } 43 | -------------------------------------------------------------------------------- /src/target/linux/hardware_breakpoint.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub struct HardwareBreakpoint { 3 | pub typ: HardwareBreakpointType, 4 | pub addr: usize, 5 | pub size: HardwareBreakpointSize, 6 | } 7 | 8 | impl HardwareBreakpoint { 9 | pub(super) fn size_bits(&self, index: usize) -> u64 { 10 | (self.size as u64) << (18 + index * 4) 11 | } 12 | 13 | pub(super) const fn bit_mask(index: usize) -> u64 { 14 | (0b11 << (2 * index)) | (0b1111 << (16 + 4 * index)) 15 | } 16 | 17 | pub(super) fn rw_bits(&self, index: usize) -> u64 { 18 | let type_bites = match self.typ { 19 | HardwareBreakpointType::Execute => 0b00, 20 | HardwareBreakpointType::Read => 0b11, 21 | HardwareBreakpointType::ReadWrite => 0b11, 22 | HardwareBreakpointType::Write => 0b01, 23 | }; 24 | type_bites << (16 + index * 4) 25 | } 26 | } 27 | 28 | #[derive(Copy, Clone, Debug)] 29 | pub enum HardwareBreakpointSize { 30 | _1 = 0b00, 31 | _2 = 0b01, 32 | _4 = 0b11, 33 | _8 = 0b10, 34 | } 35 | impl HardwareBreakpointSize { 36 | pub fn from_usize(size: usize) -> crate::CrabResult { 37 | match size { 38 | 1 => Ok(Self::_1), 39 | 2 => Ok(Self::_2), 40 | 4 => Ok(Self::_4), 41 | 8 => Ok(Self::_8), 42 | x => Err(Box::new(HardwareBreakpointError::UnsupportedWatchSize(x))), 43 | } 44 | } 45 | } 46 | 47 | #[derive(Debug)] 48 | pub enum HardwareBreakpointType { 49 | Execute, 50 | Write, 51 | Read, 52 | ReadWrite, 53 | } 54 | 55 | #[derive(Debug, Clone)] 56 | pub enum HardwareBreakpointError { 57 | NoEmptyWatchpoint, 58 | DoesNotExist(usize), 59 | UnsupportedPlatform, 60 | UnsupportedWatchSize(usize), 61 | } 62 | 63 | impl std::fmt::Display for HardwareBreakpointError { 64 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 65 | let string = match self { 66 | HardwareBreakpointError::NoEmptyWatchpoint => { 67 | "No unused hardware breakpoints left".to_string() 68 | } 69 | HardwareBreakpointError::DoesNotExist(index) => format!( 70 | "Hardware breakpoint at specified index ({}) does not exist", 71 | index 72 | ), 73 | HardwareBreakpointError::UnsupportedPlatform => { 74 | "Hardware breakpoints not supported on this platform".to_string() 75 | } 76 | HardwareBreakpointError::UnsupportedWatchSize(size) => { 77 | format!("Hardware breakpoint size of {} is not supported", size) 78 | } 79 | }; 80 | write!(f, "{}", string) 81 | } 82 | } 83 | 84 | impl std::error::Error for HardwareBreakpointError {} 85 | -------------------------------------------------------------------------------- /src/target/linux/memory.rs: -------------------------------------------------------------------------------- 1 | //! Utility functions to work with memory. 2 | 3 | use crate::target::MemoryMap; 4 | use std::cmp::{self, Ordering}; 5 | 6 | lazy_static::lazy_static! { 7 | /// Memory page size from system configuration. 8 | pub(crate) static ref PAGE_SIZE: usize = unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }; 9 | } 10 | 11 | /// Individual memory operation (reading or writing). 12 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 13 | pub(crate) struct MemoryOp { 14 | /// Remote memory location. 15 | pub remote_base: usize, 16 | /// Pointer to a local destination or source buffer. 17 | pub local_ptr: *mut libc::c_void, 18 | /// Size of the `local_ptr` buffer. 19 | pub local_ptr_len: usize, 20 | } 21 | 22 | impl MemoryOp { 23 | /// Converts the memory operation into a remote IoVec suitable for use in vector read/write syscalls. 24 | pub fn as_remote_iovec(&self) -> libc::iovec { 25 | libc::iovec { 26 | iov_base: self.remote_base as *const libc::c_void as *mut _, 27 | iov_len: self.local_ptr_len, 28 | } 29 | } 30 | 31 | /// Converts the memory operation into a local IoVec suitable for use in vector read/write syscalls. 32 | pub fn as_local_iovec(&self) -> libc::iovec { 33 | libc::iovec { 34 | iov_base: self.local_ptr, 35 | iov_len: self.local_ptr_len, 36 | } 37 | } 38 | 39 | /// Splits `MemoryOp` so that each resulting `MemoryOp` resides in only one memory page. 40 | pub(crate) fn split_on_page_boundary(&self, out: &mut Vec>) { 41 | // Number of bytes left to be read or written 42 | let mut left = self.local_ptr_len; 43 | 44 | let next_page_distance = *PAGE_SIZE - ((*PAGE_SIZE - 1) & self.remote_base); 45 | let to_next_read_op = cmp::min(left, next_page_distance); 46 | // Read or write from remote_base to the end or to the next page 47 | out.push(From::from(MemoryOp { 48 | remote_base: self.remote_base, 49 | local_ptr: self.local_ptr, 50 | local_ptr_len: to_next_read_op, 51 | })); 52 | left -= to_next_read_op; 53 | 54 | while left > 0 { 55 | if left < *PAGE_SIZE { 56 | // Read or write from beginning of the page to a part in the middle (last read or write) 57 | out.push(From::from(MemoryOp { 58 | remote_base: self.remote_base + (self.local_ptr_len - left), 59 | local_ptr: (self.local_ptr as usize + (self.local_ptr_len - left)) 60 | as *mut libc::c_void, 61 | local_ptr_len: left, 62 | })); 63 | break; 64 | } else { 65 | // Whole page is being read or written 66 | out.push(From::from(MemoryOp { 67 | remote_base: self.remote_base + (self.local_ptr_len - left), 68 | local_ptr: (self.local_ptr as usize + (self.local_ptr_len - left)) 69 | as *mut libc::c_void, 70 | local_ptr_len: *PAGE_SIZE, 71 | })); 72 | left -= *PAGE_SIZE; 73 | } 74 | } 75 | } 76 | } 77 | 78 | /// Splits memory operations to those that can access protected memory and those that do not. 79 | /// This function can be used for both write or read operations, and `protected_maps` should be 80 | /// pre-filtered to contain only protected pages, e.g.: 81 | /// ``` 82 | /// use headcrab::target::MemoryMap; 83 | /// 84 | /// let maps: Vec = vec![]; 85 | /// let protected_maps = maps.into_iter().filter(|map| !map.is_writable); 86 | /// ``` 87 | pub(crate) fn split_protected( 88 | protected_maps: &[MemoryMap], 89 | operations: impl Iterator, 90 | ) -> (Vec, Vec) { 91 | let (protected, permissioned): (_, Vec<_>) = operations.partition(|op| { 92 | protected_maps 93 | .binary_search_by(|map| { 94 | if op.remote_base < map.address.0 as usize { 95 | Ordering::Greater 96 | } else if op.remote_base > map.address.1 as usize { 97 | Ordering::Less 98 | } else { 99 | Ordering::Equal 100 | } 101 | }) 102 | .is_ok() 103 | }); 104 | 105 | (protected, permissioned) 106 | } 107 | -------------------------------------------------------------------------------- /src/target/linux/readmem.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | memory::{split_protected, MemoryOp}, 3 | LinuxTarget, 4 | }; 5 | use crate::CrabResult; 6 | use nix::{sys::ptrace, unistd::Pid}; 7 | use std::{marker::PhantomData, mem}; 8 | 9 | /// Read operations don't have any unique properties at this time. 10 | /// If needed, later this can be replaced with `struct ReadOp(MemoryOp, )`. 11 | type ReadOp = MemoryOp; 12 | 13 | /// Allows to read memory from different locations in debuggee's memory as a single operation. 14 | pub struct ReadMemory<'a> { 15 | target: &'a LinuxTarget, 16 | read_ops: Vec, 17 | /// This requires a mutable reference because we rewrite values of variables in `ReadOp`. 18 | _marker: PhantomData<&'a mut ()>, 19 | } 20 | 21 | impl<'a> ReadMemory<'a> { 22 | pub(in crate::target) fn new(target: &'a LinuxTarget) -> Self { 23 | ReadMemory { 24 | target, 25 | read_ops: Vec::new(), 26 | _marker: PhantomData, 27 | } 28 | } 29 | 30 | /// Reads a value of type `T` from debuggee's memory at location `remote_base`. 31 | /// This value will be written to the provided variable `val`. 32 | /// You should call `apply` in order to execute the memory read operation. 33 | /// The provided variable `val` can't be accessed until either `apply` is called or `self` is 34 | /// dropped. 35 | /// 36 | /// # Safety 37 | /// 38 | /// The type `T` must not have any invalid values. 39 | /// For example, `T` must not be a `bool`, as `transmute::(2)` is not a valid value for a bool. 40 | /// In case of doubt, wrap the type in [`mem::MaybeUninit`]. 41 | // todo: further document mem safety - e.g., what happens in the case of partial read 42 | pub unsafe fn read(mut self, val: &'a mut T, remote_base: usize) -> Self { 43 | MemoryOp::split_on_page_boundary( 44 | &MemoryOp { 45 | remote_base, 46 | local_ptr: val as *mut T as *mut libc::c_void, 47 | local_ptr_len: mem::size_of::(), 48 | }, 49 | &mut self.read_ops, 50 | ); 51 | self 52 | } 53 | 54 | /// Reads a value of type `*mut T` from debuggee's memory at location `remote_base`. 55 | /// This value will be written to the provided pointer `ptr`. 56 | /// You should call `apply` in order to execute the memory read operation. 57 | /// The provided pointer `ptr` can't be accessed until either `apply` is called or `self` is 58 | /// dropped. 59 | /// 60 | /// # Safety 61 | /// 62 | /// Memory location at `ptr` must be of valid size and must not be outlived by `ReadMem`. 63 | /// You need to ensure the lifetime guarantees, and generally you should prefer using `read(&mut val)`. 64 | // todo: further document mem safety - e.g., what happens in the case of partial read 65 | pub unsafe fn read_ptr(mut self, ptr: *mut T, remote_base: usize) -> Self { 66 | MemoryOp::split_on_page_boundary( 67 | &MemoryOp { 68 | remote_base, 69 | local_ptr: ptr as *mut _, 70 | local_ptr_len: mem::size_of::(), 71 | }, 72 | &mut self.read_ops, 73 | ); 74 | self 75 | } 76 | 77 | /// Reads a slice of type `&mut [T]` from debuggee's memory at location `remote_base`. 78 | /// This value will be written to the provided slice `val`. 79 | /// You should call `apply` in order to execute the memory read operation. 80 | /// The provided value `val` can't be accessed until either `apply` is called or `self` is 81 | /// dropped. 82 | /// 83 | /// # Safety 84 | /// 85 | /// The type `T` must not have any invalid values. 86 | /// For example, `T` must not be a `bool`, as `transmute::(2)` is not a valid value for a bool. 87 | /// In case of doubt, wrap the type in [`mem::MaybeUninit`]. 88 | // todo: further document mem safety - e.g., what happens in the case of partial read 89 | pub unsafe fn read_slice(mut self, val: &'a mut [T], remote_base: usize) -> Self { 90 | MemoryOp::split_on_page_boundary( 91 | &MemoryOp { 92 | remote_base, 93 | local_ptr: val.as_mut_ptr() as *mut _, 94 | local_ptr_len: val.len() * mem::size_of::(), 95 | }, 96 | &mut self.read_ops, 97 | ); 98 | self 99 | } 100 | 101 | /// Reads a `u8` byte slice from debuggee's memory at location `remote_base`. 102 | /// This value will be written to the provided slice `val`. 103 | /// You should call `apply` in order to execute the memory read operation. 104 | pub fn read_byte_slice(mut self, val: &'a mut [u8], remote_base: usize) -> Self { 105 | MemoryOp::split_on_page_boundary( 106 | &MemoryOp { 107 | remote_base, 108 | local_ptr: val.as_mut_ptr() as *mut _, 109 | local_ptr_len: val.len(), 110 | }, 111 | &mut self.read_ops, 112 | ); 113 | self 114 | } 115 | 116 | /// Executes the memory read operation. 117 | pub fn apply(self) -> CrabResult<()> { 118 | let pid = self.target.pid; 119 | let read_len = self 120 | .read_ops 121 | .iter() 122 | .fold(0, |sum, read_op| sum + read_op.local_ptr_len); 123 | 124 | if read_len > isize::MAX as usize { 125 | panic!("Read size too big"); 126 | }; 127 | 128 | // FIXME: Probably a better way to do this - see if we can get info about pages protection from 129 | // cache and predict whether this operation will require ptrace or plain read_process_vm would work. 130 | let result = Self::read_process_vm(pid, &self.read_ops); 131 | 132 | if result.is_err() && result.unwrap_err() == nix::Error::Sys(nix::errno::Errno::EFAULT) 133 | || result.is_ok() && result.unwrap() != read_len as isize 134 | { 135 | let protected_maps = self 136 | .target 137 | .memory_maps()? 138 | .into_iter() 139 | .filter(|map| !map.is_readable) 140 | .collect::>(); 141 | 142 | let (protected, readable) = split_protected(&protected_maps, self.read_ops.into_iter()); 143 | 144 | Self::read_process_vm(pid, &readable)?; 145 | Self::read_ptrace(pid, &protected)?; 146 | } 147 | Ok(()) 148 | } 149 | 150 | /// Allows to read from several different locations with one system call. 151 | /// It will error on pages that are not readable. Returns number of bytes read at granularity of ReadOps. 152 | fn read_process_vm(pid: Pid, read_ops: &[ReadOp]) -> Result { 153 | let remote_iov = read_ops 154 | .iter() 155 | .map(|read_op| read_op.as_remote_iovec()) 156 | .collect::>(); 157 | 158 | let local_iov = read_ops 159 | .iter() 160 | .map(|read_op| read_op.as_local_iovec()) 161 | .collect::>(); 162 | 163 | let bytes_read = unsafe { 164 | // todo: document unsafety 165 | libc::process_vm_readv( 166 | pid.into(), 167 | local_iov.as_ptr(), 168 | local_iov.len() as libc::c_ulong, 169 | remote_iov.as_ptr(), 170 | remote_iov.len() as libc::c_ulong, 171 | 0, 172 | ) 173 | }; 174 | 175 | if bytes_read == -1 { 176 | return Err(nix::Error::last()); 177 | } 178 | 179 | Ok(bytes_read) 180 | } 181 | 182 | /// Allows to read from protected memory pages. 183 | /// This operation results in multiple system calls and is inefficient. 184 | fn read_ptrace(pid: Pid, read_ops: &[MemoryOp]) -> CrabResult<()> { 185 | let long_size = std::mem::size_of::(); 186 | 187 | for read_op in read_ops { 188 | let mut offset: usize = 0; 189 | // Read until all of the data is read 190 | while offset < read_op.local_ptr_len { 191 | let data = 192 | ptrace::read(pid, (read_op.remote_base + offset) as *mut std::ffi::c_void)?; 193 | 194 | // Read full word. No need to preserve other data 195 | if (read_op.local_ptr_len - offset) >= long_size { 196 | // todo: document unsafety 197 | unsafe { 198 | *((read_op.local_ptr as usize + offset) as *mut i64) = data; 199 | } 200 | 201 | // Read part smaller than word. Need to preserve other data 202 | } else { 203 | // todo: document unsafety 204 | unsafe { 205 | let previous_bytes: &mut [u8] = std::slice::from_raw_parts_mut( 206 | (read_op.local_ptr as usize + offset) as *mut u8, 207 | read_op.local_ptr_len - offset, 208 | ); 209 | let data_bytes = data.to_ne_bytes(); 210 | 211 | previous_bytes[0..(read_op.local_ptr_len - offset)] 212 | .clone_from_slice(&data_bytes[0..(read_op.local_ptr_len - offset)]); 213 | } 214 | } 215 | offset += long_size; 216 | } 217 | } 218 | Ok(()) 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/target/linux/software_breakpoint.rs: -------------------------------------------------------------------------------- 1 | //! Bundles functionalities related to software breakpoints 2 | //! Software Breakpoints work by overwriting the target program's memory; 3 | //! replacing an instruction with one that causes a signal to be raised by the 4 | //! cpu. 5 | 6 | use crate::target::{LinuxTarget, ReadMemory}; 7 | use nix::sys::ptrace; 8 | use nix::unistd::Pid; 9 | use std::cell::Cell; 10 | use std::rc::Rc; 11 | const INT3: libc::c_long = 0xcc; 12 | 13 | #[derive(Debug, Clone)] 14 | pub struct Breakpoint { 15 | /// The address at which the debugger should insert this breakpoint 16 | pub addr: usize, 17 | /// The original instruction overwriten by the breakpoint 18 | pub(super) shadow: i64, 19 | pid: Pid, 20 | user_enabled: Rc>, 21 | } 22 | 23 | impl Breakpoint { 24 | /// Set a breakpoint at a given address 25 | pub(crate) fn new(addr: usize, pid: Pid) -> Result { 26 | let mut shadow = 0_i64; 27 | unsafe { 28 | ReadMemory::new(&LinuxTarget::from_debuggee_pid(pid)) 29 | .read(&mut shadow, addr) 30 | .apply() 31 | } 32 | .map_err(|_e| BreakpointError::IoError)?; 33 | Ok(Breakpoint { 34 | addr, 35 | shadow, 36 | pid, 37 | user_enabled: Rc::new(Cell::new(false)), 38 | }) 39 | } 40 | 41 | /// Put in place the trap instruction 42 | pub fn set(&mut self) -> Result<(), BreakpointError> { 43 | // We don't allow setting breakpoint twice 44 | // else it would be possible to create a breakpoint that 45 | // would 'restore' an `INT3` instruction 46 | if !self.is_armed() { 47 | let instr = ptrace::read(self.pid, self.addr as *mut _)?; 48 | self.shadow = instr; 49 | let trap_instr = (instr & !0xff) | INT3; 50 | ptrace::write(self.pid, self.addr as *mut _, trap_instr as *mut _)?; 51 | } 52 | self.user_enabled.set(true); 53 | Ok(()) 54 | } 55 | 56 | pub fn unset(&self) -> Result<(), BreakpointError> { 57 | if self.is_armed() { 58 | ptrace::write(self.pid, self.addr as *mut _, self.shadow as *mut _)?; 59 | } 60 | Ok(()) 61 | } 62 | 63 | /// Restore the previous instruction for the breakpoint. 64 | pub fn disable(&self) -> Result<(), BreakpointError> { 65 | self.unset()?; 66 | self.user_enabled.set(false); 67 | Ok(()) 68 | } 69 | 70 | pub fn is_enabled(&self) -> bool { 71 | self.user_enabled.get() 72 | } 73 | 74 | /// Whether this breakpoint has instrumented the target's code 75 | pub fn is_armed(&self) -> bool { 76 | let instr = ptrace::read(self.pid, self.addr as *mut _) 77 | .map_err(|err| eprintln!("Failed to check if breakpoint is armed ({})", err)) 78 | .unwrap_or(0); 79 | (instr & 0xff) == INT3 80 | } 81 | } 82 | 83 | #[derive(Debug)] 84 | pub enum BreakpointError { 85 | NoSuchSymbol, 86 | IoError, 87 | NixError(nix::Error), 88 | } 89 | 90 | impl std::convert::From for BreakpointError { 91 | fn from(error: nix::Error) -> Self { 92 | BreakpointError::NixError(error) 93 | } 94 | } 95 | 96 | impl std::fmt::Display for BreakpointError { 97 | fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { 98 | write!(fmt, "{:?}", self) 99 | } 100 | } 101 | 102 | impl std::error::Error for BreakpointError {} 103 | -------------------------------------------------------------------------------- /src/target/macos.rs: -------------------------------------------------------------------------------- 1 | mod readmem; 2 | mod vmmap; 3 | mod writemem; 4 | 5 | use crate::target::{registers::Registers, thread::Thread}; 6 | use crate::CrabResult; 7 | use libc::pid_t; 8 | use mach::{ 9 | kern_return, mach_types, mach_types::ipc_space_t, message::mach_msg_type_number_t, port, 10 | port::mach_port_name_t, port::mach_port_t, traps, traps::current_task, 11 | }; 12 | use nix::{unistd, unistd::Pid}; 13 | use security_framework_sys::authorization::*; 14 | use std::{ffi::CStr, ffi::CString, io, mem::MaybeUninit, ptr}; 15 | 16 | pub use readmem::ReadMemory; 17 | pub use writemem::WriteMemory; 18 | 19 | // Undocumented flag to disable address space layout randomization. 20 | // For more information about ASLR, you can refer to https://en.wikipedia.org/wiki/Address_space_layout_randomization 21 | const _POSIX_SPAWN_DISABLE_ASLR: i32 = 0x0100; 22 | 23 | // Max number of characters to read from a thread name. 24 | const MAX_THREAD_NAME: usize = 100; 25 | 26 | struct OSXThread { 27 | port: mach_port_name_t, 28 | pthread_id: Option, 29 | task_port: ipc_space_t, 30 | } 31 | 32 | impl Drop for OSXThread { 33 | fn drop(&mut self) { 34 | let result = unsafe { mach::mach_port::mach_port_deallocate(self.task_port, self.port) }; 35 | if result != kern_return::KERN_SUCCESS { 36 | panic!("Failed to deallocate port!"); 37 | } 38 | } 39 | } 40 | 41 | extern "C" { 42 | // FIXME: Use libc > 0.2.74 when available 43 | pub fn pthread_from_mach_thread_np(port: libc::c_uint) -> libc::pthread_t; 44 | } 45 | 46 | // TODO: implement `Registers` properly for macOS x86_64 47 | impl Registers for () { 48 | fn ip(&self) -> u64 { 49 | todo!() 50 | } 51 | fn set_ip(&mut self, ip: u64) { 52 | todo!() 53 | } 54 | fn sp(&self) -> u64 { 55 | todo!() 56 | } 57 | fn set_sp(&mut self, sp: u64) { 58 | todo!() 59 | } 60 | fn bp(&self) -> Option { 61 | todo!() 62 | } 63 | #[must_use] 64 | fn set_bp(&mut self, bp: u64) -> Option<()> { 65 | todo!() 66 | } 67 | fn reg_for_dwarf(&self, reg: gimli::Register) -> Option { 68 | todo!() 69 | } 70 | #[must_use] 71 | fn set_reg_for_dwarf(&mut self, reg: gimli::Register, val: u64) -> Option<()> { 72 | todo!() 73 | } 74 | fn name_for_dwarf(reg: gimli::Register) -> Option<&'static str> 75 | where 76 | Self: Sized, 77 | { 78 | todo!() 79 | } 80 | fn dwarf_for_name(name: &str) -> Option 81 | where 82 | Self: Sized, 83 | { 84 | todo!() 85 | } 86 | } 87 | 88 | impl Thread<()> for OSXThread { 89 | type ThreadId = mach_port_t; 90 | 91 | fn read_regs(&self) -> CrabResult<()> { 92 | todo!() 93 | } 94 | 95 | fn write_regs(&self, regs: ()) -> CrabResult<()> { 96 | todo!() 97 | } 98 | 99 | fn name(&self) -> CrabResult> { 100 | if let Some(pt_id) = self.pthread_id { 101 | let mut name = [0 as libc::c_char; MAX_THREAD_NAME]; 102 | let name_ptr = &mut name as *mut [libc::c_char] as *mut libc::c_char; 103 | let get_name = unsafe { libc::pthread_getname_np(pt_id, name_ptr, MAX_THREAD_NAME) }; 104 | if get_name == 0 { 105 | let name = unsafe { CStr::from_ptr(name_ptr) }.to_str()?.to_owned(); 106 | Ok(Some(name)) 107 | } else { 108 | Err(format!( 109 | "Failure to read pthread {} name. Error: {}", 110 | pt_id, get_name 111 | ) 112 | .into()) 113 | } 114 | } else { 115 | Ok(None) 116 | } 117 | } 118 | 119 | fn thread_id(&self) -> Self::ThreadId { 120 | self.port 121 | } 122 | } 123 | 124 | pub struct Target { 125 | /// Port for a target task 126 | port: port::mach_port_name_t, 127 | pid: Pid, 128 | } 129 | 130 | impl Target { 131 | /// Launch a new debuggee process. 132 | /// Returns an opaque target handle which you can use to control the debuggee. 133 | pub fn launch(path: &str) -> CrabResult { 134 | request_authorization()?; 135 | 136 | let path = CString::new(path)?; 137 | 138 | let child = unsafe { 139 | let mut pid: pid_t = 0; 140 | 141 | let mut attr = MaybeUninit::::uninit(); 142 | let res = libc::posix_spawnattr_init(attr.as_mut_ptr()); 143 | if res != 0 { 144 | // TODO: properly wrap error types 145 | return Err(Box::new(io::Error::last_os_error())); 146 | } 147 | 148 | let mut attr = attr.assume_init(); 149 | 150 | let res = libc::posix_spawnattr_setflags( 151 | &mut attr, 152 | (libc::POSIX_SPAWN_START_SUSPENDED | _POSIX_SPAWN_DISABLE_ASLR) as i16, 153 | ); 154 | if res != 0 { 155 | // TODO: properly wrap error types 156 | return Err(Box::new(io::Error::last_os_error())); 157 | } 158 | 159 | let res = libc::posix_spawn( 160 | &mut pid, 161 | path.as_ptr(), 162 | ptr::null(), 163 | &attr, 164 | ptr::null(), 165 | ptr::null(), 166 | ); 167 | if res != 0 { 168 | // TODO: properly wrap error types 169 | return Err(Box::new(io::Error::last_os_error())); 170 | } 171 | 172 | pid 173 | }; 174 | 175 | let target_port = unsafe { 176 | let self_port = traps::mach_task_self(); 177 | let mut target_port = 0; 178 | 179 | let res = traps::task_for_pid(self_port, child, &mut target_port); 180 | 181 | if res != kern_return::KERN_SUCCESS { 182 | // TODO: properly wrap return errors 183 | return Err(Box::new(io::Error::new( 184 | io::ErrorKind::Other, 185 | "Could not obtain task port for a process. This might be caused by insufficient permissions.", 186 | ))); 187 | } 188 | 189 | target_port 190 | }; 191 | 192 | Ok(Target { 193 | port: target_port, 194 | pid: Pid::from_raw(child), 195 | }) 196 | } 197 | 198 | /// Returns a list of maps in the debuggee's virtual adddress space. 199 | pub fn get_addr_range(&self) -> CrabResult { 200 | let regs = vmmap::macosx_debug_regions(self.pid, self.port); 201 | for r in regs { 202 | println!( 203 | "{:x} -> {:x}, exec: {}, read: {}, write: {} [{:?}]", 204 | r.address, 205 | r.end(), 206 | r.is_exec(), 207 | r.is_read(), 208 | r.is_write(), 209 | r 210 | ); 211 | } 212 | Ok(0) 213 | } 214 | 215 | /// Reads memory from a debuggee process. 216 | pub fn read(&self) -> ReadMemory { 217 | ReadMemory::new(self.port) 218 | } 219 | 220 | /// Uses this process as a debuggee. 221 | pub fn me() -> Target { 222 | let port = unsafe { current_task() }; 223 | let pid = unistd::getpid(); 224 | Target { port, pid } 225 | } 226 | 227 | /// Returns the current snapshot view of this debuggee process threads. 228 | pub fn threads(&self) -> CrabResult>>> { 229 | let mut threads: mach_types::thread_act_array_t = std::ptr::null_mut(); 230 | let mut tcount: mach_msg_type_number_t = 0; 231 | 232 | let result = unsafe { mach::task::task_threads(self.port, &mut threads, &mut tcount) }; 233 | 234 | if result == kern_return::KERN_SUCCESS { 235 | let tcount = tcount as usize; 236 | let mut osx_threads = Vec::with_capacity(tcount); 237 | 238 | for i in 0..tcount { 239 | let port = unsafe { *threads.add(i) }; 240 | let pthread_id = match unsafe { pthread_from_mach_thread_np(port) } { 241 | 0 => None, 242 | id => Some(id), 243 | }; 244 | let task_port = self.port; 245 | let thread = Box::new(OSXThread { 246 | port, 247 | pthread_id, 248 | task_port, 249 | }) as Box>; 250 | 251 | osx_threads.push(thread); 252 | } 253 | Ok(osx_threads) 254 | } else { 255 | Err(format!( 256 | "Failure to read task {} threads. Error: {}", 257 | self.port, result 258 | ) 259 | .into()) 260 | } 261 | } 262 | } 263 | 264 | /// Requests task_for_pid privilege for this process. 265 | fn request_authorization() -> CrabResult<()> { 266 | // TODO: rewrite this ugly ugly code when AuthorizationCopyRights is available is security_framework 267 | 268 | let name = CString::new("system.privilege.taskport:")?; 269 | 270 | let auth_items = [AuthorizationItem { 271 | name: name.as_ptr(), 272 | valueLength: 0, 273 | value: ptr::null_mut(), 274 | flags: 0, 275 | }]; 276 | 277 | let auth_item_set = AuthorizationRights { 278 | count: 1, 279 | items: auth_items.as_ptr() as *mut _, 280 | }; 281 | 282 | let auth_flags = kAuthorizationFlagExtendRights 283 | | kAuthorizationFlagPreAuthorize 284 | | kAuthorizationFlagInteractionAllowed 285 | | (1 << 5); 286 | 287 | let mut auth_ref = MaybeUninit::::uninit(); 288 | let res = 289 | unsafe { AuthorizationCreate(ptr::null(), ptr::null(), auth_flags, auth_ref.as_mut_ptr()) }; 290 | 291 | if res != errAuthorizationSuccess { 292 | return Err(Box::new(io::Error::new( 293 | io::ErrorKind::Other, 294 | "AuthorizationCreate", 295 | ))); 296 | } 297 | 298 | let auth_ref = unsafe { auth_ref.assume_init() }; 299 | 300 | let mut target_rights = MaybeUninit::::uninit(); 301 | let res = unsafe { 302 | AuthorizationCopyRights( 303 | auth_ref, 304 | &auth_item_set, 305 | ptr::null(), 306 | auth_flags, 307 | target_rights.as_mut_ptr() as *mut *mut _, 308 | ) 309 | }; 310 | 311 | if res != errAuthorizationSuccess { 312 | return Err(Box::new(io::Error::new( 313 | io::ErrorKind::Other, 314 | "AuthorizationCopyRights", 315 | ))); 316 | } 317 | 318 | Ok(()) 319 | } 320 | 321 | #[cfg(test)] 322 | mod tests { 323 | use super::ReadMemory; 324 | use super::*; 325 | use mach::traps::mach_task_self; 326 | use std::sync::{Arc, Barrier}; 327 | use std::thread; 328 | 329 | #[test] 330 | fn read_memory() { 331 | let var: usize = 52; 332 | let var2: u8 = 128; 333 | 334 | let mut read_var_op: usize = 0; 335 | let mut read_var2_op: u8 = 0; 336 | 337 | unsafe { 338 | ReadMemory::new(unsafe { mach_task_self() }) 339 | .read(&mut read_var_op, &var as *const _ as usize) 340 | .read(&mut read_var2_op, &var2 as *const _ as usize) 341 | .apply() 342 | .expect("Failed to apply memop"); 343 | } 344 | 345 | assert_eq!(read_var2_op, var2); 346 | assert_eq!(read_var_op, var); 347 | 348 | assert!(true); 349 | } 350 | 351 | #[test] 352 | fn read_threads() -> CrabResult<()> { 353 | let start_barrier = Arc::new(Barrier::new(2)); 354 | let end_barrier = Arc::new(Barrier::new(2)); 355 | 356 | let t1_start = start_barrier.clone(); 357 | let t1_end = end_barrier.clone(); 358 | 359 | let thread_name = "thread-name"; 360 | let t1_handle = thread::Builder::new() 361 | .name(thread_name.to_string()) 362 | .spawn(move || { 363 | t1_start.wait(); 364 | t1_end.wait(); 365 | }) 366 | .unwrap(); 367 | 368 | start_barrier.wait(); 369 | 370 | let proc = Target::me(); 371 | let threads = proc.threads()?; 372 | 373 | let threads: Vec<_> = threads 374 | .iter() 375 | .map(|t| { 376 | let name = t.name().unwrap().unwrap_or_else(String::new); 377 | let id = t.thread_id(); 378 | (name, id) 379 | }) 380 | .collect(); 381 | 382 | assert!( 383 | threads.len() >= 2, 384 | "Expected at least 2 threads in {:?}", 385 | threads 386 | ); 387 | 388 | assert!( 389 | threads.iter().any(|(name, _)| name == thread_name), 390 | "Expected to find thread name={} in {:?}", 391 | thread_name, 392 | threads 393 | ); 394 | 395 | end_barrier.wait(); 396 | t1_handle.join().unwrap(); 397 | Ok(()) 398 | } 399 | } 400 | -------------------------------------------------------------------------------- /src/target/macos/readmem.rs: -------------------------------------------------------------------------------- 1 | use mach::{kern_return, port, vm, vm_types::*}; 2 | use std::{io, marker::PhantomData, mem}; 3 | 4 | use crate::CrabResult; 5 | 6 | /// A single memory read operation. 7 | struct ReadOp { 8 | // Remote memory location. 9 | remote_base: usize, 10 | // Size of the `local_ptr` buffer. 11 | len: usize, 12 | // Pointer to a local destination buffer. 13 | local_ptr: *mut libc::c_void, 14 | } 15 | 16 | /// Allows to read memory from different locations in debuggee's memory as a single operation. 17 | pub struct ReadMemory<'a> { 18 | target_port: port::mach_port_name_t, 19 | read_ops: Vec, 20 | _marker: PhantomData<&'a mut ()>, 21 | } 22 | 23 | impl<'a> ReadMemory<'a> { 24 | pub(super) fn new(target_port: port::mach_port_name_t) -> Self { 25 | ReadMemory { 26 | target_port, 27 | read_ops: Vec::new(), 28 | _marker: PhantomData, 29 | } 30 | } 31 | 32 | /// Reads a value of type `T` from debuggee's memory at location `remote_base`. 33 | /// This value will be written to the provided variable `val`. 34 | /// You should call `apply` in order to execute the memory read operation. 35 | /// The provided variable `val` can't be accessed until either `apply` is called or `self` is 36 | /// dropped. 37 | /// 38 | /// # Safety 39 | /// 40 | /// The type `T` must not have any invalid values. 41 | /// For example `T` must not be a `bool`, as `transmute::(2)` is not a valid value for a bool. 42 | /// In case of doubt, wrap the type in [`mem::MaybeUninit`]. 43 | // todo: further document mem safety - e.g., what happens in the case of partial read 44 | pub fn read(mut self, val: &'a mut T, remote_base: usize) -> Self { 45 | self.read_ops.push(ReadOp { 46 | remote_base, 47 | len: mem::size_of::(), 48 | local_ptr: val as *mut T as *mut libc::c_void, 49 | }); 50 | 51 | self 52 | } 53 | 54 | /// Executes the memory read operation. 55 | pub fn apply(self) -> CrabResult<()> { 56 | for read_op in &self.read_ops { 57 | unsafe { 58 | let mut data_size: mach_vm_size_t = 0; 59 | 60 | let res = vm::mach_vm_read_overwrite( 61 | self.target_port, 62 | read_op.remote_base as mach_vm_address_t, 63 | read_op.len as mach_vm_size_t, 64 | read_op.local_ptr as *mut _ as mach_vm_size_t, 65 | &mut data_size, 66 | ); 67 | 68 | if res != kern_return::KERN_SUCCESS { 69 | // TODO: account for partial reads 70 | // TODO: properly wrap error types 71 | return Err(Box::new(io::Error::last_os_error())); 72 | } 73 | } 74 | } 75 | 76 | Ok(()) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/target/macos/vmmap.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) Julia Evans 2 | // 3 | // Implementation of vmmap was taken from 4 | // https://jvns.ca/blog/2018/01/26/mac-memory-maps/ 5 | 6 | use libproc::libproc::proc_pid::regionfilename; 7 | use mach::{ 8 | kern_return::KERN_SUCCESS, 9 | mach_types::*, 10 | message::*, 11 | port::{mach_port_name_t, mach_port_t}, 12 | task::*, 13 | task_info::*, 14 | vm_region::{ 15 | vm_region_basic_info_data_64_t, vm_region_basic_info_data_t, vm_region_info_t, 16 | VM_REGION_BASIC_INFO, 17 | }, 18 | vm_types::*, 19 | }; 20 | use nix::unistd::Pid; 21 | use std::mem; 22 | 23 | #[derive(Debug, Clone)] 24 | pub(crate) struct Region { 25 | pub size: mach_vm_size_t, 26 | pub info: vm_region_basic_info_data_t, 27 | pub address: mach_vm_address_t, 28 | pub count: mach_msg_type_number_t, 29 | pub filename: Option, 30 | } 31 | 32 | impl Region { 33 | pub fn end(&self) -> mach_vm_address_t { 34 | self.address + self.size as mach_vm_address_t 35 | } 36 | 37 | pub fn is_read(&self) -> bool { 38 | self.info.protection & mach::vm_prot::VM_PROT_READ != 0 39 | } 40 | pub fn is_write(&self) -> bool { 41 | self.info.protection & mach::vm_prot::VM_PROT_WRITE != 0 42 | } 43 | pub fn is_exec(&self) -> bool { 44 | self.info.protection & mach::vm_prot::VM_PROT_EXECUTE != 0 45 | } 46 | } 47 | 48 | pub(crate) fn macosx_debug_regions(pid: Pid, task: mach_port_name_t) -> Vec { 49 | let init_region = mach_vm_region(pid, task, 1).unwrap(); 50 | let mut vec = vec![]; 51 | let mut region = init_region.clone(); 52 | vec.push(init_region); 53 | loop { 54 | match mach_vm_region(pid, task, region.end()) { 55 | Some(r) => { 56 | vec.push(r.clone()); 57 | region = r; 58 | } 59 | _ => return vec, 60 | } 61 | } 62 | } 63 | 64 | pub(crate) fn get_task_info(task: mach_port_name_t) -> Option { 65 | const TASK_DYLD_INFO_COUNT: usize = 66 | mem::size_of::() / mem::size_of::(); 67 | let mut count = TASK_DYLD_INFO_COUNT; 68 | let mut dyld_info = unsafe { mem::zeroed::() }; 69 | let ret = unsafe { 70 | task_info( 71 | task, 72 | TASK_DYLD_INFO, 73 | &mut dyld_info as *mut task_dyld_info as task_info_t, 74 | &mut count as *mut usize as *mut mach_msg_type_number_t, 75 | ) 76 | }; 77 | 78 | if ret != KERN_SUCCESS { 79 | None 80 | } else { 81 | Some(dyld_info) 82 | } 83 | } 84 | 85 | pub(crate) fn mach_vm_region( 86 | pid: Pid, 87 | target_task: mach_port_name_t, 88 | mut address: mach_vm_address_t, 89 | ) -> Option { 90 | let mut count = mem::size_of::() as mach_msg_type_number_t; 91 | let mut object_name: mach_port_t = 0; 92 | let mut size = unsafe { mem::zeroed::() }; 93 | let mut info = unsafe { mem::zeroed::() }; 94 | let result = unsafe { 95 | mach::vm::mach_vm_region( 96 | target_task as vm_task_entry_t, 97 | &mut address, 98 | &mut size, 99 | VM_REGION_BASIC_INFO, 100 | &mut info as *mut vm_region_basic_info_data_t as vm_region_info_t, 101 | &mut count, 102 | &mut object_name, 103 | ) 104 | }; 105 | if result != KERN_SUCCESS { 106 | return None; 107 | } 108 | let filename = match regionfilename(pid.as_raw(), address) { 109 | Ok(x) => Some(x), 110 | _ => None, 111 | }; 112 | Some(Region { 113 | size, 114 | info, 115 | address, 116 | count, 117 | filename, 118 | }) 119 | } 120 | -------------------------------------------------------------------------------- /src/target/macos/writemem.rs: -------------------------------------------------------------------------------- 1 | use mach::{kern_return, message::mach_msg_type_number_t, port, vm, vm_types::*}; 2 | use std::{io, marker::PhantomData, mem}; 3 | 4 | use crate::CrabResult; 5 | 6 | /// Allows to write data to different locations in debuggee's memory as a single operation. 7 | pub struct WriteMemory<'a> { 8 | target_port: port::mach_port_name_t, 9 | write_ops: Vec, 10 | _marker: PhantomData<&'a ()>, 11 | } 12 | 13 | impl<'a> WriteMemory<'a> { 14 | pub(super) fn new(target_port: port::mach_port_name_t) -> Self { 15 | WriteMemory { 16 | target_port, 17 | write_ops: Vec::new(), 18 | _marker: PhantomData, 19 | } 20 | } 21 | 22 | pub fn write(mut self, val: &'a T, remote_base: usize) -> Self { 23 | self.write_ops.push(WriteOp { 24 | remote_base, 25 | source_len: mem::size_of_val(val), 26 | source_ptr: val as *const T as *const libc::c_void, 27 | }); 28 | self 29 | } 30 | 31 | /// Executes the memory write operation. 32 | pub fn apply(self) -> CrabResult<()> { 33 | for write_op in &self.write_ops { 34 | let res = unsafe { 35 | vm::mach_vm_write( 36 | self.target_port, 37 | write_op.remote_base as mach_vm_address_t, 38 | write_op.source_ptr as vm_offset_t, 39 | write_op.source_len as mach_msg_type_number_t, 40 | ) 41 | }; 42 | 43 | if res != kern_return::KERN_SUCCESS { 44 | // TODO: account for partial writes 45 | // TODO: properly wrap error types 46 | return Err(Box::new(io::Error::last_os_error())); 47 | } 48 | } 49 | 50 | Ok(()) 51 | } 52 | } 53 | 54 | /// A single memory write operation. 55 | #[derive(Debug, PartialEq, Eq)] 56 | pub(crate) struct WriteOp { 57 | /// Remote destation location. 58 | remote_base: usize, 59 | /// Pointer to a source. 60 | source_ptr: *const libc::c_void, 61 | /// Size of `source_ptr`. 62 | source_len: usize, 63 | } 64 | 65 | #[cfg(test)] 66 | mod tests { 67 | use super::{WriteMemory, WriteOp}; 68 | use mach::traps::mach_task_self; 69 | use std::{mem, ptr}; 70 | 71 | #[test] 72 | fn write_memory() { 73 | let var: usize = 52; 74 | let var2: u8 = 128; 75 | 76 | let write_var_op: usize = 0; 77 | let write_var2_op: u8 = 0; 78 | 79 | unsafe { 80 | WriteMemory::new(mach_task_self()) 81 | .write(&var, &write_var_op as *const _ as usize) 82 | .write(&var2, &write_var2_op as *const _ as usize) 83 | .apply() 84 | .expect("Failed to write memory") 85 | }; 86 | 87 | unsafe { 88 | assert_eq!(ptr::read_volatile(&write_var_op), var); 89 | assert_eq!(ptr::read_volatile(&write_var2_op), var2); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/target/registers.rs: -------------------------------------------------------------------------------- 1 | //! Interfaces related to registers reading & writing. 2 | 3 | use std::fmt::Debug; 4 | 5 | /// Trait that can be used to read & write the target's registers. 6 | pub trait Registers: Debug { 7 | /// Returns a current instruction pointer. 8 | fn ip(&self) -> u64; 9 | 10 | /// Sets an instruction pointer to the provided value. 11 | fn set_ip(&mut self, ip: u64); 12 | 13 | /// Returns a current stack pointer. 14 | fn sp(&self) -> u64; 15 | 16 | /// Sets a stack pointer to the provided value. 17 | fn set_sp(&mut self, sp: u64); 18 | 19 | /// Returns a base pointer. 20 | /// Returns `None` if the standard ABI on the platform has no base pointer. 21 | fn bp(&self) -> Option; 22 | 23 | /// Sets a base pointer to the provided value. 24 | /// Returns `None` if the standard ABI on the platform has no base pointer. 25 | #[must_use] 26 | fn set_bp(&mut self, bp: u64) -> Option<()>; 27 | 28 | /// Translates a DWARF register type into a value. 29 | /// See [`gimli::Register`](https://docs.rs/gimli/*/gimli/struct.Register.html) definition for a list of 30 | /// available registers. Returns `None` when a specified register doesn't exist. 31 | fn reg_for_dwarf(&self, reg: gimli::Register) -> Option; 32 | 33 | /// Sets a DWARF register to the provided value. 34 | /// See [`gimli::Register`](https://docs.rs/gimli/*/gimli/struct.Register.html) definition for a list of 35 | /// available registers. Returns `None` when a specified register doesn't exist. 36 | #[must_use] 37 | fn set_reg_for_dwarf(&mut self, reg: gimli::Register, val: u64) -> Option<()>; 38 | 39 | /// Converts a DWARF register type into a lower-case string value (the register name). 40 | /// Returns `None` when a register doesn't exist. 41 | fn name_for_dwarf(reg: gimli::Register) -> Option<&'static str> 42 | where 43 | Self: Sized; 44 | 45 | /// Converts a name of a register into a corresponding DWARF register type. 46 | /// Returns `None` when a register doesn't exist (e.g., a name is invalid). 47 | fn dwarf_for_name(name: &str) -> Option 48 | where 49 | Self: Sized; 50 | } 51 | 52 | #[cfg(all(target_os = "linux", target_arch = "x86_64"))] 53 | pub use x86_64::Registers as RegistersX86_64; 54 | 55 | #[cfg(all(target_os = "linux", target_arch = "x86_64"))] 56 | mod x86_64 { 57 | use gimli::Register; 58 | // This struct is available only on Linux. 59 | use libc::user_regs_struct; 60 | 61 | #[derive(Copy, Clone, Debug)] 62 | pub struct Registers { 63 | regs: user_regs_struct, 64 | } 65 | 66 | impl From for Registers { 67 | fn from(regs: user_regs_struct) -> Registers { 68 | Registers { regs } 69 | } 70 | } 71 | 72 | impl Into for Registers { 73 | fn into(self) -> user_regs_struct { 74 | self.regs 75 | } 76 | } 77 | 78 | impl super::Registers for Registers { 79 | fn ip(&self) -> u64 { 80 | self.regs.rip 81 | } 82 | 83 | fn sp(&self) -> u64 { 84 | self.regs.rsp 85 | } 86 | 87 | fn bp(&self) -> Option { 88 | Some(self.regs.rbp) 89 | } 90 | 91 | fn set_ip(&mut self, ip: u64) { 92 | self.regs.rip = ip; 93 | } 94 | 95 | fn set_bp(&mut self, bp: u64) -> Option<()> { 96 | self.regs.rbp = bp; 97 | Some(()) 98 | } 99 | 100 | fn set_sp(&mut self, sp: u64) { 101 | self.regs.rsp = sp; 102 | } 103 | 104 | fn set_reg_for_dwarf(&mut self, register: Register, val: u64) -> Option<()> { 105 | use gimli::X86_64; 106 | match register { 107 | X86_64::RAX => self.regs.rax = val, 108 | X86_64::RBX => self.regs.rbx = val, 109 | X86_64::RCX => self.regs.rcx = val, 110 | X86_64::RDX => self.regs.rdx = val, 111 | X86_64::RSI => self.regs.rsi = val, 112 | X86_64::RDI => self.regs.rdi = val, 113 | X86_64::RSP => self.regs.rsp = val, 114 | X86_64::RBP => self.regs.rbp = val, 115 | X86_64::R8 => self.regs.r8 = val, 116 | X86_64::R9 => self.regs.r9 = val, 117 | X86_64::R10 => self.regs.r10 = val, 118 | X86_64::R11 => self.regs.r11 = val, 119 | X86_64::R12 => self.regs.r12 = val, 120 | X86_64::R13 => self.regs.r13 = val, 121 | X86_64::R14 => self.regs.r14 = val, 122 | X86_64::R15 => self.regs.r15 = val, 123 | X86_64::CS => self.regs.cs = val, 124 | X86_64::SS => self.regs.ss = val, 125 | X86_64::DS => self.regs.ds = val, 126 | X86_64::GS => self.regs.gs = val, 127 | X86_64::ES => self.regs.es = val, 128 | X86_64::FS => self.regs.fs = val, 129 | X86_64::FS_BASE => self.regs.fs_base = val, 130 | X86_64::GS_BASE => self.regs.gs_base = val, 131 | X86_64::RFLAGS => self.regs.eflags = val, 132 | reg => unimplemented!("{:?}", reg), // FIXME 133 | } 134 | Some(()) 135 | } 136 | 137 | fn dwarf_for_name(_name: &str) -> Option { 138 | unimplemented!() 139 | } 140 | 141 | fn name_for_dwarf(register: Register) -> Option<&'static str> { 142 | gimli::X86_64::register_name(register) 143 | } 144 | 145 | fn reg_for_dwarf(&self, register: Register) -> Option { 146 | use gimli::X86_64; 147 | match register { 148 | X86_64::RAX => Some(self.regs.rax), 149 | X86_64::RBX => Some(self.regs.rbx), 150 | X86_64::RCX => Some(self.regs.rcx), 151 | X86_64::RDX => Some(self.regs.rdx), 152 | X86_64::RSI => Some(self.regs.rsi), 153 | X86_64::RDI => Some(self.regs.rdi), 154 | X86_64::RSP => Some(self.regs.rsp), 155 | X86_64::RBP => Some(self.regs.rbp), 156 | X86_64::R8 => Some(self.regs.r8), 157 | X86_64::R9 => Some(self.regs.r9), 158 | X86_64::R10 => Some(self.regs.r10), 159 | X86_64::R11 => Some(self.regs.r11), 160 | X86_64::R12 => Some(self.regs.r12), 161 | X86_64::R13 => Some(self.regs.r13), 162 | X86_64::R14 => Some(self.regs.r14), 163 | X86_64::R15 => Some(self.regs.r15), 164 | X86_64::CS => Some(self.regs.cs), 165 | X86_64::SS => Some(self.regs.ss), 166 | X86_64::DS => Some(self.regs.ds), 167 | X86_64::GS => Some(self.regs.gs), 168 | X86_64::ES => Some(self.regs.es), 169 | X86_64::FS => Some(self.regs.fs), 170 | X86_64::FS_BASE => Some(self.regs.fs_base), 171 | X86_64::GS_BASE => Some(self.regs.gs_base), 172 | X86_64::RFLAGS => Some(self.regs.eflags), 173 | reg => unimplemented!("{:?}", reg), // FIXME 174 | } 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/target/thread.rs: -------------------------------------------------------------------------------- 1 | use super::registers::Registers; 2 | use crate::CrabResult; 3 | 4 | pub trait Thread 5 | where 6 | Reg: Registers, 7 | { 8 | type ThreadId; 9 | 10 | /// Return a thread name. 11 | fn name(&self) -> CrabResult>; 12 | 13 | /// Return a thread ID. 14 | fn thread_id(&self) -> Self::ThreadId; 15 | 16 | /// Return CPU registers structure for this thread. 17 | fn read_regs(&self) -> CrabResult; 18 | 19 | /// Write CPU registers for this thread. 20 | fn write_regs(&self, registers: Reg) -> CrabResult<()>; 21 | } 22 | -------------------------------------------------------------------------------- /src/target/unix.rs: -------------------------------------------------------------------------------- 1 | use nix::{ 2 | sys::ptrace, 3 | sys::wait::{waitpid, WaitStatus}, 4 | unistd::Pid, 5 | }; 6 | use std::process::Command; 7 | 8 | use crate::CrabResult; 9 | 10 | /// This trait defines the common behavior for all *nix targets 11 | pub trait UnixTarget { 12 | /// Provides the Pid of the debugee process 13 | fn pid(&self) -> Pid; 14 | 15 | /// Step the debuggee one instruction further. 16 | fn step(&self) -> CrabResult { 17 | ptrace::step(self.pid(), None)?; 18 | let status = waitpid(self.pid(), None)?; 19 | Ok(status) 20 | } 21 | 22 | /// Continues execution of a debuggee. 23 | fn unpause(&self) -> CrabResult { 24 | ptrace::cont(self.pid(), None)?; 25 | let status = waitpid(self.pid(), None)?; 26 | Ok(status) 27 | } 28 | 29 | /// Detach from the debuggee, continuing its execution. 30 | fn detach(&self) -> CrabResult<()> { 31 | ptrace::detach(self.pid(), None)?; 32 | Ok(()) 33 | } 34 | 35 | /// Kills the debuggee. 36 | fn kill(&self) -> CrabResult { 37 | ptrace::kill(self.pid())?; 38 | let status = waitpid(self.pid(), None)?; 39 | Ok(status) 40 | } 41 | } 42 | 43 | /// Launch a new debuggee process. 44 | pub(in crate::target) fn launch(mut cmd: Command) -> CrabResult<(Pid, WaitStatus)> { 45 | use std::os::unix::process::CommandExt; 46 | unsafe { 47 | cmd.pre_exec(|| { 48 | // Disable ASLR 49 | #[cfg(target_os = "linux")] 50 | { 51 | const ADDR_NO_RANDOMIZE: libc::c_ulong = 0x0040000; 52 | libc::personality(ADDR_NO_RANDOMIZE); 53 | } 54 | 55 | ptrace::traceme().map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?; 56 | 57 | Ok(()) 58 | }); 59 | } 60 | let child = cmd.spawn()?; 61 | let pid = Pid::from_raw(child.id() as i32); 62 | let status = waitpid(pid, None)?; 63 | Ok((pid, status)) 64 | } 65 | 66 | /// Attach existing process as a debugee. 67 | pub(in crate::target) fn attach(pid: Pid) -> CrabResult { 68 | ptrace::attach(pid)?; 69 | let status = waitpid(pid, None)?; 70 | Ok(status) 71 | } 72 | -------------------------------------------------------------------------------- /src/target/windows.rs: -------------------------------------------------------------------------------- 1 | use std::mem; 2 | use winapi::shared::minwindef::FALSE; 3 | use winapi::um::processthreadsapi::{ 4 | CreateProcessW, OpenProcess, PROCESS_INFORMATION, STARTUPINFOW, 5 | }; 6 | use winapi::um::winbase; 7 | use winapi::um::winnt; 8 | 9 | use crate::CrabResult; 10 | 11 | /// This structure holds the state of the debuggee on windows systems. 12 | pub struct Target { 13 | proc_handle: winnt::HANDLE, 14 | } 15 | 16 | macro_rules! wide_string { 17 | ($string:expr) => {{ 18 | use std::os::windows::ffi::OsStrExt; 19 | let input = std::ffi::OsStr::new($string); 20 | let vec: Vec = input.encode_wide().chain(Some(0)).collect(); 21 | vec 22 | }}; 23 | } 24 | 25 | impl Target { 26 | /// Launch a new debuggee process. 27 | pub fn launch(path: &str) -> CrabResult { 28 | let startup_info = mem::MaybeUninit::::zeroed(); 29 | let mut startup_info = unsafe { startup_info.assume_init() }; 30 | let proc_info = mem::MaybeUninit::::zeroed(); 31 | let mut proc_info = unsafe { proc_info.assume_init() }; 32 | 33 | if unsafe { 34 | CreateProcessW( 35 | std::ptr::null_mut(), 36 | wide_string!(&path).as_mut_ptr(), 37 | std::ptr::null_mut(), 38 | std::ptr::null_mut(), 39 | FALSE, 40 | winbase::DEBUG_ONLY_THIS_PROCESS, 41 | std::ptr::null_mut(), 42 | std::ptr::null_mut(), 43 | &mut startup_info, 44 | &mut proc_info, 45 | ) 46 | } == FALSE 47 | { 48 | return Err(Box::new(std::io::Error::last_os_error())); 49 | } 50 | 51 | Ok(Target { 52 | proc_handle: proc_info.hProcess, 53 | }) 54 | } 55 | 56 | /// Attach to a running Process. 57 | pub fn attach(pid: u32) -> CrabResult { 58 | let access = winnt::PROCESS_VM_OPERATION | winnt::PROCESS_VM_READ | winnt::PROCESS_VM_WRITE; 59 | let proc_handle = unsafe { OpenProcess(access, FALSE, pid) }; 60 | if proc_handle == std::ptr::null_mut() { 61 | return Err(Box::new(std::io::Error::last_os_error())); 62 | } 63 | Ok(Target { proc_handle }) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/attach_readmem.rs: -------------------------------------------------------------------------------- 1 | //! This is a simple test to attach to already running debugee process 2 | 3 | #![cfg(unix)] 4 | use nix::unistd::{execv, fork, ForkResult}; 5 | use std::ffi::CString; 6 | 7 | mod test_utils; 8 | 9 | #[cfg(target_os = "linux")] 10 | use headcrab::{symbol::Dwarf, target::AttachOptions, target::LinuxTarget, CrabResult}; 11 | 12 | static BIN_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/testees/longer_hello"); 13 | 14 | // Ignoring because most linux distributions have attaching to a running process disabled. 15 | // To run the test it either requires root privilages or CAP_SYS_PTRACE capability. 16 | #[ignore] 17 | #[cfg(target_os = "linux")] 18 | #[test] 19 | fn attach_readmem() -> CrabResult<()> { 20 | test_utils::ensure_testees(); 21 | 22 | let debuginfo = Dwarf::new(BIN_PATH)?; 23 | 24 | let str_addr = debuginfo 25 | .get_var_address("STATICVAR")? 26 | .expect("Expected static var has not been found in the target binary"); 27 | 28 | match fork()? { 29 | ForkResult::Parent { child, .. } => { 30 | use std::{thread, time}; 31 | thread::sleep(time::Duration::from_millis(50)); 32 | 33 | let (target, status) = 34 | LinuxTarget::attach(child, AttachOptions { kill_on_exit: true })?; 35 | match status { 36 | nix::sys::wait::WaitStatus::Stopped(_, nix::sys::signal::SIGTRAP) => {} 37 | _ => panic!("Status: {:?}", status), 38 | } 39 | 40 | // Read pointer 41 | let mut ptr_addr: usize = 0; 42 | unsafe { 43 | target.read().read(&mut ptr_addr, str_addr).apply()?; 44 | } 45 | 46 | // Read current value 47 | let mut rval = [0u8; 13]; 48 | unsafe { 49 | target.read().read(&mut rval, ptr_addr).apply()?; 50 | } 51 | 52 | assert_eq!(&rval, b"Hello, world!"); 53 | 54 | Ok(()) 55 | } 56 | ForkResult::Child => { 57 | let path = CString::new(BIN_PATH)?; 58 | execv(&path, &[])?; 59 | 60 | // execv replaces the process image, so this place in code will not be reached. 61 | unreachable!(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tests/disassemble.rs: -------------------------------------------------------------------------------- 1 | //! This is a simple test to disassemble bytes from a child process. 2 | 3 | mod test_utils; 4 | 5 | #[cfg(target_os = "linux")] 6 | use headcrab::{symbol::RelocatedDwarf, target::UnixTarget}; 7 | 8 | static BIN_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/testees/known_asm"); 9 | 10 | // FIXME: this should be an internal impl detail 11 | #[cfg(target_os = "macos")] 12 | static MAC_DSYM_PATH: &str = concat!( 13 | env!("CARGO_MANIFEST_DIR"), 14 | "/tests/testees/known_asm.dSYM/Contents/Resources/DWARF/known_asm" 15 | ); 16 | 17 | // FIXME: Running this test just for linux because of privileges issue on macOS. Enable for everything after fixing. 18 | #[cfg(target_os = "linux")] 19 | #[test] 20 | fn disassemble() -> headcrab::CrabResult<()> { 21 | test_utils::ensure_testees(); 22 | 23 | let target = test_utils::launch(BIN_PATH); 24 | let debuginfo = RelocatedDwarf::from_maps(&target.memory_maps()?)?; 25 | 26 | // First breakpoint 27 | target.unpause()?; 28 | let ip = target.read_regs()?.ip(); 29 | println!("{:08x}", ip); 30 | assert_eq!( 31 | debuginfo.get_address_symbol_name(ip as usize).as_deref(), 32 | Some("main") 33 | ); 34 | 35 | dbg!(); 36 | let mut code = [0; 10]; 37 | unsafe { 38 | target.read().read(&mut code, ip as usize).apply()?; 39 | } 40 | dbg!(); 41 | 42 | let disassembly = 43 | headcrab::symbol::DisassemblySource::default().source_snippet(&code, ip, false)?; 44 | assert_eq!( 45 | disassembly, 46 | "nop \n\ 47 | int3 \n\ 48 | movq $0, %rax\n\ 49 | retq \n" 50 | ); 51 | 52 | // Second breakpoint 53 | target.unpause()?; 54 | 55 | test_utils::continue_to_end(&target); 56 | 57 | Ok(()) 58 | } 59 | -------------------------------------------------------------------------------- /tests/fixed_breakpoint.rs: -------------------------------------------------------------------------------- 1 | //! This is a simple test for waiting for a fixed breakpoint in a child process. 2 | //! Here the testee has hardcoded INT3 instructions that should trigger breaks 3 | //! so that headcrab can gain control at certain key points of execution. 4 | 5 | mod test_utils; 6 | 7 | #[cfg(target_os = "linux")] 8 | use headcrab::{symbol::RelocatedDwarf, target::UnixTarget, CrabResult}; 9 | 10 | static BIN_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/testees/known_asm"); 11 | 12 | // FIXME: this should be an internal impl detail 13 | #[cfg(target_os = "macos")] 14 | static MAC_DSYM_PATH: &str = concat!( 15 | env!("CARGO_MANIFEST_DIR"), 16 | "/tests/testees/known_asm.dSYM/Contents/Resources/DWARF/known_asm" 17 | ); 18 | 19 | // FIXME: Running this test just for linux because of privileges issue on macOS. Enable for everything after fixing. 20 | #[cfg(target_os = "linux")] 21 | #[test] 22 | fn fixed_breakpoint() -> CrabResult<()> { 23 | test_utils::ensure_testees(); 24 | 25 | let target = test_utils::launch(BIN_PATH); 26 | 27 | let debuginfo = RelocatedDwarf::from_maps(&target.memory_maps()?)?; 28 | 29 | // First breakpoint 30 | target.unpause()?; 31 | let ip = target.read_regs()?.ip(); 32 | assert_eq!( 33 | debuginfo.get_address_symbol_name(ip as usize).as_deref(), 34 | Some("main") 35 | ); 36 | 37 | // Second breakpoint 38 | target.unpause()?; 39 | let ip = target.read_regs()?.ip(); 40 | assert_eq!( 41 | debuginfo.get_address_symbol_name(ip as usize).as_deref(), 42 | Some("main") 43 | ); 44 | 45 | test_utils::continue_to_end(&target); 46 | 47 | Ok(()) 48 | } 49 | -------------------------------------------------------------------------------- /tests/hardware_breakpoint.rs: -------------------------------------------------------------------------------- 1 | //! This is a simple test to running a syscall in a child process. 2 | 3 | mod test_utils; 4 | 5 | #[cfg(target_os = "linux")] 6 | use headcrab::{symbol::RelocatedDwarf, target::UnixTarget, CrabResult}; 7 | 8 | static BIN_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/testees/hw_breakpoint"); 9 | 10 | // FIXME: Running this test just for linux because of privileges issue on macOS. Enable for everything after fixing. 11 | #[cfg(target_os = "linux")] 12 | #[test] 13 | fn hardware_breakpoint() -> CrabResult<()> { 14 | use headcrab::target::{HardwareBreakpoint, HardwareBreakpointSize, HardwareBreakpointType}; 15 | 16 | test_utils::ensure_testees(); 17 | 18 | let mut target = test_utils::launch(BIN_PATH); 19 | 20 | let debuginfo = RelocatedDwarf::from_maps(&target.memory_maps()?)?; 21 | 22 | let var_addr = debuginfo.get_symbol_address("STATICVAR"); 23 | assert!(var_addr.is_some()); 24 | let var2_addr = debuginfo.get_symbol_address("STATICVAR2"); 25 | assert!(var2_addr.is_some()); 26 | let var3_addr = debuginfo.get_symbol_address("STATICVAR3"); 27 | assert!(var3_addr.is_some()); 28 | 29 | let wn2 = target.set_hardware_breakpoint(HardwareBreakpoint { 30 | addr: var2_addr.unwrap(), 31 | typ: HardwareBreakpointType::Write, 32 | size: HardwareBreakpointSize::from_usize(std::mem::size_of::())?, 33 | })?; 34 | let wn = target.set_hardware_breakpoint(HardwareBreakpoint { 35 | addr: var_addr.unwrap(), 36 | typ: HardwareBreakpointType::Write, 37 | size: HardwareBreakpointSize::from_usize(std::mem::size_of::())?, 38 | })?; 39 | let _wn3 = target.set_hardware_breakpoint(HardwareBreakpoint { 40 | addr: var3_addr.unwrap(), 41 | typ: HardwareBreakpointType::Write, 42 | size: HardwareBreakpointSize::from_usize(std::mem::size_of::())?, 43 | })?; 44 | 45 | if let nix::sys::wait::WaitStatus::Stopped(_, signal) = target.unpause()? { 46 | assert_eq!(signal, nix::sys::signal::SIGTRAP) 47 | } else { 48 | panic!("Process hasn't stopped on hardware breakpoint") 49 | } 50 | assert_eq!(target.is_hardware_breakpoint_triggered()?, Some(wn)); 51 | 52 | if let nix::sys::wait::WaitStatus::Stopped(_, signal) = target.unpause()? { 53 | assert_eq!(signal, nix::sys::signal::SIGTRAP) 54 | } else { 55 | panic!("Process hasn't stopped on hardware breakpoint") 56 | } 57 | assert_eq!(target.is_hardware_breakpoint_triggered()?, Some(wn2)); 58 | 59 | target.clear_all_hardware_breakpoints()?; 60 | 61 | if let nix::sys::wait::WaitStatus::Exited(..) = target.unpause()? { 62 | } else { 63 | target.unpause()?; 64 | panic!("Hardware breakpoint wasn't cleared"); 65 | } 66 | 67 | Ok(()) 68 | } 69 | -------------------------------------------------------------------------------- /tests/read_locals.rs: -------------------------------------------------------------------------------- 1 | //! This is a simple test for reading the value of locals from a child process. 2 | 3 | mod test_utils; 4 | 5 | #[cfg(target_os = "linux")] 6 | use headcrab::{ 7 | symbol::{LocalValue, RelocatedDwarf}, 8 | target::{Registers, UnixTarget}, 9 | CrabResult, 10 | }; 11 | 12 | static BIN_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/testees/hello"); 13 | 14 | // FIXME: this should be an internal impl detail 15 | #[cfg(target_os = "macos")] 16 | static MAC_DSYM_PATH: &str = concat!( 17 | env!("CARGO_MANIFEST_DIR"), 18 | "/tests/testees/hello.dSYM/Contents/Resources/DWARF/hello" 19 | ); 20 | 21 | // FIXME: Running this test just for linux because of privileges issue on macOS. Enable for everything after fixing. 22 | #[cfg(target_os = "linux")] 23 | #[test] 24 | fn read_locals() -> CrabResult<()> { 25 | test_utils::ensure_testees(); 26 | 27 | let target = test_utils::launch(BIN_PATH); 28 | 29 | let debuginfo = RelocatedDwarf::from_maps(&target.memory_maps()?)?; 30 | 31 | // Breakpoint 32 | test_utils::patch_breakpoint(&target, &debuginfo); 33 | target.unpause()?; 34 | let ip = target.read_regs()?.ip(); 35 | assert_eq!( 36 | debuginfo.get_address_symbol_name(ip as usize).as_deref(), 37 | Some("breakpoint") 38 | ); 39 | 40 | while debuginfo 41 | .get_address_symbol_name(target.read_regs()?.ip() as usize) 42 | .as_deref() 43 | == Some("breakpoint") 44 | { 45 | target.step()?; 46 | } 47 | 48 | let regs = target.main_thread()?.read_regs()?; 49 | let ip = regs.ip(); 50 | assert!(debuginfo 51 | .get_address_symbol_name(ip as usize) 52 | .as_deref() 53 | .unwrap() 54 | .starts_with("_ZN5hello4main17h")); 55 | 56 | let () = debuginfo 57 | .with_addr_frames(ip as usize, |ip, mut frames| { 58 | let mut first_frame = true; 59 | while let Some(frame) = frames.next()? { 60 | if !first_frame { 61 | panic!("Function inlined into main"); 62 | } 63 | 64 | first_frame = false; 65 | 66 | let (_dwarf, unit, dw_die_offset) = frame 67 | .function_debuginfo() 68 | .ok_or_else(|| "No dwarf debuginfo for function".to_owned())?; 69 | 70 | let frame_base = if let Some(frame_base) = 71 | unit.entry(dw_die_offset)?.attr(gimli::DW_AT_frame_base)? 72 | { 73 | let frame_base = frame_base.exprloc_value().unwrap(); 74 | let res = headcrab::symbol::dwarf_utils::evaluate_expression( 75 | unit, 76 | frame_base, 77 | &X86_64EvalContext { 78 | frame_base: None, 79 | regs: Box::new(regs), 80 | }, 81 | )?; 82 | assert_eq!(res.len(), 1); 83 | assert_eq!(res[0].bit_offset, None); 84 | assert_eq!(res[0].size_in_bits, None); 85 | Some(match res[0].location { 86 | gimli::Location::Register { 87 | register: gimli::X86_64::RBP, 88 | } => regs.bp().unwrap(), 89 | ref loc => unimplemented!("{:?}", loc), // FIXME 90 | }) 91 | } else { 92 | None 93 | }; 94 | 95 | let eval_ctx = X86_64EvalContext { 96 | frame_base, 97 | regs: Box::new(regs), 98 | }; 99 | 100 | frame.each_argument(&eval_ctx, ip as u64, |local| { 101 | panic!("Main should not have any arguments, but it has {:?}", local); 102 | })?; 103 | 104 | frame.each_local(&eval_ctx, ip as u64, |local| { 105 | match local.name().unwrap().unwrap() { 106 | "var" => { 107 | let pieces = match local.value() { 108 | LocalValue::Pieces(pieces) => pieces, 109 | value => panic!("{:?}", value), 110 | }; 111 | assert_eq!(pieces.len(), 1); 112 | assert_eq!(pieces[0].bit_offset, None); 113 | assert_eq!(pieces[0].size_in_bits, None); 114 | match pieces[0].location { 115 | gimli::Location::Value { value } => match value { 116 | gimli::Value::Generic(val) => assert_eq!(val, 42), 117 | val => panic!("{:?}", val), 118 | }, 119 | ref loc => panic!("{:?}", loc), 120 | } 121 | } 122 | "reg_var" => match local.value() { 123 | LocalValue::Const(43) => {} 124 | val => panic!("{:?}", val), 125 | }, 126 | "a" => match local.value() { 127 | LocalValue::Pieces(_) => {} 128 | val => panic!("{:?}", val), 129 | }, 130 | name => panic!("{}", name), 131 | } 132 | 133 | Ok(()) 134 | })?; 135 | } 136 | Ok(()) 137 | })? 138 | .expect("No frames found"); 139 | 140 | test_utils::continue_to_end(&target); 141 | 142 | Ok(()) 143 | } 144 | 145 | #[cfg(target_os = "linux")] 146 | struct X86_64EvalContext { 147 | frame_base: Option, 148 | regs: Box, 149 | } 150 | 151 | #[cfg(target_os = "linux")] 152 | impl headcrab::symbol::dwarf_utils::EvalContext for X86_64EvalContext { 153 | fn frame_base(&self) -> u64 { 154 | self.frame_base.unwrap() 155 | } 156 | 157 | fn register(&self, register: gimli::Register, base_type: gimli::ValueType) -> gimli::Value { 158 | let val = self.regs.reg_for_dwarf(register).unwrap(); 159 | match base_type { 160 | gimli::ValueType::Generic => gimli::Value::Generic(val), 161 | gimli::ValueType::U64 => gimli::Value::U64(val), 162 | _ => unimplemented!(), 163 | } 164 | } 165 | 166 | fn memory( 167 | &self, 168 | _address: u64, 169 | _size: u8, 170 | _address_space: Option, 171 | _base_type: gimli::ValueType, 172 | ) -> gimli::Value { 173 | todo!() 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /tests/readmem.rs: -------------------------------------------------------------------------------- 1 | //! This is a simple test to read memory from a child process. 2 | 3 | mod test_utils; 4 | 5 | #[cfg(target_os = "linux")] 6 | use headcrab::{symbol::RelocatedDwarf, target::UnixTarget, CrabResult}; 7 | 8 | static BIN_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/testees/hello"); 9 | 10 | // FIXME: this should be an internal impl detail 11 | #[cfg(target_os = "macos")] 12 | static MAC_DSYM_PATH: &str = concat!( 13 | env!("CARGO_MANIFEST_DIR"), 14 | "/tests/testees/hello.dSYM/Contents/Resources/DWARF/hello" 15 | ); 16 | 17 | // FIXME: Running this test just for linux because of privileges issue on macOS. Enable for everything after fixing. 18 | #[cfg(target_os = "linux")] 19 | #[test] 20 | fn read_memory() -> CrabResult<()> { 21 | test_utils::ensure_testees(); 22 | 23 | let target = test_utils::launch(BIN_PATH); 24 | 25 | println!("{:#?}", target.memory_maps()?); 26 | let debuginfo = RelocatedDwarf::from_maps(&target.memory_maps()?)?; 27 | 28 | // Test that `a_function` resolves to a function. 29 | let breakpoint_addr = debuginfo.get_symbol_address("breakpoint"); 30 | assert!(breakpoint_addr.is_some()); 31 | 32 | // Test that the address of `a_function` and one byte further both resolves back to that symbol. 33 | assert_eq!( 34 | debuginfo 35 | .get_address_symbol_name(breakpoint_addr.unwrap()) 36 | .as_ref() 37 | .map(|name| &**name), 38 | Some("breakpoint") 39 | ); 40 | assert_eq!( 41 | debuginfo 42 | .get_address_symbol_name(breakpoint_addr.unwrap() + 1) 43 | .as_ref() 44 | .map(|name| &**name), 45 | Some("breakpoint") 46 | ); 47 | 48 | // Test that invalid addresses don't resolve to a symbol. 49 | assert_eq!( 50 | debuginfo 51 | .get_address_symbol_name(0) 52 | .as_ref() 53 | .map(|name| &**name), 54 | None, 55 | ); 56 | 57 | assert_eq!( 58 | debuginfo 59 | .get_address_symbol_name(0xffff_ffff_ffff_ffff) 60 | .as_ref() 61 | .map(|name| &**name), 62 | None, 63 | ); 64 | 65 | test_utils::patch_breakpoint(&target, &debuginfo); 66 | 67 | // Wait for the breakpoint to get hit. 68 | target.unpause().unwrap(); 69 | 70 | let ip = target.read_regs().unwrap().ip(); 71 | assert_eq!( 72 | debuginfo.get_address_symbol_name(ip as usize).as_deref(), 73 | Some("breakpoint") 74 | ); 75 | 76 | let str_addr = debuginfo 77 | .get_var_address("STATICVAR")? 78 | .expect("Expected static var has not been found in the target binary"); 79 | 80 | // Read pointer 81 | let mut ptr_addr: usize = 0; 82 | unsafe { 83 | target.read().read(&mut ptr_addr, str_addr).apply()?; 84 | } 85 | 86 | // Read current value 87 | let mut rval = [0u8; 13]; 88 | unsafe { 89 | target.read().read(&mut rval, ptr_addr).apply()?; 90 | } 91 | 92 | assert_eq!(&rval, b"Hello, world!"); 93 | 94 | test_utils::continue_to_end(&target); 95 | 96 | Ok(()) 97 | } 98 | -------------------------------------------------------------------------------- /tests/readregs.rs: -------------------------------------------------------------------------------- 1 | //! This is a simple test to read registers from a child process. 2 | 3 | mod test_utils; 4 | 5 | static BIN_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/testees/hello"); 6 | 7 | #[cfg(all(target_os = "linux", target_arch = "x86_64"))] 8 | #[test] 9 | fn read_regs() -> headcrab::CrabResult<()> { 10 | use gimli::X86_64; 11 | use headcrab::target::Registers; 12 | 13 | test_utils::ensure_testees(); 14 | 15 | let target = test_utils::launch(BIN_PATH); 16 | 17 | let regs = target.main_thread()?.read_regs()?; 18 | 19 | // Assert that the register values match the expected initial values on Linux 20 | assert_eq!(regs.reg_for_dwarf(X86_64::R15).unwrap(), 0); 21 | assert_eq!(regs.reg_for_dwarf(X86_64::R14).unwrap(), 0); 22 | assert_eq!(regs.reg_for_dwarf(X86_64::R13).unwrap(), 0); 23 | assert_eq!(regs.reg_for_dwarf(X86_64::R12).unwrap(), 0); 24 | assert_eq!(regs.reg_for_dwarf(X86_64::RBP).unwrap(), 0); 25 | assert_eq!(regs.reg_for_dwarf(X86_64::RBX).unwrap(), 0); 26 | assert_eq!(regs.reg_for_dwarf(X86_64::R11).unwrap(), 0); 27 | assert_eq!(regs.reg_for_dwarf(X86_64::R10).unwrap(), 0); 28 | assert_eq!(regs.reg_for_dwarf(X86_64::R9).unwrap(), 0); 29 | assert_eq!(regs.reg_for_dwarf(X86_64::R8).unwrap(), 0); 30 | assert_eq!(regs.reg_for_dwarf(X86_64::RAX).unwrap(), 0); 31 | assert_eq!(regs.reg_for_dwarf(X86_64::RCX).unwrap(), 0); 32 | assert_eq!(regs.reg_for_dwarf(X86_64::RDX).unwrap(), 0); 33 | assert_eq!(regs.reg_for_dwarf(X86_64::RSI).unwrap(), 0); 34 | assert_eq!(regs.reg_for_dwarf(X86_64::RDI).unwrap(), 0); 35 | 36 | // https://github.com/torvalds/linux/blob/f359287765c04711ff54fbd11645271d8e5ff763/arch/x86/entry/syscalls/syscall_64.tbl#L70 37 | let user_regs: libc::user_regs_struct = regs.into(); 38 | 39 | const X86_64_SYSCALL_EXECVE: u64 = 59; 40 | assert_eq!(user_regs.orig_rax, X86_64_SYSCALL_EXECVE); 41 | 42 | //assert_eq!(regs.rip, 140188621074576); // non-deterministic 43 | assert_eq!(regs.reg_for_dwarf(X86_64::CS).unwrap(), 51); 44 | 45 | // IF=EI: interrupt enable flag = enable interrupts 46 | const EFLAGS_EI: u64 = 0x0200; 47 | assert_eq!(regs.reg_for_dwarf(X86_64::RFLAGS).unwrap(), EFLAGS_EI); 48 | 49 | //assert_eq!(regs.rsp, 140735406980896); // non-deterministic 50 | assert_eq!(regs.reg_for_dwarf(X86_64::SS).unwrap(), 43); 51 | assert_eq!(regs.reg_for_dwarf(X86_64::FS_BASE).unwrap(), 0); 52 | assert_eq!(regs.reg_for_dwarf(X86_64::GS_BASE).unwrap(), 0); 53 | 54 | assert_eq!(regs.reg_for_dwarf(X86_64::DS).unwrap(), 0); 55 | assert_eq!(regs.reg_for_dwarf(X86_64::ES).unwrap(), 0); 56 | assert_eq!(regs.reg_for_dwarf(X86_64::FS).unwrap(), 0); 57 | assert_eq!(regs.reg_for_dwarf(X86_64::GS).unwrap(), 0); 58 | 59 | test_utils::continue_to_end(&target); 60 | 61 | Ok(()) 62 | } 63 | -------------------------------------------------------------------------------- /tests/runtime_breakpoint.rs: -------------------------------------------------------------------------------- 1 | //! This is a basic test for the software breakpoints 2 | //! Here we take a simple rust program, and instrument it using headcrab. 3 | //! Setting breakpoints at a symbol's address, and then checking that we 4 | //! do have the expected state once the breakpoint is hit. 5 | 6 | mod test_utils; 7 | 8 | #[cfg(target_os = "linux")] 9 | use headcrab::{ 10 | symbol::RelocatedDwarf, 11 | target::{Registers, UnixTarget}, 12 | CrabResult, 13 | }; 14 | 15 | static BIN_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/testees/hello"); 16 | static LOOPING_BIN_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/testees/loop"); 17 | 18 | // FIXME: this should be an internal impl detail 19 | #[cfg(target_os = "macos")] 20 | static MAC_DSYM_PATH: &str = concat!( 21 | env!("CARGO_MANIFEST_DIR"), 22 | "/tests/testees/hello.dSYM/Contents/Resources/DWARF/hello" 23 | ); 24 | 25 | // FIXME: Running this test just for linux because of privileges issue on macOS. Enable for everything after fixing. 26 | #[cfg(target_os = "linux")] 27 | #[test] 28 | fn runtime_breakpoint() -> CrabResult<()> { 29 | test_utils::ensure_testees(); 30 | 31 | let target = test_utils::launch(BIN_PATH); 32 | let debuginfo = RelocatedDwarf::from_maps(&target.memory_maps()?)?; 33 | let main_addr = debuginfo 34 | .get_symbol_address("main") 35 | .expect("No 'main' symbol"); 36 | let breakpoint = target 37 | .set_breakpoint(main_addr) 38 | .expect("Cannot set breakpoint"); 39 | 40 | assert!(breakpoint.is_armed()); 41 | 42 | // run the program 43 | target.unpause()?; 44 | // have we hit the breakpoint ? 45 | let ip = target.read_regs()?.ip(); 46 | assert_eq!(ip as usize, main_addr); 47 | let status = target.step()?; 48 | assert_eq!(status, test_utils::ws_sigtrap(&target)); 49 | assert!(breakpoint.is_armed()); 50 | 51 | test_utils::continue_to_end(&target); 52 | Ok(()) 53 | } 54 | 55 | #[cfg(target_os = "linux")] 56 | #[test] 57 | fn multiple_breakpoints() -> CrabResult<()> { 58 | test_utils::ensure_testees(); 59 | 60 | let target = test_utils::launch(BIN_PATH); 61 | let debuginfo = RelocatedDwarf::from_maps(&target.memory_maps()?)?; 62 | let main_addr = debuginfo 63 | .get_symbol_address("main") 64 | .expect("No 'main' symbol"); 65 | // set a breakpoint at main 66 | let breakpoint = target.set_breakpoint(main_addr)?; 67 | assert!(breakpoint.is_armed()); 68 | // Test that duplicate breakpoints do no harm 69 | let breakpoint2 = target.set_breakpoint(main_addr)?; 70 | 71 | // make sure we hit the breakpoint 72 | let status = target.unpause()?; 73 | assert_eq!(status, test_utils::ws_sigtrap(&target)); 74 | 75 | let mut regs = target.main_thread()?.read_regs()?; 76 | assert_eq!(regs.ip() as usize, main_addr); 77 | 78 | // Let's go a few instructions back and see if disabling the breakpoint works 79 | regs.set_ip(regs.ip() - 3); 80 | 81 | target.main_thread()?.write_regs(regs)?; 82 | breakpoint2.disable()?; 83 | assert!(!breakpoint.is_armed()); 84 | 85 | regs.set_ip(regs.ip() + 3); 86 | target.main_thread()?.write_regs(regs)?; 87 | 88 | // Same, let's check that creating a new breakpoint and unsetting it right away 89 | // disarms the trap 90 | let mut bp3 = target.set_breakpoint(main_addr + 4)?; 91 | bp3.set()?; 92 | bp3.disable()?; 93 | test_utils::continue_to_end(&target); 94 | Ok(()) 95 | } 96 | 97 | #[cfg(target_os = "linux")] 98 | #[test] 99 | fn looping_breakpoint() -> CrabResult<()> { 100 | test_utils::ensure_testees(); 101 | 102 | let target = test_utils::launch(LOOPING_BIN_PATH); 103 | let debuginfo = RelocatedDwarf::from_maps(&target.memory_maps()?)?; 104 | let bp_addr = debuginfo 105 | .get_symbol_address("breakpoint") 106 | .expect("No 'breakpoint' symbol"); 107 | // set the breakpoint 108 | let breakpoint = target.set_breakpoint(bp_addr)?; 109 | assert!(breakpoint.is_armed()); 110 | assert!(breakpoint.is_enabled()); 111 | 112 | // The testee should call the `breakpoint()` function 8 times 113 | // make sure we hit the breakpoint each time 114 | for _ in 0..8 { 115 | let status = target.unpause()?; 116 | assert_eq!(status, test_utils::ws_sigtrap(&target)); 117 | 118 | let regs = target.read_regs()?; 119 | assert_eq!(regs.ip() as usize, bp_addr); 120 | assert!(!breakpoint.is_armed()); 121 | } 122 | test_utils::continue_to_end(&target); 123 | Ok(()) 124 | } 125 | 126 | #[cfg(target_os = "linux")] 127 | #[test] 128 | // Make sure that calling single_step advances the P.C by 1, 129 | // and gives back control 130 | fn single_step() -> CrabResult<()> { 131 | test_utils::ensure_testees(); 132 | let target = test_utils::launch(BIN_PATH); 133 | let debuginfo = RelocatedDwarf::from_maps(&target.memory_maps()?)?; 134 | let main_addr = debuginfo 135 | .get_symbol_address("main") 136 | .expect("No 'main' symbol in target program."); 137 | let _ = target.set_breakpoint(main_addr)?; 138 | 139 | // start the program 140 | target.unpause()?; 141 | // Order of instructions: 142 | //
, , , , 143 | let offsets = [0, 1, 4, 8, 11, 17]; 144 | for offset in offsets.iter() { 145 | let rip = test_utils::current_ip(&target); 146 | assert_eq!(rip, (main_addr as u64 + offset), "Steps"); 147 | let status = target.step()?; 148 | assert_eq!(status, test_utils::ws_sigtrap(&target), "status"); 149 | } 150 | test_utils::continue_to_end(&target); 151 | Ok(()) 152 | } 153 | -------------------------------------------------------------------------------- /tests/source.rs: -------------------------------------------------------------------------------- 1 | //! This is a simple test to get the source line from a child process. 2 | 3 | mod test_utils; 4 | 5 | #[cfg(target_os = "linux")] 6 | use headcrab::{symbol::RelocatedDwarf, target::UnixTarget, CrabResult}; 7 | 8 | static BIN_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/testees/hello"); 9 | 10 | // FIXME: this should be an internal impl detail 11 | #[cfg(target_os = "macos")] 12 | static MAC_DSYM_PATH: &str = concat!( 13 | env!("CARGO_MANIFEST_DIR"), 14 | "/tests/testees/hello.dSYM/Contents/Resources/DWARF/hello" 15 | ); 16 | 17 | // FIXME: Running this test just for linux because of privileges issue on macOS. Enable for everything after fixing. 18 | #[cfg(target_os = "linux")] 19 | #[test] 20 | fn source() -> CrabResult<()> { 21 | test_utils::ensure_testees(); 22 | 23 | let target = test_utils::launch(BIN_PATH); 24 | let debuginfo = RelocatedDwarf::from_maps(&target.memory_maps()?)?; 25 | 26 | test_utils::patch_breakpoint(&target, &debuginfo); 27 | 28 | // First breakpoint 29 | target.unpause()?; 30 | let ip = target.read_regs()?.ip(); 31 | println!("{:08x}", ip); 32 | assert_eq!( 33 | debuginfo.get_address_symbol_name(ip as usize).as_deref(), 34 | Some("breakpoint") 35 | ); 36 | 37 | let source_location = debuginfo.source_location(ip as usize)?.unwrap(); 38 | assert!(source_location.0.ends_with("x86/sse2.rs")); 39 | 40 | test_utils::continue_to_end(&target); 41 | 42 | Ok(()) 43 | } 44 | -------------------------------------------------------------------------------- /tests/syscall.rs: -------------------------------------------------------------------------------- 1 | //! This is a simple test to running a syscall in a child process. 2 | 3 | mod test_utils; 4 | 5 | #[cfg(target_os = "linux")] 6 | use headcrab::{target::UnixTarget, CrabResult}; 7 | 8 | static BIN_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/testees/hello"); 9 | 10 | // FIXME: Running this test just for linux because of privileges issue on macOS. Enable for everything after fixing. 11 | #[cfg(target_os = "linux")] 12 | #[test] 13 | fn syscall() -> CrabResult<()> { 14 | test_utils::ensure_testees(); 15 | 16 | let target = test_utils::launch(BIN_PATH); 17 | 18 | println!( 19 | "{}\n", 20 | std::fs::read_to_string(format!("/proc/{}/maps", target.pid()))? 21 | ); 22 | 23 | let len = 1 << 20; 24 | let addr = target 25 | .mmap( 26 | 0 as *mut _, 27 | len, 28 | libc::PROT_READ | libc::PROT_WRITE, 29 | libc::MAP_ANONYMOUS | libc::MAP_PRIVATE, 30 | 0, 31 | 0, 32 | ) 33 | .unwrap(); 34 | assert!(target 35 | .memory_maps()? 36 | .iter() 37 | .any(|map| map.address.0 == addr)); 38 | 39 | // unmap the previously mapped memory 40 | // and check that it is no longer in the mapped memory list. 41 | dbg!("munmapping..."); 42 | target.munmap(addr as *mut _, len)?; 43 | dbg!("munmapped!"); 44 | 45 | assert!(target 46 | .memory_maps()? 47 | .iter() 48 | .all(|map| map.address.0 != addr)); 49 | 50 | test_utils::continue_to_end(&target); 51 | 52 | Ok(()) 53 | } 54 | -------------------------------------------------------------------------------- /tests/test_utils.rs: -------------------------------------------------------------------------------- 1 | use std::{process::Command, sync::Once}; 2 | 3 | #[cfg(target_os = "linux")] 4 | use headcrab::{ 5 | symbol::RelocatedDwarf, 6 | target::{LinuxTarget, UnixTarget}, 7 | }; 8 | 9 | static TESTEES_BUILD: Once = Once::new(); 10 | 11 | /// Ensure that all testees are built. 12 | pub fn ensure_testees() { 13 | TESTEES_BUILD.call_once(|| { 14 | let status = std::process::Command::new("make") 15 | .current_dir(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/testees")) 16 | .spawn() 17 | .unwrap() 18 | .wait() 19 | .unwrap(); 20 | assert!(status.success()); 21 | }); 22 | } 23 | 24 | #[cfg(target_os = "linux")] 25 | #[allow(dead_code)] 26 | pub fn launch(path: &str) -> LinuxTarget { 27 | let (target, status) = LinuxTarget::launch(Command::new(path)).unwrap(); 28 | match status { 29 | nix::sys::wait::WaitStatus::Stopped(_, nix::sys::signal::SIGTRAP) => {} 30 | _ => panic!("Status: {:?}", status), 31 | } 32 | target 33 | } 34 | 35 | #[cfg(target_os = "linux")] 36 | #[allow(dead_code)] 37 | pub fn continue_to_end(target: &LinuxTarget) { 38 | match target.unpause().expect("Failed to unpause target") { 39 | nix::sys::wait::WaitStatus::Exited(_, 0) => {} 40 | status => panic!("Unexpected signal: Status: {:?}", status), 41 | } 42 | } 43 | 44 | /// Turn the `pause` instruction inside the `breakpoint` function into a breakpoint. 45 | #[cfg(target_os = "linux")] 46 | #[allow(dead_code)] 47 | pub fn patch_breakpoint(target: &LinuxTarget, debuginfo: &RelocatedDwarf) { 48 | // Get the address of the `breakpoint` function. 49 | let breakpoint_addr = debuginfo.get_symbol_address("breakpoint").unwrap() + 4 /* prologue */; 50 | // Write breakpoint to the `breakpoint` function. 51 | let mut pause_inst = 0 as libc::c_ulong; 52 | unsafe { 53 | target 54 | .read() 55 | .read(&mut pause_inst, breakpoint_addr) 56 | .apply() 57 | .unwrap(); 58 | } 59 | // pause (rep nop); ... 60 | assert_eq!(&pause_inst.to_ne_bytes()[0..2], &[0xf3, 0x90]); 61 | let mut breakpoint_inst = pause_inst.to_ne_bytes(); 62 | // int3; nop; ... 63 | breakpoint_inst[0] = 0xcc; 64 | nix::sys::ptrace::write( 65 | target.pid(), 66 | breakpoint_addr as *mut _, 67 | libc::c_ulong::from_ne_bytes(breakpoint_inst) as *mut _, 68 | ) 69 | .unwrap(); 70 | } 71 | 72 | #[cfg(target_os = "linux")] 73 | #[allow(dead_code)] 74 | pub fn current_ip(target: &LinuxTarget) -> u64 { 75 | target.read_regs().expect("could not read registers").ip() 76 | } 77 | 78 | #[cfg(target_os = "linux")] 79 | #[allow(dead_code)] 80 | pub fn ws_sigtrap(target: &LinuxTarget) -> nix::sys::wait::WaitStatus { 81 | nix::sys::wait::WaitStatus::Stopped(target.pid(), nix::sys::signal::SIGTRAP) 82 | } 83 | -------------------------------------------------------------------------------- /tests/testees/.gitignore: -------------------------------------------------------------------------------- 1 | /loop 2 | /hello 3 | /longer_hello 4 | /hw_breakpoint 5 | /known_asm 6 | -------------------------------------------------------------------------------- /tests/testees/Makefile: -------------------------------------------------------------------------------- 1 | CC = rustc 2 | CC_FLAGS = -g -Copt-level=2 -Cforce-frame-pointers=yes 3 | AS = as 4 | AS_FLAGS = 5 | LD = gcc 6 | LD_FLAGS = 7 | SRCS = $(wildcard *.rs) $(wildcard *.S) 8 | BINS = $(patsubst %.S,%,$(patsubst %.rs,%,$(SRCS))) 9 | 10 | .PHONY: all 11 | all: $(BINS) 12 | 13 | %: %.rs 14 | $(CC) $(CC_FLAGS) -o $@ $^ 15 | 16 | %: %.S 17 | $(AS) $(AS_FLAGS) -o $@.o $^ 18 | $(LD) $(LD_FLAGS) -o $@ $@.o 19 | rm $@.o 20 | 21 | clean: 22 | rm -f $(BINS) 23 | -------------------------------------------------------------------------------- /tests/testees/hello.rs: -------------------------------------------------------------------------------- 1 | static STATICVAR: &str = "Hello, world!\n"; 2 | 3 | #[no_mangle] 4 | #[inline(never)] 5 | fn breakpoint() { 6 | unsafe { 7 | core::arch::x86_64::_mm_pause(); 8 | } 9 | } 10 | 11 | #[inline(never)] 12 | fn black_box(v: T) { 13 | unsafe { 14 | std::ptr::read_volatile(&v); 15 | } 16 | } 17 | 18 | struct A { 19 | b: u8, 20 | c: &'static u8, 21 | } 22 | 23 | pub fn main() { 24 | let var = 42usize; 25 | let reg_var = 43usize; 26 | { 27 | let mut temp = 100usize; 28 | black_box(&mut temp); 29 | } 30 | black_box(reg_var); 31 | let a = A { b: 42, c: &43 }; 32 | black_box(&a); 33 | breakpoint(); 34 | black_box(reg_var); 35 | println!("{} {}", STATICVAR, var); 36 | } 37 | -------------------------------------------------------------------------------- /tests/testees/hw_breakpoint.rs: -------------------------------------------------------------------------------- 1 | #[no_mangle] 2 | static mut STATICVAR: u8 = 100; 3 | #[no_mangle] 4 | static mut STATICVAR2: u8 = 100; 5 | #[no_mangle] 6 | static mut STATICVAR3: u8 = 100; 7 | 8 | pub fn main() { 9 | unsafe { 10 | std::ptr::write_volatile(&mut STATICVAR, 200); 11 | std::ptr::write_volatile(&mut STATICVAR2, 200); 12 | std::ptr::write_volatile(&mut STATICVAR3, 200); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/testees/known_asm.S: -------------------------------------------------------------------------------- 1 | .globl main 2 | main: 3 | # Breakpoint trap on Linux 4 | # FIXME use right interrupt on macOS 5 | int $3 6 | nop 7 | int $3 8 | mov $0, %rax 9 | ret 10 | -------------------------------------------------------------------------------- /tests/testees/longer_hello.rs: -------------------------------------------------------------------------------- 1 | static STATICVAR: &str = "Hello, world!\n"; 2 | 3 | pub fn main() { 4 | 5 | use std::{thread, time}; 6 | thread::sleep(time::Duration::from_millis(100)); 7 | 8 | println!("{}", STATICVAR); 9 | } 10 | -------------------------------------------------------------------------------- /tests/testees/loop.rs: -------------------------------------------------------------------------------- 1 | #[inline(never)] 2 | #[no_mangle] 3 | fn breakpoint(counter: u8) -> () { 4 | println!("- {} -", counter); 5 | } 6 | 7 | fn main() { 8 | let breaks = 8; 9 | for i in 0..breaks { 10 | breakpoint(i); 11 | } 12 | println!("Called `breakpoint` {} times.", breaks); 13 | } 14 | -------------------------------------------------------------------------------- /tests/unwind_stack.rs: -------------------------------------------------------------------------------- 1 | //! This is a simple test to read memory from a child process. 2 | 3 | mod test_utils; 4 | 5 | #[cfg(target_os = "linux")] 6 | use headcrab::{symbol::RelocatedDwarf, target::UnixTarget, CrabResult}; 7 | 8 | static BIN_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/testees/hello"); 9 | 10 | // FIXME: this should be an internal impl detail 11 | #[cfg(target_os = "macos")] 12 | static MAC_DSYM_PATH: &str = concat!( 13 | env!("CARGO_MANIFEST_DIR"), 14 | "/tests/testees/hello.dSYM/Contents/Resources/DWARF/hello" 15 | ); 16 | 17 | // FIXME: Running this test just for linux because of privileges issue on macOS. Enable for everything after fixing. 18 | #[cfg(target_os = "linux")] 19 | #[test] 20 | fn unwind_stack() -> CrabResult<()> { 21 | test_utils::ensure_testees(); 22 | 23 | let target = test_utils::launch(BIN_PATH); 24 | 25 | println!("{:#?}", target.memory_maps()?); 26 | let debuginfo = RelocatedDwarf::from_maps(&target.memory_maps()?)?; 27 | 28 | test_utils::patch_breakpoint(&target, &debuginfo); 29 | 30 | // Wait for the breakpoint to get hit. 31 | target.unpause().unwrap(); 32 | 33 | let regs = target.read_regs().unwrap(); 34 | 35 | // Read stack 36 | let mut stack: [usize; 256] = [0; 256]; 37 | unsafe { 38 | target.read().read(&mut stack, regs.sp() as usize).apply()?; 39 | } 40 | 41 | let call_stack: Vec<_> = 42 | headcrab::symbol::unwind::naive_unwinder(&debuginfo, &stack[..], regs.ip() as usize) 43 | .map(|func| { 44 | debuginfo 45 | .get_address_symbol_name(func) 46 | .unwrap_or_else(|| "".to_string()) 47 | }) 48 | .collect(); 49 | 50 | let expected = &["breakpoint", "_ZN5hello4main17h"]; 51 | 52 | test_backtrace(call_stack, expected); 53 | 54 | let call_stack: Vec<_> = headcrab::symbol::unwind::frame_pointer_unwinder( 55 | &debuginfo, 56 | &stack[..], 57 | regs.ip() as usize, 58 | regs.sp() as usize, 59 | regs.bp().unwrap() as usize, 60 | ) 61 | .map(|func| { 62 | debuginfo 63 | .get_address_symbol_name(func) 64 | .unwrap_or_else(|| "".to_string()) 65 | }) 66 | .collect(); 67 | 68 | println!("{:?}", call_stack); 69 | 70 | let expected = &["breakpoint", "_ZN5hello4main17h"]; 71 | 72 | test_backtrace(call_stack, expected); 73 | 74 | target.unpause()?; 75 | 76 | Ok(()) 77 | } 78 | 79 | fn test_backtrace(real: Vec, expected: &[&str]) { 80 | println!("\nReal: {:?}\nExpected: {:?}", real, expected); 81 | let mut real = real.into_iter(); 82 | let mut expected = expected.iter(); 83 | loop { 84 | match (real.next(), expected.next()) { 85 | (Some(real), Some(expected)) => assert!( 86 | real.starts_with(expected), 87 | "`{}` doesn't start with `{}`", 88 | real, 89 | expected 90 | ), 91 | (Some(_real), None) => break, // Ignore extra frames 92 | (None, Some(expected)) => panic!("Missing frame {:?}", expected), 93 | (None, None) => break, 94 | } 95 | } 96 | } 97 | --------------------------------------------------------------------------------