#![deny(clippy::arithmetic_side_effects)]
use crate::arch::arm::THUNK_SIZE;
use crate::elf::arch::thunk::Thunks;
use super::PLT_HEADER_SIZE;
pub const NAMES: &[u8] = b"$a\0$t\0$d\0";
pub const A: u32 = 0;
pub const T: u32 = 3;
pub const D: u32 = 6;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Mapping {
pub shndx: u16,
pub address: u64,
pub name: u32,
}
#[must_use]
pub fn plt(dynamic: bool) -> &'static [(u64, u32)] {
if dynamic {
&[(0, A), (16, D), (PLT_HEADER_SIZE, A)]
} else {
&[(0, A)]
}
}
pub fn thunk(key: u64) -> impl Iterator<Item = (u64, u32)> {
let thumb = key & (1 << 32) != 0;
let pic = key & (1 << 33) != 0;
let used = match (thumb, pic) {
(false, false) => 12,
(false, true) => 16,
(true, false) => 10,
(true, true) => 12,
};
let start = Some((0, if thumb { T } else { A }));
let padding = (used < THUNK_SIZE).then_some((used, D));
[start, padding].into_iter().flatten()
}
#[must_use]
pub fn symbols(
plt_at: Option<(u64, u16)>,
dynamic: bool,
thunks: &Thunks,
pool: &dyn Fn(u32) -> Option<(u64, u16)>,
) -> Vec<Mapping> {
let mut out = Vec::new();
if let Some((address, shndx)) = plt_at {
for &(offset, name) in plt(dynamic) {
out.push(Mapping {
shndx,
address: address.saturating_add(offset),
name,
});
}
}
for entry in &thunks.entries {
let Some((base, shndx)) = pool(entry.output) else {
continue;
};
let start = base.saturating_add(entry.offset);
for (offset, name) in thunk(entry.target) {
out.push(Mapping {
shndx,
address: start.saturating_add(offset),
name,
});
}
}
out.sort_unstable();
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::elf::arch::Arch;
use crate::elf::arch::arm::thunk_key;
fn thunks(keys: &[u64]) -> Thunks {
let needed = keys.iter().map(|&key| (0u32, 0u32, key)).collect();
Thunks::build_for(Arch::Arm, needed, &|_, _| 0x100)
}
#[test]
fn names_hold_the_three_marks() {
assert_eq!(NAMES.len(), 9);
assert_eq!(NAMES.get(A as usize..A as usize + 2), Some(&b"$a"[..]));
assert_eq!(NAMES.get(T as usize..T as usize + 2), Some(&b"$t"[..]));
assert_eq!(NAMES.get(D as usize..D as usize + 2), Some(&b"$d"[..]));
}
#[test]
fn a_thunk_is_marked_with_its_state_and_its_padding() {
let marks: Vec<_> = thunk(thunk_key(0x2_0000, false, false)).collect();
assert_eq!(marks, [(0, A), (12, D)]);
let marks: Vec<_> = thunk(thunk_key(0x2_0000, false, true)).collect();
assert_eq!(marks, [(0, A)]);
let marks: Vec<_> = thunk(thunk_key(0x2_0001, true, false)).collect();
assert_eq!(marks, [(0, T), (10, D)]);
let marks: Vec<_> = thunk(thunk_key(0x2_0001, true, true)).collect();
assert_eq!(marks, [(0, T), (12, D)]);
}
#[test]
fn the_plt_header_and_its_word_are_marked() {
let table = thunks(&[]);
let header = symbols(Some((0x1000, 2)), true, &table, &|_| None);
assert_eq!(
header,
[
Mapping {
shndx: 2,
address: 0x1000,
name: A
},
Mapping {
shndx: 2,
address: 0x1010,
name: D
},
Mapping {
shndx: 2,
address: 0x1014,
name: A
},
]
);
let stubs = symbols(Some((0x1000, 2)), false, &table, &|_| None);
assert_eq!(stubs.len(), 1);
}
#[test]
fn counting_and_writing_agree() {
let table = thunks(&[
thunk_key(0x2_0000, false, false),
thunk_key(0x2_0001, true, true),
]);
let counted = symbols(Some((0, 0)), true, &table, &|_| Some((0, 0)));
let written = symbols(Some((0x1000, 2)), true, &table, &|_| Some((0x8000, 1)));
assert_eq!(counted.len(), written.len());
assert_eq!(written.len(), 3 + 2 + 2);
assert!(written.windows(2).all(|w| w[0] < w[1]));
assert_eq!(symbols(None, true, &table, &|_| None).len(), 0);
}
}