Skip to main content

elfcore/
coredump.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Code for collecting system information, thread status information, and saving core dump files.
5//!
6//! Panics must be avoided as that may leave the target process in a bad state
7//! The code below must not do backward seeks so that the content can be streamed.
8
9use 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
31// For optimal performance should be in [8KiB; 64KiB] range.
32// Selected 64 KiB as data on various hardware platforms shows
33// peak performance in this case.
34const BUFFER_SIZE: usize = 0x10000;
35
36/// Wraps a Write to emulate forward seeks
37struct 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/// Struct that describes a region's access permissions
98#[derive(Debug)]
99pub struct VaProtection {
100    /// Field that indicates this is a private region
101    pub is_private: bool,
102    /// Read permissions
103    pub read: bool,
104    /// Write permissions
105    pub write: bool,
106    /// Execute permissions
107    pub execute: bool,
108}
109
110/// Struct that describes a memory region
111#[derive(Debug)]
112pub struct VaRegion {
113    /// Virtual address start
114    pub begin: u64,
115    /// Virtual address end
116    pub end: u64,
117    /// Offset in memory where the region resides
118    pub offset: u64,
119    /// Access permissions
120    pub protection: VaProtection,
121    /// Mapped file name
122    pub mapped_file_name: Option<String>,
123}
124
125/// Type that describes a mapped file region
126#[derive(Debug)]
127pub struct MappedFileRegion {
128    /// Virtual address start
129    pub begin: u64,
130    /// Virtual address end
131    pub end: u64,
132    /// Offset in memory where the region resides
133    pub offset: u64,
134}
135
136/// Type that describes a mapped file
137#[derive(Debug)]
138pub struct MappedFile {
139    /// File name
140    pub name: String,
141    /// File regions
142    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
155/// Information about a custom note that will be created from a file
156struct CustomFileNote<'a> {
157    /// Name used in the ELF note header
158    pub name: String,
159    /// (nonblocking) file to read from
160    pub file: &'a mut dyn Read,
161    /// Fixed size of the note, including header, name, data, and size
162    /// File contents will be padded or truncated to fit.
163    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    // Calculate auxv size - do not count if no auxv
194    let aux_vector = pv
195        .aux_vector()
196        .map(|auxv| header_and_name + size_of_val(auxv))
197        .unwrap_or(0);
198
199    // Calculate mapped files size - do not count if no mapped files
200    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/// Writes an ELF core dump file
246///
247/// # Agruments:
248/// * `writer` - a `std::io::Write` the data is sent to.
249/// * `pv` - a `ProcessView` reference.
250///
251/// To access new functionality, use [`CoreDumpBuilder`]
252#[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    // Check if the process is valid: has threads and va regions
273    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, &note_sizes)?;
292    total_written += writer.align_position(ELF_HEADER_ALIGN)?;
293    total_written += write_elf_notes(&mut writer, pv, &note_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    // Might be optimized if alignmet is a power of 2
304
305    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; // TODO Assuming Little-Endian
327    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, // PT_NOTE and VA regions
339        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    // SAFETY: Elf64_Ehdr is repr(C) with no padding bytes,
353    // so all byte patterns are valid.
354    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    // There will a header for PT_NOTE, and
377    // as many PT_LOAD as there are VA regions.
378    // Notes are situated right after the headers.
379
380    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, // Notes are written after the headers
394        };
395
396        // SAFETY: Elf64_Phdr is repr(C) with no padding bytes,
397        // so all byte patterns are valid.
398        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        // SAFETY: Elf64_Phdr is repr(C) with no padding bytes,
440        // so all byte patterns are valid.
441        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    // namesz accounts for the terminating zero.
467    // ELF-64 Object File Format, Version 1.5 claims that is not required
468    // but readelf and gdb refuse to read it otherwise
469
470    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    // Threads and processes in Linux are LWP (Light-weight processes)
572    // TODO That's O(N) at worst, does that hurt?
573
574    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            // TODO: Sort by virtual address? Ranges always appear sorted in proc/maps
752
753            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, // No scaling
759                    };
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                // Every precaution has been taken to read the accessible
896                // memory only and still something has gone wrong. Nevertheless,
897                // have to make forward progress to dump exactly as much memory
898                // as the caller expects.
899                //
900                // Save dummy data up to the next page boundary.
901
902                // Page size is a power of two on modern platforms.
903                debug_assert!(
904                    pv.page_size().is_power_of_two(),
905                    "Page size is expected to be a power of two"
906                );
907
908                // Round up with bit twiddling as the page size is a power of two.
909                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
963/// A builder for generating a core dump of a process
964/// optionally with custom notes with content from files
965/// This also supports generating core dumps with information
966/// from a custom source.
967pub 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    /// Create a new core dump builder for the process with the provided PID
976    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    /// Create a new core dump builder from a custom `ProcessInfoSource`
992    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    /// Add the contents of a file as a custom note to the core dump
1001    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    /// Writes an ELF core dump file
1016    ///
1017    /// # Agruments:
1018    /// * `writer` - a `std::io::Write` the data is sent to.
1019    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 that writing a core dump using a custom source with no threads provided fails
1081    #[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 that writing a core dump using a custom source with no regions provided fails
1098    #[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, // Kernel flags for the process
1106                tid: 1,
1107                uid: 0,               // User ID
1108                gid: 0,               // Group ID
1109                comm: "".to_string(), // Command name
1110                ppid: 0,              // Parent PID
1111                pgrp: 0,              // Process group ID
1112                nice: 0,              // Nice value
1113                state: 0,             // Process state
1114                utime: 0,             // User time
1115                stime: 0,             // System time
1116                cutime: 0,            // Children User time
1117                cstime: 0,            // Children User time
1118                cursig: 0,            // Current signal
1119                session: 0,           // Session ID of the process
1120                sighold: 0,           // Blocked signal
1121                sigpend: 0,           // Pending signal
1122                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 that writing a core dump using a custom source with minimal info(threads, va regions,
1142    /// pid) succeeds
1143    #[test]
1144    fn test_custom_source_success() {
1145        let slice = [0_u8; 4096];
1146        // region that maps on the above slice
1147        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, // Kernel flags for the process
1165                tid: 1,
1166                uid: 0,               // User ID
1167                gid: 0,               // Group ID
1168                comm: "".to_string(), // Command name
1169                ppid: 0,              // Parent PID
1170                pgrp: 0,              // Process group ID
1171                nice: 0,              // Nice value
1172                state: 0,             // Process state
1173                utime: 0,             // User time
1174                stime: 0,             // System time
1175                cutime: 0,            // Children User time
1176                cstime: 0,            // Children User time
1177                cursig: 0,            // Current signal
1178                session: 0,           // Session ID of the process
1179                sighold: 0,           // Blocked signal
1180                sigpend: 0,           // Pending signal
1181                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}