1use anyhow::{bail, Context, Result};
7use goblin::elf::Elf;
8
9#[derive(Clone)]
10pub struct Segment {
11 pub vaddr: u64,
12 pub bytes: Vec<u8>,
13 pub exec: bool,
14 pub write: bool,
15}
16
17#[derive(Clone, Debug)]
18pub struct Func {
19 pub name: Option<String>,
20 pub entry: u64,
21 pub size: u64,
22 pub source: FuncSource,
24}
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq)]
27pub enum FuncSource {
28 Symtab,
29 DynSym,
30 EhFrame,
31}
32
33pub struct Image {
34 pub segments: Vec<Segment>,
35 pub entry: u64,
36 pub is_pie: bool,
37}
38
39impl Image {
40 pub fn code_at(&self, vaddr: u64, len: usize) -> Option<&[u8]> {
42 for s in &self.segments {
43 if vaddr >= s.vaddr && vaddr + len as u64 <= s.vaddr + s.bytes.len() as u64 {
44 let off = (vaddr - s.vaddr) as usize;
45 return Some(&s.bytes[off..off + len]);
46 }
47 }
48 None
49 }
50}
51
52pub struct Loaded {
53 pub image: Image,
54 pub funcs: Vec<Func>,
55}
56
57pub fn load(bytes: &[u8]) -> Result<Loaded> {
58 let elf = Elf::parse(bytes).context("not a valid elf")?;
59 if elf.header.e_machine != goblin::elf::header::EM_X86_64 {
60 bail!(
61 "only x86-64 is supported in this version (got e_machine {})",
62 elf.header.e_machine
63 );
64 }
65
66 let mut segments = Vec::new();
67 for ph in &elf.program_headers {
68 if ph.p_type != goblin::elf::program_header::PT_LOAD {
69 continue;
70 }
71 let start = ph.p_offset as usize;
72 let fsz = ph.p_filesz as usize;
73 let end = start.saturating_add(fsz).min(bytes.len());
74 let mut data = bytes[start..end].to_vec();
75 if (ph.p_memsz as usize) > data.len() {
77 data.resize(ph.p_memsz as usize, 0);
78 }
79 segments.push(Segment {
80 vaddr: ph.p_vaddr,
81 bytes: data,
82 exec: ph.is_executable(),
83 write: ph.is_write(),
84 });
85 }
86 if segments.is_empty() {
87 bail!("no PT_LOAD segments");
88 }
89
90 let is_pie = elf.header.e_type == goblin::elf::header::ET_DYN;
91
92 let mut funcs = discover(&elf, bytes)?;
93 funcs.sort_by(|a, b| {
95 a.entry
96 .cmp(&b.entry)
97 .then(b.name.is_some().cmp(&a.name.is_some()))
98 });
99 funcs.dedup_by_key(|f| f.entry);
100
101 Ok(Loaded {
102 image: Image {
103 segments,
104 entry: elf.header.e_entry,
105 is_pie,
106 },
107 funcs,
108 })
109}
110
111fn discover(elf: &Elf, raw: &[u8]) -> Result<Vec<Func>> {
112 let mut out = Vec::new();
113
114 for (sym, src) in elf
115 .syms
116 .iter()
117 .map(|s| (s, FuncSource::Symtab))
118 .chain(elf.dynsyms.iter().map(|s| (s, FuncSource::DynSym)))
119 {
120 if sym.st_type() != goblin::elf::sym::STT_FUNC {
121 continue;
122 }
123 if sym.st_value == 0 || sym.st_size == 0 {
124 continue; }
126 let name = match src {
127 FuncSource::Symtab => elf.strtab.get_at(sym.st_name),
128 _ => elf.dynstrtab.get_at(sym.st_name),
129 }
130 .map(|s| s.to_string())
131 .filter(|s| !s.is_empty());
132 out.push(Func {
133 name,
134 entry: sym.st_value,
135 size: sym.st_size,
136 source: src,
137 });
138 }
139
140 if out.is_empty() {
142 if let Some(mut fdes) = eh_frame_funcs(elf, raw) {
143 out.append(&mut fdes);
144 }
145 }
146
147 Ok(out)
148}
149
150fn eh_frame_funcs(elf: &Elf, raw: &[u8]) -> Option<Vec<Func>> {
152 use gimli::{BaseAddresses, CieOrFde, EhFrame, LittleEndian, UnwindSection};
153
154 let sh = elf
155 .section_headers
156 .iter()
157 .find(|s| elf.shdr_strtab.get_at(s.sh_name) == Some(".eh_frame"))?;
158 let start = sh.sh_offset as usize;
159 let data = raw.get(start..start + sh.sh_size as usize)?;
160
161 let eh = EhFrame::new(data, LittleEndian);
162 let bases = BaseAddresses::default().set_eh_frame(sh.sh_addr);
163
164 let mut entries = eh.entries(&bases);
165 let mut out = Vec::new();
166 loop {
167 match entries.next() {
168 Ok(Some(CieOrFde::Fde(partial))) => {
169 if let Ok(fde) = partial.parse(EhFrame::cie_from_offset) {
170 let entry = fde.initial_address();
171 let size = fde.len();
172 if size > 0 {
173 out.push(Func {
174 name: None,
175 entry,
176 size,
177 source: FuncSource::EhFrame,
178 });
179 }
180 }
181 }
182 Ok(Some(CieOrFde::Cie(_))) => {}
183 Ok(None) => break,
184 Err(_) => break,
185 }
186 }
187 if out.is_empty() {
188 None
189 } else {
190 Some(out)
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197
198 #[test]
199 fn garbage_input_errors_not_panics() {
200 assert!(load(b"").is_err());
201 assert!(load(b"not an elf at all, just text here").is_err());
202 let mut junk = vec![0x7f, b'E', b'L', b'F'];
204 junk.extend(std::iter::repeat_n(0x41u8, 400));
205 let _ = load(&junk); }
207
208 #[test]
209 fn truncated_header_does_not_panic() {
210 let mut hdr = vec![0x7f, b'E', b'L', b'F', 2, 1, 1, 0];
211 hdr.extend(std::iter::repeat_n(0u8, 48));
212 let _ = load(&hdr);
213 }
214}