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