tango_bench/
linux.rs

1use goblin::{
2    elf::{Dyn, Elf},
3    elf64::{
4        dynamic::{DF_1_PIE, DT_FLAGS_1},
5        program_header::PT_DYNAMIC,
6    },
7    error::Error as GoblinError,
8};
9use scroll::{Pread, Pwrite};
10use std::{
11    fs, mem,
12    path::{Path, PathBuf},
13};
14use thiserror::Error;
15
16#[derive(Debug, Error)]
17pub enum Error {
18    #[error("Unable to parse ELF file")]
19    UnableToParseElf(GoblinError),
20
21    #[error("Unable to serialize ELF file")]
22    UnableToSerializeElf(GoblinError),
23
24    #[error("Unable to found DT_FLAGS_1 position")]
25    NoDTFlags1Found,
26
27    #[error("Unable to find PT_DYNAMIC section")]
28    NoDynamicSectionFound,
29
30    #[error("DT_FLAGS_1 flag crosscheck failed")]
31    FlagCrosscheckFailed,
32
33    #[error("IOError")]
34    IOError(#[from] std::io::Error),
35}
36
37/// Patches executable file for new version of glibc dynamic loader
38///
39/// After glibc 2.29 on linux `dlopen` is explicitly denying loading
40/// PIE executables as a shared library. The following error might appear:
41///
42/// ```console
43/// dlopen error: cannot dynamically load position-independent executable
44/// ```
45///
46/// From 2.29 [dynamic loader throws an error](glibc) if `DF_1_PIE` flag is set in
47/// `DT_FLAG_1` tag on the `PT_DYNAMIC` section. Although the loading of executable
48/// files as a shared library was never an intended use case, through the years
49/// some applications adopted this technique and it is very convenient in the context
50/// of paired benchmarking.
51///
52/// Following method check if this flag is set and patch binary at runtime
53/// (writing patched version in a different file). As far as I am aware
54/// this is safe modification because `DF_1_PIE` is purely informational and doesn't
55/// changle the dynamic linking process in any way. Theoretically in the future this modification
56/// could prevent ASLR ramndomization on the OS level which is irrelevant for benchmark
57/// executables.
58///
59/// [glibc]: https://github.com/bminor/glibc/blob/2e0c0ff95ca0e3122eb5b906ee26a31f284ce5ab/elf/dl-load.c#L1280-L1282
60pub fn patch_pie_binary_if_needed(
61    #[allow(unused_variables)] path: impl AsRef<Path>,
62) -> Result<Option<PathBuf>, Error> {
63    let mut bytes = fs::read(path.as_ref())?;
64    let elf = Elf::parse(&bytes).map_err(Error::UnableToParseElf)?;
65
66    let Some(dynamic) = elf.dynamic else {
67        return Ok(None);
68    };
69    if dynamic.info.flags_1 & DF_1_PIE == 0 {
70        return Ok(None);
71    }
72
73    let (dyn_idx, _) = dynamic
74        .dyns
75        .iter()
76        .enumerate()
77        .find(|(_, d)| d.d_tag == DT_FLAGS_1)
78        .ok_or(Error::NoDTFlags1Found)?;
79
80    // Finding PT_DYNAMIC section offset
81    let header = elf
82        .program_headers
83        .iter()
84        .find(|h| h.p_type == PT_DYNAMIC)
85        .ok_or(Error::NoDynamicSectionFound)?;
86
87    // Finding target Dyn item offset
88    let dyn_offset = header.p_offset as usize + dyn_idx * mem::size_of::<Dyn>();
89
90    // Crosschecking we found right dyn tag
91    let mut dyn_item = bytes
92        .pread::<Dyn>(dyn_offset)
93        .map_err(Error::UnableToSerializeElf)?;
94
95    if dyn_item.d_tag != DT_FLAGS_1 || dyn_item.d_val != dynamic.info.flags_1 {
96        return Err(Error::FlagCrosscheckFailed);
97    }
98
99    // clearing DF_1_PIE bit and writing patched binary
100    dyn_item.d_val &= !DF_1_PIE;
101    bytes
102        .pwrite(dyn_item, dyn_offset)
103        .map_err(Error::UnableToSerializeElf)?;
104
105    let path = path.as_ref().with_extension("patched");
106    fs::write(&path, bytes)?;
107
108    Ok(Some(path))
109}