goblin/elf/
mod.rs

1//! The generic ELF module, which gives access to ELF constants and other helper functions, which are independent of ELF bithood.  Also defines an `Elf` struct which implements a unified parser that returns a wrapped `Elf64` or `Elf32` binary.
2//!
3//! To access the exact 32-bit or 64-bit versions, use [goblin::elf32::Header](header/header32/struct.Header.html)/[goblin::elf64::Header](header/header64/struct.Header.html), etc., for the various 32/64-bit structs.
4//!
5//! # Example
6//!
7//! ```rust
8//! use std::fs::File;
9//!
10//! pub fn read (bytes: &[u8]) {
11//!   match goblin::elf::Elf::parse(&bytes) {
12//!     Ok(binary) => {
13//!       let entry = binary.entry;
14//!       for ph in binary.program_headers {
15//!         if ph.p_type == goblin::elf::program_header::PT_LOAD {
16//!           // TODO: you should validate p_filesz before allocating.
17//!           let mut _buf = vec![0u8; ph.p_filesz as usize];
18//!           // read responsibly
19//!          }
20//!       }
21//!     },
22//!     Err(_) => ()
23//!   }
24//! }
25//! ```
26//!
27//! This will properly access the underlying 32-bit or 64-bit binary automatically. Note that since
28//! 32-bit binaries typically have shorter 32-bit values in some cases (specifically for addresses and pointer
29//! values), these values are upcasted to u64/i64s when appropriate.
30//!
31//! See [goblin::elf::Elf](struct.Elf.html) for more information.
32//!
33//! You are still free to use the specific 32-bit or 64-bit versions by accessing them through `goblin::elf64`, etc., but you will have to parse and/or construct the various components yourself.
34//! In other words, there is no unified 32/64-bit `Elf` struct.
35//!
36//! # Note
37//! To use the automagic ELF datatype union parser, you _must_ enable/opt-in to the  `elf64`, `elf32`, and
38//! `endian_fd` features if you disable `default`.
39
40#[macro_use]
41pub(crate) mod gnu_hash;
42
43// These are shareable values for the 32/64 bit implementations.
44//
45// They are publicly re-exported by the pub-using module
46pub mod compression_header;
47pub mod header;
48pub mod program_header;
49pub mod section_header;
50#[macro_use]
51pub mod sym;
52pub mod dynamic;
53#[macro_use]
54pub mod reloc;
55pub mod note;
56#[cfg(all(any(feature = "elf32", feature = "elf64"), feature = "alloc"))]
57pub mod symver;
58
59macro_rules! if_sylvan {
60    ($($i:item)*) => ($(
61        #[cfg(all(feature = "elf32", feature = "elf64", feature = "endian_fd"))]
62        $i
63    )*)
64}
65
66if_sylvan! {
67    use scroll::{ctx, Pread, Endian};
68    use crate::strtab::Strtab;
69    use crate::error;
70    use crate::options::{Permissive, ParseOptions};
71    use crate::container::{Container, Ctx};
72    use alloc::vec::Vec;
73    use core::cmp;
74
75    pub use header::Header;
76    pub use program_header::ProgramHeader;
77    pub use section_header::SectionHeader;
78    pub use sym::Symtab;
79    pub use sym::Sym;
80    pub use dynamic::Dyn;
81    pub use dynamic::Dynamic;
82    pub use reloc::Reloc;
83    pub use reloc::RelocSection;
84    pub use symver::{VersymSection, VerdefSection, VerneedSection};
85
86    pub type ProgramHeaders = Vec<ProgramHeader>;
87    pub type SectionHeaders = Vec<SectionHeader>;
88    pub type ShdrIdx = usize;
89
90    #[derive(Debug)]
91    /// An ELF binary. The underlying data structures are read according to the headers byte order and container size (32 or 64).
92    pub struct Elf<'a> {
93        /// The ELF header, which provides a rudimentary index into the rest of the binary
94        pub header: Header,
95        /// The program headers; they primarily tell the kernel and the dynamic linker
96        /// how to load this binary
97        pub program_headers: ProgramHeaders,
98        /// The sections headers. These are strippable, never count on them being
99        /// here unless you're a static linker!
100        pub section_headers: SectionHeaders,
101        /// The section header string table
102        pub shdr_strtab: Strtab<'a>,
103        /// The string table for the dynamically accessible symbols
104        pub dynstrtab: Strtab<'a>,
105        /// The dynamically accessible symbols, i.e., exports, imports.
106        /// This is what the dynamic linker uses to dynamically load and link your binary,
107        /// or find imported symbols for binaries which dynamically link against your library
108        pub dynsyms: Symtab<'a>,
109        /// The debugging symbol table
110        pub syms: Symtab<'a>,
111        /// The string table for the symbol table
112        pub strtab: Strtab<'a>,
113        /// Contains dynamic linking information, with the _DYNAMIC array + a preprocessed DynamicInfo for that array
114        pub dynamic: Option<Dynamic>,
115        /// The dynamic relocation entries (strings, copy-data, etc.) with an addend
116        pub dynrelas: RelocSection<'a>,
117        /// The dynamic relocation entries without an addend
118        pub dynrels: RelocSection<'a>,
119        /// The plt relocation entries (procedure linkage table). For 32-bit binaries these are usually Rel (no addend)
120        pub pltrelocs: RelocSection<'a>,
121        /// Section relocations by section index (only present if this is a relocatable object file)
122        pub shdr_relocs: Vec<(ShdrIdx, RelocSection<'a>)>,
123        /// The binary's soname, if it has one
124        pub soname: Option<&'a str>,
125        /// The binary's program interpreter (e.g., dynamic linker), if it has one
126        pub interpreter: Option<&'a str>,
127        /// A list of this binary's dynamic libraries it uses, if there are any
128        pub libraries: Vec<&'a str>,
129        /// A list of runtime search paths for this binary's dynamic libraries it uses, if there
130        /// are any. (deprecated)
131        pub rpaths: Vec<&'a str>,
132        /// A list of runtime search paths for this binary's dynamic libraries it uses, if there
133        /// are any.
134        pub runpaths: Vec<&'a str>,
135        /// Whether this is a 64-bit elf or not
136        pub is_64: bool,
137        /// Whether this is a shared object or not
138        pub is_lib: bool,
139        /// The binaries entry point address, if it has one
140        pub entry: u64,
141        /// Whether the binary is little endian or not
142        pub little_endian: bool,
143        /// Contains the symbol version information from the optional section
144        /// [`SHT_GNU_VERSYM`][section_header::SHT_GNU_VERSYM] (GNU extenstion).
145        pub versym : Option<VersymSection<'a>>,
146        /// Contains the version definition information from the optional section
147        /// [`SHT_GNU_VERDEF`][section_header::SHT_GNU_VERDEF] (GNU extenstion).
148        pub verdef : Option<VerdefSection<'a>>,
149        /// Contains the version needed information from the optional section
150        /// [`SHT_GNU_VERNEED`][section_header::SHT_GNU_VERNEED] (GNU extenstion).
151        pub verneed : Option<VerneedSection<'a>>,
152        ctx: Ctx,
153    }
154
155    impl<'a> Elf<'a> {
156        /// Try to iterate notes in PT_NOTE program headers; returns `None` if there aren't any note headers in this binary
157        pub fn iter_note_headers(&self, data: &'a [u8]) -> Option<note::NoteIterator<'a>> {
158            let mut iters = vec![];
159            for phdr in &self.program_headers {
160                if phdr.p_type == program_header::PT_NOTE {
161                    let offset = phdr.p_offset as usize;
162                    let alignment = phdr.p_align as usize;
163
164                    iters.push(note::NoteDataIterator {
165                        data,
166                        offset,
167                        size: offset.saturating_add(phdr.p_filesz as usize),
168                        ctx: (alignment, self.ctx)
169                    });
170                }
171            }
172
173            if iters.is_empty() {
174                None
175            } else {
176                Some(note::NoteIterator {
177                    iters: iters,
178                    index: 0,
179                })
180            }
181        }
182        /// Try to iterate notes in SHT_NOTE sections; returns `None` if there aren't any note sections in this binary
183        ///
184        /// If a section_name is given, only the section with the according name is iterated.
185        pub fn iter_note_sections(
186            &self,
187            data: &'a [u8],
188            section_name: Option<&str>,
189        ) -> Option<note::NoteIterator<'a>> {
190            let mut iters = vec![];
191            for sect in &self.section_headers {
192                if sect.sh_type != section_header::SHT_NOTE {
193                    continue;
194                }
195
196                if section_name.is_some() && self.shdr_strtab.get_at(sect.sh_name) != section_name {
197                    continue;
198                }
199
200                let offset = sect.sh_offset as usize;
201                let alignment = sect.sh_addralign as usize;
202                iters.push(note::NoteDataIterator {
203                    data,
204                    offset,
205                    size: offset.saturating_add(sect.sh_size as usize),
206                    ctx: (alignment, self.ctx)
207                });
208            }
209
210            if iters.is_empty() {
211                None
212            } else {
213                Some(note::NoteIterator {
214                    iters: iters,
215                    index: 0,
216                })
217            }
218        }
219        pub fn is_object_file(&self) -> bool {
220            self.header.e_type == header::ET_REL
221        }
222
223        /// Parses the contents to get the Header only. This `bytes` buffer should contain at least the length for parsing Header.
224        pub fn parse_header(bytes: &'a [u8]) -> error::Result<Header> {
225            bytes.pread::<Header>(0)
226        }
227
228        /// Lazy parse the ELF contents. This function mainly just assembles an Elf struct. Once we have the struct, we can choose to parse whatever we want.
229        pub fn lazy_parse(header: Header) -> error::Result<Self> {
230            let misc = parse_misc(&header)?;
231
232            Ok(Elf {
233                header,
234                program_headers: vec![],
235                section_headers: Default::default(),
236                shdr_strtab: Default::default(),
237                dynamic: None,
238                dynsyms: Default::default(),
239                dynstrtab: Strtab::default(),
240                syms: Default::default(),
241                strtab: Default::default(),
242                dynrelas: Default::default(),
243                dynrels: Default::default(),
244                pltrelocs: Default::default(),
245                shdr_relocs: Default::default(),
246                soname: None,
247                interpreter: None,
248                libraries: vec![],
249                rpaths: vec![],
250                runpaths: vec![],
251                is_64: misc.is_64,
252                is_lib: misc.is_lib,
253                entry: misc.entry,
254                little_endian: misc.little_endian,
255                ctx: misc.ctx,
256                versym: None,
257                verdef: None,
258                verneed: None,
259            })
260        }
261
262        /// Parses the contents of the byte stream in `bytes`, and maybe returns a unified binary
263        pub fn parse(bytes: &'a [u8]) -> error::Result<Self> {
264            Self::parse_with_opts(bytes, &ParseOptions::default())
265        }
266
267        /// Parses the contents of the byte stream in `bytes` with options, and maybe returns a unified binary
268        pub fn parse_with_opts(bytes: &'a [u8], opts: &ParseOptions) -> error::Result<Self> {
269            let header = Self::parse_header(bytes)?;
270            let misc = parse_misc(&header)?;
271            let ctx = misc.ctx;
272            let permissive = opts.parse_mode.is_permissive();
273
274            let program_headers = ProgramHeader::parse(bytes, header.e_phoff as usize, header.e_phnum as usize, ctx)?;
275
276            let mut interpreter = None;
277            for ph in &program_headers {
278                if ph.p_type == program_header::PT_INTERP && ph.p_filesz != 0 {
279                    let count = (ph.p_filesz - 1) as usize;
280                    let offset = ph.p_offset as usize;
281                    interpreter = bytes.pread_with::<&str>(offset, ::scroll::ctx::StrCtx::Length(count)).ok();
282                }
283            }
284
285            let section_headers = SectionHeader::parse(bytes, header.e_shoff as usize, header.e_shnum as usize, ctx)
286                .or_permissive_and_default(permissive, "Failed to parse section headers")?;
287
288            let get_strtab = |section_headers: &[SectionHeader], mut section_idx: usize| {
289                if section_idx == section_header::SHN_XINDEX as usize {
290                    if section_headers.is_empty() {
291                        return Ok(Strtab::default())
292                    }
293                    section_idx = section_headers[0].sh_link as usize;
294                }
295
296                if section_idx >= section_headers.len() {
297                    // FIXME: warn! here
298                    Ok(Strtab::default())
299                } else {
300                    let shdr = &section_headers[section_idx];
301                    shdr.check_size_with_opts(bytes.len(), permissive)?;
302                    Strtab::parse_with_opts(bytes, shdr.sh_offset as usize, shdr.sh_size as usize, 0x0, opts)
303                }
304            };
305
306            let strtab_idx = header.e_shstrndx as usize;
307            let shdr_strtab = get_strtab(&section_headers, strtab_idx)?;
308
309            let mut syms = Symtab::default();
310            let mut strtab = Strtab::default();
311            if let Some(shdr) = section_headers.iter().rfind(|shdr| shdr.sh_type as u32 == section_header::SHT_SYMTAB) {
312                let size = shdr.sh_entsize;
313                let initial_count = if size == 0 { 0 } else { shdr.sh_size / size };
314
315                // Check for extremely large counts that exceed usize capacity
316                let count = if initial_count > usize::MAX as u64 {
317
318                    Err(crate::error::Error::Malformed(
319                        format!(
320                            "Symbol table count ({}) from section header exceeds maximum possible value",
321                            initial_count
322                        )
323                    ))
324                    .or_permissive_and_then(
325                        permissive,
326                        "Symbol table count exceeds maximum; truncating",
327                        || usize::MAX as u64,
328                    )?
329                } else {
330                    initial_count
331                };
332
333                syms = Symtab::parse_with_opts(bytes, shdr.sh_offset as usize, count as usize, ctx, opts)?;
334                strtab = get_strtab(&section_headers, shdr.sh_link as usize)?;
335            }
336
337            let mut is_pie = false;
338            let mut soname = None;
339            let mut libraries = vec![];
340            let mut rpaths = vec![];
341            let mut runpaths = vec![];
342            let mut dynsyms = Symtab::default();
343            let mut dynrelas = RelocSection::default();
344            let mut dynrels = RelocSection::default();
345            let mut pltrelocs = RelocSection::default();
346            let mut dynstrtab = Strtab::default();
347            let dynamic = Dynamic::parse(bytes, &program_headers, ctx)
348                .or_permissive_and_default(permissive, "Failed to parse dynamic section")?;
349            if let Some(ref dynamic) = dynamic {
350                let dyn_info = &dynamic.info;
351
352                is_pie = dyn_info.flags_1 & dynamic::DF_1_PIE != 0;
353                dynstrtab = Strtab::parse_with_opts(bytes,
354                                      dyn_info.strtab,
355                                      dyn_info.strsz,
356                                      0x0, opts)
357                    .or_permissive_and_default(permissive, "Failed to parse dynamic string table")?;
358
359                if dyn_info.soname != 0 {
360                    // FIXME: warn! here
361                    soname = dynstrtab.get_at(dyn_info.soname);
362                }
363                if dyn_info.needed_count > 0 {
364                    libraries = dynamic.get_libraries(&dynstrtab);
365                }
366                for dyn_ in &dynamic.dyns {
367                    if dyn_.d_tag == dynamic::DT_RPATH {
368                        if let Some(path) = dynstrtab.get_at(dyn_.d_val as usize) {
369                            rpaths.push(path);
370                        }
371                    } else if dyn_.d_tag == dynamic::DT_RUNPATH {
372                        if let Some(path) = dynstrtab.get_at(dyn_.d_val as usize) {
373                            runpaths.push(path);
374                        }
375                    }
376                }
377                // parse the dynamic relocations
378                dynrelas = RelocSection::parse(bytes, dyn_info.rela, dyn_info.relasz, true, ctx)
379                    .or_permissive_and_default(permissive, "Failed to parse dynamic RELA relocations")?;
380
381                dynrels = RelocSection::parse(bytes, dyn_info.rel, dyn_info.relsz, false, ctx)
382                    .or_permissive_and_default(permissive, "Failed to parse dynamic REL relocations")?;
383
384                let is_rela = dyn_info.pltrel as u64 == dynamic::DT_RELA;
385                pltrelocs = RelocSection::parse(bytes, dyn_info.jmprel, dyn_info.pltrelsz, is_rela, ctx)
386                    .or_permissive_and_default(permissive, "Failed to parse PLT relocations")?;
387
388                let mut num_syms = if let Some(gnu_hash) = dyn_info.gnu_hash {
389                    gnu_hash_len(bytes, gnu_hash as usize, ctx)
390                        .or_permissive_and_default(permissive, "Failed to parse GNU hash table")?
391                } else if let Some(hash) = dyn_info.hash {
392                    hash_len(bytes, hash as usize, header.e_machine, ctx)
393                        .or_permissive_and_default(permissive, "Failed to parse hash table")?
394                } else {
395                    0
396                };
397                let max_reloc_sym = dynrelas.iter()
398                    .chain(dynrels.iter())
399                    .chain(pltrelocs.iter())
400                    .fold(0, |num, reloc| cmp::max(num, reloc.r_sym));
401                if max_reloc_sym != 0 {
402                    num_syms = cmp::max(num_syms, max_reloc_sym + 1);
403                }
404                dynsyms = Symtab::parse_with_opts(bytes, dyn_info.symtab, num_syms, ctx, opts)?;
405            }
406
407            let mut shdr_relocs = vec![];
408            for (idx, section) in section_headers.iter().enumerate() {
409                let is_rela = section.sh_type == section_header::SHT_RELA;
410                if is_rela || section.sh_type == section_header::SHT_REL {
411
412                    section.check_size_with_opts(bytes.len(), permissive)?;
413                    let sh_relocs_opt = RelocSection::parse(bytes, section.sh_offset as usize, section.sh_size as usize, is_rela, ctx)
414                        .map(Some)
415                        .or_permissive_and_default(
416                            permissive,
417                            "Failed to parse section relocation; skipping",
418                        )?;
419
420                    if let Some(sh_relocs) = sh_relocs_opt {
421                        shdr_relocs.push((idx, sh_relocs));
422                    }
423                }
424            }
425
426            let versym = symver::VersymSection::parse(bytes, &section_headers, ctx)
427                .or_permissive_and_default(permissive, "Failed to parse version symbol section")?;
428
429            let verdef = symver::VerdefSection::parse(bytes, &section_headers, ctx)
430                .or_permissive_and_default(permissive, "Failed to parse version definition section")?;
431
432            let verneed = symver::VerneedSection::parse(bytes, &section_headers, ctx)
433                .or_permissive_and_default(permissive, "Failed to parse version need section")?;
434
435            let is_lib = misc.is_lib && !is_pie;
436
437            Ok(Elf {
438                header,
439                program_headers,
440                section_headers,
441                shdr_strtab,
442                dynamic,
443                dynsyms,
444                dynstrtab,
445                syms,
446                strtab,
447                dynrelas,
448                dynrels,
449                pltrelocs,
450                shdr_relocs,
451                soname,
452                interpreter,
453                libraries,
454                rpaths,
455                runpaths,
456                is_64: misc.is_64,
457                is_lib,
458                entry: misc.entry,
459                little_endian: misc.little_endian,
460                ctx: ctx,
461                versym,
462                verdef,
463                verneed,
464            })
465        }
466    }
467
468    impl<'a> ctx::TryFromCtx<'a, (usize, Endian)> for Elf<'a> {
469        type Error = crate::error::Error;
470        fn try_from_ctx(src: &'a [u8], (_, _): (usize, Endian)) -> Result<(Elf<'a>, usize), Self::Error> {
471            let elf = Elf::parse(src)?;
472            Ok((elf, src.len()))
473        }
474    }
475
476    fn gnu_hash_len(bytes: &[u8], offset: usize, ctx: Ctx) -> error::Result<usize> {
477        let buckets_num = bytes.pread_with::<u32>(offset, ctx.le)? as usize;
478        let min_chain = bytes.pread_with::<u32>(offset + 4, ctx.le)? as usize;
479        let bloom_size = bytes.pread_with::<u32>(offset + 8, ctx.le)? as usize;
480        // We could handle min_chain==0 if we really had to, but it shouldn't happen.
481        if buckets_num == 0 || min_chain == 0 || bloom_size == 0 {
482            return Err(error::Error::Malformed(format!("Invalid DT_GNU_HASH: buckets_num={} min_chain={} bloom_size={}",
483                                                       buckets_num, min_chain, bloom_size)));
484        }
485        // Find the last bucket.
486        let buckets_offset = offset + 16 + bloom_size * if ctx.container.is_big() { 8 } else { 4 };
487        let mut max_chain = 0;
488        for bucket in 0..buckets_num {
489            let chain = bytes.pread_with::<u32>(buckets_offset + bucket * 4, ctx.le)? as usize;
490            if max_chain < chain {
491                max_chain = chain;
492            }
493        }
494        if max_chain < min_chain {
495            return Ok(0);
496        }
497        // Find the last chain within the bucket.
498        let mut chain_offset = buckets_offset + buckets_num * 4 + (max_chain - min_chain) * 4;
499        loop {
500            let hash = bytes.pread_with::<u32>(chain_offset, ctx.le)?;
501            max_chain += 1;
502            chain_offset += 4;
503            if hash & 1 != 0 {
504                return Ok(max_chain);
505            }
506        }
507    }
508
509    fn hash_len(bytes: &[u8], offset: usize, machine: u16, ctx: Ctx) -> error::Result<usize> {
510        // Based on readelf code.
511        let nchain = if (machine == header::EM_FAKE_ALPHA || machine == header::EM_S390) && ctx.container.is_big() {
512            bytes.pread_with::<u64>(offset.saturating_add(4), ctx.le)? as usize
513        } else {
514            bytes.pread_with::<u32>(offset.saturating_add(4), ctx.le)? as usize
515        };
516        Ok(nchain)
517    }
518
519    struct Misc {
520        is_64: bool,
521        is_lib: bool,
522        entry: u64,
523        little_endian: bool,
524        ctx: Ctx,
525    }
526
527    fn parse_misc(header: &Header) -> error::Result<Misc> {
528        let entry = header.e_entry as usize;
529        let is_lib = header.e_type == header::ET_DYN;
530        let is_lsb = header.e_ident[header::EI_DATA] == header::ELFDATA2LSB;
531        let endianness = scroll::Endian::from(is_lsb);
532        let class = header.e_ident[header::EI_CLASS];
533        if class != header::ELFCLASS64 && class != header::ELFCLASS32 {
534            return Err(error::Error::Malformed(format!("Unknown values in ELF ident header: class: {} endianness: {}",
535                                                        class,
536                                                        header.e_ident[header::EI_DATA])));
537        }
538        let is_64 = class == header::ELFCLASS64;
539        let container = if is_64 { Container::Big } else { Container::Little };
540        let ctx = Ctx::new(container, endianness);
541
542        Ok(Misc{
543            is_64,
544            is_lib,
545            entry: entry as u64,
546            little_endian:is_lsb,
547            ctx,
548        })
549    }
550}
551
552#[cfg(test)]
553mod tests {
554    use super::*;
555
556    #[test]
557    fn parse_crt1_64bit() {
558        let crt1: Vec<u8> = include!("../../etc/crt1.rs");
559        match Elf::parse(&crt1) {
560            Ok(binary) => {
561                assert!(binary.is_64);
562                assert!(!binary.is_lib);
563                assert_eq!(binary.entry, 0);
564                assert!(binary.syms.get(1000).is_none());
565                assert!(binary.syms.get(5).is_some());
566                let syms = binary.syms.to_vec();
567                assert!(!binary.section_headers.is_empty());
568                for (i, sym) in syms.iter().enumerate() {
569                    if i == 11 {
570                        let symtab = binary.strtab;
571                        println!("sym: {:?}", &sym);
572                        assert_eq!(&symtab[sym.st_name], "_start");
573                        break;
574                    }
575                }
576                assert!(!syms.is_empty());
577            }
578            Err(err) => {
579                panic!("failed: {}", err);
580            }
581        }
582    }
583
584    #[test]
585    fn parse_crt1_32bit() {
586        let crt1: Vec<u8> = include!("../../etc/crt132.rs");
587        match Elf::parse(&crt1) {
588            Ok(binary) => {
589                assert!(!binary.is_64);
590                assert!(!binary.is_lib);
591                assert_eq!(binary.entry, 0);
592                assert!(binary.syms.get(1000).is_none());
593                assert!(binary.syms.get(5).is_some());
594                let syms = binary.syms.to_vec();
595                assert!(!binary.section_headers.is_empty());
596                for (i, sym) in syms.iter().enumerate() {
597                    if i == 11 {
598                        let symtab = binary.strtab;
599                        println!("sym: {:?}", &sym);
600                        assert_eq!(&symtab[sym.st_name], "__libc_csu_fini");
601                        break;
602                    }
603                }
604                assert!(!syms.is_empty());
605            }
606            Err(err) => {
607                panic!("failed: {}", err);
608            }
609        }
610    }
611
612    // See https://github.com/m4b/goblin/issues/257
613    #[test]
614    #[allow(unused)]
615    fn no_use_statement_conflict() {
616        use crate::elf::section_header::*;
617        use crate::elf::*;
618
619        fn f(_: SectionHeader) {}
620    }
621}