probe_rs/core/
dump.rs

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