Skip to content

Commit 3467e0b

Browse files
Make basic lazy PLT stuff work
.got is now separated into .got and .got.plt. .plt is now separated into .plt and .plt.got. The naming of these sections is super-confusing, but matches what GNU ld does. There's a bunch of things where we don't do the same as GNU ld. In particular, we still generate .got.plt and .plt.got in cases when GNU ld doesn't. But fixing those is left for later, since this change is big enough as-is. ifuncs aren't yet done correctly, since it turns out it's hard to do that without breaking ifuncs for non-pie static binaries. Issue #70
1 parent 2a3dc8d commit 3467e0b

File tree

10 files changed

+417
-81
lines changed

10 files changed

+417
-81
lines changed

linker-diff/src/asm_diff.rs

+32-14
Original file line numberDiff line numberDiff line change
@@ -829,6 +829,9 @@ pub(crate) struct AddressIndex<'data> {
829829
tls_segment_size: u64,
830830
load_offset: u64,
831831

832+
/// GOT addresses for each JMPREL relocation by their index.
833+
jmprel_got_addresses: Vec<u64>,
834+
832835
/// The address of the start of the .got section.
833836
got_base_address: Option<u64>,
834837

@@ -887,6 +890,7 @@ impl<'data> AddressIndex<'data> {
887890
verneed: Default::default(),
888891
load_offset: decide_load_offset(object),
889892
dynamic_symbol_names: Default::default(),
893+
jmprel_got_addresses: Vec::new(),
890894
};
891895

892896
if let Err(error) = info.build_indexes(object) {
@@ -1084,29 +1088,38 @@ impl<'data> AddressIndex<'data> {
10841088
if let Ok(Some((relocations, _))) =
10851089
elf_section_header.rela(LittleEndian, elf_file.data())
10861090
{
1091+
let mut jmprel_got_addresses = (Some(elf_section_header.sh_addr(LittleEndian))
1092+
== self.jmprel_address)
1093+
.then(|| Vec::with_capacity(relocations.len()));
10871094
for rel in relocations {
1088-
self.index_dynamic_relocation(rel, elf_file);
1095+
let address = self.index_dynamic_relocation(rel, elf_file);
1096+
if let Some(j) = jmprel_got_addresses.as_mut() {
1097+
j.push(address);
1098+
}
1099+
}
1100+
if let Some(j) = jmprel_got_addresses {
1101+
self.jmprel_got_addresses = j;
10891102
}
10901103
}
10911104
}
10921105
}
10931106
}
10941107

1095-
fn index_dynamic_relocation(&mut self, rel: &Rela64, elf_file: &ElfFile64) {
1108+
fn index_dynamic_relocation(&mut self, rel: &Rela64, elf_file: &ElfFile64) -> u64 {
10961109
let e = LittleEndian;
10971110
let r_type = rel.r_type(e, false);
10981111
let address = self.load_offset + rel.r_offset(e);
10991112
let symbol_index = rel.r_sym(e, false);
11001113
if symbol_index != 0 {
11011114
let Some(symbol_name) = self.dynamic_symbol_names.get(symbol_index as usize) else {
1102-
return;
1115+
return address;
11031116
};
11041117
let basic = match r_type {
11051118
object::elf::R_X86_64_COPY => BasicResolution::Copy(*symbol_name),
11061119
_ => BasicResolution::Dynamic(*symbol_name),
11071120
};
11081121
self.add_resolution(address, AddressResolution::Basic(basic));
1109-
return;
1122+
return address;
11101123
}
11111124
match r_type {
11121125
object::elf::R_X86_64_DTPMOD64 => {
@@ -1144,6 +1157,7 @@ impl<'data> AddressIndex<'data> {
11441157
}
11451158
_ => {}
11461159
}
1160+
address
11471161
}
11481162

11491163
fn index_plt_sections(&mut self, elf_file: &ElfFile64<'data>) -> Result {
@@ -1176,7 +1190,6 @@ impl<'data> AddressIndex<'data> {
11761190
if let Some(got_address) = PltEntry::decode(chunk, plt_base, plt_offset)
11771191
.map(|entry| self.got_address(entry))
11781192
.transpose()?
1179-
.map(|o| self.load_offset + o)
11801193
{
11811194
for res in self.resolve(got_address) {
11821195
if let AddressResolution::Basic(got_resolution) = res {
@@ -1234,11 +1247,17 @@ impl<'data> AddressIndex<'data> {
12341247

12351248
fn got_address(&self, plt_entry: PltEntry) -> Result<u64> {
12361249
match plt_entry {
1237-
PltEntry::DerefJmp(address) => Ok(address),
1238-
PltEntry::GotIndex(got_index) => Ok(self
1239-
.got_plt_address
1240-
.context("Index-based PLT entry with no DT_PLTGOT")?
1241-
+ u64::from(got_index) * 8),
1250+
PltEntry::DerefJmp(address) => Ok(address + self.load_offset),
1251+
PltEntry::JumpSlot(index) => self
1252+
.jmprel_got_addresses
1253+
.get(index as usize)
1254+
.copied()
1255+
.with_context(|| {
1256+
format!(
1257+
"Invalid jump slot index {index} out of {}",
1258+
self.jmprel_got_addresses.len()
1259+
)
1260+
}),
12421261
}
12431262
}
12441263

@@ -1475,9 +1494,8 @@ enum PltEntry {
14751494
/// PLT entry then jumped to.
14761495
DerefJmp(u64),
14771496

1478-
/// The parameter is an index into the GOT. This is used by PLT entries that are going to be
1479-
/// lazily evaluated.
1480-
GotIndex(u32),
1497+
/// The parameter is an index into .rela.plt.
1498+
JumpSlot(u32),
14811499
}
14821500

14831501
impl PltEntry {
@@ -1539,7 +1557,7 @@ impl PltEntry {
15391557
// instructions, so that we support these variants.
15401558
if plt_entry[..5] == PLT_ENTRY_TEMPLATE[..5] {
15411559
let index = u32::from_le_bytes(*plt_entry[5..].first_chunk::<4>().unwrap());
1542-
return Some(PltEntry::GotIndex(index));
1560+
return Some(PltEntry::JumpSlot(index));
15431561
}
15441562
}
15451563

linker-diff/src/lib.rs

+5
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,11 @@ impl Config {
130130
".dynamic.DT_JMPREL",
131131
".dynamic.DT_PLTGOT",
132132
".dynamic.DT_PLTREL",
133+
// We currently produce a .got.plt whenever we produce .plt, but GNU ld doesn't
134+
"section.got.plt",
135+
".got.plt",
136+
// We don't currently produce a separate .plt.sec section.
137+
"section.plt.sec",
133138
// We don't yet write this.
134139
".dynamic.DT_HASH",
135140
// We do support this. TODO: Should definitely look into why we're seeing this missing

wild/tests/sources/libc-integration.c

+8
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@
6363
//#LinkArgs:--cc=gcc -dynamic -Wl,--strip-debug -Wl,--gc-sections -Wl,-z,now
6464
//#Shared:libc-integration-0.c
6565

66+
//#Config:clang-lazy:default
67+
//#CompArgs:-g -fPIC -ftls-model=global-dynamic -DDYNAMIC_DEP
68+
//#LinkArgs:--cc=clang -fPIC -dynamic -Wl,--strip-debug -Wl,--gc-sections -Wl,-rpath,$ORIGIN -Wl,-z,lazy
69+
//#EnableLinker:lld
70+
//#Shared:libc-integration-0.c
71+
//#DiffIgnore:.dynamic.DT_NEEDED
72+
//#DiffIgnore:section.relro_padding
73+
6674
#include <stdlib.h>
6775
#include <string.h>
6876
#include <pthread.h>

wild_lib/src/args.rs

+13
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,19 @@ impl Args {
500500
let should_trace = self.print_allocations == Some(file_id);
501501
should_trace.then(|| tracing::trace_span!(crate::debug_trace::TRACE_SPAN_NAME).entered())
502502
}
503+
504+
/// Returns whether we should use a separate .got.plt section. This is required if we're using
505+
/// lazy PLT entries.
506+
pub(crate) fn should_use_got_plt(&self) -> bool {
507+
// If we're statically linking, then we can't use jump slots for PLT entries. A jump slot is
508+
// used when the PLT flag is set but not the GOT flag. So for static linking, we set the GOT
509+
// flag whenever the PLT flag is set. Ideally we should perhaps do this when `-z now` is set
510+
// too, but the other linkers don't, so we don't too.
511+
if self.output_kind.is_static_executable() {
512+
return false;
513+
}
514+
!self.bind_now
515+
}
503516
}
504517

505518
fn parse_number(s: &str) -> Result<u64> {

wild_lib/src/elf.rs

+18
Original file line numberDiff line numberDiff line change
@@ -346,10 +346,28 @@ pub(crate) const PLT_ENTRY_TEMPLATE: &[u8] = &[
346346
0x0f, 0x1f, 0x44, 0x0, 0x0, // nopl 0x0(%rax,%rax,1)
347347
];
348348

349+
/// The first entry in the PLT when we're using lazy loading. All the other entries jump to this
350+
/// one, which then jumps to the loader function that the runtime put into
351+
/// _GLOBAL_OFFSET_TABLE_+0x10.
352+
pub(crate) const PLT_LAZY_HEADER_TEMPLATE: &[u8] = &[
353+
0xff, 0x35, 0, 0, 0, 0, // push {_GLOBAL_OFFSET_TABLE_+0x8}(%rip)
354+
0xf2, 0xff, 0x25, 0, 0, 0, 0, // bnd jmp *{_GLOBAL_OFFSET_TABLE_+0x10}(%rip)
355+
0x0f, 0x1f, 0, // nopl (%rax)
356+
];
357+
358+
pub(crate) const JUMP_SLOT_TEMPLATE: &[u8] = &[
359+
0xf3, 0x0f, 0x1e, 0xfa, // endbr64
360+
0x68, 0, 0, 0, 0, // push ${index}
361+
0xf2, 0xe9, 0, 0, 0, 0, // bnd jmp {PLT base address}(%rip)
362+
0x90, // nop
363+
];
364+
349365
const _ASSERTS: () = {
350366
assert!(FILE_HEADER_SIZE as usize == std::mem::size_of::<FileHeader>());
351367
assert!(PROGRAM_HEADER_SIZE as usize == std::mem::size_of::<ProgramHeader>());
352368
assert!(SECTION_HEADER_SIZE as usize == std::mem::size_of::<SectionHeader>());
369+
assert!(PLT_LAZY_HEADER_TEMPLATE.len() == PLT_ENTRY_TEMPLATE.len());
370+
assert!(JUMP_SLOT_TEMPLATE.len() == PLT_ENTRY_TEMPLATE.len());
353371
};
354372

355373
#[derive(Clone, Copy, Debug, PartialEq, Eq)]

0 commit comments

Comments
 (0)