Skip to main content

crashrustler/unwind/
arch.rs

1//! Architecture-specific DWARF register maps and compact unwind encoding decoders.
2
3use super::registers::{arm64, x86_64};
4
5// ==========================================================================
6// DWARF register number to name (for debugging)
7// ==========================================================================
8
9/// Returns the register name for an ARM64 DWARF register number.
10pub fn arm64_reg_name(dwarf_reg: u16) -> &'static str {
11    match dwarf_reg {
12        0..=28 => {
13            const NAMES: [&str; 29] = [
14                "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8", "x9", "x10", "x11", "x12",
15                "x13", "x14", "x15", "x16", "x17", "x18", "x19", "x20", "x21", "x22", "x23", "x24",
16                "x25", "x26", "x27", "x28",
17            ];
18            NAMES[dwarf_reg as usize]
19        }
20        29 => "fp",
21        30 => "lr",
22        31 => "sp",
23        32 => "pc",
24        _ => "?",
25    }
26}
27
28/// Returns the register name for an x86_64 DWARF register number.
29pub fn x86_64_reg_name(dwarf_reg: u16) -> &'static str {
30    match dwarf_reg {
31        0 => "rax",
32        1 => "rdx",
33        2 => "rcx",
34        3 => "rbx",
35        4 => "rsi",
36        5 => "rdi",
37        6 => "rbp",
38        7 => "rsp",
39        8 => "r8",
40        9 => "r9",
41        10 => "r10",
42        11 => "r11",
43        12 => "r12",
44        13 => "r13",
45        14 => "r14",
46        15 => "r15",
47        16 => "rip",
48        49 => "rflags",
49        _ => "?",
50    }
51}
52
53// ==========================================================================
54// Compact unwind encoding constants
55// ==========================================================================
56
57/// ARM64 compact unwind mode (bits 27-24).
58pub mod arm64_compact {
59    pub const MODE_MASK: u32 = 0x0F00_0000;
60    pub const MODE_FRAMELESS: u32 = 0x0200_0000;
61    pub const MODE_DWARF: u32 = 0x0300_0000;
62    pub const MODE_FRAME: u32 = 0x0400_0000;
63
64    /// Saved register pair bit positions for frame-based unwinding.
65    /// Bits 12-0 encode which register pairs are saved at FP-offset.
66    /// Each pair of registers is: (X19,X20), (X21,X22), ..., (X27,X28), (D8,D9), ..., (D14,D15)
67    pub const FRAME_REG_PAIRS: &[(u16, u16)] = &[
68        (19, 20), // bit 0
69        (21, 22), // bit 1
70        (23, 24), // bit 2
71        (25, 26), // bit 3
72        (27, 28), // bit 4
73                  // bits 5-8 are D8-D15 pairs (FP registers, not tracked)
74    ];
75
76    /// Frameless: stack size in 16-byte units from bits 23-12.
77    pub const FRAMELESS_STACK_SIZE_MASK: u32 = 0x00FF_F000;
78    pub const FRAMELESS_STACK_SIZE_SHIFT: u32 = 12;
79
80    /// DWARF: FDE offset in bits 23-0.
81    pub const DWARF_FDE_OFFSET_MASK: u32 = 0x00FF_FFFF;
82}
83
84/// x86_64 compact unwind mode (bits 27-24).
85pub mod x86_64_compact {
86    pub const MODE_MASK: u32 = 0x0F00_0000;
87    pub const MODE_FRAME: u32 = 0x0100_0000;
88    pub const MODE_FRAMELESS_IMMEDIATE: u32 = 0x0200_0000;
89    pub const MODE_FRAMELESS_INDIRECT: u32 = 0x0300_0000;
90    pub const MODE_DWARF: u32 = 0x0400_0000;
91
92    /// Frame-based: offset from RBP to first saved register, in 8-byte units (bits 23-16).
93    pub const FRAME_OFFSET_MASK: u32 = 0x00FF_0000;
94    pub const FRAME_OFFSET_SHIFT: u32 = 16;
95
96    /// Frame-based: saved register bits 14-0.
97    /// Each 3-bit field encodes which register is saved at that slot.
98    pub const FRAME_REG_MASK: u32 = 0x0000_7FFF;
99
100    /// Frameless immediate: stack size in 8-byte units from bits 23-16.
101    pub const FRAMELESS_STACK_SIZE_MASK: u32 = 0x00FF_0000;
102    pub const FRAMELESS_STACK_SIZE_SHIFT: u32 = 16;
103    /// Register count from bits 15-10.
104    pub const FRAMELESS_REG_COUNT_MASK: u32 = 0x0000_FC00;
105    pub const FRAMELESS_REG_COUNT_SHIFT: u32 = 10;
106    /// Permutation encoding from bits 9-0.
107    pub const FRAMELESS_PERMUTATION_MASK: u32 = 0x0000_03FF;
108
109    /// Frameless indirect: offset to stack size in function body from bits 23-16.
110    pub const INDIRECT_STACK_OFFSET_MASK: u32 = 0x00FF_0000;
111    pub const INDIRECT_STACK_OFFSET_SHIFT: u32 = 16;
112    /// Stack adjust from bits 15-13.
113    pub const INDIRECT_STACK_ADJUST_MASK: u32 = 0x0000_E000;
114    pub const INDIRECT_STACK_ADJUST_SHIFT: u32 = 13;
115
116    /// DWARF: FDE offset in bits 23-0.
117    pub const DWARF_FDE_OFFSET_MASK: u32 = 0x00FF_FFFF;
118
119    /// Register encoding for x86_64 frame-based save slots.
120    /// 3-bit value → DWARF register number.
121    pub const FRAME_REG_MAP: [u16; 7] = [
122        0, // 0 = none
123        super::x86_64::RBX,
124        super::x86_64::R12,
125        super::x86_64::R13,
126        super::x86_64::R14,
127        super::x86_64::R15,
128        super::x86_64::RBP,
129    ];
130}
131
132// ==========================================================================
133// Compact unwind decoding helpers
134// ==========================================================================
135
136/// Decodes ARM64 frameless permutation encoding.
137/// Returns the DWARF register numbers of saved register pairs.
138pub fn arm64_decode_frameless_regs(encoding: u32) -> Vec<u16> {
139    // Bits 19-12: register permutation encoding
140    // This encodes up to 5 register pairs using a factorial numbering system
141    let _stack_size = ((encoding & arm64_compact::FRAMELESS_STACK_SIZE_MASK)
142        >> arm64_compact::FRAMELESS_STACK_SIZE_SHIFT) as u64
143        * 16;
144    // For ARM64 frameless, the permutation encoding is simpler:
145    // it's a bitmask of which register pairs to save, same as frame-based
146    let mut regs = Vec::new();
147    for (bit, &(r1, r2)) in arm64_compact::FRAME_REG_PAIRS.iter().enumerate() {
148        if encoding & (1 << bit) != 0 {
149            regs.push(r1);
150            regs.push(r2);
151        }
152    }
153    regs
154}
155
156/// Decodes x86_64 permutation encoding for frameless functions.
157/// Returns the DWARF register numbers in stack order (bottom to top).
158pub fn x86_64_decode_permutation(encoding: u32) -> Vec<u16> {
159    let reg_count = ((encoding & x86_64_compact::FRAMELESS_REG_COUNT_MASK)
160        >> x86_64_compact::FRAMELESS_REG_COUNT_SHIFT) as usize;
161    let permutation = (encoding & x86_64_compact::FRAMELESS_PERMUTATION_MASK) as usize;
162
163    if reg_count == 0 || reg_count > 6 {
164        return Vec::new();
165    }
166
167    // The permutation encoding uses a factorial numbering system.
168    // Possible registers: RBX, R12, R13, R14, R15, RBP (DWARF 3, 12, 13, 14, 15, 6)
169    let all_regs: [u16; 6] = [
170        x86_64::RBX,
171        x86_64::R12,
172        x86_64::R13,
173        x86_64::R14,
174        x86_64::R15,
175        x86_64::RBP,
176    ];
177
178    let mut available: Vec<u16> = all_regs[..reg_count].to_vec();
179    let mut result = Vec::with_capacity(reg_count);
180    let mut perm = permutation;
181
182    for i in (1..=reg_count).rev() {
183        let idx = perm / factorial(i - 1);
184        perm %= factorial(i - 1);
185        if idx < available.len() {
186            result.push(available.remove(idx));
187        }
188    }
189
190    result
191}
192
193fn factorial(n: usize) -> usize {
194    match n {
195        0 | 1 => 1,
196        2 => 2,
197        3 => 6,
198        4 => 24,
199        5 => 120,
200        _ => 720,
201    }
202}
203
204/// Returns the return address register for the given CPU type.
205pub fn return_address_register(is_arm: bool) -> u16 {
206    if is_arm { arm64::LR } else { x86_64::RIP }
207}
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212
213    #[test]
214    fn arm64_reg_names() {
215        assert_eq!(arm64_reg_name(0), "x0");
216        assert_eq!(arm64_reg_name(28), "x28");
217        assert_eq!(arm64_reg_name(29), "fp");
218        assert_eq!(arm64_reg_name(30), "lr");
219        assert_eq!(arm64_reg_name(31), "sp");
220        assert_eq!(arm64_reg_name(32), "pc");
221        assert_eq!(arm64_reg_name(99), "?");
222    }
223
224    #[test]
225    fn x86_64_reg_names() {
226        assert_eq!(x86_64_reg_name(0), "rax");
227        assert_eq!(x86_64_reg_name(6), "rbp");
228        assert_eq!(x86_64_reg_name(7), "rsp");
229        assert_eq!(x86_64_reg_name(16), "rip");
230        assert_eq!(x86_64_reg_name(49), "rflags");
231        assert_eq!(x86_64_reg_name(99), "?");
232    }
233
234    #[test]
235    fn x86_64_decode_permutation_empty() {
236        // reg_count = 0
237        let encoding = 0u32;
238        assert!(x86_64_decode_permutation(encoding).is_empty());
239    }
240
241    #[test]
242    fn x86_64_decode_permutation_single() {
243        // reg_count = 1, permutation = 0 → RBX
244        let encoding = (1 << x86_64_compact::FRAMELESS_REG_COUNT_SHIFT) | 0;
245        let regs = x86_64_decode_permutation(encoding);
246        assert_eq!(regs, vec![x86_64::RBX]);
247    }
248
249    #[test]
250    fn return_address_register_arm64() {
251        assert_eq!(return_address_register(true), arm64::LR);
252    }
253
254    #[test]
255    fn return_address_register_x86_64() {
256        assert_eq!(return_address_register(false), x86_64::RIP);
257    }
258}