1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
use super::{Context, Mapping, Path, Stash, Vec};
use core::convert::TryFrom;
use object::pe::{ImageDosHeader, ImageSymbol};
use object::read::pe::{ImageNtHeaders, ImageOptionalHeader, SectionTable};
use object::read::StringTable;
use object::{Bytes, LittleEndian as LE};

#[cfg(target_pointer_width = "32")]
type Pe = object::pe::ImageNtHeaders32;
#[cfg(target_pointer_width = "64")]
type Pe = object::pe::ImageNtHeaders64;

impl Mapping {
    pub fn new(path: &Path) -> Option<Mapping> {
        let map = super::mmap(path)?;
        Mapping::mk(map, |data, stash| Context::new(stash, Object::parse(data)?))
    }
}

pub struct Object<'a> {
    data: Bytes<'a>,
    sections: SectionTable<'a>,
    symbols: Vec<(usize, &'a ImageSymbol)>,
    strings: StringTable<'a>,
}

pub fn get_image_base(data: &[u8]) -> Option<usize> {
    let data = Bytes(data);
    let dos_header = ImageDosHeader::parse(data).ok()?;
    let (nt_headers, _, _) = dos_header.nt_headers::<Pe>(data).ok()?;
    usize::try_from(nt_headers.optional_header().image_base()).ok()
}

impl<'a> Object<'a> {
    fn parse(data: &'a [u8]) -> Option<Object<'a>> {
        let data = Bytes(data);
        let dos_header = ImageDosHeader::parse(data).ok()?;
        let (nt_headers, _, nt_tail) = dos_header.nt_headers::<Pe>(data).ok()?;
        let sections = nt_headers.sections(nt_tail).ok()?;
        let symtab = nt_headers.symbols(data).ok()?;
        let strings = symtab.strings();
        let image_base = usize::try_from(nt_headers.optional_header().image_base()).ok()?;

        // Collect all the symbols into a local vector which is sorted
        // by address and contains enough data to learn about the symbol
        // name. Note that we only look at function symbols and also
        // note that the sections are 1-indexed because the zero section
        // is special (apparently).
        let mut symbols = Vec::new();
        let mut i = 0;
        let len = symtab.len();
        while i < len {
            let sym = symtab.symbol(i).ok()?;
            i += 1 + sym.number_of_aux_symbols as usize;
            let section_number = sym.section_number.get(LE);
            if sym.derived_type() != object::pe::IMAGE_SYM_DTYPE_FUNCTION || section_number == 0 {
                continue;
            }
            let addr = usize::try_from(sym.value.get(LE)).ok()?;
            let section = sections
                .section(usize::try_from(section_number).ok()?)
                .ok()?;
            let va = usize::try_from(section.virtual_address.get(LE)).ok()?;
            symbols.push((addr + va + image_base, sym));
        }
        symbols.sort_unstable_by_key(|x| x.0);
        Some(Object {
            data,
            sections,
            strings,
            symbols,
        })
    }

    pub fn section(&self, _: &Stash, name: &str) -> Option<&'a [u8]> {
        Some(
            self.sections
                .section_by_name(self.strings, name.as_bytes())?
                .1
                .pe_data(self.data)
                .ok()?
                .0,
        )
    }

    pub fn search_symtab<'b>(&'b self, addr: u64) -> Option<&'b [u8]> {
        // Note that unlike other formats COFF doesn't embed the size of
        // each symbol. As a last ditch effort search for the *closest*
        // symbol to a particular address and return that one. This gets
        // really wonky once symbols start getting removed because the
        // symbols returned here can be totally incorrect, but we have
        // no idea of knowing how to detect that.
        let addr = usize::try_from(addr).ok()?;
        let i = match self.symbols.binary_search_by_key(&addr, |p| p.0) {
            Ok(i) => i,
            // typically `addr` isn't in the array, but `i` is where
            // we'd insert it, so the previous position must be the
            // greatest less than `addr`
            Err(i) => i.checked_sub(1)?,
        };
        self.symbols[i].1.name(self.strings).ok()
    }

    pub(super) fn search_object_map(&self, _addr: u64) -> Option<(&Context<'_>, u64)> {
        None
    }
}