1use goblin::elf;
2use nix::{errno::Errno, sys::wait::wait, unistd::Pid};
3use procfs::process::{MMPermissions, MMapPath, Process};
4use std::{collections::HashMap, fs::File, io::Read, path::Path};
5
6use crate::diag::{Error, Result};
7
8const AT_ENTRY: u64 = 9;
9const AT_PHDR: u64 = 3;
10const EI_DATA: usize = 5;
11const MAX_OPCODE_SIZE: u64 = 16;
12
13pub struct Info {
19 pid: Pid,
20 auxv: HashMap<u64, u64>,
21 buffer: Vec<u8>,
22 header: elf::Header,
23 sections: HashMap<String, elf::SectionHeader>,
24 load_vaddr: u64,
25 load_offset: u64,
26 mem_offset: u64,
27}
28
29impl Info {
30 pub fn build(path: &str, pid: Pid) -> Result<Self> {
45 wait()?;
47
48 let auxv = Self::get_auxv(pid)?;
49
50 let mut file = File::open(Path::new(path))?;
51 let mut buffer = Vec::new();
52 file.read_to_end(&mut buffer)?;
53
54 let elf = elf::Elf::parse(&buffer)?;
55 let header = elf.header;
56
57 let sections: HashMap<String, elf::SectionHeader> = elf
58 .section_headers
59 .iter()
60 .filter_map(|header| {
61 elf.shdr_strtab
62 .get_at(header.sh_name)
63 .map(|name| (name.to_string(), header.clone()))
64 })
65 .collect();
66 sections
67 .get(".text")
68 .ok_or_else(|| Error::from(Errno::ENODATA))?;
69
70 let dynamic = match elf.header.e_type {
71 elf::header::ET_DYN => Ok(true),
72 elf::header::ET_EXEC => Ok(false),
73 _ => Err(Error::from(Errno::ENOEXEC)),
74 }?;
75
76 let (load_vaddr, load_offset) = if dynamic {
77 elf.program_headers
78 .iter()
79 .find(|ph| {
80 ph.p_type == elf::program_header::PT_LOAD && ph.is_executable()
81 })
82 .map(|ph| (ph.p_vaddr, ph.p_offset))
83 .ok_or_else(|| Error::from(Errno::ENODATA))?
84 } else {
85 (0, 0)
86 };
87
88 let mem_offset = if dynamic {
89 Self::get_mem_offset(path, pid)?
90 } else {
91 0
92 };
93
94 Ok(Self {
95 pid,
96 auxv,
97 buffer,
98 header,
99 sections,
100 load_vaddr,
101 load_offset,
102 mem_offset,
103 })
104 }
105
106 fn get_auxv(pid: Pid) -> Result<HashMap<u64, u64>> {
107 let process = Process::new(pid.into())?;
108 let auxv = process.auxv()?;
109 Ok(auxv.into_iter().collect())
110 }
111
112 fn get_mem_offset(path: &str, pid: Pid) -> Result<u64> {
113 let absolute_path = std::fs::canonicalize(path)?;
114
115 let process = Process::new(pid.into())?;
116 let maps = process.maps()?;
117
118 maps.into_iter()
119 .find_map(|map| match &map.pathname {
120 MMapPath::Path(buf)
121 if buf == &absolute_path
122 && map.perms.contains(MMPermissions::READ)
123 && map.perms.contains(MMPermissions::EXECUTE) =>
124 {
125 Some(map.address.0)
126 }
127 _ => None,
128 })
129 .ok_or_else(|| Error::from(Errno::ENODATA))
130 }
131
132 fn get_buffer_data(&self, offset: u64, len: u64) -> Result<Option<&[u8]>> {
133 let offset = usize::try_from(offset)?;
134 let len = usize::try_from(len)?;
135 Ok(self.buffer.get(offset..offset + len))
136 }
137
138 #[must_use]
139 pub fn pid(&self) -> Pid {
145 self.pid
146 }
147
148 #[must_use]
149 pub fn endianness(&self) -> u8 {
158 self.header.e_ident[EI_DATA]
159 }
160
161 #[must_use]
162 pub fn offset(&self) -> u64 {
170 self.mem_offset - self.load_vaddr
171 }
172
173 #[must_use]
174 pub fn is_addr_in_section(&self, addr: u64, name: &str) -> bool {
189 self.sections.get(name).is_some_and(|section| {
190 let start = section.sh_addr + self.mem_offset;
191 let end = start + section.sh_size;
192 (start..end).contains(&addr)
193 })
194 }
195
196 pub fn entry(&self) -> Result<&u64> {
206 self.auxv
207 .get(&AT_ENTRY)
208 .ok_or_else(|| Error::from(Errno::ENODATA))
209 }
210
211 pub fn get_section_data(&self, name: &str) -> Result<Option<&[u8]>> {
225 self.sections.get(name).map_or(Ok(None), |section| {
226 self.get_buffer_data(section.sh_offset, section.sh_size)
227 })
228 }
229
230 pub fn get_opcode_from_addr(&self, addr: u64) -> Result<Option<&[u8]>> {
244 let phdr = self
245 .auxv
246 .get(&AT_PHDR)
247 .ok_or_else(|| Error::from(Errno::ENODATA))?;
248 let offset =
249 (addr - phdr + self.header.e_phoff) + self.load_offset - self.load_vaddr;
250 self.get_buffer_data(offset, MAX_OPCODE_SIZE)
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257
258 use std::io::Write;
259
260 fn create_temp_elf_file() -> std::io::Result<String> {
261 let path = "/tmp/test.elf";
262 let mut file = File::create(path)?;
263 file.write_all(b"\x7fELF")?;
264 Ok(path.to_string())
265 }
266
267 #[test]
268 fn test_get_section_data_invalid() {
269 let pid = Pid::from_raw(1234);
270 let path =
271 create_temp_elf_file().expect("Failed to create temporary ELF file");
272 let info = Info::build(&path, pid);
273 if let Ok(info) = info {
274 let data = info.get_section_data(".invalid");
275 assert!(matches!(data, Ok(None)));
276 }
277 }
278
279 #[test]
280 fn test_get_opcode_from_addr_invalid_auxv() {
281 let pid = Pid::from_raw(1234);
282 let path =
283 create_temp_elf_file().expect("Failed to create temporary ELF file");
284 let info = Info::build(&path, pid);
285 if let Ok(info) = info {
286 let result = info.get_opcode_from_addr(0xdeadbeef);
287 assert!(result.is_err());
288 }
289 }
290
291 #[test]
292 fn test_is_addr_in_section_false() {
293 let pid = Pid::from_raw(1234);
294 let path =
295 create_temp_elf_file().expect("Failed to create temporary ELF file");
296 let info = Info::build(&path, pid);
297 if let Ok(info) = info {
298 let found = info.is_addr_in_section(0xdeadbeef, ".text");
299 assert!(!found);
300 }
301 }
302
303 #[test]
304 fn test_entry_missing() {
305 let pid = Pid::from_raw(1234);
306 let path =
307 create_temp_elf_file().expect("Failed to create temporary ELF file");
308 let info = Info::build(&path, pid);
309 if let Ok(info) = info {
310 let entry = info.entry();
311 assert!(entry.is_err());
312 }
313 }
314
315 #[test]
316 fn test_build_invalid_path() {
317 let pid = Pid::from_raw(1234);
318 let result = Info::build("/invalid/path", pid);
319 assert!(result.is_err());
320 }
321
322 #[test]
323 fn test_build_invalid_elf() {
324 let pid = Pid::from_raw(1234);
325 let path =
326 create_temp_elf_file().expect("Failed to create temporary ELF file");
327 let result = Info::build(&path, pid);
328 assert!(result.is_err());
329 }
330
331 #[test]
332 fn test_get_mem_offset_invalid_path() {
333 let pid = Pid::from_raw(1234);
334 let result = Info::get_mem_offset("/invalid/path", pid);
335 assert!(result.is_err());
336 }
337
338 #[test]
339 fn test_get_buffer_and_section_helpers() {
340 let pid = Pid::from_raw(1);
341 let buffer = vec![0u8; 64];
342 let header = elf::Header {
343 e_ident: [0; 16],
344 e_type: 0,
345 e_machine: 0,
346 e_version: 0,
347 e_entry: 0,
348 e_phoff: 0,
349 e_shoff: 0,
350 e_flags: 0,
351 e_ehsize: 0,
352 e_phentsize: 0,
353 e_phnum: 0,
354 e_shentsize: 0,
355 e_shnum: 0,
356 e_shstrndx: 0,
357 };
358 let sections: HashMap<String, elf::SectionHeader> = HashMap::new();
359
360 let info = Info {
361 pid,
362 auxv: HashMap::new(),
363 buffer,
364 header,
365 sections,
366 load_vaddr: 0,
367 load_offset: 0,
368 mem_offset: 0,
369 };
370
371 let data = info.get_buffer_data(0, 16).expect("get_buffer_data failed");
372 assert!(data.is_some());
373
374 let sec = info.get_section_data(".text");
375 assert!(sec.expect("get_section_data returned Err").is_none());
376 }
377
378 #[test]
379 fn test_info_accessors_and_opcode() {
380 let pid = Pid::from_raw(42);
381
382 let mut buffer = vec![0u8; 256];
383 for i in 100..116 {
384 buffer[i] = (i - 100) as u8;
385 }
386
387 let header = elf::Header {
388 e_ident: [0; 16],
389 e_type: 0,
390 e_machine: 0,
391 e_version: 0,
392 e_entry: 0,
393 e_phoff: 20,
394 e_shoff: 0,
395 e_flags: 0,
396 e_ehsize: 0,
397 e_phentsize: 0,
398 e_phnum: 0,
399 e_shentsize: 0,
400 e_shnum: 0,
401 e_shstrndx: 0,
402 };
403
404 let mut sections: HashMap<String, elf::SectionHeader> = HashMap::new();
405 let sh = elf::SectionHeader {
406 sh_name: 0,
407 sh_type: 0,
408 sh_flags: 0,
409 sh_addr: 0x200,
410 sh_offset: 50,
411 sh_size: 10,
412 sh_link: 0,
413 sh_info: 0,
414 sh_addralign: 0,
415 sh_entsize: 0,
416 };
417 sections.insert(".text".to_string(), sh);
418
419 let mut auxv = HashMap::new();
420 auxv.insert(AT_PHDR, 150u64);
421
422 let info = Info {
423 pid,
424 auxv,
425 buffer,
426 header,
427 sections,
428 load_vaddr: 0,
429 load_offset: 0,
430 mem_offset: 0,
431 };
432
433 assert_eq!(info.pid(), pid);
434 assert_eq!(info.endianness(), 0);
435 assert_eq!(info.offset(), 0);
436
437 let addr = 0x200 + 5;
438 assert!(info.is_addr_in_section(addr, ".text"));
439
440 let sec = info
441 .get_section_data(".text")
442 .expect("get_section_data failed");
443 assert!(sec.is_some());
444 let sec = sec.unwrap();
445 assert_eq!(sec.len(), 10);
446
447 let addr_for_opcode = 150u64 - 20u64 + 100u64;
448 let opc = info
449 .get_opcode_from_addr(addr_for_opcode)
450 .expect("get_opcode_from_addr failed")
451 .expect("opcode not found");
452
453 assert_eq!(opc.len(), usize::try_from(MAX_OPCODE_SIZE).unwrap());
454 assert_eq!(opc[0], 0u8);
455 assert_eq!(opc[15], 15u8);
456 }
457}