Skip to main content

probe_rs/core/
dump.rs

1use crate::architecture::arm::core::registers::aarch32::{
2    AARCH32_CORE_REGISTERS, AARCH32_WITH_FP_16_CORE_REGISTERS, AARCH32_WITH_FP_32_CORE_REGISTERS,
3};
4use crate::architecture::arm::core::registers::aarch64::AARCH64_CORE_REGISTERS;
5use crate::architecture::arm::core::registers::cortex_m::{
6    CORTEX_M_CORE_REGISTERS, CORTEX_M_WITH_FP_CORE_REGISTERS,
7};
8use crate::architecture::riscv::registers::{RISCV_CORE_REGISTERS, RISCV_WITH_FP_CORE_REGISTERS};
9use crate::architecture::riscv::registers64::{
10    RISCV64_CORE_REGISTERS, RISCV64_WITH_FP_CORE_REGISTERS,
11};
12use crate::architecture::xtensa::arch::{Register as XtensaRegister, SpecialRegister};
13use crate::architecture::xtensa::registers::XTENSA_CORE_REGISTERS;
14use crate::{Core, CoreRegisters, CoreType, Error, InstructionSet, MemoryInterface};
15use crate::{RegisterId, RegisterValue};
16use object::elf::PT_NOTE;
17use object::read::elf::ProgramHeader;
18use object::{Object, ObjectSegment};
19use probe_rs_target::MemoryRange;
20use scroll::Cread;
21use serde::{Deserialize, Serialize};
22use std::array;
23use std::sync::LazyLock;
24use std::{
25    collections::HashMap,
26    fs::OpenOptions,
27    ops::Range,
28    path::{Path, PathBuf},
29};
30
31trait Processor {
32    /// Returns the instruction set of the processor.
33    fn instruction_set(&self) -> InstructionSet;
34
35    /// Returns the core type of the processor.
36    fn core_type(&self) -> CoreType;
37
38    /// Returns whether the processor supports native 64 bit access.
39    fn supports_native_64bit_access(&self) -> bool {
40        false
41    }
42
43    /// Returns the length of the register data in bytes.
44    fn register_data_len(&self) -> usize;
45
46    /// Returns the core registers and their positions in the register data.
47    ///
48    /// The positions are in register indexes, not byte offsets.
49    fn register_map(&self) -> &[(usize, RegisterId)];
50
51    /// Reads the registers from the .elf note data and stores them in the provided map.
52    fn read_registers(
53        &self,
54        note_data: &[u8],
55        registers: &mut HashMap<RegisterId, RegisterValue>,
56    ) -> Result<(), CoreDumpError> {
57        for (offset, reg_id) in self.register_map().iter().copied() {
58            let value = self.read_register(note_data, offset)?;
59            registers.insert(reg_id, value);
60        }
61
62        Ok(())
63    }
64
65    /// Reads a single register value from the note data. The register is addressed by its
66    /// position in the .elf note data.
67    fn read_register(&self, note_data: &[u8], idx: usize) -> Result<RegisterValue, CoreDumpError> {
68        let value = u32::from_le_bytes(note_data[idx * 4..][..4].try_into().unwrap());
69        Ok(RegisterValue::U32(value))
70    }
71}
72
73struct XtensaProcessor;
74impl Processor for XtensaProcessor {
75    fn instruction_set(&self) -> InstructionSet {
76        InstructionSet::Xtensa
77    }
78    fn core_type(&self) -> CoreType {
79        CoreType::Xtensa
80    }
81    fn register_data_len(&self) -> usize {
82        128 * 4
83    }
84    fn register_map(&self) -> &[(usize, RegisterId)] {
85        static REGS: LazyLock<[(usize, RegisterId); 24]> = LazyLock::new(|| {
86            let core_regs = &XTENSA_CORE_REGISTERS;
87
88            array::from_fn(|idx| {
89                match idx {
90                    // First 8 registers are special registers.
91                    0 => (idx, RegisterId::from(XtensaRegister::CurrentPc)),
92                    1 => (idx, RegisterId::from(XtensaRegister::CurrentPs)),
93                    2 => (idx, RegisterId::from(SpecialRegister::Lbeg)),
94                    3 => (idx, RegisterId::from(SpecialRegister::Lend)),
95                    4 => (idx, RegisterId::from(SpecialRegister::Lcount)),
96                    5 => (idx, RegisterId::from(SpecialRegister::Sar)),
97                    6 => (idx, RegisterId::from(SpecialRegister::Windowstart)),
98                    7 => (idx, RegisterId::from(SpecialRegister::Windowbase)),
99                    // There are 56 reserved registers before the core registers.
100                    8..24 => {
101                        // The coredump contains all 64 AR registers but we don't define them yet,
102                        // so let's just use the 16 of the visible window.
103                        let ar_idx = idx - 8;
104                        (ar_idx + 64, core_regs.core_register(ar_idx).id())
105                    }
106                    _ => unreachable!(),
107                }
108            })
109        });
110
111        &*REGS
112    }
113}
114
115struct RiscvProcessor;
116impl Processor for RiscvProcessor {
117    fn instruction_set(&self) -> InstructionSet {
118        InstructionSet::RV32
119    }
120    fn core_type(&self) -> CoreType {
121        CoreType::Riscv
122    }
123    fn register_data_len(&self) -> usize {
124        32 * 4
125    }
126    fn register_map(&self) -> &[(usize, RegisterId)] {
127        static REGS: LazyLock<[(usize, RegisterId); 32]> = LazyLock::new(|| {
128            let core_regs = &RISCV_CORE_REGISTERS;
129
130            array::from_fn(|idx| {
131                // Core register 0 is the "zero" register. Coredumps place the PC there instead.
132                let regid = if idx == 0 {
133                    core_regs.pc().unwrap().id()
134                } else {
135                    core_regs.core_register(idx).id()
136                };
137                (idx, regid)
138            })
139        });
140        &*REGS
141    }
142}
143
144/// A snapshot representation of a core state.
145#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct CoreDump {
147    /// The registers we dumped from the core.
148    pub registers: HashMap<RegisterId, RegisterValue>,
149    /// The memory we dumped from the core.
150    pub data: Vec<(Range<u64>, Vec<u8>)>,
151    /// The instruction set of the dumped core.
152    pub instruction_set: InstructionSet,
153    /// Whether or not the target supports native 64 bit support (64bit architectures)
154    pub supports_native_64bit_access: bool,
155    /// The type of core we have at hand.
156    pub core_type: CoreType,
157    /// Whether this core supports floating point.
158    pub fpu_support: bool,
159    /// The number of floating point registers.
160    pub floating_point_register_count: Option<usize>,
161}
162
163impl CoreDump {
164    /// Dump the core info with the current state.
165    ///
166    /// # Arguments
167    /// * `core`: The core to dump.
168    /// * `ranges`: Memory ranges that should be dumped.
169    pub fn dump_core(core: &mut Core<'_>, ranges: Vec<Range<u64>>) -> Result<Self, Error> {
170        core.spill_registers()?;
171
172        let mut registers = HashMap::new();
173        for register in core.registers().all_registers() {
174            let value = core.read_core_reg(register.id())?;
175            registers.insert(register.id(), value);
176        }
177
178        let mut data = Vec::new();
179        for range in ranges {
180            let mut values = vec![0; (range.end - range.start) as usize];
181            core.read(range.start, &mut values)?;
182            data.push((range, values));
183        }
184
185        Ok(CoreDump {
186            registers,
187            data,
188            instruction_set: core.instruction_set()?,
189            supports_native_64bit_access: core.supports_native_64bit_access(),
190            core_type: core.core_type(),
191            fpu_support: core.fpu_support()?,
192            floating_point_register_count: Some(core.floating_point_register_count()?),
193        })
194    }
195
196    /// Store the dumped core to a file.
197    pub fn store(&self, path: &Path) -> Result<(), CoreDumpError> {
198        let mut file = OpenOptions::new()
199            .create(true)
200            .write(true)
201            .truncate(true)
202            .open(path)
203            .map_err(|e| {
204                CoreDumpError::CoreDumpFileWrite(e, dunce::canonicalize(path).unwrap_or_default())
205            })?;
206        rmp_serde::encode::write_named(&mut file, self).map_err(CoreDumpError::EncodingCoreDump)?;
207        Ok(())
208    }
209
210    /// Load the dumped core from a file.
211    pub fn load(path: &Path) -> Result<Self, CoreDumpError> {
212        let file_contents = std::fs::read(path).map_err(|e| {
213            CoreDumpError::CoreDumpFileRead(e, dunce::canonicalize(path).unwrap_or_default())
214        })?;
215        Self::load_raw(&file_contents)
216    }
217
218    /// Load the dumped core from a file.
219    pub fn load_raw(data: &[u8]) -> Result<Self, CoreDumpError> {
220        if let Ok(elf) = object::read::elf::ElfFile32::parse(data) {
221            Self::load_elf(elf)
222        } else if let Ok(elf) = object::read::elf::ElfFile64::parse(data) {
223            Self::load_elf(elf)
224        } else {
225            rmp_serde::from_slice(data).map_err(CoreDumpError::DecodingCoreDump)
226        }
227    }
228
229    fn load_elf<Elf: object::read::elf::FileHeader<Endian = object::Endianness>>(
230        elf: object::read::elf::ElfFile<'_, Elf>,
231    ) -> Result<Self, CoreDumpError> {
232        let endianness = elf.endianness();
233        let elf_data = elf.data();
234
235        let processor: Box<dyn Processor> = match elf.architecture() {
236            object::Architecture::Riscv32 => Box::new(RiscvProcessor),
237            object::Architecture::Xtensa => Box::new(XtensaProcessor),
238            other => {
239                return Err(CoreDumpError::DecodingElfCoreDump(format!(
240                    "Unsupported architecture: {other:?}",
241                )));
242            }
243        };
244
245        // The memory is in a Load segment.
246        let mut data = Vec::new();
247        // `elf.segments()` returns PT_LOAD segments only.
248        for segment in elf.segments() {
249            let address: u64 = segment.elf_program_header().p_vaddr(endianness).into();
250            let size: u64 = segment.elf_program_header().p_memsz(endianness).into();
251            let memory = segment.data()?;
252            tracing::debug!(
253                "Adding memory segment: {:#x} - {:#x}",
254                address,
255                address + size
256            );
257            data.push((address..address + size, memory.to_vec()));
258        }
259
260        // Registers are in a Note segment.
261        let Some(register_note) = elf
262            .elf_program_headers()
263            .iter()
264            .find(|s| s.p_type(endianness) == PT_NOTE)
265        else {
266            return Err(CoreDumpError::DecodingElfCoreDump(
267                "No note segment found".to_string(),
268            ));
269        };
270
271        let mut registers = HashMap::new();
272        for note in register_note
273            .notes(endianness, elf_data)?
274            .expect("Failed to read notes from a PT_NOTE segment. This is a bug, please report it.")
275        {
276            let note = note?;
277            if note.name() != b"CORE" {
278                continue;
279            }
280
281            // The CORE note contains some thread-specific information before/after the registers.
282            // We only care about the registers, so let's cut off the rest. If we decide to use
283            // the other information, we can do that later, most likely without
284            // architecture-specific processing code.
285            const CORE_NOTE_HEADER_SIZE: usize = 72;
286            let note_length = processor.register_data_len();
287
288            if note.desc().len() < CORE_NOTE_HEADER_SIZE + note_length {
289                return Err(CoreDumpError::DecodingElfCoreDump(format!(
290                    "Note segment is too small: {} bytes instead of at least {}",
291                    note.desc().len(),
292                    CORE_NOTE_HEADER_SIZE + note_length
293                )));
294            }
295
296            let note_data = &note.desc()[CORE_NOTE_HEADER_SIZE..][..note_length];
297            processor.read_registers(note_data, &mut registers)?;
298        }
299
300        Ok(Self {
301            registers,
302            data,
303            instruction_set: processor.instruction_set(),
304            supports_native_64bit_access: processor.supports_native_64bit_access(),
305            core_type: processor.core_type(),
306            fpu_support: false,
307            floating_point_register_count: None,
308        })
309    }
310
311    /// Returns the type of the core.
312    pub fn core_type(&self) -> CoreType {
313        self.core_type
314    }
315
316    /// Returns the currently active instruction-set
317    pub fn instruction_set(&self) -> InstructionSet {
318        self.instruction_set
319    }
320
321    /// Retrieve a memory range that contains the requested address and size, from the coredump.
322    fn get_memory_from_coredump(
323        &self,
324        address: u64,
325        size_in_bytes: u64,
326    ) -> Result<&[u8], crate::Error> {
327        for (range, memory) in &self.data {
328            if range.contains_range(&(address..(address + size_in_bytes))) {
329                let offset = (address - range.start) as usize;
330
331                return Ok(&memory[offset..][..size_in_bytes as usize]);
332            }
333        }
334        // If we get here, then no range with the requested memory address and size was found.
335        Err(crate::Error::Other(format!(
336            "The coredump does not include the memory for address {address:#x} of size {size_in_bytes:#x}"
337        )))
338    }
339
340    /// Read the requested memory range from the coredump, and return the data in the requested buffer.
341    /// The word-size of the read is determined by the size of the items in the `data` buffer.
342    fn read_memory_range<T>(&self, address: u64, data: &mut [T]) -> Result<(), crate::Error>
343    where
344        T: scroll::ctx::FromCtx<scroll::Endian>,
345    {
346        let memory =
347            self.get_memory_from_coredump(address, (std::mem::size_of_val(data)) as u64)?;
348
349        let value_size = std::mem::size_of::<T>();
350
351        for (n, data) in data.iter_mut().enumerate() {
352            *data = memory.cread_with::<T>(n * value_size, scroll::LE);
353        }
354        Ok(())
355    }
356
357    /// Returns the register map for the core type.
358    pub fn registers(&self) -> &'static CoreRegisters {
359        match self.core_type {
360            CoreType::Armv6m => &CORTEX_M_CORE_REGISTERS,
361            CoreType::Armv7a | CoreType::Armv7r => match self.floating_point_register_count {
362                Some(16) => &AARCH32_WITH_FP_16_CORE_REGISTERS,
363                Some(32) => &AARCH32_WITH_FP_32_CORE_REGISTERS,
364                _ => &AARCH32_CORE_REGISTERS,
365            },
366            CoreType::Armv7m => {
367                if self.fpu_support {
368                    &CORTEX_M_WITH_FP_CORE_REGISTERS
369                } else {
370                    &CORTEX_M_CORE_REGISTERS
371                }
372            }
373            CoreType::Armv7em => {
374                if self.fpu_support {
375                    &CORTEX_M_WITH_FP_CORE_REGISTERS
376                } else {
377                    &CORTEX_M_CORE_REGISTERS
378                }
379            }
380            // TODO: This can be wrong if the CPU is 32 bit. For lack of better design at the time
381            // of writing this code this differentiation has been omitted.
382            CoreType::Armv8a => &AARCH64_CORE_REGISTERS,
383            CoreType::Armv8m => {
384                if self.fpu_support {
385                    &CORTEX_M_WITH_FP_CORE_REGISTERS
386                } else {
387                    &CORTEX_M_CORE_REGISTERS
388                }
389            }
390            CoreType::Riscv => {
391                if self.fpu_support {
392                    &RISCV_WITH_FP_CORE_REGISTERS
393                } else {
394                    &RISCV_CORE_REGISTERS
395                }
396            }
397            CoreType::Riscv64 => {
398                if self.fpu_support {
399                    &RISCV64_WITH_FP_CORE_REGISTERS
400                } else {
401                    &RISCV64_CORE_REGISTERS
402                }
403            }
404            CoreType::Xtensa => &XTENSA_CORE_REGISTERS,
405        }
406    }
407}
408
409impl MemoryInterface for CoreDump {
410    fn supports_native_64bit_access(&mut self) -> bool {
411        self.supports_native_64bit_access
412    }
413
414    fn read_word_64(&mut self, address: u64) -> Result<u64, crate::Error> {
415        let mut data = [0u64; 1];
416        self.read_memory_range(address, &mut data)?;
417        Ok(data[0])
418    }
419
420    fn read_word_32(&mut self, address: u64) -> Result<u32, crate::Error> {
421        let mut data = [0u32; 1];
422        self.read_memory_range(address, &mut data)?;
423        Ok(data[0])
424    }
425
426    fn read_word_16(&mut self, address: u64) -> Result<u16, crate::Error> {
427        let mut data = [0u16; 1];
428        self.read_memory_range(address, &mut data)?;
429        Ok(data[0])
430    }
431
432    fn read_word_8(&mut self, address: u64) -> Result<u8, crate::Error> {
433        let mut data = [0u8; 1];
434        self.read_memory_range(address, &mut data)?;
435        Ok(data[0])
436    }
437
438    fn read_64(&mut self, address: u64, data: &mut [u64]) -> Result<(), crate::Error> {
439        self.read_memory_range(address, data)?;
440        Ok(())
441    }
442
443    fn read_32(&mut self, address: u64, data: &mut [u32]) -> Result<(), crate::Error> {
444        self.read_memory_range(address, data)?;
445        Ok(())
446    }
447
448    fn read_16(&mut self, address: u64, data: &mut [u16]) -> Result<(), crate::Error> {
449        self.read_memory_range(address, data)?;
450        Ok(())
451    }
452
453    fn read_8(&mut self, address: u64, data: &mut [u8]) -> Result<(), crate::Error> {
454        self.read_memory_range(address, data)?;
455        Ok(())
456    }
457
458    fn write_word_64(&mut self, _address: u64, _data: u64) -> Result<(), crate::Error> {
459        todo!()
460    }
461
462    fn write_word_32(&mut self, _address: u64, _data: u32) -> Result<(), crate::Error> {
463        todo!()
464    }
465
466    fn write_word_16(&mut self, _address: u64, _data: u16) -> Result<(), crate::Error> {
467        todo!()
468    }
469
470    fn write_word_8(&mut self, _address: u64, _data: u8) -> Result<(), crate::Error> {
471        todo!()
472    }
473
474    fn write_64(&mut self, _address: u64, _data: &[u64]) -> Result<(), crate::Error> {
475        todo!()
476    }
477
478    fn write_32(&mut self, _address: u64, _data: &[u32]) -> Result<(), crate::Error> {
479        todo!()
480    }
481
482    fn write_16(&mut self, _address: u64, _data: &[u16]) -> Result<(), crate::Error> {
483        todo!()
484    }
485
486    fn write_8(&mut self, _address: u64, _data: &[u8]) -> Result<(), crate::Error> {
487        todo!()
488    }
489
490    fn supports_8bit_transfers(&self) -> Result<bool, crate::Error> {
491        todo!()
492    }
493
494    fn flush(&mut self) -> Result<(), crate::Error> {
495        todo!()
496    }
497}
498
499/// The overarching error type which contains all possible errors as variants.
500#[derive(thiserror::Error, Debug)]
501pub enum CoreDumpError {
502    /// Opening the file for writing the core dump failed.
503    #[error("Opening {1} for writing the core dump failed.")]
504    CoreDumpFileWrite(std::io::Error, PathBuf),
505    /// Opening the file for reading the core dump failed.
506    #[error("Opening {1} for reading the core dump failed.")]
507    CoreDumpFileRead(std::io::Error, PathBuf),
508    /// Encoding the coredump MessagePack failed.
509    #[error("Encoding the coredump MessagePack failed.")]
510    EncodingCoreDump(rmp_serde::encode::Error),
511    /// Decoding the coredump MessagePack failed.
512    #[error("Decoding the coredump MessagePack failed.")]
513    DecodingCoreDump(rmp_serde::decode::Error),
514    /// Decoding the coredump .elf failed.
515    #[error("Decoding the coredump .elf failed.")]
516    DecodingElfCoreDump(String),
517    /// Invalid ELF file.
518    #[error("Invalid ELF file.")]
519    ElfCoreDumpFormat(#[from] object::read::Error),
520}