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.map(|h| &self.input[(h.offset() as usize)..])
110    }
111}
112
113/// A trait for things that are common ELF conventions but not part of the ELF
114/// specification.
115pub trait Extensions<'a> {
116    /// Parse and return the value of the .note.gnu.build-id section, if it
117    /// exists and is well-formed.
118    fn get_gnu_buildid(&self) -> Option<&'a [u8]>;
119
120    /// Parse and return the value of the .gnu_debuglink section, if it
121    /// exists and is well-formed.
122    fn get_gnu_debuglink(&self) -> Option<(&'a str, u32)>;
123
124    /// Parse and return the value of the .gnu_debugaltlink section, if it
125    /// exists and is well-formed.
126    fn get_gnu_debugaltlink(&self) -> Option<(&'a str, &'a [u8])>;
127}
128
129impl<'a> Extensions<'a> for ElfFile<'a> {
130    fn get_gnu_buildid(&self) -> Option<&'a [u8]> {
131        self.find_section_by_name(".note.gnu.build-id")
132            .and_then(|header| header.get_data(self).ok())
133            .and_then(|data| match data {
134                // Handle Note32 if it's ever implemented!
135                sections::SectionData::Note64(header, data) => Some((header, data)),
136                _ => None,
137            })
138            .and_then(|(header, data)| {
139                // Check for NT_GNU_BUILD_ID
140                if header.type_() != 0x3 {
141                    return None;
142                }
143
144                if header.name(data) != "GNU" {
145                    return None;
146                }
147
148                Some(header.desc(data))
149            })
150    }
151
152    fn get_gnu_debuglink(&self) -> Option<(&'a str, u32)> {
153        self.find_section_by_name(".gnu_debuglink")
154            .and_then(|header| {
155                let data = header.raw_data(self);
156                let file = read_str(data);
157                // Round up to the nearest multiple of 4.
158                let checksum_pos = ((file.len() + 4) / 4) * 4;
159                if checksum_pos + 4 <= data.len() {
160                    let checksum: u32 = *read(&data[checksum_pos..]);
161                    Some((file, checksum))
162                } else {
163                    None
164                }
165            })
166    }
167
168    fn get_gnu_debugaltlink(&self) -> Option<(&'a str, &'a [u8])> {
169        self.find_section_by_name(".gnu_debugaltlink")
170            .map(|header| header.raw_data(self))
171            .and_then(|data| {
172                let file = read_str(data);
173                // The rest of the data is a SHA1 checksum of the debuginfo, no alignment
174                let checksum_pos = file.len() + 1;
175                if checksum_pos <= data.len() {
176                    Some((file, &data[checksum_pos..]))
177                } else {
178                    None
179                }
180            })
181    }
182}
183
184#[cfg(test)]
185#[macro_use]
186extern crate std;
187
188#[cfg(test)]
189mod test {
190    use std::prelude::v1::*;
191
192    use std::mem;
193
194    use super::*;
195    use header::{HeaderPt1, HeaderPt2_};
196
197    fn mk_elf_header(class: u8) -> Vec<u8> {
198        let header_size = mem::size_of::<HeaderPt1>() +
199                          match class {
200            1 => mem::size_of::<HeaderPt2_<P32>>(),
201            2 => mem::size_of::<HeaderPt2_<P64>>(),
202            _ => 0,
203        };
204        let mut header = vec![0x7f, 'E' as u8, 'L' as u8, 'F' as u8];
205        let data = 1u8;
206        let version = 1u8;
207        header.extend_from_slice(&[class, data, version]);
208        header.resize(header_size, 0);
209        header
210    }
211
212    #[test]
213    fn interpret_class() {
214        assert!(ElfFile::new(&mk_elf_header(0)).is_err());
215        assert!(ElfFile::new(&mk_elf_header(1)).is_ok());
216        assert!(ElfFile::new(&mk_elf_header(2)).is_ok());
217        assert!(ElfFile::new(&mk_elf_header(42u8)).is_err());
218    }
219}