1use libc;
4
5use crate::Segment as SegmentTrait;
6use crate::SharedLibrary as SharedLibraryTrait;
7use crate::{Bias, IterationControl, SharedLibraryId, Svma};
8
9use std::any::Any;
10use std::borrow::Cow;
11use std::env::current_exe;
12use std::ffi::{CStr, CString, OsStr};
13use std::fmt;
14use std::iter;
15use std::marker::PhantomData;
16use std::mem;
17use std::os::unix::ffi::OsStrExt;
18use std::os::unix::ffi::OsStringExt;
19use std::panic;
20use std::slice;
21use std::usize;
22
23#[cfg(target_pointer_width = "32")]
24type Phdr = libc::Elf32_Phdr;
25
26#[cfg(target_pointer_width = "64")]
27type Phdr = libc::Elf64_Phdr;
28
29const NT_GNU_BUILD_ID: u32 = 3;
30
31#[repr(C)]
35#[derive(Debug, Clone, Copy)]
36struct Nhdr {
37 pub n_namesz: libc::Elf32_Word,
38 pub n_descsz: libc::Elf32_Word,
39 pub n_type: libc::Elf32_Word,
40}
41
42#[derive(Debug)]
44pub struct Segment<'a> {
45 phdr: *const Phdr,
46 shlib: PhantomData<&'a SharedLibrary<'a>>,
47}
48
49impl<'a> Segment<'a> {
50 fn phdr(&self) -> &'a Phdr {
51 unsafe { self.phdr.as_ref().unwrap() }
52 }
53
54 unsafe fn data(&self, shlib: &SharedLibrary<'a>) -> &'a [u8] {
56 let phdr = self.phdr();
57 let avma = (shlib.addr as usize).wrapping_add(phdr.p_vaddr as usize);
58 slice::from_raw_parts(avma as *const u8, phdr.p_memsz as usize)
59 }
60
61 fn is_note(&self) -> bool {
62 self.phdr().p_type == libc::PT_NOTE
63 }
64
65 unsafe fn notes(
75 &self,
76 shlib: &SharedLibrary<'a>,
77 ) -> impl Iterator<Item = (libc::Elf32_Word, &'a [u8], &'a [u8])> {
78 let alignment = std::cmp::max(self.phdr().p_align as usize, 4);
82 let align_up = move |data: &'a [u8]| {
83 if alignment != 4 && alignment != 8 {
84 return None;
85 }
86
87 let ptr = data.as_ptr() as usize;
88 let alignment_minus_one = alignment - 1;
89 let aligned_ptr = ptr.checked_add(alignment_minus_one)? & !alignment_minus_one;
90 let diff = aligned_ptr - ptr;
91 if data.len() < diff {
92 None
93 } else {
94 Some(&data[diff..])
95 }
96 };
97
98 let mut data = self.data(shlib);
99
100 iter::from_fn(move || {
101 if (data.as_ptr() as usize % alignment) != 0 {
102 return None;
103 }
104
105 let nhdr_size = mem::size_of::<Nhdr>();
108 let nhdr = try_split_at(&mut data, nhdr_size)?;
109 let nhdr = (nhdr.as_ptr() as *const Nhdr).as_ref().unwrap();
110
111 let name_size = nhdr.n_namesz as usize;
114 let name = try_split_at(&mut data, name_size)?;
115
116 data = align_up(data)?;
119 let desc_size = nhdr.n_descsz as usize;
120 let desc = try_split_at(&mut data, desc_size)?;
121
122 data = align_up(data)?;
124
125 Some((nhdr.n_type, name, desc))
126 })
127 .fuse()
128 }
129}
130
131fn try_split_at<'a>(data: &mut &'a [u8], index: usize) -> Option<&'a [u8]> {
132 if data.len() < index {
133 None
134 } else {
135 let (head, tail) = data.split_at(index);
136 *data = tail;
137 Some(head)
138 }
139}
140
141impl<'a> SegmentTrait for Segment<'a> {
142 type SharedLibrary = SharedLibrary<'a>;
143
144 fn name(&self) -> &str {
145 unsafe {
146 match self.phdr.as_ref().unwrap().p_type {
147 libc::PT_NULL => "NULL",
148 libc::PT_LOAD => "LOAD",
149 libc::PT_DYNAMIC => "DYNAMIC",
150 libc::PT_INTERP => "INTERP",
151 libc::PT_NOTE => "NOTE",
152 libc::PT_SHLIB => "SHLIB",
153 libc::PT_PHDR => "PHDR",
154 libc::PT_TLS => "TLS",
155 libc::PT_GNU_EH_FRAME => "GNU_EH_FRAME",
156 libc::PT_GNU_STACK => "GNU_STACK",
157 libc::PT_GNU_RELRO => "GNU_RELRO",
158 _ => "(unknown segment type)",
159 }
160 }
161 }
162
163 #[inline]
164 fn is_code(&self) -> bool {
165 let hdr = self.phdr();
166 hdr.p_type == libc::PT_LOAD && (hdr.p_flags & 0x1) != 0
168 }
169
170 #[inline]
171 fn is_load(&self) -> bool {
172 self.phdr().p_type == libc::PT_LOAD
173 }
174
175 #[inline]
176 fn stated_virtual_memory_address(&self) -> Svma {
177 Svma(self.phdr().p_vaddr as _)
178 }
179
180 #[inline]
181 fn len(&self) -> usize {
182 self.phdr().p_memsz as _
183 }
184}
185
186pub struct SegmentIter<'a> {
188 inner: std::slice::Iter<'a, Phdr>,
189}
190
191impl<'a> Iterator for SegmentIter<'a> {
192 type Item = Segment<'a>;
193
194 #[inline]
195 fn next(&mut self) -> Option<Self::Item> {
196 self.inner.next().map(|phdr| Segment {
197 phdr: phdr,
198 shlib: PhantomData,
199 })
200 }
201}
202
203impl<'a> fmt::Debug for SegmentIter<'a> {
204 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
205 let ref phdr = self.inner.as_slice()[0];
206
207 f.debug_struct("SegmentIter")
208 .field("phdr", &DebugPhdr(phdr))
209 .finish()
210 }
211}
212
213pub struct SharedLibrary<'a> {
215 size: usize,
216 addr: *const u8,
217 name: Cow<'a, CStr>,
218 headers: &'a [Phdr],
219}
220
221struct IterState<F> {
222 f: F,
223 panic: Option<Box<dyn Any + Send>>,
224 idx: usize,
225}
226
227const CONTINUE: libc::c_int = 0;
228const BREAK: libc::c_int = 1;
229
230impl<'a> SharedLibrary<'a> {
231 unsafe fn new(info: &'a libc::dl_phdr_info, size: usize, is_first_lib: bool) -> Self {
232 let mut name = Cow::Borrowed(if info.dlpi_name.is_null() {
240 CStr::from_bytes_with_nul_unchecked(b"\0")
241 } else {
242 CStr::from_ptr(info.dlpi_name)
243 });
244 if name.to_bytes().is_empty() {
245 if is_first_lib {
246 if let Ok(exe) = current_exe() {
247 name = Cow::Owned(CString::from_vec_unchecked(exe.into_os_string().into_vec()));
248 }
249 } else {
250 let mut dlinfo: libc::Dl_info = mem::zeroed();
251 if libc::dladdr(info.dlpi_addr as *const libc::c_void, &mut dlinfo) != 0 {
252 name = Cow::Owned(CString::from(CStr::from_ptr(dlinfo.dli_fname)));
253 }
254 }
255 }
256
257 SharedLibrary {
258 size: size,
259 addr: info.dlpi_addr as usize as *const _,
260 name,
261 headers: slice::from_raw_parts(info.dlpi_phdr, info.dlpi_phnum as usize),
262 }
263 }
264
265 unsafe extern "C" fn callback<F, C>(
266 info: *mut libc::dl_phdr_info,
267 size: usize,
268 state: *mut libc::c_void,
269 ) -> libc::c_int
270 where
271 F: FnMut(&Self) -> C,
272 C: Into<IterationControl>,
273 {
274 if (*info).dlpi_phdr.is_null() {
275 return CONTINUE;
276 }
277
278 let state = &mut *(state as *mut IterState<F>);
279 state.idx += 1;
280
281 match panic::catch_unwind(panic::AssertUnwindSafe(|| {
282 let info = info.as_ref().unwrap();
283 let shlib = SharedLibrary::new(info, size, state.idx == 1);
284
285 (state.f)(&shlib).into()
286 })) {
287 Ok(IterationControl::Continue) => CONTINUE,
288 Ok(IterationControl::Break) => BREAK,
289 Err(panicked) => {
290 state.panic = Some(panicked);
291 BREAK
292 }
293 }
294 }
295
296 fn note_segments(&self) -> impl Iterator<Item = Segment<'a>> {
297 self.segments().filter(|s| s.is_note())
298 }
299}
300
301impl<'a> SharedLibraryTrait for SharedLibrary<'a> {
302 type Segment = Segment<'a>;
303 type SegmentIter = SegmentIter<'a>;
304
305 #[inline]
306 fn name(&self) -> &OsStr {
307 OsStr::from_bytes(self.name.to_bytes())
308 }
309
310 fn id(&self) -> Option<SharedLibraryId> {
311 for segment in self.note_segments() {
316 for (note_type, note_name, note_descriptor) in unsafe { segment.notes(self) } {
317 if note_type == NT_GNU_BUILD_ID && note_name == b"GNU\0" {
318 return Some(SharedLibraryId::GnuBuildId(note_descriptor.to_vec()));
319 }
320 }
321 }
322
323 None
324 }
325
326 #[inline]
327 fn segments(&self) -> Self::SegmentIter {
328 SegmentIter {
329 inner: self.headers.iter(),
330 }
331 }
332
333 #[inline]
334 fn virtual_memory_bias(&self) -> Bias {
335 Bias(self.addr as usize)
336 }
337
338 #[inline]
339 fn each<F, C>(f: F)
340 where
341 F: FnMut(&Self) -> C,
342 C: Into<IterationControl>,
343 {
344 let mut state = IterState {
345 f: f,
346 panic: None,
347 idx: 0,
348 };
349
350 unsafe {
351 libc::dl_iterate_phdr(Some(Self::callback::<F, C>), &mut state as *mut _ as *mut _);
352 }
353
354 if let Some(panic) = state.panic {
355 panic::resume_unwind(panic);
356 }
357 }
358}
359
360impl<'a> fmt::Debug for SharedLibrary<'a> {
361 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
362 write!(
363 f,
364 "SharedLibrary {{ size: {:?}, addr: {:?}, ",
365 self.size, self.addr
366 )?;
367 write!(f, "name: {:?}, headers: [", self.name)?;
368
369 let l = self.headers.len();
372 self.headers[..(l - 1)]
373 .into_iter()
374 .map(|phdr| write!(f, "{:?}, ", &DebugPhdr(phdr)))
375 .collect::<fmt::Result>()?;
376
377 write!(f, "{:?}", &DebugPhdr(&self.headers[l - 1]))?;
378
379 write!(f, "] }}")
380 }
381}
382
383struct DebugPhdr<'a>(&'a Phdr);
384
385impl<'a> fmt::Debug for DebugPhdr<'a> {
386 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
387 let phdr = self.0;
388
389 f.debug_struct("Phdr")
392 .field("p_type", &phdr.p_type)
393 .field("p_flags", &phdr.p_flags)
394 .field("p_offset", &phdr.p_offset)
395 .field("p_vaddr", &phdr.p_vaddr)
396 .field("p_paddr", &phdr.p_paddr)
397 .field("p_filesz", &phdr.p_filesz)
398 .field("p_memsz", &phdr.p_memsz)
399 .field("p_align", &phdr.p_align)
400 .finish()
401 }
402}
403
404#[cfg(test)]
405mod tests {
406 use crate::linux;
407 use crate::{IterationControl, Segment, SharedLibrary};
408
409 #[test]
410 fn have_libc() {
411 let mut found_libc = false;
412 linux::SharedLibrary::each(|info| {
413 found_libc |= info
414 .name
415 .to_bytes()
416 .split(|c| *c == b'.' || *c == b'/')
417 .find(|s| s == b"libc")
418 .is_some();
419 });
420 assert!(found_libc);
421 }
422
423 #[test]
424 fn can_break() {
425 let mut first_count = 0;
426 linux::SharedLibrary::each(|_| {
427 first_count += 1;
428 });
429 assert!(first_count > 2);
430
431 let mut second_count = 0;
432 linux::SharedLibrary::each(|_| {
433 second_count += 1;
434
435 if second_count == first_count - 1 {
436 IterationControl::Break
437 } else {
438 IterationControl::Continue
439 }
440 });
441 assert_eq!(second_count, first_count - 1);
442 }
443
444 #[test]
445 fn get_name() {
446 use std::ffi::OsStr;
447 let mut names = vec![];
448 linux::SharedLibrary::each(|shlib| {
449 println!("{:?}", shlib);
450 let name = shlib.name();
451 if name != OsStr::new("") {
452 names.push(name.to_str().unwrap().to_string());
453 }
454 });
455
456 assert!(names.iter().any(|x| x.contains("findshlibs")));
457 assert!(names.iter().any(|x| x.contains("libc.so")));
458 }
459
460 #[test]
461 #[cfg(target_os = "linux")]
462 fn get_id() {
463 use std::path::Path;
464 use std::process::Command;
465
466 linux::SharedLibrary::each(|shlib| {
467 let name = shlib.name();
468 let id = shlib.id();
469 if id.is_none() {
470 println!("no id found for {:?}", name);
471 return;
472 }
473 let path: &Path = name.as_ref();
474 if !path.is_absolute() {
475 return;
476 }
477 let gnu_build_id = id.unwrap().to_string();
478 let readelf = Command::new("readelf")
479 .arg("-n")
480 .arg(path)
481 .output()
482 .unwrap();
483 for line in String::from_utf8(readelf.stdout).unwrap().lines() {
484 if let Some(index) = line.find("Build ID: ") {
485 let readelf_build_id = line[index + 9..].trim();
486 assert_eq!(readelf_build_id, gnu_build_id);
487 }
488 }
489 println!("{}: {}", path.display(), gnu_build_id);
490 });
491 }
492
493 #[test]
494 fn have_load_segment() {
495 linux::SharedLibrary::each(|shlib| {
496 println!("shlib = {:?}", shlib.name());
497
498 let mut found_load = false;
499 for seg in shlib.segments() {
500 println!(" segment = {:?}", seg.name());
501
502 found_load |= seg.name() == "LOAD";
503 }
504 assert!(found_load);
505 });
506 }
507}