@@ -11,6 +11,7 @@ use std::borrow::Cow;
1111use std:: env:: current_exe;
1212use std:: ffi:: { CStr , CString , OsStr } ;
1313use std:: fmt;
14+ use std:: iter;
1415use std:: marker:: PhantomData ;
1516use std:: mem;
1617use std:: os:: unix:: ffi:: OsStrExt ;
@@ -27,7 +28,12 @@ type Phdr = libc::Elf64_Phdr;
2728
2829const NT_GNU_BUILD_ID : u32 = 3 ;
2930
30- struct Nhdr32 {
31+ // Normally we would use `Elf32_Nhdr` on 32-bit platforms and `Elf64_Nhdr` on
32+ // 64-bit platforms. However, in practice it seems that only `Elf32_Nhdr` is
33+ // used, and reading through binutil's `readelf` source confirms this.
34+ #[ repr( C ) ]
35+ #[ derive( Debug , Clone , Copy ) ]
36+ struct Nhdr {
3137 pub n_namesz : libc:: Elf32_Word ,
3238 pub n_descsz : libc:: Elf32_Word ,
3339 pub n_type : libc:: Elf32_Word ,
@@ -41,14 +47,100 @@ pub struct Segment<'a> {
4147}
4248
4349impl < ' a > Segment < ' a > {
50+ fn phdr ( & self ) -> & ' a Phdr {
51+ unsafe { self . phdr . as_ref ( ) . unwrap ( ) }
52+ }
53+
54+ /// You must pass this segment's `SharedLibrary` or else this is wild UB.
55+ 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+
4461 fn is_load ( & self ) -> bool {
45- unsafe {
46- let hdr = self . phdr . as_ref ( ) . unwrap ( ) ;
47- match hdr. p_type {
48- libc:: PT_LOAD => true ,
49- _ => false ,
62+ self . phdr ( ) . p_type == libc:: PT_LOAD
63+ }
64+
65+ fn is_note ( & self ) -> bool {
66+ self . phdr ( ) . p_type == libc:: PT_NOTE
67+ }
68+
69+ /// Parse the contents of a `PT_NOTE` segment.
70+ ///
71+ /// Returns a triple of
72+ ///
73+ /// 1. The `NT_*` note type.
74+ /// 2. The note name.
75+ /// 3. The note descriptor payload.
76+ ///
77+ /// You must pass this segment's `SharedLibrary` or else this is wild UB.
78+ unsafe fn notes (
79+ & self ,
80+ shlib : & SharedLibrary < ' a > ,
81+ ) -> impl Iterator < Item = ( libc:: Elf32_Word , & ' a [ u8 ] , & ' a [ u8 ] ) > {
82+ assert ! ( self . is_note( ) ) ;
83+
84+ // `man 5 readelf` says that all of the `Nhdr`, name, and descriptor are
85+ // always 4-byte aligned, but we copy this alignment behavior from
86+ // `readelf` since that seems to match reality in practice.
87+ let alignment = std:: cmp:: max ( self . phdr ( ) . p_align as usize , 4 ) ;
88+ let align_up = move |data : & ' a [ u8 ] | {
89+ if alignment != 4 && alignment != 8 {
90+ return None ;
5091 }
51- }
92+
93+ let ptr = data. as_ptr ( ) as usize ;
94+ let alignment_minus_one = alignment - 1 ;
95+ let aligned_ptr = ptr. checked_add ( alignment_minus_one) ? & !alignment_minus_one;
96+ let diff = aligned_ptr - ptr;
97+ if data. len ( ) < diff {
98+ None
99+ } else {
100+ Some ( & data[ diff..] )
101+ }
102+ } ;
103+
104+ let mut data = self . data ( shlib) ;
105+
106+ iter:: from_fn ( move || {
107+ assert_eq ! ( data. as_ptr( ) as usize % alignment, 0 ) ;
108+
109+ // Each entry in a `PT_NOTE` segment begins with a
110+ // fixed-size header `Nhdr`.
111+ let nhdr_size = mem:: size_of :: < Nhdr > ( ) ;
112+ let nhdr = try_split_at ( & mut data, nhdr_size) ?;
113+ let nhdr = ( nhdr. as_ptr ( ) as * const Nhdr ) . as_ref ( ) . unwrap ( ) ;
114+
115+ // No need to `align_up` after the `Nhdr`.
116+ debug_assert_eq ! ( nhdr_size % alignment, 0 ) ;
117+
118+ // It is followed by a name of size `n_namesz`.
119+ let name_size = nhdr. n_namesz as usize ;
120+ let name = try_split_at ( & mut data, name_size) ?;
121+
122+ // And after that is the note's (aligned) descriptor payload of size
123+ // `n_descsz`.
124+ data = align_up ( data) ?;
125+ let desc_size = nhdr. n_descsz as usize ;
126+ let desc = try_split_at ( & mut data, desc_size) ?;
127+
128+ // Align the data for the next `Nhdr`.
129+ data = align_up ( data) ?;
130+
131+ Some ( ( nhdr. n_type , name, desc) )
132+ } )
133+ . fuse ( )
134+ }
135+ }
136+
137+ fn try_split_at < ' a > ( data : & mut & ' a [ u8 ] , index : usize ) -> Option < & ' a [ u8 ] > {
138+ if data. len ( ) < index {
139+ None
140+ } else {
141+ let ( head, tail) = data. split_at ( index) ;
142+ * data = tail;
143+ Some ( head)
52144 }
53145}
54146
@@ -204,6 +296,10 @@ impl<'a> SharedLibrary<'a> {
204296 }
205297 }
206298 }
299+
300+ fn note_segments ( & self ) -> impl Iterator < Item = Segment < ' a > > {
301+ self . segments ( ) . filter ( |s| s. is_note ( ) )
302+ }
207303}
208304
209305impl < ' a > SharedLibraryTrait for SharedLibrary < ' a > {
@@ -216,46 +312,14 @@ impl<'a> SharedLibraryTrait for SharedLibrary<'a> {
216312 }
217313
218314 fn id ( & self ) -> Option < SharedLibraryId > {
219- fn align ( alignment : usize , offset : & mut usize ) {
220- let diff = * offset % alignment;
221- if diff != 0 {
222- * offset += alignment - diff;
223- }
224- }
225-
226- unsafe {
227- for segment in self . segments ( ) {
228- let phdr = segment. phdr . as_ref ( ) . unwrap ( ) ;
229- if phdr. p_type != libc:: PT_NOTE {
230- continue ;
231- }
232-
233- let mut alignment = phdr. p_align as usize ;
234- // same logic as in gimli which took it from readelf
235- if alignment < 4 {
236- alignment = 4 ;
237- } else if alignment != 4 && alignment != 8 {
238- continue ;
239- }
240-
241- let mut offset = phdr. p_offset as usize ;
242- let end = offset + phdr. p_filesz as usize ;
243-
244- while offset < end {
245- // we always use an nhdr32 here as 64bit notes have not
246- // been observed in practice.
247- let nhdr = & * ( ( self . addr as usize + offset) as * const Nhdr32 ) ;
248- offset += mem:: size_of_val ( nhdr) ;
249- offset += nhdr. n_namesz as usize ;
250- align ( alignment, & mut offset) ;
251- let value =
252- slice:: from_raw_parts ( self . addr . add ( offset) , nhdr. n_descsz as usize ) ;
253- offset += nhdr. n_descsz as usize ;
254- align ( alignment, & mut offset) ;
255-
256- if nhdr. n_type as u32 == NT_GNU_BUILD_ID {
257- return Some ( SharedLibraryId :: GnuBuildId ( value. to_vec ( ) ) ) ;
258- }
315+ // Search for `PT_NOTE` segments, containing auxilliary information.
316+ // Such segments contain a series of "notes" and one kind of note is
317+ // `NT_GNU_BUILD_ID`, whose payload contains a unique identifier
318+ // generated by the linker. Return the first one we find, if any.
319+ for segment in self . note_segments ( ) {
320+ for ( note_type, note_name, note_descriptor) in unsafe { segment. notes ( self ) } {
321+ if note_type == NT_GNU_BUILD_ID && note_name == b"GNU\0 " {
322+ return Some ( SharedLibraryId :: GnuBuildId ( note_descriptor. to_vec ( ) ) ) ;
259323 }
260324 }
261325 }
0 commit comments