1use std::path::Path;
7
8use crate::{Error, FormatPlugin, PhysicalMemoryProvider, PhysicalRange, Result};
9
10#[derive(Debug, Clone)]
12struct LoadSegment {
13 paddr: u64,
15 file_offset: u64,
17 file_size: u64,
19}
20
21pub struct ElfCoreProvider {
23 data: Vec<u8>,
24 segments: Vec<LoadSegment>,
25 ranges: Vec<PhysicalRange>,
26}
27
28impl ElfCoreProvider {
29 pub fn from_bytes(data: Vec<u8>) -> Result<Self> {
31 let elf = goblin::elf::Elf::parse(&data)
32 .map_err(|e| Error::Corrupt(format!("ELF parse error: {e}")))?;
33
34 if elf.header.e_type != goblin::elf::header::ET_CORE {
35 return Err(Error::Corrupt("not an ELF core dump".into()));
36 }
37
38 let mut segments = Vec::new();
39 for phdr in &elf.program_headers {
40 if phdr.p_type == goblin::elf::program_header::PT_LOAD && phdr.p_filesz > 0 {
41 segments.push(LoadSegment {
42 paddr: phdr.p_paddr,
43 file_offset: phdr.p_offset,
44 file_size: phdr.p_filesz,
45 });
46 }
47 }
48
49 segments.sort_by_key(|s| s.paddr);
50
51 let ranges: Vec<PhysicalRange> = segments
52 .iter()
53 .map(|s| PhysicalRange {
54 start: s.paddr,
55 end: s.paddr + s.file_size,
56 })
57 .collect();
58
59 Ok(Self {
60 data,
61 segments,
62 ranges,
63 })
64 }
65}
66
67impl PhysicalMemoryProvider for ElfCoreProvider {
68 fn read_phys(&self, addr: u64, buf: &mut [u8]) -> Result<usize> {
69 if buf.is_empty() {
70 return Ok(0);
71 }
72
73 for seg in &self.segments {
74 let seg_end = seg.paddr + seg.file_size;
75 if addr >= seg.paddr && addr < seg_end {
76 let offset_in_seg = addr - seg.paddr;
77 let available = (seg.file_size - offset_in_seg) as usize;
78 let to_read = buf.len().min(available);
79 let file_pos = seg.file_offset + offset_in_seg;
80 let file_pos_usize = file_pos as usize;
81 buf[..to_read]
82 .copy_from_slice(&self.data[file_pos_usize..file_pos_usize + to_read]);
83 return Ok(to_read);
84 }
85 }
86
87 Ok(0)
88 }
89
90 fn ranges(&self) -> &[PhysicalRange] {
91 &self.ranges
92 }
93
94 fn format_name(&self) -> &str {
95 "ELF Core"
96 }
97}
98
99struct ElfCorePlugin;
101
102impl FormatPlugin for ElfCorePlugin {
103 fn name(&self) -> &str {
104 "ELF Core"
105 }
106
107 fn probe(&self, header: &[u8]) -> u8 {
108 if header.len() < 18 {
109 return 0;
110 }
111 if header[0..4] != [0x7F, b'E', b'L', b'F'] {
113 return 0;
114 }
115 let e_type = u16::from_le_bytes([header[16], header[17]]);
117 if e_type == 4 {
118 90
119 } else {
120 0
121 }
122 }
123
124 fn open(&self, path: &Path) -> Result<Box<dyn PhysicalMemoryProvider>> {
125 let data = std::fs::read(path)?;
126 let provider = ElfCoreProvider::from_bytes(data)?;
127 Ok(Box::new(provider))
128 }
129}
130
131inventory::submit!(&ElfCorePlugin as &dyn FormatPlugin);
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136 use crate::test_builders::ElfCoreBuilder;
137
138 #[test]
139 fn probe_elf_core() {
140 let dump = ElfCoreBuilder::new()
141 .add_segment(0x1000, &[0xAA; 128])
142 .build();
143 let plugin = ElfCorePlugin;
144 assert_eq!(plugin.probe(&dump[..64.min(dump.len())]), 90);
145 }
146
147 #[test]
148 fn probe_non_core_elf() {
149 let mut header = vec![0u8; 64];
151 header[0..4].copy_from_slice(&[0x7F, b'E', b'L', b'F']);
152 header[4] = 2; header[5] = 1; header[16..18].copy_from_slice(&2u16.to_le_bytes()); let plugin = ElfCorePlugin;
156 assert_eq!(plugin.probe(&header), 0);
157 }
158
159 #[test]
160 fn probe_non_elf() {
161 let data = vec![0u8; 128];
162 let plugin = ElfCorePlugin;
163 assert_eq!(plugin.probe(&data), 0);
164 }
165
166 #[test]
167 fn single_segment() {
168 let payload = vec![0xBB; 256];
169 let dump = ElfCoreBuilder::new().add_segment(0x1000, &payload).build();
170 let provider = ElfCoreProvider::from_bytes(dump).unwrap();
171
172 assert_eq!(provider.format_name(), "ELF Core");
173 assert_eq!(provider.ranges().len(), 1);
174 assert_eq!(provider.ranges()[0].start, 0x1000);
175 assert_eq!(provider.ranges()[0].end, 0x1000 + 256);
176
177 let mut buf = [0u8; 8];
178 let n = provider.read_phys(0x1000, &mut buf).unwrap();
179 assert_eq!(n, 8);
180 assert_eq!(buf, [0xBB; 8]);
181 }
182
183 #[test]
184 fn two_segments() {
185 let dump = ElfCoreBuilder::new()
186 .add_segment(0x1000, &[0xAA; 128])
187 .add_segment(0x5000, &[0xCC; 256])
188 .build();
189 let provider = ElfCoreProvider::from_bytes(dump).unwrap();
190
191 assert_eq!(provider.ranges().len(), 2);
192 assert_eq!(provider.total_size(), 128 + 256);
193
194 let mut buf = [0u8; 4];
195 let n = provider.read_phys(0x5000, &mut buf).unwrap();
196 assert_eq!(n, 4);
197 assert_eq!(buf, [0xCC; 4]);
198 }
199
200 #[test]
201 fn read_gap_returns_zero() {
202 let dump = ElfCoreBuilder::new()
203 .add_segment(0x1000, &[0xAA; 128])
204 .build();
205 let provider = ElfCoreProvider::from_bytes(dump).unwrap();
206
207 let mut buf = [0xFF; 8];
208 let n = provider.read_phys(0x9000, &mut buf).unwrap();
209 assert_eq!(n, 0);
210 assert_eq!(buf, [0xFF; 8]);
212 }
213
214 #[test]
215 fn from_path_via_plugin_open() {
216 let payload = vec![0xDD; 256];
217 let dump = ElfCoreBuilder::new().add_segment(0x3000, &payload).build();
218 let path = std::env::temp_dir().join("memf_test_elf_core_from_path.core");
219 std::fs::write(&path, &dump).unwrap();
220 let plugin = ElfCorePlugin;
221 let provider = plugin.open(&path).unwrap();
222 assert_eq!(provider.format_name(), "ELF Core");
223 assert_eq!(provider.ranges().len(), 1);
224 assert_eq!(provider.total_size(), 256);
225 std::fs::remove_file(&path).ok();
226 }
227
228 #[test]
229 fn plugin_name() {
230 let plugin = ElfCorePlugin;
231 assert_eq!(plugin.name(), "ELF Core");
232 }
233
234 #[test]
235 fn probe_too_short_returns_zero() {
236 let plugin = ElfCorePlugin;
237 assert_eq!(plugin.probe(&[0x7F, b'E', b'L', b'F']), 0); assert_eq!(plugin.probe(&[]), 0);
239 }
240
241 #[test]
242 fn read_phys_empty_buffer() {
243 let dump = ElfCoreBuilder::new()
244 .add_segment(0x1000, &[0xAA; 128])
245 .build();
246 let provider = ElfCoreProvider::from_bytes(dump).unwrap();
247 let mut buf = [];
248 let n = provider.read_phys(0x1000, &mut buf).unwrap();
249 assert_eq!(n, 0);
250 }
251
252 #[test]
258 fn empty_data_returns_corrupt_error() {
259 let result = ElfCoreProvider::from_bytes(vec![]);
260 assert!(result.is_err(), "empty input must be rejected");
261 let err = result.err().unwrap();
262 assert!(
263 matches!(err, crate::Error::Corrupt(_)),
264 "error must be Corrupt variant, got: {err}"
265 );
266 }
267
268 #[test]
271 fn truncated_elf_data_returns_corrupt_error() {
272 let truncated = vec![0x7F, b'E', b'L', b'F', 2, 1, 1, 0];
274 let result = ElfCoreProvider::from_bytes(truncated);
275 assert!(result.is_err(), "truncated ELF must be rejected");
276 let err = result.err().unwrap();
277 assert!(
278 matches!(err, crate::Error::Corrupt(_)),
279 "error must be Corrupt, got: {err}"
280 );
281 }
282
283 #[test]
286 fn non_core_elf_type_returns_corrupt_error() {
287 let mut header = vec![0u8; 64];
289 header[0..4].copy_from_slice(&[0x7F, b'E', b'L', b'F']);
290 header[4] = 2; header[5] = 1; header[6] = 1; header[16..18].copy_from_slice(&2u16.to_le_bytes()); header[20..24].copy_from_slice(&1u32.to_le_bytes()); header[32..40].copy_from_slice(&64u64.to_le_bytes()); header[52..54].copy_from_slice(&64u16.to_le_bytes()); header[54..56].copy_from_slice(&56u16.to_le_bytes()); header[56..58].copy_from_slice(&0u16.to_le_bytes()); let result = ElfCoreProvider::from_bytes(header);
302 assert!(result.is_err(), "non-core ELF must be rejected");
303 let err = result.err().unwrap();
304 assert!(
305 matches!(err, crate::Error::Corrupt(_)),
306 "error must be Corrupt, got: {err}"
307 );
308 assert!(
309 err.to_string().contains("not an ELF core dump"),
310 "error message should explain the rejection reason, got: {err}"
311 );
312 }
313
314 #[test]
316 fn garbage_data_returns_corrupt_error() {
317 let mut garbage = vec![0x00u8; 128];
318 garbage[0..4].copy_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]);
319 let result = ElfCoreProvider::from_bytes(garbage);
320 assert!(result.is_err(), "garbage input must be rejected");
321 let err = result.err().unwrap();
322 assert!(
323 matches!(err, crate::Error::Corrupt(_)),
324 "error must be Corrupt, got: {err}"
325 );
326 }
327}