xmas_elf/
lib.rs

1#![no_std]
2#![warn(box_pointers, missing_copy_implementations, missing_debug_implementations)]
3#![warn(unused_extern_crates, unused_import_braces, unused_qualifications, unused_results)]
4#![warn(variant_size_differences)]
5
6// TODO move to a module
7macro_rules! check {
8    ($e:expr) => {
9        if !$e {
10            return Err("");
11        }
12    };
13    ($e:expr, $msg: expr) => {
14        if !$e {
15            return Err($msg);
16        }
17    };
18}
19
20#[cfg(feature = "compression")]
21extern crate std;
22#[cfg(feature = "compression")]
23extern crate flate2;
24
25extern crate zero;
26
27pub mod header;
28pub mod sections;
29pub mod program;
30pub mod symbol_table;
31pub mod dynamic;
32pub mod hash;
33
34use header::Header;
35use sections::{SectionHeader, SectionIter};
36use program::{ProgramHeader, ProgramIter};
37use zero::{read, read_str};
38
39pub type P32 = u32;
40pub type P64 = u64;
41
42#[derive(Debug)]
43pub struct ElfFile<'a> {
44    pub input: &'a [u8],
45    pub header: Header<'a>,
46}
47
48impl<'a> ElfFile<'a> {
49    pub fn new(input: &'a [u8]) -> Result<ElfFile<'a>, &'static str> {
50        header::parse_header(input).map(|header| ElfFile {input, header})
51    }
52
53    pub fn section_header(&self, index: u16) -> Result<SectionHeader<'a>, &'static str> {
54        sections::parse_section_header(self.input, self.header, index)
55    }
56
57    pub fn section_iter(&self) -> impl Iterator<Item = SectionHeader<'a>> + '_ {
58        SectionIter {
59            file: self,
60            next_index: 0,
61        }
62    }
63
64    pub fn program_header(&self, index: u16) -> Result<ProgramHeader<'a>, &'static str> {
65        program::parse_program_header(self.input, self.header, index)
66    }
67
68    pub fn program_iter(&self) -> impl Iterator<Item = ProgramHeader<'a>> + '_ {
69        ProgramIter {
70            file: self,
71            next_index: 0,
72        }
73    }
74
75    pub fn get_shstr(&self, index: u32) -> Result<&'a str, &'static str> {
76        self.get_shstr_table().map(|shstr_table| read_str(&shstr_table[(index as usize)..]))
77    }
78
79    pub fn get_string(&self, index: u32) -> Result<&'a str, &'static str> {
80        let header = self.find_section_by_name(".strtab").ok_or("no .strtab section")?;
81        if header.get_type()? != sections::ShType::StrTab {
82            return Err("expected .strtab to be StrTab");
83        }
84        Ok(read_str(&header.raw_data(self)[(index as usize)..]))
85    }
86
87    pub fn get_dyn_string(&self, index: u32) -> Result<&'a str, &'static str> {
88        let header = self.find_section_by_name(".dynstr").ok_or("no .dynstr section")?;
89        Ok(read_str(&header.raw_data(self)[(index as usize)..]))
90    }
91
92    // This is really, stupidly slow. Not sure how to fix that, perhaps keeping
93    // a HashTable mapping names to section header indices?
94    pub fn find_section_by_name(&self, name: &str) -> Option<SectionHeader<'a>> {
95        for sect in self.section_iter() {
96            if let Ok(sect_name) = sect.get_name(self) {
97                if sect_name == name {
98                    return Some(sect);
99                }
100            }
101        }
102
103        None
104    }
105
106    fn get_shstr_table(&self) -> Result<&'a [u8], &'static str> {
107        // TODO cache this?
108        let header = self.section_header(self.header.pt2.sh_str_index());
109        header.and_then(|h| {
110            let offset = h.offset() as usize;
111            if self.input.len() < offset {
112                return Err("File is shorter than section offset");
113            }
114            Ok(&self.input[offset..])
115        })
116    }
117}
118
119/// A trait for things that are common ELF conventions but not part of the ELF
120/// specification.
121pub trait Extensions<'a> {
122    /// Parse and return the value of the .note.gnu.build-id section, if it
123    /// exists and is well-formed.
124    fn get_gnu_buildid(&self) -> Option<&'a [u8]>;
125
126    /// Parse and return the value of the .gnu_debuglink section, if it
127    /// exists and is well-formed.
128    fn get_gnu_debuglink(&self) -> Option<(&'a str, u32)>;
129
130    /// Parse and return the value of the .gnu_debugaltlink section, if it
131    /// exists and is well-formed.
132    fn get_gnu_debugaltlink(&self) -> Option<(&'a str, &'a [u8])>;
133}
134
135impl<'a> Extensions<'a> for ElfFile<'a> {
136    fn get_gnu_buildid(&self) -> Option<&'a [u8]> {
137        self.find_section_by_name(".note.gnu.build-id")
138            .and_then(|header| header.get_data(self).ok())
139            .and_then(|data| match data {
140                // Handle Note32 if it's ever implemented!
141                sections::SectionData::Note64(header, data) => Some((header, data)),
142                _ => None,
143            })
144            .and_then(|(header, data)| {
145                // Check for NT_GNU_BUILD_ID
146                if header.type_() != 0x3 {
147                    return None;
148                }
149
150                if header.name(data) != "GNU" {
151                    return None;
152                }
153
154                Some(header.desc(data))
155            })
156    }
157
158    fn get_gnu_debuglink(&self) -> Option<(&'a str, u32)> {
159        self.find_section_by_name(".gnu_debuglink")
160            .and_then(|header| {
161                let data = header.raw_data(self);
162                let file = read_str(data);
163                // Round up to the nearest multiple of 4.
164                let checksum_pos = ((file.len() + 4) / 4) * 4;
165                if checksum_pos + 4 <= data.len() {
166                    let checksum: u32 = *read(&data[checksum_pos..]);
167                    Some((file, checksum))
168                } else {
169                    None
170                }
171            })
172    }
173
174    fn get_gnu_debugaltlink(&self) -> Option<(&'a str, &'a [u8])> {
175        self.find_section_by_name(".gnu_debugaltlink")
176            .map(|header| header.raw_data(self))
177            .and_then(|data| {
178                let file = read_str(data);
179                // The rest of the data is a SHA1 checksum of the debuginfo, no alignment
180                let checksum_pos = file.len() + 1;
181                if checksum_pos <= data.len() {
182                    Some((file, &data[checksum_pos..]))
183                } else {
184                    None
185                }
186            })
187    }
188}
189
190#[cfg(test)]
191#[macro_use]
192extern crate std;
193
194#[cfg(test)]
195mod test {
196    use std::prelude::v1::*;
197
198    use std::mem;
199
200    use super::*;
201    use header::{HeaderPt1, HeaderPt2_};
202
203    fn mk_elf_header(class: u8) -> Vec<u8> {
204        let header_size = mem::size_of::<HeaderPt1>() +
205                          match class {
206            1 => mem::size_of::<HeaderPt2_<P32>>(),
207            2 => mem::size_of::<HeaderPt2_<P64>>(),
208            _ => 0,
209        };
210        let mut header = vec![0x7f, 'E' as u8, 'L' as u8, 'F' as u8];
211        let data = 1u8;
212        let version = 1u8;
213        header.extend_from_slice(&[class, data, version]);
214        header.resize(header_size, 0);
215        header
216    }
217
218    #[test]
219    fn interpret_class() {
220        assert!(ElfFile::new(&mk_elf_header(0)).is_err());
221        assert!(ElfFile::new(&mk_elf_header(1)).is_ok());
222        assert!(ElfFile::new(&mk_elf_header(2)).is_ok());
223        assert!(ElfFile::new(&mk_elf_header(42u8)).is_err());
224    }
225}