minidump_writer/linux/
module_reader.rs

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