firedbg_rust_debugger/
lib.rs

1//! ## FireDBG Debugger Engine for Rust
2//!
3//! Based on [lldb](https://lldb.llvm.org/).
4//!
5//! This library is semver exempt. The version number is intended to track rustc's version.
6//!
7//! ### Debugger Config
8//!
9//! Configuration can be set via the `FIREDBG_RUST_CONFIG` environment variable. e.g. `FIREDBG_RUST_CONFIG=MAX_ARRAY_SIZE=100;DONT_TRACE_ALLOCATION`
10//!
11//! | Key | Type | Description |
12//! |:---:|:----:|:-----------:|
13//! | `MAX_ARRAY_SIZE` | `usize` | Maximum number of items in array, string and other containers |
14//! | `RECURSIVE_DEREF_LIMIT` | `usize` | Recursive limit; i.e. this limits the depth of a binary tree |
15//! | `KEEP_HASH_ORDER` | `bool` | If set, don't sort hash maps by hash key |
16//! | `DONT_TRACE_ALLOCATION` | `bool` | If set, don't trace heap allocations |
17//!
18//! ### Instruction Set
19//!
20//! Currently supports x64 (aka amd64) and arm64 (aka aarch64). There are quite some assembly parsing and register fiddling catered to each platform. There are some assumptions to pointers being 8 bytes in the codebase. It requires considerable effort to support a new architecture, but we are open to commercial collaboration.
21//!
22//! ### Operating Systems
23//!
24//! There is no OS specific code for now. lldb is used on both Linux and macOS. But on macOS we'd connect to the host's `lldb-server`.
25//! If we decided to support Windows natively, we'd need to make a Windows backend.
26//!
27//! The `debugger` binary must be compiled for the same platform as the target binary.
28//! In addition, we assume they both use the same `rustc` (not necessarily exactly the same, but ABI compatible with each other).
29//!
30//! ### Standard Library Types
31//!
32//! We intend to build-in all handling of standard library types. For `HashMap`, [frozen-hashbrown](https://github.com/tyt2y3/frozen-hashbrown) is used.
33//! In the future, we want to open scripting interface (maybe via [rhai](https://rhai.rs/)) to handle vendor library types (e.g. `DateTime`, `Decimal`), in a similar sense to Natvis.
34//!
35//! ### Binary Value Format
36//!
37//! The format for serializing Rust values can be best understood by reading `SourceReader::read_values()` in `reader.rs`.
38//! It should be pretty straight-forward, the only tricky part is `ReaderContext` which is for resolving memory references.
39//!
40//! ### Return Value Capture
41//!
42//! This is highly architecture specific. We try to capture the return value at the moment the function returns, i.e. at the `ret` instruction. Not everything is on the stack, sometimes the return value will be passed through registers.
43//!
44//! According to Rust's ABI convention, it means if the return value is:
45//!
46//! 1. One or two primitives, each no bigger than 64 bits. This includes `(i64, i64)` and `struct { a: i64, b: i64 }`.
47//! 2. One `i128` / `u128`; will be split into `rax` / `rdx`
48//! 3. One or two `f32` / `f64`; will be put in `xmm0` / `xmm1`
49//! 4. `Option<T>` and `Result<T, E>`; where `T` & `E` is no bigger than 64 bits
50//!
51//!     The enum discriminant is in `rax`, where:
52//!
53//!     | Type | T | E |
54//!     |:----:|:-:|:-:|
55//!     | `Option` | `Some = 1` | `None = 0` |
56//!     | `Result` | `Ok = 0` | `Err = 1` |
57//!
58//!     and the `T` / `E` will be in `rdx`.
59//!
60//! Unfortunately it is much more complicated than [the above summary](https://darkcoding.net/software/rust-multiple-return-types/). Right now the implementation is all mess, and completely based on heuristics.
61//! If we got the chance to do this properly, may be we can generate rust code and inspect the assembly (systematically).
62//! Say our return type is `(T, F)`:
63//!
64//! ```ignore
65//! fn probe() -> (T, F) { todo!() }
66//! fn extractor() {
67//!     let res = probe();
68//!     std::hint::black_box(&res);
69//! }
70//! ```
71//!
72//! If we disassemble the binary, we should see:
73//!
74//! ```ignore
75//! extractor:
76//! call probe
77//! mov ..
78//! call black_box
79//! ```
80//!
81//! So between `probe` and `black_box`, there would be some memory fiddling, which would end up writing the full struct onto the stack, where the address will then be stored in `rax` / `x0`.
82//!
83//! There should be better ways to do this, if you have an idea please open a discussion thread!
84//!
85//! ### Heap allocation
86//!
87//! Right now it is still a WIP. We can trace all `Box`, `Rc`, `Arc` allocations now, so that we are able to extract the content of `Box<dyn T>`.
88//! The TODO is to trace deallocations and output the information to a dedicated event stream.
89#![cfg_attr(docsrs, feature(doc_cfg))]
90#![deny(
91    missing_debug_implementations,
92    clippy::print_stderr,
93    clippy::print_stdout
94)]
95
96mod bytes;
97#[cfg(feature = "debugger")]
98mod debugger;
99mod event;
100mod reader;
101mod rvalue;
102pub mod typename;
103mod value;
104pub mod version;
105
106pub use bytes::*;
107#[cfg(feature = "debugger")]
108pub use debugger::*;
109pub use event::*;
110pub use reader::*;
111use rvalue::*;
112use value::*;
113
114pub use firedbg_protocol::{breakpoint::*, event::*, info::*, source::*, value::*};