rldd 0.4.0

A program to print shared object dependencies
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
// Parse the dynamic symbol table and the dynamic relocations of an ELF object.
// It is used to mimic the loader symbol resolution for the --data-relocs,
// --function-relocs, and --unused options.

use std::collections::HashSet;
use std::path::Path;
use std::str;

use object::elf::*;
use object::read::elf::{
    Dyn, FileHeader, ProgramHeader, Rel, Rela, SectionHeader, SectionTable, Sym, SymbolTable,
    VersionTable,
};
use object::read::SymbolIndex;
use object::Endianness;

// An undefined symbol referenced by a dynamic relocation.
#[derive(Debug)]
pub struct SymbolRef {
    pub name: String,
    // The required symbol version, from the .gnu.version information.
    pub version: Option<String>,
    // Whether the reference is weak (unresolved weak symbols are not an error).
    pub weak: bool,
    // Whether the relocation comes from the DT_JMPREL table (function/PLT
    // relocation, only processed in bind-now mode by the loader).
    pub plt: bool,
    // Whether the reference comes from a COPY relocation.
    pub copy: bool,
}

// A version required from a dependency, from the .gnu.version_r information.
#[derive(Debug)]
pub struct VersionNeed {
    // The dependency file name (vn_file).
    pub file: String,
    pub version: String,
    // Weak version references (VER_FLG_WEAK) are not an error.
    pub weak: bool,
}

#[derive(Debug, Default)]
pub struct ObjectSymbols {
    // Global visible defined symbols, candidates to resolve other objects
    // undefined references.
    pub defined: HashSet<String>,
    // Defined symbols along their version definition.
    pub defined_versioned: HashSet<(String, String)>,
    // Whether the object defines any version (.gnu.version_d presence).
    pub has_verdef: bool,
    // The version names the object defines.
    pub verdef_names: HashSet<String>,
    // The versions the object requires from its dependencies.
    pub verneeded: Vec<VersionNeed>,
    // Undefined symbols referenced by the object dynamic relocations.
    pub references: Vec<SymbolRef>,
    // Whether the object sets DF_BIND_NOW/DF_1_NOW, which makes the loader
    // also process the PLT relocations in --data-relocs mode.
    pub bind_now: bool,
}

pub fn parse<P: AsRef<Path>>(filename: &P) -> Option<ObjectSymbols> {
    let file = std::fs::File::open(filename).ok()?;
    let mmap = unsafe { memmap2::Mmap::map(&file) }.ok()?;
    let data: &[u8] = &mmap;

    match object::FileKind::parse(data).ok()? {
        object::FileKind::Elf32 => parse_elf(FileHeader32::<Endianness>::parse(data).ok()?, data),
        object::FileKind::Elf64 => parse_elf(FileHeader64::<Endianness>::parse(data).ok()?, data),
        _ => None,
    }
}

fn parse_elf<Elf: FileHeader<Endian = Endianness>>(
    elf: &Elf,
    data: &[u8],
) -> Option<ObjectSymbols> {
    let endian = elf.endian().ok()?;

    let sections = elf.sections(endian, data).ok()?;
    let dynsyms = sections.symbols(endian, data, SHT_DYNSYM).ok()?;
    let versions = sections.versions(endian, data).ok().flatten();

    let mut obj = ObjectSymbols::default();

    parse_verdef(&mut obj, endian, &sections, data);
    parse_verneed(&mut obj, endian, &sections, data);

    for (symidx, sym) in dynsyms.enumerate() {
        if sym.st_shndx(endian) == SHN_UNDEF {
            continue;
        }
        let bind = sym.st_bind();
        if bind != STB_GLOBAL && bind != STB_WEAK && bind != STB_GNU_UNIQUE {
            continue;
        }
        // Hidden and internal symbols do not participate in the dynamic
        // resolution of other objects.
        let visibility = sym.st_visibility();
        if visibility != STV_DEFAULT && visibility != STV_PROTECTED {
            continue;
        }
        if let Some(name) = symbol_name(endian, &dynsyms, sym) {
            obj.defined.insert(name.to_string());
            if let Some(version) = symbol_version(endian, &versions, symidx) {
                obj.defined_versioned.insert((name.to_string(), version));
            }
        }
    }

    // The address of the PLT relocation table, used to distinguish function
    // relocations from data ones, and the object bind-now state.
    let dyninfo = parse_dynamic_info(endian, elf, data);
    let jmprel = dyninfo.jmprel;
    obj.bind_now = dyninfo.bind_now;
    let is_mips64el = elf.is_mips64el(endian);
    let copy_reloc = copy_relocation_type(elf.e_machine(endian));
    let tlsdesc_reloc = tlsdesc_relocation_type(elf.e_machine(endian));

    // Track already seen references to avoid reporting a symbol multiple times
    // for the same object.
    let mut seen = HashSet::<(String, bool)>::new();

    for section in sections.iter() {
        // The loader only processes the allocated relocation tables.
        if !section.sh_flags(endian).contains(SHF_ALLOC) {
            continue;
        }
        let plt = jmprel.is_some_and(|addr| section.sh_addr(endian).into() == addr);

        if let Ok(Some((relas, _link))) = section.rela(endian, data) {
            for rela in relas {
                let r_type = rela.r_type(endian, is_mips64el);
                add_reference(
                    endian,
                    &dynsyms,
                    &versions,
                    rela.symbol(endian, is_mips64el),
                    plt && tlsdesc_reloc != Some(r_type),
                    copy_reloc == Some(r_type),
                    &mut seen,
                    &mut obj.references,
                );
            }
        } else if let Ok(Some((rels, _link))) = section.rel(endian, data) {
            for rel in rels {
                // The object crate Rel trait does not handle the mips64el
                // packed r_info (unlike the Rela one), where the fields are
                // encoded as a byte-swapped 64-bit value with r_sym on the
                // first four bytes (is_mips64el implies ELFCLASS64).
                let (symidx, r_type) = if is_mips64el {
                    let info: u64 = rel.r_info(endian).into();
                    let info = (info << 32)
                        | ((info >> 8) & 0xff000000)
                        | ((info >> 24) & 0x00ff0000)
                        | ((info >> 40) & 0x0000ff00)
                        | ((info >> 56) & 0x000000ff);
                    let sym = (info >> 32) as u32;
                    (
                        if sym == 0 {
                            None
                        } else {
                            Some(SymbolIndex(sym as usize))
                        },
                        RelocationType((info & 0xffff_ffff) as u32),
                    )
                } else {
                    (rel.symbol(endian), rel.r_type(endian))
                };
                add_reference(
                    endian,
                    &dynsyms,
                    &versions,
                    symidx,
                    plt && tlsdesc_reloc != Some(r_type),
                    copy_reloc == Some(r_type),
                    &mut seen,
                    &mut obj.references,
                );
            }
        }
    }

    // The MIPS ABI does not use dynamic relocations for the external symbol
    // references: the dynamic symbols from DT_MIPS_GOTSYM onwards correspond
    // to the global GOT entries ( which the loader resolves directly).
    // The data entries are resolved eagerly, while the function ones with a
    // lazy stub (non zero st_value) are resolved lazily like the PLT relocations.
    if elf.e_machine(endian) == EM_MIPS {
        if let (Some(gotsym), Some(symtabno)) = (dyninfo.mips_gotsym, dyninfo.mips_symtabno) {
            for idx in gotsym..symtabno {
                let symidx = SymbolIndex(idx as usize);
                let Ok(sym) = dynsyms.symbol(symidx) else {
                    continue;
                };
                let lazy = sym.st_type() == STT_FUNC && sym.st_value(endian).into() != 0;
                add_reference(
                    endian,
                    &dynsyms,
                    &versions,
                    Some(symidx),
                    lazy,
                    false,
                    &mut seen,
                    &mut obj.references,
                );
            }
        }
    }

    Some(obj)
}

fn symbol_name<'data, Elf: FileHeader>(
    endian: Elf::Endian,
    dynsyms: &SymbolTable<'data, Elf, &'data [u8]>,
    sym: &Elf::Sym,
) -> Option<&'data str> {
    let name = dynsyms.symbol_name(endian, sym).ok()?;
    let name = str::from_utf8(name).ok()?;
    if name.is_empty() {
        return None;
    }
    Some(name)
}

// The architecture COPY relocation type.
fn copy_relocation_type(e_machine: Machine) -> Option<RelocationType> {
    match e_machine {
        EM_386 => Some(R_386_COPY),
        EM_X86_64 => Some(R_X86_64_COPY),
        EM_AARCH64 => Some(R_AARCH64_COPY),
        EM_ARM => Some(R_ARM_COPY),
        EM_68K => Some(R_68K_COPY),
        EM_ALPHA => Some(R_ALPHA_COPY),
        EM_CSKY => Some(R_CKCORE_COPY),
        EM_IA_64 => Some(R_IA64_COPY),
        EM_LOONGARCH => Some(R_LARCH_COPY),
        EM_MICROBLAZE => Some(R_MICROBLAZE_COPY),
        EM_MIPS => Some(R_MIPS_COPY),
        EM_PARISC => Some(R_PARISC_COPY),
        EM_PPC => Some(R_PPC_COPY),
        EM_PPC64 => Some(R_PPC64_COPY),
        EM_RISCV => Some(R_RISCV_COPY),
        EM_S390 => Some(R_390_COPY),
        EM_SH => Some(R_SH_COPY),
        EM_SPARC | EM_SPARC32PLUS | EM_SPARCV9 => Some(R_SPARC_COPY),
        _ => None,
    }
}

// TLSDESC relocations are placed on the DT_JMPREL table, but the glibc loader
// processes them eagerly even in lazy mode, so they are reported by --data-relocs
// like the data relocations.
// ARM is not included, where the loader still handles R_ARM_TLS_DESC lazily.
fn tlsdesc_relocation_type(e_machine: Machine) -> Option<RelocationType> {
    match e_machine {
        EM_386 => Some(R_386_TLS_DESC),
        EM_X86_64 => Some(R_X86_64_TLSDESC),
        EM_AARCH64 => Some(R_AARCH64_TLSDESC),
        EM_LOONGARCH => Some(R_LARCH_TLS_DESC64),
        EM_RISCV => Some(R_RISCV_TLSDESC),
        _ => None,
    }
}

#[allow(clippy::too_many_arguments)]
fn add_reference<'data, Elf: FileHeader>(
    endian: Elf::Endian,
    dynsyms: &SymbolTable<'data, Elf, &'data [u8]>,
    versions: &Option<VersionTable<'data, Elf>>,
    symidx: Option<SymbolIndex>,
    plt: bool,
    copy: bool,
    seen: &mut HashSet<(String, bool)>,
    references: &mut Vec<SymbolRef>,
) {
    let Some(symidx) = symidx else {
        return;
    };
    let Ok(sym) = dynsyms.symbol(symidx) else {
        return;
    };
    // Only undefined symbols require a lookup on the loaded objects scope,
    // with the exception of COPY relocations.
    if sym.st_shndx(endian) != SHN_UNDEF && !copy {
        return;
    }
    let Some(name) = symbol_name(endian, dynsyms, sym) else {
        return;
    };
    if seen.insert((name.to_string(), plt)) {
        references.push(SymbolRef {
            name: name.to_string(),
            version: symbol_version(endian, versions, symidx),
            weak: sym.st_bind() == STB_WEAK,
            plt,
            copy,
        });
    }
}

fn symbol_version<'data, Elf: FileHeader>(
    endian: Elf::Endian,
    versions: &Option<VersionTable<'data, Elf>>,
    symidx: SymbolIndex,
) -> Option<String> {
    let versions = versions.as_ref()?;
    let version = versions
        .version(versions.version_index(endian, symidx).index())
        .ok()??;
    str::from_utf8(version.name()).ok().map(|s| s.to_string())
}

fn parse_verdef<'data, Elf: FileHeader>(
    obj: &mut ObjectSymbols,
    endian: Elf::Endian,
    sections: &SectionTable<'data, Elf, &'data [u8]>,
    data: &'data [u8],
) {
    let Ok(Some((mut verdefs, link))) = sections.gnu_verdef(endian, data) else {
        return;
    };
    let Ok(strings) = sections.strings(endian, data, link) else {
        return;
    };
    while let Ok(Some((verdef, mut verdauxs))) = verdefs.next() {
        obj.has_verdef = true;
        // The base version is the object name itself, not a symbol version.
        if verdef.vd_flags.get(endian).contains(VER_FLG_BASE) {
            continue;
        }
        if let Ok(Some(verdaux)) = verdauxs.next() {
            if let Some(name) = verdaux
                .name(endian, strings)
                .ok()
                .and_then(|name| str::from_utf8(name).ok())
            {
                obj.verdef_names.insert(name.to_string());
            }
        }
    }
}

fn parse_verneed<'data, Elf: FileHeader>(
    obj: &mut ObjectSymbols,
    endian: Elf::Endian,
    sections: &SectionTable<'data, Elf, &'data [u8]>,
    data: &'data [u8],
) {
    let Ok(Some((mut verneeds, link))) = sections.gnu_verneed(endian, data) else {
        return;
    };
    let Ok(strings) = sections.strings(endian, data, link) else {
        return;
    };
    while let Ok(Some((verneed, mut vernauxs))) = verneeds.next() {
        let Some(file) = verneed
            .file(endian, strings)
            .ok()
            .and_then(|file| str::from_utf8(file).ok())
        else {
            continue;
        };
        while let Ok(Some(vernaux)) = vernauxs.next() {
            if let Some(version) = vernaux
                .name(endian, strings)
                .ok()
                .and_then(|name| str::from_utf8(name).ok())
            {
                obj.verneeded.push(VersionNeed {
                    file: file.to_string(),
                    version: version.to_string(),
                    weak: vernaux.vna_flags.get(endian).contains(VER_FLG_WEAK),
                });
            }
        }
    }
}

struct DynamicInfo {
    jmprel: Option<u64>,
    bind_now: bool,
    // The MIPS global GOT dynamic symbol range (DT_MIPS_GOTSYM to
    // DT_MIPS_SYMTABNO).
    mips_gotsym: Option<u64>,
    mips_symtabno: Option<u64>,
}

fn parse_dynamic_info<Elf: FileHeader>(endian: Elf::Endian, elf: &Elf, data: &[u8]) -> DynamicInfo {
    let mut r = DynamicInfo {
        jmprel: None,
        bind_now: false,
        mips_gotsym: None,
        mips_symtabno: None,
    };
    let Ok(headers) = elf.program_headers(endian, data) else {
        return r;
    };
    let Some(segment) = headers
        .iter()
        .find(|&&hdr| hdr.p_type(endian) == PT_DYNAMIC)
    else {
        return r;
    };
    let Ok(Some(dynamic)) = segment.dynamic(endian, data) else {
        return r;
    };
    for d in dynamic {
        let tag = d.d_tag(endian);
        if tag == DT_NULL {
            break;
        }
        if tag == DT_JMPREL {
            r.jmprel = Some(d.d_val(endian).into());
        } else if tag == DT_BIND_NOW {
            r.bind_now = true;
        } else if tag == DT_FLAGS {
            r.bind_now |= DynamicFlags(d.d_val(endian).into()).contains(DF_BIND_NOW);
        } else if tag == DT_FLAGS_1 {
            r.bind_now |= DynamicFlags1(d.d_val(endian).into()).contains(DF_1_NOW);
        } else if tag == DT_MIPS_GOTSYM {
            r.mips_gotsym = Some(d.d_val(endian).into());
        } else if tag == DT_MIPS_SYMTABNO {
            r.mips_symtabno = Some(d.d_val(endian).into());
        }
    }
    r
}