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
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
//! Relocatable input objects as the link sees them.
//!
//! [`ObjectInput`] wraps a parsed [`ObjectFile`] with what later stages need
//! per file: the global symbol names (prehashed for interning) and how each
//! one takes part in resolution, a classification of every section, the
//! relocation section of every section, the COMDAT groups, and the GNU
//! property and stack notes.
//!
//! Parsing touches only headers and the symbol table; section contents and
//! relocations stay in the mapping until they are needed.
#![deny(clippy::arithmetic_side_effects)]
use crate::elf::read::consts::{
SHF_ALLOC, SHF_EXCLUDE, SHF_EXECINSTR, SHF_MERGE, SHF_WRITE, SHT_GROUP, SHT_LLVM_ADDRSIG,
SHT_NOBITS, SHT_NULL, SHT_REL, SHT_RELA, SHT_STRTAB, SHT_SYMTAB, SHT_SYMTAB_SHNDX, STB_LOCAL,
STB_WEAK,
};
use std::sync::Arc;
use crate::debug::section::{CompressedSection, ZDEBUG_PREFIX};
use crate::elf::read::consts::SHF_COMPRESSED;
use crate::elf::read::{
Elf64Le, ElfFormat, GnuProperties, ObjectFile, SectionHeader, SectionIndex, Source,
};
use crate::error::{Error, Result};
use crate::input::FileTable;
use crate::passes::merge::{MergeKind, SplitSection, split_section};
use crate::symbols::{DefinitionKind, SymbolName, SymbolUse};
use super::resolve::AUX_COMDAT;
/// Largest alignment a section that is not allocated keeps. Its alignment
/// constrains no address, only how far the output file is padded before it,
/// and no real toolchain asks for more than a page.
const MAX_NOALLOC_ALIGN: u64 = 1 << 16;
/// How an input section takes part in the output.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SectionKind {
/// Never copied: the null section, symbol and string tables, relocation
/// sections, groups, `.note.GNU-stack`, `.note.gnu.property` (merged into
/// a synthetic note), `SHF_EXCLUDE` sections, stripped debug sections.
Ignored,
/// Copied (or allocated, for `SHT_NOBITS`) and relocated.
Regular,
/// A `SHF_MERGE` section whose pieces are deduplicated.
Merge,
/// An `.eh_frame` section, rebuilt from its live records.
EhFrame,
}
/// One section of an input object.
#[derive(Clone, Copy, Debug)]
pub struct InputSection<'a> {
/// The section name.
pub name: &'a [u8],
/// The section header.
pub header: SectionHeader,
/// How the section is used.
pub kind: SectionKind,
/// Index of the relocation section that applies to this one, or 0.
pub relocs: u32,
/// Index plus one of the COMDAT group (in [`ObjectInput::groups`]) this
/// section belongs to, or 0.
pub group: u32,
/// For [`SectionKind::Merge`], the index of its split in
/// [`ObjectInput::splits`].
pub split: u32,
/// The decompressed contents of a compressed section, whose header then
/// describes the decompressed data.
pub contents: Option<&'a [u8]>,
}
impl InputSection<'_> {
/// Whether the section occupies memory at run time.
#[must_use]
pub fn is_alloc(&self) -> bool {
self.header.sh_flags & SHF_ALLOC != 0
}
/// Whether the section is `SHT_NOBITS`.
#[must_use]
pub fn is_nobits(&self) -> bool {
self.header.sh_type == SHT_NOBITS
}
}
/// A COMDAT group of an input object.
#[derive(Clone, Debug)]
pub struct ComdatGroup<'a> {
/// The group signature.
pub signature: &'a [u8],
/// The signature as a claim key, hashed once while the object is
/// parsed (resolution looks each group up twice).
pub key: SymbolName<'a>,
/// Member section indices.
pub members: Vec<u32>,
}
/// What `--wrap` does to undefined references.
#[derive(Clone, Debug, Default)]
pub struct WrapTable {
/// `(name, __wrap_name, __real_name)` for each wrapped symbol, sorted by
/// name.
entries: Vec<(Vec<u8>, Vec<u8>, Vec<u8>)>,
}
impl WrapTable {
/// Builds the table for the `--wrap` options.
#[must_use]
pub fn new(names: &[String]) -> Self {
let mut entries: Vec<(Vec<u8>, Vec<u8>, Vec<u8>)> = names
.iter()
.map(|name| {
let name = name.as_bytes().to_vec();
let mut wrapped = b"__wrap_".to_vec();
wrapped.extend_from_slice(&name);
let mut real = b"__real_".to_vec();
real.extend_from_slice(&name);
(name, wrapped, real)
})
.collect();
entries.sort();
entries.dedup();
Self { entries }
}
/// Whether no symbol is wrapped.
#[must_use]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
/// The name an undefined reference to `name` resolves to.
#[must_use]
pub fn redirect<'s>(&'s self, name: &'s [u8]) -> &'s [u8] {
if self.entries.is_empty() {
return name;
}
if let Ok(found) = self.entries.binary_search_by(|e| e.0.as_slice().cmp(name))
&& let Some(entry) = self.entries.get(found)
{
return &entry.1;
}
if name.starts_with(b"__real_")
&& let Some(entry) = self.entries.iter().find(|e| e.2.as_slice() == name)
{
return &entry.0;
}
name
}
}
/// Settings that affect how objects are read.
#[derive(Clone, Copy, Debug)]
pub struct ParseConfig<'a> {
/// Drop debug sections (`-S`, `-s`).
pub strip_debug: bool,
/// `--wrap` redirections.
pub wrap: &'a WrapTable,
/// Where decompressed section contents are kept for the link.
pub table: &'a FileTable,
}
/// Known `.zdebug_*` names and the names of their decompressed sections.
const ZDEBUG_NAMES: &[(&[u8], &[u8])] = &[
(b".zdebug_abbrev", b".debug_abbrev"),
(b".zdebug_addr", b".debug_addr"),
(b".zdebug_aranges", b".debug_aranges"),
(b".zdebug_frame", b".debug_frame"),
(b".zdebug_info", b".debug_info"),
(b".zdebug_line", b".debug_line"),
(b".zdebug_line_str", b".debug_line_str"),
(b".zdebug_loc", b".debug_loc"),
(b".zdebug_loclists", b".debug_loclists"),
(b".zdebug_macinfo", b".debug_macinfo"),
(b".zdebug_macro", b".debug_macro"),
(b".zdebug_names", b".debug_names"),
(b".zdebug_pubnames", b".debug_pubnames"),
(b".zdebug_pubtypes", b".debug_pubtypes"),
(b".zdebug_ranges", b".debug_ranges"),
(b".zdebug_rnglists", b".debug_rnglists"),
(b".zdebug_str", b".debug_str"),
(b".zdebug_str_offsets", b".debug_str_offsets"),
(b".zdebug_types", b".debug_types"),
];
/// A decompressed section: its patched header, output name and contents.
type Decompressed<'a> = (SectionHeader, &'a [u8], &'a [u8]);
/// Decompresses a compressed non-allocated section into the file table.
fn decompress_section<'a, F: ElfFormat>(
elf: &ObjectFile<'a, F>,
header: &SectionHeader,
name: &'a [u8],
config: &ParseConfig<'a>,
) -> Result<Option<Decompressed<'a>>> {
let Some(compressed) = CompressedSection::detect(elf, header)? else {
return Ok(None);
};
let source = elf.source();
let data = compressed.decompress(source)?;
let label = match source.member {
Some(member) => format!(
"{}({member}) decompressed section at {:#x}",
source.path.display(),
header.sh_offset
),
None => format!(
"{} decompressed section at {:#x}",
source.path.display(),
header.sh_offset
),
};
let id = config.table.add_bytes(label, Arc::from(data))?;
let contents = config
.table
.get(id)
.map(crate::input::InputFile::data)
.ok_or_else(|| Error::Internal("decompressed section missing from table".into()))?;
let mut patched = *header;
patched.sh_size = u64::try_from(contents.len()).unwrap_or(u64::MAX);
patched.sh_addralign = compressed.align.max(1);
patched.sh_flags &= !SHF_COMPRESSED;
let name = if compressed.zdebug {
ZDEBUG_NAMES
.iter()
.find(|(z, _)| *z == name)
.map_or(name, |&(_, plain)| plain)
} else {
name
};
Ok(Some((patched, name, contents)))
}
/// A parsed input object.
#[derive(Debug)]
pub struct ObjectInput<'a, F: ElfFormat = Elf64Le> {
/// The underlying ELF reader.
pub elf: ObjectFile<'a, F>,
/// Index of the first global symbol.
pub first_global: usize,
/// Global symbol names, in symbol table order from `first_global`.
pub names: Vec<SymbolName<'a>>,
/// How each global symbol takes part in resolution.
pub uses: Vec<SymbolUse>,
/// Every section, by section index.
pub sections: Vec<InputSection<'a>>,
/// COMDAT groups.
pub groups: Vec<ComdatGroup<'a>>,
/// Whether a `.note.GNU-stack` section was present.
pub has_gnu_stack_note: bool,
/// Whether `.note.GNU-stack` requests an executable stack.
pub exec_stack: bool,
/// GNU properties, when the object has a `.note.gnu.property`.
pub properties: Option<GnuProperties>,
/// Index of the `.llvm_addrsig` section, or 0.
pub addrsig: u32,
/// Indices of `.gnu.warning*` sections.
pub warnings: Vec<u32>,
/// The pieces of every [`SectionKind::Merge`] section, split at parse
/// time.
pub splits: Vec<SplitSection<'a>>,
/// Whether some global symbol is named `name@@VERSION`.
pub has_default_versions: bool,
/// For each of [`groups`](Self::groups), whether another file's copy
/// was kept and this one is discarded.
pub discarded_groups: Vec<bool>,
/// The global symbols [`discard_groups`](Self::discard_groups) turned
/// into [`SymbolUse::Ignore`], with their original uses. LTO reports
/// them as used by a regular object: their references bind to the kept
/// copy.
pub group_ignored: Vec<(u32, SymbolUse)>,
/// Whether the object carries GCC LTO IR (`.gnu.lto_*` sections).
pub gcc_lto: GccLto,
/// For each of [`groups`](Self::groups), its claim slot (see
/// [`ComdatHook`](super::resolve::ComdatHook)), once looked up; taken
/// when the claims of the object's round are settled.
pub group_slots: Option<Vec<u32>>,
/// The rank the object offered its groups with, in the round that made
/// it live.
pub claim_rank: Option<u32>,
}
/// Whether an ELF object carries GCC LTO IR.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum GccLto {
/// No `.gnu.lto_*` sections: an ordinary object.
#[default]
None,
/// IR only (`-flto` without `-ffat-lto-objects`): the object defines
/// the `__gnu_lto_slim` marker and has no code of its own.
Slim,
/// IR next to native code (`-ffat-lto-objects`): linkable without the
/// plugin.
Fat,
}
/// The common symbol GCC puts in slim LTO objects.
pub const GCC_LTO_SLIM_MARKER: &[u8] = b"__gnu_lto_slim";
/// The section name prefix of GCC LTO IR.
pub const GCC_LTO_PREFIX: &[u8] = b".gnu.lto_";
/// Whether a section name is debug information that `--strip-debug` drops.
#[must_use]
pub fn is_debug_name(name: &[u8]) -> bool {
name.starts_with(b".debug")
|| name.starts_with(b".zdebug")
|| name.starts_with(b".gnu.debuglto_")
|| name == b".line"
|| name.starts_with(b".stab")
}
impl<'a, F: ElfFormat> ObjectInput<'a, F> {
/// Parses an object and prepares its symbols for resolution.
///
/// # Errors
///
/// Returns [`Error::Malformed`] for malformed files, and
/// [`Error::Unimplemented`] for objects qld cannot link yet.
pub fn parse(data: &'a [u8], source: Source<'a>, config: &ParseConfig<'a>) -> Result<Self> {
let elf = ObjectFile::<F>::parse(data, source)?;
let machine = elf.elf().header().e_machine;
// `EM_NONE`: a machine-neutral `-b binary` input (`binary_input`).
if machine != crate::elf::read::consts::EM_NONE
&& crate::elf::arch::Arch::from_machine(machine).is_none()
{
return Err(source.malformed(18, "ELF machine (not an architecture qld links)"));
}
let count = elf.section_count();
let mut sections = Vec::with_capacity(count);
let mut has_gnu_stack_note = false;
let mut exec_stack = false;
let mut has_property_note = false;
let mut addrsig = 0u32;
let mut warnings = Vec::new();
let mut has_lto_ir = false;
for (index, header) in elf.elf().enumerate_sections() {
let mut name = elf.section_name(&header)?;
has_lto_ir |= name.starts_with(GCC_LTO_PREFIX);
let mut header = header;
let mut contents = None;
let stripped =
config.strip_debug && header.sh_flags & SHF_ALLOC == 0 && is_debug_name(name);
if !stripped && (header.is_compressed() || name.starts_with(ZDEBUG_PREFIX)) {
if header.sh_flags & SHF_ALLOC != 0 {
return Err(source.malformed(
elf.elf().section_header_offset(index),
"section flags (SHF_COMPRESSED on an allocated section)",
));
}
if let Some((patched, output_name, data)) =
decompress_section(&elf, &header, name, config)?
{
header = patched;
name = output_name;
contents = Some(data);
}
}
let flags = header.sh_flags;
// A table the link rebuilds from scratch, so neither its size
// nor its alignment reaches the output. Every other section's
// does, so both are checked below.
let mut rebuilt = false;
let kind = match header.sh_type {
SHT_NULL | SHT_SYMTAB | SHT_STRTAB | SHT_REL | SHT_RELA | SHT_GROUP
| SHT_SYMTAB_SHNDX => {
rebuilt = true;
SectionKind::Ignored
}
SHT_LLVM_ADDRSIG => {
addrsig = index;
rebuilt = true;
SectionKind::Ignored
}
_ if flags & SHF_EXCLUDE != 0 => SectionKind::Ignored,
_ if name == b".note.GNU-stack" => {
has_gnu_stack_note = true;
exec_stack |= flags & SHF_EXECINSTR != 0;
SectionKind::Ignored
}
_ if name == b".note.gnu.property" => {
has_property_note = true;
SectionKind::Ignored
}
_ if name.starts_with(b".gnu.warning") => {
// `.gnu.warning.SYM`: a message for links that use SYM.
warnings.push(index);
SectionKind::Ignored
}
_ if config.strip_debug && flags & SHF_ALLOC == 0 && is_debug_name(name) => {
SectionKind::Ignored
}
_ if elf.is_eh_frame(&header)? && flags & SHF_ALLOC != 0 => SectionKind::EhFrame,
_ if flags & SHF_MERGE != 0
&& header.sh_entsize != 0
&& flags & SHF_WRITE == 0
&& header.sh_type != SHT_NOBITS =>
{
SectionKind::Merge
}
_ => SectionKind::Regular,
};
if !rebuilt {
// Layout trusts the sizes and alignments of the sections it
// copies, and a relocatable link or `--emit-relocs` copies
// ignored ones too (`.note.GNU-stack`, `SHF_EXCLUDE`), so
// check both here. GNU ld likewise rejects an object with
// "a section extending past end of file".
if contents.is_none() {
elf.section_data(&header)?;
}
if header.sh_addralign > 1 && !header.sh_addralign.is_power_of_two() {
return Err(source.malformed(
elf.elf().section_header_offset(index),
"section alignment (not a power of two)",
));
}
if header.sh_addralign > u64::from(u32::MAX) {
// lld's limit. Beyond it, aligning the output section
// would reserve gigabytes for one input section.
return Err(source.malformed(
elf.elf().section_header_offset(index),
"section alignment (too large)",
));
}
// A section that is not allocated has no address, so its
// alignment constrains nothing but file padding, which GNU
// ld does not do at all. Capping it keeps one absurd
// `sh_addralign` in a corrupt object — 2 GiB, say — from
// inflating the output, the merged string pool or a
// relocatable link by that much.
if flags & SHF_ALLOC == 0 {
header.sh_addralign = header.sh_addralign.min(MAX_NOALLOC_ALIGN);
}
}
sections.push(InputSection {
name,
header,
kind,
relocs: 0,
group: 0,
split: 0,
contents,
});
}
// Relocation sections: attach them to their targets. A merge section
// with relocations is copied as a regular section instead.
for (index, header) in elf.elf().enumerate_sections() {
if !matches!(header.sh_type, SHT_REL | SHT_RELA) {
continue;
}
let target = usize::try_from(header.sh_info)
.ok()
.filter(|&t| t != 0)
.and_then(|t| sections.get_mut(t));
let Some(target) = target else {
// GNU as emits relocation sections for nothing only in broken
// objects; treat a zero target as malformed.
return Err(source.malformed(
elf.elf().section_header_offset(index),
"relocation section target",
));
};
if header.sh_link != elf.symbols().section_index() && header.sh_link != 0 {
return Err(source.malformed(
elf.elf().section_header_offset(index),
"relocation section symbol table link",
));
}
if target.relocs != 0 {
return Err(source.malformed(
elf.elf().section_header_offset(index),
"relocation section target (duplicate)",
));
}
target.relocs = index;
if target.kind == SectionKind::Merge {
target.kind = SectionKind::Regular;
}
}
// COMDAT groups.
let mut groups = Vec::new();
for group in elf.groups() {
let group = group?;
if !group.is_comdat() {
continue;
}
let signature = elf.group_signature(&group)?;
let group_number = u32::try_from(groups.len())
.ok()
.and_then(|n| n.checked_add(1))
.ok_or_else(|| source.malformed(group.header.sh_offset, "section group count"))?;
let mut members = Vec::with_capacity(group.member_count());
for member in group.members() {
let section = usize::try_from(member)
.ok()
.and_then(|m| sections.get_mut(m))
.filter(|_| member != 0)
.ok_or_else(|| {
source.malformed(group.header.sh_offset, "section group member")
})?;
if section.group != 0 {
return Err(source.malformed(
group.header.sh_offset,
"section group member (in two groups)",
));
}
section.group = group_number;
members.push(member);
}
groups.push(ComdatGroup {
signature,
key: SymbolName::new(signature),
members,
});
}
// Split mergeable sections into pieces now, while the file is hot.
let mut splits = Vec::new();
for section in &mut sections {
if section.kind != SectionKind::Merge {
continue;
}
let Some(kind) = merge_kind(§ion.header) else {
section.kind = SectionKind::Regular;
continue;
};
let data = match section.contents {
Some(contents) => contents,
None => elf.section_data(§ion.header)?,
};
let alignment = section.header.sh_addralign.max(1);
if !alignment.is_power_of_two() {
section.kind = SectionKind::Regular;
continue;
}
let split = split_section(data, kind, alignment).map_err(|malformed| {
let mut error = malformed.into_error(source.path, section.header.sh_offset);
if let Error::Malformed { member, .. } = &mut error {
*member = source.member.map(str::to_owned);
}
error
})?;
section.split = u32::try_from(splits.len())
.map_err(|_| Error::Limit("too many merge sections in one object".into()))?;
splits.push(split);
}
let properties = if has_property_note {
Some(elf.gnu_properties()?)
} else {
None
};
let symbols = *elf.symbols();
let first_global = symbols.first_global();
let global_count = symbols.len().saturating_sub(first_global);
let mut names = Vec::with_capacity(global_count);
let mut uses = Vec::with_capacity(global_count);
let mut has_default_versions = false;
let mut slim = false;
for index in first_global..symbols.len() {
let Some(raw) = symbols.get_raw(index) else {
break;
};
let name = symbols.name(index, &raw)?;
slim |= has_lto_ir && name == GCC_LTO_SLIM_MARKER;
let section = symbols.section(index, &raw)?;
let binding = raw.binding();
let weak = binding == STB_WEAK;
let (name, use_) = match section {
_ if binding == STB_LOCAL => (name, SymbolUse::Ignore),
SectionIndex::Undefined => {
(config.wrap.redirect(name), SymbolUse::Reference { weak })
}
SectionIndex::Common => (
name,
SymbolUse::Definition {
kind: DefinitionKind::Common,
aux: raw.st_size & !AUX_COMDAT,
},
),
SectionIndex::Absolute => (name, definition(weak, false)),
SectionIndex::Section(s) => {
let section = usize::try_from(s)
.ok()
.and_then(|s| sections.get(s))
.ok_or_else(|| {
source.malformed(
symbols.strtab().file_offset(),
format!("section index {s} of symbol {index}"),
)
})?;
(name, definition(weak, section.group != 0))
}
SectionIndex::Reserved(_) => (name, SymbolUse::Ignore),
};
// `redirect` may return a name owned by the wrap table, which
// lives as long as the link.
let (base, version) = split_version(name);
has_default_versions |= version.is_none() && base.len() < name.len();
names.push(SymbolName::with_version(base, version));
uses.push(use_);
}
Ok(Self {
elf,
first_global,
names,
uses,
sections,
groups,
has_gnu_stack_note,
exec_stack,
properties,
addrsig,
warnings,
splits,
has_default_versions,
discarded_groups: Vec::new(),
group_ignored: Vec::new(),
group_slots: None,
claim_rank: None,
gcc_lto: match (has_lto_ir, slim) {
(false, _) => GccLto::None,
(true, true) => GccLto::Slim,
(true, false) => GccLto::Fat,
},
})
}
/// Discards the COMDAT groups flagged in `discarded` (by index in
/// [`groups`](Self::groups)): their global definitions stop taking part
/// in resolution, so references bind to the kept copy.
pub fn discard_groups(&mut self, discarded: Vec<bool>) {
let symbols = *self.elf.symbols();
for (local, use_) in self.uses.iter_mut().enumerate() {
if !matches!(use_, SymbolUse::Definition { .. }) {
continue;
}
let Some(index) = local.checked_add(self.first_global) else {
break;
};
let Some(raw) = symbols.get_raw(index) else {
break;
};
let Ok(SectionIndex::Section(section)) = symbols.section(index, &raw) else {
continue;
};
let group = self
.sections
.get(section as usize)
.and_then(|s| s.group.checked_sub(1));
if group.is_some_and(|g| discarded.get(g as usize).copied().unwrap_or(false)) {
if let Ok(local) = u32::try_from(local) {
self.group_ignored.push((local, *use_));
}
*use_ = SymbolUse::Ignore;
}
}
self.discarded_groups = discarded;
}
/// For global symbol `local` (an index into [`names`](Self::names)) named
/// `name@@VERSION`, the version.
#[must_use]
pub fn default_version(&self, local: usize) -> Option<&'a [u8]> {
if !self.has_default_versions {
return None;
}
let symbols = self.elf.symbols();
let index = local.checked_add(self.first_global)?;
let raw = symbols.get_raw(index)?;
let name = symbols.name(index, &raw).ok()?;
let at = name.windows(2).position(|w| w == b"@@")?;
name.get(at.checked_add(2)?..)
}
/// The contents of `section`: decompressed for compressed sections.
///
/// # Errors
///
/// Returns [`Error::Malformed`] if the contents lie outside the file.
pub fn section_data(&self, section: &InputSection<'a>) -> Result<&'a [u8]> {
match section.contents {
Some(contents) => Ok(contents),
None => self.elf.section_data(§ion.header),
}
}
/// The object's source, for diagnostics.
#[must_use]
pub fn source(&self) -> Source<'a> {
self.elf.source()
}
/// The input section `index`, if it exists.
#[must_use]
pub fn section(&self, index: u32) -> Option<&InputSection<'a>> {
self.sections.get(usize::try_from(index).ok()?)
}
/// Returns an error naming this file.
#[must_use]
pub fn malformed(&self, offset: u64, what: impl Into<String>) -> Error {
self.source().malformed(offset, what)
}
}
/// How a mergeable section splits, or `None` if it cannot be merged.
fn merge_kind(header: &SectionHeader) -> Option<MergeKind> {
use crate::elf::read::consts::SHF_STRINGS;
if header.sh_flags & SHF_STRINGS != 0 {
let char_size = u8::try_from(header.sh_entsize).ok()?;
matches!(char_size, 1 | 2 | 4).then_some(MergeKind::Strings { char_size })
} else {
(header.sh_entsize != 0).then_some(MergeKind::Fixed {
entry_size: header.sh_entsize,
})
}
}
fn definition(weak: bool, comdat: bool) -> SymbolUse {
SymbolUse::Definition {
kind: if weak {
DefinitionKind::Weak
} else {
DefinitionKind::Regular
},
aux: if comdat { AUX_COMDAT } else { 0 },
}
}
/// Splits a symbol table name into the name and its explicit version:
/// `foo@@VERSION` is the default version of `foo` (the plain name, returned
/// without a version), `foo@VERSION` a distinct, versioned symbol.
#[must_use]
pub fn split_version(name: &[u8]) -> (&[u8], Option<&[u8]>) {
let Some(at) = crate::elf::read::strtab::find_byte(name, b'@') else {
return (name, None);
};
let base = name.get(..at).unwrap_or(name);
if base.is_empty() {
return (name, None);
}
let rest = name.get(at.saturating_add(1)..).unwrap_or_default();
match rest.strip_prefix(b"@") {
Some(_) => (base, None),
None if rest.is_empty() => (name, None),
None => (base, Some(rest)),
}
}