1use super::arch;
10use super::arch::Arch;
11use crate::elf::*;
12use crate::CoreError;
13use crate::ProcessInfoSource;
14use crate::ReadProcessMemory;
15use smallvec::smallvec;
16use smallvec::SmallVec;
17use std::io::Read;
18use std::io::Write;
19use std::slice;
20use zerocopy::Immutable;
21use zerocopy::IntoBytes;
22
23#[cfg(target_os = "linux")]
24use crate::{LinuxProcessMemoryReader, ProcessView};
25
26const ELF_HEADER_ALIGN: usize = 8;
27const ELF_NOTE_ALIGN: usize = 4;
28
29const NOTE_NAME_CORE: &[u8] = b"CORE";
30
31const BUFFER_SIZE: usize = 0x10000;
35
36struct ElfCoreWriter<T: Write> {
38 writer: T,
39 written: usize,
40}
41
42impl<T> Write for ElfCoreWriter<T>
43where
44 T: Write,
45{
46 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
47 let result = self.writer.write(buf);
48 if let Ok(written) = result {
49 self.written += written;
50 }
51 result
52 }
53
54 fn flush(&mut self) -> std::io::Result<()> {
55 self.writer.flush()
56 }
57}
58
59impl<T> ElfCoreWriter<T>
60where
61 T: Write,
62{
63 pub fn new(writer: T) -> Self {
64 Self { writer, written: 0 }
65 }
66
67 pub fn write_padding(&mut self, bytes: usize) -> std::io::Result<usize> {
68 let buf: SmallVec<[u8; BUFFER_SIZE]> = smallvec![0; bytes];
69 self.write_all(&buf)?;
70 Ok(buf.len())
71 }
72
73 pub fn align_position(&mut self, alignment: usize) -> std::io::Result<usize> {
74 self.write_padding(round_up(self.written, alignment) - self.written)
75 }
76
77 pub fn stream_position(&self) -> std::io::Result<usize> {
78 Ok(self.written)
79 }
80}
81
82#[derive(IntoBytes, Immutable)]
83#[repr(C, packed)]
84struct MappedFilesNoteIntro {
85 file_count: u64,
86 page_size: u64,
87}
88
89#[derive(IntoBytes, Immutable)]
90#[repr(C, packed)]
91struct MappedFilesNoteItem {
92 start_addr: u64,
93 end_addr: u64,
94 page_count: u64,
95}
96
97#[derive(Debug)]
99pub struct VaProtection {
100 pub is_private: bool,
102 pub read: bool,
104 pub write: bool,
106 pub execute: bool,
108}
109
110#[derive(Debug)]
112pub struct VaRegion {
113 pub begin: u64,
115 pub end: u64,
117 pub offset: u64,
119 pub protection: VaProtection,
121 pub mapped_file_name: Option<String>,
123}
124
125#[derive(Debug)]
127pub struct MappedFileRegion {
128 pub begin: u64,
130 pub end: u64,
132 pub offset: u64,
134}
135
136#[derive(Debug)]
138pub struct MappedFile {
139 pub name: String,
141 pub regions: Vec<MappedFileRegion>,
143}
144
145#[derive(Default)]
146struct NoteSizes {
147 process_info: usize,
148 process_status: usize,
149 aux_vector: usize,
150 mapped_files: usize,
151 custom: usize,
152 total_note_size: usize,
153}
154
155struct CustomFileNote<'a> {
157 pub name: String,
159 pub file: &'a mut dyn Read,
161 pub note_len: usize,
164}
165
166fn get_elf_notes_sizes<P: ProcessInfoSource>(
167 pv: &P,
168 custom_notes: Option<&[CustomFileNote<'_>]>,
169) -> Result<NoteSizes, CoreError> {
170 let header_and_name =
171 size_of::<Elf64_Nhdr>() + round_up(NOTE_NAME_CORE.len() + 1, ELF_NOTE_ALIGN);
172 let process_info = header_and_name + round_up(size_of::<prpsinfo_t>(), ELF_NOTE_ALIGN);
173 let one_thread_status = header_and_name
174 + round_up(size_of::<siginfo_t>(), ELF_NOTE_ALIGN)
175 + header_and_name
176 + round_up(
177 size_of::<prstatus_t>() + {
178 let mut arch_size = 0;
179 for component in pv
180 .threads()
181 .first()
182 .ok_or(CoreError::ProcParsingError)?
183 .arch_state
184 .components()
185 {
186 arch_size += header_and_name + component.data.len();
187 }
188 arch_size
189 },
190 ELF_NOTE_ALIGN,
191 );
192 let process_status = one_thread_status * pv.threads().len();
193 let aux_vector = pv
195 .aux_vector()
196 .map(|auxv| header_and_name + size_of_val(auxv))
197 .unwrap_or(0);
198
199 let mapped_files = pv
201 .mapped_files()
202 .map(|files| {
203 let mut addr_layout_size = 0_usize;
204 let mut string_size = 0_usize;
205
206 for mapped_file in files {
207 string_size += (mapped_file.name.len() + 1) * mapped_file.regions.len();
208 addr_layout_size += size_of::<MappedFilesNoteItem>() * mapped_file.regions.len();
209 }
210
211 let intro_size = size_of::<MappedFilesNoteIntro>();
212
213 header_and_name + round_up(intro_size + addr_layout_size + string_size, ELF_NOTE_ALIGN)
214 })
215 .unwrap_or(0);
216
217 let custom = if let Some(custom_notes) = custom_notes {
218 round_up(
219 custom_notes.iter().map(|x| x.note_len).sum::<usize>(),
220 ELF_NOTE_ALIGN,
221 )
222 } else {
223 0
224 };
225
226 let total_note_size = process_info + process_status + aux_vector + mapped_files + custom;
227
228 tracing::info!("Estimated process info note size: {}", process_info);
229 tracing::info!("Estimated process status note size: {}", process_status);
230 tracing::info!("Estimated aux vector note size: {}", aux_vector);
231 tracing::info!("Estimated mapped files note size: {}", mapped_files);
232 tracing::info!("Estimated custom note size: {}", custom);
233 tracing::info!("Estimated total note size: {}", total_note_size);
234
235 Ok(NoteSizes {
236 process_info,
237 process_status,
238 aux_vector,
239 mapped_files,
240 custom,
241 total_note_size,
242 })
243}
244
245#[cfg(target_os = "linux")]
253pub fn write_core_dump<T: Write>(writer: T, pv: &ProcessView) -> Result<usize, CoreError> {
254 let mut memory_reader = ProcessView::create_memory_reader(pv.pid)?;
255 write_core_dump_inner::<T, ProcessView, LinuxProcessMemoryReader>(
256 writer,
257 pv,
258 None,
259 &mut memory_reader,
260 )
261}
262
263fn write_core_dump_inner<T: Write, P: ProcessInfoSource, M: ReadProcessMemory>(
264 writer: T,
265 pv: &P,
266 custom_notes: Option<&mut [CustomFileNote<'_>]>,
267 memory_reader: &mut M,
268) -> Result<usize, CoreError> {
269 let mut total_written = 0_usize;
270 let mut writer = ElfCoreWriter::new(writer);
271
272 if pv.threads().is_empty() || pv.va_regions().is_empty() {
274 return Err(CoreError::CustomSourceInfo);
275 }
276
277 #[cfg(target_os = "linux")]
278 tracing::info!(
279 "Creating core dump file for process {}. This process id: {}, this thread id: {}",
280 pv.pid(),
281 nix::unistd::getpid(),
282 nix::unistd::gettid()
283 );
284 #[cfg(not(target_os = "linux"))]
285 tracing::info!("Creating core dump file for process {}.", pv.pid());
286
287 let note_sizes = get_elf_notes_sizes(pv, custom_notes.as_deref())?;
288
289 total_written += write_elf_header(&mut writer, pv)?;
290 total_written += writer.align_position(ELF_HEADER_ALIGN)?;
291 total_written += write_program_headers(&mut writer, pv, ¬e_sizes)?;
292 total_written += writer.align_position(ELF_HEADER_ALIGN)?;
293 total_written += write_elf_notes(&mut writer, pv, ¬e_sizes, custom_notes)?;
294 total_written += writer.align_position(pv.page_size())?;
295 total_written += write_va_regions(&mut writer, pv, memory_reader)?;
296
297 tracing::info!("Wrote {} bytes for ELF core dump", total_written);
298
299 Ok(total_written)
300}
301
302fn round_up(value: usize, alignment: usize) -> usize {
303 if value == 0 {
306 return 0;
307 }
308
309 if !value.is_multiple_of(alignment) {
310 (value + alignment) / alignment * alignment
311 } else {
312 value
313 }
314}
315
316fn write_elf_header<T: Write, P: ProcessInfoSource>(
317 writer: &mut ElfCoreWriter<T>,
318 pv: &P,
319) -> Result<usize, CoreError> {
320 let mut e_ident = [0_u8; 16];
321 e_ident[EI_MAG0] = ELFMAG0;
322 e_ident[EI_MAG1] = ELFMAG1;
323 e_ident[EI_MAG2] = ELFMAG2;
324 e_ident[EI_MAG3] = ELFMAG3;
325 e_ident[EI_CLASS] = ELFCLASS64;
326 e_ident[EI_DATA] = ELFDATA2LSB; e_ident[EI_VERSION] = EV_CURRENT;
328 e_ident[EI_OSABI] = ELFOSABI_NONE;
329
330 let elf_header = Elf64_Ehdr {
331 e_ident,
332 e_type: ET_CORE,
333 e_machine: arch::ArchState::EM_ELF_MACHINE,
334 e_version: EV_CURRENT as u32,
335 e_phoff: size_of::<Elf64_Ehdr>() as u64,
336 e_ehsize: size_of::<Elf64_Ehdr>() as u16,
337 e_phentsize: size_of::<Elf64_Phdr>() as u16,
338 e_phnum: 1 + pv.va_regions().len() as u16, e_shentsize: 0,
340 e_entry: 0,
341 e_shoff: 0,
342 e_flags: 0,
343 e_shnum: 0,
344 e_shstrndx: 0,
345 };
346
347 tracing::info!(
348 "Writing ELF header at offset {}...",
349 writer.stream_position()?
350 );
351
352 let slice = unsafe {
355 slice::from_raw_parts(&elf_header as *const _ as *mut u8, size_of::<Elf64_Ehdr>())
356 };
357 writer.write_all(slice)?;
358
359 tracing::info!("Wrote {} bytes", slice.len());
360
361 Ok(slice.len())
362}
363
364fn write_program_headers<T: Write, P: ProcessInfoSource>(
365 writer: &mut ElfCoreWriter<T>,
366 pv: &P,
367 note_sizes: &NoteSizes,
368) -> Result<usize, CoreError> {
369 tracing::info!(
370 "Writing program headers at offset {}...",
371 writer.stream_position()?
372 );
373
374 let mut written = 0_usize;
375
376 let phdr_size = size_of::<Elf64_Phdr>() * (pv.va_regions().len() + 1);
381 let ehdr_size = size_of::<Elf64_Ehdr>();
382 let data_offset = round_up(ehdr_size, ELF_HEADER_ALIGN) + round_up(phdr_size, ELF_HEADER_ALIGN);
383
384 {
385 let mut note_header = Elf64_Phdr {
386 p_type: PT_NOTE,
387 p_flags: 0,
388 p_vaddr: 0,
389 p_paddr: 0,
390 p_filesz: note_sizes.total_note_size as u64,
391 p_memsz: note_sizes.total_note_size as u64,
392 p_align: 1,
393 p_offset: data_offset as u64, };
395
396 let slice = unsafe {
399 slice::from_raw_parts_mut(
400 &mut note_header as *mut _ as *mut u8,
401 size_of::<Elf64_Phdr>(),
402 )
403 };
404 writer.write_all(slice)?;
405 written += slice.len();
406 }
407
408 let mut current_offset = round_up(data_offset + note_sizes.total_note_size, pv.page_size());
409
410 for region in pv.va_regions() {
411 let mut seg_header = Elf64_Phdr {
412 p_type: PT_LOAD,
413 p_flags: {
414 const PF_X: u32 = 1u32 << 0;
415 const PF_W: u32 = 1u32 << 1;
416 const PF_R: u32 = 1u32 << 2;
417
418 let mut seg_prot: u32 = 0;
419 if region.protection.execute {
420 seg_prot |= PF_X;
421 }
422 if region.protection.write {
423 seg_prot |= PF_W;
424 }
425 if region.protection.read {
426 seg_prot |= PF_R;
427 }
428
429 seg_prot
430 },
431 p_offset: current_offset as u64,
432 p_vaddr: region.begin,
433 p_paddr: 0,
434 p_filesz: region.end - region.begin,
435 p_memsz: region.end - region.begin,
436 p_align: pv.page_size() as u64,
437 };
438
439 let slice = unsafe {
442 slice::from_raw_parts_mut(
443 &mut seg_header as *mut _ as *mut u8,
444 size_of::<Elf64_Phdr>(),
445 )
446 };
447 writer.write_all(slice)?;
448 written += slice.len();
449
450 current_offset += seg_header.p_filesz as usize;
451 }
452
453 tracing::info!("Wrote {} bytes", written);
454
455 Ok(written)
456}
457
458fn write_elf_note_header<T: Write>(
459 writer: &mut ElfCoreWriter<T>,
460 note_kind: u32,
461 name_bytes: &[u8],
462 data_len: usize,
463) -> Result<usize, CoreError> {
464 let mut written = 0_usize;
465
466 let namesz = name_bytes.len() + 1;
471 let note_header = Elf64_Nhdr {
472 ntype: note_kind,
473 namesz: namesz as u32,
474 descsz: data_len as u32,
475 };
476
477 tracing::debug!(
478 "Writing note header at offset {}...",
479 writer.stream_position()?
480 );
481 writer.write_all(note_header.as_bytes())?;
482 written += size_of::<Elf64_Nhdr>();
483
484 tracing::debug!(
485 "Writing note name at offset {}...",
486 writer.stream_position()?
487 );
488
489 writer.write_all(name_bytes)?;
490 written += name_bytes.len();
491
492 let padding = [0_u8; ELF_NOTE_ALIGN];
493 let padding_len = round_up(namesz, ELF_NOTE_ALIGN) - namesz + 1;
494 writer.write_all(&padding[..padding_len])?;
495 written += padding_len;
496
497 Ok(written)
498}
499
500fn write_elf_note<T: Write>(
501 writer: &mut ElfCoreWriter<T>,
502 note_kind: u32,
503 name_bytes: &[u8],
504 data: &[u8],
505) -> Result<usize, CoreError> {
506 let mut written = 0_usize;
507
508 written += write_elf_note_header(writer, note_kind, name_bytes, data.len())?;
509
510 tracing::debug!(
511 "Writing note payload {} bytes at offset {}...",
512 data.len(),
513 writer.stream_position()?
514 );
515
516 writer.write_all(data)?;
517 written += data.len();
518 written += writer.align_position(ELF_NOTE_ALIGN)?;
519
520 Ok(written)
521}
522
523fn write_elf_note_file<T: Write>(
524 writer: &mut ElfCoreWriter<T>,
525 note_kind: u32,
526 name_bytes: &[u8],
527 file: &mut dyn Read,
528 note_len: usize,
529) -> Result<usize, CoreError> {
530 let mut written = 0_usize;
531
532 let header_and_name = size_of::<Elf64_Nhdr>() + round_up(name_bytes.len() + 1, ELF_NOTE_ALIGN);
533 let data_len = note_len - header_and_name;
534 written += write_elf_note_header(writer, note_kind, name_bytes, data_len)?;
535
536 tracing::debug!(
537 "Writing note payload {} bytes at offset {}...",
538 data_len,
539 writer.stream_position()?
540 );
541
542 let max_len = data_len - size_of::<u32>();
543 let total = std::io::copy(&mut file.take(max_len as u64), writer)? as usize;
544 if file.read(&mut [0]).unwrap_or(0) != 0 {
545 tracing::warn!(truncated_len = total, "note will be truncated");
546 }
547 written += total;
548
549 if total < max_len {
550 written += writer.write_padding(max_len - total)?;
551 }
552
553 writer.write_all((total as u32).as_bytes())?;
554 written += size_of::<u32>();
555 written += writer.align_position(ELF_NOTE_ALIGN)?;
556
557 Ok(written)
558}
559
560fn write_process_info_note<T: Write, P: ProcessInfoSource>(
561 writer: &mut ElfCoreWriter<T>,
562 pv: &P,
563) -> Result<usize, CoreError> {
564 let mut written = 0_usize;
565
566 tracing::info!(
567 "Writing process info note at offset {}...",
568 writer.stream_position()?
569 );
570
571 for thread_view in pv.threads() {
575 if thread_view.tid == pv.pid() {
576 let pr_info = prpsinfo_t {
577 pr_state: thread_view.state,
578 pr_sname: thread_view.state,
579 pr_zomb: if thread_view.state == b'Z' { 1 } else { 0 },
580 pr_nice: thread_view.nice as u8,
581 pad0: 0,
582 pr_flag: thread_view.flags as u64,
583 pr_uid: thread_view.uid as u32,
584 pr_gid: thread_view.gid,
585 pr_pid: thread_view.tid as u32,
586 pr_ppid: thread_view.ppid as u32,
587 pr_pgrp: thread_view.pgrp as u32,
588 pr_sid: thread_view.session as u32,
589 pr_fname: {
590 let bytes = thread_view.comm.as_bytes();
591 let mut fname = [0_u8; 16];
592
593 for i in 0..fname.len() {
594 if i < bytes.len() {
595 fname[i] = bytes[i];
596 } else {
597 break;
598 }
599 }
600
601 fname
602 },
603 pr_psargs: {
604 let bytes = thread_view.cmd_line.as_bytes();
605 let mut args = [0_u8; 80];
606
607 for i in 0..args.len() {
608 if i < bytes.len() {
609 args[i] = bytes[i];
610 } else {
611 break;
612 }
613 }
614
615 args
616 },
617 };
618 written = write_elf_note(writer, NT_PRPSINFO, NOTE_NAME_CORE, pr_info.as_bytes())?;
619 break;
620 }
621 }
622
623 tracing::info!("Wrote {} bytes for the process info note", written);
624
625 Ok(written)
626}
627
628fn write_process_status_notes<T: Write, P: ProcessInfoSource>(
629 writer: &mut ElfCoreWriter<T>,
630 pv: &P,
631) -> Result<usize, CoreError> {
632 let mut total_written = 0_usize;
633
634 tracing::info!(
635 "Writing thread status notes at offset {}...",
636 writer.stream_position()?
637 );
638
639 for thread_view in pv.threads() {
640 let status = prstatus_t {
641 si_signo: thread_view.cursig as u32,
642 si_code: 0,
643 si_errno: 0,
644 pr_cursig: thread_view.cursig,
645 pad0: 0,
646 pr_sigpend: thread_view.sigpend,
647 pr_sighold: thread_view.sighold,
648 pr_pid: thread_view.tid as u32,
649 pr_ppid: thread_view.ppid as u32,
650 pr_pgrp: thread_view.pgrp as u32,
651 pr_sid: thread_view.session as u32,
652 pr_utime: pr_timeval_t {
653 tv_sec: thread_view.utime / 1000,
654 tv_usec: (thread_view.utime % 1000) * 1000,
655 },
656 pr_stime: pr_timeval_t {
657 tv_sec: thread_view.stime / 1000,
658 tv_usec: (thread_view.stime % 1000) * 1000,
659 },
660 pr_cutime: pr_timeval_t {
661 tv_sec: thread_view.cutime / 1000,
662 tv_usec: (thread_view.cutime % 1000) * 1000,
663 },
664 pr_cstime: pr_timeval_t {
665 tv_sec: thread_view.cstime / 1000,
666 tv_usec: (thread_view.cstime % 1000) * 1000,
667 },
668 pr_reg: thread_view.arch_state.greg_set(),
669 pr_fpvalid: 1,
670 pad1: 0,
671 };
672
673 let signals = siginfo_t {
674 si_signo: thread_view.cursig as u32,
675 si_errno: 0,
676 si_code: 0,
677 pad0: 0,
678 si_data: [0_u32; 28],
679 };
680
681 let mut written = write_elf_note(writer, NT_PRSTATUS, NOTE_NAME_CORE, status.as_bytes())?;
682 total_written += written;
683
684 for arch_component in thread_view.arch_state.components() {
685 written = write_elf_note(
686 writer,
687 arch_component.note_type,
688 arch_component.note_name,
689 arch_component.data.as_bytes(),
690 )?;
691 total_written += written;
692 }
693
694 written = write_elf_note(writer, NT_SIGINFO, NOTE_NAME_CORE, signals.as_bytes())?;
695 total_written += written;
696 }
697
698 tracing::info!(
699 "Wrote {} bytes for the thread status notes, {} notes",
700 total_written,
701 pv.threads().len()
702 );
703
704 Ok(total_written)
705}
706
707fn write_aux_vector_note<T: Write, P: ProcessInfoSource>(
708 writer: &mut ElfCoreWriter<T>,
709 pv: &P,
710) -> Result<usize, CoreError> {
711 tracing::info!(
712 "Writing auxiliary vector at offset {}...",
713 writer.stream_position()?
714 );
715
716 let written = pv
717 .aux_vector()
718 .map(|auxv| write_elf_note(writer, NT_AUXV, NOTE_NAME_CORE, auxv.as_bytes()))
719 .unwrap_or(Ok(0))?;
720
721 tracing::info!("Wrote {} bytes for the auxiliary vector", written);
722
723 Ok(written)
724}
725
726fn write_mapped_files_note<T: Write, P: ProcessInfoSource>(
727 writer: &mut ElfCoreWriter<T>,
728 pv: &P,
729) -> Result<usize, CoreError> {
730 tracing::info!(
731 "Writing mapped files note at offset {}...",
732 writer.stream_position()?
733 );
734
735 let written = pv
736 .mapped_files()
737 .map(|files| {
738 let mut data: Vec<u8> = Vec::with_capacity(pv.page_size());
739
740 let mut intro = MappedFilesNoteIntro {
741 file_count: 0,
742 page_size: 1,
743 };
744
745 for mapped_file in files {
746 intro.file_count += mapped_file.regions.len() as u64;
747 }
748
749 data.extend_from_slice(intro.as_bytes());
750
751 for mapped_file in files {
754 for region in &mapped_file.regions {
755 let item = MappedFilesNoteItem {
756 start_addr: region.begin,
757 end_addr: region.end,
758 page_count: region.offset, };
760 data.extend_from_slice(item.as_bytes());
761 }
762 }
763
764 for mapped_file in files {
765 for _ in &mapped_file.regions {
766 data.extend_from_slice(mapped_file.name.as_bytes());
767 data.push(0_u8);
768 }
769 }
770
771 write_elf_note(writer, NT_FILE, NOTE_NAME_CORE, data.as_bytes())
772 })
773 .unwrap_or(Ok(0))?;
774
775 tracing::info!("Wrote {} bytes for mapped files note", written);
776
777 Ok(written)
778}
779
780fn write_custom_notes<T: Write>(
781 writer: &mut ElfCoreWriter<T>,
782 custom_notes: &mut [CustomFileNote<'_>],
783) -> Result<usize, CoreError> {
784 let mut total_written = 0;
785
786 for note in custom_notes {
787 tracing::info!(
788 "Writing custom note \"{}\" at offset {}...",
789 note.name,
790 writer.stream_position()?
791 );
792
793 let written = write_elf_note_file(
794 writer,
795 0xffffffff,
796 note.name.as_bytes(),
797 &mut note.file,
798 note.note_len,
799 )?;
800
801 tracing::info!("Wrote {} bytes for the custom note", written);
802 total_written += written;
803 }
804
805 Ok(total_written)
806}
807
808fn write_elf_notes<T: Write, P: ProcessInfoSource>(
809 writer: &mut ElfCoreWriter<T>,
810 pv: &P,
811 note_sizes: &NoteSizes,
812 custom_notes: Option<&mut [CustomFileNote<'_>]>,
813) -> Result<usize, CoreError> {
814 let mut total_written = 0_usize;
815 let mut written;
816
817 tracing::info!("Writing notes at offset {}...", writer.stream_position()?);
818
819 if note_sizes.process_info != 0 {
820 written = write_process_info_note(writer, pv)?;
821 if written != note_sizes.process_info {
822 return Err(CoreError::InternalError(
823 "Mismatched process info note size",
824 ));
825 }
826 total_written += written;
827 }
828
829 if note_sizes.process_status != 0 {
830 written = write_process_status_notes(writer, pv)?;
831 if written != note_sizes.process_status {
832 return Err(CoreError::InternalError(
833 "Mismatched process status note size",
834 ));
835 }
836 total_written += written;
837 }
838
839 if note_sizes.aux_vector != 0 {
840 written = write_aux_vector_note(writer, pv)?;
841 if written != note_sizes.aux_vector {
842 return Err(CoreError::InternalError("Mismatched aux vector note size"));
843 }
844 total_written += written;
845 }
846
847 if note_sizes.mapped_files != 0 {
848 written = write_mapped_files_note(writer, pv)?;
849 if written != note_sizes.mapped_files {
850 return Err(CoreError::InternalError(
851 "Mismatched mapped files note size",
852 ));
853 }
854 total_written += written;
855 }
856
857 if let Some(custom_notes) = custom_notes {
858 written = write_custom_notes(writer, custom_notes)?;
859 if written != note_sizes.custom {
860 return Err(CoreError::InternalError("Mismatched custom note size"));
861 }
862 total_written += written;
863 }
864
865 tracing::info!("Wrote {} bytes for notes", total_written);
866
867 Ok(total_written)
868}
869
870fn write_va_region<T, P, M>(
871 writer: &mut ElfCoreWriter<T>,
872 va_region: &VaRegion,
873 pv: &P,
874 memory_reader: &mut M,
875) -> Result<usize, CoreError>
876where
877 T: Write,
878 P: ProcessInfoSource,
879 M: ReadProcessMemory,
880{
881 let mut dumped = 0_usize;
882 let mut address = va_region.begin;
883 let mut buffer = [0_u8; BUFFER_SIZE];
884
885 while address < va_region.end {
886 let len = std::cmp::min((va_region.end - address) as usize, BUFFER_SIZE);
887 match memory_reader.read_process_memory(address as usize, &mut buffer[..len]) {
888 Ok(bytes_read) => {
889 writer.write_all(&buffer[..bytes_read])?;
890
891 address += bytes_read as u64;
892 dumped += bytes_read;
893 }
894 Err(_) => {
895 debug_assert!(
904 pv.page_size().is_power_of_two(),
905 "Page size is expected to be a power of two"
906 );
907
908 let next_address = (pv.page_size() + address as usize) & !(pv.page_size() - 1);
910 let next_address = std::cmp::min(next_address, va_region.end as usize);
911 let dummy_data_size = next_address - address as usize;
912
913 let dummy_data: SmallVec<[u8; BUFFER_SIZE]> = smallvec![0xf1_u8; dummy_data_size];
914
915 writer.write_all(&dummy_data[..dummy_data_size])?;
916
917 address = next_address as u64;
918 dumped += dummy_data_size;
919 }
920 }
921 }
922
923 Ok(dumped)
924}
925
926fn write_va_regions<T, P, M>(
927 writer: &mut ElfCoreWriter<T>,
928 pv: &P,
929 memory_reader: &mut M,
930) -> Result<usize, CoreError>
931where
932 T: Write,
933 P: ProcessInfoSource,
934 M: ReadProcessMemory,
935{
936 let mut written = 0_usize;
937
938 tracing::info!(
939 "Writing memory content at offset {}...",
940 writer.stream_position()?
941 );
942
943 for va_region in pv.va_regions() {
944 let dumped = write_va_region(writer, va_region, pv, memory_reader)?;
945
946 written += dumped;
947
948 tracing::debug!(
949 "Saved {} bytes from region [0x{:x}; 0x{:x}] of size {}, current file offset {}",
950 dumped,
951 va_region.begin,
952 va_region.end,
953 va_region.end - va_region.begin,
954 writer.stream_position()?
955 );
956 }
957
958 tracing::info!("Wrote {} bytes for VA regions", written);
959
960 Ok(written)
961}
962
963pub struct CoreDumpBuilder<'a, P: ProcessInfoSource, M: ReadProcessMemory> {
968 pv: P,
969 custom_notes: Vec<CustomFileNote<'a>>,
970 memory_reader: M,
971}
972
973#[cfg(target_os = "linux")]
974impl<'a> CoreDumpBuilder<'a, ProcessView, LinuxProcessMemoryReader> {
975 pub fn new(
977 pid: libc::pid_t,
978 ) -> Result<CoreDumpBuilder<'a, ProcessView, LinuxProcessMemoryReader>, CoreError> {
979 let pv = ProcessView::new(pid)?;
980 let memory_reader = ProcessView::create_memory_reader(pv.pid)?;
981
982 Ok(CoreDumpBuilder {
983 pv,
984 custom_notes: Vec::new(),
985 memory_reader,
986 })
987 }
988}
989
990impl<'a, P: ProcessInfoSource, M: ReadProcessMemory> CoreDumpBuilder<'a, P, M> {
991 pub fn from_source(source: P, memory_reader: M) -> CoreDumpBuilder<'a, P, M> {
993 CoreDumpBuilder {
994 pv: source,
995 custom_notes: Vec::new(),
996 memory_reader,
997 }
998 }
999
1000 pub fn add_custom_file_note(
1002 &mut self,
1003 name: &str,
1004 file: &'a mut dyn Read,
1005 note_len: usize,
1006 ) -> &mut Self {
1007 self.custom_notes.push(CustomFileNote {
1008 name: name.to_owned(),
1009 file,
1010 note_len,
1011 });
1012 self
1013 }
1014
1015 pub fn write<T: Write>(mut self, writer: T) -> Result<usize, CoreError> {
1020 write_core_dump_inner(
1021 writer,
1022 &self.pv,
1023 Some(&mut self.custom_notes),
1024 &mut self.memory_reader,
1025 )
1026 }
1027}
1028
1029#[cfg(test)]
1030mod tests {
1031 use crate::{ArchState, ThreadView};
1032
1033 use super::*;
1034
1035 struct MockProcessInfoSource {
1036 pid: i32,
1037 page_size: usize,
1038 regions: Vec<VaRegion>,
1039 threads: Vec<ThreadView>,
1040 }
1041
1042 impl ProcessInfoSource for MockProcessInfoSource {
1043 fn pid(&self) -> i32 {
1044 self.pid
1045 }
1046
1047 fn page_size(&self) -> usize {
1048 self.page_size
1049 }
1050
1051 fn threads(&self) -> &[ThreadView] {
1052 &self.threads
1053 }
1054
1055 fn aux_vector(&self) -> Option<&[Elf64_Auxv]> {
1056 None
1057 }
1058
1059 fn mapped_files(&self) -> Option<&[MappedFile]> {
1060 None
1061 }
1062
1063 fn va_regions(&self) -> &[VaRegion] {
1064 &self.regions
1065 }
1066 }
1067
1068 struct MockMemoryReader {}
1069
1070 impl ReadProcessMemory for MockMemoryReader {
1071 fn read_process_memory(
1072 &mut self,
1073 _address: usize,
1074 buffer: &mut [u8],
1075 ) -> Result<usize, CoreError> {
1076 Ok(buffer.len())
1077 }
1078 }
1079
1080 #[test]
1082 fn test_custom_source_no_threads() {
1083 let custom_source = MockProcessInfoSource {
1084 pid: 1,
1085 page_size: 4096,
1086 regions: vec![],
1087 threads: vec![],
1088 };
1089
1090 let memory_reader = MockMemoryReader {};
1091
1092 let core_dump_builder = CoreDumpBuilder::from_source(custom_source, memory_reader);
1093 let res = core_dump_builder.write(std::io::sink());
1094 matches!(res, Err(CoreError::CustomSourceInfo));
1095 }
1096
1097 #[test]
1099 fn test_custom_source_no_regions() {
1100 let custom_source = MockProcessInfoSource {
1101 pid: 1,
1102 page_size: 4096,
1103 regions: vec![],
1104 threads: vec![ThreadView {
1105 flags: 0, tid: 1,
1107 uid: 0, gid: 0, comm: "".to_string(), ppid: 0, pgrp: 0, nice: 0, state: 0, utime: 0, stime: 0, cutime: 0, cstime: 0, cursig: 0, session: 0, sighold: 0, sigpend: 0, cmd_line: "".to_string(),
1123
1124 arch_state: Box::new(ArchState {
1125 #[cfg(target_arch = "aarch64")]
1126 gpr_state: vec![0; 34],
1127 #[cfg(target_arch = "x86_64")]
1128 gpr_state: vec![0; 27],
1129 components: vec![],
1130 }),
1131 }],
1132 };
1133
1134 let memory_reader = MockMemoryReader {};
1135
1136 let core_dump_builder = CoreDumpBuilder::from_source(custom_source, memory_reader);
1137 let res = core_dump_builder.write(std::io::sink());
1138 matches!(res, Err(CoreError::CustomSourceInfo));
1139 }
1140
1141 #[test]
1144 fn test_custom_source_success() {
1145 let slice = [0_u8; 4096];
1146 let region = VaRegion {
1148 begin: 0x1000,
1149 end: 0x2000,
1150 offset: slice.as_ptr() as u64,
1151 mapped_file_name: None,
1152 protection: VaProtection {
1153 read: true,
1154 write: false,
1155 execute: false,
1156 is_private: false,
1157 },
1158 };
1159 let custom_source = MockProcessInfoSource {
1160 pid: 1,
1161 page_size: 4096,
1162 regions: vec![region],
1163 threads: vec![ThreadView {
1164 flags: 0, tid: 1,
1166 uid: 0, gid: 0, comm: "".to_string(), ppid: 0, pgrp: 0, nice: 0, state: 0, utime: 0, stime: 0, cutime: 0, cstime: 0, cursig: 0, session: 0, sighold: 0, sigpend: 0, cmd_line: "".to_string(),
1182
1183 arch_state: Box::new(ArchState {
1184 #[cfg(target_arch = "aarch64")]
1185 gpr_state: vec![0; 34],
1186 #[cfg(target_arch = "x86_64")]
1187 gpr_state: vec![0; 27],
1188 components: vec![],
1189 }),
1190 }],
1191 };
1192
1193 let memory_reader = MockMemoryReader {};
1194
1195 let core_dump_builder = CoreDumpBuilder::from_source(custom_source, memory_reader);
1196 let res = core_dump_builder.write(std::io::sink());
1197 res.as_ref().unwrap();
1198 assert!(res.is_ok());
1199 }
1200}