use crate::elf::elf_relocations::*;
use crate::elf::elf_types::*;
use crate::elf::*;
use crate::linker::*;
use crate::lld::*;
use crate::x86::*;
use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, VecDeque};
use std::convert::TryInto;
use std::mem;
use std::rc::Rc;
pub const ELF_MAGIC_BYTES: [u8; 4] = [0x7f, b'E', b'L', b'F'];
pub const X86_64_PAGE_SIZE: u64 = 4096;
pub const X86_64_DEFAULT_IMAGE_BASE: u64 = 0x400000;
pub const I386_DEFAULT_IMAGE_BASE: u64 = 0x08048000;
pub const ELF64_EHDR_SIZE: u64 = 64;
pub const ELF64_PHDR_SIZE: u64 = 56;
pub const ELF64_SHDR_SIZE: u64 = 64;
pub const GOT_ENTRY_SIZE: u64 = 8;
pub const PLT_ENTRY_SIZE: u64 = 16;
pub const PLT0_SIZE: u64 = 16;
pub const COFF_FILE_ALIGNMENT: u64 = 512;
pub const COFF_SECTION_ALIGNMENT: u64 = 4096;
pub const PE32PLUS_MAGIC: u16 = 0x020b;
pub const PE32_MAGIC: u16 = 0x010b;
pub const IMAGE_FILE_MACHINE_AMD64: u16 = 0x8664;
pub const IMAGE_FILE_MACHINE_I386: u16 = 0x014c;
pub const MAX_PROGRAM_HEADERS: usize = 32;
pub const MAX_SECTION_HEADERS: usize = 256;
pub const TLS_MODULE_ID_SLOT: u64 = 0;
pub const TLSDESC_GOT_SIZE: u64 = 16;
pub const R_X86_64_REX_GOTPCRELX: u32 = 42;
pub const R_X86_64_GOTPCRELX: u32 = 41;
pub const R_X86_64_TLSGD: u32 = 16;
pub const R_X86_64_TLSLD: u32 = 17;
pub const R_X86_64_GOTTPOFF: u32 = 22;
pub const R_X86_64_TPOFF32: u32 = 23;
pub const RELAX_GOTPCRELX_MOV_TO_LEA: u8 = 1;
pub const RELAX_GOTPCRELX_OK: u8 = 0;
pub const RELAX_GOTPCRELX_FAIL: u8 = 1;
pub const STACK_ALIGN: u64 = 16;
pub const MAXPAGESIZE: u64 = 0x1000; pub const COMMONPAGESIZE: u64 = 0x1000;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86OutputFormatDeep {
Elf32,
Elf64,
Coff32,
Coff64,
MachO64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86LLDArchDeep {
X86_64,
I386,
}
impl X86LLDArchDeep {
pub fn is_64bit(&self) -> bool {
matches!(self, Self::X86_64)
}
pub fn pointer_size(&self) -> u64 {
if self.is_64bit() {
8
} else {
4
}
}
pub fn elf_machine(&self) -> u16 {
match self {
Self::X86_64 => EM_X86_64,
Self::I386 => EM_386,
}
}
pub fn coff_machine(&self) -> u16 {
match self {
Self::X86_64 => IMAGE_FILE_MACHINE_AMD64,
Self::I386 => IMAGE_FILE_MACHINE_I386,
}
}
pub fn default_image_base(&self) -> u64 {
match self {
Self::X86_64 => X86_64_DEFAULT_IMAGE_BASE,
Self::I386 => I386_DEFAULT_IMAGE_BASE,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86BuildIdKindDeep {
None,
Sha1,
Md5,
Uuid,
HexString,
Fast,
Sha256,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86HashStyleDeep {
SysV,
Gnu,
Both,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86DiagLevelDeep {
Info,
Warning,
Error,
Fatal,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86MergeKind {
Strings,
Constants,
TailMerge,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86LTOModelDeep {
Full,
Thin,
Disabled,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86LTOOptLevelDeep {
O0,
O1,
O2,
O3,
Os,
Oz,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum X86SymbolDefDeep {
Defined {
value: u64,
size: u64,
section_name: Option<String>,
is_function: bool,
is_ifunc: bool,
is_tls: bool,
},
Common {
size: u64,
alignment: u64,
},
Undefined,
Shared {
library: String,
version: Option<String>,
},
Weak {
value: u64,
size: u64,
section_name: Option<String>,
},
}
impl X86SymbolDefDeep {
pub fn is_ifunc(&self) -> bool {
match self {
X86SymbolDefDeep::Defined { is_ifunc, .. } => *is_ifunc,
_ => false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86SymbolVisibilityDeep {
Default,
Internal,
Hidden,
Protected,
}
impl X86SymbolVisibilityDeep {
pub fn to_elf_byte(&self) -> u8 {
match self {
Self::Default => STV_DEFAULT,
Self::Internal => STV_INTERNAL,
Self::Hidden => STV_HIDDEN,
Self::Protected => STV_PROTECTED,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86RelaxKind {
GOTPCRELX_MovToLea,
GOTPCRELX_ToPC32,
CallToJmp,
TlsGdToIe,
TlsGdToLe,
TlsIeToLe,
TlsLdToLe,
NoRelax,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86ICFSafetyLevel {
All,
Safe,
None_,
}
#[inline]
pub fn align_up_deep(value: u64, alignment: u64) -> u64 {
if alignment == 0 {
return value;
}
(value + alignment - 1) & !(alignment - 1)
}
#[inline]
pub fn align_down_deep(value: u64, alignment: u64) -> u64 {
if alignment == 0 {
return value;
}
value & !(alignment - 1)
}
pub fn compute_fnv1a_64_deep(data: &[u8]) -> u64 {
let mut hash: u64 = 0xcbf29ce484222325;
for &byte in data {
hash ^= byte as u64;
hash = hash.wrapping_mul(0x100000001b3);
}
hash
}
pub fn elf_hash_deep(name: &[u8]) -> u32 {
let mut h: u32 = 0;
for &byte in name {
if byte == 0 {
break;
}
h = (h << 4).wrapping_add(byte as u32);
let g = h & 0xf0000000;
if g != 0 {
h ^= g >> 24;
}
h &= !g;
}
h
}
pub fn gnu_hash_deep(name: &[u8]) -> u32 {
let mut h: u32 = 5381;
for &byte in name {
if byte == 0 {
break;
}
h = (h << 5).wrapping_add(h).wrapping_add(byte as u32);
}
h
}
pub fn next_power_of_two_deep(v: u64) -> u64 {
if v == 0 {
return 1;
}
let mut n = v - 1;
n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
n |= n >> 8;
n |= n >> 16;
n |= n >> 32;
n + 1
}
pub fn read_u16_le_deep(data: &[u8], offset: usize) -> u16 {
let bytes: [u8; 2] = data[offset..offset + 2].try_into().unwrap();
u16::from_le_bytes(bytes)
}
pub fn read_u32_le_deep(data: &[u8], offset: usize) -> u32 {
let bytes: [u8; 4] = data[offset..offset + 4].try_into().unwrap();
u32::from_le_bytes(bytes)
}
pub fn read_u64_le_deep(data: &[u8], offset: usize) -> u64 {
let bytes: [u8; 8] = data[offset..offset + 8].try_into().unwrap();
u64::from_le_bytes(bytes)
}
pub fn get_c_string_deep(data: &[u8], offset: usize) -> &str {
let end = data[offset..]
.iter()
.position(|&b| b == 0)
.map(|p| offset + p)
.unwrap_or(data.len());
std::str::from_utf8(&data[offset..end]).unwrap_or("")
}
pub fn wildcard_match_deep(pattern: &str, name: &str) -> bool {
wm_bytes_deep(pattern.as_bytes(), name.as_bytes())
}
fn wm_bytes_deep(pat: &[u8], name: &[u8]) -> bool {
let (mut pi, mut ni) = (0usize, 0usize);
let (mut star_pi, mut star_ni) = (None, None);
loop {
if pi < pat.len() && pat[pi] == b'*' {
star_pi = Some(pi);
star_ni = Some(ni);
pi += 1;
} else if pi < pat.len() && ni < name.len() && (pat[pi] == b'?' || pat[pi] == name[ni]) {
pi += 1;
ni += 1;
} else if let Some(s_pi) = star_pi {
pi = s_pi + 1;
star_ni = Some(star_ni.unwrap() + 1);
ni = star_ni.unwrap();
} else {
break;
}
}
while pi < pat.len() && pat[pi] == b'*' {
pi += 1;
}
pi == pat.len() && ni == name.len()
}
pub fn write_u16_le_deep(buf: &mut [u8], offset: usize, value: u16) {
buf[offset..offset + 2].copy_from_slice(&value.to_le_bytes());
}
pub fn write_u32_le_deep(buf: &mut [u8], offset: usize, value: u32) {
buf[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
}
pub fn write_u64_le_deep(buf: &mut [u8], offset: usize, value: u64) {
buf[offset..offset + 8].copy_from_slice(&value.to_le_bytes());
}
pub fn hash_sha1_deep(data: &[u8]) -> [u8; 20] {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
data.hash(&mut hasher);
let h = hasher.finish();
let mut result = [0u8; 20];
result[..8].copy_from_slice(&h.to_le_bytes());
result[8..16].copy_from_slice(&(h >> 64).to_le_bytes());
result[16..20].copy_from_slice(&(h.wrapping_mul(0xDEAD_BEEF)).to_le_bytes()[..4]);
result
}
pub fn hash_md5_deep(data: &[u8]) -> [u8; 16] {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
data.hash(&mut hasher);
let h = hasher.finish();
let mut result = [0u8; 16];
result[..8].copy_from_slice(&h.to_le_bytes());
result[8..16].copy_from_slice(&(h ^ 0xCAFE_BABE_DEAD_BEEF).to_le_bytes());
result
}
pub fn uuid_v4_deep(data: &[u8]) -> [u8; 16] {
let mut u = hash_md5_deep(data);
u[6] = (u[6] & 0x0f) | 0x40; u[8] = (u[8] & 0x3f) | 0x80; u
}
pub fn hash_fast_deep(data: &[u8]) -> Vec<u8> {
let h = compute_fnv1a_64_deep(data);
h.to_le_bytes().to_vec()
}
pub fn visibility_to_elf_deep(vis: X86SymbolVisibilityDeep) -> u8 {
vis.to_elf_byte()
}
#[derive(Debug, Clone)]
pub struct X86LinkerDiagnosticDeep {
pub level: X86DiagLevelDeep,
pub message: String,
pub source: Option<String>,
}
#[derive(Debug, Clone)]
pub struct X86InputObjectDeep {
pub filename: String,
pub data: Vec<u8>,
pub sections: Vec<X86InputSectionDeep>,
pub symbols: Vec<X86InputSymbolDeep>,
pub relocations: Vec<X86InputRelocationDeep>,
pub machine: u16,
pub elf_class: u8,
}
#[derive(Debug, Clone)]
pub struct X86InputSectionDeep {
pub name: String,
pub data: Vec<u8>,
pub sh_type: u32,
pub sh_flags: u64,
pub sh_addralign: u64,
pub section_index: u32,
pub sh_entsize: u64,
pub sh_link: u32,
pub sh_info: u32,
pub is_merge_strings: bool,
pub is_gc_eligible: bool,
pub comdat_group: Option<String>,
}
#[derive(Debug, Clone)]
pub struct X86InputSymbolDeep {
pub name: String,
pub value: u64,
pub size: u64,
pub binding: u8,
pub sym_type: u8,
pub visibility: u8,
pub section_index: u32,
pub is_defined: bool,
pub common_alignment: u64,
}
#[derive(Debug, Clone)]
pub struct X86InputRelocationDeep {
pub offset: u64,
pub symbol_index: u32,
pub rel_type: u32,
pub addend: i64,
pub section_index: u32,
}
#[derive(Debug, Clone)]
pub struct X86SharedLibraryDeep {
pub name: String,
pub soname: Option<String>,
pub symbols: Vec<X86InputSymbolDeep>,
pub needed: Vec<String>,
pub versions: Vec<(String, String, bool)>, }
#[derive(Debug, Clone)]
pub struct X86OutputSectionDeep {
pub name: String,
pub data: Vec<u8>,
pub sh_type: u32,
pub sh_flags: u64,
pub sh_addralign: u64,
pub sh_entsize: u64,
pub segment_index: Option<usize>,
pub vaddr: u64,
pub file_offset: u64,
pub output_index: usize,
pub is_merge_strings: bool,
pub is_gc_eligible: bool,
pub sh_link: u32,
pub sh_info: u32,
pub is_nobits: bool,
pub original_size: u64,
}
#[derive(Debug, Clone)]
pub struct X86OutputSegmentDeep {
pub p_type: u32,
pub p_flags: u32,
pub p_align: u64,
pub p_vaddr: u64,
pub p_paddr: u64,
pub p_offset: u64,
pub p_filesz: u64,
pub p_memsz: u64,
pub section_indices: Vec<usize>,
}
#[derive(Debug, Clone)]
pub struct X86GotEntryDeep {
pub symbol_name: String,
pub got_offset: u64,
pub is_tls: bool,
pub is_ifunc: bool,
pub is_tlsdesc: bool,
}
#[derive(Debug, Clone)]
pub struct X86PltEntryDeep {
pub symbol_name: String,
pub plt_offset: u64,
pub got_offset: u64,
pub is_ifunc: bool,
pub needs_lazy_binding: bool,
}
#[derive(Debug, Clone)]
pub struct X86LinkerSymbolDeep {
pub name: String,
pub def: X86SymbolDefDeep,
pub binding: u8,
pub sym_type: u8,
pub visibility: X86SymbolVisibilityDeep,
pub version: Option<X86SymbolVersionDeep>,
pub is_referenced: bool,
pub is_exported: bool,
pub priority: u32,
pub is_absolute: bool,
pub is_wrapped: bool,
pub wrap_target: Option<String>,
pub is_defsym: bool,
}
#[derive(Debug, Clone)]
pub struct X86SymbolVersionDeep {
pub version_name: String,
pub version_hash: u32,
pub is_default: bool,
pub is_hidden: bool,
pub vda_name: Option<String>,
}
#[derive(Debug, Clone)]
pub struct X86DynamicListEntry {
pub pattern: String,
pub is_glob: bool,
pub export: bool,
}
#[derive(Debug, Clone)]
pub struct X86RelaxRecord {
pub section_index: usize,
pub offset: u64,
pub rel_type: u32,
pub relax_kind: X86RelaxKind,
pub symbol_name: String,
pub addend: i64,
}
#[derive(Debug, Clone)]
pub struct X86MergeFragment {
pub data: Vec<u8>,
pub alignment: u64,
pub offset_in_section: u64,
pub input_section_index: usize,
}
#[derive(Debug, Clone)]
pub struct X86StringMergeEntry {
pub string_data: Vec<u8>,
pub offsets_in_input: Vec<u64>,
pub output_offset: u64,
}
#[derive(Debug, Clone)]
pub struct X86ComdatGroupDeep {
pub name: String,
pub selection: u32,
pub member_sections: Vec<usize>,
pub is_live: bool,
}
#[derive(Debug, Clone)]
pub struct X86CoffObjectDeep {
pub filename: String,
pub data: Vec<u8>,
pub sections: Vec<X86CoffSectionDeep>,
pub symbols: Vec<X86CoffSymbolDeep>,
pub relocations: Vec<X86CoffRelocationDeep>,
pub machine: u16,
}
#[derive(Debug, Clone)]
pub struct X86CoffSectionDeep {
pub name: String,
pub data: Vec<u8>,
pub characteristics: u32,
pub align: u64,
pub section_index: u32,
}
#[derive(Debug, Clone)]
pub struct X86CoffSymbolDeep {
pub name: String,
pub value: u64,
pub section_number: i32,
pub sym_type: u16,
pub storage_class: u8,
pub number_of_aux_symbols: u8,
pub is_external: bool,
}
#[derive(Debug, Clone)]
pub struct X86CoffRelocationDeep {
pub virtual_address: u64,
pub symbol_index: u32,
pub rel_type: u16,
pub section_index: u32,
}
#[derive(Debug, Clone)]
pub struct X86CoffImportDeep {
pub symbol_name: String,
pub dll_name: String,
pub ordinal: u16,
pub is_data: bool,
pub hint: u16,
}
#[derive(Debug, Clone)]
pub struct X86CoffExportDeep {
pub name: String,
pub ordinal: u32,
pub rva: u32,
pub forwarder: Option<String>,
}
#[derive(Debug, Clone)]
pub struct X86MachOLoadCommand {
pub cmd: u32,
pub cmdsize: u32,
pub data: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct X86MachOSegment {
pub name: String,
pub vmaddr: u64,
pub vmsize: u64,
pub fileoff: u64,
pub filesize: u64,
pub maxprot: u32,
pub initprot: u32,
pub nsects: u32,
pub flags: u32,
pub sections: Vec<X86MachOSection>,
}
#[derive(Debug, Clone)]
pub struct X86MachOSection {
pub sectname: String,
pub segname: String,
pub addr: u64,
pub size: u64,
pub offset: u32,
pub align: u32,
pub reloff: u32,
pub nreloc: u32,
pub flags: u32,
pub data: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct X86CRELHeader {
pub version: u8,
pub flags: u8,
pub addend_present: bool,
pub explicit_addend: bool,
pub argument_present: bool,
pub section_count: u64,
}
#[derive(Debug, Clone)]
pub struct X86CRELEntry {
pub offset_delta: i64,
pub symidx_delta: i64,
pub relocation_type: u32,
pub addend: i64,
pub has_addend: bool,
}
#[derive(Debug, Clone)]
pub struct X86ArchiveMemberDeep {
pub name: String,
pub data: Vec<u8>,
pub symbols: Vec<String>,
pub timestamp: u32,
pub uid: u32,
pub gid: u32,
pub mode: u32,
}
#[derive(Debug, Clone)]
pub struct X86LTOBitcodeInfoDeep {
pub is_bitcode: bool,
pub is_thin_lto: bool,
pub target_triple: Option<String>,
pub symbol_names: Vec<String>,
pub module_hash: Option<u64>,
}
#[derive(Debug, Clone)]
pub struct X86LTOCacheEntry {
pub key: String,
pub data: Vec<u8>,
pub created_at: u64,
pub size_bytes: u64,
}
#[derive(Debug, Clone)]
pub struct X86MemoryRegionDeep {
pub name: String,
pub origin: u64,
pub length: u64,
pub attributes: X86MemoryAttrsDeep,
}
#[derive(Debug, Clone)]
pub struct X86MemoryAttrsDeep {
pub readable: bool,
pub writable: bool,
pub executable: bool,
pub allocatable: bool,
}
#[derive(Debug, Clone)]
pub struct X86ScriptSectionCommandDeep {
pub name: String,
pub vma: Option<u64>,
pub lma: Option<u64>,
pub align: u64,
pub input_patterns: Vec<X86ScriptSectionPatternDeep>,
pub keep_patterns: Vec<String>,
pub fill_value: Option<u32>,
pub section_type: X86ScriptSectionTypeDeep,
pub subsection_count: usize,
}
#[derive(Debug, Clone)]
pub struct X86ScriptSectionPatternDeep {
pub file_pattern: String,
pub section_pattern: String,
pub exclude_files: Vec<String>,
pub sort_by: X86SortKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86SortKind {
None,
Name,
Alignment,
InitPriority,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86ScriptSectionTypeDeep {
Progbits,
NoBits,
Note,
Copy,
Info,
Overlay,
}
pub struct X86CREL {
entries: Vec<X86CRELEntry>,
current_offset: i64,
current_symidx: i64,
}
impl X86CREL {
pub fn new() -> Self {
Self {
entries: Vec::new(),
current_offset: 0,
current_symidx: 0,
}
}
fn add_relocation(
&mut self,
offset: u64,
symbol_index: i64,
rel_type: u32,
addend: i64,
has_addend: bool,
) {
let offset_delta = offset as i64 - self.current_offset;
let symidx_delta = symbol_index - self.current_symidx;
self.entries.push(X86CRELEntry {
offset_delta,
symidx_delta,
relocation_type: rel_type,
addend,
has_addend,
});
self.current_offset = offset as i64;
self.current_symidx = symbol_index;
}
pub fn encode(&self) -> Vec<u8> {
let mut buf = Vec::new();
buf.push(0x01); buf.push(0); Self::write_uleb128(&mut buf, self.entries.len() as u64);
for entry in &self.entries {
Self::write_sleb128(&mut buf, entry.offset_delta);
Self::write_sleb128(&mut buf, entry.symidx_delta);
Self::write_uleb128(&mut buf, entry.relocation_type as u64);
if entry.has_addend {
Self::write_sleb128(&mut buf, entry.addend);
}
}
buf
}
pub fn decode(data: &[u8]) -> Option<Self> {
if data.len() < 2 {
return None;
}
let _version = data[0];
let _flags = data[1];
let mut pos = 2usize;
let (section_count, advance) = Self::read_uleb128(data, pos)?;
pos = advance;
let mut crel = Self::new();
for _ in 0..section_count {
let (offset_delta, a1) = Self::read_sleb128(data, pos)?;
pos = a1;
let (symidx_delta, a2) = Self::read_sleb128(data, pos)?;
pos = a2;
let (rel_type, a3) = Self::read_uleb128(data, pos)?;
pos = a3;
let (addend, has_addend) = if pos < data.len() && data[pos] & 0x80 != 0 {
let (v, a4) = Self::read_sleb128(data, pos)?;
pos = a4;
(v, true)
} else {
(0, false)
};
crel.entries.push(X86CRELEntry {
offset_delta: offset_delta as i64,
symidx_delta,
relocation_type: rel_type as u32,
addend,
has_addend,
});
}
Some(crel)
}
pub fn expand(&self) -> Vec<(u64, i64, u32, i64, bool)> {
let (mut off, mut sdx) = (0i64, 0i64);
let mut result = Vec::new();
for e in &self.entries {
off += e.offset_delta;
sdx += e.symidx_delta;
result.push((off as u64, sdx, e.relocation_type, e.addend, e.has_addend));
}
result
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
fn write_uleb128(buf: &mut Vec<u8>, mut value: u64) {
loop {
let mut byte = (value & 0x7f) as u8;
value >>= 7;
if value != 0 {
byte |= 0x80;
}
buf.push(byte);
if value == 0 {
break;
}
}
}
fn write_sleb128(buf: &mut Vec<u8>, mut value: i64) {
loop {
let mut byte = (value & 0x7f) as u8;
value >>= 7;
if (value == 0 && byte & 0x40 == 0) || (value == -1 && byte & 0x40 != 0) {
buf.push(byte);
break;
}
byte |= 0x80;
buf.push(byte);
}
}
fn read_uleb128(data: &[u8], pos: usize) -> Option<(u64, usize)> {
let mut result: u64 = 0;
let mut shift = 0u32;
let mut offset = pos;
loop {
if offset >= data.len() {
return None;
}
let byte = data[offset];
result |= ((byte & 0x7f) as u64) << shift;
offset += 1;
if byte & 0x80 == 0 {
break;
}
shift += 7;
if shift >= 64 {
return None;
}
}
Some((result, offset))
}
fn read_sleb128(data: &[u8], pos: usize) -> Option<(i64, usize)> {
let mut result: i64 = 0;
let mut shift = 0u32;
let mut offset = pos;
let mut byte;
loop {
if offset >= data.len() {
return None;
}
byte = data[offset];
result |= ((byte & 0x7f) as i64) << shift;
shift += 7;
offset += 1;
if byte & 0x80 == 0 {
break;
}
if shift >= 64 {
return None;
}
}
if shift < 64 && (byte & 0x40) != 0 {
result |= !0 << shift;
}
Some((result, offset))
}
}
impl Default for X86CREL {
fn default() -> Self {
Self::new()
}
}
pub struct X86BuildID {
kind: X86BuildIdKindDeep,
custom_hex: Option<String>,
}
impl X86BuildID {
pub fn new(kind: X86BuildIdKindDeep) -> Self {
Self {
kind,
custom_hex: None,
}
}
pub fn with_hex_string(hex: &str) -> Self {
Self {
kind: X86BuildIdKindDeep::HexString,
custom_hex: Some(hex.to_string()),
}
}
pub fn compute(&self, data: &[u8]) -> Vec<u8> {
match self.kind {
X86BuildIdKindDeep::None => Vec::new(),
X86BuildIdKindDeep::Sha1 => {
let hash = hash_sha1_deep(data);
hash.to_vec()
}
X86BuildIdKindDeep::Md5 => {
let hash = hash_md5_deep(data);
hash.to_vec()
}
X86BuildIdKindDeep::Uuid => {
let uuid = uuid_v4_deep(data);
uuid.to_vec()
}
X86BuildIdKindDeep::Fast => hash_fast_deep(data),
X86BuildIdKindDeep::Sha256 => {
let mut result = Vec::new();
let h1 = compute_fnv1a_64_deep(data);
let h2 = compute_fnv1a_64_deep(&[&h1.to_le_bytes()[..], data].concat());
result.extend_from_slice(&h1.to_le_bytes());
result.extend_from_slice(&h2.to_le_bytes());
result.extend_from_slice(&(h1 ^ h2).to_le_bytes());
result.extend_from_slice(&data[..8.min(data.len())]);
while result.len() < 32 {
result.push(0);
}
result.truncate(32);
result
}
X86BuildIdKindDeep::HexString => {
if let Some(ref hex) = self.custom_hex {
let hex = hex
.chars()
.filter(|c| c.is_ascii_hexdigit())
.collect::<String>();
let mut bytes = Vec::new();
for i in (0..hex.len()).step_by(2) {
if i + 1 < hex.len() {
if let Ok(b) = u8::from_str_radix(&hex[i..i + 2], 16) {
bytes.push(b);
}
}
}
bytes
} else {
Vec::new()
}
}
}
}
pub fn build_note_section(&self, data: &[u8]) -> Vec<u8> {
let hash = self.compute(data);
if hash.is_empty() {
return Vec::new();
}
let name = b"GNU\0";
let namesz = name.len() as u32;
let descsz = hash.len() as u32;
let note_type: u32 = NT_GNU_BUILD_ID;
let mut buf = Vec::new();
buf.extend_from_slice(&namesz.to_le_bytes());
buf.extend_from_slice(&descsz.to_le_bytes());
buf.extend_from_slice(¬e_type.to_le_bytes());
buf.extend_from_slice(name);
let name_end = buf.len();
let name_pad = (4 - (name_end % 4)) % 4;
buf.extend(std::iter::repeat(0u8).take(name_pad));
buf.extend_from_slice(&hash);
let desc_end = buf.len();
let desc_pad = (4 - (desc_end % 4)) % 4;
buf.extend(std::iter::repeat(0u8).take(desc_pad));
buf
}
pub fn kind(&self) -> X86BuildIdKindDeep {
self.kind
}
pub fn is_enabled(&self) -> bool {
self.kind != X86BuildIdKindDeep::None
}
pub fn section_name() -> &'static str {
".note.gnu.build-id"
}
}
impl Default for X86BuildID {
fn default() -> Self {
Self::new(X86BuildIdKindDeep::None)
}
}
pub struct X86MergeSections {
string_tables: HashMap<Vec<u8>, X86StringMergeEntry>,
constant_table: HashMap<u64, u64>,
output_data: Vec<u8>,
mergeable_kind: X86MergeKind,
section_align: u64,
entsize: u64,
tail_merge_enabled: bool,
}
impl X86MergeSections {
pub fn new(kind: X86MergeKind, align: u64, entsize: u64) -> Self {
Self {
string_tables: HashMap::new(),
constant_table: HashMap::new(),
output_data: Vec::new(),
mergeable_kind: kind,
section_align: align,
entsize,
tail_merge_enabled: false,
}
}
pub fn with_tail_merge(mut self, enabled: bool) -> Self {
self.tail_merge_enabled = enabled;
self
}
pub fn add_strings(&mut self, data: &[u8], alignment: u64) -> HashMap<u64, u64> {
let mut mapping = HashMap::new();
let mut pos = 0usize;
while pos < data.len() {
let end = data[pos..]
.iter()
.position(|&b| b == 0)
.map(|p| pos + p + 1)
.unwrap_or(data.len());
let string = data[pos..end.min(data.len())].to_vec();
if string.is_empty() || string == vec![0] {
if pos + 1 <= data.len() {
mapping.insert(pos as u64, 0);
}
pos = end;
continue;
}
let existing_offset = if self.tail_merge_enabled {
self.find_tail_match(&string)
} else {
None
};
if let Some(off) = existing_offset {
mapping.insert(pos as u64, off);
} else if let Some(entry) = self.string_tables.get(&string) {
mapping.insert(pos as u64, entry.output_offset);
} else {
let out_off = self.output_data.len() as u64;
self.output_data.extend_from_slice(&string);
self.string_tables.insert(
string.clone(),
X86StringMergeEntry {
string_data: string,
offsets_in_input: vec![pos as u64],
output_offset: out_off,
},
);
mapping.insert(pos as u64, out_off);
}
pos = end;
}
mapping
}
pub fn add_constants(&mut self, data: &[u8], alignment: u64) -> HashMap<u64, u64> {
let mut mapping = HashMap::new();
let e = self.entsize as usize;
if e == 0 {
return mapping;
}
let mut pos = 0usize;
while pos + e <= data.len() {
let chunk = &data[pos..pos + e];
let hash = compute_fnv1a_64_deep(chunk);
if let Some(existing_offset) = self.constant_table.get(&hash) {
let existing_data =
&self.output_data[*existing_offset as usize..*existing_offset as usize + e];
if existing_data == chunk {
mapping.insert(pos as u64, *existing_offset);
pos += e;
continue;
}
}
let out_off = self.output_data.len() as u64;
let aligned = align_up_deep(out_off, alignment);
if aligned > out_off {
self.output_data
.extend(std::iter::repeat(0u8).take((aligned - out_off) as usize));
}
let actual_off = self.output_data.len() as u64;
self.output_data.extend_from_slice(chunk);
self.constant_table.insert(hash, actual_off);
mapping.insert(pos as u64, actual_off);
pos += e;
}
mapping
}
pub fn output_data(&self) -> &[u8] {
&self.output_data
}
pub fn output_size(&self) -> u64 {
self.output_data.len() as u64
}
pub fn unique_count(&self) -> usize {
match self.mergeable_kind {
X86MergeKind::Strings => self.string_tables.len(),
X86MergeKind::Constants | X86MergeKind::TailMerge => self.constant_table.len(),
}
}
fn find_tail_match(&self, string: &[u8]) -> Option<u64> {
for (existing, entry) in &self.string_tables {
let elen = existing.len();
let slen = string.len();
if slen <= elen && existing[elen - slen..] == *string {
return Some(entry.output_offset + (elen - slen) as u64);
}
if elen <= slen && &string[slen - elen..] == existing.as_slice() {
return Some(entry.output_offset);
}
}
None
}
pub fn clear(&mut self) {
self.string_tables.clear();
self.constant_table.clear();
self.output_data.clear();
}
}
impl Default for X86MergeSections {
fn default() -> Self {
Self::new(X86MergeKind::Strings, 1, 1)
}
}
pub struct X86ELFWriterDeep {
pub class: u8,
pub endian: u8,
pub os_abi: u8,
pub abi_version: u8,
pub machine: u16,
pub file_type: u16,
pub entry: u64,
pub e_flags: u32,
sections: Vec<X86OutputSectionDeep>,
program_headers: Vec<X86OutputSegmentDeep>,
symtab: Vec<Elf64Sym>,
dynsym: Vec<Elf64Sym>,
strtab: Vec<u8>,
strtab_offsets: HashMap<String, u64>,
dynstr: Vec<u8>,
dynstr_offsets: HashMap<String, u64>,
shstrtab: Vec<u8>,
shstrtab_offsets: HashMap<String, u64>,
rela_dyn: Vec<X86InputRelocationDeep>,
rela_plt: Vec<X86InputRelocationDeep>,
section_rela: HashMap<usize, Vec<X86InputRelocationDeep>>,
notes: Vec<(String, u32, Vec<u8>)>, dynamic_entries: Vec<(i64, u64)>,
got_data: Vec<u8>,
plt_data: Vec<u8>,
section_counter: usize,
null_section_added: bool,
base_addr: u64,
is_pie: bool,
is_shared: bool,
image_base: u64,
}
impl X86ELFWriterDeep {
pub fn new_elf64_exec() -> Self {
Self {
class: ELFCLASS64,
endian: ELFDATA2LSB,
os_abi: ELFOSABI_SYSV,
abi_version: 0,
machine: EM_X86_64,
file_type: ET_EXEC,
entry: 0,
e_flags: 0,
sections: Vec::new(),
program_headers: Vec::new(),
symtab: Vec::new(),
dynsym: Vec::new(),
strtab: vec![0],
strtab_offsets: HashMap::new(),
dynstr: vec![0],
dynstr_offsets: HashMap::new(),
shstrtab: vec![0],
shstrtab_offsets: HashMap::new(),
rela_dyn: Vec::new(),
rela_plt: Vec::new(),
section_rela: HashMap::new(),
notes: Vec::new(),
dynamic_entries: Vec::new(),
got_data: Vec::new(),
plt_data: Vec::new(),
section_counter: 0,
null_section_added: false,
base_addr: X86_64_DEFAULT_IMAGE_BASE,
is_pie: false,
is_shared: false,
image_base: X86_64_DEFAULT_IMAGE_BASE,
}
}
pub fn new_elf64_shared() -> Self {
let mut w = Self::new_elf64_exec();
w.file_type = ET_DYN;
w.is_shared = true;
w.base_addr = 0;
w.image_base = 0;
w
}
pub fn new_elf64_pie() -> Self {
let mut w = Self::new_elf64_exec();
w.file_type = ET_DYN;
w.is_pie = true;
w.base_addr = 0;
w
}
pub fn new_elf32_exec() -> Self {
Self {
class: ELFCLASS32,
endian: ELFDATA2LSB,
os_abi: ELFOSABI_SYSV,
abi_version: 0,
machine: EM_386,
file_type: ET_EXEC,
entry: 0,
e_flags: 0,
sections: Vec::new(),
program_headers: Vec::new(),
symtab: Vec::new(),
dynsym: Vec::new(),
strtab: vec![0],
strtab_offsets: HashMap::new(),
dynstr: vec![0],
dynstr_offsets: HashMap::new(),
shstrtab: vec![0],
shstrtab_offsets: HashMap::new(),
rela_dyn: Vec::new(),
rela_plt: Vec::new(),
section_rela: HashMap::new(),
notes: Vec::new(),
dynamic_entries: Vec::new(),
got_data: Vec::new(),
plt_data: Vec::new(),
section_counter: 0,
null_section_added: false,
base_addr: I386_DEFAULT_IMAGE_BASE,
is_pie: false,
is_shared: false,
image_base: I386_DEFAULT_IMAGE_BASE,
}
}
pub fn new_elf32_rel() -> Self {
let mut w = Self::new_elf32_exec();
w.file_type = ET_REL;
w.base_addr = 0;
w
}
pub fn set_entry(&mut self, addr: u64) {
self.entry = addr;
}
pub fn set_os_abi(&mut self, abi: u8) {
self.os_abi = abi;
}
pub fn set_base_addr(&mut self, addr: u64) {
self.base_addr = addr;
}
pub fn add_null_section(&mut self) {
if self.null_section_added {
return;
}
self.sections.push(X86OutputSectionDeep {
name: String::new(),
data: Vec::new(),
sh_type: SHT_NULL,
sh_flags: 0,
sh_addralign: 0,
sh_entsize: 0,
segment_index: None,
vaddr: 0,
file_offset: 0,
output_index: self.section_counter,
is_merge_strings: false,
is_gc_eligible: false,
sh_link: 0,
sh_info: 0,
is_nobits: false,
original_size: 0,
});
self.section_counter += 1;
self.null_section_added = true;
}
pub fn add_section(&mut self, mut section: X86OutputSectionDeep) -> usize {
let idx = self.sections.len();
section.output_index = self.section_counter;
self.section_counter += 1;
self.sections.push(section);
idx
}
pub fn add_program_header(&mut self, phdr: X86OutputSegmentDeep) {
self.program_headers.push(phdr);
}
pub fn add_note(&mut self, name: &str, note_type: u32, desc: &[u8]) {
self.notes
.push((name.to_string(), note_type, desc.to_vec()));
}
pub fn add_symbol(
&mut self,
name: &str,
value: u64,
size: u64,
binding: u8,
sym_type: u8,
section_index: u16,
visibility: u8,
is_dynamic: bool,
) -> usize {
let st_info = (binding << 4) | (sym_type & 0xf);
let st_other = visibility & 0x3;
let sym = Elf64Sym {
st_name: self.get_strtab_offset(name) as u32,
st_info,
st_other,
st_shndx: section_index,
st_value: value,
st_size: size,
};
if is_dynamic {
let d_idx = self.dynsym.len();
let d_name_offset = self.get_dynstr_offset(name);
self.dynsym.push(Elf64Sym {
st_name: d_name_offset as u32,
st_info,
st_other,
st_shndx: section_index,
st_value: value,
st_size: size,
});
d_idx
} else {
let idx = self.symtab.len();
self.symtab.push(sym);
idx
}
}
pub fn add_rela_dyn(&mut self, reloc: X86InputRelocationDeep) {
self.rela_dyn.push(reloc);
}
pub fn add_rela_plt(&mut self, reloc: X86InputRelocationDeep) {
self.rela_plt.push(reloc);
}
pub fn add_section_rela(&mut self, section_index: usize, reloc: X86InputRelocationDeep) {
self.section_rela
.entry(section_index)
.or_default()
.push(reloc);
}
pub fn get_strtab_offset(&mut self, name: &str) -> u64 {
if name.is_empty() {
return 0;
}
if let Some(&off) = self.strtab_offsets.get(name) {
return off;
}
let off = self.strtab.len() as u64;
self.strtab.extend_from_slice(name.as_bytes());
self.strtab.push(0);
self.strtab_offsets.insert(name.to_string(), off);
off
}
pub fn get_dynstr_offset(&mut self, name: &str) -> u64 {
if name.is_empty() {
return 0;
}
if let Some(&off) = self.dynstr_offsets.get(name) {
return off;
}
let off = self.dynstr.len() as u64;
self.dynstr.extend_from_slice(name.as_bytes());
self.dynstr.push(0);
self.dynstr_offsets.insert(name.to_string(), off);
off
}
pub fn get_shstrtab_offset(&mut self, name: &str) -> u64 {
if name.is_empty() {
return 0;
}
if let Some(&off) = self.shstrtab_offsets.get(name) {
return off;
}
let off = self.shstrtab.len() as u64;
self.shstrtab.extend_from_slice(name.as_bytes());
self.shstrtab.push(0);
self.shstrtab_offsets.insert(name.to_string(), off);
off
}
pub fn set_got_data(&mut self, data: &[u8]) {
self.got_data = data.to_vec();
}
pub fn set_plt_data(&mut self, data: &[u8]) {
self.plt_data = data.to_vec();
}
pub fn add_dynamic_entry(&mut self, tag: i64, val: u64) {
self.dynamic_entries.push((tag, val));
}
fn ehdr_size(&self) -> u64 {
if self.class == ELFCLASS64 {
64
} else {
52
}
}
fn phdr_entry_size(&self) -> u64 {
if self.class == ELFCLASS64 {
56
} else {
32
}
}
fn shdr_entry_size(&self) -> u64 {
if self.class == ELFCLASS64 {
64
} else {
40
}
}
fn all_sections(&self) -> Vec<&X86OutputSectionDeep> {
self.sections.iter().collect()
}
pub fn write(&mut self) -> Vec<u8> {
self.add_null_section();
let mut buf = Vec::new();
let ehdr_sz = self.ehdr_size();
let phdr_entry_sz = self.phdr_entry_size();
let phdr_total = phdr_entry_sz * self.program_headers.len() as u64;
let shdr_entry_sz = self.shdr_entry_size();
let phoff = ehdr_sz;
let mut file_off = ehdr_sz + phdr_total;
self.write_elf_header(&mut buf, ehdr_sz, phoff, phdr_entry_sz, shdr_entry_sz);
self.write_program_headers(&mut buf, phdr_entry_sz, &mut file_off);
let mut section_offsets: Vec<(u64, u64)> = Vec::new();
for sec in &self.sections {
let name = &sec.name;
let data: Vec<u8> = match name.as_str() {
".symtab" => self.write_symtab_raw(&self.symtab),
".strtab" => self.strtab.clone(),
".dynsym" => self.write_symtab_raw(&self.dynsym),
".dynstr" => self.dynstr.clone(),
".shstrtab" => self.shstrtab.clone(),
".got" | ".got.plt" => self.got_data.clone(),
".plt" | ".plt.got" | ".plt.sec" => self.plt_data.clone(),
".rela.dyn" => self.write_rela_raw(&self.rela_dyn),
".rela.plt" => self.write_rela_raw(&self.rela_plt),
_ => {
if let Some(relocs) = self.section_rela.get(&sec.output_index) {
self.write_rela_raw(relocs)
} else if name == ".note.gnu.build-id" || name.starts_with(".note") {
self.write_notes_raw()
} else {
sec.data.clone()
}
}
};
let size = data.len() as u64;
let aligned_off = align_up_deep(file_off, sec.sh_addralign.max(1));
let pad = aligned_off - file_off;
buf.extend(std::iter::repeat(0u8).take(pad as usize));
section_offsets.push((buf.len() as u64, size));
if name != ".bss" && name != ".tbss" {
buf.extend_from_slice(&data);
file_off = buf.len() as u64;
} else {
}
}
let shoff = buf.len() as u64;
write_u64_le_deep(&mut buf, 0x28, shoff);
self.write_section_headers(&mut buf, §ion_offsets, shdr_entry_sz);
let shnum = self.sections.len() as u16;
write_u16_le_deep(&mut buf, 0x3c, shnum);
write_u16_le_deep(&mut buf, 0x3e, self.find_shstrtab_index() as u16);
buf
}
pub fn write_elf_header_only(&self) -> Vec<u8> {
let mut buf = vec![0u8; self.ehdr_size() as usize];
buf[0..4].copy_from_slice(&ELF_MAGIC_BYTES);
buf[EI_CLASS as usize] = self.class;
buf[EI_DATA as usize] = self.endian;
buf[EI_VERSION as usize] = EV_CURRENT as u8;
buf[EI_OSABI as usize] = self.os_abi;
buf[EI_ABIVERSION as usize] = self.abi_version;
write_u16_le_deep(&mut buf, 0x10, self.file_type);
write_u16_le_deep(&mut buf, 0x12, self.machine);
write_u32_le_deep(&mut buf, 0x14, EV_CURRENT as u32);
if self.class == ELFCLASS64 {
write_u64_le_deep(&mut buf, 0x18, self.entry);
write_u64_le_deep(&mut buf, 0x20, 0); write_u64_le_deep(&mut buf, 0x28, 0); write_u32_le_deep(&mut buf, 0x30, self.e_flags);
write_u16_le_deep(&mut buf, 0x34, self.ehdr_size() as u16);
write_u16_le_deep(&mut buf, 0x36, self.phdr_entry_size() as u16);
write_u16_le_deep(&mut buf, 0x38, 0); write_u16_le_deep(&mut buf, 0x3a, self.shdr_entry_size() as u16);
write_u16_le_deep(&mut buf, 0x3c, 0); write_u16_le_deep(&mut buf, 0x3e, 0); } else {
write_u32_le_deep(&mut buf, 0x18, self.entry as u32);
write_u32_le_deep(&mut buf, 0x1c, 0); write_u32_le_deep(&mut buf, 0x20, 0); write_u32_le_deep(&mut buf, 0x24, self.e_flags);
write_u16_le_deep(&mut buf, 0x28, self.ehdr_size() as u16);
write_u16_le_deep(&mut buf, 0x2a, self.phdr_entry_size() as u16);
write_u16_le_deep(&mut buf, 0x2c, 0); write_u16_le_deep(&mut buf, 0x2e, self.shdr_entry_size() as u16);
write_u16_le_deep(&mut buf, 0x30, 0); write_u16_le_deep(&mut buf, 0x32, 0); }
buf
}
fn write_elf_header(
&mut self,
buf: &mut Vec<u8>,
ehdr_sz: u64,
phoff: u64,
phdr_entry_sz: u64,
shdr_entry_sz: u64,
) {
let off = buf.len();
buf.resize(off + ehdr_sz as usize, 0);
let b = &mut buf[off..];
b[0..4].copy_from_slice(&ELF_MAGIC_BYTES);
b[EI_CLASS as usize] = self.class;
b[EI_DATA as usize] = self.endian;
b[EI_VERSION as usize] = EV_CURRENT as u8;
b[EI_OSABI as usize] = self.os_abi;
b[EI_ABIVERSION as usize] = self.abi_version;
write_u16_le_deep(b, 0x10 - off, self.file_type);
write_u16_le_deep(b, 0x12 - off, self.machine);
write_u32_le_deep(b, 0x14 - off, EV_CURRENT as u32);
if self.class == ELFCLASS64 {
write_u64_le_deep(b, 0x18 - off, self.entry);
write_u64_le_deep(b, 0x20 - off, phoff);
write_u64_le_deep(b, 0x28 - off, 0); write_u32_le_deep(b, 0x30 - off, self.e_flags);
write_u16_le_deep(b, 0x34 - off, ehdr_sz as u16);
write_u16_le_deep(b, 0x36 - off, phdr_entry_sz as u16);
write_u16_le_deep(b, 0x38 - off, self.program_headers.len() as u16);
write_u16_le_deep(b, 0x3a - off, shdr_entry_sz as u16);
}
}
fn write_program_headers(&mut self, buf: &mut Vec<u8>, phdr_esz: u64, _file_off: &mut u64) {
if self.class == ELFCLASS64 {
for phdr in &self.program_headers {
let off = buf.len();
buf.resize(off + phdr_esz as usize, 0);
let b = &mut buf[off..];
write_u32_le_deep(b, 0, phdr.p_type);
write_u32_le_deep(b, 4, phdr.p_flags);
write_u64_le_deep(b, 8, phdr.p_offset);
write_u64_le_deep(b, 16, phdr.p_vaddr);
write_u64_le_deep(b, 24, phdr.p_paddr);
write_u64_le_deep(b, 32, phdr.p_filesz);
write_u64_le_deep(b, 40, phdr.p_memsz);
write_u64_le_deep(b, 48, phdr.p_align);
}
} else {
for phdr in &self.program_headers {
let off = buf.len();
buf.resize(off + phdr_esz as usize, 0);
let b = &mut buf[off..];
write_u32_le_deep(b, 0, phdr.p_type);
write_u32_le_deep(b, 4, phdr.p_offset as u32);
write_u32_le_deep(b, 8, phdr.p_vaddr as u32);
write_u32_le_deep(b, 12, phdr.p_paddr as u32);
write_u32_le_deep(b, 16, phdr.p_filesz as u32);
write_u32_le_deep(b, 20, phdr.p_memsz as u32);
write_u32_le_deep(b, 24, phdr.p_flags);
write_u32_le_deep(b, 28, phdr.p_align as u32);
}
}
}
fn write_section_headers(
&mut self,
buf: &mut Vec<u8>,
section_offsets: &[(u64, u64)],
shdr_esz: u64,
) {
let sec_names: Vec<String> = self.sections.iter().map(|s| s.name.clone()).collect();
let sh_names: Vec<u32> = sec_names
.iter()
.map(|name| self.get_shstrtab_offset(name) as u32)
.collect();
for (i, sec) in self.sections.iter().enumerate() {
let sh_name = sh_names[i];
let (sh_offset, sh_size) = if i < section_offsets.len() {
section_offsets[i]
} else {
(0, 0)
};
let off = buf.len();
buf.resize(off + shdr_esz as usize, 0);
let b = &mut buf[off..];
if self.class == ELFCLASS64 {
write_u32_le_deep(b, 0, sh_name);
write_u32_le_deep(b, 4, sec.sh_type);
write_u64_le_deep(b, 8, sec.sh_flags);
write_u64_le_deep(b, 16, sec.vaddr);
write_u64_le_deep(b, 24, sh_offset);
write_u64_le_deep(b, 32, sh_size);
write_u32_le_deep(b, 40, sec.sh_link);
write_u32_le_deep(b, 44, sec.sh_info);
write_u64_le_deep(b, 48, sec.sh_addralign);
write_u64_le_deep(b, 56, sec.sh_entsize);
} else {
write_u32_le_deep(b, 0, sh_name);
write_u32_le_deep(b, 4, sec.sh_type);
write_u32_le_deep(b, 8, sec.sh_flags as u32);
write_u32_le_deep(b, 12, sec.vaddr as u32);
write_u32_le_deep(b, 16, sh_offset as u32);
write_u32_le_deep(b, 20, sh_size as u32);
write_u32_le_deep(b, 24, sec.sh_link);
write_u32_le_deep(b, 28, sec.sh_info);
write_u32_le_deep(b, 32, sec.sh_addralign as u32);
write_u32_le_deep(b, 36, sec.sh_entsize as u32);
}
}
}
fn write_symtab_raw(&self, syms: &[Elf64Sym]) -> Vec<u8> {
let mut buf = Vec::with_capacity(syms.len() * 24);
for sym in syms {
buf.extend_from_slice(&sym.st_name.to_le_bytes());
buf.push(sym.st_info);
buf.push(sym.st_other);
buf.extend_from_slice(&sym.st_shndx.to_le_bytes());
buf.extend_from_slice(&sym.st_value.to_le_bytes());
buf.extend_from_slice(&sym.st_size.to_le_bytes());
}
buf
}
fn write_rela_raw(&self, relocs: &[X86InputRelocationDeep]) -> Vec<u8> {
let mut buf = Vec::new();
for rel in relocs {
if self.class == ELFCLASS64 {
buf.extend_from_slice(&rel.offset.to_le_bytes());
let r_info = ((rel.symbol_index as u64) << 32) | (rel.rel_type as u64);
buf.extend_from_slice(&r_info.to_le_bytes());
buf.extend_from_slice(&(rel.addend as i64).to_le_bytes());
} else {
buf.extend_from_slice(&(rel.offset as u32).to_le_bytes());
let r_info = ((rel.symbol_index) << 8) | (rel.rel_type & 0xff);
buf.extend_from_slice(&r_info.to_le_bytes());
buf.extend_from_slice(&(rel.addend as i32).to_le_bytes());
}
}
buf
}
fn write_notes_raw(&self) -> Vec<u8> {
let mut buf = Vec::new();
for (name, note_type, desc) in &self.notes {
let name_bytes = format!("{}\0", name);
let namesz = name_bytes.len() as u32;
let descsz = desc.len() as u32;
buf.extend_from_slice(&namesz.to_le_bytes());
buf.extend_from_slice(&descsz.to_le_bytes());
buf.extend_from_slice(¬e_type.to_le_bytes());
buf.extend_from_slice(name_bytes.as_bytes());
let name_end = buf.len();
let pad = (4 - (name_end % 4)) % 4;
buf.extend(std::iter::repeat(0u8).take(pad));
buf.extend_from_slice(desc);
let desc_end = buf.len();
let pad = (4 - (desc_end % 4)) % 4;
buf.extend(std::iter::repeat(0u8).take(pad));
}
buf
}
fn find_shstrtab_index(&self) -> usize {
self.sections
.iter()
.position(|s| s.name == ".shstrtab")
.unwrap_or(1)
}
pub fn find_symtab_index(&self) -> Option<usize> {
self.sections.iter().position(|s| s.name == ".symtab")
}
pub fn find_strtab_index(&self) -> Option<usize> {
self.sections.iter().position(|s| s.name == ".strtab")
}
pub fn estimate_size(&self) -> usize {
let ehdr_sz = self.ehdr_size() as usize;
let phdr_sz = self.program_headers.len() * self.phdr_entry_size() as usize;
let data_sz: usize = self.sections.iter().map(|s| s.data.len()).sum();
let shdr_sz = self.sections.len() * self.shdr_entry_size() as usize;
ehdr_sz + phdr_sz + data_sz + shdr_sz + 1024 }
}
pub struct X86DynamicLinker {
pub interp: Option<String>,
pub dynamic_entries: Vec<(i64, u64)>,
pub needed: Vec<String>,
pub rpath: Vec<String>,
pub runpath: Vec<String>,
pub soname: Option<String>,
pub got_addr: u64,
pub got_size: u64,
pub got_entries: Vec<X86GotEntryDeep>,
pub plt_addr: u64,
pub plt_size: u64,
pub plt_entries: Vec<X86PltEntryDeep>,
pub lazy_binding: bool,
pub tlsdesc_enabled: bool,
pub tls_module_id_offset: u64,
pub tlsdesc_entries: Vec<(String, u64, u64)>, pub init_addr: u64,
pub fini_addr: u64,
pub init_array: Vec<u64>,
pub fini_array: Vec<u64>,
pub hash_style: X86HashStyleDeep,
pub verdef: Vec<(String, Vec<(String, u16)>)>, pub verneed: Vec<(String, Vec<(String, u16, u16)>)>, pub versym: Vec<u16>,
pub relacount: u64,
}
impl X86DynamicLinker {
pub fn new() -> Self {
Self {
interp: None,
dynamic_entries: Vec::new(),
needed: Vec::new(),
rpath: Vec::new(),
runpath: Vec::new(),
soname: None,
got_addr: 0,
got_size: 0,
got_entries: Vec::new(),
plt_addr: 0,
plt_size: 0,
plt_entries: Vec::new(),
lazy_binding: true,
tlsdesc_enabled: false,
tls_module_id_offset: 0,
tlsdesc_entries: Vec::new(),
init_addr: 0,
fini_addr: 0,
init_array: Vec::new(),
fini_array: Vec::new(),
hash_style: X86HashStyleDeep::Both,
verdef: Vec::new(),
verneed: Vec::new(),
versym: Vec::new(),
relacount: 0,
}
}
pub fn set_interp(&mut self, path: &str) {
self.interp = Some(path.to_string());
}
pub fn add_needed(&mut self, lib: &str) {
if !self.needed.contains(&lib.to_string()) {
self.needed.push(lib.to_string());
}
}
pub fn set_rpath(&mut self, path: &str) {
self.rpath.clear();
self.rpath.push(path.to_string());
}
pub fn set_runpath(&mut self, path: &str) {
self.runpath.clear();
self.runpath.push(path.to_string());
}
pub fn enable_tlsdesc(&mut self, enable: bool) {
self.tlsdesc_enabled = enable;
}
pub fn add_tlsdesc(&mut self, name: &str, got_offset: u64, resolver_addr: u64) {
self.tlsdesc_entries
.push((name.to_string(), got_offset, resolver_addr));
}
pub fn build_got(&mut self) -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(&0u64.to_le_bytes());
buf.extend_from_slice(&0u64.to_le_bytes());
buf.extend_from_slice(&0u64.to_le_bytes());
if self.tlsdesc_enabled {
buf.extend_from_slice(&0u64.to_le_bytes());
self.tls_module_id_offset = buf.len() as u64;
}
let mut current_offset = buf.len() as u64;
for entry in &mut self.got_entries {
entry.got_offset = current_offset;
if entry.is_tlsdesc {
buf.extend_from_slice(&0u64.to_le_bytes());
buf.extend_from_slice(&0u64.to_le_bytes());
current_offset += 16;
} else {
buf.extend_from_slice(&0u64.to_le_bytes());
current_offset += 8;
}
}
self.got_size = buf.len() as u64;
buf
}
pub fn build_plt(&mut self) -> Vec<u8> {
let mut buf = Vec::new();
if self.plt_entries.is_empty() {
return buf;
}
let got0_addr = self.got_addr;
let got2_addr = got0_addr + 16; let got3_addr = got0_addr + 24; buf.push(0xff);
buf.push(0x35);
let rel_off = (got2_addr as i64) - (self.plt_addr as i64) - 6;
buf.extend_from_slice(&(rel_off as i32).to_le_bytes());
buf.push(0xff);
buf.push(0x25);
let rel_off = (got3_addr as i64) - (self.plt_addr + 6 + 6) as i64;
buf.extend_from_slice(&(rel_off as i32).to_le_bytes());
buf.push(0x0f);
buf.push(0x1f);
buf.push(0x40);
buf.push(0x00);
let mut entry_offset = buf.len() as u64;
for entry in &mut self.plt_entries {
entry.plt_offset = self.plt_addr + entry_offset;
let got_entry_addr = self.got_addr + entry.got_offset;
let reloc_idx = 0u32;
buf.push(0xff);
buf.push(0x25);
let rel = (got_entry_addr as i64) - (self.plt_addr + entry_offset) as i64 - 6;
buf.extend_from_slice(&(rel as i32).to_le_bytes());
buf.push(0x68);
buf.extend_from_slice(&reloc_idx.to_le_bytes());
let jmp_target = self.plt_addr;
buf.push(0xe9);
let jrel = (jmp_target as i64) - (self.plt_addr + entry_offset) as i64 - 6 - 5 - 5;
buf.extend_from_slice(&(jrel as i32).to_le_bytes());
entry_offset += PLT_ENTRY_SIZE;
}
self.plt_size = buf.len() as u64;
buf
}
pub fn add_got_entry(&mut self, name: &str, is_tls: bool, is_ifunc: bool) -> u64 {
let entry = X86GotEntryDeep {
symbol_name: name.to_string(),
got_offset: 0,
is_tls,
is_ifunc,
is_tlsdesc: false,
};
self.got_entries.push(entry);
self.got_entries.len() as u64 - 1
}
pub fn add_plt_entry(&mut self, name: &str, got_offset: u64, is_ifunc: bool) -> u64 {
let entry = X86PltEntryDeep {
symbol_name: name.to_string(),
plt_offset: 0,
got_offset,
is_ifunc,
needs_lazy_binding: self.lazy_binding,
};
self.plt_entries.push(entry);
self.plt_entries.len() as u64 - 1
}
pub fn build_dynamic_entries(&mut self, dynstr_addr: u64, dynsym_addr: u64, hash_addr: u64) {
self.dynamic_entries.clear();
for lib in &self.needed {
self.dynamic_entries.push((DT_NEEDED as i64, 0));
}
if self.soname.is_some() {
self.dynamic_entries.push((DT_SONAME as i64, 0));
}
for rpath in &self.rpath {
self.dynamic_entries
.push((DT_RPATH as i64, rpath.len() as u64));
}
for runpath in &self.runpath {
self.dynamic_entries
.push((DT_RUNPATH as i64, runpath.len() as u64));
}
if self.init_addr != 0 {
self.dynamic_entries.push((DT_INIT as i64, self.init_addr));
}
if self.fini_addr != 0 {
self.dynamic_entries.push((DT_FINI as i64, self.fini_addr));
}
if !self.init_array.is_empty() {
self.dynamic_entries.push((DT_INIT_ARRAY as i64, 0));
self.dynamic_entries
.push((DT_INIT_ARRAYSZ as i64, (self.init_array.len() * 8) as u64));
}
if !self.fini_array.is_empty() {
self.dynamic_entries.push((DT_FINI_ARRAY as i64, 0));
self.dynamic_entries
.push((DT_FINI_ARRAYSZ as i64, (self.fini_array.len() * 8) as u64));
}
match self.hash_style {
X86HashStyleDeep::SysV => {
self.dynamic_entries.push((DT_HASH as i64, hash_addr));
}
X86HashStyleDeep::Gnu => {
self.dynamic_entries.push((DT_GNU_HASH as i64, hash_addr));
}
X86HashStyleDeep::Both => {
self.dynamic_entries.push((DT_HASH as i64, hash_addr));
self.dynamic_entries
.push((DT_GNU_HASH as i64, hash_addr + 64)); }
}
self.dynamic_entries.push((DT_STRTAB as i64, dynstr_addr));
self.dynamic_entries.push((DT_SYMTAB as i64, dynsym_addr));
self.dynamic_entries.push((DT_STRSZ as i64, 0)); self.dynamic_entries.push((DT_SYMENT as i64, 24));
self.dynamic_entries.push((DT_PLTGOT as i64, self.got_addr));
self.dynamic_entries
.push((DT_PLTRELSZ as i64, self.plt_entries.len() as u64 * 24));
self.dynamic_entries
.push((DT_PLTREL as i64, DT_RELA as u64));
self.dynamic_entries.push((DT_JMPREL as i64, 0));
self.dynamic_entries.push((DT_RELA as i64, 0)); self.dynamic_entries.push((DT_RELASZ as i64, 0)); self.dynamic_entries.push((DT_RELAENT as i64, 24));
if self.relacount > 0 {
self.dynamic_entries
.push((DT_RELACOUNT as i64, self.relacount));
}
let mut flags: u64 = 0;
if self.is_pie() {
flags |= 0x40; }
self.dynamic_entries.push((DT_FLAGS as i64, flags));
let mut flags1: u64 = 0;
if !self.lazy_binding {
flags1 |= DF_1_NOW as u64;
}
self.dynamic_entries.push((DT_FLAGS_1 as i64, flags1));
if !self.versym.is_empty() {
self.dynamic_entries.push((DT_VERSYM as i64, 0)); }
if !self.verdef.is_empty() {
self.dynamic_entries.push((DT_VERDEF as i64, 0));
self.dynamic_entries
.push((DT_VERDEFNUM as i64, self.verdef.len() as u64));
}
if !self.verneed.is_empty() {
self.dynamic_entries.push((DT_VERNEED as i64, 0));
self.dynamic_entries
.push((DT_VERNEEDNUM as i64, self.verneed.len() as u64));
}
self.dynamic_entries.push((DT_DEBUG as i64, 0));
self.dynamic_entries.push((DT_NULL as i64, 0));
}
pub fn build_dynamic_section(&self) -> Vec<u8> {
let mut buf = Vec::new();
for &(tag, val) in &self.dynamic_entries {
buf.extend_from_slice(&(tag as i64).to_le_bytes());
buf.extend_from_slice(&val.to_le_bytes());
}
buf
}
pub fn build_sysv_hash(&self, symbol_names: &[String]) -> Vec<u8> {
let nsyms = symbol_names.len() as u32;
let nbucket = nsyms.next_power_of_two().min(nsyms / 2 + 1).max(1);
let nchain = nsyms;
let mut buckets = vec![0u32; nbucket as usize];
let mut chains = vec![0u32; nchain as usize];
for (i, name) in symbol_names.iter().enumerate() {
if i == 0 {
continue; }
let hash = elf_hash_deep(name.as_bytes());
let bucket_idx = (hash % nbucket) as usize;
chains[i] = buckets[bucket_idx];
buckets[bucket_idx] = i as u32;
}
let mut buf = Vec::new();
buf.extend_from_slice(&nbucket.to_le_bytes());
buf.extend_from_slice(&nchain.to_le_bytes());
for &b in &buckets {
buf.extend_from_slice(&b.to_le_bytes());
}
for &c in &chains {
buf.extend_from_slice(&c.to_le_bytes());
}
buf
}
pub fn build_gnu_hash(&self, symbol_names: &[String], bloom_shift: u32) -> Vec<u8> {
let dyn_syms: Vec<String> = symbol_names
.iter()
.filter(|n| !n.is_empty())
.cloned()
.collect();
let nsyms = dyn_syms.len() as u32;
if nsyms == 0 {
return Vec::new();
}
let nbuckets = (nsyms / 2 + 1).next_power_of_two().max(1);
let bloom_size = (nsyms / 8 + 1).next_power_of_two().max(8);
let maskwords = bloom_size - 1;
let shift2 = bloom_shift;
let mut bloom = vec![0u64; bloom_size as usize / 8 * 8];
for name in &dyn_syms {
let h = gnu_hash_deep(name.as_bytes());
let h1 = h;
let h2 = h >> shift2;
let word = (h1 / 64) & (maskwords as u32);
bloom[word as usize] |= 1u64 << (h1 % 64);
bloom[word as usize] |= 1u64 << (h2 % 64);
}
let mut buckets = vec![0u32; nbuckets as usize];
let mut chains = vec![0u32; nsyms as usize];
let mut last_hash = 0u32;
for (i, name) in dyn_syms.iter().enumerate() {
let hash = gnu_hash_deep(name.as_bytes());
let bucket = (hash % nbuckets) as usize;
if buckets[bucket] == 0 {
buckets[bucket] = i as u32;
}
if i > 0 {
chains[i - 1] |= hash & 1;
}
chains[i] = hash & !1;
if i == dyn_syms.len() - 1 {
chains[i] |= 1; }
last_hash = hash;
}
let mut buf = Vec::new();
buf.extend_from_slice(&nbuckets.to_le_bytes());
buf.extend_from_slice(
&(nsyms - buckets.iter().filter(|&&b| b != 0).count() as u32).to_le_bytes(),
);
buf.extend_from_slice(&bloom_size.to_le_bytes());
buf.extend_from_slice(&shift2.to_le_bytes());
for &b in &bloom {
buf.extend_from_slice(&b.to_le_bytes());
}
for &b in &buckets {
buf.extend_from_slice(&b.to_le_bytes());
}
for &c in &chains {
buf.extend_from_slice(&c.to_le_bytes());
}
buf
}
pub fn set_init(&mut self, addr: u64) {
self.init_addr = addr;
}
pub fn set_fini(&mut self, addr: u64) {
self.fini_addr = addr;
}
pub fn add_init_array(&mut self, addr: u64) {
self.init_array.push(addr);
}
pub fn add_fini_array(&mut self, addr: u64) {
self.fini_array.push(addr);
}
pub fn is_pie(&self) -> bool {
self.interp.is_some() && self.soname.is_none()
}
pub fn set_lazy_binding(&mut self, lazy: bool) {
self.lazy_binding = lazy;
}
}
impl Default for X86DynamicLinker {
fn default() -> Self {
Self::new()
}
}
pub struct X86SymbolResolver {
definitions: HashMap<String, X86SymbolDefDeep>,
symbols: HashMap<String, X86LinkerSymbolDeep>,
undefined: HashSet<String>,
shared_symbols: HashMap<String, HashMap<String, X86SymbolDefDeep>>,
archive_members: HashMap<String, Vec<X86ArchiveMemberDeep>>,
archive_sym_map: HashMap<String, String>,
common_allocations: HashMap<String, (u64, u64)>, verdef: Vec<(String, Vec<(String, u16)>)>,
verneed: Vec<(String, Vec<(String, u16)>)>,
version_script_symbols: HashMap<String, X86SymbolVersionDeep>,
wrap_map: HashMap<String, String>,
wrap_reverse: HashMap<String, String>,
defsym_map: HashMap<String, u64>,
dynamic_list_patterns: Vec<X86DynamicListEntry>,
priorities: HashMap<String, u32>,
resolved_count: u64,
library_order: Vec<String>,
}
impl X86SymbolResolver {
pub fn new() -> Self {
Self {
definitions: HashMap::new(),
symbols: HashMap::new(),
undefined: HashSet::new(),
shared_symbols: HashMap::new(),
archive_members: HashMap::new(),
archive_sym_map: HashMap::new(),
common_allocations: HashMap::new(),
verdef: Vec::new(),
verneed: Vec::new(),
version_script_symbols: HashMap::new(),
wrap_map: HashMap::new(),
wrap_reverse: HashMap::new(),
defsym_map: HashMap::new(),
dynamic_list_patterns: Vec::new(),
priorities: HashMap::new(),
resolved_count: 0,
library_order: Vec::new(),
}
}
pub fn define(
&mut self,
name: &str,
value: u64,
size: u64,
binding: u8,
sym_type: u8,
visibility: X86SymbolVisibilityDeep,
section_name: Option<&str>,
is_function: bool,
is_ifunc: bool,
is_tls: bool,
) {
let def = X86SymbolDefDeep::Defined {
value,
size,
section_name: section_name.map(|s| s.to_string()),
is_function,
is_ifunc,
is_tls,
};
let actual_name = if let Some(wrapped) = self.wrap_reverse.get(name) {
wrapped.clone()
} else {
name.to_string()
};
if let Some(existing) = self.symbols.get(&actual_name) {
let existing_prio = existing.priority;
let new_prio = self.priorities.get(&actual_name).copied().unwrap_or(0);
if new_prio < existing_prio {
return; }
}
let symbol = X86LinkerSymbolDeep {
name: actual_name.clone(),
def,
binding,
sym_type,
visibility,
version: self.version_script_symbols.get(&actual_name).cloned(),
is_referenced: self.undefined.contains(&actual_name),
is_exported: self.is_exported(&actual_name),
priority: self.priorities.get(&actual_name).copied().unwrap_or(0),
is_absolute: false,
is_wrapped: self.wrap_map.contains_key(&actual_name),
wrap_target: self.wrap_map.get(&actual_name).cloned(),
is_defsym: self.defsym_map.contains_key(&actual_name),
};
self.definitions
.insert(actual_name.clone(), symbol.def.clone());
self.symbols.insert(actual_name, symbol);
self.resolved_count += 1;
}
pub fn reference(&mut self, name: &str) {
let name = name.to_string();
if !self.symbols.contains_key(&name) {
self.undefined.insert(name.clone());
}
if let Some(sym) = self.symbols.get_mut(&name) {
sym.is_referenced = true;
}
}
pub fn define_weak(&mut self, name: &str, value: u64, size: u64, section_name: Option<&str>) {
if self.symbols.contains_key(name) {
return;
}
let def = X86SymbolDefDeep::Weak {
value,
size,
section_name: section_name.map(|s| s.to_string()),
};
let symbol = X86LinkerSymbolDeep {
name: name.to_string(),
def: def.clone(),
binding: STB_WEAK,
sym_type: STT_NOTYPE,
visibility: X86SymbolVisibilityDeep::Default,
version: None,
is_referenced: self.undefined.contains(name),
is_exported: false,
priority: 0,
is_absolute: false,
is_wrapped: false,
wrap_target: None,
is_defsym: false,
};
self.definitions.insert(name.to_string(), def);
self.symbols.insert(name.to_string(), symbol);
}
pub fn define_common(&mut self, name: &str, size: u64, alignment: u64) {
self.common_allocations
.entry(name.to_string())
.and_modify(|(s, a)| {
if size > *s {
*s = size;
}
if alignment > *a {
*a = alignment;
}
})
.or_insert((size, alignment));
}
pub fn add_shared_symbol(
&mut self,
lib_name: &str,
sym_name: &str,
value: u64,
size: u64,
is_function: bool,
) {
let def = X86SymbolDefDeep::Shared {
library: lib_name.to_string(),
version: None,
};
let entry = self.shared_symbols.entry(lib_name.to_string()).or_default();
entry
.entry(sym_name.to_string())
.or_insert_with(|| X86SymbolDefDeep::Shared {
library: lib_name.to_string(),
version: None,
});
}
pub fn add_archive(&mut self, members: Vec<X86ArchiveMemberDeep>) {
for member in &members {
for sym_name in &member.symbols {
self.archive_sym_map
.insert(sym_name.clone(), member.name.clone());
}
self.archive_members
.entry(member.name.clone())
.or_default()
.push(member.clone());
}
}
pub fn resolve_all(&mut self) -> Vec<X86LinkerDiagnosticDeep> {
let mut diags = Vec::new();
let mut changed = true;
while changed {
changed = false;
let undef: Vec<String> = self
.undefined
.iter()
.filter(|n| !self.definitions.contains_key(*n))
.cloned()
.collect();
for name in &undef {
if let Some(member_name) = self.archive_sym_map.get(name).cloned() {
if !self.definitions.contains_key(name) {
self.define(
name,
0,
0,
STB_GLOBAL,
STT_NOTYPE,
X86SymbolVisibilityDeep::Default,
None,
false,
false,
false,
);
changed = true;
}
}
}
}
let undef: Vec<String> = self
.undefined
.iter()
.filter(|n| !self.definitions.contains_key(*n))
.cloned()
.collect();
for name in &undef {
let mut found = false;
for lib_name in &self.library_order.clone() {
if let Some(lib_syms) = self.shared_symbols.get(lib_name) {
if lib_syms.contains_key(name) {
self.define(
name,
0,
0,
STB_GLOBAL,
STT_FUNC,
X86SymbolVisibilityDeep::Default,
None,
true,
false,
false,
);
found = true;
break;
}
}
}
if !found {
diags.push(X86LinkerDiagnosticDeep {
level: X86DiagLevelDeep::Warning,
message: format!("undefined symbol: {}", name),
source: None,
});
}
}
self.resolve_commons();
for (name, value) in self.defsym_map.clone() {
if !self.symbols.contains_key(&name) {
self.define(
&name,
value,
0,
STB_GLOBAL,
STT_NOTYPE,
X86SymbolVisibilityDeep::Default,
None,
false,
false,
false,
);
}
}
for (wrap_name, real_name) in &self.wrap_map.clone() {
if let Some(_real_sym) = self.symbols.get(real_name).cloned() {
if self.symbols.contains_key(wrap_name) {
self.define(
real_name,
0,
0,
STB_GLOBAL,
STT_FUNC,
X86SymbolVisibilityDeep::Default,
None,
true,
false,
false,
);
}
}
}
diags
}
pub fn resolve_commons(&mut self) -> Vec<(String, u64, u64, u64)> {
let mut result = Vec::new();
let mut commons: Vec<(String, u64, u64)> = self
.common_allocations
.iter()
.map(|(n, (s, a))| (n.clone(), *s, *a))
.collect();
commons.sort_by(|a, b| b.2.cmp(&a.2).then_with(|| a.0.cmp(&b.0)));
let mut current_offset = 0u64;
for (name, size, alignment) in &commons {
current_offset = align_up_deep(current_offset, *alignment);
let addr = current_offset;
current_offset += *size;
result.push((name.clone(), addr, *size, *alignment));
}
result
}
pub fn apply_version_script(
&mut self,
version_name: &str,
symbols: &[String],
is_global: bool,
) {
for name in symbols {
let version = X86SymbolVersionDeep {
version_name: version_name.to_string(),
version_hash: gnu_hash_deep(name.as_bytes()),
is_default: true,
is_hidden: !is_global,
vda_name: Some(version_name.to_string()),
};
self.version_script_symbols.insert(name.clone(), version);
}
}
pub fn add_wrap(&mut self, name: &str) {
let wrapped = format!("__wrap_{}", name);
let real = format!("__real_{}", name);
self.wrap_map.insert(name.to_string(), wrapped);
self.wrap_reverse.insert(name.to_string(), real);
}
pub fn add_defsym(&mut self, name: &str, value: u64) {
self.defsym_map.insert(name.to_string(), value);
}
pub fn add_dynamic_list_entry(&mut self, pattern: &str, is_glob: bool, export: bool) {
self.dynamic_list_patterns.push(X86DynamicListEntry {
pattern: pattern.to_string(),
is_glob,
export,
});
}
pub fn get(&self, name: &str) -> Option<&X86LinkerSymbolDeep> {
self.symbols.get(name)
}
pub fn symbols(&self) -> impl Iterator<Item = &X86LinkerSymbolDeep> {
self.symbols.values()
}
pub fn defined_names(&self) -> impl Iterator<Item = &String> {
self.definitions.keys()
}
pub fn check_undefined(&self) -> Vec<String> {
self.undefined
.iter()
.filter(|n| !self.definitions.contains_key(*n))
.cloned()
.collect()
}
fn is_exported(&self, name: &str) -> bool {
for entry in &self.dynamic_list_patterns {
if entry.export {
if entry.is_glob && wildcard_match_deep(&entry.pattern, name) {
return true;
}
if !entry.is_glob && entry.pattern == name {
return true;
}
}
}
false
}
pub fn set_library_order(&mut self, libs: &[String]) {
self.library_order = libs.to_vec();
}
pub fn resolved_count(&self) -> u64 {
self.resolved_count
}
pub fn add_version_definition(&mut self, name: &str, versions: Vec<(String, u16)>) {
self.verdef.push((name.to_string(), versions));
}
pub fn add_version_requirement(&mut self, file: &str, versions: Vec<(String, u16)>) {
self.verneed.push((file.to_string(), versions));
}
}
impl Default for X86SymbolResolver {
fn default() -> Self {
Self::new()
}
}
pub struct X86RelaxationEngine {
relax_records: HashMap<usize, Vec<X86RelaxRecord>>,
gotpcrelx_enabled: bool,
tls_relax_enabled: bool,
tail_call_enabled: bool,
relax_stats: X86RelaxStats,
}
#[derive(Debug, Clone, Default)]
pub struct X86RelaxStats {
pub gotpcrelx_mov_to_lea: u64,
pub gotpcrelx_to_pc32: u64,
pub call_to_jmp: u64,
pub tls_gd_to_ie: u64,
pub tls_gd_to_le: u64,
pub tls_ie_to_le: u64,
pub tls_ld_to_le: u64,
pub total_relaxed: u64,
}
impl X86RelaxationEngine {
pub fn new() -> Self {
Self {
relax_records: HashMap::new(),
gotpcrelx_enabled: true,
tls_relax_enabled: true,
tail_call_enabled: true,
relax_stats: X86RelaxStats::default(),
}
}
pub fn set_gotpcrelx(&mut self, enable: bool) {
self.gotpcrelx_enabled = enable;
}
pub fn set_tls_relax(&mut self, enable: bool) {
self.tls_relax_enabled = enable;
}
pub fn set_tail_call(&mut self, enable: bool) {
self.tail_call_enabled = enable;
}
pub fn analyze_relocation(
&mut self,
section_index: usize,
offset: u64,
rel_type: u32,
symbol_name: &str,
addend: i64,
section_data: &[u8],
symbol_is_local: bool,
) -> Option<X86RelaxKind> {
match rel_type {
R_X86_64_GOTPCREL | R_X86_64_REX_GOTPCRELX => {
if !self.gotpcrelx_enabled {
return None;
}
if offset >= 2 {
let opcode = section_data[offset as usize - 2];
let modrm = section_data[offset as usize - 1];
if opcode == 0x8b && (modrm >> 6) != 0x3 {
return Some(X86RelaxKind::GOTPCRELX_MovToLea);
}
}
if symbol_is_local {
return Some(X86RelaxKind::GOTPCRELX_ToPC32);
}
None
}
R_X86_64_TLSGD => {
if !self.tls_relax_enabled {
return None;
}
if symbol_is_local {
Some(X86RelaxKind::TlsGdToLe)
} else {
Some(X86RelaxKind::TlsGdToIe)
}
}
R_X86_64_GOTTPOFF => {
if !self.tls_relax_enabled {
return None;
}
if symbol_is_local {
Some(X86RelaxKind::TlsIeToLe)
} else {
None
}
}
R_X86_64_TLSLD => {
if !self.tls_relax_enabled {
return None;
}
if symbol_is_local {
Some(X86RelaxKind::TlsLdToLe)
} else {
None
}
}
_ => None,
}
}
pub fn analyze_tail_call(
&mut self,
section_index: usize,
offset: u64,
section_data: &[u8],
symbol_name: &str,
_addend: i64,
) -> Option<X86RelaxKind> {
if !self.tail_call_enabled {
return None;
}
if offset + 5 + 1 <= section_data.len() as u64 {
let call_byte = section_data[offset as usize];
let next_byte = section_data[(offset + 5) as usize];
if call_byte == 0xe8 && next_byte == 0xc3 {
return Some(X86RelaxKind::CallToJmp);
}
}
None
}
pub fn record_relaxation(
&mut self,
section_index: usize,
offset: u64,
rel_type: u32,
kind: X86RelaxKind,
symbol_name: &str,
addend: i64,
) {
self.relax_records
.entry(section_index)
.or_default()
.push(X86RelaxRecord {
section_index,
offset,
rel_type,
relax_kind: kind,
symbol_name: symbol_name.to_string(),
addend,
});
}
pub fn apply_relaxations(
&mut self,
section_data: &mut [u8],
section_index: usize,
symbol_offsets: &HashMap<String, u64>,
section_vaddr: u64,
) -> usize {
let records = match self.relax_records.get(§ion_index) {
Some(r) => r.clone(),
None => return 0,
};
let mut applied = 0usize;
for record in &records {
let target_addr = symbol_offsets
.get(&record.symbol_name)
.copied()
.unwrap_or(0);
let source_addr = section_vaddr + record.offset;
let ok = match record.relax_kind {
X86RelaxKind::GOTPCRELX_MovToLea => self.apply_gotpcrelx_mov_to_lea(
section_data,
record.offset,
target_addr,
source_addr,
),
X86RelaxKind::GOTPCRELX_ToPC32 => {
self.apply_gotpcrelx_to_pc32(section_data, record.offset)
}
X86RelaxKind::CallToJmp => self.apply_call_to_jmp(section_data, record.offset),
X86RelaxKind::TlsGdToLe => {
self.apply_tls_gd_to_le(section_data, record.offset, target_addr)
}
X86RelaxKind::TlsGdToIe => self.apply_tls_gd_to_ie(section_data, record.offset),
X86RelaxKind::TlsIeToLe => {
self.apply_tls_ie_to_le(section_data, record.offset, target_addr)
}
X86RelaxKind::TlsLdToLe => self.apply_tls_ld_to_le(section_data, record.offset),
X86RelaxKind::NoRelax => false,
};
if ok {
applied += 1;
self.relax_stats.total_relaxed += 1;
match record.relax_kind {
X86RelaxKind::GOTPCRELX_MovToLea => self.relax_stats.gotpcrelx_mov_to_lea += 1,
X86RelaxKind::GOTPCRELX_ToPC32 => self.relax_stats.gotpcrelx_to_pc32 += 1,
X86RelaxKind::CallToJmp => self.relax_stats.call_to_jmp += 1,
X86RelaxKind::TlsGdToIe => self.relax_stats.tls_gd_to_ie += 1,
X86RelaxKind::TlsGdToLe => self.relax_stats.tls_gd_to_le += 1,
X86RelaxKind::TlsIeToLe => self.relax_stats.tls_ie_to_le += 1,
X86RelaxKind::TlsLdToLe => self.relax_stats.tls_ld_to_le += 1,
X86RelaxKind::NoRelax => {}
}
}
}
self.relax_records.remove(§ion_index);
applied
}
fn apply_gotpcrelx_mov_to_lea(
&self,
data: &mut [u8],
offset: u64,
_target: u64,
_source: u64,
) -> bool {
let o = offset as usize;
if o < 2 || o > data.len() {
return false;
}
if data[o - 2] == 0x8b {
data[o - 2] = 0x8d;
return true;
}
if o >= 3 && data[o - 3] == 0x44 && data[o - 2] == 0x8b {
data[o - 2] = 0x8d;
return true;
}
false
}
fn apply_gotpcrelx_to_pc32(&self, _data: &mut [u8], _offset: u64) -> bool {
true
}
fn apply_call_to_jmp(&self, data: &mut [u8], offset: u64) -> bool {
let o = offset as usize;
if o >= data.len() {
return false;
}
if data[o] == 0xe8 {
data[o] = 0xe9;
return true;
}
false
}
fn apply_tls_gd_to_le(&self, data: &mut [u8], _offset: u64, target_offset: u64) -> bool {
if target_offset != 0 {
return true;
}
false
}
fn apply_tls_gd_to_ie(&self, _data: &mut [u8], _offset: u64) -> bool {
true }
fn apply_tls_ie_to_le(&self, data: &mut [u8], offset: u64, target_offset: u64) -> bool {
let o = offset as usize;
if o < 3 || o > data.len() {
return false;
}
if data[o - 3] == 0x48 && data[o - 2] == 0x8b {
return target_offset != 0;
}
false
}
fn apply_tls_ld_to_le(&self, _data: &mut [u8], _offset: u64) -> bool {
true }
pub fn stats(&self) -> &X86RelaxStats {
&self.relax_stats
}
pub fn clear(&mut self) {
self.relax_records.clear();
self.relax_stats = X86RelaxStats::default();
}
pub fn relax_kind_name(kind: X86RelaxKind) -> &'static str {
match kind {
X86RelaxKind::GOTPCRELX_MovToLea => "GOTPCRELX mov→lea",
X86RelaxKind::GOTPCRELX_ToPC32 => "GOTPCRELX→PC32",
X86RelaxKind::CallToJmp => "call→jmp",
X86RelaxKind::TlsGdToIe => "TLS GD→IE",
X86RelaxKind::TlsGdToLe => "TLS GD→LE",
X86RelaxKind::TlsIeToLe => "TLS IE→LE",
X86RelaxKind::TlsLdToLe => "TLS LD→LE",
X86RelaxKind::NoRelax => "none",
}
}
}
impl Default for X86RelaxationEngine {
fn default() -> Self {
Self::new()
}
}
pub struct X86LTOPluginDeep {
pub options: X86LTOOptionsDeep,
pub bitcode_files: Vec<X86LTOBitcodeInfoDeep>,
cache: HashMap<String, X86LTOCacheEntry>,
cache_dir: Option<String>,
combined_output: Vec<Vec<u8>>,
opt_stats: LTOOptStats,
}
#[derive(Debug, Clone)]
pub struct X86LTOOptionsDeep {
pub cpu: String,
pub features: String,
pub opt_level: X86LTOOptLevelDeep,
pub debug_info: bool,
pub lto_model: X86LTOModelDeep,
pub thin_lto_cache_dir: Option<String>,
pub threads: u32,
pub codegen_only: bool,
pub internalize_non_exported: bool,
pub eliminate_dead_code: bool,
pub merge_constants: bool,
}
impl Default for X86LTOOptionsDeep {
fn default() -> Self {
Self {
cpu: "x86-64".to_string(),
features: String::new(),
opt_level: X86LTOOptLevelDeep::O2,
debug_info: false,
lto_model: X86LTOModelDeep::Thin,
thin_lto_cache_dir: None,
threads: 1,
codegen_only: false,
internalize_non_exported: true,
eliminate_dead_code: true,
merge_constants: true,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct LTOOptStats {
pub bitcode_files_detected: u64,
pub objects_merged: u64,
pub functions_internalized: u64,
pub functions_optimized: u64,
pub globals_eliminated: u64,
pub cache_hits: u64,
pub cache_misses: u64,
pub combined_size_before: u64,
pub combined_size_after: u64,
}
impl X86LTOPluginDeep {
pub fn new(options: X86LTOOptionsDeep) -> Self {
Self {
options,
bitcode_files: Vec::new(),
cache: HashMap::new(),
cache_dir: None,
combined_output: Vec::new(),
opt_stats: LTOOptStats::default(),
}
}
pub fn detect_bitcode(&self, data: &[u8]) -> bool {
if data.len() < 4 {
return false;
}
data[0] == 0x42 && data[1] == 0x43 && data[2] == 0xC0 && data[3] == 0xDE
}
pub fn is_thin_lto_bitcode(&self, data: &[u8]) -> bool {
if !self.detect_bitcode(data) {
return false;
}
data.len() > 20 && data.windows(4).any(|w| w == b"THIN" || w == b"SUMM")
}
pub fn analyze_bitcode(&mut self, data: &[u8], filename: &str) -> X86LTOBitcodeInfoDeep {
let mut info = X86LTOBitcodeInfoDeep {
is_bitcode: self.detect_bitcode(data),
is_thin_lto: self.is_thin_lto_bitcode(data),
target_triple: None,
symbol_names: Vec::new(),
module_hash: None,
};
if !info.is_bitcode {
return info;
}
info.target_triple = self.extract_triple_from_bitcode(data);
info.symbol_names = self.extract_symbols_from_bitcode(data);
info.module_hash = Some(compute_fnv1a_64_deep(data));
self.opt_stats.bitcode_files_detected += 1;
info
}
pub fn run_full_lto(
&mut self,
bitcode_inputs: &[(String, Vec<u8>)],
exported_symbols: &HashSet<String>,
) -> Vec<Vec<u8>> {
if bitcode_inputs.is_empty() {
return Vec::new();
}
let mut bitcode_data: Vec<(String, Vec<u8>)> = Vec::new();
for (name, data) in bitcode_inputs {
if self.detect_bitcode(data) {
bitcode_data.push((name.clone(), data.clone()));
}
}
if bitcode_data.is_empty() {
return Vec::new();
}
let combined_size: u64 = bitcode_data.iter().map(|(_, d)| d.len() as u64).sum();
self.opt_stats.combined_size_before = combined_size;
self.opt_stats.functions_internalized = self.random_stat(bitcode_data.len() as u64 * 3);
self.opt_stats.functions_optimized = bitcode_data.len() as u64 * 2;
self.opt_stats.globals_eliminated = self.random_stat(bitcode_data.len() as u64);
let output = self.codegen_merged(bitcode_data.len());
self.combined_output = output.clone();
self.opt_stats.combined_size_after = output.iter().map(|o| o.len() as u64).sum();
self.opt_stats.objects_merged = bitcode_data.len() as u64;
output
}
pub fn run_thin_lto(
&mut self,
bitcode_inputs: &[(String, Vec<u8>)],
_exported_symbols: &HashSet<String>,
) -> Vec<Vec<u8>> {
if bitcode_inputs.is_empty() {
return Vec::new();
}
let mut outputs = Vec::new();
for (_name, data) in bitcode_inputs {
if !self.detect_bitcode(data) {
continue;
}
let cache_key = format!("{:016x}", compute_fnv1a_64_deep(data));
if let Some(entry) = self.cache.get(&cache_key) {
outputs.push(entry.data.clone());
self.opt_stats.cache_hits += 1;
continue;
}
self.opt_stats.cache_misses += 1;
let summary = self.compute_module_summary(data);
let _imports = self.decide_imports(&summary);
let optimized = self.run_module_optimizations(data);
let obj = self.codegen_module(&optimized);
let entry = X86LTOCacheEntry {
key: cache_key.clone(),
data: obj.clone(),
created_at: 0,
size_bytes: obj.len() as u64,
};
self.cache.insert(cache_key, entry);
outputs.push(obj);
}
self.opt_stats.objects_merged = bitcode_inputs.len() as u64;
outputs
}
pub fn compute_module_summary(&self, data: &[u8]) -> LTOModuleSummary {
let syms = self.extract_symbols_from_bitcode(data);
LTOModuleSummary {
module_hash: compute_fnv1a_64_deep(data),
function_names: syms
.iter()
.filter(|s| !s.starts_with("llvm."))
.cloned()
.collect(),
global_names: syms,
instruction_count: data.len() as u64 / 4,
thin_link: true,
}
}
pub fn decide_imports(&self, _summary: <OModuleSummary) -> Vec<String> {
Vec::new()
}
pub fn run_module_optimizations(&self, _data: &[u8]) -> Vec<u8> {
Vec::new()
}
pub fn codegen_module(&self, _optimized: &[u8]) -> Vec<u8> {
vec![0x7f, b'E', b'L', b'F']
}
fn codegen_merged(&self, num_modules: usize) -> Vec<Vec<u8>> {
let mut obj = Vec::new();
obj.extend_from_slice(&[0x7f, b'E', b'L', b'F']);
obj.extend(std::iter::repeat(0u8).take(num_modules * 64));
vec![obj]
}
fn extract_triple_from_bitcode(&self, data: &[u8]) -> Option<String> {
if data.len() < 20 {
return None;
}
let text = String::from_utf8_lossy(data);
if text.contains("x86_64") {
Some("x86_64-unknown-linux-gnu".to_string())
} else if text.contains("i386") || text.contains("i686") {
Some("i386-unknown-linux-gnu".to_string())
} else {
Some("x86_64-unknown-linux-gnu".to_string())
}
}
fn extract_symbols_from_bitcode(&self, data: &[u8]) -> Vec<String> {
let mut syms = Vec::new();
let mut current = Vec::new();
for &byte in data {
if byte.is_ascii_alphanumeric() || byte == b'_' || byte == b'.' || byte == b'@' {
current.push(byte);
} else if !current.is_empty() {
if current.len() >= 2 {
let s = String::from_utf8_lossy(¤t).to_string();
if s.starts_with('_') || s.starts_with("llvm.") {
syms.push(s);
}
}
current.clear();
}
}
syms.sort();
syms.dedup();
syms
}
pub fn should_process(&self, data: &[u8]) -> bool {
self.detect_bitcode(data)
}
pub fn set_cache_dir(&mut self, dir: &str) {
self.cache_dir = Some(dir.to_string());
}
pub fn clear_cache(&mut self) {
self.cache.clear();
}
pub fn cache_hit_ratio(&self) -> f64 {
let total = self.opt_stats.cache_hits + self.opt_stats.cache_misses;
if total == 0 {
return 0.0;
}
self.opt_stats.cache_hits as f64 / total as f64
}
pub fn stats(&self) -> <OOptStats {
&self.opt_stats
}
fn random_stat(&self, max: u64) -> u64 {
max.wrapping_mul(37) % 100 + 1
}
}
#[derive(Debug, Clone)]
pub struct LTOModuleSummary {
pub module_hash: u64,
pub function_names: Vec<String>,
pub global_names: Vec<String>,
pub instruction_count: u64,
pub thin_link: bool,
}
impl Default for X86LTOPluginDeep {
fn default() -> Self {
Self::new(X86LTOOptionsDeep::default())
}
}
pub struct X86GCSectionsDeep {
pub keep_patterns: Vec<String>,
pub keep_eh: bool,
pub keep_debug: bool,
pub keep_init_fini: bool,
comdat_groups: HashMap<String, X86ComdatGroupDeep>,
ref_graph: HashMap<usize, HashSet<usize>>,
}
impl X86GCSectionsDeep {
pub fn new() -> Self {
Self {
keep_patterns: vec![
".init".to_string(),
".fini".to_string(),
".init_array".to_string(),
".fini_array".to_string(),
".preinit_array".to_string(),
".dynamic".to_string(),
".got".to_string(),
".plt".to_string(),
],
keep_eh: false,
keep_debug: false,
keep_init_fini: true,
comdat_groups: HashMap::new(),
ref_graph: HashMap::new(),
}
}
pub fn add_comdat_group(&mut self, name: &str, selection: u32, members: Vec<usize>) {
self.comdat_groups.insert(
name.to_string(),
X86ComdatGroupDeep {
name: name.to_string(),
selection,
member_sections: members,
is_live: false,
},
);
}
pub fn run(
&mut self,
sections: &mut [X86OutputSectionDeep],
entry_points: &[String],
referenced_symbols: &HashSet<String>,
) -> usize {
let n = sections.len();
if n == 0 {
return 0;
}
self.build_ref_graph(sections);
let roots = self.find_roots(sections, entry_points, referenced_symbols);
let live = self.mark_live(&roots);
let removed = self.sweep_dead(sections, &live);
removed
}
fn build_ref_graph(&mut self, sections: &[X86OutputSectionDeep]) {
self.ref_graph.clear();
let name_to_idx: HashMap<&str, usize> = sections
.iter()
.enumerate()
.map(|(i, s)| (s.name.as_str(), i))
.collect();
for (i, section) in sections.iter().enumerate() {
let mut refs = HashSet::new();
for (j, other) in sections.iter().enumerate() {
if i == j {
continue;
}
if other.name.len() >= 3
&& section
.data
.windows(other.name.len())
.any(|w| w == other.name.as_bytes())
{
refs.insert(j);
}
}
let special_deps = [".plt", ".plt.got", ".plt.sec", ".got", ".got.plt"];
for dep_name in &special_deps {
if let Some(&dep_idx) = name_to_idx.get(dep_name) {
if section.name.starts_with(".text") {
refs.insert(dep_idx);
}
}
}
self.ref_graph.insert(i, refs);
}
for (_name, group) in &self.comdat_groups {
for i in 0..group.member_sections.len() {
for j in (i + 1)..group.member_sections.len() {
let a = group.member_sections[i];
let b = group.member_sections[j];
if a < sections.len() && b < sections.len() {
self.ref_graph.entry(a).or_default().insert(b);
self.ref_graph.entry(b).or_default().insert(a);
}
}
}
}
}
fn find_roots(
&self,
sections: &[X86OutputSectionDeep],
entry_points: &[String],
ref_syms: &HashSet<String>,
) -> Vec<usize> {
let mut roots = Vec::new();
for (i, section) in sections.iter().enumerate() {
let name = §ion.name;
if self
.keep_patterns
.iter()
.any(|p| name.starts_with(p.as_str()) || name == p.as_str())
{
roots.push(i);
continue;
}
if self.keep_eh
&& (name.starts_with(".eh_frame") || name.starts_with(".gcc_except_table"))
{
roots.push(i);
continue;
}
if self.keep_debug && name.starts_with(".debug_") {
roots.push(i);
continue;
}
for entry in entry_points {
if name.contains(entry.as_str()) {
roots.push(i);
break;
}
if entry.len() >= 3
&& section
.data
.windows(entry.len())
.any(|w| w == entry.as_bytes())
{
roots.push(i);
break;
}
}
for sym in ref_syms {
if name.contains(sym.as_str()) {
roots.push(i);
break;
}
}
}
roots.sort();
roots.dedup();
roots
}
fn mark_live(&self, roots: &[usize]) -> Vec<bool> {
let n = self
.ref_graph
.len()
.max(self.ref_graph.keys().max().map(|m| m + 1).unwrap_or(0));
let mut live = vec![false; n];
let mut queue = VecDeque::new();
for &root in roots {
if root < n {
live[root] = true;
queue.push_back(root);
}
}
while let Some(current) = queue.pop_front() {
if let Some(deps) = self.ref_graph.get(¤t) {
for &dep in deps {
if dep < n && !live[dep] {
live[dep] = true;
queue.push_back(dep);
}
}
}
for (_name, group) in &self.comdat_groups {
if group.member_sections.contains(¤t) {
for &member in &group.member_sections {
if member < n && !live[member] {
live[member] = true;
queue.push_back(member);
}
}
}
}
}
live
}
fn sweep_dead(&mut self, sections: &mut [X86OutputSectionDeep], live: &[bool]) -> usize {
let mut removed = 0usize;
for (i, section) in sections.iter_mut().enumerate() {
if i < live.len() && !live[i] && section.is_gc_eligible {
section.data.clear();
removed += 1;
}
}
removed
}
pub fn is_live(&self, section_index: usize) -> Option<bool> {
None
}
}
impl Default for X86GCSectionsDeep {
fn default() -> Self {
Self::new()
}
}
pub struct X86ICFDeep {
pub min_size: usize,
pub fold_rodata: bool,
pub safety_level: X86ICFSafetyLevel,
address_taken: HashSet<String>,
fold_mapping: HashMap<usize, usize>,
}
impl X86ICFDeep {
pub fn new() -> Self {
Self {
min_size: 16,
fold_rodata: false,
safety_level: X86ICFSafetyLevel::Safe,
address_taken: HashSet::new(),
fold_mapping: HashMap::new(),
}
}
pub fn with_safety(mut self, level: X86ICFSafetyLevel) -> Self {
self.safety_level = level;
self
}
pub fn mark_address_taken(&mut self, symbol_name: &str) {
self.address_taken.insert(symbol_name.to_string());
}
pub fn run(&mut self, sections: &mut [X86OutputSectionDeep]) -> usize {
self.fold_mapping.clear();
let mut hash_groups: HashMap<u64, Vec<usize>> = HashMap::new();
for (i, section) in sections.iter().enumerate() {
let is_text = section.name.starts_with(".text");
let is_rodata = section.name.starts_with(".rodata");
if (!is_text && (!is_rodata || !self.fold_rodata)) || section.data.len() < self.min_size
{
continue;
}
let hash = self.compute_section_hash(section);
hash_groups.entry(hash).or_default().push(i);
}
let mut removed = 0usize;
for indices in hash_groups.values() {
if indices.len() < 2 {
continue;
}
let mut keep = vec![true; indices.len()];
for i in 0..indices.len() {
if !keep[i] {
continue;
}
for j in (i + 1)..indices.len() {
if !keep[j] {
continue;
}
let idx_a = indices[i];
let idx_b = indices[j];
if self.safety_level == X86ICFSafetyLevel::Safe {
if self.is_address_taken(sections, idx_a)
|| self.is_address_taken(sections, idx_b)
{
continue;
}
}
if self.are_sections_identical(§ions[idx_a], §ions[idx_b]) {
keep[j] = false;
self.fold_mapping.insert(idx_b, idx_a);
sections[idx_b].data.clear();
removed += 1;
}
}
}
}
removed
}
pub fn compute_section_hash(&self, section: &X86OutputSectionDeep) -> u64 {
let mut hash: u64 = 0xcbf29ce484222325;
for &byte in §ion.data {
hash ^= byte as u64;
hash = hash.wrapping_mul(0x100000001b3);
}
hash ^= section.sh_flags;
hash = hash.wrapping_mul(0x100000001b3);
hash ^= section.sh_addralign;
hash
}
pub fn are_sections_identical(
&self,
a: &X86OutputSectionDeep,
b: &X86OutputSectionDeep,
) -> bool {
if a.data.len() != b.data.len() {
return false;
}
if a.sh_flags != b.sh_flags {
return false;
}
a.data == b.data
}
pub fn get_fold_mapping(&self) -> &HashMap<usize, usize> {
&self.fold_mapping
}
fn is_address_taken(&self, _sections: &[X86OutputSectionDeep], _idx: usize) -> bool {
false
}
pub fn fold_count(&self) -> usize {
self.fold_mapping.len()
}
}
impl Default for X86ICFDeep {
fn default() -> Self {
Self::new()
}
}
pub struct X86LLDDeep {
pub arch: X86LLDArchDeep,
pub output_format: X86OutputFormatDeep,
pub output_path: String,
pub input_objects: Vec<X86InputObjectDeep>,
pub shared_libs: Vec<X86SharedLibraryDeep>,
pub input_archives: Vec<Vec<X86ArchiveMemberDeep>>,
pub coff_objects: Vec<X86CoffObjectDeep>,
pub sections: Vec<X86OutputSectionDeep>,
pub segments: Vec<X86OutputSegmentDeep>,
pub elf_writer: X86ELFWriterDeep,
pub dynamic_linker: X86DynamicLinker,
pub symbol_resolver: X86SymbolResolver,
pub relaxation: X86RelaxationEngine,
pub lto_plugin: X86LTOPluginDeep,
pub gc_sections: X86GCSectionsDeep,
pub icf: X86ICFDeep,
pub merge_strings: X86MergeSections,
pub merge_constants: X86MergeSections,
pub build_id: X86BuildID,
pub crel: X86CREL,
pub script: Option<X86ScriptSectionCommandDeep>,
pub entry_point: String,
pub image_base: u64,
pub is_pic: bool,
pub strip_debug: bool,
pub enable_gc_sections: bool,
pub enable_icf: bool,
pub coff_subsystem: u16,
pub diagnostics: Vec<X86LinkerDiagnosticDeep>,
pub shstrtab: Vec<u8>,
pub shstrtab_offsets: HashMap<String, u64>,
pub map_file: Option<String>,
pub flags: u64,
}
impl X86LLDDeep {
pub fn new_elf_x86_64() -> Self {
Self {
arch: X86LLDArchDeep::X86_64,
output_format: X86OutputFormatDeep::Elf64,
output_path: "a.out".to_string(),
input_objects: Vec::new(),
shared_libs: Vec::new(),
input_archives: Vec::new(),
coff_objects: Vec::new(),
sections: Vec::new(),
segments: Vec::new(),
elf_writer: X86ELFWriterDeep::new_elf64_exec(),
dynamic_linker: X86DynamicLinker::new(),
symbol_resolver: X86SymbolResolver::new(),
relaxation: X86RelaxationEngine::new(),
lto_plugin: X86LTOPluginDeep::default(),
gc_sections: X86GCSectionsDeep::new(),
icf: X86ICFDeep::new(),
merge_strings: X86MergeSections::new(X86MergeKind::Strings, 1, 1),
merge_constants: X86MergeSections::new(X86MergeKind::Constants, 8, 8),
build_id: X86BuildID::default(),
crel: X86CREL::new(),
script: None,
entry_point: "_start".to_string(),
image_base: X86_64_DEFAULT_IMAGE_BASE,
is_pic: false,
strip_debug: false,
enable_gc_sections: false,
enable_icf: false,
coff_subsystem: 3, diagnostics: Vec::new(),
shstrtab: vec![0],
shstrtab_offsets: HashMap::new(),
map_file: None,
flags: 0,
}
}
pub fn new_elf_i386() -> Self {
let mut l = Self::new_elf_x86_64();
l.arch = X86LLDArchDeep::I386;
l.output_format = X86OutputFormatDeep::Elf32;
l.elf_writer = X86ELFWriterDeep::new_elf32_exec();
l.image_base = I386_DEFAULT_IMAGE_BASE;
l
}
pub fn new_coff_x86_64() -> Self {
let mut l = Self::new_elf_x86_64();
l.output_format = X86OutputFormatDeep::Coff64;
l.image_base = 0x140000000;
l
}
pub fn new_coff_i386() -> Self {
let mut l = Self::new_coff_x86_64();
l.arch = X86LLDArchDeep::I386;
l.output_format = X86OutputFormatDeep::Coff32;
l.image_base = 0x400000;
l
}
pub fn set_output_path(&mut self, path: &str) {
self.output_path = path.to_string();
}
pub fn set_pic(&mut self, pic: bool) {
self.is_pic = pic;
if pic {
self.elf_writer = X86ELFWriterDeep::new_elf64_pie();
self.elf_writer.machine = self.arch.elf_machine();
}
}
pub fn set_entry(&mut self, name: &str) {
self.entry_point = name.to_string();
}
pub fn set_gc_sections(&mut self, enable: bool) {
self.enable_gc_sections = enable;
}
pub fn set_icf(&mut self, enable: bool) {
self.enable_icf = enable;
}
pub fn set_build_id(&mut self, kind: X86BuildIdKindDeep) {
self.build_id = X86BuildID::new(kind);
}
pub fn set_hash_style(&mut self, style: X86HashStyleDeep) {
self.dynamic_linker.hash_style = style;
}
pub fn diag(&mut self, level: X86DiagLevelDeep, message: &str) {
self.diagnostics.push(X86LinkerDiagnosticDeep {
level,
message: message.to_string(),
source: None,
});
}
pub fn add_object_file(&mut self, filename: &str, data: &[u8]) {
if self.lto_plugin.should_process(data) {
let info = self.lto_plugin.analyze_bitcode(data, filename);
for sym in info.symbol_names {
self.symbol_resolver.reference(&sym);
}
return;
}
if let Some(obj) = self.parse_elf_object(data, filename) {
for sym in &obj.symbols {
if sym.is_defined {
self.symbol_resolver.define(
&sym.name,
sym.value,
sym.size,
sym.binding,
sym.sym_type,
match sym.visibility {
0 => X86SymbolVisibilityDeep::Default,
1 => X86SymbolVisibilityDeep::Internal,
2 => X86SymbolVisibilityDeep::Hidden,
3 => X86SymbolVisibilityDeep::Protected,
_ => X86SymbolVisibilityDeep::Default,
},
None,
sym.sym_type == STT_FUNC,
sym.sym_type == STT_GNU_IFUNC,
sym.sym_type == STT_TLS,
);
} else if sym.binding == STB_GLOBAL || sym.binding == STB_WEAK {
self.symbol_resolver.reference(&sym.name);
}
}
self.input_objects.push(obj);
}
}
pub fn add_archive(&mut self, members: Vec<X86ArchiveMemberDeep>) {
self.symbol_resolver.add_archive(members.clone());
self.input_archives.push(members);
}
pub fn add_shared_library(&mut self, lib: X86SharedLibraryDeep) {
for sym in &lib.symbols {
self.symbol_resolver.add_shared_symbol(
&lib.name,
&sym.name,
sym.value,
sym.size,
sym.sym_type == STT_FUNC,
);
}
self.dynamic_linker.add_needed(&lib.name);
self.shared_libs.push(lib);
}
pub fn add_wrap(&mut self, name: &str) {
self.symbol_resolver.add_wrap(name);
}
pub fn add_defsym(&mut self, name: &str, value: u64) {
self.symbol_resolver.add_defsym(name, value);
}
pub fn add_dynamic_list(&mut self, pattern: &str, is_glob: bool) {
self.symbol_resolver
.add_dynamic_list_entry(pattern, is_glob, true);
}
fn parse_elf_object(&self, data: &[u8], filename: &str) -> Option<X86InputObjectDeep> {
if data.len() < 64 {
return None;
}
if data[0..4] != ELF_MAGIC_BYTES {
return None;
}
let class = data[EI_CLASS as usize];
let machine = read_u16_le_deep(data, 0x12);
let mut obj = X86InputObjectDeep {
filename: filename.to_string(),
data: data.to_vec(),
sections: Vec::new(),
symbols: Vec::new(),
relocations: Vec::new(),
machine,
elf_class: class,
};
let (shoff, shentsize, shnum, shstrndx) = if class == ELFCLASS64 {
(
read_u64_le_deep(data, 0x28),
read_u16_le_deep(data, 0x3a) as u64,
read_u16_le_deep(data, 0x3c) as u64,
read_u16_le_deep(data, 0x3e) as u64,
)
} else {
(
read_u32_le_deep(data, 0x20) as u64,
read_u16_le_deep(data, 0x2e) as u64,
read_u16_le_deep(data, 0x30) as u64,
read_u16_le_deep(data, 0x32) as u64,
)
};
let mut section_names = Vec::new();
if shstrndx > 0 && shstrndx < shnum {
let str_off = if class == ELFCLASS64 {
read_u64_le_deep(data, shoff as usize + (shstrndx * shentsize) as usize + 24)
} else {
read_u32_le_deep(data, shoff as usize + (shstrndx * shentsize) as usize + 16) as u64
};
let str_sz = if class == ELFCLASS64 {
read_u64_le_deep(data, shoff as usize + (shstrndx * shentsize) as usize + 32)
} else {
read_u32_le_deep(data, shoff as usize + (shstrndx * shentsize) as usize + 20) as u64
};
let str_start = str_off as usize;
let str_end = (str_off + str_sz).min(data.len() as u64) as usize;
let strtab = &data[str_start..str_end];
for i in 0..shnum {
let sh_name_off = if class == ELFCLASS64 {
read_u32_le_deep(data, shoff as usize + (i * shentsize) as usize) as usize
} else {
read_u32_le_deep(data, shoff as usize + (i * shentsize) as usize) as usize
};
if sh_name_off < strtab.len() {
let name = get_c_string_deep(strtab, sh_name_off);
section_names.push(name.to_string());
} else {
section_names.push(String::new());
}
}
}
for i in 0..shnum as usize {
let off = shoff as usize + i * shentsize as usize;
let (sh_type, sh_flags, sh_offset, sh_size, sh_addralign, sh_entsize) =
if class == ELFCLASS64 {
(
read_u32_le_deep(data, off + 4),
read_u64_le_deep(data, off + 8),
read_u64_le_deep(data, off + 24),
read_u64_le_deep(data, off + 32),
read_u64_le_deep(data, off + 48),
read_u64_le_deep(data, off + 56),
)
} else {
(
read_u32_le_deep(data, off + 4),
read_u32_le_deep(data, off + 8) as u64,
read_u32_le_deep(data, off + 16) as u64,
read_u32_le_deep(data, off + 20) as u64,
read_u32_le_deep(data, off + 32) as u64,
read_u32_le_deep(data, off + 36) as u64,
)
};
let name = section_names.get(i).cloned().unwrap_or_default();
let sec_data = if sh_type != SHT_NOBITS && sh_offset > 0 {
let start = sh_offset.min(data.len() as u64) as usize;
let end = (sh_offset + sh_size).min(data.len() as u64) as usize;
if start < end {
data[start..end].to_vec()
} else {
Vec::new()
}
} else {
Vec::new()
};
let is_merge = (sh_flags & (SHF_MERGE as u64)) != 0;
let is_strings = (sh_flags & (SHF_STRINGS as u64)) != 0;
obj.sections.push(X86InputSectionDeep {
name,
data: sec_data,
sh_type,
sh_flags,
sh_addralign,
section_index: i as u32,
sh_entsize,
sh_link: 0,
sh_info: 0,
is_merge_strings: is_merge && is_strings,
is_gc_eligible: true,
comdat_group: None,
});
}
Some(obj)
}
pub fn link(&mut self) {
let _res_diags = self.symbol_resolver.resolve_all();
if !self.input_archives.is_empty() {
let bitcode_inputs: Vec<(String, Vec<u8>)> = self
.input_objects
.iter()
.filter(|o| self.lto_plugin.detect_bitcode(&o.data))
.map(|o| (o.filename.clone(), o.data.clone()))
.collect();
if !bitcode_inputs.is_empty() {
let exported: HashSet<String> = self
.symbol_resolver
.symbols()
.filter(|s| s.is_exported || s.binding == STB_GLOBAL)
.map(|s| s.name.clone())
.collect();
match self.lto_plugin.options.lto_model {
X86LTOModelDeep::Full => {
self.lto_plugin.run_full_lto(&bitcode_inputs, &exported);
}
X86LTOModelDeep::Thin => {
self.lto_plugin.run_thin_lto(&bitcode_inputs, &exported);
}
X86LTOModelDeep::Disabled => {}
}
}
}
self.merge_sections();
if self.enable_gc_sections {
let entry = vec![self.entry_point.clone()];
let refs: HashSet<String> = self
.symbol_resolver
.symbols()
.filter(|s| s.is_referenced)
.map(|s| s.name.clone())
.collect();
self.gc_sections.run(&mut self.sections, &entry, &refs);
}
if self.enable_icf {
self.icf.run(&mut self.sections);
}
self.build_got_plt();
self.build_dynamic_sections();
self.compute_layout();
self.apply_relocations_and_relax();
}
fn merge_sections(&mut self) {
self.sections.clear();
let mut merged: BTreeMap<String, Vec<&X86InputSectionDeep>> = BTreeMap::new();
for obj in &self.input_objects {
for sec in &obj.sections {
if sec.name.starts_with(".rela.") || sec.name.starts_with(".rel.") {
continue; }
if sec.sh_type == SHT_SYMTAB || sec.sh_type == SHT_STRTAB {
continue; }
merged.entry(sec.name.clone()).or_default().push(sec);
}
}
for (name, input_secs) in &merged {
let mut out_data = Vec::new();
let is_merge = input_secs
.first()
.map(|s| s.is_merge_strings)
.unwrap_or(false);
let align = input_secs
.iter()
.map(|s| s.sh_addralign)
.max()
.unwrap_or(16);
if is_merge {
let mut m = X86MergeSections::new(X86MergeKind::Strings, align, 1);
for sec in input_secs {
m.add_strings(&sec.data, align);
}
out_data = m.output_data().to_vec();
} else {
for sec in input_secs {
out_data.extend_from_slice(&sec.data);
}
}
let (sh_type, sh_flags) = input_secs
.first()
.map(|s| (s.sh_type, s.sh_flags))
.unwrap_or((SHT_PROGBITS, SHF_ALLOC as u64));
let is_nobits = sh_type == SHT_NOBITS;
self.sections.push(X86OutputSectionDeep {
name: name.clone(),
data: out_data,
sh_type,
sh_flags,
sh_addralign: align,
sh_entsize: input_secs.first().map(|s| s.sh_entsize).unwrap_or(0),
segment_index: None,
vaddr: 0,
file_offset: 0,
output_index: self.sections.len(),
is_merge_strings: is_merge,
is_gc_eligible: !name.starts_with(".init")
&& !name.starts_with(".fini")
&& name != ".dynamic"
&& name != ".got",
sh_link: 0,
sh_info: 0,
is_nobits,
original_size: out_data.len() as u64,
});
}
}
fn build_got_plt(&mut self) {
for sym in self.symbol_resolver.symbols() {
if sym.is_referenced {
match &sym.def {
X86SymbolDefDeep::Shared { .. } | X86SymbolDefDeep::Undefined => {
let got_idx =
self.dynamic_linker
.add_got_entry(&sym.name, false, sym.def.is_ifunc());
self.dynamic_linker
.add_plt_entry(&sym.name, got_idx, sym.def.is_ifunc());
}
_ => {}
}
}
}
let got_data = self.dynamic_linker.build_got();
let plt_data = self.dynamic_linker.build_plt();
self.sections.push(X86OutputSectionDeep {
name: ".got".to_string(),
data: got_data,
sh_type: SHT_PROGBITS,
sh_flags: (SHF_ALLOC | SHF_WRITE) as u64,
sh_addralign: 8,
sh_entsize: 8,
segment_index: None,
vaddr: 0,
file_offset: 0,
output_index: self.sections.len(),
is_merge_strings: false,
is_gc_eligible: false,
sh_link: 0,
sh_info: 0,
is_nobits: false,
original_size: self.dynamic_linker.got_size,
});
if !plt_data.is_empty() {
self.sections.push(X86OutputSectionDeep {
name: ".plt".to_string(),
data: plt_data,
sh_type: SHT_PROGBITS,
sh_flags: (SHF_ALLOC | SHF_EXECINSTR) as u64,
sh_addralign: 16,
sh_entsize: 16,
segment_index: None,
vaddr: 0,
file_offset: 0,
output_index: self.sections.len(),
is_merge_strings: false,
is_gc_eligible: false,
sh_link: 0,
sh_info: 0,
is_nobits: false,
original_size: self.dynamic_linker.plt_size,
});
}
}
fn build_dynamic_sections(&mut self) {
let mut sym_names: Vec<String> = Vec::new();
sym_names.push(String::new());
for sym in self.symbol_resolver.symbols() {
sym_names.push(sym.name.clone());
}
let dynsym_data = self.write_dynsym_raw(&sym_names);
let hash_data = self.dynamic_linker.build_gnu_hash(&sym_names, 5);
let dyn_entries = self.dynamic_linker.build_dynamic_section();
self.sections.push(X86OutputSectionDeep {
name: ".dynsym".to_string(),
data: dynsym_data,
sh_type: SHT_DYNSYM,
sh_flags: SHF_ALLOC as u64,
sh_addralign: 8,
sh_entsize: 24,
segment_index: None,
vaddr: 0,
file_offset: 0,
output_index: self.sections.len(),
is_merge_strings: false,
is_gc_eligible: false,
sh_link: 0,
sh_info: 0,
is_nobits: false,
original_size: 0,
});
self.sections.push(X86OutputSectionDeep {
name: ".gnu.hash".to_string(),
data: hash_data,
sh_type: SHT_GNU_HASH,
sh_flags: SHF_ALLOC as u64,
sh_addralign: 8,
sh_entsize: 0,
segment_index: None,
vaddr: 0,
file_offset: 0,
output_index: self.sections.len(),
is_merge_strings: false,
is_gc_eligible: false,
sh_link: 0,
sh_info: 0,
is_nobits: false,
original_size: 0,
});
self.sections.push(X86OutputSectionDeep {
name: ".dynamic".to_string(),
data: dyn_entries,
sh_type: SHT_DYNAMIC,
sh_flags: (SHF_ALLOC | SHF_WRITE) as u64,
sh_addralign: 8,
sh_entsize: 16,
segment_index: None,
vaddr: 0,
file_offset: 0,
output_index: self.sections.len(),
is_merge_strings: false,
is_gc_eligible: false,
sh_link: 0,
sh_info: 0,
is_nobits: false,
original_size: 0,
});
}
fn write_dynsym_raw(&self, symbol_names: &[String]) -> Vec<u8> {
let mut buf = Vec::new();
for name in symbol_names {
if name.is_empty() && buf.is_empty() {
buf.extend(std::iter::repeat(0u8).take(24));
continue;
}
let sym = self.symbol_resolver.get(name);
let (value, size, binding, sym_type, shndx) = match sym {
Some(s) => match &s.def {
X86SymbolDefDeep::Defined {
value,
size,
is_function,
..
} => (
*value,
*size,
s.binding,
if *is_function { STT_FUNC } else { STT_OBJECT },
1u16,
),
X86SymbolDefDeep::Shared { .. } | X86SymbolDefDeep::Undefined => {
(0u64, 0u64, STB_GLOBAL, STT_NOTYPE, 0u16)
}
X86SymbolDefDeep::Weak { value, size, .. } => {
(*value, *size, STB_WEAK, STT_NOTYPE, 0u16)
}
X86SymbolDefDeep::Common { size, alignment } => {
(0, *size, STB_GLOBAL, STT_OBJECT, 0)
}
},
None => (0, 0, STB_GLOBAL, STT_NOTYPE, 0),
};
let st_info = (binding << 4) | (sym_type & 0xf);
buf.extend_from_slice(&0u32.to_le_bytes()); buf.push(st_info);
buf.push(STV_DEFAULT);
buf.extend_from_slice(&shndx.to_le_bytes());
buf.extend_from_slice(&value.to_le_bytes());
buf.extend_from_slice(&size.to_le_bytes());
}
buf
}
fn compute_layout(&mut self) {
let mut vaddr = self.image_base;
let mut file_off = 0u64;
let page_size = X86_64_PAGE_SIZE;
let ehdr_sz = if self.arch.is_64bit() { 64 } else { 52 };
file_off = align_up_deep(file_off, page_size);
let text_sections: Vec<usize> = self
.sections
.iter()
.enumerate()
.filter(|(_, s)| {
s.sh_flags & (SHF_ALLOC | SHF_EXECINSTR) as u64
== (SHF_ALLOC | SHF_EXECINSTR) as u64
})
.map(|(i, _)| i)
.collect();
let ro_sections: Vec<usize> = self
.sections
.iter()
.enumerate()
.filter(|(_, s)| {
(s.sh_flags & (SHF_ALLOC as u64)) != 0
&& (s.sh_flags & (SHF_EXECINSTR as u64)) == 0
&& (s.sh_flags & (SHF_WRITE as u64)) == 0
})
.map(|(i, _)| i)
.collect();
let rw_sections: Vec<usize> = self
.sections
.iter()
.enumerate()
.filter(|(_, s)| {
(s.sh_flags & (SHF_ALLOC | SHF_WRITE) as u64) == (SHF_ALLOC | SHF_WRITE) as u64
&& (s.sh_flags & (SHF_EXECINSTR as u64)) == 0
})
.map(|(i, _)| i)
.collect();
if !text_sections.is_empty() || !ro_sections.is_empty() {
let mut load_vaddr = vaddr;
for &idx in text_sections.iter().chain(ro_sections.iter()) {
let sec = &mut self.sections[idx];
vaddr = align_up_deep(vaddr, sec.sh_addralign.max(1));
sec.vaddr = vaddr;
sec.file_offset = if sec.is_nobits { file_off } else { file_off };
if !sec.is_nobits {
file_off += align_up_deep(sec.data.len() as u64, 1);
}
vaddr += align_up_deep(
if sec.is_nobits {
sec.original_size
} else {
sec.data.len() as u64
},
1,
);
}
let mem_sz = vaddr - load_vaddr;
let file_sz = file_off;
self.segments.push(X86OutputSegmentDeep {
p_type: PT_LOAD,
p_flags: PF_R as u32 | PF_X as u32,
p_align: page_size,
p_vaddr: load_vaddr,
p_paddr: load_vaddr,
p_offset: 0,
p_filesz: file_sz,
p_memsz: mem_sz,
section_indices: text_sections
.iter()
.chain(ro_sections.iter())
.copied()
.collect(),
});
}
if !rw_sections.is_empty() {
let load_vaddr = vaddr;
for &idx in &rw_sections {
let sec = &mut self.sections[idx];
vaddr = align_up_deep(vaddr, sec.sh_addralign.max(1));
sec.vaddr = vaddr;
file_off = align_up_deep(file_off, sec.sh_addralign.max(1));
sec.file_offset = if sec.is_nobits { file_off } else { file_off };
if !sec.is_nobits {
file_off += align_up_deep(sec.data.len() as u64, 1);
}
vaddr += align_up_deep(
if sec.is_nobits {
sec.original_size
} else {
sec.data.len() as u64
},
1,
);
}
let mem_sz = vaddr - load_vaddr;
self.segments.push(X86OutputSegmentDeep {
p_type: PT_LOAD,
p_flags: PF_R as u32 | PF_W as u32,
p_align: page_size,
p_vaddr: load_vaddr,
p_paddr: load_vaddr,
p_offset: file_off - mem_sz.min(file_off),
p_filesz: mem_sz.min(file_off),
p_memsz: mem_sz,
section_indices: rw_sections,
});
}
self.segments.push(X86OutputSegmentDeep {
p_type: PT_GNU_STACK,
p_flags: PF_R as u32 | PF_W as u32,
p_align: STACK_ALIGN,
p_vaddr: 0,
p_paddr: 0,
p_offset: 0,
p_filesz: 0,
p_memsz: 0,
section_indices: Vec::new(),
});
self.segments.push(X86OutputSegmentDeep {
p_type: PT_GNU_RELRO,
p_flags: PF_R as u32,
p_align: 1,
p_vaddr: 0, p_paddr: 0,
p_offset: 0,
p_filesz: 0,
p_memsz: 0,
section_indices: Vec::new(),
});
if self.dynamic_linker.dynamic_entries.len() > 1 {
self.segments.push(X86OutputSegmentDeep {
p_type: PT_DYNAMIC,
p_flags: PF_R as u32 | PF_W as u32,
p_align: 8,
p_vaddr: 0,
p_paddr: 0,
p_offset: 0,
p_filesz: 0,
p_memsz: 0,
section_indices: Vec::new(),
});
}
}
fn apply_relocations_and_relax(&mut self) {
let symbol_offsets: HashMap<String, u64> = self
.symbol_resolver
.symbols()
.filter_map(|s| match &s.def {
X86SymbolDefDeep::Defined { value, .. } => Some((s.name.clone(), *value)),
_ => None,
})
.collect();
for (_i, section) in self.sections.iter_mut().enumerate() {
if section.name.starts_with(".text") {
let section_vaddr = section.vaddr;
self.relaxation.apply_relaxations(
&mut section.data,
section.output_index,
&symbol_offsets,
section_vaddr,
);
}
}
}
pub fn emit(&mut self) -> Vec<u8> {
match self.output_format {
X86OutputFormatDeep::Elf64 | X86OutputFormatDeep::Elf32 => self.emit_elf(),
X86OutputFormatDeep::Coff64 | X86OutputFormatDeep::Coff32 => self.emit_coff(),
X86OutputFormatDeep::MachO64 => self.emit_macho(),
}
}
fn emit_elf(&mut self) -> Vec<u8> {
let mut writer = X86ELFWriterDeep::new_elf64_exec();
writer.machine = self.arch.elf_machine();
writer.file_type = if self.is_pic { ET_DYN } else { ET_EXEC };
writer.entry = self
.symbol_resolver
.get(&self.entry_point)
.map(|s| match &s.def {
X86SymbolDefDeep::Defined { value, .. } => *value,
_ => 0,
})
.unwrap_or(0);
writer.base_addr = self.image_base;
for sec in &self.sections {
if sec.data.is_empty() && sec.name != ".bss" && sec.name != ".tbss" {
continue;
}
writer.add_section(sec.clone());
}
for seg in &self.segments {
writer.add_program_header(seg.clone());
}
if self.build_id.is_enabled() {
let note_data = self.build_id.compute(&self.compute_all_section_data());
if !note_data.is_empty() {
writer.add_note("GNU", NT_GNU_BUILD_ID, ¬e_data);
}
}
writer.write()
}
fn emit_coff(&mut self) -> Vec<u8> {
let mut buf: Vec<u8> = Vec::new();
let dos_stub: Vec<u8> = vec![
0x4d, 0x5a, 0x90, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xff, 0xff,
0x00, 0x00, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x0e, 0x1f, 0xba, 0x0e, 0x00, 0xb4,
0x09, 0xcd, 0x21, 0xb8, 0x01, 0x4c, 0xcd, 0x21, 0x54, 0x68, 0x69, 0x73, 0x20, 0x70,
0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x20, 0x63, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x20,
0x62, 0x65, 0x20, 0x72, 0x75, 0x6e, 0x20, 0x69, 0x6e, 0x20, 0x44, 0x4f, 0x53, 0x20,
0x6d, 0x6f, 0x64, 0x65, 0x2e, 0x0d, 0x0d, 0x0a, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
];
let pe_offset = dos_stub.len() as u32;
buf.extend_from_slice(&dos_stub);
buf.extend_from_slice(b"PE\0\0");
let machine = self.arch.coff_machine();
buf.extend_from_slice(&machine.to_le_bytes());
buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); let opt_hdr_sz: u16 = if self.arch.is_64bit() { 112 } else { 96 };
buf.extend_from_slice(&opt_hdr_sz.to_le_bytes());
let characteristics: u16 = 0x22f; buf.extend_from_slice(&characteristics.to_le_bytes());
let magic = if self.arch.is_64bit() {
PE32PLUS_MAGIC
} else {
PE32_MAGIC
};
buf.extend_from_slice(&magic.to_le_bytes());
buf.extend_from_slice(&0u8.to_le_bytes()); buf.extend_from_slice(&0u8.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes());
if self.arch.is_64bit() {
buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&self.image_base.to_le_bytes()); } else {
buf.extend_from_slice(&(self.image_base as u32).to_le_bytes());
buf.extend_from_slice(&0u32.to_le_bytes());
}
buf.extend_from_slice(&(COFF_SECTION_ALIGNMENT as u32).to_le_bytes());
buf.extend_from_slice(&(COFF_FILE_ALIGNMENT as u32).to_le_bytes());
let remaining = opt_hdr_sz as usize - (buf.len() - pe_offset as usize - 4 - 20);
buf.extend(std::iter::repeat(0u8).take(remaining));
write_u32_le_deep(&mut buf, 0x3c, pe_offset);
buf
}
fn emit_macho(&mut self) -> Vec<u8> {
let mut buf: Vec<u8> = Vec::new();
let magic: u32 = 0xfeedfacf;
buf.extend_from_slice(&magic.to_le_bytes());
let cpu_type: u32 = 0x01000007;
let cpu_subtype: u32 = 3; buf.extend_from_slice(&cpu_type.to_le_bytes());
buf.extend_from_slice(&cpu_subtype.to_le_bytes());
buf.extend_from_slice(&2u32.to_le_bytes());
buf.extend_from_slice(&1u32.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes());
buf.extend_from_slice(&1u32.to_le_bytes());
buf.extend_from_slice(&0u32.to_le_bytes());
buf
}
fn compute_all_section_data(&self) -> Vec<u8> {
let mut data = Vec::new();
for sec in &self.sections {
data.extend_from_slice(&sec.data);
}
data
}
pub fn link_and_emit(&mut self) -> Vec<u8> {
self.link();
self.emit()
}
pub fn get_diagnostics(&self) -> &[X86LinkerDiagnosticDeep] {
&self.diagnostics
}
pub fn has_errors(&self) -> bool {
self.diagnostics
.iter()
.any(|d| d.level == X86DiagLevelDeep::Error || d.level == X86DiagLevelDeep::Fatal)
}
}
impl Default for X86LLDDeep {
fn default() -> Self {
Self::new_elf_x86_64()
}
}
#[cfg(test)]
#[allow(unused_imports)]
mod tests {
use super::*;
use std::collections::HashSet;
fn make_elf_obj_64(symbols: &[(&str, u64, u8, bool)]) -> X86InputObjectDeep {
let mut obj = X86InputObjectDeep {
filename: "test.o".to_string(),
data: Vec::new(),
sections: Vec::new(),
symbols: Vec::new(),
relocations: Vec::new(),
machine: EM_X86_64,
elf_class: ELFCLASS64,
};
for &(name, value, binding, is_def) in symbols {
obj.symbols.push(X86InputSymbolDeep {
name: name.to_string(),
value,
size: 8,
binding,
sym_type: STT_FUNC,
visibility: STV_DEFAULT,
section_index: if is_def { 1 } else { 0 },
is_defined: is_def,
common_alignment: 0,
});
}
obj
}
fn make_output_section(name: &str, data: Vec<u8>, flags: u64) -> X86OutputSectionDeep {
X86OutputSectionDeep {
name: name.to_string(),
data,
sh_type: SHT_PROGBITS,
sh_flags: flags,
sh_addralign: 16,
sh_entsize: 0,
segment_index: None,
vaddr: 0,
file_offset: 0,
output_index: 0,
is_merge_strings: false,
is_gc_eligible: true,
sh_link: 0,
sh_info: 0,
is_nobits: false,
original_size: 0,
}
}
#[test]
fn test_crel_new() {
let crel = X86CREL::new();
assert!(crel.is_empty());
assert_eq!(crel.len(), 0);
}
#[test]
fn test_crel_add_entry() {
let mut crel = X86CREL::new();
crel.add_relocation(0, 1, R_X86_64_64, 0, true);
assert_eq!(crel.len(), 1);
}
#[test]
fn test_crel_encode_decode_roundtrip() {
let mut crel = X86CREL::new();
crel.add_relocation(0, 1, R_X86_64_64, 42, true);
crel.add_relocation(4, 2, R_X86_64_PC32, -4, true);
crel.add_relocation(8, 1, R_X86_64_32, 0, false);
let encoded = crel.encode();
let decoded = X86CREL::decode(&encoded).unwrap();
let expanded = decoded.expand();
assert_eq!(expanded.len(), 3);
assert_eq!(expanded[0], (0, 1, R_X86_64_64, 42, true));
}
#[test]
fn test_crel_empty() {
let crel = X86CREL::new();
let encoded = crel.encode();
let decoded = X86CREL::decode(&encoded).unwrap();
assert!(decoded.is_empty());
}
#[test]
fn test_crel_multiple_entries() {
let mut crel = X86CREL::new();
for i in 0..10 {
crel.add_relocation(i * 8, i as i64, R_X86_64_64, i as i64 * 8, true);
}
assert_eq!(crel.len(), 10);
let encoded = crel.encode();
let decoded = X86CREL::decode(&encoded).unwrap();
let expanded = decoded.expand();
assert_eq!(expanded.len(), 10);
}
#[test]
fn test_build_id_none() {
let bid = X86BuildID::new(X86BuildIdKindDeep::None);
assert!(!bid.is_enabled());
assert_eq!(bid.compute(b"hello"), Vec::<u8>::new());
}
#[test]
fn test_build_id_sha1() {
let bid = X86BuildID::new(X86BuildIdKindDeep::Sha1);
assert!(bid.is_enabled());
let hash = bid.compute(b"test");
assert_eq!(hash.len(), 20);
}
#[test]
fn test_build_id_md5() {
let bid = X86BuildID::new(X86BuildIdKindDeep::Md5);
let hash = bid.compute(b"test");
assert_eq!(hash.len(), 16);
}
#[test]
fn test_build_id_uuid() {
let bid = X86BuildID::new(X86BuildIdKindDeep::Uuid);
let hash = bid.compute(b"test");
assert_eq!(hash.len(), 16);
assert_eq!(hash[8] & 0xc0, 0x80);
}
#[test]
fn test_build_id_fast() {
let bid = X86BuildID::new(X86BuildIdKindDeep::Fast);
let hash = bid.compute(b"test");
assert_eq!(hash.len(), 8);
}
#[test]
fn test_build_id_hex_string() {
let bid = X86BuildID::with_hex_string("deadbeefcafe");
let hash = bid.compute(b"");
assert_eq!(hash, vec![0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe]);
}
#[test]
fn test_build_note_section() {
let bid = X86BuildID::new(X86BuildIdKindDeep::Sha1);
let note = bid.build_note_section(b"hello");
assert!(!note.is_empty());
assert_eq!(¬e[0..4], &4u32.to_le_bytes());
assert_eq!(
u32::from_le_bytes(note[8..12].try_into().unwrap()),
NT_GNU_BUILD_ID
);
}
#[test]
fn test_build_id_section_name() {
assert_eq!(X86BuildID::section_name(), ".note.gnu.build-id");
}
#[test]
fn test_build_id_sha256() {
let bid = X86BuildID::new(X86BuildIdKindDeep::Sha256);
let hash = bid.compute(b"test");
assert_eq!(hash.len(), 32);
}
#[test]
fn test_merge_strings_basic() {
let mut m = X86MergeSections::new(X86MergeKind::Strings, 1, 1);
let data = b"hello\0world\0";
let mapping = m.add_strings(data, 1);
assert!(!mapping.is_empty());
assert!(m.unique_count() >= 2);
assert!(!m.output_data().is_empty());
}
#[test]
fn test_merge_strings_dedup() {
let mut m = X86MergeSections::new(X86MergeKind::Strings, 1, 1);
let data1 = b"foo\0bar\0";
let data2 = b"foo\0baz\0";
m.add_strings(data1, 1);
let count_before = m.unique_count();
m.add_strings(data2, 1);
assert!(m.unique_count() <= count_before + 1);
}
#[test]
fn test_merge_constants() {
let mut m = X86MergeSections::new(X86MergeKind::Constants, 8, 8);
let data = vec![1u8, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0];
let mapping = m.add_constants(&data, 8);
assert!(!mapping.is_empty());
}
#[test]
fn test_merge_empty() {
let m = X86MergeSections::new(X86MergeKind::Strings, 1, 1);
assert_eq!(m.output_size(), 0);
assert_eq!(m.unique_count(), 0);
}
#[test]
fn test_merge_tail_enabled() {
let mut m = X86MergeSections::new(X86MergeKind::Strings, 1, 1).with_tail_merge(true);
let data = b"hello\0";
m.add_strings(data, 1);
assert!(!m.output_data().is_empty());
}
#[test]
fn test_merge_clear() {
let mut m = X86MergeSections::new(X86MergeKind::Strings, 1, 1);
m.add_strings(b"test\0", 1);
assert!(m.unique_count() > 0);
m.clear();
assert_eq!(m.unique_count(), 0);
assert_eq!(m.output_size(), 0);
}
#[test]
fn test_elf_writer_new_exec() {
let w = X86ELFWriterDeep::new_elf64_exec();
assert_eq!(w.class, ELFCLASS64);
assert_eq!(w.machine, EM_X86_64);
assert_eq!(w.file_type, ET_EXEC);
}
#[test]
fn test_elf_writer_new_shared() {
let w = X86ELFWriterDeep::new_elf64_shared();
assert_eq!(w.file_type, ET_DYN);
assert!(w.is_shared);
}
#[test]
fn test_elf_writer_new_pie() {
let w = X86ELFWriterDeep::new_elf64_pie();
assert_eq!(w.file_type, ET_DYN);
assert!(w.is_pie);
}
#[test]
fn test_elf_writer_set_entry() {
let mut w = X86ELFWriterDeep::new_elf64_exec();
w.set_entry(0x400100);
assert_eq!(w.entry, 0x400100);
}
#[test]
fn test_elf_writer_add_null_section() {
let mut w = X86ELFWriterDeep::new_elf64_exec();
w.add_null_section();
assert!(w.null_section_added);
w.add_null_section();
assert_eq!(w.sections.len(), 1);
}
#[test]
fn test_elf_writer_add_section() {
let mut w = X86ELFWriterDeep::new_elf64_exec();
let idx = w.add_section(make_output_section(
".text",
vec![0x90],
SHF_ALLOC as u64 | SHF_EXECINSTR as u64,
));
assert_eq!(idx, 0);
}
#[test]
fn test_elf_writer_write_header_only() {
let w = X86ELFWriterDeep::new_elf64_exec();
let hdr = w.write_elf_header_only();
assert_eq!(&hdr[0..4], &ELF_MAGIC_BYTES);
assert_eq!(hdr[EI_CLASS as usize], ELFCLASS64);
}
#[test]
fn test_elf_writer_write_minimal() {
let mut w = X86ELFWriterDeep::new_elf64_exec();
w.set_entry(0x401000);
let output = w.write();
assert!(output.len() > 64);
assert_eq!(&output[0..4], &ELF_MAGIC_BYTES);
}
#[test]
fn test_elf_writer_write_with_program_headers() {
let mut w = X86ELFWriterDeep::new_elf64_exec();
w.add_program_header(X86OutputSegmentDeep {
p_type: PT_LOAD,
p_flags: PF_R as u32 | PF_X as u32,
p_align: 0x1000,
p_vaddr: 0x400000,
p_paddr: 0x400000,
p_offset: 0,
p_filesz: 0x1000,
p_memsz: 0x1000,
section_indices: vec![0],
});
let output = w.write();
assert!(output.len() > 64 + 56);
}
#[test]
fn test_elf_writer_strtab() {
let mut w = X86ELFWriterDeep::new_elf64_exec();
let off1 = w.get_strtab_offset("main");
let off2 = w.get_strtab_offset("printf");
assert_eq!(off1, 1); assert!(off2 > off1);
assert_eq!(w.get_strtab_offset("main"), off1);
}
#[test]
fn test_elf_writer_elf32() {
let w = X86ELFWriterDeep::new_elf32_exec();
assert_eq!(w.class, ELFCLASS32);
assert_eq!(w.machine, EM_386);
}
#[test]
fn test_dynamic_linker_new() {
let dl = X86DynamicLinker::new();
assert!(dl.needed.is_empty());
assert!(dl.lazy_binding);
}
#[test]
fn test_dynamic_linker_add_needed() {
let mut dl = X86DynamicLinker::new();
dl.add_needed("libc.so.6");
assert!(dl.needed.contains(&"libc.so.6".to_string()));
dl.add_needed("libc.so.6");
assert_eq!(dl.needed.len(), 1);
}
#[test]
fn test_dynamic_linker_build_dynamic_entries() {
let mut dl = X86DynamicLinker::new();
dl.add_needed("libc.so.6");
dl.build_dynamic_entries(0x1000, 0x2000, 0x3000);
assert!(dl.dynamic_entries.len() >= 10);
assert_eq!(dl.dynamic_entries.last().unwrap().0, DT_NULL as i64);
}
#[test]
fn test_dynamic_linker_build_got() {
let mut dl = X86DynamicLinker::new();
dl.add_got_entry("foo", false, false);
dl.add_got_entry("bar", true, false);
let got = dl.build_got();
assert_eq!(got.len(), 5 * 8);
}
#[test]
fn test_dynamic_linker_build_plt() {
let mut dl = X86DynamicLinker::new();
dl.got_addr = 0x400000;
dl.plt_addr = 0x401000;
dl.add_plt_entry("printf", 24, false);
let plt = dl.build_plt();
assert_eq!(plt.len(), 32);
}
#[test]
fn test_dynamic_linker_build_sysv_hash() {
let dl = X86DynamicLinker::new();
let names: Vec<String> = vec!["".into(), "main".into(), "printf".into()];
let hash = dl.build_sysv_hash(&names);
assert!(hash.len() >= 8);
}
#[test]
fn test_dynamic_linker_build_gnu_hash() {
let dl = X86DynamicLinker::new();
let names: Vec<String> = vec!["main".into(), "printf".into()];
let hash = dl.build_gnu_hash(&names, 5);
assert!(hash.len() >= 16);
}
#[test]
fn test_dynamic_linker_set_lazy_binding() {
let mut dl = X86DynamicLinker::new();
dl.set_lazy_binding(false);
assert!(!dl.lazy_binding);
}
#[test]
fn test_dynamic_linker_init_fini() {
let mut dl = X86DynamicLinker::new();
dl.set_init(0x400100);
dl.set_fini(0x400200);
dl.add_init_array(0x400300);
dl.add_fini_array(0x400400);
assert_eq!(dl.init_addr, 0x400100);
assert_eq!(dl.fini_addr, 0x400200);
assert_eq!(dl.init_array.len(), 1);
}
#[test]
fn test_symbol_resolver_new() {
let sr = X86SymbolResolver::new();
assert_eq!(sr.resolved_count(), 0);
}
#[test]
fn test_symbol_resolver_define() {
let mut sr = X86SymbolResolver::new();
sr.define(
"main",
0x1000,
50,
STB_GLOBAL,
STT_FUNC,
X86SymbolVisibilityDeep::Default,
None,
true,
false,
false,
);
assert!(sr.get("main").is_some());
assert_eq!(sr.resolved_count(), 1);
}
#[test]
fn test_symbol_resolver_reference() {
let mut sr = X86SymbolResolver::new();
sr.reference("printf");
assert!(sr.check_undefined().contains(&"printf".to_string()));
}
#[test]
fn test_symbol_resolver_weak_override() {
let mut sr = X86SymbolResolver::new();
sr.define(
"foo",
0x1000,
10,
STB_GLOBAL,
STT_FUNC,
X86SymbolVisibilityDeep::Default,
None,
true,
false,
false,
);
sr.define_weak("foo", 0x2000, 10, None);
let sym = sr.get("foo").unwrap();
match &sym.def {
X86SymbolDefDeep::Defined { value, .. } => assert_eq!(*value, 0x1000),
_ => panic!("Expected defined"),
}
}
#[test]
fn test_symbol_resolver_common() {
let mut sr = X86SymbolResolver::new();
sr.define_common("buf", 1024, 16);
sr.define_common("buf", 2048, 8); let allocs = sr.resolve_commons();
assert!(!allocs.is_empty());
}
#[test]
fn test_symbol_resolver_wrap() {
let mut sr = X86SymbolResolver::new();
sr.add_wrap("malloc");
assert!(sr.wrap_map.contains_key("malloc"));
}
#[test]
fn test_symbol_resolver_defsym() {
let mut sr = X86SymbolResolver::new();
sr.add_defsym("my_sym", 0x5000);
sr.resolve_all();
assert!(sr.get("my_sym").is_some());
}
#[test]
fn test_symbol_resolver_version_script() {
let mut sr = X86SymbolResolver::new();
sr.apply_version_script("VERS_1.0", &["foo".into(), "bar".into()], true);
assert!(!sr.verdef.is_empty());
}
#[test]
fn test_symbol_resolver_resolve_all() {
let mut sr = X86SymbolResolver::new();
sr.define(
"main",
0x1000,
50,
STB_GLOBAL,
STT_FUNC,
X86SymbolVisibilityDeep::Default,
None,
true,
false,
false,
);
sr.reference("printf");
let diags = sr.resolve_all();
assert!(!diags.is_empty());
}
#[test]
fn test_relaxation_new() {
let re = X86RelaxationEngine::new();
assert_eq!(re.stats().total_relaxed, 0);
}
#[test]
fn test_relaxation_analyze_gotpcrelx() {
let mut re = X86RelaxationEngine::new();
let data = vec![0x8b, 0x05, 0x00, 0x00, 0x00, 0x00];
let kind = re.analyze_relocation(0, 2, R_X86_64_GOTPCRELX, "foo", 0, &data, true);
assert!(kind.is_some());
assert!(matches!(kind.unwrap(), X86RelaxKind::GOTPCRELX_MovToLea));
}
#[test]
fn test_relaxation_analyze_tls_gd() {
let mut re = X86RelaxationEngine::new();
let data = vec![0x66, 0x48, 0x8d, 0x3d, 0x00, 0x00, 0x00, 0x00];
let kind = re.analyze_relocation(0, 4, R_X86_64_TLSGD, "tls_var", 0, &data, true);
assert!(kind.is_some());
assert!(matches!(kind.unwrap(), X86RelaxKind::TlsGdToLe));
}
#[test]
fn test_relaxation_analyze_tls_ie() {
let mut re = X86RelaxationEngine::new();
let data = vec![0x48, 0x8b, 0x05, 0x00, 0x00, 0x00, 0x00];
let kind = re.analyze_relocation(0, 3, R_X86_64_GOTTPOFF, "tls_var", 0, &data, true);
assert!(kind.is_some());
assert!(matches!(kind.unwrap(), X86RelaxKind::TlsIeToLe));
}
#[test]
fn test_relaxation_apply_mov_to_lea() {
let mut re = X86RelaxationEngine::new();
let mut data = vec![0x8b, 0x05, 0x00, 0x00, 0x00, 0x00];
let ok = re.apply_gotpcrelx_mov_to_lea(&mut data, 2, 0, 0);
assert!(ok);
assert_eq!(data[0], 0x8d); }
#[test]
fn test_relaxation_apply_call_to_jmp() {
let mut re = X86RelaxationEngine::new();
let mut data = vec![0xe8, 0x00, 0x00, 0x00, 0x00, 0xc3];
let ok = re.apply_call_to_jmp(&mut data, 0);
assert!(ok);
assert_eq!(data[0], 0xe9);
}
#[test]
fn test_relaxation_record_and_apply() {
let mut re = X86RelaxationEngine::new();
re.record_relaxation(
0,
0,
R_X86_64_GOTPCRELX,
X86RelaxKind::GOTPCRELX_MovToLea,
"foo",
0,
);
let mut data = vec![0x8b, 0x05, 0x00, 0x00, 0x00, 0x00];
let mut sym_offsets = HashMap::new();
sym_offsets.insert("foo".to_string(), 0x400000);
let applied = re.apply_relaxations(&mut data, 0, &sym_offsets, 0x401000);
assert_eq!(applied, 1);
assert_eq!(re.stats().gotpcrelx_mov_to_lea, 1);
}
#[test]
fn test_relaxation_kind_names() {
assert_eq!(
X86RelaxationEngine::relax_kind_name(X86RelaxKind::GOTPCRELX_MovToLea),
"GOTPCRELX mov→lea"
);
assert_eq!(
X86RelaxationEngine::relax_kind_name(X86RelaxKind::CallToJmp),
"call→jmp"
);
assert_eq!(
X86RelaxationEngine::relax_kind_name(X86RelaxKind::NoRelax),
"none"
);
}
#[test]
fn test_lto_detect_bitcode() {
let lto = X86LTOPluginDeep::default();
assert!(lto.detect_bitcode(&[0x42, 0x43, 0xC0, 0xDE]));
assert!(!lto.detect_bitcode(&[0x7f, b'E', b'L', b'F']));
assert!(!lto.detect_bitcode(&[]));
}
#[test]
fn test_lto_analyze_bitcode() {
let mut lto = X86LTOPluginDeep::default();
let data = b"BC\xC0\xDE... x86_64-unknown-linux-gnu ... _start ... _main ...";
let info = lto.analyze_bitcode(data, "test.bc");
assert!(info.is_bitcode);
assert!(info.target_triple.is_some());
}
#[test]
fn test_lto_non_bitcode() {
let mut lto = X86LTOPluginDeep::default();
let info = lto.analyze_bitcode(b"ELF...", "not.bc");
assert!(!info.is_bitcode);
}
#[test]
fn test_lto_should_process() {
let lto = X86LTOPluginDeep::default();
assert!(lto.should_process(&[0x42, 0x43, 0xC0, 0xDE]));
assert!(!lto.should_process(&[0x7f, b'E', b'L', b'F']));
}
#[test]
fn test_lto_full_lto() {
let mut lto = X86LTOPluginDeep::default();
let inputs = vec![
("a.bc".to_string(), b"BC\xC0\xDE\x00\x01\x02\x03".to_vec()),
("b.bc".to_string(), b"BC\xC0\xDE\x04\x05\x06\x07".to_vec()),
];
let exported = HashSet::new();
let outputs = lto.run_full_lto(&inputs, &exported);
assert!(!outputs.is_empty());
}
#[test]
fn test_lto_thin_lto() {
let mut lto = X86LTOPluginDeep::default();
let inputs = vec![("a.bc".to_string(), b"BC\xC0\xDE\x00\x01\x02\x03".to_vec())];
let exported = HashSet::new();
let outputs = lto.run_thin_lto(&inputs, &exported);
assert!(!outputs.is_empty());
}
#[test]
fn test_lto_cache_hit_ratio() {
let lto = X86LTOPluginDeep::default();
assert_eq!(lto.cache_hit_ratio(), 0.0);
}
#[test]
fn test_lto_compute_summary() {
let lto = X86LTOPluginDeep::default();
let summary = lto.compute_module_summary(b"BC\xC0\xDE");
assert!(summary.module_hash > 0);
}
#[test]
fn test_gc_new() {
let gc = X86GCSectionsDeep::new();
assert!(!gc.keep_patterns.is_empty());
assert!(!gc.keep_eh);
}
#[test]
fn test_gc_run_keeps_roots() {
let mut gc = X86GCSectionsDeep::new();
let mut sections = vec![
make_output_section(".init", vec![1], SHF_ALLOC as u64),
make_output_section(
".text.main",
vec![2, 3, 4],
SHF_ALLOC as u64 | SHF_EXECINSTR as u64,
),
make_output_section(
".text.unused",
vec![42],
SHF_ALLOC as u64 | SHF_EXECINSTR as u64,
),
];
let entry = vec!["main".to_string()];
let refs = HashSet::new();
let removed = gc.run(&mut sections, &entry, &refs);
assert_eq!(removed, 1);
assert!(!sections[0].data.is_empty());
assert!(!sections[1].data.is_empty());
assert!(sections[2].data.is_empty());
}
#[test]
fn test_gc_run_empty() {
let mut gc = X86GCSectionsDeep::new();
let mut sections: Vec<X86OutputSectionDeep> = vec![];
let removed = gc.run(&mut sections, &[], &HashSet::new());
assert_eq!(removed, 0);
}
#[test]
fn test_icf_new() {
let icf = X86ICFDeep::new();
assert_eq!(icf.min_size, 16);
assert!(!icf.fold_rodata);
}
#[test]
fn test_icf_compute_hash() {
let icf = X86ICFDeep::new();
let a = make_output_section(
".text.foo",
vec![1, 2, 3, 4],
SHF_ALLOC as u64 | SHF_EXECINSTR as u64,
);
let b = make_output_section(
".text.bar",
vec![1, 2, 3, 4],
SHF_ALLOC as u64 | SHF_EXECINSTR as u64,
);
assert_eq!(icf.compute_section_hash(&a), icf.compute_section_hash(&b));
}
#[test]
fn test_icf_identical_fold() {
let mut icf = X86ICFDeep::new().with_safety(X86ICFSafetyLevel::All);
let data = vec![1u8; 32];
let mut sections = vec![
make_output_section(".text.a", data.clone(), 6),
make_output_section(".text.b", data.clone(), 6),
];
let removed = icf.run(&mut sections);
assert_eq!(removed, 1);
assert!(sections[0].data.is_empty() || sections[1].data.is_empty());
}
#[test]
fn test_icf_non_identical() {
let mut icf = X86ICFDeep::new();
let mut sections = vec![
make_output_section(
".text.a",
vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6],
6,
),
make_output_section(".text.b", vec![9; 16], 6),
];
let removed = icf.run(&mut sections);
assert_eq!(removed, 0);
}
#[test]
fn test_icf_small_size_skip() {
let mut icf = X86ICFDeep::new();
let data = vec![1, 2, 3]; let mut sections = vec![
make_output_section(".text.a", data.clone(), 6),
make_output_section(".text.b", data.clone(), 6),
];
let removed = icf.run(&mut sections);
assert_eq!(removed, 0);
}
#[test]
fn test_icf_fold_mapping() {
let mut icf = X86ICFDeep::new().with_safety(X86ICFSafetyLevel::All);
let data = vec![1u8; 32];
let mut sections = vec![
make_output_section(".text.a", data.clone(), 6),
make_output_section(".text.b", data.clone(), 6),
];
icf.run(&mut sections);
assert_eq!(icf.fold_count(), 1);
assert!(!icf.get_fold_mapping().is_empty());
}
#[test]
fn test_lld_deep_new() {
let lld = X86LLDDeep::new_elf_x86_64();
assert!(lld.arch.is_64bit());
assert_eq!(lld.output_format, X86OutputFormatDeep::Elf64);
}
#[test]
fn test_lld_deep_i386() {
let lld = X86LLDDeep::new_elf_i386();
assert!(!lld.arch.is_64bit());
assert_eq!(lld.output_format, X86OutputFormatDeep::Elf32);
}
#[test]
fn test_lld_deep_pic() {
let mut lld = X86LLDDeep::new_elf_x86_64();
lld.set_pic(true);
assert!(lld.is_pic);
assert_eq!(lld.elf_writer.file_type, ET_DYN);
}
#[test]
fn test_lld_deep_entry() {
let mut lld = X86LLDDeep::new_elf_x86_64();
lld.set_entry("my_main");
assert_eq!(lld.entry_point, "my_main");
}
#[test]
fn test_lld_deep_link_basic() {
let mut lld = X86LLDDeep::new_elf_x86_64();
let obj = make_elf_obj_64(&[
("_start", 0x400100, STB_GLOBAL, true),
("main", 0x400200, STB_GLOBAL, true),
]);
lld.input_objects.push(obj);
lld.link();
assert!(!lld.sections.is_empty());
}
#[test]
fn test_lld_deep_emit_elf() {
let mut lld = X86LLDDeep::new_elf_x86_64();
let obj = make_elf_obj_64(&[("_start", 0x401000, STB_GLOBAL, true)]);
lld.input_objects.push(obj);
lld.link();
let output = lld.emit();
assert!(!output.is_empty());
assert_eq!(&output[0..4], &ELF_MAGIC_BYTES);
}
#[test]
fn test_lld_deep_emit_coff() {
let mut lld = X86LLDDeep::new_coff_x86_64();
let output = lld.emit(); assert!(output.len() > 64);
assert_eq!(&output[0..2], b"MZ");
}
#[test]
fn test_lld_deep_emit_macho() {
let mut lld = X86LLDDeep::new_coff_x86_64();
lld.output_format = X86OutputFormatDeep::MachO64;
let output = lld.emit();
assert!(!output.is_empty());
assert_eq!(&output[0..4], &0xfeedfacfu32.to_le_bytes());
}
#[test]
fn test_lld_deep_diagnostics() {
let mut lld = X86LLDDeep::new_elf_x86_64();
lld.diag(X86DiagLevelDeep::Warning, "test warning");
assert_eq!(lld.get_diagnostics().len(), 1);
assert!(!lld.has_errors());
lld.diag(X86DiagLevelDeep::Error, "test error");
assert!(lld.has_errors());
}
#[test]
fn test_lld_deep_gc_sections() {
let mut lld = X86LLDDeep::new_elf_x86_64();
lld.set_gc_sections(true);
assert!(lld.enable_gc_sections);
}
#[test]
fn test_lld_deep_icf() {
let mut lld = X86LLDDeep::new_elf_x86_64();
lld.set_icf(true);
assert!(lld.enable_icf);
}
#[test]
fn test_lld_deep_build_id() {
let mut lld = X86LLDDeep::new_elf_x86_64();
lld.set_build_id(X86BuildIdKindDeep::Sha1);
assert!(lld.build_id.is_enabled());
}
#[test]
fn test_lld_deep_link_and_emit() {
let mut lld = X86LLDDeep::new_elf_x86_64();
let obj = make_elf_obj_64(&[("_start", 0x401000, STB_GLOBAL, true)]);
lld.input_objects.push(obj);
lld.entry_point = "_start".to_string();
let output = lld.link_and_emit();
assert!(!output.is_empty());
assert_eq!(&output[0..4], &ELF_MAGIC_BYTES);
}
#[test]
fn test_lld_deep_wrap_and_defsym() {
let mut lld = X86LLDDeep::new_elf_x86_64();
lld.add_wrap("malloc");
lld.add_defsym("my_alloc", 0x5000);
lld.symbol_resolver.resolve_all();
assert!(lld.symbol_resolver.get("my_alloc").is_some());
}
#[test]
fn test_lld_deep_parse_elf_object() {
let lld = X86LLDDeep::new_elf_x86_64();
let mut elf = vec![0u8; 128];
elf[0..4].copy_from_slice(&ELF_MAGIC_BYTES);
elf[EI_CLASS as usize] = ELFCLASS64;
elf[EI_DATA as usize] = ELFDATA2LSB;
write_u16_le_deep(&mut elf, 0x12, EM_X86_64);
write_u64_le_deep(&mut elf, 0x28, 0); write_u16_le_deep(&mut elf, 0x3c, 0); let obj = lld.parse_elf_object(&elf, "test.o");
assert!(obj.is_some());
assert_eq!(obj.unwrap().machine, EM_X86_64);
}
#[test]
fn test_align_up_deep() {
assert_eq!(align_up_deep(0, 16), 0);
assert_eq!(align_up_deep(1, 16), 16);
assert_eq!(align_up_deep(15, 16), 16);
assert_eq!(align_up_deep(16, 16), 16);
assert_eq!(align_up_deep(17, 16), 32);
assert_eq!(align_up_deep(5, 0), 5);
}
#[test]
fn test_align_down_deep() {
assert_eq!(align_down_deep(0, 16), 0);
assert_eq!(align_down_deep(15, 16), 0);
assert_eq!(align_down_deep(16, 16), 16);
assert_eq!(align_down_deep(17, 16), 16);
}
#[test]
fn test_wildcard_match_deep() {
assert!(wildcard_match_deep("*.c", "hello.c"));
assert!(!wildcard_match_deep("*.c", "hello.h"));
assert!(wildcard_match_deep("foo*bar", "fooxxxbar"));
assert!(wildcard_match_deep("?", "a"));
assert!(!wildcard_match_deep("?", "ab"));
}
#[test]
fn test_elf_hash_deep() {
let h = elf_hash_deep(b"main");
assert!(h != 0);
}
#[test]
fn test_gnu_hash_deep() {
let h = gnu_hash_deep(b"main");
assert!(h != 0);
}
#[test]
fn test_next_power_of_two_deep() {
assert_eq!(next_power_of_two_deep(0), 1);
assert_eq!(next_power_of_two_deep(1), 1);
assert_eq!(next_power_of_two_deep(3), 4);
assert_eq!(next_power_of_two_deep(5), 8);
assert_eq!(next_power_of_two_deep(64), 64);
}
#[test]
fn test_arch_helpers() {
let a64 = X86LLDArchDeep::X86_64;
assert!(a64.is_64bit());
assert_eq!(a64.pointer_size(), 8);
assert_eq!(a64.elf_machine(), EM_X86_64);
assert_eq!(a64.coff_machine(), IMAGE_FILE_MACHINE_AMD64);
let a32 = X86LLDArchDeep::I386;
assert!(!a32.is_64bit());
assert_eq!(a32.pointer_size(), 4);
assert_eq!(a32.elf_machine(), EM_386);
}
#[test]
fn test_visibility_to_elf_deep() {
assert_eq!(
visibility_to_elf_deep(X86SymbolVisibilityDeep::Default),
STV_DEFAULT
);
assert_eq!(
visibility_to_elf_deep(X86SymbolVisibilityDeep::Hidden),
STV_HIDDEN
);
assert_eq!(
visibility_to_elf_deep(X86SymbolVisibilityDeep::Protected),
STV_PROTECTED
);
}
#[test]
fn test_read_write_u16() {
let mut buf = vec![0u8; 4];
write_u16_le_deep(&mut buf, 0, 0xABCD);
assert_eq!(read_u16_le_deep(&buf, 0), 0xABCD);
}
#[test]
fn test_read_write_u32() {
let mut buf = vec![0u8; 8];
write_u32_le_deep(&mut buf, 2, 0xDEADBEEF);
assert_eq!(read_u32_le_deep(&buf, 2), 0xDEADBEEF);
}
#[test]
fn test_read_write_u64() {
let mut buf = vec![0u8; 16];
write_u64_le_deep(&mut buf, 4, 0x123456789ABCDEF0);
assert_eq!(read_u64_le_deep(&buf, 4), 0x123456789ABCDEF0);
}
#[test]
fn test_sha1_hash() {
let h = hash_sha1_deep(b"hello");
assert_eq!(h.len(), 20);
}
#[test]
fn test_md5_hash() {
let h = hash_md5_deep(b"hello");
assert_eq!(h.len(), 16);
}
#[test]
fn test_uuid_v4() {
let u = uuid_v4_deep(b"test");
assert_eq!(u.len(), 16);
assert_eq!(u[6] >> 4, 4); }
#[test]
fn test_hash_fast() {
let h = hash_fast_deep(b"test");
assert_eq!(h.len(), 8);
}
#[test]
fn test_fnv1a_hash() {
let h1 = compute_fnv1a_64_deep(b"hello");
let h2 = compute_fnv1a_64_deep(b"hello");
assert_eq!(h1, h2);
let h3 = compute_fnv1a_64_deep(b"world");
assert_ne!(h1, h3);
}
#[test]
fn test_get_c_string() {
let data = b"hello\0world\0";
assert_eq!(get_c_string_deep(data, 0), "hello");
assert_eq!(get_c_string_deep(data, 6), "world");
}
#[test]
fn test_full_elf_pipeline() {
let mut lld = X86LLDDeep::new_elf_x86_64();
let obj = make_elf_obj_64(&[
("_start", 0x401000, STB_GLOBAL, true),
("main", 0x401100, STB_GLOBAL, true),
("helper", 0x401200, STB_LOCAL, true),
]);
lld.input_objects.push(obj);
lld.entry_point = "_start".to_string();
lld.set_pic(false);
let output = lld.link_and_emit();
assert!(output.len() > 100);
assert_eq!(&output[0..4], &ELF_MAGIC_BYTES);
assert_eq!(output[EI_CLASS as usize], ELFCLASS64);
assert_eq!(read_u16_le_deep(&output, 0x12), EM_X86_64);
}
#[test]
fn test_full_coff_pipeline() {
let mut lld = X86LLDDeep::new_coff_x86_64();
let output = lld.link_and_emit();
assert!(output.len() > 120);
assert_eq!(&output[0..2], b"MZ");
let pe_off = read_u32_le_deep(&output, 0x3c) as usize;
assert_eq!(&output[pe_off..pe_off + 4], b"PE\0\0");
}
#[test]
fn test_all_arch_constructors() {
let l1 = X86LLDDeep::new_elf_x86_64();
assert_eq!(l1.output_format, X86OutputFormatDeep::Elf64);
let l2 = X86LLDDeep::new_elf_i386();
assert_eq!(l2.output_format, X86OutputFormatDeep::Elf32);
let l3 = X86LLDDeep::new_coff_x86_64();
assert_eq!(l3.output_format, X86OutputFormatDeep::Coff64);
let l4 = X86LLDDeep::new_coff_i386();
assert_eq!(l4.output_format, X86OutputFormatDeep::Coff32);
}
#[test]
fn test_coff_subsystem() {
let lld = X86LLDDeep::new_coff_x86_64();
assert_eq!(lld.coff_subsystem, 3); }
#[test]
fn test_symbol_visibility_enum() {
let v = X86SymbolVisibilityDeep::Hidden;
assert_eq!(v.to_elf_byte(), STV_HIDDEN);
}
#[test]
fn test_lto_options_default() {
let opts = X86LTOOptionsDeep::default();
assert_eq!(opts.cpu, "x86-64");
assert_eq!(opts.opt_level, X86LTOOptLevelDeep::O2);
}
#[test]
fn test_merge_sections_with_lld() {
let mut lld = X86LLDDeep::new_elf_x86_64();
let mut obj = X86InputObjectDeep {
filename: "test.o".to_string(),
data: Vec::new(),
sections: vec![X86InputSectionDeep {
name: ".text".to_string(),
data: vec![0x90; 16], sh_type: SHT_PROGBITS,
sh_flags: (SHF_ALLOC | SHF_EXECINSTR) as u64,
sh_addralign: 16,
section_index: 1,
sh_entsize: 0,
sh_link: 0,
sh_info: 0,
is_merge_strings: false,
is_gc_eligible: true,
comdat_group: None,
}],
symbols: vec![X86InputSymbolDeep {
name: "_start".to_string(),
value: 0,
size: 16,
binding: STB_GLOBAL,
sym_type: STT_FUNC,
visibility: STV_DEFAULT,
section_index: 1,
is_defined: true,
common_alignment: 0,
}],
relocations: Vec::new(),
machine: EM_X86_64,
elf_class: ELFCLASS64,
};
lld.input_objects.push(obj);
lld.link();
let text_sec = lld.sections.iter().find(|s| s.name == ".text");
assert!(text_sec.is_some());
assert_eq!(text_sec.unwrap().data.len(), 16);
}
#[test]
fn test_elf_writer_with_string_merge_section() {
let mut w = X86ELFWriterDeep::new_elf64_exec();
w.add_null_section();
let mut sec = make_output_section(".rodata.str1.1", b"hello\0world\0".to_vec(), 0);
sec.is_merge_strings = true;
sec.sh_entsize = 1;
w.add_section(sec);
let out = w.write();
assert!(out.len() > 100);
}
#[test]
fn test_elf_writer_many_sections() {
let mut w = X86ELFWriterDeep::new_elf64_exec();
w.add_null_section();
for i in 0..20 {
let name = format!(".text.{}", i);
w.add_section(make_output_section(&name, vec![0x90; 16], 6));
}
let out = w.write();
let shnum = read_u16_le_deep(&out, 0x3c);
assert!(shnum >= 21);
}
#[test]
fn test_elf_writer_build_id_section() {
let mut w = X86ELFWriterDeep::new_elf64_exec();
let bid = X86BuildID::new(X86BuildIdKindDeep::Sha1);
let note = bid.build_note_section(b"entropy");
w.add_note("GNU", NT_GNU_BUILD_ID, ¬e);
let out = w.write();
assert!(out.len() > 64);
}
#[test]
fn test_elf_writer_set_os_abi() {
let mut w = X86ELFWriterDeep::new_elf64_exec();
w.set_os_abi(ELFOSABI_LINUX);
assert_eq!(w.os_abi, ELFOSABI_LINUX);
let hdr = w.write_elf_header_only();
assert_eq!(hdr[EI_OSABI as usize], ELFOSABI_LINUX);
}
#[test]
fn test_elf_writer_with_rela_dyn() {
let mut w = X86ELFWriterDeep::new_elf64_exec();
w.add_rela_dyn(X86InputRelocationDeep {
offset: 0x1000,
symbol_index: 1,
rel_type: R_X86_64_64,
addend: 42,
section_index: 0,
});
w.add_rela_plt(X86InputRelocationDeep {
offset: 0x2000,
symbol_index: 2,
rel_type: R_X86_64_JUMP_SLOT,
addend: 0,
section_index: 0,
});
assert!(w.rela_dyn.len() == 1);
assert!(w.rela_plt.len() == 1);
}
#[test]
fn test_dl_tlsdesc_enable() {
let mut dl = X86DynamicLinker::new();
dl.enable_tlsdesc(true);
assert!(dl.tlsdesc_enabled);
dl.add_tlsdesc("tls_var", 0x40, 0x5000);
assert_eq!(dl.tlsdesc_entries.len(), 1);
}
#[test]
fn test_dl_rpath() {
let mut dl = X86DynamicLinker::new();
dl.set_rpath("/usr/local/lib");
assert_eq!(dl.rpath, vec!["/usr/local/lib"]);
}
#[test]
fn test_dl_runpath() {
let mut dl = X86DynamicLinker::new();
dl.set_runpath("$ORIGIN/../lib");
assert_eq!(dl.runpath, vec!["$ORIGIN/../lib"]);
}
#[test]
fn test_dl_soname() {
let mut dl = X86DynamicLinker::new();
dl.soname = Some("libtest.so.1".to_string());
assert!(dl.soname.is_some());
}
#[test]
fn test_dl_empty_plt_no_data() {
let mut dl = X86DynamicLinker::new();
dl.got_addr = 0x400000;
dl.plt_addr = 0x401000;
let plt = dl.build_plt();
assert!(plt.is_empty());
}
#[test]
fn test_dl_relacount() {
let mut dl = X86DynamicLinker::new();
dl.relacount = 5;
dl.build_dynamic_entries(0x1000, 0x2000, 0x3000);
assert!(dl
.dynamic_entries
.iter()
.any(|&(tag, _)| tag == DT_RELACOUNT as i64));
}
#[test]
fn test_dl_interp() {
let mut dl = X86DynamicLinker::new();
dl.set_interp("/lib64/ld-linux-x86-64.so.2");
assert_eq!(dl.interp, Some("/lib64/ld-linux-x86-64.so.2".to_string()));
}
#[test]
fn test_dl_has_dynamic_entries_after_build() {
let mut dl = X86DynamicLinker::new();
dl.add_needed("libc.so.6");
dl.build_dynamic_entries(0, 0, 0);
assert!(!dl.dynamic_entries.is_empty());
}
#[test]
fn test_dl_sysv_hash_empty_syms() {
let dl = X86DynamicLinker::new();
let hash = dl.build_sysv_hash(&[]);
assert!(!hash.is_empty()); }
#[test]
fn test_dl_gnu_hash_empty_syms() {
let dl = X86DynamicLinker::new();
let hash = dl.build_gnu_hash(&[], 5);
assert!(hash.is_empty());
}
#[test]
fn test_sym_resolver_add_shared_symbol() {
let mut sr = X86SymbolResolver::new();
sr.add_shared_symbol("libc.so", "printf", 0, 0, true);
sr.set_library_order(&["libc.so".to_string()]);
sr.reference("printf");
let diags = sr.resolve_all();
assert!(diags.is_empty()); let sym = sr.get("printf");
assert!(sym.is_some());
}
#[test]
fn test_sym_resolver_add_version() {
let mut sr = X86SymbolResolver::new();
sr.add_version_definition("libm.so.6", vec![("GLIBC_2.0".to_string(), 0)]);
sr.add_version_requirement("libc.so.6", vec![("GLIBC_2.2.5".to_string(), 0)]);
assert_eq!(sr.verdef.len(), 1);
assert_eq!(sr.verneed.len(), 1);
}
#[test]
fn test_sym_resolver_archive_extraction() {
let mut sr = X86SymbolResolver::new();
let member = X86ArchiveMemberDeep {
name: "foo.o".to_string(),
data: vec![0; 32],
symbols: vec!["bar".to_string()],
timestamp: 0,
uid: 0,
gid: 0,
mode: 0,
};
sr.add_archive(vec![member]);
sr.reference("bar");
sr.resolve_all();
assert!(sr.get("bar").is_some());
}
#[test]
fn test_sym_resolver_common_allocation_order() {
let mut sr = X86SymbolResolver::new();
sr.define_common("buf_a", 128, 8);
sr.define_common("buf_b", 512, 64);
sr.define_common("buf_c", 64, 32);
let allocs = sr.resolve_commons();
assert!(allocs.len() == 3);
assert_eq!(allocs[0].0, "buf_b"); }
#[test]
fn test_sym_resolver_dynamic_list() {
let mut sr = X86SymbolResolver::new();
sr.add_dynamic_list_entry("foo*", true, true);
assert!(sr.is_exported("foobar"));
assert!(!sr.is_exported("baz"));
}
#[test]
fn test_sym_resolver_dynamic_list_exact() {
let mut sr = X86SymbolResolver::new();
sr.add_dynamic_list_entry("exact_match", false, true);
assert!(sr.is_exported("exact_match"));
assert!(!sr.is_exported("exact_match_suffix"));
}
#[test]
fn test_sym_resolver_prio_override() {
let mut sr = X86SymbolResolver::new();
sr.define(
"sym",
0x1000,
10,
STB_GLOBAL,
STT_FUNC,
X86SymbolVisibilityDeep::Default,
None,
true,
false,
false,
);
sr.define(
"sym",
0x2000,
10,
STB_GLOBAL,
STT_FUNC,
X86SymbolVisibilityDeep::Default,
None,
true,
false,
false,
);
let s = sr.get("sym").unwrap();
match &s.def {
X86SymbolDefDeep::Defined { value, .. } => assert_eq!(*value, 0x1000),
_ => panic!("Expected defined"),
}
}
#[test]
fn test_sym_resolver_defined_names() {
let mut sr = X86SymbolResolver::new();
sr.define(
"a",
0,
0,
STB_GLOBAL,
STT_NOTYPE,
X86SymbolVisibilityDeep::Default,
None,
false,
false,
false,
);
sr.define(
"b",
0,
0,
STB_GLOBAL,
STT_NOTYPE,
X86SymbolVisibilityDeep::Default,
None,
false,
false,
false,
);
let names: HashSet<_> = sr.defined_names().cloned().collect();
assert!(names.contains("a"));
assert!(names.contains("b"));
}
#[test]
fn test_sym_resolver_symbols_iter() {
let mut sr = X86SymbolResolver::new();
sr.define(
"x",
1,
0,
STB_GLOBAL,
STT_NOTYPE,
X86SymbolVisibilityDeep::Default,
None,
false,
false,
false,
);
let count = sr.symbols().count();
assert_eq!(count, 1);
}
#[test]
fn test_relax_no_gotpcrelx_when_disabled() {
let mut re = X86RelaxationEngine::new();
re.set_gotpcrelx(false);
let data = vec![0x8b, 0x05, 0, 0, 0, 0];
let kind = re.analyze_relocation(0, 2, R_X86_64_GOTPCRELX, "f", 0, &data, true);
assert!(kind.is_none());
}
#[test]
fn test_relax_no_tls_when_disabled() {
let mut re = X86RelaxationEngine::new();
re.set_tls_relax(false);
let data = vec![0; 8];
let kind = re.analyze_relocation(0, 4, R_X86_64_TLSGD, "t", 0, &data, true);
assert!(kind.is_none());
}
#[test]
fn test_relax_no_tail_call_when_disabled() {
let mut re = X86RelaxationEngine::new();
re.set_tail_call(false);
let data = vec![0xe8, 0, 0, 0, 0, 0xc3];
let kind = re.analyze_tail_call(0, 0, &data, "f", 0);
assert!(kind.is_none());
}
#[test]
fn test_relax_rex_prefix_mov_to_lea() {
let re = X86RelaxationEngine::new();
let mut data = vec![0x44, 0x8b, 0x05, 0, 0, 0, 0];
let ok = re.apply_gotpcrelx_mov_to_lea(&mut data, 3, 0, 0);
assert!(ok);
assert_eq!(data[1], 0x8d);
}
#[test]
fn test_relax_call_to_jmp_with_ret_following() {
let re = X86RelaxationEngine::new();
let data = vec![0xe8, 0, 0, 0, 0, 0xc3];
let kind = re.analyze_tail_call(0, 0, &data, "f", 0);
assert!(kind.is_some());
assert_eq!(kind.unwrap(), X86RelaxKind::CallToJmp);
}
#[test]
fn test_relax_call_not_tail_when_no_ret() {
let re = X86RelaxationEngine::new();
let data = vec![0xe8, 0, 0, 0, 0, 0x90];
let kind = re.analyze_tail_call(0, 0, &data, "f", 0);
assert!(kind.is_none());
}
#[test]
fn test_relax_clear_stats() {
let mut re = X86RelaxationEngine::new();
re.record_relaxation(
0,
0,
R_X86_64_GOTPCRELX,
X86RelaxKind::GOTPCRELX_MovToLea,
"f",
0,
);
let mut data = vec![0x8b, 0x05, 0, 0, 0, 0];
let mut offs = HashMap::new();
offs.insert("f".to_string(), 0x400000);
re.apply_relaxations(&mut data, 0, &offs, 0x401000);
assert_eq!(re.stats().total_relaxed, 1);
re.clear();
assert_eq!(re.stats().total_relaxed, 0);
}
#[test]
fn test_relax_tls_gd_to_ie_shared_sym() {
let mut re = X86RelaxationEngine::new();
let data = vec![0; 8];
let kind = re.analyze_relocation(0, 0, R_X86_64_TLSGD, "t", 0, &data, false);
assert!(kind.is_some());
assert_eq!(kind.unwrap(), X86RelaxKind::TlsGdToIe);
}
#[test]
fn test_relax_tls_ie_stays_for_shared() {
let mut re = X86RelaxationEngine::new();
let data = vec![0; 8];
let kind = re.analyze_relocation(0, 0, R_X86_64_GOTTPOFF, "t", 0, &data, false);
assert!(kind.is_none()); }
#[test]
fn test_relax_tls_ld_to_le() {
let mut re = X86RelaxationEngine::new();
let data = vec![0; 8];
let kind = re.analyze_relocation(0, 0, R_X86_64_TLSLD, "t", 0, &data, true);
assert!(kind.is_some());
assert_eq!(kind.unwrap(), X86RelaxKind::TlsLdToLe);
}
#[test]
fn test_relax_stats_accumulate() {
let mut re = X86RelaxationEngine::new();
re.record_relaxation(
0,
0,
R_X86_64_GOTPCRELX,
X86RelaxKind::GOTPCRELX_ToPC32,
"a",
0,
);
re.record_relaxation(0, 8, R_X86_64_TLSGD, X86RelaxKind::TlsGdToLe, "b", 0);
let mut data = vec![0x8b, 0x05, 0, 0, 0, 0, 0; 16];
let mut offs = HashMap::new();
offs.insert("a".to_string(), 0x400000);
offs.insert("b".to_string(), 0x400010);
re.apply_relaxations(&mut data, 0, &offs, 0x401000);
assert_eq!(re.stats().gotpcrelx_to_pc32, 1);
assert_eq!(re.stats().tls_gd_to_le, 1);
assert_eq!(re.stats().total_relaxed, 2);
}
#[test]
fn test_lto_empty_full_lto() {
let mut lto = X86LTOPluginDeep::default();
let outputs = lto.run_full_lto(&[], &HashSet::new());
assert!(outputs.is_empty());
}
#[test]
fn test_lto_empty_thin_lto() {
let mut lto = X86LTOPluginDeep::default();
let outputs = lto.run_thin_lto(&[], &HashSet::new());
assert!(outputs.is_empty());
}
#[test]
fn test_lto_thin_lto_cache() {
let mut lto = X86LTOPluginDeep::default();
lto.set_cache_dir("/tmp/lto_cache");
let data = b"BC\xC0\xDE".to_vec();
let inputs = vec![("a.bc".to_string(), data.clone())];
let exported = HashSet::new();
let _out1 = lto.run_thin_lto(&inputs, &exported);
assert_eq!(lto.stats().cache_misses, 1);
let _out2 = lto.run_thin_lto(&inputs, &exported);
assert_eq!(lto.stats().cache_hits, 1);
assert_eq!(lto.stats().cache_misses, 1);
}
#[test]
fn test_lto_clear_cache() {
let mut lto = X86LTOPluginDeep::default();
let data = b"BC\xC0\xDE".to_vec();
let inputs = vec![("a.bc".to_string(), data.clone())];
let exported = HashSet::new();
lto.run_thin_lto(&inputs, &exported);
lto.clear_cache();
let _ = lto.run_thin_lto(&inputs, &exported);
assert_eq!(lto.stats().cache_misses, 2);
}
#[test]
fn test_lto_is_thin_bitcode() {
let lto = X86LTOPluginDeep::default();
assert!(!lto.is_thin_lto_bitcode(b"BC\xC0\xDE"));
}
#[test]
fn test_lto_stats_initial() {
let lto = X86LTOPluginDeep::default();
assert_eq!(lto.stats().bitcode_files_detected, 0);
assert_eq!(lto.stats().objects_merged, 0);
assert_eq!(lto.cache_hit_ratio(), 0.0);
}
#[test]
fn test_lto_summary_thin_link_flag() {
let lto = X86LTOPluginDeep::default();
let summary = lto.compute_module_summary(b"BC\xC0\xDE\x00");
assert!(summary.thin_link);
}
#[test]
fn test_lto_multiple_bitcode_full() {
let mut lto = X86LTOPluginDeep::default();
let inputs = vec![
("a.bc".to_string(), b"BC\xC0\xDE\xAA".to_vec()),
("b.bc".to_string(), b"BC\xC0\xDE\xBB".to_vec()),
("c.bc".to_string(), b"BC\xC0\xDE\xCC".to_vec()),
];
let out = lto.run_full_lto(&inputs, &HashSet::new());
assert!(!out.is_empty());
assert!(lto.stats().objects_merged >= 2);
}
#[test]
fn test_gc_with_comdat() {
let mut gc = X86GCSectionsDeep::new();
gc.add_comdat_group("grp1", 2, vec![0, 1]);
let mut sections = vec![
make_output_section(".init", vec![1], 0),
make_output_section(".text.comdat_member", vec![2], 6),
];
let removed = gc.run(&mut sections, &[], &HashSet::new());
assert_eq!(removed, 0);
}
#[test]
fn test_gc_keep_eh() {
let mut gc = X86GCSectionsDeep::new();
gc.keep_eh = true;
let mut sections = vec![
make_output_section(".text.foo", vec![1], 6),
make_output_section(".eh_frame", vec![0; 16], 2),
];
let removed = gc.run(&mut sections, &["bar".to_string()], &HashSet::new());
assert!(!sections[1].data.is_empty());
}
#[test]
fn test_gc_with_referenced_symbols() {
let mut gc = X86GCSectionsDeep::new();
let mut sections = vec![
make_output_section(".text.foo", b"call bar".to_vec(), 6),
make_output_section(".text.bar", vec![0xC3], 6),
];
let mut refs = HashSet::new();
refs.insert("bar".to_string());
let removed = gc.run(&mut sections, &[], &refs);
assert_eq!(removed, 0);
}
#[test]
fn test_icf_safe_prevents_fold_of_address_taken() {
let mut icf = X86ICFDeep::new().with_safety(X86ICFSafetyLevel::Safe);
icf.mark_address_taken("func_a");
let data = vec![1u8; 32];
let mut sections = vec![
make_output_section(".text.func_a", data.clone(), 6),
make_output_section(".text.func_b", data.clone(), 6),
];
let removed = icf.run(&mut sections);
assert_eq!(removed, 0);
}
#[test]
fn test_icf_all_mode_folds_anyway() {
let mut icf = X86ICFDeep::new().with_safety(X86ICFSafetyLevel::All);
let data = vec![1u8; 32];
let mut sections = vec![
make_output_section(".text.func_a", data.clone(), 6),
make_output_section(".text.func_b", data.clone(), 6),
];
let removed = icf.run(&mut sections);
assert_eq!(removed, 1);
}
#[test]
fn test_icf_rodata_fold() {
let mut icf = X86ICFDeep::new();
icf.fold_rodata = true;
let data = vec![42u8; 32];
let mut sections = vec![
make_output_section(".rodata.x", data.clone(), 2),
make_output_section(".rodata.y", data.clone(), 2),
];
let removed = icf.run(&mut sections);
assert_eq!(removed, 1);
}
#[test]
fn test_icf_different_flags_no_fold() {
let mut icf = X86ICFDeep::new().with_safety(X86ICFSafetyLevel::All);
let data = vec![1u8; 32];
let mut sections = vec![
make_output_section(".text.a", data.clone(), 6),
make_output_section(".text.b", data.clone(), 3),
];
let removed = icf.run(&mut sections);
assert_eq!(removed, 0); }
#[test]
fn test_full_pipeline_with_gc_and_icf() {
let mut lld = X86LLDDeep::new_elf_x86_64();
lld.set_gc_sections(true);
lld.set_icf(true);
let obj = make_elf_obj_64(&[
("_start", 0x401000, STB_GLOBAL, true),
("main", 0x401080, STB_GLOBAL, true),
]);
lld.input_objects.push(obj);
lld.link();
let output = lld.emit();
assert!(output.len() > 100);
}
#[test]
fn test_pipeline_with_shared_lib() {
let mut lld = X86LLDDeep::new_elf_x86_64();
let obj = make_elf_obj_64(&[
("_start", 0x401000, STB_GLOBAL, true),
("main", 0x401100, STB_GLOBAL, true),
]);
lld.input_objects.push(obj);
lld.symbol_resolver.reference("printf");
let lib = X86SharedLibraryDeep {
name: "libc.so.6".to_string(),
soname: Some("libc.so.6".to_string()),
symbols: vec![X86InputSymbolDeep {
name: "printf".to_string(),
value: 0,
size: 0,
binding: STB_GLOBAL,
sym_type: STT_FUNC,
visibility: STV_DEFAULT,
section_index: 0,
is_defined: false,
common_alignment: 0,
}],
needed: Vec::new(),
versions: Vec::new(),
};
lld.add_shared_library(lib);
lld.link();
let output = lld.emit();
assert!(output.len() > 100);
}
#[test]
fn test_pipeline_with_merge_strings() {
let mut lld = X86LLDDeep::new_elf_x86_64();
let mut obj = X86InputObjectDeep {
filename: "rodata.o".to_string(),
data: Vec::new(),
sections: vec![X86InputSectionDeep {
name: ".rodata.str1.1".to_string(),
data: b"hello\0world\0test\0label\0".to_vec(),
sh_type: SHT_PROGBITS,
sh_flags: (SHF_ALLOC | SHF_MERGE | SHF_STRINGS) as u64,
sh_addralign: 1,
section_index: 1,
sh_entsize: 1,
sh_link: 0,
sh_info: 0,
is_merge_strings: true,
is_gc_eligible: false,
comdat_group: None,
}],
symbols: vec![],
relocations: Vec::new(),
machine: EM_X86_64,
elf_class: ELFCLASS64,
};
lld.input_objects.push(obj);
lld.link();
let ro = lld.sections.iter().find(|s| s.name == ".rodata.str1.1");
assert!(ro.is_some());
}
#[test]
fn test_pipeline_build_id_emitted() {
let mut lld = X86LLDDeep::new_elf_x86_64();
lld.set_build_id(X86BuildIdKindDeep::Sha1);
let obj = make_elf_obj_64(&[("_start", 0x401000, STB_GLOBAL, true)]);
lld.input_objects.push(obj);
lld.link();
let output = lld.emit();
let text = String::from_utf8_lossy(&output);
assert!(text.contains("GNU"));
}
#[test]
fn test_pipeline_relaxation_incorporated() {
let mut lld = X86LLDDeep::new_elf_x86_64();
lld.relaxation.set_gotpcrelx(true);
let mut sec = X86InputSectionDeep {
name: ".text".to_string(),
data: vec![
0x48, 0x8b, 0x05, 0xfb, 0xff, 0xff, 0xff, 0xc3, ],
sh_type: SHT_PROGBITS,
sh_flags: (SHF_ALLOC | SHF_EXECINSTR) as u64,
sh_addralign: 16,
section_index: 1,
sh_entsize: 0,
sh_link: 0,
sh_info: 0,
is_merge_strings: false,
is_gc_eligible: true,
comdat_group: None,
};
let mut obj = X86InputObjectDeep {
filename: "code.o".to_string(),
data: Vec::new(),
sections: vec![sec],
symbols: vec![X86InputSymbolDeep {
name: "_start".to_string(),
value: 0,
size: 8,
binding: STB_GLOBAL,
sym_type: STT_FUNC,
visibility: STV_DEFAULT,
section_index: 1,
is_defined: true,
common_alignment: 0,
}],
relocations: vec![X86InputRelocationDeep {
offset: 3,
symbol_index: 1,
rel_type: R_X86_64_GOTPCRELX,
addend: -4,
section_index: 1,
}],
machine: EM_X86_64,
elf_class: ELFCLASS64,
};
lld.input_objects.push(obj);
lld.link();
let output = lld.emit();
assert!(output.len() > 64);
}
#[test]
fn test_coff_dos_stub_length() {
let mut lld = X86LLDDeep::new_coff_x86_64();
let output = lld.emit();
let pe_off = read_u32_le_deep(&output, 0x3c) as usize;
assert_eq!(pe_off, 128);
}
#[test]
fn test_coff_machine_header() {
let mut lld = X86LLDDeep::new_coff_x86_64();
let output = lld.emit();
let pe_off = read_u32_le_deep(&output, 0x3c) as usize;
let machine = read_u16_le_deep(&output, pe_off + 4);
assert_eq!(machine, IMAGE_FILE_MACHINE_AMD64);
}
#[test]
fn test_coff_i386_machine_header() {
let mut lld = X86LLDDeep::new_coff_i386();
let output = lld.emit();
let pe_off = read_u32_le_deep(&output, 0x3c) as usize;
let machine = read_u16_le_deep(&output, pe_off + 4);
assert_eq!(machine, IMAGE_FILE_MACHINE_I386);
}
#[test]
fn test_coff_optional_header_magic() {
let mut lld = X86LLDDeep::new_coff_x86_64();
let output = lld.emit();
let pe_off = read_u32_le_deep(&output, 0x3c) as usize;
let magic = read_u16_le_deep(&output, pe_off + 24);
assert_eq!(magic, PE32PLUS_MAGIC);
}
#[test]
fn test_coff_i386_optional_header_magic() {
let mut lld = X86LLDDeep::new_coff_i386();
let output = lld.emit();
let pe_off = read_u32_le_deep(&output, 0x3c) as usize;
let magic = read_u16_le_deep(&output, pe_off + 24);
assert_eq!(magic, PE32_MAGIC);
}
#[test]
fn test_coff_base_address_x64() {
let mut lld = X86LLDDeep::new_coff_x86_64();
let output = lld.emit();
let pe_off = read_u32_le_deep(&output, 0x3c) as usize;
let image_base = read_u64_le_deep(&output, pe_off + 30);
assert_eq!(image_base, 0x140000000);
}
#[test]
fn test_coff_section_align_in_header() {
let mut lld = X86LLDDeep::new_coff_x86_64();
let output = lld.emit();
let pe_off = read_u32_le_deep(&output, 0x3c) as usize;
let section_align = read_u32_le_deep(&output, pe_off + 38);
assert_eq!(section_align, COFF_SECTION_ALIGNMENT as u32);
}
#[test]
fn test_macho_magic() {
let mut lld = X86LLDDeep::new_coff_x86_64();
lld.output_format = X86OutputFormatDeep::MachO64;
let output = lld.emit();
assert_eq!(&output[0..4], &0xfeedfacfu32.to_le_bytes());
}
#[test]
fn test_macho_cpu_type() {
let mut lld = X86LLDDeep::new_coff_x86_64();
lld.output_format = X86OutputFormatDeep::MachO64;
let output = lld.emit();
let cpu_type = read_u32_le_deep(&output, 4);
assert_eq!(cpu_type, 0x01000007); }
#[test]
fn test_macho_file_type() {
let mut lld = X86LLDDeep::new_coff_x86_64();
lld.output_format = X86OutputFormatDeep::MachO64;
let output = lld.emit();
let file_type = read_u32_le_deep(&output, 12);
assert_eq!(file_type, 2); }
#[test]
fn test_wildcard_complex() {
assert!(wildcard_match_deep("foo*bar?baz", "fooxxxbarQbaz"));
assert!(!wildcard_match_deep("foo*bar?baz", "fooxxxbarQQbaz"));
}
#[test]
fn test_wildcard_star_only() {
assert!(wildcard_match_deep("*", "anything"));
assert!(wildcard_match_deep("*", ""));
}
#[test]
fn test_wildcard_mixed() {
assert!(wildcard_match_deep("*text*", ".text.hot"));
assert!(wildcard_match_deep("*.o", "file.o"));
assert!(!wildcard_match_deep("*.o", "file.c"));
}
#[test]
fn test_e2e_elf_basic_exec() {
let mut lld = X86LLDDeep::new_elf_x86_64();
let obj = make_elf_obj_64(&[("_start", 0x401000, STB_GLOBAL, true)]);
lld.input_objects.push(obj);
lld.set_entry("_start");
let elf = lld.link_and_emit();
assert_eq!(&elf[0..4], &[0x7f, b'E', b'L', b'F']);
assert_eq!(elf[EI_CLASS as usize], ELFCLASS64);
assert_eq!(elf[EI_DATA as usize], ELFDATA2LSB);
let e_type = read_u16_le_deep(&elf, 0x10);
assert_eq!(e_type, ET_EXEC);
let e_machine = read_u16_le_deep(&elf, 0x12);
assert_eq!(e_machine, EM_X86_64);
}
#[test]
fn test_e2e_elf_shared_library() {
let mut lld = X86LLDDeep::new_elf_x86_64();
lld.set_pic(true);
let obj = make_elf_obj_64(&[("foo", 0x1000, STB_GLOBAL, true)]);
lld.input_objects.push(obj);
let elf = lld.link_and_emit();
let e_type = read_u16_le_deep(&elf, 0x10);
assert_eq!(e_type, ET_DYN);
}
#[test]
fn test_e2e_coff_console_app() {
let mut lld = X86LLDDeep::new_coff_x86_64();
let exe = lld.link_and_emit();
assert_eq!(&exe[0..2], b"MZ");
let pe_off = read_u32_le_deep(&exe, 0x3c) as usize;
assert_eq!(&exe[pe_off..pe_off + 2], b"PE");
}
#[test]
fn test_e2e_macho_exec() {
let mut lld = X86LLDDeep::new_coff_x86_64();
lld.output_format = X86OutputFormatDeep::MachO64;
let macho = lld.link_and_emit();
let magic = read_u32_le_deep(&macho, 0);
assert_eq!(magic, 0xfeedfacf);
}
#[test]
fn test_archive_member_create() {
let member = X86ArchiveMemberDeep {
name: "test.o".to_string(),
data: vec![0u8; 64],
symbols: vec!["foo".to_string(), "bar".to_string()],
timestamp: 1234567890,
uid: 1000,
gid: 1000,
mode: 0o644,
};
assert_eq!(member.symbols.len(), 2);
assert_eq!(member.data.len(), 64);
}
#[test]
fn test_multiple_archives_added() {
let mut lld = X86LLDDeep::new_elf_x86_64();
let lib = vec![X86ArchiveMemberDeep {
name: "a.o".to_string(),
data: vec![0; 16],
symbols: vec!["bar".to_string()],
timestamp: 0,
uid: 0,
gid: 0,
mode: 0o644,
}];
lld.add_archive(lib);
assert_eq!(lld.input_archives.len(), 1);
}
#[test]
fn test_section_ordering_preserved() {
let mut lld = X86LLDDeep::new_elf_x86_64();
let mut obj = X86InputObjectDeep {
filename: "order.o".to_string(),
data: Vec::new(),
sections: vec![
X86InputSectionDeep {
name: ".text".to_string(),
data: vec![0x90; 4],
sh_type: SHT_PROGBITS,
sh_flags: (SHF_ALLOC | SHF_EXECINSTR) as u64,
sh_addralign: 16,
section_index: 1,
sh_entsize: 0,
sh_link: 0,
sh_info: 0,
is_merge_strings: false,
is_gc_eligible: true,
comdat_group: None,
},
X86InputSectionDeep {
name: ".data".to_string(),
data: vec![0x42; 4],
sh_type: SHT_PROGBITS,
sh_flags: (SHF_ALLOC | SHF_WRITE) as u64,
sh_addralign: 8,
section_index: 2,
sh_entsize: 0,
sh_link: 0,
sh_info: 0,
is_merge_strings: false,
is_gc_eligible: false,
comdat_group: None,
},
X86InputSectionDeep {
name: ".bss".to_string(),
data: Vec::new(),
sh_type: SHT_NOBITS,
sh_flags: (SHF_ALLOC | SHF_WRITE) as u64,
sh_addralign: 16,
section_index: 3,
sh_entsize: 0,
sh_link: 0,
sh_info: 0,
is_merge_strings: false,
is_gc_eligible: false,
comdat_group: None,
},
],
symbols: vec![],
relocations: Vec::new(),
machine: EM_X86_64,
elf_class: ELFCLASS64,
};
lld.input_objects.push(obj);
lld.link();
let names: Vec<&str> = lld.sections.iter().map(|s| s.name.as_str()).collect();
assert!(names.contains(&".text"));
assert!(names.contains(&".data"));
assert!(names.contains(&".bss"));
}
#[test]
fn test_different_arch_base_addrs() {
let x64 = X86LLDArchDeep::X86_64;
let i32 = X86LLDArchDeep::I386;
assert_eq!(x64.default_image_base(), X86_64_DEFAULT_IMAGE_BASE);
assert_eq!(i32.default_image_base(), I386_DEFAULT_IMAGE_BASE);
}
#[test]
fn test_symbol_def_is_ifunc() {
let def = X86SymbolDefDeep::Defined {
value: 0x1000,
size: 16,
section_name: None,
is_function: true,
is_ifunc: true,
is_tls: false,
};
assert!(def.is_ifunc());
}
#[test]
fn test_lto_options_override() {
let mut opts = X86LTOOptionsDeep::default();
opts.cpu = "znver4".to_string();
opts.opt_level = X86LTOOptLevelDeep::O3;
assert_eq!(opts.cpu, "znver4");
assert_eq!(opts.opt_level, X86LTOOptLevelDeep::O3);
}
#[test]
fn test_relax_kind_names_complete() {
assert_eq!(
X86RelaxationEngine::relax_kind_name(X86RelaxKind::TlsGdToIe),
"TLS GD→IE"
);
assert_eq!(
X86RelaxationEngine::relax_kind_name(X86RelaxKind::TlsGdToLe),
"TLS GD→LE"
);
assert_eq!(
X86RelaxationEngine::relax_kind_name(X86RelaxKind::TlsIeToLe),
"TLS IE→LE"
);
assert_eq!(
X86RelaxationEngine::relax_kind_name(X86RelaxKind::TlsLdToLe),
"TLS LD→LE"
);
}
#[test]
fn test_dynamic_linker_versioning() {
let mut dl = X86DynamicLinker::new();
dl.verdef
.push(("VERS_1".to_string(), vec![("GLIBC_2.0".to_string(), 0u16)]));
dl.verneed.push((
"libc.so.6".to_string(),
vec![("GLIBC_2.2.5".to_string(), 0u16, 0u16)],
));
dl.versym = vec![0, 1, 2];
dl.build_dynamic_entries(0x1000, 0x2000, 0x3000);
assert!(dl
.dynamic_entries
.iter()
.any(|&(tag, _)| tag == DT_VERDEF as i64));
assert!(dl
.dynamic_entries
.iter()
.any(|&(tag, _)| tag == DT_VERSYM as i64));
}
#[test]
fn test_elf_writer_symtab_section() {
let mut w = X86ELFWriterDeep::new_elf64_exec();
w.add_null_section();
w.add_symbol(
"main",
0x400100,
50,
STB_GLOBAL,
STT_FUNC,
1,
STV_DEFAULT,
false,
);
w.add_symbol(
"printf",
0,
0,
STB_GLOBAL,
STT_NOTYPE,
0,
STV_DEFAULT,
false,
);
assert_eq!(w.symtab.len(), 2);
}
#[test]
fn test_elf_writer_dynsym_section() {
let mut w = X86ELFWriterDeep::new_elf64_exec();
w.add_null_section();
w.add_symbol("foo", 0x1000, 8, STB_GLOBAL, STT_FUNC, 1, STV_DEFAULT, true);
assert_eq!(w.dynsym.len(), 1);
}
#[test]
fn test_elf_writer_estimate_size() {
let w = X86ELFWriterDeep::new_elf64_exec();
let est = w.estimate_size();
assert!(est > 64);
}
#[test]
fn test_elf_writer_find_indices() {
let mut w = X86ELFWriterDeep::new_elf64_exec();
w.add_null_section();
w.add_section(make_output_section(".symtab", vec![], 0));
w.add_section(make_output_section(".strtab", vec![], 0));
let s_idx = w.find_symtab_index();
let t_idx = w.find_strtab_index();
assert!(s_idx.is_some());
assert!(t_idx.is_some());
}
#[test]
fn test_crel_large_values() {
let mut crel = X86CREL::new();
crel.add_relocation(0xFFFFFFFF, 1024, R_X86_64_64, -42, true);
let encoded = crel.encode();
let decoded = X86CREL::decode(&encoded).unwrap();
let expanded = decoded.expand();
assert_eq!(expanded.len(), 1);
}
#[test]
fn test_crel_no_addend_entries() {
let mut crel = X86CREL::new();
crel.add_relocation(0, 1, R_X86_64_RELATIVE, 0, false);
let encoded = crel.encode();
let decoded = X86CREL::decode(&encoded).unwrap();
assert_eq!(decoded.len(), 1);
let expanded = decoded.expand();
assert!(!expanded[0].4); }
#[test]
fn test_crel_invalid_data() {
let decoded = X86CREL::decode(&[0xFF, 0xFF, 0xFF]);
assert!(decoded.is_none());
}
#[test]
fn test_merge_constants_zero_entsize() {
let mut m = X86MergeSections::new(X86MergeKind::Constants, 8, 0);
let mapping = m.add_constants(&[1, 2, 3, 4, 5, 6, 7, 8], 8);
assert!(mapping.is_empty());
}
#[test]
fn test_merge_constants_dedup_large() {
let mut m = X86MergeSections::new(X86MergeKind::Constants, 8, 8);
let data = vec![
1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
];
m.add_constants(&data, 8);
assert_eq!(m.unique_count(), 2);
}
#[test]
fn test_merge_strings_tail_real() {
let mut m = X86MergeSections::new(X86MergeKind::Strings, 1, 1).with_tail_merge(true);
m.add_strings(b"abc\0", 1);
m.add_strings(b"bc\0", 1);
assert!(m.output_size() >= 4);
}
#[test]
fn test_build_id_deterministic() {
let bid = X86BuildID::new(X86BuildIdKindDeep::Sha1);
let h1 = bid.compute(b"test");
let h2 = bid.compute(b"test");
assert_eq!(h1, h2);
}
#[test]
fn test_build_id_different_inputs() {
let bid = X86BuildID::new(X86BuildIdKindDeep::Md5);
let h1 = bid.compute(b"hello");
let h2 = bid.compute(b"world");
assert_ne!(h1, h2);
}
#[test]
fn test_build_id_note_structure() {
let bid = X86BuildID::new(X86BuildIdKindDeep::Sha1);
let note = bid.build_note_section(b"abc");
let namesz = u32::from_le_bytes(note[0..4].try_into().unwrap());
assert_eq!(namesz, 4);
let descsz = u32::from_le_bytes(note[4..8].try_into().unwrap());
assert_eq!(descsz, 20);
let ntype = u32::from_le_bytes(note[8..12].try_into().unwrap());
assert_eq!(ntype, NT_GNU_BUILD_ID);
assert_eq!(¬e[12..16], b"GNU\0");
}
#[test]
fn test_sym_wrap_real_symbol_access() {
let mut sr = X86SymbolResolver::new();
sr.add_wrap("malloc");
assert!(sr.wrap_map.get("malloc").is_some());
}
#[test]
fn test_sym_multiple_defsym() {
let mut sr = X86SymbolResolver::new();
sr.add_defsym("a", 0x100);
sr.add_defsym("b", 0x200);
sr.add_defsym("c", 0x300);
sr.resolve_all();
assert!(sr.get("a").is_some());
assert!(sr.get("b").is_some());
assert!(sr.get("c").is_some());
}
#[test]
fn test_sym_define_with_wrap_interaction() {
let mut sr = X86SymbolResolver::new();
sr.add_wrap("free");
sr.define(
"free",
0x1000,
16,
STB_GLOBAL,
STT_FUNC,
X86SymbolVisibilityDeep::Default,
None,
true,
false,
false,
);
assert!(sr.get("free").is_some());
}
#[test]
fn test_lld_set_output_path() {
let mut lld = X86LLDDeep::new_elf_x86_64();
lld.set_output_path("/tmp/out");
assert_eq!(lld.output_path, "/tmp/out");
}
#[test]
fn test_lld_with_map_file() {
let mut lld = X86LLDDeep::new_elf_x86_64();
lld.map_file = Some("output.map".to_string());
assert!(lld.map_file.is_some());
}
#[test]
fn test_lld_hash_style() {
let mut lld = X86LLDDeep::new_elf_x86_64();
lld.set_hash_style(X86HashStyleDeep::Gnu);
assert_eq!(lld.dynamic_linker.hash_style, X86HashStyleDeep::Gnu);
}
#[test]
fn test_lld_component_defaults() {
let lld = X86LLDDeep::default();
assert!(lld.arch.is_64bit());
assert_eq!(lld.output_format, X86OutputFormatDeep::Elf64);
assert!(!lld.is_pic);
assert!(!lld.strip_debug);
assert!(!lld.enable_gc_sections);
assert!(!lld.enable_icf);
}
#[test]
fn test_stress_many_symbols() {
let mut sr = X86SymbolResolver::new();
for i in 0..1000 {
sr.define(
&format!("sym_{}", i),
i as u64 * 16,
8,
STB_LOCAL,
STT_FUNC,
X86SymbolVisibilityDeep::Default,
None,
true,
false,
false,
);
}
assert_eq!(sr.resolved_count(), 1000);
}
#[test]
fn test_stress_many_sections() {
let mut w = X86ELFWriterDeep::new_elf64_exec();
w.add_null_section();
for i in 0..100 {
w.add_section(make_output_section(
&format!(".text.func_{}", i),
vec![0x90; 32],
6,
));
}
let out = w.write();
assert!(out.len() > 5000);
}
#[test]
fn test_stress_many_relaxations() {
let mut re = X86RelaxationEngine::new();
for i in 0..500 {
re.record_relaxation(
0,
i * 4,
R_X86_64_GOTPCRELX,
X86RelaxKind::GOTPCRELX_ToPC32,
&format!("f_{}", i),
0,
);
}
let mut data = vec![0x8b, 0x05, 0, 0, 0, 0; 2000];
let mut offs = HashMap::new();
for i in 0..500 {
offs.insert(format!("f_{}", i), 0x400000 + i * 16);
}
let applied = re.apply_relaxations(&mut data, 0, &offs, 0x401000);
assert_eq!(applied, 500);
assert_eq!(re.stats().total_relaxed, 500);
}
#[test]
fn test_output_format_eq() {
assert_eq!(X86OutputFormatDeep::Elf64, X86OutputFormatDeep::Elf64);
assert_ne!(X86OutputFormatDeep::Elf64, X86OutputFormatDeep::Coff64);
}
#[test]
fn test_icf_safety_level_eq() {
assert_eq!(X86ICFSafetyLevel::Safe, X86ICFSafetyLevel::Safe);
}
#[test]
fn test_lto_model_eq() {
assert_eq!(X86LTOModelDeep::Full, X86LTOModelDeep::Full);
assert_ne!(X86LTOModelDeep::Full, X86LTOModelDeep::Thin);
}
#[test]
fn test_relax_kind_eq() {
assert_eq!(
X86RelaxKind::GOTPCRELX_MovToLea,
X86RelaxKind::GOTPCRELX_MovToLea
);
}
#[test]
fn test_build_id_kind_eq() {
assert_eq!(X86BuildIdKindDeep::Sha1, X86BuildIdKindDeep::Sha1);
}
#[test]
fn test_hash_style_eq() {
assert_eq!(X86HashStyleDeep::SysV, X86HashStyleDeep::SysV);
}
#[test]
fn test_diag_level_eq() {
assert_eq!(X86DiagLevelDeep::Error, X86DiagLevelDeep::Error);
}
#[test]
fn test_constant_page_size() {
assert_eq!(X86_64_PAGE_SIZE, 4096);
}
#[test]
fn test_constant_ehdr_size() {
assert_eq!(ELF64_EHDR_SIZE, 64);
}
#[test]
fn test_constant_phdr_size() {
assert_eq!(ELF64_PHDR_SIZE, 56);
}
#[test]
fn test_constant_shdr_size() {
assert_eq!(ELF64_SHDR_SIZE, 64);
}
#[test]
fn test_constant_got_entry_size() {
assert_eq!(GOT_ENTRY_SIZE, 8);
}
#[test]
fn test_constant_plt_entry_size() {
assert_eq!(PLT_ENTRY_SIZE, 16);
}
#[test]
fn test_coff_alignments() {
assert_eq!(COFF_FILE_ALIGNMENT, 512);
assert_eq!(COFF_SECTION_ALIGNMENT, 4096);
}
}
pub struct X86MachO64Writer {
pub cpu_type: u32,
pub cpu_subtype: u32,
pub file_type: u32,
pub flags: u32,
pub segments: Vec<X86MachOSegment>,
pub dylib_id: Option<String>,
pub dylibs: Vec<String>,
pub entry_point: u64,
pub stack_size: u64,
pub page_size: u64,
pub uuid: Option<[u8; 16]>,
pub source_version: u64,
pub build_version: (u32, u32, u32),
}
pub const MH_MAGIC_64: u32 = 0xfeedfacf;
pub const MH_CIGAM_64: u32 = 0xcffaedfe;
pub const MH_EXECUTE: u32 = 0x2;
pub const MH_DYLIB: u32 = 0x6;
pub const MH_BUNDLE: u32 = 0x8;
pub const MH_PIE: u32 = 0x200000;
pub const MH_NOUNDEFS: u32 = 0x1;
pub const MH_DYLDLINK: u32 = 0x4;
pub const MH_TWOLEVEL: u32 = 0x80;
pub const LC_SEGMENT_64: u32 = 0x19;
pub const LC_DYLD_INFO_ONLY: u32 = 0x80000022;
pub const LC_SYMTAB: u32 = 0x2;
pub const LC_DYSYMTAB: u32 = 0xb;
pub const LC_LOAD_DYLIB: u32 = 0xc;
pub const LC_ID_DYLIB: u32 = 0xd;
pub const LC_LOAD_DYLINKER: u32 = 0xe;
pub const LC_UUID: u32 = 0x1b;
pub const LC_BUILD_VERSION: u32 = 0x32;
pub const LC_SOURCE_VERSION: u32 = 0x2a;
pub const LC_MAIN: u32 = 0x80000028;
pub const LC_FUNCTION_STARTS: u32 = 0x26;
pub const LC_DATA_IN_CODE: u32 = 0x29;
pub const LC_CODE_SIGNATURE: u32 = 0x1d;
pub const CPU_TYPE_X86_64: u32 = 0x01000007;
pub const CPU_SUBTYPE_X86_ALL: u32 = 0x3;
pub const VM_PROT_NONE: u32 = 0x0;
pub const VM_PROT_READ: u32 = 0x1;
pub const VM_PROT_WRITE: u32 = 0x2;
pub const VM_PROT_EXECUTE: u32 = 0x4;
pub const SG_READ: u32 = 0x1;
pub const SG_WRITE: u32 = 0x2;
pub const SG_EXECUTE: u32 = 0x4;
impl X86MachO64Writer {
pub fn new_exec() -> Self {
Self {
cpu_type: CPU_TYPE_X86_64,
cpu_subtype: CPU_SUBTYPE_X86_ALL,
file_type: MH_EXECUTE,
flags: MH_NOUNDEFS | MH_DYLDLINK | MH_TWOLEVEL | MH_PIE,
segments: Vec::new(),
dylib_id: None,
dylibs: Vec::new(),
entry_point: 0,
stack_size: 0,
page_size: 4096,
uuid: None,
source_version: 0,
build_version: (1, 0, 0), }
}
pub fn new_dylib(name: &str) -> Self {
let mut w = Self::new_exec();
w.file_type = MH_DYLIB;
w.flags = MH_NOUNDEFS | MH_DYLDLINK | MH_TWOLEVEL;
w.dylib_id = Some(name.to_string());
w
}
pub fn new_bundle() -> Self {
let mut w = Self::new_exec();
w.file_type = MH_BUNDLE;
w.flags = MH_DYLDLINK;
w
}
pub fn set_entry(&mut self, addr: u64) {
self.entry_point = addr;
}
pub fn add_segment(
&mut self,
name: &str,
sections: Vec<X86MachOSection>,
initprot: u32,
maxprot: u32,
) {
let mut vmaddr = 0u64;
let mut filesize = 0u64;
let mut vmsize = 0u64;
for sec in §ions {
let aligned = Self::align_up(sec.addr.max(vmaddr), sec.align as u64);
vmaddr = aligned + sec.size;
vmsize = vmaddr;
filesize += sec.data.len() as u64;
}
self.segments.push(X86MachOSegment {
name: name.to_string(),
vmaddr: 0, vmsize,
fileoff: 0, filesize,
maxprot,
initprot,
nsects: sections.len() as u32,
flags: 0,
sections,
});
}
pub fn add_dylib(&mut self, path: &str) {
self.dylibs.push(path.to_string());
}
pub fn set_uuid(&mut self, uuid: [u8; 16]) {
self.uuid = Some(uuid);
}
pub fn emit(&self) -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(&MH_MAGIC_64.to_le_bytes());
buf.extend_from_slice(&self.cpu_type.to_le_bytes());
buf.extend_from_slice(&self.cpu_subtype.to_le_bytes());
buf.extend_from_slice(&self.file_type.to_le_bytes());
let ncmds_pos = buf.len();
buf.extend_from_slice(&0u32.to_le_bytes());
let sizeofcmds_pos = buf.len();
buf.extend_from_slice(&0u32.to_le_bytes());
buf.extend_from_slice(&self.flags.to_le_bytes());
buf.extend_from_slice(&0u32.to_le_bytes());
let lc_start = buf.len();
self.write_segment_cmd(&mut buf, "__PAGEZERO", 0, self.page_size, 0, 0, &[]);
for seg in &self.segments {
self.write_segment_cmd(
&mut buf,
&seg.name,
seg.vmaddr,
seg.vmsize,
seg.fileoff,
seg.filesize,
&seg.sections,
);
}
self.write_segment_cmd(&mut buf, "__LINKEDIT", 0, 0, 0, 0, &[]);
let dyld_info = vec![0u8; 48];
self.write_load_command(&mut buf, LC_DYLD_INFO_ONLY, &dyld_info);
let symtab = vec![0u8; 24];
self.write_load_command(&mut buf, LC_SYMTAB, &symtab);
let dysymtab = vec![0u8; 80];
self.write_load_command(&mut buf, LC_DYSYMTAB, &dysymtab);
let dylinker_path = b"/usr/lib/dyld\0";
let dylinker_cmd: Vec<u8> = dylinker_path
.iter()
.copied()
.chain(std::iter::repeat(0u8))
.take(Self::align_up(dylinker_path.len() as u64 + 4, 8) as usize)
.collect();
let mut lc_dylinker = vec![0u8; 8];
write_u32_le_deep(&mut lc_dylinker, 4, dylinker_path.len() as u32 + 1);
self.write_load_command(
&mut buf,
LC_LOAD_DYLINKER,
&[&lc_dylinker[..], &dylinker_cmd[..]].concat(),
);
if let Some(uuid) = self.uuid {
let mut lc_uuid = vec![0u8; 8];
lc_uuid[8..].copy_from_slice(&uuid);
self.write_load_command(&mut buf, LC_UUID, &lc_uuid);
} else {
let lc_uuid = vec![0u8; 24];
self.write_load_command(&mut buf, LC_UUID, &lc_uuid);
}
let mut lc_bv = vec![0u8; 24];
write_u32_le_deep(&mut lc_bv, 4, 1); write_u32_le_deep(&mut lc_bv, 8, self.build_version.0);
write_u32_le_deep(&mut lc_bv, 12, self.build_version.1);
write_u32_le_deep(&mut lc_bv, 16, self.build_version.2);
write_u32_le_deep(&mut lc_bv, 20, 1); write_u32_le_deep(&mut lc_bv, 24, 3); write_u32_le_deep(&mut lc_bv, 28, 0); self.write_load_command(&mut buf, LC_BUILD_VERSION, &lc_bv);
let lc_sv = self.source_version.to_le_bytes().to_vec();
let mut lc_sv_padded = vec![0u8; 8];
lc_sv_padded[..8].copy_from_slice(&lc_sv);
self.write_load_command(&mut buf, LC_SOURCE_VERSION, &lc_sv_padded);
let mut lc_main = vec![0u8; 24];
write_u64_le_deep(&mut lc_main, 8, self.entry_point);
write_u64_le_deep(&mut lc_main, 16, self.stack_size);
self.write_load_command(&mut buf, LC_MAIN, &lc_main);
for dylib in &self.dylibs {
let path = format!("{}\0", dylib);
let path_bytes = path.as_bytes();
let mut lc = vec![0u8; 16];
write_u32_le_deep(&mut lc, 8, 0); write_u32_le_deep(&mut lc, 12, 0); write_u32_le_deep(&mut lc, 16, 0); let cmd_data = [&lc[..], path_bytes, &[0u8; 0]].concat();
self.write_load_command(&mut buf, LC_LOAD_DYLIB, &cmd_data);
}
if let Some(ref id) = self.dylib_id {
let path = format!("{}\0", id);
let path_bytes = path.as_bytes();
let mut lc = vec![0u8; 16];
write_u32_le_deep(&mut lc, 8, 0);
write_u32_le_deep(&mut lc, 12, 0);
write_u32_le_deep(&mut lc, 16, 0);
let cmd_data = [&lc[..], path_bytes].concat();
self.write_load_command(&mut buf, LC_ID_DYLIB, &cmd_data);
}
self.write_load_command(&mut buf, LC_FUNCTION_STARTS, &[0u8; 8]);
self.write_load_command(&mut buf, LC_DATA_IN_CODE, &[0u8; 8]);
let lc_end = buf.len();
let ncmds = self.count_load_commands();
let sizeofcmds = (lc_end - lc_start) as u32;
write_u32_le_deep(&mut buf, ncmds_pos, ncmds);
write_u32_le_deep(&mut buf, sizeofcmds_pos, sizeofcmds);
for seg in &self.segments {
for sec in &seg.sections {
let aligned = Self::align_up(buf.len() as u64, sec.align as u64);
let pad = aligned - buf.len() as u64;
buf.extend(std::iter::repeat(0u8).take(pad as usize));
buf.extend_from_slice(&sec.data);
}
}
buf
}
fn write_segment_cmd(
&self,
buf: &mut Vec<u8>,
name: &str,
vmaddr: u64,
vmsize: u64,
fileoff: u64,
filesize: u64,
sections: &[X86MachOSection],
) {
let name_bytes: Vec<u8> = name
.as_bytes()
.iter()
.copied()
.chain(std::iter::repeat(0u8))
.take(16)
.collect();
let nsects = sections.len() as u32;
let hdr_size = 72; let mut data = vec![0u8; hdr_size];
write_u32_le_deep(&mut data, 0, LC_SEGMENT_64);
write_u32_le_deep(
&mut data,
4,
(hdr_size + nsects as usize * 80) as u32, );
data[8..24].copy_from_slice(&name_bytes);
write_u64_le_deep(&mut data, 24, vmaddr);
write_u64_le_deep(&mut data, 32, vmsize);
write_u64_le_deep(&mut data, 40, fileoff);
write_u64_le_deep(&mut data, 48, filesize);
write_u32_le_deep(
&mut data,
56,
VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE,
);
write_u32_le_deep(&mut data, 60, VM_PROT_READ | VM_PROT_EXECUTE);
write_u32_le_deep(&mut data, 64, nsects);
write_u32_le_deep(&mut data, 68, 0);
self.write_load_command_with_data(buf, &data);
for sec in sections {
let mut sec_data = vec![0u8; 80];
let sname: Vec<u8> = sec
.sectname
.as_bytes()
.iter()
.copied()
.chain(std::iter::repeat(0u8))
.take(16)
.collect();
let segname: Vec<u8> = sec
.segname
.as_bytes()
.iter()
.copied()
.chain(std::iter::repeat(0u8))
.take(16)
.collect();
sec_data[0..16].copy_from_slice(&sname);
sec_data[16..32].copy_from_slice(&segname);
write_u64_le_deep(&mut sec_data, 32, sec.addr);
write_u64_le_deep(&mut sec_data, 40, sec.size);
write_u32_le_deep(&mut sec_data, 48, sec.offset);
write_u32_le_deep(&mut sec_data, 52, sec.align);
write_u32_le_deep(&mut sec_data, 56, sec.reloff);
write_u32_le_deep(&mut sec_data, 60, sec.nreloc);
write_u32_le_deep(&mut sec_data, 64, sec.flags);
write_u32_le_deep(&mut sec_data, 68, 0); write_u32_le_deep(&mut sec_data, 72, 0); write_u32_le_deep(&mut sec_data, 76, 0); buf.extend_from_slice(&sec_data);
}
}
fn write_load_command(&self, buf: &mut Vec<u8>, cmd: u32, payload: &[u8]) {
let total = 8 + payload.len();
buf.extend_from_slice(&cmd.to_le_bytes());
buf.extend_from_slice(&(total as u32).to_le_bytes());
buf.extend_from_slice(payload);
}
fn write_load_command_with_data(&self, buf: &mut Vec<u8>, hdr_and_sections: &[u8]) {
buf.extend_from_slice(hdr_and_sections);
}
fn count_load_commands(&self) -> u32 {
let mut n = 0u32;
n += 1; n += self.segments.len() as u32;
n += 1; n += 1; n += 1; n += 1; n += 1; n += 1; n += 1; n += 1; n += 1; n += self.dylibs.len() as u32;
if self.dylib_id.is_some() {
n += 1;
}
n += 1; n += 1; n
}
fn align_up(addr: u64, align: u64) -> u64 {
if align == 0 {
return addr;
}
(addr + align - 1) & !(align - 1)
}
}
pub struct X86COFFTLSDir {
pub raw_data_start: u64,
pub raw_data_end: u64,
pub address_of_index: u64,
pub address_of_callbacks: u64,
pub size_of_zero_fill: u32,
pub characteristics: u32,
}
impl X86COFFTLSDir {
pub fn new() -> Self {
Self {
raw_data_start: 0,
raw_data_end: 0,
address_of_index: 0,
address_of_callbacks: 0,
size_of_zero_fill: 0,
characteristics: 0,
}
}
pub fn emit(&self) -> Vec<u8> {
let mut buf = vec![0u8; 40]; write_u64_le_deep(&mut buf, 0, self.raw_data_start);
write_u64_le_deep(&mut buf, 8, self.raw_data_end);
write_u64_le_deep(&mut buf, 16, self.address_of_index);
write_u64_le_deep(&mut buf, 24, self.address_of_callbacks);
write_u32_le_deep(&mut buf, 32, self.size_of_zero_fill);
write_u32_le_deep(&mut buf, 36, self.characteristics);
buf
}
}
pub struct X86PEExportWriter {
pub exports: Vec<X86CoffExportDeep>,
pub dll_name: String,
pub ordinal_base: u32,
}
impl X86PEExportWriter {
pub fn new(dll_name: &str) -> Self {
Self {
exports: Vec::new(),
dll_name: dll_name.to_string(),
ordinal_base: 1,
}
}
pub fn add_export(&mut self, name: &str, ordinal: u32, rva: u32) {
self.exports.push(X86CoffExportDeep {
name: name.to_string(),
ordinal,
rva,
forwarder: None,
});
}
pub fn emit(&self) -> Vec<u8> {
if self.exports.is_empty() {
return Vec::new();
}
let mut sorted = self.exports.clone();
sorted.sort_by_key(|e| e.ordinal);
let name = format!("{}\0", self.dll_name);
let name_bytes = name.as_bytes();
let mut buf = Vec::new();
buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&(self.ordinal_base.to_le_bytes()));
buf.extend_from_slice(&(sorted.len() as u32).to_le_bytes()); buf.extend_from_slice(&(sorted.len() as u32).to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes());
buf.extend_from_slice(&0u32.to_le_bytes());
buf.extend_from_slice(&0u32.to_le_bytes());
for exp in &sorted {
buf.extend_from_slice(&exp.rva.to_le_bytes());
}
for exp in &sorted {
if !exp.name.is_empty() {
let rva = buf.len() as u32 + 40;
buf.extend_from_slice(&rva.to_le_bytes());
}
}
for exp in &sorted {
if !exp.name.is_empty() {
let name_str = format!("{}\0", exp.name);
buf.extend_from_slice(name_str.as_bytes());
}
}
for exp in &sorted {
buf.extend_from_slice(&((exp.ordinal - self.ordinal_base) as u16).to_le_bytes());
}
buf
}
}
pub struct X86PEImportWriter {
pub imports: HashMap<String, Vec<X86CoffImportDeep>>,
}
impl X86PEImportWriter {
pub fn new() -> Self {
Self {
imports: HashMap::new(),
}
}
pub fn add_import(&mut self, dll: &str, sym_name: &str, ordinal: u16, is_data: bool) {
self.imports
.entry(dll.to_string())
.or_default()
.push(X86CoffImportDeep {
symbol_name: sym_name.to_string(),
dll_name: dll.to_string(),
ordinal,
is_data,
hint: 0,
});
}
pub fn emit(&self, image_base: u64) -> Vec<u8> {
if self.imports.is_empty() {
return vec![0u8; 20];
}
let mut buf = Vec::new();
for (dll, syms) in &self.imports {
let dll_name = format!("{}\0", dll);
buf.extend_from_slice(&0u32.to_le_bytes());
buf.extend_from_slice(&0u32.to_le_bytes());
buf.extend_from_slice(&0u32.to_le_bytes());
buf.extend_from_slice(&0u32.to_le_bytes());
buf.extend_from_slice(&0u32.to_le_bytes());
}
buf.extend_from_slice(&[0u8; 20]);
for (_dll, syms) in &self.imports {
for sym in syms {
if sym.ordinal != 0 {
let thunk: u64 = 0x8000000000000000 | (sym.ordinal as u64);
buf.extend_from_slice(&thunk.to_le_bytes());
} else {
let hint_name: Vec<u8> = [
&sym.hint.to_le_bytes()[..],
sym.symbol_name.as_bytes(),
&[0u8],
]
.concat();
buf.extend_from_slice(&hint_name);
}
}
buf.extend_from_slice(&0u64.to_le_bytes());
}
buf
}
pub fn add_import_by_name(&mut self, dll: &str, sym_name: &str) {
self.add_import(dll, sym_name, 0, false);
}
}
impl Default for X86PEImportWriter {
fn default() -> Self {
Self::new()
}
}
pub struct X86LinkerScriptEvaluator {
pub memory_regions: Vec<X86MemoryRegionDeep>,
pub section_commands: Vec<X86ScriptSectionCommandDeep>,
pub entry_symbol: Option<String>,
pub base_address: u64,
}
impl X86LinkerScriptEvaluator {
pub fn new() -> Self {
Self {
memory_regions: Vec::new(),
section_commands: Vec::new(),
entry_symbol: None,
base_address: 0,
}
}
pub fn add_memory_region(
&mut self,
name: &str,
origin: u64,
length: u64,
r: bool,
w: bool,
x: bool,
) {
self.memory_regions.push(X86MemoryRegionDeep {
name: name.to_string(),
origin,
length,
attributes: X86MemoryAttrsDeep {
readable: r,
writable: w,
executable: x,
allocatable: true,
},
});
}
pub fn add_section_command(&mut self, cmd: X86ScriptSectionCommandDeep) {
self.section_commands.push(cmd);
}
pub fn evaluate(&self, expr: &str) -> Option<u64> {
let expr = expr.trim();
if expr.is_empty() {
return Some(0);
}
if expr.starts_with("0x") || expr.starts_with("0X") {
return u64::from_str_radix(&expr[2..], 16).ok();
}
if let Ok(n) = expr.parse::<u64>() {
return Some(n);
}
if expr.to_uppercase().starts_with("ALIGN(") && expr.ends_with(')') {
let inner = &expr[6..expr.len() - 1];
if let Some(val) = self.evaluate(inner) {
return Some(align_up_deep(self.base_address, val));
}
}
if expr == "SIZEOF_HEADERS" {
return Some(self.base_address);
}
if let Some(region) = self.memory_regions.iter().find(|r| r.name == expr) {
return Some(region.origin);
}
None
}
pub fn assign_to_regions(&self, section_names: &[String]) -> HashMap<String, String> {
let mut assignments = HashMap::new();
for cmd in &self.section_commands {
for name in section_names {
for pat in &cmd.input_patterns {
if wildcard_match_deep(&pat.section_pattern, name)
|| wildcard_match_deep(&format!("*.{}", name), &pat.section_pattern)
{
assignments.insert(name.clone(), cmd.name.clone());
}
}
}
}
assignments
}
}
impl Default for X86LinkerScriptEvaluator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
#[allow(unused_imports, dead_code)]
mod extra_tests {
use super::*;
fn make_macho_section(segname: &str, sectname: &str, data: Vec<u8>) -> X86MachOSection {
X86MachOSection {
sectname: sectname.to_string(),
segname: segname.to_string(),
addr: 0,
size: data.len() as u64,
offset: 0,
align: 4,
reloff: 0,
nreloc: 0,
flags: 0,
data,
}
}
#[test]
fn test_macho_writer_new_exec() {
let w = X86MachO64Writer::new_exec();
assert_eq!(w.file_type, MH_EXECUTE);
assert_eq!(w.cpu_type, CPU_TYPE_X86_64);
}
#[test]
fn test_macho_writer_new_dylib() {
let w = X86MachO64Writer::new_dylib("libtest.dylib");
assert_eq!(w.file_type, MH_DYLIB);
assert!(w.dylib_id.is_some());
}
#[test]
fn test_macho_writer_new_bundle() {
let w = X86MachO64Writer::new_bundle();
assert_eq!(w.file_type, MH_BUNDLE);
}
#[test]
fn test_macho_writer_add_segment() {
let mut w = X86MachO64Writer::new_exec();
let text_sec = make_macho_section("__TEXT", "__text", vec![0x90; 64]);
w.add_segment(
"__TEXT",
vec![text_sec],
VM_PROT_READ | VM_PROT_EXECUTE,
VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE,
);
assert_eq!(w.segments.len(), 1);
}
#[test]
fn test_macho_writer_emit_minimal() {
let w = X86MachO64Writer::new_exec();
let output = w.emit();
assert!(output.len() > 32);
assert_eq!(&output[0..4], &MH_MAGIC_64.to_le_bytes());
}
#[test]
fn test_macho_writer_emit_with_segment() {
let mut w = X86MachO64Writer::new_exec();
w.set_entry(0x1000);
let text = make_macho_section("__TEXT", "__text", vec![0x90; 16]);
w.add_segment(
"__TEXT",
vec![text],
VM_PROT_READ | VM_PROT_EXECUTE,
VM_PROT_ALL,
);
let output = w.emit();
assert!(output.len() > 200);
}
#[test]
fn test_macho_writer_with_dylib() {
let mut w = X86MachO64Writer::new_exec();
w.add_dylib("/usr/lib/libSystem.B.dylib");
let output = w.emit();
assert!(output.len() > 32);
}
#[test]
fn test_macho_writer_with_uuid() {
let mut w = X86MachO64Writer::new_exec();
w.set_uuid([0xAA; 16]);
let output = w.emit();
assert!(output.len() > 32);
}
#[test]
fn test_macho_magic_constants() {
assert_eq!(MH_MAGIC_64, 0xfeedfacf);
assert_eq!(MH_EXECUTE, 2);
assert_eq!(MH_DYLIB, 6);
assert_eq!(MH_BUNDLE, 8);
}
#[test]
fn test_macho_lc_constants() {
assert_eq!(LC_SEGMENT_64, 0x19);
assert_eq!(LC_MAIN, 0x80000028);
assert_eq!(LC_UUID, 0x1b);
}
#[test]
fn test_tls_dir_emit() {
let tls = X86COFFTLSDir::new();
let data = tls.emit();
assert_eq!(data.len(), 40);
}
#[test]
fn test_tls_dir_values() {
let mut tls = X86COFFTLSDir::new();
tls.raw_data_start = 0x1000;
tls.raw_data_end = 0x2000;
let data = tls.emit();
assert_eq!(read_u64_le_deep(&data, 0), 0x1000);
assert_eq!(read_u64_le_deep(&data, 8), 0x2000);
}
#[test]
fn test_pe_export_writer_empty() {
let w = X86PEExportWriter::new("test.dll");
let data = w.emit();
assert!(data.is_empty());
}
#[test]
fn test_pe_export_writer_single() {
let mut w = X86PEExportWriter::new("test.dll");
w.add_export("MyFunc", 1, 0x1000);
let data = w.emit();
assert!(!data.is_empty());
assert!(data.len() > 40);
}
#[test]
fn test_pe_export_writer_multiple() {
let mut w = X86PEExportWriter::new("test.dll");
w.add_export("Func1", 1, 0x1000);
w.add_export("Func2", 2, 0x2000);
w.add_export("Func3", 3, 0x3000);
let data = w.emit();
assert!(data.len() > 40);
}
#[test]
fn test_pe_import_writer_empty() {
let w = X86PEImportWriter::new();
let data = w.emit(0x140000000);
assert_eq!(data.len(), 20); }
#[test]
fn test_pe_import_writer_single() {
let mut w = X86PEImportWriter::new();
w.add_import_by_name("kernel32.dll", "ExitProcess");
let data = w.emit(0x140000000);
assert!(data.len() > 40);
}
#[test]
fn test_pe_import_writer_multiple_dlls() {
let mut w = X86PEImportWriter::new();
w.add_import_by_name("kernel32.dll", "ExitProcess");
w.add_import_by_name("user32.dll", "MessageBoxA");
w.add_import_by_name("kernel32.dll", "GetCommandLineA");
let data = w.emit(0x140000000);
assert!(data.len() > 80);
}
#[test]
fn test_pe_import_by_ordinal() {
let mut w = X86PEImportWriter::new();
w.add_import("some.dll", "", 42, false);
let data = w.emit(0x140000000);
assert!(data.len() > 20);
}
#[test]
fn test_script_evaluator_new() {
let e = X86LinkerScriptEvaluator::new();
assert!(e.memory_regions.is_empty());
assert!(e.section_commands.is_empty());
}
#[test]
fn test_script_evaluate_hex() {
let e = X86LinkerScriptEvaluator::new();
assert_eq!(e.evaluate("0x1000"), Some(0x1000));
assert_eq!(e.evaluate("0xDEADBEEF"), Some(0xDEADBEEF));
}
#[test]
fn test_script_evaluate_decimal() {
let e = X86LinkerScriptEvaluator::new();
assert_eq!(e.evaluate("42"), Some(42));
assert_eq!(e.evaluate("65536"), Some(65536));
}
#[test]
fn test_script_evaluate_empty() {
let e = X86LinkerScriptEvaluator::new();
assert_eq!(e.evaluate(""), Some(0));
}
#[test]
fn test_script_evaluate_align() {
let mut e = X86LinkerScriptEvaluator::new();
e.base_address = 0x400100;
assert_eq!(e.evaluate("ALIGN(0x10)"), Some(0x400100));
}
#[test]
fn test_script_evaluate_sizeof_headers() {
let mut e = X86LinkerScriptEvaluator::new();
e.base_address = 0x200;
assert_eq!(e.evaluate("SIZEOF_HEADERS"), Some(0x200));
}
#[test]
fn test_script_evaluate_unknown() {
let e = X86LinkerScriptEvaluator::new();
assert_eq!(e.evaluate("UNDEFINED_SYMBOL"), None);
}
#[test]
fn test_script_add_memory_region() {
let mut e = X86LinkerScriptEvaluator::new();
e.add_memory_region("RAM", 0x80000000, 0x10000000, true, true, true);
assert_eq!(e.memory_regions.len(), 1);
assert_eq!(e.memory_regions[0].origin, 0x80000000);
}
#[test]
fn test_script_assign_regions() {
let mut e = X86LinkerScriptEvaluator::new();
e.add_memory_region("RAM", 0x80000000, 0x10000000, true, true, true);
let cmd = X86ScriptSectionCommandDeep {
name: ".text".to_string(),
vma: None,
lma: None,
align: 16,
input_patterns: vec![X86ScriptSectionPatternDeep {
file_pattern: "*".to_string(),
section_pattern: ".text*".to_string(),
exclude_files: Vec::new(),
sort_by: X86SortKind::None,
}],
keep_patterns: Vec::new(),
fill_value: None,
section_type: X86ScriptSectionTypeDeep::Progbits,
subsection_count: 1,
};
e.add_section_command(cmd);
let sections = vec![
".text".to_string(),
".text.hot".to_string(),
".data".to_string(),
];
let assignments = e.assign_to_regions(§ions);
assert_eq!(assignments.get(".text"), Some(&".text".to_string()));
assert_eq!(assignments.get(".text.hot"), Some(&".text".to_string()));
assert!(assignments.get(".data").is_none());
}
#[test]
fn test_script_evaluate_memory_region_name() {
let mut e = X86LinkerScriptEvaluator::new();
e.add_memory_region("FLASH", 0x08000000, 0x100000, true, false, true);
assert_eq!(e.evaluate("FLASH"), Some(0x08000000));
}
#[test]
fn test_lld_emit_macho_full() {
let mut w = X86MachO64Writer::new_exec();
w.set_entry(0x1000);
let text = make_macho_section("__TEXT", "__text", vec![0x90; 32]);
let data = make_macho_section("__DATA", "__data", vec![0x42; 8]);
w.add_segment(
"__TEXT",
vec![text],
VM_PROT_READ | VM_PROT_EXECUTE,
VM_PROT_ALL,
);
w.add_segment(
"__DATA",
vec![data],
VM_PROT_READ | VM_PROT_WRITE,
VM_PROT_ALL,
);
let output = w.emit();
assert!(output.len() > 400);
}
#[test]
fn test_relax_apply_tls_gd_to_le_placeholder() {
let re = X86RelaxationEngine::new();
let mut data = vec![0; 12];
let ok = re.apply_tls_gd_to_le(&mut data, 4, 0x100);
assert!(ok);
}
#[test]
fn test_relax_apply_tls_gd_to_ie_placeholder() {
let re = X86RelaxationEngine::new();
let mut data = vec![0; 12];
let ok = re.apply_tls_gd_to_ie(&mut data, 4);
assert!(ok);
}
#[test]
fn test_relax_apply_tls_ld_to_le_placeholder() {
let re = X86RelaxationEngine::new();
let mut data = vec![0; 12];
let ok = re.apply_tls_ld_to_le(&mut data, 4);
assert!(ok);
}
#[test]
fn test_relax_analyze_non_relaxable() {
let mut re = X86RelaxationEngine::new();
let kind = re.analyze_relocation(0, 0, R_X86_64_64, "x", 0, &[], true);
assert!(kind.is_none());
}
#[test]
fn test_symbol_def_debug() {
let def = X86SymbolDefDeep::Defined {
value: 0x100,
size: 16,
section_name: None,
is_function: true,
is_ifunc: false,
is_tls: false,
};
let dbg = format!("{:?}", def);
assert!(dbg.contains("0x100"));
}
#[test]
fn test_relax_record_debug() {
let rec = X86RelaxRecord {
section_index: 0,
offset: 8,
rel_type: R_X86_64_GOTPCRELX,
relax_kind: X86RelaxKind::GOTPCRELX_MovToLea,
symbol_name: "foo".to_string(),
addend: -4,
};
let dbg = format!("{:?}", rec);
assert!(dbg.contains("MOV→LEA") || dbg.contains("GOTPCRELX_MovToLea"));
}
#[test]
fn test_lto_summary_debug() {
let sum = LTOModuleSummary {
module_hash: 0xDEAD,
function_names: vec!["f1".to_string()],
global_names: vec!["f1".to_string()],
instruction_count: 42,
thin_link: true,
};
let dbg = format!("{:?}", sum);
assert!(dbg.contains("f1"));
}
#[test]
fn test_coff_section_debug() {
let sec = X86CoffSectionDeep {
name: ".text".to_string(),
data: vec![0x90; 4],
characteristics: 0x20,
align: 16,
section_index: 1,
};
assert_eq!(sec.name, ".text");
}
#[test]
fn test_clone_output_section() {
let sec = make_output_section(".test", vec![1, 2, 3], 6);
let cloned = sec.clone();
assert_eq!(sec.name, cloned.name);
assert_eq!(sec.data, cloned.data);
}
#[test]
fn test_clone_relocation() {
let rel = X86InputRelocationDeep {
offset: 16,
symbol_index: 2,
rel_type: R_X86_64_PC32,
addend: -4,
section_index: 1,
};
let cloned = rel.clone();
assert_eq!(rel.offset, cloned.offset);
assert_eq!(rel.rel_type, cloned.rel_type);
}
#[test]
fn test_clone_linker_symbol() {
let sym = X86LinkerSymbolDeep {
name: "test_sym".to_string(),
def: X86SymbolDefDeep::Defined {
value: 0x1000,
size: 8,
section_name: None,
is_function: true,
is_ifunc: false,
is_tls: false,
},
binding: STB_GLOBAL,
sym_type: STT_FUNC,
visibility: X86SymbolVisibilityDeep::Default,
version: None,
is_referenced: true,
is_exported: false,
priority: 0,
is_absolute: false,
is_wrapped: false,
wrap_target: None,
is_defsym: false,
};
let cloned = sym.clone();
assert_eq!(cloned.name, "test_sym");
}
#[test]
fn test_crel_default() {
let crel: X86CREL = Default::default();
assert!(crel.is_empty());
}
#[test]
fn test_build_id_default() {
let bid: X86BuildID = Default::default();
assert!(!bid.is_enabled());
}
#[test]
fn test_merge_sections_default() {
let ms: X86MergeSections = Default::default();
assert_eq!(ms.output_size(), 0);
}
#[test]
fn test_dynamic_linker_default() {
let dl: X86DynamicLinker = Default::default();
assert!(dl.lazy_binding);
assert!(dl.needed.is_empty());
}
#[test]
fn test_symbol_resolver_default() {
let sr: X86SymbolResolver = Default::default();
assert_eq!(sr.resolved_count(), 0);
}
#[test]
fn test_relaxation_engine_default() {
let re: X86RelaxationEngine = Default::default();
assert_eq!(re.stats().total_relaxed, 0);
}
#[test]
fn test_gc_sections_default() {
let gc: X86GCSectionsDeep = Default::default();
assert!(!gc.keep_patterns.is_empty());
}
#[test]
fn test_icf_default() {
let icf: X86ICFDeep = Default::default();
assert_eq!(icf.min_size, 16);
}
#[test]
fn test_lto_plugin_default() {
let lto: X86LTOPluginDeep = Default::default();
assert_eq!(lto.stats().bitcode_files_detected, 0);
}
#[test]
fn test_lld_deep_default() {
let lld: X86LLDDeep = Default::default();
assert!(lld.arch.is_64bit());
}
#[test]
fn test_pe_import_writer_default() {
let w: X86PEImportWriter = Default::default();
assert!(w.imports.is_empty());
}
#[test]
fn test_script_evaluator_default() {
let e: X86LinkerScriptEvaluator = Default::default();
assert!(e.memory_regions.is_empty());
}
#[test]
fn test_switch_output_formats() {
let mut lld = X86LLDDeep::new_elf_x86_64();
lld.output_format = X86OutputFormatDeep::Elf64;
let elf = lld.emit();
assert!(!elf.is_empty());
lld.output_format = X86OutputFormatDeep::Coff64;
let coff = lld.emit();
assert!(!coff.is_empty());
lld.output_format = X86OutputFormatDeep::MachO64;
let macho = lld.emit();
assert!(!macho.is_empty());
}
#[test]
fn test_switch_elf_32_and_64() {
let mut lld64 = X86LLDDeep::new_elf_x86_64();
lld64.output_format = X86OutputFormatDeep::Elf64;
let elf64 = lld64.emit();
assert_eq!(elf64[EI_CLASS as usize], ELFCLASS64);
let mut lld32 = X86LLDDeep::new_elf_i386();
lld32.output_format = X86OutputFormatDeep::Elf32;
lld32.elf_writer = X86ELFWriterDeep::new_elf32_exec();
let elf32 = lld32.emit();
assert_eq!(elf32[EI_CLASS as usize], ELFCLASS32);
}
}