use anyhow::{bail, Context, Result};
use object::write::{Object, StandardSection, Symbol, SymbolSection};
use object::{
Architecture, BinaryFormat, Endianness, Object as _, ObjectSymbol, SymbolFlags, SymbolKind,
SymbolScope,
};
use std::collections::HashSet;
use std::path::Path;
use crate::hotpatch::cache::HotpatchModuleCache;
use crate::hotpatch::LinkerOs;
pub fn create_undefined_symbol_stub(
cache: &HotpatchModuleCache,
patch_obj: &Path,
target_os: LinkerOs,
aslr_reference: u64,
) -> Result<Vec<u8>> {
let needed = compute_needed_symbols(patch_obj)?;
build_stub_for_needed(&needed, cache, target_os, aslr_reference)
}
pub fn compute_needed_symbols(patch_obj: &Path) -> Result<Vec<String>> {
compute_needed_symbols_multi(std::slice::from_ref(&patch_obj))
}
pub fn compute_needed_symbols_multi(patch_objs: &[&Path]) -> Result<Vec<String>> {
let mut undefined: HashSet<String> = HashSet::new();
let mut defined: HashSet<String> = HashSet::new();
for patch_obj in patch_objs {
let bytes = std::fs::read(patch_obj)
.with_context(|| format!("read patch obj {}", patch_obj.display()))?;
let file = object::File::parse(&*bytes).context("parse patch obj")?;
for sym in file.symbols() {
let Ok(name) = sym.name() else {
continue;
};
if name.is_empty() {
continue;
}
if sym.is_undefined() {
undefined.insert(name.to_string());
} else {
defined.insert(name.to_string());
}
}
}
let mut needed: Vec<String> = undefined.difference(&defined).cloned().collect();
needed.sort();
Ok(needed)
}
pub fn build_stub_for_needed(
needed: &[String],
cache: &HotpatchModuleCache,
target_os: LinkerOs,
aslr_reference: u64,
) -> Result<Vec<u8>> {
let host_static_anchor = cache.aslr_reference;
if host_static_anchor == 0 {
bail!(
"host cache has no `whisker_aslr_anchor` symbol address \
(aslr_reference=0); ensure the `#[whisker::main]` macro \
emitted the synthetic anchor and that the cache parsed it"
);
}
if aslr_reference < host_static_anchor {
bail!(
"device-reported aslr_reference {:#x} is below host's static \
whisker_aslr_anchor address {:#x} — would underflow when \
computing the ASLR slide. Is the device running a stale \
build of the host .so?",
aslr_reference,
host_static_anchor,
);
}
let aslr_offset = aslr_reference - host_static_anchor;
let (bin_fmt, endian) = match target_os {
LinkerOs::Linux => (BinaryFormat::Elf, Endianness::Little),
LinkerOs::Macos => (BinaryFormat::MachO, Endianness::Little),
LinkerOs::Other => bail!(
"stub object generation: unsupported target_os {:?}",
target_os
),
};
let mut obj = Object::new(bin_fmt, Architecture::Aarch64, endian);
let text = obj.section_id(StandardSection::Text);
for name in needed {
let lookup_name = name.trim_start_matches("__imp_");
let Some(sym) = cache.symbols.by_name.get(lookup_name) else {
continue;
};
if sym.is_undefined || sym.address == 0 {
continue;
}
let abs_addr = sym.address + aslr_offset;
if !matches!(sym.kind, SymbolKind::Text) {
continue;
}
let code = arm64_jump_stub(abs_addr);
let off = obj.append_section_data(text, &code, 4);
obj.add_symbol(Symbol {
name: name.as_bytes().to_vec(),
value: off,
size: code.len() as u64,
scope: SymbolScope::Linkage,
kind: SymbolKind::Text,
weak: true,
section: SymbolSection::Section(text),
flags: SymbolFlags::None,
});
}
obj.write().context("serialize stub object")
}
fn arm64_jump_stub(addr: u64) -> Vec<u8> {
let mut code = Vec::with_capacity(20);
let imm0 = (addr & 0xFFFF) as u32;
code.extend_from_slice(&(0xD280_0010_u32 | (imm0 << 5)).to_le_bytes());
let imm1 = ((addr >> 16) & 0xFFFF) as u32;
code.extend_from_slice(&(0xF2A0_0010_u32 | (imm1 << 5)).to_le_bytes());
let imm2 = ((addr >> 32) & 0xFFFF) as u32;
code.extend_from_slice(&(0xF2C0_0010_u32 | (imm2 << 5)).to_le_bytes());
let imm3 = ((addr >> 48) & 0xFFFF) as u32;
code.extend_from_slice(&(0xF2E0_0010_u32 | (imm3 << 5)).to_le_bytes());
code.extend_from_slice(&[0x00, 0x02, 0x1F, 0xD6]);
code
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn arm64_stub_encodes_address_zero_as_clear_zero_movs() {
let code = arm64_jump_stub(0);
assert_eq!(code.len(), 20);
assert_eq!(&code[0..4], &0xD280_0010_u32.to_le_bytes()); assert_eq!(&code[4..8], &0xF2A0_0010_u32.to_le_bytes());
assert_eq!(&code[8..12], &0xF2C0_0010_u32.to_le_bytes());
assert_eq!(&code[12..16], &0xF2E0_0010_u32.to_le_bytes());
assert_eq!(&code[16..20], &[0x00, 0x02, 0x1F, 0xD6]); }
#[test]
fn arm64_stub_encodes_a_canonical_aarch64_userspace_address() {
let addr = 0x7B40_91FF_2C00_u64;
let code = arm64_jump_stub(addr);
let imm0 = (addr & 0xFFFF) as u32;
assert_eq!(&code[0..4], &(0xD280_0010_u32 | (imm0 << 5)).to_le_bytes(),);
let imm3 = ((addr >> 48) & 0xFFFF) as u32;
assert_eq!(
&code[12..16],
&(0xF2E0_0010_u32 | (imm3 << 5)).to_le_bytes(),
);
}
}