Skip to main content

crashrustler/unwind/
registers.rs

1//! Architecture-abstracted register context indexed by DWARF register number.
2
3use crate::types::{CpuType, ThreadState};
4
5/// Maximum number of DWARF registers we track.
6const MAX_REGS: usize = 96;
7
8/// Register context holding register values indexed by DWARF register number.
9///
10/// Supports ARM64 and x86_64 DWARF register maps.
11#[derive(Debug, Clone)]
12pub struct RegisterContext {
13    regs: [Option<u64>; MAX_REGS],
14    pub cpu_type: CpuType,
15    pub is_64_bit: bool,
16}
17
18// ARM64 DWARF register numbers
19pub mod arm64 {
20    // X0-X28 = DWARF 0-28
21    pub const FP: u16 = 29; // X29
22    pub const LR: u16 = 30; // X30
23    pub const SP: u16 = 31;
24    pub const PC: u16 = 32; // not a real DWARF reg, we use it internally
25
26    /// Non-volatile registers: X19-X28, FP, LR
27    pub const NON_VOLATILE: &[u16] = &[19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30];
28}
29
30// x86_64 DWARF register numbers
31pub mod x86_64 {
32    pub const RAX: u16 = 0;
33    pub const RDX: u16 = 1;
34    pub const RCX: u16 = 2;
35    pub const RBX: u16 = 3;
36    pub const RSI: u16 = 4;
37    pub const RDI: u16 = 5;
38    pub const RBP: u16 = 6;
39    pub const RSP: u16 = 7;
40    pub const R8: u16 = 8;
41    pub const R9: u16 = 9;
42    pub const R10: u16 = 10;
43    pub const R11: u16 = 11;
44    pub const R12: u16 = 12;
45    pub const R13: u16 = 13;
46    pub const R14: u16 = 14;
47    pub const R15: u16 = 15;
48    pub const RIP: u16 = 16;
49    // DWARF 49 = RFLAGS (used by some producers)
50    pub const RFLAGS: u16 = 49;
51
52    /// Non-volatile registers: RBX, RBP, R12-R15
53    pub const NON_VOLATILE: &[u16] = &[3, 6, 12, 13, 14, 15];
54}
55
56impl RegisterContext {
57    /// Creates a new empty register context.
58    pub fn new(cpu_type: CpuType) -> Self {
59        Self {
60            regs: [None; MAX_REGS],
61            cpu_type,
62            is_64_bit: cpu_type.is_64_bit(),
63        }
64    }
65
66    /// Gets the program counter.
67    pub fn pc(&self) -> Option<u64> {
68        if self.is_arm() {
69            // Try internal PC slot first, then LR
70            self.regs[arm64::PC as usize].or(self.regs[arm64::LR as usize])
71        } else {
72            self.regs[x86_64::RIP as usize]
73        }
74    }
75
76    /// Gets the stack pointer.
77    pub fn sp(&self) -> Option<u64> {
78        if self.is_arm() {
79            self.regs[arm64::SP as usize]
80        } else {
81            self.regs[x86_64::RSP as usize]
82        }
83    }
84
85    /// Gets the frame pointer.
86    pub fn fp(&self) -> Option<u64> {
87        if self.is_arm() {
88            self.regs[arm64::FP as usize]
89        } else {
90            self.regs[x86_64::RBP as usize]
91        }
92    }
93
94    /// Gets the link register (ARM64 only, returns None for x86_64).
95    pub fn lr(&self) -> Option<u64> {
96        if self.is_arm() {
97            self.regs[arm64::LR as usize]
98        } else {
99            None
100        }
101    }
102
103    /// Gets a register by DWARF number.
104    pub fn get(&self, dwarf_reg: u16) -> Option<u64> {
105        if (dwarf_reg as usize) < MAX_REGS {
106            self.regs[dwarf_reg as usize]
107        } else {
108            None
109        }
110    }
111
112    /// Sets a register by DWARF number.
113    pub fn set(&mut self, dwarf_reg: u16, value: u64) {
114        if (dwarf_reg as usize) < MAX_REGS {
115            self.regs[dwarf_reg as usize] = Some(value);
116        }
117    }
118
119    /// Clears a register by DWARF number.
120    pub fn clear(&mut self, dwarf_reg: u16) {
121        if (dwarf_reg as usize) < MAX_REGS {
122            self.regs[dwarf_reg as usize] = None;
123        }
124    }
125
126    /// Sets the program counter.
127    pub fn set_pc(&mut self, value: u64) {
128        if self.is_arm() {
129            self.regs[arm64::PC as usize] = Some(value);
130        } else {
131            self.regs[x86_64::RIP as usize] = Some(value);
132        }
133    }
134
135    /// Sets the stack pointer.
136    pub fn set_sp(&mut self, value: u64) {
137        if self.is_arm() {
138            self.regs[arm64::SP as usize] = Some(value);
139        } else {
140            self.regs[x86_64::RSP as usize] = Some(value);
141        }
142    }
143
144    /// Sets the frame pointer.
145    pub fn set_fp(&mut self, value: u64) {
146        if self.is_arm() {
147            self.regs[arm64::FP as usize] = Some(value);
148        } else {
149            self.regs[x86_64::RBP as usize] = Some(value);
150        }
151    }
152
153    /// Clears all volatile registers, keeping only non-volatile ones + SP + PC.
154    pub fn clear_volatile(&mut self) {
155        let non_volatile = if self.is_arm() {
156            arm64::NON_VOLATILE
157        } else {
158            x86_64::NON_VOLATILE
159        };
160
161        let saved: Vec<(u16, Option<u64>)> =
162            non_volatile.iter().map(|&r| (r, self.get(r))).collect();
163        let sp = self.sp();
164        let pc = self.pc();
165
166        self.regs = [None; MAX_REGS];
167
168        for (r, v) in saved {
169            if let Some(val) = v {
170                self.set(r, val);
171            }
172        }
173        if let Some(v) = sp {
174            self.set_sp(v);
175        }
176        if let Some(v) = pc {
177            self.set_pc(v);
178        }
179    }
180
181    /// Converts from the existing `ThreadState` type used by CrashRustler.
182    pub fn from_thread_state(state: &ThreadState, cpu_type: CpuType) -> Option<Self> {
183        let mut ctx = Self::new(cpu_type);
184        let regs = &state.registers;
185        let flavor = state.flavor;
186
187        if cpu_type == CpuType::ARM64 || cpu_type == CpuType::ARM {
188            ctx.populate_from_arm_thread_state(flavor, regs)?;
189        } else if cpu_type == CpuType::X86_64 || cpu_type == CpuType::X86 {
190            ctx.populate_from_x86_thread_state(flavor, regs)?;
191        } else {
192            return None;
193        }
194
195        Some(ctx)
196    }
197
198    fn populate_from_arm_thread_state(&mut self, flavor: u32, regs: &[u32]) -> Option<()> {
199        match flavor {
200            6 if regs.len() >= 68 => {
201                // ARM_THREAD_STATE64: 33 registers * 2 u32s + cpsr + pad
202                self.read_arm64_regs(regs, 0);
203                Some(())
204            }
205            1 if !regs.is_empty() => {
206                let sub_flavor = regs[0];
207                if sub_flavor == 2 && regs.len() >= 70 {
208                    // ARM_THREAD_STATE, sub_flavor=2 (ARM64)
209                    self.read_arm64_regs(regs, 2);
210                    Some(())
211                } else {
212                    None // ARM32 not supported for unwinding
213                }
214            }
215            _ => None,
216        }
217    }
218
219    fn read_arm64_regs(&mut self, regs: &[u32], offset: usize) {
220        let r64 = |idx: usize| -> u64 {
221            let base = offset + idx * 2;
222            (regs[base] as u64) | ((regs[base + 1] as u64) << 32)
223        };
224
225        // X0-X28
226        for i in 0..29 {
227            self.set(i as u16, r64(i));
228        }
229        // FP (X29)
230        self.set(arm64::FP, r64(29));
231        // LR (X30)
232        self.set(arm64::LR, r64(30));
233        // SP
234        self.set(arm64::SP, r64(31));
235        // PC
236        self.set(arm64::PC, r64(32));
237    }
238
239    fn populate_from_x86_thread_state(&mut self, flavor: u32, regs: &[u32]) -> Option<()> {
240        match flavor {
241            7 if !regs.is_empty() => {
242                let sub_flavor = regs[0];
243                if sub_flavor == 1 && regs.len() >= 18 {
244                    // 32-bit x86 — not supported for unwinding
245                    return None;
246                }
247                if regs.len() >= 44 {
248                    // 64-bit x86
249                    self.read_x86_64_regs(regs, 2);
250                    return Some(());
251                }
252                None
253            }
254            _ => None,
255        }
256    }
257
258    fn read_x86_64_regs(&mut self, regs: &[u32], offset: usize) {
259        let r64 = |idx: usize| -> u64 {
260            let base = offset + idx * 2;
261            (regs[base] as u64) | ((regs[base + 1] as u64) << 32)
262        };
263
264        // x86_THREAD_STATE64 layout: rax, rbx, rcx, rdx, rdi, rsi, rbp, rsp,
265        // r8, r9, r10, r11, r12, r13, r14, r15, rip, rflags, cs, fs, gs
266        self.set(x86_64::RAX, r64(0));
267        self.set(x86_64::RBX, r64(1));
268        self.set(x86_64::RCX, r64(2));
269        self.set(x86_64::RDX, r64(3));
270        self.set(x86_64::RDI, r64(4));
271        self.set(x86_64::RSI, r64(5));
272        self.set(x86_64::RBP, r64(6));
273        self.set(x86_64::RSP, r64(7));
274        self.set(x86_64::R8, r64(8));
275        self.set(x86_64::R9, r64(9));
276        self.set(x86_64::R10, r64(10));
277        self.set(x86_64::R11, r64(11));
278        self.set(x86_64::R12, r64(12));
279        self.set(x86_64::R13, r64(13));
280        self.set(x86_64::R14, r64(14));
281        self.set(x86_64::R15, r64(15));
282        self.set(x86_64::RIP, r64(16));
283        self.set(x86_64::RFLAGS, r64(17));
284    }
285
286    fn is_arm(&self) -> bool {
287        self.cpu_type == CpuType::ARM64 || self.cpu_type == CpuType::ARM
288    }
289}
290
291#[cfg(test)]
292mod tests {
293    use super::*;
294
295    #[test]
296    fn new_context_is_empty() {
297        let ctx = RegisterContext::new(CpuType::ARM64);
298        assert!(ctx.pc().is_none());
299        assert!(ctx.sp().is_none());
300        assert!(ctx.fp().is_none());
301        assert!(ctx.lr().is_none());
302    }
303
304    #[test]
305    fn arm64_set_get_registers() {
306        let mut ctx = RegisterContext::new(CpuType::ARM64);
307        ctx.set(0, 0xAAAA);
308        ctx.set(arm64::FP, 0xBBBB);
309        ctx.set(arm64::LR, 0xCCCC);
310        ctx.set(arm64::SP, 0xDDDD);
311        ctx.set(arm64::PC, 0xEEEE);
312
313        assert_eq!(ctx.get(0), Some(0xAAAA));
314        assert_eq!(ctx.fp(), Some(0xBBBB));
315        assert_eq!(ctx.lr(), Some(0xCCCC));
316        assert_eq!(ctx.sp(), Some(0xDDDD));
317        assert_eq!(ctx.pc(), Some(0xEEEE));
318    }
319
320    #[test]
321    fn x86_64_set_get_registers() {
322        let mut ctx = RegisterContext::new(CpuType::X86_64);
323        ctx.set(x86_64::RAX, 0x1111);
324        ctx.set(x86_64::RBP, 0x2222);
325        ctx.set(x86_64::RSP, 0x3333);
326        ctx.set(x86_64::RIP, 0x4444);
327
328        assert_eq!(ctx.get(x86_64::RAX), Some(0x1111));
329        assert_eq!(ctx.fp(), Some(0x2222));
330        assert_eq!(ctx.sp(), Some(0x3333));
331        assert_eq!(ctx.pc(), Some(0x4444));
332        // x86_64 has no LR
333        assert!(ctx.lr().is_none());
334    }
335
336    #[test]
337    fn from_thread_state_arm64_flavor6() {
338        let mut regs = vec![0u32; 68];
339        regs[0] = 0xCAFE_BABE; // x0 low
340        regs[1] = 0x0000_0001; // x0 high
341        regs[58] = 0x1111_0000; // FP low (index 29*2)
342        regs[59] = 0x0000_AAAA; // FP high
343        regs[60] = 0x2222_0000; // LR low (index 30*2)
344        regs[61] = 0x0000_BBBB; // LR high
345        regs[62] = 0x3333_0000; // SP low (index 31*2)
346        regs[63] = 0x0000_CCCC; // SP high
347        regs[64] = 0x4444_0000; // PC low (index 32*2)
348        regs[65] = 0x0000_DDDD; // PC high
349
350        let state = ThreadState {
351            flavor: 6,
352            registers: regs,
353        };
354        let ctx = RegisterContext::from_thread_state(&state, CpuType::ARM64).unwrap();
355        assert_eq!(ctx.get(0), Some(0x0000_0001_CAFE_BABE));
356        assert_eq!(ctx.fp(), Some(0x0000_AAAA_1111_0000));
357        assert_eq!(ctx.lr(), Some(0x0000_BBBB_2222_0000));
358        assert_eq!(ctx.sp(), Some(0x0000_CCCC_3333_0000));
359        assert_eq!(ctx.pc(), Some(0x0000_DDDD_4444_0000));
360    }
361
362    #[test]
363    fn from_thread_state_x86_64_flavor7() {
364        let mut regs = vec![0u32; 44];
365        regs[0] = 4; // sub_flavor (64-bit)
366        regs[1] = 0;
367        // rax at offset 2 (index 0)
368        regs[2] = 0xDEAD_BEEF;
369        regs[3] = 0x0000_0001;
370        // rbp at offset 2+6*2=14 (index 6)
371        regs[14] = 0xAAAA_0000;
372        regs[15] = 0x0000_FFFF;
373        // rsp at offset 2+7*2=16 (index 7)
374        regs[16] = 0xBBBB_0000;
375        regs[17] = 0x0000_EEEE;
376        // rip at offset 2+16*2=34 (index 16)
377        regs[34] = 0xCCCC_0000;
378        regs[35] = 0x0000_DDDD;
379
380        let state = ThreadState {
381            flavor: 7,
382            registers: regs,
383        };
384        let ctx = RegisterContext::from_thread_state(&state, CpuType::X86_64).unwrap();
385        assert_eq!(ctx.get(x86_64::RAX), Some(0x0000_0001_DEAD_BEEF));
386        assert_eq!(ctx.fp(), Some(0x0000_FFFF_AAAA_0000));
387        assert_eq!(ctx.sp(), Some(0x0000_EEEE_BBBB_0000));
388        assert_eq!(ctx.pc(), Some(0x0000_DDDD_CCCC_0000));
389    }
390
391    #[test]
392    fn clear_volatile_arm64() {
393        let mut ctx = RegisterContext::new(CpuType::ARM64);
394        // Set volatile (x0) and non-volatile (x19, fp, lr)
395        ctx.set(0, 0x1111); // x0 - volatile
396        ctx.set(19, 0x2222); // x19 - non-volatile
397        ctx.set(arm64::FP, 0x3333);
398        ctx.set(arm64::LR, 0x4444);
399        ctx.set(arm64::SP, 0x5555);
400        ctx.set(arm64::PC, 0x6666);
401
402        ctx.clear_volatile();
403
404        assert!(ctx.get(0).is_none()); // x0 cleared
405        assert_eq!(ctx.get(19), Some(0x2222)); // x19 preserved
406        assert_eq!(ctx.fp(), Some(0x3333));
407        // LR is non-volatile on ARM64
408        assert_eq!(ctx.lr(), Some(0x4444));
409        assert_eq!(ctx.sp(), Some(0x5555));
410        assert_eq!(ctx.pc(), Some(0x6666));
411    }
412
413    #[test]
414    fn clear_volatile_x86_64() {
415        let mut ctx = RegisterContext::new(CpuType::X86_64);
416        ctx.set(x86_64::RAX, 0x1111); // volatile
417        ctx.set(x86_64::RBX, 0x2222); // non-volatile
418        ctx.set(x86_64::RBP, 0x3333); // non-volatile
419        ctx.set(x86_64::RSP, 0x4444);
420        ctx.set(x86_64::RIP, 0x5555);
421        ctx.set(x86_64::R12, 0x6666); // non-volatile
422
423        ctx.clear_volatile();
424
425        assert!(ctx.get(x86_64::RAX).is_none()); // cleared
426        assert_eq!(ctx.get(x86_64::RBX), Some(0x2222));
427        assert_eq!(ctx.fp(), Some(0x3333));
428        assert_eq!(ctx.sp(), Some(0x4444));
429        assert_eq!(ctx.pc(), Some(0x5555));
430        assert_eq!(ctx.get(x86_64::R12), Some(0x6666));
431    }
432
433    #[test]
434    fn from_thread_state_unsupported_cpu() {
435        let state = ThreadState {
436            flavor: 0,
437            registers: vec![],
438        };
439        assert!(RegisterContext::from_thread_state(&state, CpuType::POWERPC).is_none());
440    }
441}