Skip to main content

minidump_writer/linux/
module_reader.rs

1use {
2    super::{
3        process_inspection::{self, ProcessInspector},
4        serializers::*,
5    },
6    crate::module_reader::{ModuleMemoryReadError, ProcessModuleMemoryReader},
7    crate::{minidump_format::GUID, serializers::*},
8    goblin::{
9        container::{Container, Ctx, Endian},
10        elf,
11    },
12    std::{
13        borrow::Cow,
14        ffi::{CStr, OsString},
15        path::Path,
16    },
17};
18
19type Error = ModuleReaderError;
20
21const NOTE_SECTION_NAME: &[u8] = b".note.gnu.build-id\0";
22
23#[derive(Debug, thiserror::Error, serde::Serialize)]
24pub enum ModuleReaderError {
25    #[error("failed to map modile into memory")]
26    MapModuleFailed(#[source] process_inspection::Error),
27    #[error("failed to read module file ({path}): {error}")]
28    MapFile {
29        path: std::path::PathBuf,
30        #[source]
31        #[serde(serialize_with = "serialize_io_error")]
32        error: std::io::Error,
33    },
34    #[error(transparent)]
35    ReadModuleMemory(#[from] ModuleMemoryReadError),
36    #[error("failed to parse ELF memory: {0}")]
37    Parsing(
38        #[from]
39        #[serde(serialize_with = "serialize_goblin_error")]
40        goblin::error::Error,
41    ),
42    #[error("no build id notes in program headers")]
43    NoProgramHeaderNote,
44    #[error("no string table available to locate note sections")]
45    NoStrTab,
46    #[error("no build id note sections")]
47    NoSectionNote,
48    #[error("the ELF data contains no program headers")]
49    NoProgramHeaders,
50    #[error("the ELF data contains no sections")]
51    NoSections,
52    #[error("the ELF data does not have a .text section from which to generate a build id")]
53    NoTextSection,
54    #[error(
55        "failed to calculate build id\n\
56    ... from program headers: {program_headers}\n\
57    ... from sections: {section}\n\
58    ... from the text section: {section}"
59    )]
60    NoBuildId {
61        program_headers: Box<Self>,
62        section: Box<Self>,
63        generated: Box<Self>,
64    },
65    #[error("no dynamic string table section")]
66    NoDynStrSection,
67    #[error("a string in the strtab did not have a terminating nul byte")]
68    StrTabNoNulByte,
69    #[error("no SONAME found in dynamic linking information")]
70    NoSoNameEntry,
71    #[error("no dynamic linking information section")]
72    NoDynamicSection,
73    #[error(
74        "failed to retrieve soname\n\
75    ... from program headers: {program_headers}\n\
76    ... from sections: {section}"
77    )]
78    NoSoName {
79        program_headers: Box<Self>,
80        section: Box<Self>,
81    },
82    #[error("Not safe to open mapping {}", .0.to_string_lossy())]
83    NotSafeToOpenMapping(OsString),
84    #[error("Mmapped file empty or not an ELF file")]
85    MmapSanityCheckFailed,
86    #[error("Linux gate location doesn't fit in the required integer type")]
87    LinuxGateNotConvertable(
88        #[source]
89        #[serde(skip)]
90        std::num::TryFromIntError,
91    ),
92    #[error("IO Error")]
93    FileError(
94        #[source]
95        #[serde(serialize_with = "serialize_io_error")]
96        std::io::Error,
97    ),
98    #[error("failed to map file")]
99    MapError(
100        #[source]
101        #[serde(serialize_with = "serialize_io_error")]
102        std::io::Error,
103    ),
104}
105
106#[inline]
107fn is_executable_section(header: &elf::SectionHeader) -> bool {
108    header.sh_type == elf::section_header::SHT_PROGBITS
109        && header.sh_flags & u64::from(elf::section_header::SHF_ALLOC) != 0
110        && header.sh_flags & u64::from(elf::section_header::SHF_EXECINSTR) != 0
111}
112
113/// Return bytes to use as a build id, computed by hashing the given data.
114///
115/// This provides `size_of::<GUID>` bytes to keep identifiers produced by this function compatible
116/// with other build ids.
117fn build_id_from_bytes(data: &[u8]) -> Vec<u8> {
118    // Only provide mem::size_of(MDGUID) bytes to keep identifiers produced by this
119    // function backwards-compatible.
120    data.chunks(std::mem::size_of::<GUID>()).fold(
121        vec![0u8; std::mem::size_of::<GUID>()],
122        |mut bytes, chunk| {
123            bytes
124                .iter_mut()
125                .zip(chunk.iter())
126                .for_each(|(b, c)| *b ^= *c);
127            bytes
128        },
129    )
130}
131
132// `name` should be null-terminated
133fn section_header_with_name<'sc, MM: ReadModuleMemory>(
134    section_headers: &'sc elf::SectionHeaders,
135    strtab_index: usize,
136    name: &[u8],
137    module_memory: &MM,
138) -> Result<Option<&'sc elf::SectionHeader>, Error> {
139    let strtab_section_header = section_headers
140        .get(strtab_index)
141        .and_then(|hdr| (hdr.sh_type == elf::section_header::SHT_STRTAB).then_some(hdr))
142        .ok_or(Error::NoStrTab)?;
143
144    for header in section_headers {
145        let sh_name = header.sh_name as u64;
146        if sh_name >= strtab_section_header.sh_size {
147            log::warn!("invalid sh_name offset for {name:?}");
148            continue;
149        }
150        if sh_name + name.len() as u64 >= strtab_section_header.sh_size {
151            // This can't be a match.
152            continue;
153        }
154        let n = module_memory.read(strtab_section_header.sh_offset + sh_name, name.len() as u64)?;
155        if name == &*n {
156            return Ok(Some(header));
157        }
158    }
159    Ok(None)
160}
161
162pub fn read_build_id_from_file(
163    process_inspector: &ProcessInspector,
164    path: &Path,
165) -> Result<Vec<u8>, Error> {
166    let module_memory_reader = process_inspector
167        .map_module_into_memory(path, 0)
168        .map_err(Error::MapModuleFailed)?;
169    read_build_id_from_module(module_memory_reader)
170}
171
172pub fn read_build_id_from_module(module_memory: impl ReadModuleMemory) -> Result<Vec<u8>, Error> {
173    let reader = ModuleReader::new(module_memory)?;
174    let program_headers = match reader.build_id_from_program_headers() {
175        Ok(v) => return Ok(v),
176        Err(e) => Box::new(e),
177    };
178    let section = match reader.build_id_from_section() {
179        Ok(v) => return Ok(v),
180        Err(e) => Box::new(e),
181    };
182    let generated = match reader.build_id_generate_from_text() {
183        Ok(v) => return Ok(v),
184        Err(e) => Box::new(e),
185    };
186    Err(Error::NoBuildId {
187        program_headers,
188        section,
189        generated,
190    })
191}
192
193pub fn read_soname_from_file(
194    process_inspector: &ProcessInspector,
195    path: &Path,
196    offset: usize,
197) -> Result<String, Error> {
198    let offset = u64::try_from(offset).map_err(Error::LinuxGateNotConvertable)?;
199
200    // It is unsafe to attempt to open a mapped file that lives under /dev,
201    // because the semantics of the open may be driver-specific so we'd risk
202    // hanging the crash dumper. And a file in /dev/ almost certainly has no
203    // ELF file identifier anyways.
204    if path.starts_with("/dev/") {
205        return Err(Error::NotSafeToOpenMapping(path.as_os_str().to_os_string()));
206    }
207
208    let module_memory_reader = process_inspector
209        .map_module_into_memory(path, offset)
210        .map_err(Error::MapModuleFailed)?;
211
212    let memory_len = module_memory_reader.len().map_err(Error::MapModuleFailed)?;
213
214    if memory_len < elf::header::SELFMAG {
215        return Err(Error::MmapSanityCheckFailed);
216    }
217
218    read_soname_from_module(module_memory_reader)
219}
220
221pub fn read_soname_from_module(module_memory: impl ReadModuleMemory) -> Result<String, Error> {
222    let reader = ModuleReader::new(module_memory)?;
223    let program_headers = match reader.soname_from_program_headers() {
224        Ok(v) => return Ok(v),
225        Err(e) => Box::new(e),
226    };
227    let section = match reader.soname_from_sections() {
228        Ok(v) => return Ok(v),
229        Err(e) => Box::new(e),
230    };
231    Err(Error::NoSoName {
232        program_headers,
233        section,
234    })
235}
236
237struct DynIter<'a> {
238    data: &'a [u8],
239    offset: usize,
240    ctx: Ctx,
241}
242
243impl<'a> DynIter<'a> {
244    pub fn new(data: &'a [u8], ctx: Ctx) -> Self {
245        DynIter {
246            data,
247            offset: 0,
248            ctx,
249        }
250    }
251}
252
253impl Iterator for DynIter<'_> {
254    type Item = Result<elf::dynamic::Dyn, Error>;
255
256    fn next(&mut self) -> Option<Self::Item> {
257        use scroll::Pread;
258        let dyn_: elf::dynamic::Dyn = match self.data.gread_with(&mut self.offset, self.ctx) {
259            Ok(v) => v,
260            Err(e) => return Some(Err(e.into())),
261        };
262        if dyn_.d_tag == elf::dynamic::DT_NULL {
263            None
264        } else {
265            Some(Ok(dyn_))
266        }
267    }
268}
269
270pub struct ModuleReader<MM> {
271    module_memory: MM,
272    header: elf::Header,
273    context: Ctx,
274}
275
276impl<MM: ReadModuleMemory> ModuleReader<MM> {
277    pub fn new(module_memory: MM) -> Result<ModuleReader<MM>, Error> {
278        // We could use `Ctx::default()` (which defaults to the native system), however to be extra
279        // permissive we'll just use a 64-bit ("Big") context which would result in the largest
280        // possible header size.
281        let header_size = elf::Header::size(Ctx::new(Container::Big, Endian::default()));
282        let header_data = module_memory.read(0, header_size as u64)?;
283        let header = elf::Elf::parse_header(&header_data)?;
284        let context = Ctx::new(header.container()?, header.endianness()?);
285
286        Ok(Self {
287            module_memory,
288            header,
289            context,
290        })
291    }
292
293    /// Find a note referenced by the program headers.
294    pub fn find_program_note(
295        &self,
296        note_type: u32,
297        note_size: usize,
298        note_name: &str,
299    ) -> Result<Option<Vec<u8>>, Error> {
300        let program_headers = self.read_program_headers()?;
301        for header in program_headers {
302            if header.p_type != elf::program_header::PT_NOTE
303                || (header.p_flags & elf::program_header::PF_R) == 0
304                || (header.p_memsz as usize) < note_size
305            {
306                continue;
307            }
308
309            if let Some(data) = self.find_note(
310                header.p_offset,
311                header.p_filesz,
312                header.p_align,
313                note_type,
314                note_size,
315                note_name,
316            )? {
317                return Ok(Some(data));
318            }
319        }
320        Ok(None)
321    }
322
323    /// Read the SONAME using program headers to locate dynamic library information.
324    pub fn soname_from_program_headers(&self) -> Result<String, Error> {
325        let program_headers = self.read_program_headers()?;
326
327        let dynamic_segment_header = program_headers
328            .iter()
329            .find(|h| h.p_type == elf::program_header::PT_DYNAMIC)
330            .ok_or(Error::NoDynamicSection)?;
331
332        let dynamic_section = self.read_segment(dynamic_segment_header)?;
333
334        let mut soname_strtab_offset = None;
335        let mut strtab_addr = None;
336        let mut strtab_size = None;
337        for dyn_ in DynIter::new(&dynamic_section, self.context) {
338            let dyn_ = dyn_?;
339            match dyn_.d_tag {
340                elf::dynamic::DT_SONAME => soname_strtab_offset = Some(dyn_.d_val),
341                elf::dynamic::DT_STRTAB => strtab_addr = Some(dyn_.d_val),
342                elf::dynamic::DT_STRSZ => strtab_size = Some(dyn_.d_val),
343                _ => (),
344            }
345        }
346
347        match (strtab_addr, strtab_size, soname_strtab_offset) {
348            (None, _, _) | (_, None, _) => Err(Error::NoDynStrSection),
349            (_, _, None) => Err(Error::NoSoNameEntry),
350            (Some(addr), Some(size), Some(offset)) => {
351                // If loaded in memory, the address will be altered to be absolute.
352                if offset < size {
353                    self.read_name_from_strtab(
354                        self.module_memory
355                            .absolute_to_relative(addr)
356                            .unwrap_or(addr),
357                        size,
358                        offset,
359                    )
360                } else {
361                    log::warn!("soname strtab offset ({offset}) exceeds strtab size ({size})");
362                    Err(Error::NoSoNameEntry)
363                }
364            }
365        }
366    }
367
368    /// Read the SONAME using section headers to locate dynamic library information.
369    pub fn soname_from_sections(&self) -> Result<String, Error> {
370        let section_headers = self.read_section_headers()?;
371
372        let dynamic_section_header = section_headers
373            .iter()
374            .find(|h| h.sh_type == elf::section_header::SHT_DYNAMIC)
375            .ok_or(Error::NoDynamicSection)?;
376
377        let dynstr_section_header =
378            match section_headers.get(dynamic_section_header.sh_link as usize) {
379                Some(header) if header.sh_type == elf::section_header::SHT_STRTAB => header,
380                _ => section_header_with_name(
381                    &section_headers,
382                    self.header.e_shstrndx as usize,
383                    b".dynstr\0",
384                    &self.module_memory,
385                )?
386                .ok_or(Error::NoDynStrSection)?,
387            };
388
389        let dynamic_section = self.module_memory.read(
390            self.section_offset(dynamic_section_header),
391            dynamic_section_header.sh_size,
392        )?;
393
394        for dyn_ in DynIter::new(&dynamic_section, self.context) {
395            let dyn_ = dyn_?;
396            if dyn_.d_tag == elf::dynamic::DT_SONAME {
397                let name_offset = dyn_.d_val;
398                if name_offset < dynstr_section_header.sh_size {
399                    return self.read_name_from_strtab(
400                        self.section_offset(dynstr_section_header),
401                        dynstr_section_header.sh_size,
402                        name_offset,
403                    );
404                } else {
405                    log::warn!(
406                        "soname offset ({name_offset}) exceeds dynstr section size ({})",
407                        dynstr_section_header.sh_size
408                    );
409                }
410            }
411        }
412
413        Err(Error::NoSoNameEntry)
414    }
415
416    /// Read the build id from a program header note.
417    pub fn build_id_from_program_headers(&self) -> Result<Vec<u8>, Error> {
418        let program_headers = self.read_program_headers()?;
419        for header in program_headers {
420            if header.p_type != elf::program_header::PT_NOTE {
421                continue;
422            }
423            if let Ok(Some(result)) =
424                self.find_build_id_note(header.p_offset, header.p_filesz, header.p_align)
425            {
426                return Ok(result);
427            }
428        }
429        Err(Error::NoProgramHeaderNote)
430    }
431
432    /// Read the build id from a notes section.
433    pub fn build_id_from_section(&self) -> Result<Vec<u8>, Error> {
434        let section_headers = self.read_section_headers()?;
435
436        let header = section_header_with_name(
437            &section_headers,
438            self.header.e_shstrndx as usize,
439            NOTE_SECTION_NAME,
440            &self.module_memory,
441        )?
442        .ok_or(Error::NoSectionNote)?;
443
444        match self.find_build_id_note(header.sh_offset, header.sh_size, header.sh_addralign) {
445            Ok(Some(v)) => Ok(v),
446            Ok(None) => Err(Error::NoSectionNote),
447            Err(e) => Err(e),
448        }
449    }
450
451    /// Generate a build id by hashing the first page of the text section.
452    pub fn build_id_generate_from_text(&self) -> Result<Vec<u8>, Error> {
453        let Some(text_header) = self
454            .read_section_headers()?
455            .into_iter()
456            .find(is_executable_section)
457        else {
458            return Err(Error::NoTextSection);
459        };
460
461        // Take at most one page of the text section (we assume page size is 4096 bytes).
462        let len = std::cmp::min(4096, text_header.sh_size);
463        let text_data = self.module_memory.read(text_header.sh_offset, len)?;
464        Ok(build_id_from_bytes(&text_data))
465    }
466
467    fn read_segment<'a>(&'a self, header: &elf::ProgramHeader) -> Result<Cow<'a, [u8]>, Error> {
468        let (offset, size) = if self.module_memory.is_process_memory() {
469            (header.p_vaddr, header.p_memsz)
470        } else {
471            (header.p_offset, header.p_filesz)
472        };
473
474        self.module_memory.read(offset, size).map_err(|e| e.into())
475    }
476
477    fn read_name_from_strtab(
478        &self,
479        strtab_offset: u64,
480        strtab_size: u64,
481        name_offset: u64,
482    ) -> Result<String, Error> {
483        assert!(name_offset < strtab_size);
484        let name = self
485            .module_memory
486            .read(strtab_offset + name_offset, strtab_size - name_offset)?;
487        CStr::from_bytes_until_nul(&name)
488            .map(|s| s.to_string_lossy().into_owned())
489            .map_err(|_| Error::StrTabNoNulByte)
490    }
491
492    fn section_offset(&self, header: &elf::SectionHeader) -> u64 {
493        if self.module_memory.is_process_memory() {
494            header.sh_addr
495        } else {
496            header.sh_offset
497        }
498    }
499
500    fn read_program_headers(&self) -> Result<elf::ProgramHeaders, Error> {
501        if self.header.e_phoff == 0 {
502            return Err(Error::NoProgramHeaders);
503        }
504        let program_headers_data = self.module_memory.read(
505            self.header.e_phoff,
506            self.header.e_phentsize as u64 * self.header.e_phnum as u64,
507        )?;
508        let program_headers = elf::ProgramHeader::parse(
509            &program_headers_data,
510            0,
511            self.header.e_phnum as usize,
512            self.context,
513        )?;
514        Ok(program_headers)
515    }
516
517    fn read_section_headers(&self) -> Result<elf::SectionHeaders, Error> {
518        if self.header.e_shoff == 0 {
519            return Err(Error::NoSections);
520        }
521
522        let section_headers_data = self.module_memory.read(
523            self.header.e_shoff,
524            self.header.e_shentsize as u64 * self.header.e_shnum as u64,
525        )?;
526        // Use `parse_from` rather than `parse`, which allows a 0 offset.
527        let section_headers = elf::SectionHeader::parse_from(
528            &section_headers_data,
529            0,
530            self.header.e_shnum as usize,
531            self.context,
532        )?;
533        Ok(section_headers)
534    }
535
536    fn find_build_id_note(
537        &self,
538        offset: u64,
539        size: u64,
540        alignment: u64,
541    ) -> Result<Option<Vec<u8>>, Error> {
542        self.find_note(
543            offset,
544            size,
545            alignment,
546            elf::note::NT_GNU_BUILD_ID,
547            0,
548            "GNU",
549        )
550    }
551
552    fn find_note(
553        &self,
554        offset: u64,
555        size: u64,
556        alignment: u64,
557        note_type: u32,
558        note_min_size: usize,
559        note_name: &str,
560    ) -> Result<Option<Vec<u8>>, Error> {
561        let notes = self.module_memory.read(offset, size)?;
562        for note in (elf::note::NoteDataIterator {
563            data: &notes,
564            // Note that `NoteDataIterator::size` is poorly named, it is actually an end offset. In
565            // this case since our start offset is 0 we still set it to the size.
566            size: size as usize,
567            offset: 0,
568            ctx: (alignment as usize, self.context),
569        }) {
570            let Ok(note) = note else { break };
571            if note.name == note_name
572                && note.n_type == note_type
573                && note.desc.len() >= note_min_size
574            {
575                return Ok(Some(note.desc.to_owned()));
576            }
577        }
578        Ok(None)
579    }
580}
581
582pub trait ReadModuleMemory {
583    fn read<'a>(&'a self, offset: u64, length: u64)
584    -> Result<Cow<'a, [u8]>, ModuleMemoryReadError>;
585    fn absolute_to_relative(&self, addr: u64) -> Option<u64>;
586    /// Calculates the absolute address of the specified relative address
587    fn relative_to_absolute(&self, addr: u64) -> Option<u64>;
588    fn is_process_memory(&self) -> bool;
589}
590
591impl<'a> ReadModuleMemory for ProcessModuleMemoryReader<'a> {
592    fn read(&self, offset: u64, length: u64) -> Result<Cow<'_, [u8]>, ModuleMemoryReadError> {
593        self.read(offset, length)
594    }
595    fn absolute_to_relative(&self, addr: u64) -> Option<u64> {
596        addr.checked_sub(self.start_address)
597    }
598    /// Calculates the absolute address of the specified relative address
599    fn relative_to_absolute(&self, addr: u64) -> Option<u64> {
600        self.start_address.checked_add(addr)
601    }
602    fn is_process_memory(&self) -> bool {
603        true
604    }
605}
606
607impl<T: ReadModuleMemory + ?Sized> ReadModuleMemory for &T {
608    fn read(&self, offset: u64, length: u64) -> Result<Cow<'_, [u8]>, ModuleMemoryReadError> {
609        T::read(self, offset, length)
610    }
611    fn absolute_to_relative(&self, addr: u64) -> Option<u64> {
612        T::absolute_to_relative(self, addr)
613    }
614    fn relative_to_absolute(&self, addr: u64) -> Option<u64> {
615        T::relative_to_absolute(self, addr)
616    }
617    fn is_process_memory(&self) -> bool {
618        T::is_process_memory(self)
619    }
620}
621
622#[cfg(test)]
623mod test {
624    use super::*;
625
626    /// This is a small (but valid) 64-bit little-endian elf executable with the following layout:
627    /// * ELF header
628    /// * program header: text segment
629    /// * program header: note
630    /// * program header: dynamic
631    /// * section header: null
632    /// * section header: .text
633    /// * section header: .note.gnu.build-id
634    /// * section header: .shstrtab
635    /// * section header: .dynamic
636    /// * section header: .dynstr
637    /// * note header (build id note)
638    /// * shstrtab
639    /// * dynamic (SONAME/STRTAB/STRSZ)
640    /// * dynstr (SONAME string = libfoo.so.1)
641    /// * program (calls exit(0))
642    const TINY_ELF: &[u8] = &[
643        0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
644        0x00, 0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x03, 0x40, 0x00, 0x00, 0x00,
645        0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe8, 0x00, 0x00, 0x00, 0x00,
646        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, 0x03, 0x00, 0x40, 0x00,
647        0x06, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0a, 0x03, 0x00,
648        0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x03, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
649        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
650        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
651        0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
652        0x00, 0x68, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
653        0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
654        0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
655        0x00, 0x00, 0x00, 0x00, 0xbd, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbd, 0x02, 0x40,
656        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
657        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
658        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
659        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
660        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
661        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
662        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
663        0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x03, 0x40,
664        0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00,
665        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
666        0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
667        0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
668        0x00, 0x68, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x02, 0x00, 0x00, 0x00, 0x00,
669        0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
670        0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
671        0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
672        0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x02,
673        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
674        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
675        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00,
676        0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbd, 0x02, 0x40, 0x00, 0x00, 0x00,
677        0x00, 0x00, 0xbd, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00,
678        0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
679        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00,
680        0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0x02,
681        0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d,
682        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
683        0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
684        0x00, 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x47, 0x4e,
685        0x55, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
686        0x0e, 0x0f, 0x10, 0x00, 0x2e, 0x74, 0x65, 0x78, 0x74, 0x00, 0x2e, 0x6e, 0x6f, 0x74, 0x65,
687        0x2e, 0x67, 0x6e, 0x75, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2d, 0x69, 0x64, 0x00, 0x2e,
688        0x73, 0x68, 0x73, 0x74, 0x72, 0x74, 0x61, 0x62, 0x00, 0x2e, 0x64, 0x79, 0x6e, 0x61, 0x6d,
689        0x69, 0x63, 0x00, 0x2e, 0x64, 0x79, 0x6e, 0x73, 0x74, 0x72, 0x00, 0x0e, 0x00, 0x00, 0x00,
690        0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00,
691        0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00,
692        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
693        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
694        0x00, 0x6c, 0x69, 0x62, 0x66, 0x6f, 0x6f, 0x2e, 0x73, 0x6f, 0x2e, 0x31, 0x00, 0x6a, 0x3c,
695        0x58, 0x31, 0xff, 0x0f, 0x05,
696    ];
697
698    #[test]
699    fn build_id_program_headers() {
700        let reader = ModuleReader::new(SliceModuleMemoryReader(TINY_ELF)).unwrap();
701        let id = reader.build_id_from_program_headers().unwrap();
702        assert_eq!(
703            id,
704            vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
705        );
706    }
707
708    #[test]
709    fn build_id_section() {
710        let reader = ModuleReader::new(SliceModuleMemoryReader(TINY_ELF)).unwrap();
711        let id = reader.build_id_from_section().unwrap();
712        assert_eq!(
713            id,
714            vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
715        );
716    }
717
718    #[test]
719    fn build_id_text_hash() {
720        let reader = ModuleReader::new(SliceModuleMemoryReader(TINY_ELF)).unwrap();
721        let id = reader.build_id_generate_from_text().unwrap();
722        assert_eq!(
723            id,
724            vec![
725                0x6a, 0x3c, 0x58, 0x31, 0xff, 0x0f, 0x05, 0, 0, 0, 0, 0, 0, 0, 0, 0
726            ]
727        );
728    }
729
730    #[test]
731    fn soname_program_headers() {
732        let reader = ModuleReader::new(SliceModuleMemoryReader(TINY_ELF)).unwrap();
733        let soname = reader.soname_from_program_headers().unwrap();
734        assert_eq!(soname, "libfoo.so.1");
735    }
736
737    #[test]
738    fn soname_section() {
739        let reader = ModuleReader::new(SliceModuleMemoryReader(TINY_ELF)).unwrap();
740        let soname = reader.soname_from_sections().unwrap();
741        assert_eq!(soname, "libfoo.so.1");
742    }
743
744    pub struct SliceModuleMemoryReader<'a>(pub &'a [u8]);
745
746    impl<'a> ReadModuleMemory for SliceModuleMemoryReader<'a> {
747        fn read<'b>(
748            &'b self,
749            offset: u64,
750            length: u64,
751        ) -> Result<Cow<'b, [u8]>, ModuleMemoryReadError> {
752            let inner = || {
753                use crate::module_reader::ReadError as E;
754                let offset = usize::try_from(offset).map_err(|_| E::Overflow)?;
755                let length = usize::try_from(length).map_err(|_| E::Overflow)?;
756                let end = offset.checked_add(length).ok_or(E::Overflow)?;
757                self.0
758                    .get(offset..end)
759                    .map(Cow::Borrowed)
760                    .ok_or(E::OutOfBounds)
761            };
762
763            inner().map_err(|error| ModuleMemoryReadError {
764                start_address: None,
765                offset,
766                length,
767                error,
768            })
769        }
770        fn absolute_to_relative(&self, addr: u64) -> Option<u64> {
771            Some(addr)
772        }
773        /// Calculates the absolute address of the specified relative address
774        fn relative_to_absolute(&self, addr: u64) -> Option<u64> {
775            Some(addr)
776        }
777        fn is_process_memory(&self) -> bool {
778            false
779        }
780    }
781}