use crate::elf::elf_reader::{
read_u16 as elf_read_u16, read_u32 as elf_read_u32, read_u64 as elf_read_u64, ElfReader,
};
use crate::elf::elf_relocations::{
ElfRelocationHandler, R_X86_64_32, R_X86_64_32S, R_X86_64_64, R_X86_64_NONE, R_X86_64_PC32,
};
use crate::elf::elf_types::{
binding_to_str, dynamic_tag_to_str, elf_r_info, elf_r_info32, elf_r_sym, elf_r_sym32,
elf_r_type, elf_r_type32, machine_to_str, osabi_to_str, phdr_type_to_str, section_type_to_str,
st_bind, st_info, st_other, st_type, st_visibility, sym_type_to_str, visibility_to_str,
Elf32Dyn, Elf32Ehdr, Elf32Phdr, Elf32Rel, Elf32Rela, Elf32Shdr, Elf32Sym, Elf64Dyn, Elf64Ehdr,
Elf64Phdr, Elf64Rel, Elf64Rela, Elf64Shdr, Elf64Sym, ElfClass, ElfEndian, ElfMachine, ElfOsAbi,
ELF_MAGIC, SECTION_NAME_BSS, SECTION_NAME_DATA, SECTION_NAME_RODATA, SECTION_NAME_SHSTRTAB,
SECTION_NAME_STRTAB, SECTION_NAME_SYMTAB, SECTION_NAME_TEXT,
};
use crate::elf::elf_writer::{ElfNote, ElfPhdrInfo, ElfRelocation, ElfSection, ElfWriter};
use crate::linker::{IRLinker, LinkDiagLevel, LinkDiagnostic, LinkResult};
use crate::lld::lld_coff::CoffLinker;
use crate::lld::lld_driver::{InputFile, LldDriver, LldOptions, OutputFormat, UndefinedHandler};
use crate::lld::lld_elf::ElfLinker;
use crate::lld::lld_gc::GCSections;
use crate::lld::lld_icf::ICF;
use crate::lld::lld_macho::MachOLinker;
use crate::lld::lld_relro::RELRO;
use crate::x86::x86_lld_full::{
align_down, align_up, compute_fnv1a_hash, elf_hash, get_c_string, gnu_hash_bytes, hash_fast,
hash_md5, hash_sha1, next_power_of_two, parse_coff_object_full, parse_script_number,
read_u16_le, read_u32_le, read_u64_le, tokenize_linker_script, uuid_v4, visibility_to_elf,
wildcard_match, wm_bytes, X86ArchiveMember, X86ArchiveReader, X86ArchiveWriter, X86BuildIdKind,
X86COFFLinker, X86CoffExport, X86CoffImport, X86CoffObject, X86CoffOutputSection,
X86CoffRelocation, X86CoffResolvedSymbol, X86CoffSection, X86CoffSubsystem, X86CoffSymbol,
X86DiagLevel, X86ELFLinker, X86GCSections, X86GotEntry, X86HashStyle, X86InputObject,
X86InputRelocation, X86InputSection, X86InputSymbol, X86LLDArch, X86LLDLinker,
X86LTOBitcodeInfo, X86LTOModel, X86LTOOptLevel, X86LTOOptions, X86LTOPlugin,
X86LinkerDiagnostic, X86LinkerScript, X86LinkerSymbol, X86MemoryAttrs, X86OutputFormat,
X86OutputSection, X86OutputSegment, X86PltEntry, X86RelocAction, X86RelocationEngine,
X86ScriptAssignment, X86ScriptMemoryRegion, X86ScriptPhdr, X86ScriptSectionCommand,
X86ScriptSectionPattern, X86ScriptSectionType, X86ScriptSubCommand, X86SharedLibrary,
X86SymbolDef, X86SymbolTable, X86SymbolVersion, X86SymbolVisibility, COFF_FILE_ALIGNMENT,
COFF_SECTION_ALIGNMENT, ELF64_EHDR_SIZE, ELF64_PHDR_SIZE, ELF64_SHDR_SIZE, ELF_MAGIC_BYTES,
GOT_ENTRY_SIZE, I386_DEFAULT_IMAGE_BASE, IMAGE_FILE_MACHINE_AMD64, IMAGE_FILE_MACHINE_I386,
PE32PLUS_MAGIC, PE32_MAGIC, PLT0_SIZE, PLT_ENTRY_SIZE, R_386_32, R_386_32PLT, R_386_COPY,
R_386_GLOB_DAT, R_386_GOT32, R_386_GOT32X, R_386_GOTOFF, R_386_GOTPC, R_386_IRELATIVE,
R_386_JMP_SLOT, R_386_NONE, R_386_PC32, R_386_PLT32, R_386_RELATIVE, R_386_SIZE32,
R_386_TLS_DESC, R_386_TLS_DESC_CALL, R_386_TLS_DTPMOD32, R_386_TLS_DTPOFF32, R_386_TLS_GD,
R_386_TLS_GD_32, R_386_TLS_GD_CALL, R_386_TLS_GD_POP, R_386_TLS_GD_PUSH, R_386_TLS_GOTDESC,
R_386_TLS_GOTIE, R_386_TLS_IE, R_386_TLS_IE_32, R_386_TLS_LDM, R_386_TLS_LDM_32,
R_386_TLS_LDM_CALL, R_386_TLS_LDM_POP, R_386_TLS_LDM_PUSH, R_386_TLS_LDO_32, R_386_TLS_LE,
R_386_TLS_LE_32, R_386_TLS_TPOFF, R_386_TLS_TPOFF32, R_X86_64_DTPMOD64, R_X86_64_DTPOFF32,
R_X86_64_DTPOFF64, R_X86_64_GOTTPOFF, R_X86_64_IRELATIVE, R_X86_64_TLSGD, R_X86_64_TLSLD,
R_X86_64_TPOFF32, R_X86_64_TPOFF64, X86ICF, X86_64_DEFAULT_IMAGE_BASE, X86_64_PAGE_SIZE,
};
pub struct X86LinkerFull {
pub arch: X86LLDArch,
pub output_format: X86OutputFormat,
pub output_path: String,
pub is_pic: bool,
pub is_shared: bool,
pub image_base: u64,
pub entry_point: String,
pub strip_debug: bool,
pub enable_gc_sections: bool,
pub enable_icf: bool,
pub build_id: X86BuildIdKind,
pub hash_style: X86HashStyle,
elf_linker: X86ELFLinker,
coff_linker: X86COFFLinkerFull,
macho_linker: X86MachOLinker,
symbol_table: X86SymbolTable,
relocation_engine: X86RelocationEngine,
pub diagnostics: Vec<X86LinkerDiagnostic>,
pub objects: Vec<X86InputObject>,
pub shared_libs: Vec<X86SharedLibrary>,
pub imports: Vec<X86CoffImport>,
pub exports: Vec<X86CoffExport>,
pub got_entries: Vec<X86GotEntry>,
pub plt_entries: Vec<X86PltEntry>,
pub library_search_paths: Vec<String>,
}
impl X86LinkerFull {
pub fn new(arch: X86LLDArch, output_format: X86OutputFormat) -> Self {
let elf_linker = X86ELFLinker::new(arch.clone());
let coff_linker = X86COFFLinkerFull::new(arch.clone());
let macho_linker = X86MachOLinker::new(arch.clone());
let symbol_table = X86SymbolTable::new();
let relocation_engine = X86RelocationEngine::new();
X86LinkerFull {
arch: arch.clone(),
output_format,
output_path: String::new(),
is_pic: false,
is_shared: false,
image_base: if arch.is_64bit() {
X86_64_DEFAULT_IMAGE_BASE
} else {
I386_DEFAULT_IMAGE_BASE as u64
},
entry_point: String::from("_start"),
strip_debug: false,
enable_gc_sections: false,
enable_icf: false,
build_id: X86BuildIdKind::None,
hash_style: X86HashStyle::Both,
elf_linker,
coff_linker,
macho_linker,
symbol_table,
relocation_engine,
diagnostics: Vec::new(),
objects: Vec::new(),
shared_libs: Vec::new(),
imports: Vec::new(),
exports: Vec::new(),
got_entries: Vec::new(),
plt_entries: Vec::new(),
library_search_paths: Vec::new(),
}
}
pub fn new_elf_x86_64() -> Self {
Self::new(X86LLDArch::X86_64, X86OutputFormat::Elf64)
}
pub fn new_elf_i386() -> Self {
Self::new(X86LLDArch::I386, X86OutputFormat::Elf32)
}
pub fn new_coff_x86_64() -> Self {
Self::new(X86LLDArch::X86_64, X86OutputFormat::Coff64)
}
pub fn new_coff_i386() -> Self {
Self::new(X86LLDArch::I386, X86OutputFormat::Coff32)
}
pub fn new_macho_x86_64() -> Self {
Self::new(X86LLDArch::X86_64, X86OutputFormat::Elf64)
}
pub fn set_output_path(&mut self, path: &str) {
self.output_path = path.to_string();
self.elf_linker.output_path = path.to_string();
}
pub fn set_pic(&mut self, pic: bool) {
self.is_pic = pic;
self.elf_linker.is_pic = pic;
}
pub fn diag(&mut self, level: X86DiagLevel, message: &str) {
self.diagnostics.push(X86LinkerDiagnostic {
level,
message: message.to_string(),
});
}
pub fn add_object_file(&mut self, filename: &str, data: Vec<u8>) {
self.elf_linker.add_object(filename, data.clone());
self.objects.push(X86InputObject {
filename: filename.to_string(),
data,
sections: Vec::new(),
symbols: Vec::new(),
relocations: Vec::new(),
machine: self.arch.elf_machine(),
elf_class: if self.arch.is_64bit() { 2 } else { 1 },
});
}
pub fn add_shared_library(&mut self, name: &str) {
self.shared_libs.push(X86SharedLibrary {
name: name.to_string(),
symbols: Vec::new(),
needed: true,
soname: name.to_string(),
});
self.elf_linker.add_shared_library(name);
}
pub fn add_coff_import(
&mut self,
symbol_name: &str,
dll_name: &str,
ordinal: Option<u16>,
is_data: bool,
) {
self.imports.push(X86CoffImport {
symbol_name: symbol_name.to_string(),
dll_name: dll_name.to_string(),
ordinal,
is_data,
});
}
pub fn add_coff_export(&mut self, name: &str, ordinal: u16, rva: u32) {
self.exports.push(X86CoffExport {
name: name.to_string(),
ordinal,
rva,
forwarder: None,
});
}
pub fn link(&mut self) -> Result<(), String> {
self.diagnostics.clear();
match self.output_format {
X86OutputFormat::Elf32 | X86OutputFormat::Elf64 => {
self.elf_linker.link()?;
for d in &self.elf_linker.diagnostics {
self.diagnostics.push(d.clone());
}
}
X86OutputFormat::Coff32 | X86OutputFormat::Coff64 => {
self.coff_linker.link()?;
}
}
Ok(())
}
pub fn emit(&mut self) -> Result<Vec<u8>, String> {
match self.output_format {
X86OutputFormat::Elf32 | X86OutputFormat::Elf64 => self.elf_linker.emit(),
X86OutputFormat::Coff32 | X86OutputFormat::Coff64 => self.coff_linker.emit(),
}
}
pub fn link_and_emit(&mut self) -> Result<Vec<u8>, String> {
self.link()?;
self.emit()
}
pub fn add_library_path(&mut self, path: &str) {
self.library_search_paths.push(path.to_string());
}
pub fn set_entry(&mut self, entry: &str) {
self.entry_point = entry.to_string();
}
pub fn set_image_base(&mut self, base: u64) {
self.image_base = base;
self.coff_linker.image_base = base;
}
}
#[repr(C)]
pub struct CoffFileHeader {
pub machine: u16,
pub number_of_sections: u16,
pub time_date_stamp: u32,
pub pointer_to_symbol_table: u32,
pub number_of_symbols: u32,
pub size_of_optional_header: u16,
pub characteristics: u16,
}
impl CoffFileHeader {
pub const SIZE: usize = 20;
}
#[repr(C)]
pub struct CoffSectionHeader {
pub name: [u8; 8],
pub virtual_size: u32,
pub virtual_address: u32,
pub size_of_raw_data: u32,
pub pointer_to_raw_data: u32,
pub pointer_to_relocations: u32,
pub pointer_to_line_numbers: u32,
pub number_of_relocations: u16,
pub number_of_line_numbers: u16,
pub characteristics: u32,
}
impl CoffSectionHeader {
pub const SIZE: usize = 40;
}
#[repr(C)]
pub struct CoffSymbol {
pub name: [u8; 8],
pub value: u32,
pub section_number: i16,
pub sym_type: u16,
pub storage_class: u8,
pub number_of_aux_symbols: u8,
}
impl CoffSymbol {
pub const SIZE: usize = 18;
}
pub struct CoffRelocation {
pub virtual_address: u32,
pub symbol_table_index: u32,
pub rel_type: u16,
}
pub struct PE32PlusOptionalHeader {
pub magic: u16,
pub major_linker_version: u8,
pub minor_linker_version: u8,
pub size_of_code: u32,
pub size_of_initialized_data: u32,
pub size_of_uninitialized_data: u32,
pub address_of_entry_point: u32,
pub base_of_code: u32,
pub image_base: u64,
pub section_alignment: u32,
pub file_alignment: u32,
pub major_os_version: u16,
pub minor_os_version: u16,
pub major_image_version: u16,
pub minor_image_version: u16,
pub major_subsystem_version: u16,
pub minor_subsystem_version: u16,
pub win32_version_value: u32,
pub size_of_image: u32,
pub size_of_headers: u32,
pub checksum: u32,
pub subsystem: u16,
pub dll_characteristics: u16,
pub size_of_stack_reserve: u64,
pub size_of_stack_commit: u64,
pub size_of_heap_reserve: u64,
pub size_of_heap_commit: u64,
pub loader_flags: u32,
pub number_of_rva_and_sizes: u32,
}
impl PE32PlusOptionalHeader {
pub const SIZE: usize = 112;
}
pub struct PE32OptionalHeader {
pub magic: u16,
pub major_linker_version: u8,
pub minor_linker_version: u8,
pub size_of_code: u32,
pub size_of_initialized_data: u32,
pub size_of_uninitialized_data: u32,
pub address_of_entry_point: u32,
pub base_of_code: u32,
pub base_of_data: u32,
pub image_base: u32,
pub section_alignment: u32,
pub file_alignment: u32,
pub major_os_version: u16,
pub minor_os_version: u16,
pub major_image_version: u16,
pub minor_image_version: u16,
pub major_subsystem_version: u16,
pub minor_subsystem_version: u16,
pub win32_version_value: u32,
pub size_of_image: u32,
pub size_of_headers: u32,
pub checksum: u32,
pub subsystem: u16,
pub dll_characteristics: u16,
pub size_of_stack_reserve: u32,
pub size_of_stack_commit: u32,
pub size_of_heap_reserve: u32,
pub size_of_heap_commit: u32,
pub loader_flags: u32,
pub number_of_rva_and_sizes: u32,
}
impl PE32OptionalHeader {
pub const SIZE: usize = 96;
}
pub struct ImageDataDirectory {
pub virtual_address: u32,
pub size: u32,
}
impl ImageDataDirectory {
pub const SIZE: usize = 8;
}
pub const IMAGE_DIRECTORY_ENTRY_EXPORT: usize = 0;
pub const IMAGE_DIRECTORY_ENTRY_IMPORT: usize = 1;
pub const IMAGE_DIRECTORY_ENTRY_RESOURCE: usize = 2;
pub const IMAGE_DIRECTORY_ENTRY_EXCEPTION: usize = 3;
pub const IMAGE_DIRECTORY_ENTRY_SECURITY: usize = 4;
pub const IMAGE_DIRECTORY_ENTRY_BASERELOC: usize = 5;
pub const IMAGE_DIRECTORY_ENTRY_DEBUG: usize = 6;
pub const IMAGE_DIRECTORY_ENTRY_ARCHITECTURE: usize = 7;
pub const IMAGE_DIRECTORY_ENTRY_GLOBALPTR: usize = 8;
pub const IMAGE_DIRECTORY_ENTRY_TLS: usize = 9;
pub const IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG: usize = 10;
pub const IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT: usize = 11;
pub const IMAGE_DIRECTORY_ENTRY_IAT: usize = 12;
pub const IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT: usize = 13;
pub const IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR: usize = 14;
pub const IMAGE_NUMBEROF_DIRECTORY_ENTRIES: usize = 16;
pub const IMAGE_SCN_CNT_CODE: u32 = 0x00000020;
pub const IMAGE_SCN_CNT_INITIALIZED_DATA: u32 = 0x00000040;
pub const IMAGE_SCN_CNT_UNINITIALIZED_DATA: u32 = 0x00000080;
pub const IMAGE_SCN_MEM_EXECUTE: u32 = 0x20000000;
pub const IMAGE_SCN_MEM_READ: u32 = 0x40000000;
pub const IMAGE_SCN_MEM_WRITE: u32 = 0x80000000;
pub const IMAGE_SCN_MEM_DISCARDABLE: u32 = 0x02000000;
pub const IMAGE_SCN_MEM_NOT_CACHED: u32 = 0x04000000;
pub const IMAGE_SCN_MEM_NOT_PAGED: u32 = 0x08000000;
pub const IMAGE_SCN_MEM_SHARED: u32 = 0x10000000;
pub const IMAGE_SCN_LNK_REMOVE: u32 = 0x00000800;
pub const IMAGE_SCN_LNK_COMDAT: u32 = 0x00001000;
pub const IMAGE_FILE_RELOCS_STRIPPED: u16 = 0x0001;
pub const IMAGE_FILE_EXECUTABLE_IMAGE: u16 = 0x0002;
pub const IMAGE_FILE_LINE_NUMS_STRIPPED: u16 = 0x0004;
pub const IMAGE_FILE_LOCAL_SYMS_STRIPPED: u16 = 0x0008;
pub const IMAGE_FILE_LARGE_ADDRESS_AWARE: u16 = 0x0020;
pub const IMAGE_FILE_32BIT_MACHINE: u16 = 0x0100;
pub const IMAGE_FILE_DEBUG_STRIPPED: u16 = 0x0200;
pub const IMAGE_FILE_DLL: u16 = 0x2000;
pub const IMAGE_FILE_SYSTEM: u16 = 0x1000;
pub const DLL_NX_COMPAT: u16 = 0x0100;
pub const DLL_DYNAMIC_BASE: u16 = 0x0040;
pub const DLL_TERMINAL_SERVER_AWARE: u16 = 0x8000;
pub const DLL_HIGH_ENTROPY_VA: u16 = 0x0020;
pub const IMAGE_REL_AMD64_ABSOLUTE: u16 = 0x0000;
pub const IMAGE_REL_AMD64_ADDR64: u16 = 0x0001;
pub const IMAGE_REL_AMD64_ADDR32: u16 = 0x0002;
pub const IMAGE_REL_AMD64_ADDR32NB: u16 = 0x0003;
pub const IMAGE_REL_AMD64_REL32: u16 = 0x0004;
pub const IMAGE_REL_AMD64_REL32_1: u16 = 0x0005;
pub const IMAGE_REL_AMD64_REL32_2: u16 = 0x0006;
pub const IMAGE_REL_AMD64_REL32_3: u16 = 0x0007;
pub const IMAGE_REL_AMD64_REL32_4: u16 = 0x0008;
pub const IMAGE_REL_AMD64_REL32_5: u16 = 0x0009;
pub const IMAGE_REL_AMD64_SECTION: u16 = 0x000A;
pub const IMAGE_REL_AMD64_SECREL: u16 = 0x000B;
pub const IMAGE_REL_AMD64_SECREL7: u16 = 0x000C;
pub const IMAGE_REL_AMD64_TOKEN: u16 = 0x000D;
pub const IMAGE_REL_AMD64_SREL32: u16 = 0x000E;
pub const IMAGE_REL_AMD64_PAIR: u16 = 0x000F;
pub const IMAGE_REL_AMD64_SSPAN32: u16 = 0x0010;
pub const IMAGE_REL_I386_ABSOLUTE: u16 = 0x0000;
pub const IMAGE_REL_I386_DIR16: u16 = 0x0001;
pub const IMAGE_REL_I386_REL16: u16 = 0x0002;
pub const IMAGE_REL_I386_DIR32: u16 = 0x0006;
pub const IMAGE_REL_I386_DIR32NB: u16 = 0x0007;
pub const IMAGE_REL_I386_SEG12: u16 = 0x0009;
pub const IMAGE_REL_I386_SECTION: u16 = 0x000A;
pub const IMAGE_REL_I386_SECREL: u16 = 0x000B;
pub const IMAGE_REL_I386_TOKEN: u16 = 0x000C;
pub const IMAGE_REL_I386_SECREL7: u16 = 0x000D;
pub const IMAGE_REL_I386_REL32: u16 = 0x0014;
pub struct X86COFFLinkerFull {
pub objects: Vec<CoffObjectFull>,
pub symbols: Vec<CoffResolvedSymbolFull>,
pub undefined: Vec<String>,
pub sections: Vec<CoffOutputSectionFull>,
pub section_map: std::collections::HashMap<String, usize>,
pub arch: X86LLDArch,
pub imports: Vec<CoffImportFull>,
pub exports: Vec<CoffExportFull>,
pub image_base: u64,
pub subsystem: u16,
pub entry_point: String,
pub is_dll: bool,
pub has_reloc_section: bool,
pub has_tls: bool,
pub has_exception: bool,
pub has_debug: bool,
pub has_resource: bool,
pub delay_imports: Vec<DelayImportDescriptor>,
pub bound_imports: Vec<BoundImportEntry>,
pub debug_entries: Vec<DebugDirectoryEntry>,
pub tls_data: Option<TlsDirectoryData>,
pub load_config: Option<LoadConfigData>,
pub resource_data: Vec<u8>,
pub exception_data: Vec<ExceptionTableEntry>,
}
pub struct CoffObjectFull {
pub filename: String,
pub data: Vec<u8>,
pub header: CoffFileHeader,
pub sections: Vec<CoffSectionFull>,
pub symbols: Vec<CoffSymbolFull>,
pub relocations: Vec<CoffRelocationFull>,
}
pub struct CoffSectionFull {
pub name: String,
pub data: Vec<u8>,
pub header: CoffSectionHeader,
pub section_index: usize,
}
#[derive(Clone)]
pub struct CoffSymbolFull {
pub name: String,
pub value: u32,
pub section_number: i16,
pub sym_type: u16,
pub storage_class: u8,
pub number_of_aux_symbols: u8,
pub is_external: bool,
}
pub struct CoffRelocationFull {
pub virtual_address: u32,
pub symbol_table_index: u32,
pub rel_type: u16,
pub section_index: usize,
pub symbol_name: Option<String>,
}
pub struct CoffImportFull {
pub symbol_name: String,
pub dll_name: String,
pub ordinal: Option<u16>,
pub is_data: bool,
}
pub struct CoffExportFull {
pub name: String,
pub ordinal: u16,
pub rva: u32,
pub forwarder: Option<String>,
}
pub struct CoffOutputSectionFull {
pub name: String,
pub data: Vec<u8>,
pub characteristics: u32,
pub virtual_address: u32,
pub virtual_size: u32,
pub raw_offset: u32,
pub align: u32,
}
pub struct CoffResolvedSymbolFull {
pub name: String,
pub value: u64,
pub section_index: i32,
pub is_external: bool,
pub is_weak: bool,
}
pub struct DelayImportDescriptor {
pub dll_name: String,
pub module_handle_rva: u32,
pub import_address_table_rva: u32,
pub import_name_table_rva: u32,
pub bound_iat_rva: u32,
pub unload_iat_rva: u32,
pub timestamp: u32,
}
pub struct BoundImportEntry {
pub name: String,
pub timestamp: u32,
pub offset_module_name: u16,
pub number_of_module_forwarder_refs: u16,
}
pub struct DebugDirectoryEntry {
pub characteristics: u32,
pub time_date_stamp: u32,
pub major_version: u16,
pub minor_version: u16,
pub debug_type: u32,
pub size_of_data: u32,
pub address_of_raw_data: u32,
pub pointer_to_raw_data: u32,
}
pub const IMAGE_DEBUG_TYPE_UNKNOWN: u32 = 0;
pub const IMAGE_DEBUG_TYPE_COFF: u32 = 1;
pub const IMAGE_DEBUG_TYPE_CODEVIEW: u32 = 2;
pub const IMAGE_DEBUG_TYPE_FPO: u32 = 3;
pub const IMAGE_DEBUG_TYPE_MISC: u32 = 4;
pub const IMAGE_DEBUG_TYPE_EXCEPTION: u32 = 5;
pub const IMAGE_DEBUG_TYPE_FIXUP: u32 = 6;
pub const IMAGE_DEBUG_TYPE_OMAP_TO_SRC: u32 = 7;
pub const IMAGE_DEBUG_TYPE_OMAP_FROM_SRC: u32 = 8;
pub const IMAGE_DEBUG_TYPE_BORLAND: u32 = 9;
pub const IMAGE_DEBUG_TYPE_RESERVED10: u32 = 10;
pub const IMAGE_DEBUG_TYPE_CLSID: u32 = 11;
pub const IMAGE_DEBUG_TYPE_VC_FEATURE: u32 = 12;
pub const IMAGE_DEBUG_TYPE_POGO: u32 = 13;
pub const IMAGE_DEBUG_TYPE_ILTCG: u32 = 14;
pub const IMAGE_DEBUG_TYPE_MPX: u32 = 15;
pub const IMAGE_DEBUG_TYPE_REPRO: u32 = 16;
pub struct TlsDirectoryData {
pub start_address_of_raw_data: u64,
pub end_address_of_raw_data: u64,
pub address_of_index: u64,
pub address_of_callbacks: u64,
pub size_of_zero_fill: u32,
pub characteristics: u32,
}
pub struct LoadConfigData {
pub size: u32,
pub time_date_stamp: u32,
pub major_version: u16,
pub minor_version: u16,
pub global_flags_clear: u32,
pub global_flags_set: u32,
pub critical_section_default_timeout: u32,
pub decommit_free_block_threshold: u64,
pub decommit_total_free_threshold: u64,
pub lock_prefix_table: u64,
pub maximum_allocation_size: u64,
pub virtual_memory_threshold: u64,
pub process_affinity_mask: u64,
pub process_heap_flags: u32,
pub csd_version: u16,
pub dependent_load_flags: u16,
pub edit_list: u64,
pub security_cookie: u64,
pub se_handler_table: u64,
pub se_handler_count: u64,
pub guard_cf_check_function_pointer: u64,
pub guard_cf_dispatch_function_pointer: u64,
pub guard_cf_function_table: u64,
pub guard_cf_function_count: u64,
pub guard_flags: u32,
}
pub struct ExceptionTableEntry {
pub begin_address: u32,
pub end_address: u32,
pub unwind_info_address: u32,
}
pub struct BaseRelocationBlock {
pub page_rva: u32,
pub entries: Vec<BaseRelocationEntry>,
}
pub struct BaseRelocationEntry {
pub offset: u16,
pub rel_type: u8,
}
pub const IMAGE_REL_BASED_ABSOLUTE: u8 = 0;
pub const IMAGE_REL_BASED_HIGH: u8 = 1;
pub const IMAGE_REL_BASED_LOW: u8 = 2;
pub const IMAGE_REL_BASED_HIGHLOW: u8 = 3;
pub const IMAGE_REL_BASED_HIGHADJ: u8 = 4;
pub const IMAGE_REL_BASED_DIR64: u8 = 10;
pub struct ResourceDirectoryHeader {
pub characteristics: u32,
pub time_date_stamp: u32,
pub major_version: u16,
pub minor_version: u16,
pub number_of_named_entries: u16,
pub number_of_id_entries: u16,
}
pub struct ResourceDirectoryEntry {
pub name_or_id: u32,
pub offset_to_data_or_directory: u32,
pub is_subdir: bool,
}
pub struct ResourceDataEntry {
pub offset_to_data: u32,
pub size: u32,
pub code_page: u32,
pub reserved: u32,
}
impl X86COFFLinkerFull {
pub fn new(arch: X86LLDArch) -> Self {
X86COFFLinkerFull {
arch: arch.clone(),
objects: Vec::new(),
symbols: Vec::new(),
undefined: Vec::new(),
sections: Vec::new(),
section_map: std::collections::HashMap::new(),
imports: Vec::new(),
exports: Vec::new(),
image_base: if arch.is_64bit() {
X86_64_DEFAULT_IMAGE_BASE
} else {
I386_DEFAULT_IMAGE_BASE as u64
},
subsystem: 3, entry_point: String::from("mainCRTStartup"),
is_dll: false,
has_reloc_section: false,
has_tls: false,
has_exception: false,
has_debug: false,
has_resource: false,
delay_imports: Vec::new(),
bound_imports: Vec::new(),
debug_entries: Vec::new(),
tls_data: None,
load_config: None,
resource_data: Vec::new(),
exception_data: Vec::new(),
}
}
pub fn add_object(&mut self, filename: &str, data: Vec<u8>) -> Result<(), String> {
let obj = self.parse_coff_object_full(filename, &data)?;
self.objects.push(obj);
Ok(())
}
pub fn parse_coff_object_full(
&self,
filename: &str,
data: &[u8],
) -> Result<CoffObjectFull, String> {
if data.len() < 20 {
return Err(format!("{}: file too small for COFF header", filename));
}
let machine = u16::from_le_bytes([data[0], data[1]]);
let number_of_sections = u16::from_le_bytes([data[2], data[3]]);
let time_date_stamp = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
let pointer_to_symbol_table = u32::from_le_bytes([data[8], data[9], data[10], data[11]]);
let number_of_symbols = u32::from_le_bytes([data[12], data[13], data[14], data[15]]);
let size_of_optional_header = u16::from_le_bytes([data[16], data[17]]);
let characteristics = u16::from_le_bytes([data[18], data[19]]);
let header = CoffFileHeader {
machine,
number_of_sections,
time_date_stamp,
pointer_to_symbol_table,
number_of_symbols,
size_of_optional_header,
characteristics,
};
let mut cursor = 20usize + size_of_optional_header as usize;
let mut sections = Vec::new();
let mut relocs = Vec::new();
for i in 0..number_of_sections as usize {
if cursor + CoffSectionHeader::SIZE > data.len() {
return Err(format!(
"{}: truncated section header at index {}",
filename, i
));
}
let d = &data[cursor..];
let mut name = [0u8; 8];
name.copy_from_slice(&d[0..8]);
let virtual_size = u32::from_le_bytes([d[8], d[9], d[10], d[11]]);
let virtual_address = u32::from_le_bytes([d[12], d[13], d[14], d[15]]);
let size_of_raw_data = u32::from_le_bytes([d[16], d[17], d[18], d[19]]);
let pointer_to_raw_data = u32::from_le_bytes([d[20], d[21], d[22], d[23]]);
let pointer_to_relocations = u32::from_le_bytes([d[24], d[25], d[26], d[27]]);
let pointer_to_line_numbers = u32::from_le_bytes([d[28], d[29], d[30], d[31]]);
let number_of_relocations = u16::from_le_bytes([d[32], d[33]]);
let number_of_line_numbers = u16::from_le_bytes([d[34], d[35]]);
let characteristics_field = u32::from_le_bytes([d[36], d[37], d[38], d[39]]);
let sec_header = CoffSectionHeader {
name,
virtual_size,
virtual_address,
size_of_raw_data,
pointer_to_raw_data,
pointer_to_relocations,
pointer_to_line_numbers,
number_of_relocations,
number_of_line_numbers,
characteristics: characteristics_field,
};
let sec_name = if name[0] == b'/' {
let offset_str = std::str::from_utf8(&name[1..])
.unwrap_or("")
.trim_end_matches('\0');
if let Ok(offset) = offset_str.parse::<usize>() {
get_c_string(data, offset).unwrap_or_else(|| format!("sect{}", i))
} else {
format!("sect{}", i)
}
} else {
let len = name.iter().position(|&b| b == 0).unwrap_or(8);
String::from_utf8_lossy(&name[..len]).to_string()
};
let mut sec_data = Vec::new();
if size_of_raw_data > 0 && pointer_to_raw_data > 0 {
let start = pointer_to_raw_data as usize;
let end = std::cmp::min(start + size_of_raw_data as usize, data.len());
if start < data.len() {
sec_data.extend_from_slice(&data[start..end]);
}
if sec_data.len() < size_of_raw_data as usize {
sec_data.resize(size_of_raw_data as usize, 0u8);
}
}
if number_of_relocations > 0 && pointer_to_relocations > 0 {
let reloc_start = pointer_to_relocations as usize;
for r in 0..number_of_relocations as usize {
let roff = reloc_start + r * 10;
if roff + 10 > data.len() {
break;
}
let va = u32::from_le_bytes([
data[roff],
data[roff + 1],
data[roff + 2],
data[roff + 3],
]);
let si = u32::from_le_bytes([
data[roff + 4],
data[roff + 5],
data[roff + 6],
data[roff + 7],
]);
let rt = u16::from_le_bytes([data[roff + 8], data[roff + 9]]);
relocs.push(CoffRelocationFull {
virtual_address: va,
symbol_table_index: si,
rel_type: rt,
section_index: i,
symbol_name: None,
});
}
}
sections.push(CoffSectionFull {
name: sec_name,
data: sec_data,
header: sec_header,
section_index: i,
});
cursor += CoffSectionHeader::SIZE;
}
let mut symbols = Vec::new();
let mut strtab_offset = 0usize;
if pointer_to_symbol_table > 0 && number_of_symbols > 0 {
let sym_start = pointer_to_symbol_table as usize;
let sym_end = sym_start + (number_of_symbols as usize * CoffSymbol::SIZE);
if sym_start < data.len() {
for s in 0..number_of_symbols as usize {
let soff = sym_start + s * CoffSymbol::SIZE;
if soff + CoffSymbol::SIZE > data.len() {
break;
}
let d2 = &data[soff..];
let mut sname = [0u8; 8];
sname.copy_from_slice(&d2[0..8]);
let val = u32::from_le_bytes([d2[8], d2[9], d2[10], d2[11]]);
let sec = i16::from_le_bytes([d2[12], d2[13]]);
let st = u16::from_le_bytes([d2[14], d2[15]]);
let sc = d2[16];
let nas = d2[17];
let sym_name =
if sname[0] == 0 && sname[1] == 0 && sname[2] == 0 && sname[3] == 0 {
let off = u32::from_le_bytes([sname[4], sname[5], sname[6], sname[7]])
as usize;
if off > 0 {
strtab_offset = sym_end;
get_c_string(data, strtab_offset + off)
.unwrap_or_else(|| format!("sym{}", s))
} else {
format!("sym{}", s)
}
} else {
let len = sname.iter().position(|&b| b == 0).unwrap_or(8);
String::from_utf8_lossy(&sname[..len]).to_string()
};
symbols.push(CoffSymbolFull {
name: sym_name,
value: val,
section_number: sec,
sym_type: st,
storage_class: sc,
number_of_aux_symbols: nas,
is_external: sc == 2, });
s += nas as usize;
}
}
}
for reloc in &mut relocs {
let si = reloc.symbol_table_index as usize;
if si < symbols.len() {
reloc.symbol_name = Some(symbols[si].name.clone());
}
}
Ok(CoffObjectFull {
filename: filename.to_string(),
data: data.to_vec(),
header,
sections,
symbols,
relocations: relocs,
})
}
pub fn add_import(
&mut self,
symbol_name: &str,
dll_name: &str,
ordinal: Option<u16>,
is_data: bool,
) {
self.imports.push(CoffImportFull {
symbol_name: symbol_name.to_string(),
dll_name: dll_name.to_string(),
ordinal,
is_data,
});
}
pub fn add_export(&mut self, name: &str, ordinal: u16, rva: u32) {
self.exports.push(CoffExportFull {
name: name.to_string(),
ordinal,
rva,
forwarder: None,
});
}
pub fn add_delay_import(&mut self, dll_name: &str) {
self.delay_imports.push(DelayImportDescriptor {
dll_name: dll_name.to_string(),
module_handle_rva: 0,
import_address_table_rva: 0,
import_name_table_rva: 0,
bound_iat_rva: 0,
unload_iat_rva: 0,
timestamp: 0,
});
}
pub fn add_bound_import(&mut self, name: &str, timestamp: u32) {
self.bound_imports.push(BoundImportEntry {
name: name.to_string(),
timestamp,
offset_module_name: 0,
number_of_module_forwarder_refs: 0,
});
}
pub fn add_debug_entry(&mut self, debug_type: u32, data: &[u8]) {
self.debug_entries.push(DebugDirectoryEntry {
characteristics: 0,
time_date_stamp: 0,
major_version: 0,
minor_version: 0,
debug_type,
size_of_data: data.len() as u32,
address_of_raw_data: 0,
pointer_to_raw_data: 0,
});
}
pub fn set_tls_data(&mut self, tls: TlsDirectoryData) {
self.tls_data = Some(tls);
self.has_tls = true;
}
pub fn set_load_config(&mut self, lc: LoadConfigData) {
self.load_config = Some(lc);
}
pub fn set_resource_data(&mut self, data: Vec<u8>) {
self.resource_data = data;
self.has_resource = true;
}
pub fn add_exception_entry(&mut self, entry: ExceptionTableEntry) {
self.exception_data.push(entry);
self.has_exception = true;
}
pub fn link(&mut self) -> Result<(), String> {
self.resolve_symbols()?;
self.merge_coff_sections()?;
self.compute_coff_layout();
Ok(())
}
fn resolve_symbols(&mut self) -> Result<(), String> {
let mut sym_map: std::collections::HashMap<String, CoffResolvedSymbolFull> =
std::collections::HashMap::new();
for obj in &self.objects {
for sym in &obj.symbols {
let entry =
sym_map
.entry(sym.name.clone())
.or_insert_with(|| CoffResolvedSymbolFull {
name: sym.name.clone(),
value: 0,
section_index: -1,
is_external: sym.is_external,
is_weak: false,
});
if sym.section_number > 0 {
entry.section_index = sym.section_number as i32;
entry.value = sym.value as u64;
}
}
}
for imp in &self.imports {
sym_map
.entry(imp.symbol_name.clone())
.or_insert_with(|| CoffResolvedSymbolFull {
name: imp.symbol_name.clone(),
value: 0,
section_index: -1,
is_external: true,
is_weak: false,
});
}
self.symbols = sym_map.into_values().collect();
Ok(())
}
fn merge_coff_sections(&mut self) -> Result<(), String> {
let mut groups: std::collections::HashMap<String, Vec<usize>> =
std::collections::HashMap::new();
for (i, obj) in self.objects.iter().enumerate() {
for sec in &obj.sections {
groups.entry(sec.name.clone()).or_default().push(i);
}
}
let section_order = vec![
".text", ".rdata", ".data", ".bss", ".idata", ".edata", ".pdata", ".xdata", ".rsrc",
".reloc", ".tls", ".debug", ".CRT",
];
for name in §ion_order {
if let Some(obj_indices) = groups.get(*name) {
let mut data = Vec::new();
for &idx in obj_indices {
let obj = &self.objects[idx];
for sec in &obj.sections {
if sec.name == *name && !sec.data.is_empty() {
data.extend_from_slice(&sec.data);
}
}
}
let chars = Self::section_name_to_characteristics(name);
let align = 16;
self.sections.push(CoffOutputSectionFull {
name: name.to_string(),
data,
characteristics: chars,
virtual_address: 0,
virtual_size: 0,
raw_offset: 0,
align,
});
self.section_map
.insert(name.to_string(), self.sections.len() - 1);
}
}
for name in groups.keys() {
if !self.section_map.contains_key(name.as_str()) {
let mut data = Vec::new();
if let Some(obj_indices) = groups.get(name.as_str()) {
for &idx in obj_indices {
let obj = &self.objects[idx];
for sec in &obj.sections {
if sec.name == *name && !sec.data.is_empty() {
data.extend_from_slice(&sec.data);
}
}
}
}
self.sections.push(CoffOutputSectionFull {
name: name.clone(),
data,
characteristics: IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ,
virtual_address: 0,
virtual_size: 0,
raw_offset: 0,
align: 16,
});
self.section_map
.insert(name.clone(), self.sections.len() - 1);
}
}
Ok(())
}
fn section_name_to_characteristics(name: &str) -> u32 {
match name {
".text" | ".code" => IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ,
".rdata" | ".rodata" | ".edata" | ".idata" | ".pdata" | ".xdata" => {
IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ
}
".data" | ".tls" => {
IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE
}
".bss" => IMAGE_SCN_CNT_UNINITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE,
".rsrc" => IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ,
".reloc" => {
IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_DISCARDABLE
}
".debug" => {
IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_DISCARDABLE
}
_ => IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ,
}
}
fn compute_coff_layout(&mut self) {
let section_alignment = 4096u32;
let file_alignment = 512u32;
let header_size = self.compute_header_size();
let mut vaddr = align_up(header_size as u64, section_alignment as u64) as u32;
let mut file_offset = align_up(header_size as u64, file_alignment as u64) as u32;
for sec in &mut self.sections {
sec.virtual_address = vaddr;
sec.virtual_size = align_up(sec.data.len() as u64, section_alignment as u64) as u32;
sec.raw_offset = file_offset;
if !sec.data.is_empty() {
vaddr = align_up(
(sec.virtual_address + sec.virtual_size) as u64,
section_alignment as u64,
) as u32;
file_offset = align_up(
(file_offset + sec.data.len() as u32) as u64,
file_alignment as u64,
) as u32;
}
}
}
fn compute_header_size(&self) -> usize {
let dos_size = 64; let pe_sig = 4; let coff_hdr = 20; let opt_hdr = if self.arch.is_64bit() {
PE32PlusOptionalHeader::SIZE
} else {
PE32OptionalHeader::SIZE
};
let data_dirs = IMAGE_NUMBEROF_DIRECTORY_ENTRIES * ImageDataDirectory::SIZE;
let sec_hdrs = self.sections.len() * CoffSectionHeader::SIZE;
dos_size + pe_sig + coff_hdr + opt_hdr + data_dirs + sec_hdrs
}
pub fn emit(&mut self) -> Result<Vec<u8>, String> {
let is_64 = self.arch.is_64bit();
let section_alignment = 4096u32;
let file_alignment = 512u32;
let header_size = self.compute_header_size();
let mut buf = Vec::new();
self.emit_dos_stub(&mut buf);
buf.extend_from_slice(b"PE\0\0");
let mut characteristics = IMAGE_FILE_EXECUTABLE_IMAGE;
if is_64 {
characteristics |= IMAGE_FILE_LARGE_ADDRESS_AWARE;
} else {
characteristics |= IMAGE_FILE_32BIT_MACHINE;
}
if self.is_dll {
characteristics |= IMAGE_FILE_DLL;
}
let machine = if is_64 {
IMAGE_FILE_MACHINE_AMD64
} else {
IMAGE_FILE_MACHINE_I386
};
let num_sections = self.sections.len() as u16;
let opt_hdr_size = if is_64 {
(PE32PlusOptionalHeader::SIZE
+ IMAGE_NUMBEROF_DIRECTORY_ENTRIES * ImageDataDirectory::SIZE) as u16
} else {
(PE32OptionalHeader::SIZE + IMAGE_NUMBEROF_DIRECTORY_ENTRIES * ImageDataDirectory::SIZE)
as u16
};
buf.extend_from_slice(&machine.to_le_bytes());
buf.extend_from_slice(&num_sections.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(&opt_hdr_size.to_le_bytes());
buf.extend_from_slice(&characteristics.to_le_bytes());
if is_64 {
self.emit_optional_header_64(&mut buf, section_alignment, file_alignment);
} else {
self.emit_optional_header_32(&mut buf, section_alignment, file_alignment);
}
self.emit_data_directories(&mut buf);
self.emit_section_headers_full(&mut buf, file_alignment);
for sec in &self.sections {
while buf.len() < sec.raw_offset as usize {
buf.push(0u8);
}
buf.extend_from_slice(&sec.data);
let aligned = align_up(sec.data.len() as u64, file_alignment as u64) as usize;
while buf.len() < sec.raw_offset as usize + aligned {
buf.push(0u8);
}
}
Ok(buf)
}
fn emit_dos_stub(&self, buf: &mut Vec<u8>) {
buf.extend_from_slice(b"MZ");
buf.extend_from_slice(&[0x90, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00]);
buf.extend_from_slice(&[0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00]);
let mut rest = vec![0u8; 44];
let pe_offset: u32 = 64; rest[0x3C - 0x1A] = (pe_offset & 0xFF) as u8;
rest[0x3C - 0x1A + 1] = ((pe_offset >> 8) & 0xFF) as u8;
rest[0x3C - 0x1A + 2] = ((pe_offset >> 16) & 0xFF) as u8;
rest[0x3C - 0x1A + 3] = ((pe_offset >> 24) & 0xFF) as u8;
buf.extend_from_slice(&rest);
}
fn emit_optional_header_64(
&self,
buf: &mut Vec<u8>,
section_alignment: u32,
file_alignment: u32,
) {
let entry_rva = self.find_entry_rva();
buf.extend_from_slice(&PE32PLUS_MAGIC.to_le_bytes());
buf.push(0); buf.push(0);
let code_size = self
.sections
.iter()
.filter(|s| s.characteristics & IMAGE_SCN_CNT_CODE != 0)
.map(|s| s.data.len() as u32)
.sum::<u32>();
let init_data_size = self
.sections
.iter()
.filter(|s| s.characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA != 0)
.map(|s| s.data.len() as u32)
.sum::<u32>();
let uninit_data_size = self
.sections
.iter()
.filter(|s| s.characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA != 0)
.map(|s| s.data.len() as u32)
.sum::<u32>();
buf.extend_from_slice(&code_size.to_le_bytes());
buf.extend_from_slice(&init_data_size.to_le_bytes());
buf.extend_from_slice(&uninit_data_size.to_le_bytes());
buf.extend_from_slice(&entry_rva.to_le_bytes());
let base_of_code = self
.sections
.iter()
.find(|s| s.characteristics & IMAGE_SCN_CNT_CODE != 0)
.map(|s| s.virtual_address)
.unwrap_or(0);
buf.extend_from_slice(&base_of_code.to_le_bytes());
buf.extend_from_slice(&self.image_base.to_le_bytes());
buf.extend_from_slice(§ion_alignment.to_le_bytes());
buf.extend_from_slice(&file_alignment.to_le_bytes());
buf.extend_from_slice(&6u16.to_le_bytes()); buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&6u16.to_le_bytes()); buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); let last_sec = self.sections.last();
let image_size = match last_sec {
Some(s) => align_up(
(s.virtual_address + s.virtual_size) as u64,
section_alignment as u64,
) as u32,
None => align_up(self.compute_header_size() as u64, section_alignment as u64) as u32,
};
buf.extend_from_slice(&image_size.to_le_bytes());
let headers_size =
align_up(self.compute_header_size() as u64, file_alignment as u64) as u32;
buf.extend_from_slice(&headers_size.to_le_bytes());
buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&self.subsystem.to_le_bytes());
let mut dll_chars: u16 = DLL_NX_COMPAT | DLL_DYNAMIC_BASE | DLL_TERMINAL_SERVER_AWARE;
if true {
dll_chars |= DLL_HIGH_ENTROPY_VA;
}
buf.extend_from_slice(&dll_chars.to_le_bytes());
buf.extend_from_slice(&0x100000u64.to_le_bytes()); buf.extend_from_slice(&0x1000u64.to_le_bytes()); buf.extend_from_slice(&0x100000u64.to_le_bytes()); buf.extend_from_slice(&0x1000u64.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&(IMAGE_NUMBEROF_DIRECTORY_ENTRIES as u32).to_le_bytes());
}
fn emit_optional_header_32(
&self,
buf: &mut Vec<u8>,
section_alignment: u32,
file_alignment: u32,
) {
let entry_rva = self.find_entry_rva();
buf.extend_from_slice(&PE32_MAGIC.to_le_bytes());
buf.push(0);
buf.push(0); let code_size = self
.sections
.iter()
.filter(|s| s.characteristics & IMAGE_SCN_CNT_CODE != 0)
.map(|s| s.data.len() as u32)
.sum::<u32>();
let init_size = self
.sections
.iter()
.filter(|s| s.characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA != 0)
.map(|s| s.data.len() as u32)
.sum::<u32>();
let uninit_size = self
.sections
.iter()
.filter(|s| s.characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA != 0)
.map(|s| s.data.len() as u32)
.sum::<u32>();
buf.extend_from_slice(&code_size.to_le_bytes());
buf.extend_from_slice(&init_size.to_le_bytes());
buf.extend_from_slice(&uninit_size.to_le_bytes());
buf.extend_from_slice(&entry_rva.to_le_bytes());
let base_of_code = self
.sections
.iter()
.find(|s| s.characteristics & IMAGE_SCN_CNT_CODE != 0)
.map(|s| s.virtual_address)
.unwrap_or(0);
buf.extend_from_slice(&base_of_code.to_le_bytes());
buf.extend_from_slice(&0u32.to_le_bytes());
buf.extend_from_slice(&(self.image_base as u32).to_le_bytes());
buf.extend_from_slice(§ion_alignment.to_le_bytes());
buf.extend_from_slice(&file_alignment.to_le_bytes());
buf.extend_from_slice(&6u16.to_le_bytes());
buf.extend_from_slice(&0u16.to_le_bytes());
buf.extend_from_slice(&0u16.to_le_bytes());
buf.extend_from_slice(&0u16.to_le_bytes());
buf.extend_from_slice(&6u16.to_le_bytes());
buf.extend_from_slice(&0u16.to_le_bytes());
buf.extend_from_slice(&0u32.to_le_bytes());
let last_sec = self.sections.last();
let image_size = match last_sec {
Some(s) => align_up(
(s.virtual_address + s.virtual_size) as u64,
section_alignment as u64,
) as u32,
None => align_up(self.compute_header_size() as u64, section_alignment as u64) as u32,
};
buf.extend_from_slice(&image_size.to_le_bytes());
let hdr_size = align_up(self.compute_header_size() as u64, file_alignment as u64) as u32;
buf.extend_from_slice(&hdr_size.to_le_bytes());
buf.extend_from_slice(&0u32.to_le_bytes());
buf.extend_from_slice(&self.subsystem.to_le_bytes());
let dll_chars: u16 = DLL_NX_COMPAT | DLL_DYNAMIC_BASE | DLL_TERMINAL_SERVER_AWARE;
buf.extend_from_slice(&dll_chars.to_le_bytes());
buf.extend_from_slice(&0x100000u32.to_le_bytes()); buf.extend_from_slice(&0x1000u32.to_le_bytes()); buf.extend_from_slice(&0x100000u32.to_le_bytes()); buf.extend_from_slice(&0x1000u32.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes());
buf.extend_from_slice(&(IMAGE_NUMBEROF_DIRECTORY_ENTRIES as u32).to_le_bytes());
}
fn emit_data_directories(&self, buf: &mut Vec<u8>) {
self.emit_one_directory(
buf,
IMAGE_DIRECTORY_ENTRY_EXPORT,
self.emit_export_directory(),
);
self.emit_one_directory(
buf,
IMAGE_DIRECTORY_ENTRY_IMPORT,
self.emit_import_directory(),
);
if self.has_resource {
let rva = self.find_section_rva(".rsrc");
let sz = self.resource_data.len() as u32;
self.write_data_dir(buf, rva, sz);
} else {
self.write_data_dir(buf, 0, 0);
}
if self.has_exception {
let rva = self.find_section_rva(".pdata");
let sz = (self.exception_data.len() * 12) as u32;
self.write_data_dir(buf, rva, sz);
} else {
self.write_data_dir(buf, 0, 0);
}
self.write_data_dir(buf, 0, 0);
self.write_data_dir(buf, 0, 0);
self.write_data_dir(buf, 0, 0);
self.write_data_dir(buf, 0, 0);
self.write_data_dir(buf, 0, 0);
if self.has_tls {
let rva = self.find_section_rva(".tls");
let sz = 0u32; self.write_data_dir(buf, rva, sz);
} else {
self.write_data_dir(buf, 0, 0);
}
self.write_data_dir(buf, 0, 0);
self.write_data_dir(buf, 0, 0);
self.write_data_dir(buf, 0, 0);
self.write_data_dir(buf, 0, 0);
self.write_data_dir(buf, 0, 0);
self.write_data_dir(buf, 0, 0);
}
fn write_data_dir(&self, buf: &mut Vec<u8>, rva: u32, size: u32) {
buf.extend_from_slice(&rva.to_le_bytes());
buf.extend_from_slice(&size.to_le_bytes());
}
fn emit_one_directory(&self, buf: &mut Vec<u8>, slot: usize, (rva, size): (u32, u32)) {
let current_slot = buf.len() / 8;
while buf.len() < (slot * 8) {
}
let _ = current_slot;
buf.extend_from_slice(&rva.to_le_bytes());
buf.extend_from_slice(&size.to_le_bytes());
}
fn emit_export_directory(&self) -> (u32, u32) {
if self.exports.is_empty() {
return (0, 0);
}
let rva = self.find_section_rva(".edata");
let sz = 40u32; (rva, sz)
}
fn emit_import_directory(&self) -> (u32, u32) {
if self.imports.is_empty() {
return (0, 0);
}
let rva = self.find_section_rva(".idata");
let sz = 20u32; (rva, sz)
}
fn emit_section_headers_full(&self, buf: &mut Vec<u8>, file_alignment: u32) {
for sec in &self.sections {
let mut name = [0u8; 8];
let name_bytes = sec.name.as_bytes();
let n = std::cmp::min(name_bytes.len(), 8);
name[..n].copy_from_slice(&name_bytes[..n]);
buf.extend_from_slice(&name);
buf.extend_from_slice(&sec.virtual_size.to_le_bytes());
buf.extend_from_slice(&sec.virtual_address.to_le_bytes());
buf.extend_from_slice(&(sec.data.len() as u32).to_le_bytes());
buf.extend_from_slice(&sec.raw_offset.to_le_bytes());
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(&sec.characteristics.to_le_bytes());
}
}
pub fn find_section_rva(&self, name: &str) -> u32 {
for sec in &self.sections {
if sec.name == name {
return sec.virtual_address;
}
}
0
}
fn find_entry_rva(&self) -> u32 {
let section_idx = self.section_map.get(".text").copied();
if let Some(idx) = section_idx {
if idx < self.sections.len() {
return self.sections[idx].virtual_address;
}
}
0x1000 }
pub fn find_symbol_rva(&self, name: &str) -> Option<u32> {
for sym in &self.symbols {
if sym.name == name && sym.section_index > 0 {
return Some(sym.value as u32);
}
}
None
}
pub fn generate_import_library(&self) -> Vec<u8> {
let mut archive = X86ArchiveWriter::new();
let mut dll_groups: std::collections::HashMap<String, Vec<&CoffImportFull>> =
std::collections::HashMap::new();
for imp in &self.imports {
dll_groups
.entry(imp.dll_name.clone())
.or_default()
.push(imp);
}
for (dll_name, imports) in &dll_groups {
let mut obj = Vec::new();
let machine = if self.arch.is_64bit() {
IMAGE_FILE_MACHINE_AMD64
} else {
IMAGE_FILE_MACHINE_I386
};
obj.extend_from_slice(&machine.to_le_bytes());
obj.extend_from_slice(&2u16.to_le_bytes());
obj.extend_from_slice(&0u32.to_le_bytes()); obj.extend_from_slice(&0u32.to_le_bytes()); obj.extend_from_slice(&0u32.to_le_bytes()); obj.extend_from_slice(&0u16.to_le_bytes()); obj.extend_from_slice(&0u16.to_le_bytes());
let mut idata6 = Vec::new();
for imp in imports {
if let Some(ord) = imp.ordinal {
idata6.extend_from_slice(&ord.to_le_bytes());
} else {
idata6.extend_from_slice(&0u16.to_le_bytes());
}
idata6.extend_from_slice(imp.symbol_name.as_bytes());
idata6.push(0u8);
}
let idata2 = vec![0u8; imports.len() * (if self.arch.is_64bit() { 8 } else { 4 })];
archive.add_member(&format!("{}.obj", dll_name), &obj);
}
archive.build()
}
pub fn generate_exports(&self) -> Vec<u8> {
if self.exports.is_empty() {
return Vec::new();
}
let mut data = Vec::new();
let flags: u32 = 0;
let timestamp: u32 = 0;
let major: u16 = 0;
let minor: u16 = 0;
let name_rva: u32 = 0;
let base: u32 = 1;
let number_of_functions = self.exports.len() as u32;
let number_of_names = self.exports.len() as u32;
let addr_funcs_rva: u32 = 0;
let addr_names_rva: u32 = 0;
let addr_ordinals_rva: u32 = 0;
data.extend_from_slice(&flags.to_le_bytes());
data.extend_from_slice(×tamp.to_le_bytes());
data.extend_from_slice(&major.to_le_bytes());
data.extend_from_slice(&minor.to_le_bytes());
data.extend_from_slice(&name_rva.to_le_bytes());
data.extend_from_slice(&base.to_le_bytes());
data.extend_from_slice(&number_of_functions.to_le_bytes());
data.extend_from_slice(&number_of_names.to_le_bytes());
data.extend_from_slice(&addr_funcs_rva.to_le_bytes());
data.extend_from_slice(&addr_names_rva.to_le_bytes());
data.extend_from_slice(&addr_ordinals_rva.to_le_bytes());
data
}
pub fn generate_delay_import_table(&self) -> Vec<u8> {
let mut data = Vec::new();
for delay in &self.delay_imports {
data.extend_from_slice(&delay.attributes().to_le_bytes()); data.extend_from_slice(delay.dll_name.as_bytes());
data.push(0u8);
while data.len() % 4 != 0 {
data.push(0u8);
}
data.extend_from_slice(&delay.module_handle_rva.to_le_bytes());
data.extend_from_slice(&delay.import_address_table_rva.to_le_bytes());
data.extend_from_slice(&delay.import_name_table_rva.to_le_bytes());
data.extend_from_slice(&delay.bound_iat_rva.to_le_bytes());
data.extend_from_slice(&delay.unload_iat_rva.to_le_bytes());
data.extend_from_slice(&delay.timestamp.to_le_bytes());
}
data.extend_from_slice(&[0u8; 32]);
data
}
pub fn generate_exception_table(&self) -> Vec<u8> {
let mut data = Vec::new();
for entry in &self.exception_data {
data.extend_from_slice(&entry.begin_address.to_le_bytes());
data.extend_from_slice(&entry.end_address.to_le_bytes());
data.extend_from_slice(&entry.unwind_info_address.to_le_bytes());
}
data
}
pub fn generate_base_relocations(&self, blocks: &[BaseRelocationBlock]) -> Vec<u8> {
let mut data = Vec::new();
for block in blocks {
data.extend_from_slice(&block.page_rva.to_le_bytes());
let block_size = 8u32 + (block.entries.len() as u32 * 2);
data.extend_from_slice(&block_size.to_le_bytes());
for entry in &block.entries {
let type_offset: u16 = ((entry.rel_type as u16) << 12) | (entry.offset & 0x0FFF);
data.extend_from_slice(&type_offset.to_le_bytes());
}
while data.len() % 4 != 0 {
data.push(0u8);
}
}
data
}
pub fn generate_tls_directory(&self) -> Vec<u8> {
match &self.tls_data {
Some(tls) => {
let mut data = Vec::new();
let is_64 = self.arch.is_64bit();
if is_64 {
data.extend_from_slice(&tls.start_address_of_raw_data.to_le_bytes());
data.extend_from_slice(&tls.end_address_of_raw_data.to_le_bytes());
data.extend_from_slice(&tls.address_of_index.to_le_bytes());
data.extend_from_slice(&tls.address_of_callbacks.to_le_bytes());
} else {
data.extend_from_slice(&(tls.start_address_of_raw_data as u32).to_le_bytes());
data.extend_from_slice(&(tls.end_address_of_raw_data as u32).to_le_bytes());
data.extend_from_slice(&(tls.address_of_index as u32).to_le_bytes());
data.extend_from_slice(&(tls.address_of_callbacks as u32).to_le_bytes());
}
data.extend_from_slice(&tls.size_of_zero_fill.to_le_bytes());
data.extend_from_slice(&tls.characteristics.to_le_bytes());
data
}
None => Vec::new(),
}
}
pub fn generate_load_config(&self) -> Vec<u8> {
match &self.load_config {
Some(lc) => {
let mut data = Vec::new();
data.extend_from_slice(&lc.size.to_le_bytes());
data.extend_from_slice(&lc.time_date_stamp.to_le_bytes());
data.extend_from_slice(&lc.major_version.to_le_bytes());
data.extend_from_slice(&lc.minor_version.to_le_bytes());
data.extend_from_slice(&lc.global_flags_clear.to_le_bytes());
data.extend_from_slice(&lc.global_flags_set.to_le_bytes());
data.extend_from_slice(&lc.critical_section_default_timeout.to_le_bytes());
let is_64 = self.arch.is_64bit();
if is_64 {
data.extend_from_slice(&lc.decommit_free_block_threshold.to_le_bytes());
data.extend_from_slice(&lc.decommit_total_free_threshold.to_le_bytes());
data.extend_from_slice(&lc.lock_prefix_table.to_le_bytes());
data.extend_from_slice(&lc.maximum_allocation_size.to_le_bytes());
data.extend_from_slice(&lc.virtual_memory_threshold.to_le_bytes());
data.extend_from_slice(&lc.process_affinity_mask.to_le_bytes());
} else {
data.extend_from_slice(
&(lc.decommit_free_block_threshold as u32).to_le_bytes(),
);
data.extend_from_slice(
&(lc.decommit_total_free_threshold as u32).to_le_bytes(),
);
data.extend_from_slice(&(lc.lock_prefix_table as u32).to_le_bytes());
data.extend_from_slice(&(lc.maximum_allocation_size as u32).to_le_bytes());
data.extend_from_slice(&(lc.virtual_memory_threshold as u32).to_le_bytes());
data.extend_from_slice(&(lc.process_affinity_mask as u32).to_le_bytes());
}
data.extend_from_slice(&lc.process_heap_flags.to_le_bytes());
data.extend_from_slice(&lc.csd_version.to_le_bytes());
data.extend_from_slice(&lc.dependent_load_flags.to_le_bytes());
if is_64 {
data.extend_from_slice(&lc.edit_list.to_le_bytes());
data.extend_from_slice(&lc.security_cookie.to_le_bytes());
data.extend_from_slice(&lc.se_handler_table.to_le_bytes());
data.extend_from_slice(&lc.se_handler_count.to_le_bytes());
data.extend_from_slice(&lc.guard_cf_check_function_pointer.to_le_bytes());
data.extend_from_slice(&lc.guard_cf_dispatch_function_pointer.to_le_bytes());
data.extend_from_slice(&lc.guard_cf_function_table.to_le_bytes());
data.extend_from_slice(&lc.guard_cf_function_count.to_le_bytes());
} else {
data.extend_from_slice(&(lc.edit_list as u32).to_le_bytes());
data.extend_from_slice(&(lc.security_cookie as u32).to_le_bytes());
data.extend_from_slice(&(lc.se_handler_table as u32).to_le_bytes());
data.extend_from_slice(&(lc.se_handler_count as u32).to_le_bytes());
data.extend_from_slice(
&(lc.guard_cf_check_function_pointer as u32).to_le_bytes(),
);
data.extend_from_slice(
&(lc.guard_cf_dispatch_function_pointer as u32).to_le_bytes(),
);
data.extend_from_slice(&(lc.guard_cf_function_table as u32).to_le_bytes());
data.extend_from_slice(&(lc.guard_cf_function_count as u32).to_le_bytes());
}
data.extend_from_slice(&lc.guard_flags.to_le_bytes());
data
}
None => Vec::new(),
}
}
pub fn generate_bound_import_table(&self) -> Vec<u8> {
let mut data = Vec::new();
for entry in &self.bound_imports {
data.extend_from_slice(&entry.timestamp.to_le_bytes());
data.extend_from_slice(&entry.offset_module_name.to_le_bytes());
data.extend_from_slice(&entry.number_of_module_forwarder_refs.to_le_bytes());
data.extend_from_slice(entry.name.as_bytes());
data.push(0u8);
if data.len() % 2 != 0 {
data.push(0u8);
}
}
data.extend_from_slice(&[0u8; 6]);
data
}
pub fn generate_debug_directory(&self) -> Vec<u8> {
let mut data = Vec::new();
for entry in &self.debug_entries {
data.extend_from_slice(&entry.characteristics.to_le_bytes());
data.extend_from_slice(&entry.time_date_stamp.to_le_bytes());
data.extend_from_slice(&entry.major_version.to_le_bytes());
data.extend_from_slice(&entry.minor_version.to_le_bytes());
data.extend_from_slice(&entry.debug_type.to_le_bytes());
data.extend_from_slice(&entry.size_of_data.to_le_bytes());
data.extend_from_slice(&entry.address_of_raw_data.to_le_bytes());
data.extend_from_slice(&entry.pointer_to_raw_data.to_le_bytes());
}
data
}
}
impl DelayImportDescriptor {
pub fn attributes(&self) -> u32 {
1
}
}
pub const FAT_MAGIC: u32 = 0xcafebabe;
pub const FAT_MAGIC_64: u32 = 0xcafebabf;
pub const MH_MAGIC: u32 = 0xfeedface;
pub const MH_MAGIC_64: u32 = 0xfeedfacf;
pub const MH_CIGAM: u32 = 0xcefaedfe;
pub const MH_CIGAM_64: u32 = 0xcffaedfe;
pub const MH_OBJECT: u32 = 0x1;
pub const MH_EXECUTE: u32 = 0x2;
pub const MH_FVMLIB: u32 = 0x3;
pub const MH_CORE: u32 = 0x4;
pub const MH_PRELOAD: u32 = 0x5;
pub const MH_DYLIB: u32 = 0x6;
pub const MH_DYLINKER: u32 = 0x7;
pub const MH_BUNDLE: u32 = 0x8;
pub const MH_DYLIB_STUB: u32 = 0x9;
pub const MH_DSYM: u32 = 0xa;
pub const MH_KEXT_BUNDLE: u32 = 0xb;
pub const CPU_TYPE_X86: u32 = 7;
pub const CPU_TYPE_X86_64: u32 = 0x01000007;
pub const CPU_TYPE_ARM64: u32 = 0x0100000c;
pub const CPU_SUBTYPE_X86_ALL: u32 = 3;
pub const CPU_SUBTYPE_X86_64_ALL: u32 = 3;
pub const CPU_SUBTYPE_ARM64_ALL: u32 = 0;
pub const LC_SEGMENT: u32 = 0x1;
pub const LC_SYMTAB: u32 = 0x2;
pub const LC_SYMSEG: u32 = 0x3;
pub const LC_THREAD: u32 = 0x4;
pub const LC_UNIXTHREAD: u32 = 0x5;
pub const LC_LOADFVMLIB: u32 = 0x6;
pub const LC_IDFVMLIB: u32 = 0x7;
pub const LC_IDENT: u32 = 0x8;
pub const LC_FVMFILE: u32 = 0x9;
pub const LC_PREPAGE: u32 = 0xa;
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_ID_DYLINKER: u32 = 0xf;
pub const LC_PREBOUND_DYLIB: u32 = 0x10;
pub const LC_ROUTINES: u32 = 0x11;
pub const LC_SUB_FRAMEWORK: u32 = 0x12;
pub const LC_SUB_UMBRELLA: u32 = 0x13;
pub const LC_SUB_CLIENT: u32 = 0x14;
pub const LC_SUB_LIBRARY: u32 = 0x15;
pub const LC_TWOLEVEL_HINTS: u32 = 0x16;
pub const LC_PREBIND_CKSUM: u32 = 0x17;
pub const LC_LOAD_WEAK_DYLIB: u32 = 0x18;
pub const LC_SEGMENT_64: u32 = 0x19;
pub const LC_ROUTINES_64: u32 = 0x1a;
pub const LC_UUID: u32 = 0x1b;
pub const LC_RPATH: u32 = 0x8000001c;
pub const LC_CODE_SIGNATURE: u32 = 0x1d;
pub const LC_SEGMENT_SPLIT_INFO: u32 = 0x1e;
pub const LC_REEXPORT_DYLIB: u32 = 0x8000001f;
pub const LC_LAZY_LOAD_DYLIB: u32 = 0x20;
pub const LC_ENCRYPTION_INFO: u32 = 0x21;
pub const LC_DYLD_INFO: u32 = 0x22;
pub const LC_DYLD_INFO_ONLY: u32 = 0x80000022;
pub const LC_LOAD_UPWARD_DYLIB: u32 = 0x23;
pub const LC_VERSION_MIN_MACOSX: u32 = 0x24;
pub const LC_VERSION_MIN_IPHONEOS: u32 = 0x25;
pub const LC_FUNCTION_STARTS: u32 = 0x26;
pub const LC_DYLD_ENVIRONMENT: u32 = 0x27;
pub const LC_MAIN: u32 = 0x80000028;
pub const LC_DATA_IN_CODE: u32 = 0x29;
pub const LC_SOURCE_VERSION: u32 = 0x2a;
pub const LC_DYLIB_CODE_SIGN_DRS: u32 = 0x2b;
pub const LC_ENCRYPTION_INFO_64: u32 = 0x2c;
pub const LC_LINKER_OPTION: u32 = 0x2d;
pub const LC_LINKER_OPTIMIZATION_HINT: u32 = 0x2e;
pub const LC_VERSION_MIN_TVOS: u32 = 0x2f;
pub const LC_VERSION_MIN_WATCHOS: u32 = 0x30;
pub const LC_NOTE: u32 = 0x31;
pub const LC_BUILD_VERSION: u32 = 0x32;
pub const LC_DYLD_EXPORTS_TRIE: u32 = 0x80000033;
pub const LC_DYLD_CHAINED_FIXUPS: u32 = 0x80000034;
pub const PLATFORM_MACOS: u32 = 1;
pub const PLATFORM_IOS: u32 = 2;
pub const PLATFORM_TVOS: u32 = 3;
pub const PLATFORM_WATCHOS: u32 = 4;
pub const PLATFORM_BRIDGEOS: u32 = 5;
pub const PLATFORM_MACCATALYST: u32 = 6;
pub const PLATFORM_IOSSIMULATOR: u32 = 7;
pub const PLATFORM_TVOSSIMULATOR: u32 = 8;
pub const PLATFORM_WATCHOSSIMULATOR: u32 = 9;
pub const PLATFORM_DRIVERKIT: u32 = 10;
pub const MH_NOUNDEFS: u32 = 0x1;
pub const MH_INCRLINK: u32 = 0x2;
pub const MH_DYLDLINK: u32 = 0x4;
pub const MH_BINDATLOAD: u32 = 0x8;
pub const MH_PREBOUND: u32 = 0x10;
pub const MH_SPLIT_SEGS: u32 = 0x20;
pub const MH_LAZY_INIT: u32 = 0x40;
pub const MH_TWOLEVEL: u32 = 0x80;
pub const MH_FORCE_FLAT: u32 = 0x100;
pub const MH_NOMULTIDEFS: u32 = 0x200;
pub const MH_NOFIXPREBINDING: u32 = 0x400;
pub const MH_PREBINDABLE: u32 = 0x800;
pub const MH_ALLMODSBOUND: u32 = 0x1000;
pub const MH_SUBSECTIONS_VIA_SYMBOLS: u32 = 0x2000;
pub const MH_CANONICAL: u32 = 0x4000;
pub const MH_WEAK_DEFINES: u32 = 0x8000;
pub const MH_BINDS_TO_WEAK: u32 = 0x10000;
pub const MH_ALLOW_STACK_EXECUTION: u32 = 0x20000;
pub const MH_ROOT_SAFE: u32 = 0x40000;
pub const MH_SETUID_SAFE: u32 = 0x80000;
pub const MH_NO_REEXPORTED_DYLIBS: u32 = 0x100000;
pub const MH_PIE: u32 = 0x200000;
pub const MH_DEAD_STRIPPABLE_DYLIB: u32 = 0x400000;
pub const MH_HAS_TLV_DESCRIPTORS: u32 = 0x800000;
pub const MH_NO_HEAP_EXECUTION: u32 = 0x1000000;
pub const MH_APP_EXTENSION_SAFE: u32 = 0x02000000;
pub const MH_NLIST_OUTOFSYNC_WITH_DYLDINFO: u32 = 0x04000000;
pub const MH_SIM_SUPPORT: u32 = 0x08000000;
pub const BIND_TYPE_POINTER: u8 = 1;
pub const BIND_TYPE_TEXT_ABSOLUTE32: u8 = 2;
pub const BIND_TYPE_TEXT_PCREL32: u8 = 3;
pub const BIND_OPCODE_DONE: u8 = 0x00;
pub const BIND_OPCODE_SET_DYLIB_ORDINAL_IMM: u8 = 0x10;
pub const BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB: u8 = 0x20;
pub const BIND_OPCODE_SET_DYLIB_SPECIAL_IMM: u8 = 0x30;
pub const BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM: u8 = 0x40;
pub const BIND_OPCODE_SET_TYPE_IMM: u8 = 0x50;
pub const BIND_OPCODE_SET_ADDEND_SLEB: u8 = 0x60;
pub const BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: u8 = 0x70;
pub const BIND_OPCODE_ADD_ADDR_ULEB: u8 = 0x80;
pub const BIND_OPCODE_DO_BIND: u8 = 0x90;
pub const BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: u8 = 0xA0;
pub const BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: u8 = 0xB0;
pub const BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB: u8 = 0xC0;
pub const EXPORT_SYMBOL_FLAGS_KIND_MASK: u32 = 0x03;
pub const EXPORT_SYMBOL_FLAGS_KIND_REGULAR: u32 = 0x00;
pub const EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL: u32 = 0x01;
pub const EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE: u32 = 0x02;
pub const EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION: u32 = 0x04;
pub const EXPORT_SYMBOL_FLAGS_REEXPORT: u32 = 0x08;
pub const EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER: u32 = 0x10;
pub struct FatArch {
pub cputype: u32,
pub cpusubtype: u32,
pub offset: u32,
pub size: u32,
pub align: u32,
}
pub struct MachOSegment {
pub segname: [u8; 16],
pub vmaddr: u32,
pub vmsize: u32,
pub fileoff: u32,
pub filesize: u32,
pub maxprot: u32,
pub initprot: u32,
pub nsects: u32,
pub flags: u32,
}
pub struct MachOSegment64 {
pub segname: [u8; 16],
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 struct MachOSection64 {
pub sectname: [u8; 16],
pub segname: [u8; 16],
pub addr: u64,
pub size: u64,
pub offset: u32,
pub align: u32,
pub reloff: u32,
pub nreloc: u32,
pub flags: u32,
pub reserved1: u32,
pub reserved2: u32,
pub reserved3: u32,
}
pub struct MachOSection {
pub sectname: [u8; 16],
pub segname: [u8; 16],
pub addr: u32,
pub size: u32,
pub offset: u32,
pub align: u32,
pub reloff: u32,
pub nreloc: u32,
pub flags: u32,
pub reserved1: u32,
pub reserved2: u32,
}
pub struct MachOSymtabCommand {
pub symoff: u32,
pub nsyms: u32,
pub stroff: u32,
pub strsize: u32,
}
pub struct MachODysymtabCommand {
pub ilocalsym: u32,
pub nlocalsym: u32,
pub iextdefsym: u32,
pub nextdefsym: u32,
pub iundefsym: u32,
pub nundefsym: u32,
pub tocoff: u32,
pub ntoc: u32,
pub modtaboff: u32,
pub nmodtab: u32,
pub extrefsymoff: u32,
pub nextrefsyms: u32,
pub indirectsymoff: u32,
pub nindirectsyms: u32,
pub extreloff: u32,
pub nextrel: u32,
pub locreloff: u32,
pub nlocrel: u32,
}
pub struct MachODylibCommand {
pub name_offset: u32,
pub timestamp: u32,
pub current_version: u32,
pub compatibility_version: u32,
pub name: String,
}
pub struct MachOUuidCommand {
pub uuid: [u8; 16],
}
pub struct MachOBuildVersion {
pub platform: u32,
pub minos: u32,
pub sdk: u32,
pub ntools: u32,
}
pub struct MachOBuildToolVersion {
pub tool: u32,
pub version: u32,
}
pub struct MachOVersionMin {
pub version: u32,
pub sdk: u32,
}
pub struct MachOSourceVersion {
pub version: u64,
}
pub struct MachOMainCommand {
pub entryoff: u64,
pub stacksize: u64,
}
pub struct MachODyldInfo {
pub rebase_off: u32,
pub rebase_size: u32,
pub bind_off: u32,
pub bind_size: u32,
pub weak_bind_off: u32,
pub weak_bind_size: u32,
pub lazy_bind_off: u32,
pub lazy_bind_size: u32,
pub export_off: u32,
pub export_size: u32,
}
pub struct MachOLinkerOptHint {
pub dataoff: u32,
pub datasize: u32,
}
pub struct MachOCodeSignature {
pub dataoff: u32,
pub datasize: u32,
}
pub struct MachODylibCodeSignDrs {
pub dataoff: u32,
pub datasize: u32,
}
pub struct MachOInputSection {
pub name: String,
pub segment: String,
pub data: Vec<u8>,
pub align: u32,
pub addr: u64,
pub flags: u32,
}
pub struct MachOInputSymbol {
pub name: String,
pub value: u64,
pub is_external: bool,
pub is_private_extern: bool,
pub is_undefined: bool,
pub section_index: u8,
}
pub struct MachODylib {
pub name: String,
pub timestamp: u32,
pub current_version: u32,
pub compatibility_version: u32,
pub is_weak: bool,
pub is_reexport: bool,
}
pub struct MachOBinding {
pub segment: u8,
pub offset: u64,
pub symbol: String,
pub dylib_ordinal: i32,
pub addend: i64,
pub bind_type: u8,
}
pub struct MachOLazyBindingStub {
pub symbol: String,
pub dylib_ordinal: i32,
}
pub struct X86MachOLinker {
pub arch: X86LLDArch,
pub is_64bit: bool,
pub objects: Vec<MachOInputSection>,
pub symbols: Vec<MachOInputSymbol>,
pub dylibs: Vec<MachODylib>,
pub bindings: Vec<MachOBinding>,
pub weak_bindings: Vec<MachOBinding>,
pub lazy_bindings: Vec<MachOBinding>,
pub lazy_stubs: Vec<MachOLazyBindingStub>,
pub exports: Vec<String>,
pub entry_point: String,
pub output_type: u32,
pub install_name: String,
pub rpaths: Vec<String>,
pub uuid: Option<[u8; 16]>,
pub build_version: Option<MachOBuildVersion>,
pub version_min: Option<MachOVersionMin>,
pub source_version: Option<u64>,
pub has_code_signature: bool,
pub has_split_info: bool,
pub has_function_starts: bool,
pub has_data_in_code: bool,
pub platform: u32,
pub min_os_version: u32,
pub sdk_version: u32,
pub is_dylib: bool,
pub twolevel: bool,
pub allow_stack_execution: bool,
pub pie: bool,
pub has_tlv_descriptors: bool,
pub no_heap_execution: bool,
pub app_extension_safe: bool,
}
impl X86MachOLinker {
pub fn new(arch: X86LLDArch) -> Self {
X86MachOLinker {
arch: arch.clone(),
is_64bit: arch.is_64bit(),
objects: Vec::new(),
symbols: Vec::new(),
dylibs: Vec::new(),
bindings: Vec::new(),
weak_bindings: Vec::new(),
lazy_bindings: Vec::new(),
lazy_stubs: Vec::new(),
exports: Vec::new(),
entry_point: String::from("_main"),
output_type: MH_EXECUTE,
install_name: String::new(),
rpaths: Vec::new(),
uuid: None,
build_version: None,
version_min: None,
source_version: None,
has_code_signature: false,
has_split_info: false,
has_function_starts: false,
has_data_in_code: false,
platform: PLATFORM_MACOS,
min_os_version: 0x000A0E00, sdk_version: 0x000A0E00,
is_dylib: false,
twolevel: true,
allow_stack_execution: false,
pie: true,
has_tlv_descriptors: false,
no_heap_execution: true,
app_extension_safe: false,
}
}
pub fn set_dylib(&mut self, install_name: &str) {
self.is_dylib = true;
self.output_type = MH_DYLIB;
self.install_name = install_name.to_string();
}
pub fn set_bundle(&mut self) {
self.output_type = MH_BUNDLE;
}
pub fn add_dylib(&mut self, name: &str, weak: bool, reexport: bool) {
self.dylibs.push(MachODylib {
name: name.to_string(),
timestamp: 2, current_version: 0x00010000,
compatibility_version: 0x00010000,
is_weak: weak,
is_reexport: reexport,
});
}
pub fn add_rpath(&mut self, path: &str) {
self.rpaths.push(path.to_string());
}
pub fn set_uuid(&mut self, uuid: [u8; 16]) {
self.uuid = Some(uuid);
}
pub fn set_build_version(&mut self, platform: u32, minos: u32, sdk: u32) {
self.build_version = Some(MachOBuildVersion {
platform,
minos,
sdk,
ntools: 0,
});
}
pub fn set_version_min(&mut self, version: u32, sdk: u32) {
self.version_min = Some(MachOVersionMin { version, sdk });
}
pub fn set_source_version(&mut self, version: u64) {
self.source_version = Some(version);
}
pub fn add_export(&mut self, name: &str) {
self.exports.push(name.to_string());
}
pub fn add_binding(
&mut self,
segment: u8,
offset: u64,
symbol: &str,
dylib_ordinal: i32,
addend: i64,
) {
self.bindings.push(MachOBinding {
segment,
offset,
symbol: symbol.to_string(),
dylib_ordinal,
addend,
bind_type: BIND_TYPE_POINTER,
});
}
pub fn add_weak_binding(&mut self, segment: u8, offset: u64, symbol: &str) {
self.weak_bindings.push(MachOBinding {
segment,
offset,
symbol: symbol.to_string(),
dylib_ordinal: 0,
addend: 0,
bind_type: BIND_TYPE_POINTER,
});
}
pub fn add_lazy_binding(&mut self, symbol: &str, dylib_ordinal: i32) {
self.lazy_stubs.push(MachOLazyBindingStub {
symbol: symbol.to_string(),
dylib_ordinal,
});
}
pub fn generate_bind_opcodes(&self) -> Vec<u8> {
let mut data = Vec::new();
for b in &self.bindings {
if b.dylib_ordinal <= 0 {
data.push(BIND_OPCODE_SET_DYLIB_SPECIAL_IMM | ((b.dylib_ordinal as u8) & 0x0F));
} else if b.dylib_ordinal < 16 {
data.push(BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | ((b.dylib_ordinal as u8) & 0x0F));
} else {
data.push(BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB);
Self::write_uleb128(&mut data, b.dylib_ordinal as u64);
}
data.push(BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM);
data.extend_from_slice(b.symbol.as_bytes());
data.push(0u8);
data.push(BIND_OPCODE_SET_TYPE_IMM | (b.bind_type & 0x0F));
if b.addend != 0 {
data.push(BIND_OPCODE_SET_ADDEND_SLEB);
Self::write_sleb128(&mut data, b.addend);
}
data.push(BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | (b.segment & 0x0F));
Self::write_uleb128(&mut data, b.offset);
data.push(BIND_OPCODE_DO_BIND);
}
data.push(BIND_OPCODE_DONE);
data
}
pub fn generate_weak_bind_opcodes(&self) -> Vec<u8> {
let mut data = Vec::new();
for b in &self.weak_bindings {
data.push(BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM);
data.extend_from_slice(b.symbol.as_bytes());
data.push(0u8);
data.push(BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | (b.segment & 0x0F));
Self::write_uleb128(&mut data, b.offset);
data.push(BIND_OPCODE_DO_BIND);
}
data.push(BIND_OPCODE_DONE);
data
}
pub fn generate_lazy_bind_opcodes(&self) -> Vec<u8> {
let mut data = Vec::new();
for b in &self.lazy_bindings {
if b.dylib_ordinal <= 0 {
data.push(BIND_OPCODE_SET_DYLIB_SPECIAL_IMM | ((b.dylib_ordinal as u8) & 0x0F));
} else {
data.push(BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | ((b.dylib_ordinal as u8) & 0x0F));
}
data.push(BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM);
data.extend_from_slice(b.symbol.as_bytes());
data.push(0u8);
data.push(BIND_OPCODE_DO_BIND);
}
data.push(BIND_OPCODE_DONE);
data
}
pub fn generate_export_trie(&self) -> Vec<u8> {
if self.exports.is_empty() {
return vec![0u8]; }
let mut data = Vec::new();
for (idx, name) in self.exports.iter().enumerate() {
let terminal_size = name.len() + 1; data.push(0u8); let flags = EXPORT_SYMBOL_FLAGS_KIND_REGULAR;
Self::write_uleb128(&mut data, flags as u64);
Self::write_uleb128(&mut data, (idx * 8) as u64); data.extend_from_slice(name.as_bytes());
data.push(0u8);
}
data
}
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 as u8) & 0x7F;
value >>= 7;
if (value == 0 && (byte & 0x40) == 0) || (value == -1 && (byte & 0x40) != 0) {
buf.push(byte);
break;
}
byte |= 0x80;
buf.push(byte);
}
}
pub fn generate_fat_binary(x86_64_data: &[u8], arm64_data: &[u8]) -> Vec<u8> {
let num_archs = 2u32;
let header_size = 8 + num_archs as usize * 20;
let mut buf = Vec::with_capacity(header_size + x86_64_data.len() + arm64_data.len());
buf.extend_from_slice(&FAT_MAGIC.to_be_bytes());
buf.extend_from_slice(&num_archs.to_be_bytes());
let x86_offset = header_size as u32;
let x86_size = x86_64_data.len() as u32;
buf.extend_from_slice(&(CPU_TYPE_X86_64 as u32).to_be_bytes());
buf.extend_from_slice(&CPU_SUBTYPE_X86_64_ALL.to_be_bytes());
buf.extend_from_slice(&x86_offset.to_be_bytes());
buf.extend_from_slice(&x86_size.to_be_bytes());
buf.extend_from_slice(&14u32.to_be_bytes());
let arm64_offset = (header_size + x86_64_data.len()) as u32;
let arm64_size = arm64_data.len() as u32;
buf.extend_from_slice(&CPU_TYPE_ARM64.to_be_bytes());
buf.extend_from_slice(&CPU_SUBTYPE_ARM64_ALL.to_be_bytes());
buf.extend_from_slice(&arm64_offset.to_be_bytes());
buf.extend_from_slice(&arm64_size.to_be_bytes());
buf.extend_from_slice(&14u32.to_be_bytes());
buf.extend_from_slice(x86_64_data);
buf.extend_from_slice(arm64_data);
buf
}
pub fn emit(&self) -> Result<Vec<u8>, String> {
let is_64 = self.is_64bit;
let mut buf = Vec::new();
let mut commands: Vec<Vec<u8>> = Vec::new();
let magic = if is_64 { MH_MAGIC_64 } else { MH_MAGIC };
let cputype = if self.arch == X86LLDArch::X86_64 {
CPU_TYPE_X86_64
} else {
CPU_TYPE_X86
};
let cpusubtype = if self.arch == X86LLDArch::X86_64 {
CPU_SUBTYPE_X86_64_ALL
} else {
CPU_SUBTYPE_X86_ALL
};
let filetype = self.output_type;
buf.extend_from_slice(&magic.to_le_bytes());
buf.extend_from_slice(&cputype.to_le_bytes());
buf.extend_from_slice(&cpusubtype.to_le_bytes());
buf.extend_from_slice(&filetype.to_le_bytes());
let ncmds_placeholder = buf.len(); buf.extend_from_slice(&0u32.to_le_bytes()); let sizeofcmds_placeholder = buf.len();
buf.extend_from_slice(&0u32.to_le_bytes());
let mut flags: u32 = MH_NOUNDEFS | MH_DYLDLINK | MH_TWOLEVEL;
if self.pie {
flags |= MH_PIE;
}
if self.app_extension_safe {
flags |= MH_APP_EXTENSION_SAFE;
}
if self.no_heap_execution {
flags |= MH_NO_HEAP_EXECUTION;
}
if self.allow_stack_execution {
flags |= MH_ALLOW_STACK_EXECUTION;
}
if self.has_tlv_descriptors {
flags |= MH_HAS_TLV_DESCRIPTORS;
}
buf.extend_from_slice(&flags.to_le_bytes());
if is_64 {
buf.extend_from_slice(&0u32.to_le_bytes());
}
if is_64 && self.output_type == MH_EXECUTE {
let mut cmd = Vec::new();
cmd.extend_from_slice(&LC_SEGMENT_64.to_le_bytes());
let cmd_size = 72u32; cmd.extend_from_slice(&cmd_size.to_le_bytes());
let mut segname = [0u8; 16];
segname[..11].copy_from_slice(b"__PAGEZERO\0");
cmd.extend_from_slice(&segname);
cmd.extend_from_slice(&0u64.to_le_bytes()); cmd.extend_from_slice(&0x100000000u64.to_le_bytes()); cmd.extend_from_slice(&0u64.to_le_bytes()); cmd.extend_from_slice(&0u64.to_le_bytes()); cmd.extend_from_slice(&0u32.to_le_bytes()); cmd.extend_from_slice(&0u32.to_le_bytes()); cmd.extend_from_slice(&0u32.to_le_bytes()); cmd.extend_from_slice(&0u32.to_le_bytes()); commands.push(cmd);
}
let text_cmd = self.build_segment_command(
if is_64 && self.output_type == MH_EXECUTE {
"__TEXT\0\0\0\0\0\0\0\0\0\0"
} else {
"__TEXT\0\0\0\0\0\0\0\0\0\0"
},
0x100000000,
0x4000,
0,
0x4000,
7,
5,
&[],
is_64,
);
commands.push(text_cmd);
let data_cmd = self.build_segment_command(
"__DATA\0\0\0\0\0\0\0\0\0\0",
0x100004000,
0x4000,
0x4000,
0x4000,
7,
3,
&[],
is_64,
);
commands.push(data_cmd);
let linkedit_cmd = self.build_segment_command(
"__LINKEDIT\0\0\0\0\0\0",
0x100008000,
0x4000,
0x8000,
0x4000,
7,
1,
&[],
is_64,
);
commands.push(linkedit_cmd);
let bind_data = self.generate_bind_opcodes();
let weak_bind_data = self.generate_weak_bind_opcodes();
let lazy_bind_data = self.generate_lazy_bind_opcodes();
let export_data = self.generate_export_trie();
let mut dyld_cmd = Vec::new();
dyld_cmd.extend_from_slice(&LC_DYLD_INFO_ONLY.to_le_bytes());
dyld_cmd.extend_from_slice(&48u32.to_le_bytes()); dyld_cmd.extend_from_slice(&0u32.to_le_bytes()); dyld_cmd.extend_from_slice(&0u32.to_le_bytes()); dyld_cmd.extend_from_slice(&0u32.to_le_bytes()); dyld_cmd.extend_from_slice(&(bind_data.len() as u32).to_le_bytes());
dyld_cmd.extend_from_slice(&0u32.to_le_bytes()); dyld_cmd.extend_from_slice(&(weak_bind_data.len() as u32).to_le_bytes());
dyld_cmd.extend_from_slice(&0u32.to_le_bytes()); dyld_cmd.extend_from_slice(&(lazy_bind_data.len() as u32).to_le_bytes());
dyld_cmd.extend_from_slice(&0u32.to_le_bytes()); dyld_cmd.extend_from_slice(&(export_data.len() as u32).to_le_bytes());
commands.push(dyld_cmd);
let mut symtab_cmd = Vec::new();
symtab_cmd.extend_from_slice(&LC_SYMTAB.to_le_bytes());
symtab_cmd.extend_from_slice(&24u32.to_le_bytes());
symtab_cmd.extend_from_slice(&0u32.to_le_bytes()); symtab_cmd.extend_from_slice(&0u32.to_le_bytes()); symtab_cmd.extend_from_slice(&0u32.to_le_bytes()); symtab_cmd.extend_from_slice(&0u32.to_le_bytes()); commands.push(symtab_cmd);
let mut dysym_cmd = Vec::new();
dysym_cmd.extend_from_slice(&LC_DYSYMTAB.to_le_bytes());
dysym_cmd.extend_from_slice(&80u32.to_le_bytes());
for _ in 0..18 {
dysym_cmd.extend_from_slice(&0u32.to_le_bytes());
}
commands.push(dysym_cmd);
for dylib in &self.dylibs {
let lc = if dylib.is_weak {
LC_LOAD_WEAK_DYLIB
} else if dylib.is_reexport {
LC_REEXPORT_DYLIB
} else {
LC_LOAD_DYLIB
};
let mut cmd = Vec::new();
cmd.extend_from_slice(&lc.to_le_bytes());
let name_bytes = dylib.name.as_bytes();
let name_len = name_bytes.len() + 1;
let cmd_size = align_up(24 + name_len as u64, 4) as u32; cmd.extend_from_slice(&cmd_size.to_le_bytes());
cmd.extend_from_slice(&24u32.to_le_bytes()); cmd.extend_from_slice(&dylib.timestamp.to_le_bytes());
cmd.extend_from_slice(&dylib.current_version.to_le_bytes());
cmd.extend_from_slice(&dylib.compatibility_version.to_le_bytes());
cmd.extend_from_slice(name_bytes);
cmd.push(0u8);
while cmd.len() % 4 != 0 {
cmd.push(0u8);
}
commands.push(cmd);
}
if self.is_dylib && !self.install_name.is_empty() {
let mut cmd = Vec::new();
cmd.extend_from_slice(&LC_ID_DYLIB.to_le_bytes());
let name_bytes = self.install_name.as_bytes();
let name_len = name_bytes.len() + 1;
let cmd_size = align_up(24 + name_len as u64, 4) as u32;
cmd.extend_from_slice(&cmd_size.to_le_bytes());
cmd.extend_from_slice(&24u32.to_le_bytes()); cmd.extend_from_slice(&0u32.to_le_bytes()); cmd.extend_from_slice(&0x00010000u32.to_le_bytes()); cmd.extend_from_slice(&0x00010000u32.to_le_bytes()); cmd.extend_from_slice(name_bytes);
cmd.push(0u8);
while cmd.len() % 4 != 0 {
cmd.push(0u8);
}
commands.push(cmd);
}
if self.output_type == MH_EXECUTE {
let mut cmd = Vec::new();
cmd.extend_from_slice(&LC_MAIN.to_le_bytes());
cmd.extend_from_slice(&24u32.to_le_bytes());
cmd.extend_from_slice(&0u64.to_le_bytes()); cmd.extend_from_slice(&0u64.to_le_bytes()); commands.push(cmd);
}
for rpath in &self.rpaths {
let mut cmd = Vec::new();
cmd.extend_from_slice(&LC_RPATH.to_le_bytes());
let path_bytes = rpath.as_bytes();
let path_len = path_bytes.len() + 1;
let cmd_size = align_up(12 + path_len as u64, 4) as u32;
cmd.extend_from_slice(&cmd_size.to_le_bytes());
cmd.extend_from_slice(&12u32.to_le_bytes()); cmd.extend_from_slice(path_bytes);
cmd.push(0u8);
while cmd.len() % 4 != 0 {
cmd.push(0u8);
}
commands.push(cmd);
}
if let Some(uuid) = self.uuid {
let mut cmd = Vec::new();
cmd.extend_from_slice(&LC_UUID.to_le_bytes());
cmd.extend_from_slice(&24u32.to_le_bytes());
cmd.extend_from_slice(&uuid);
commands.push(cmd);
}
if let Some(ref bv) = self.build_version {
let mut cmd = Vec::new();
cmd.extend_from_slice(&LC_BUILD_VERSION.to_le_bytes());
cmd.extend_from_slice(&24u32.to_le_bytes()); cmd.extend_from_slice(&bv.platform.to_le_bytes());
cmd.extend_from_slice(&bv.minos.to_le_bytes());
cmd.extend_from_slice(&bv.sdk.to_le_bytes());
cmd.extend_from_slice(&0u32.to_le_bytes()); commands.push(cmd);
} else if let Some(ref vm) = self.version_min {
let lc = if self.platform == PLATFORM_IOS || self.platform == PLATFORM_IOSSIMULATOR {
LC_VERSION_MIN_IPHONEOS
} else {
LC_VERSION_MIN_MACOSX
};
let mut cmd = Vec::new();
cmd.extend_from_slice(&lc.to_le_bytes());
cmd.extend_from_slice(&16u32.to_le_bytes());
cmd.extend_from_slice(&vm.version.to_le_bytes());
cmd.extend_from_slice(&vm.sdk.to_le_bytes());
commands.push(cmd);
}
if let Some(sv) = self.source_version {
let mut cmd = Vec::new();
cmd.extend_from_slice(&LC_SOURCE_VERSION.to_le_bytes());
cmd.extend_from_slice(&16u32.to_le_bytes());
cmd.extend_from_slice(&sv.to_le_bytes());
commands.push(cmd);
}
if self.has_function_starts {
let mut cmd = Vec::new();
cmd.extend_from_slice(&LC_FUNCTION_STARTS.to_le_bytes());
cmd.extend_from_slice(&16u32.to_le_bytes());
cmd.extend_from_slice(&0u32.to_le_bytes());
cmd.extend_from_slice(&0u32.to_le_bytes());
commands.push(cmd);
}
if self.has_data_in_code {
let mut cmd = Vec::new();
cmd.extend_from_slice(&LC_DATA_IN_CODE.to_le_bytes());
cmd.extend_from_slice(&16u32.to_le_bytes());
cmd.extend_from_slice(&0u32.to_le_bytes());
cmd.extend_from_slice(&0u32.to_le_bytes());
commands.push(cmd);
}
if self.has_code_signature {
let mut cmd = Vec::new();
cmd.extend_from_slice(&LC_CODE_SIGNATURE.to_le_bytes());
cmd.extend_from_slice(&16u32.to_le_bytes());
cmd.extend_from_slice(&0u32.to_le_bytes());
cmd.extend_from_slice(&0u32.to_le_bytes());
commands.push(cmd);
}
let ncmds = commands.len() as u32;
let sizeofcmds: u32 = commands.iter().map(|c| c.len() as u32).sum();
buf[ncmds_placeholder..ncmds_placeholder + 4].copy_from_slice(&ncmds.to_le_bytes());
buf[sizeofcmds_placeholder..sizeofcmds_placeholder + 4]
.copy_from_slice(&sizeofcmds.to_le_bytes());
for cmd in &commands {
buf.extend_from_slice(cmd);
}
Ok(buf)
}
fn build_segment_command(
&self,
name: &str,
vmaddr: u64,
vmsize: u64,
fileoff: u64,
filesize: u64,
maxprot: u32,
initprot: u32,
sections: &[MachOSection64],
is_64: bool,
) -> Vec<u8> {
let mut cmd = Vec::new();
if is_64 {
cmd.extend_from_slice(&LC_SEGMENT_64.to_le_bytes());
let nsects = sections.len() as u32;
let cmd_size = 72u32 + nsects * 80; cmd.extend_from_slice(&cmd_size.to_le_bytes());
let mut sname = [0u8; 16];
let name_bytes = name.as_bytes();
let n = std::cmp::min(name_bytes.len(), 16);
sname[..n].copy_from_slice(&name_bytes[..n]);
cmd.extend_from_slice(&sname);
cmd.extend_from_slice(&vmaddr.to_le_bytes());
cmd.extend_from_slice(&vmsize.to_le_bytes());
cmd.extend_from_slice(&fileoff.to_le_bytes());
cmd.extend_from_slice(&filesize.to_le_bytes());
cmd.extend_from_slice(&maxprot.to_le_bytes());
cmd.extend_from_slice(&initprot.to_le_bytes());
cmd.extend_from_slice(&nsects.to_le_bytes());
cmd.extend_from_slice(&0u32.to_le_bytes()); for sec in sections {
cmd.extend_from_slice(&sec.sectname);
cmd.extend_from_slice(&sec.segname);
cmd.extend_from_slice(&sec.addr.to_le_bytes());
cmd.extend_from_slice(&sec.size.to_le_bytes());
cmd.extend_from_slice(&sec.offset.to_le_bytes());
cmd.extend_from_slice(&sec.align.to_le_bytes());
cmd.extend_from_slice(&sec.reloff.to_le_bytes());
cmd.extend_from_slice(&sec.nreloc.to_le_bytes());
cmd.extend_from_slice(&sec.flags.to_le_bytes());
cmd.extend_from_slice(&sec.reserved1.to_le_bytes());
cmd.extend_from_slice(&sec.reserved2.to_le_bytes());
cmd.extend_from_slice(&sec.reserved3.to_le_bytes());
}
} else {
cmd.extend_from_slice(&LC_SEGMENT.to_le_bytes());
let nsects = sections.len() as u32;
let cmd_size = 56u32 + nsects * 68; cmd.extend_from_slice(&cmd_size.to_le_bytes());
let mut sname = [0u8; 16];
let name_bytes = name.as_bytes();
let n = std::cmp::min(name_bytes.len(), 16);
sname[..n].copy_from_slice(&name_bytes[..n]);
cmd.extend_from_slice(&sname);
cmd.extend_from_slice(&(vmaddr as u32).to_le_bytes());
cmd.extend_from_slice(&(vmsize as u32).to_le_bytes());
cmd.extend_from_slice(&(fileoff as u32).to_le_bytes());
cmd.extend_from_slice(&(filesize as u32).to_le_bytes());
cmd.extend_from_slice(&maxprot.to_le_bytes());
cmd.extend_from_slice(&initprot.to_le_bytes());
cmd.extend_from_slice(&nsects.to_le_bytes());
cmd.extend_from_slice(&0u32.to_le_bytes()); }
cmd
}
}
pub const PT_NULL: u32 = 0;
pub const PT_LOAD: u32 = 1;
pub const PT_DYNAMIC: u32 = 2;
pub const PT_INTERP: u32 = 3;
pub const PT_NOTE: u32 = 4;
pub const PT_SHLIB: u32 = 5;
pub const PT_PHDR: u32 = 6;
pub const PT_TLS: u32 = 7;
pub const PT_GNU_EH_FRAME: u32 = 0x6474e550;
pub const PT_GNU_STACK: u32 = 0x6474e551;
pub const PT_GNU_RELRO: u32 = 0x6474e552;
pub const PT_GNU_PROPERTY: u32 = 0x6474e553;
pub const PT_OPENBSD_RANDOMIZE: u32 = 0x65a3dbe6;
pub const PT_OPENBSD_WXNEEDED: u32 = 0x65a3dbe7;
pub const PT_OPENBSD_BOOTDATA: u32 = 0x65a41be6;
pub const PF_X: u32 = 1;
pub const PF_W: u32 = 2;
pub const PF_R: u32 = 4;
pub const PF_RW: u32 = PF_R | PF_W;
pub const PF_RX: u32 = PF_R | PF_X;
pub const PF_RWX: u32 = PF_R | PF_W | PF_X;
pub type X86Elf64Phdr = Elf64Phdr;
pub type X86Elf32Phdr = Elf32Phdr;
pub struct X86ELFPhdrFull {
pub p_type: u32,
pub p_flags: u32,
pub p_offset: u64,
pub p_vaddr: u64,
pub p_paddr: u64,
pub p_filesz: u64,
pub p_memsz: u64,
pub p_align: u64,
}
pub struct X86ELFPhdrs {
pub entries: Vec<X86ELFPhdrFull>,
pub is_shared: bool,
pub has_tls: bool,
pub has_eh_frame: bool,
pub has_relro: bool,
pub has_gnu_property: bool,
pub exec_stack: bool,
}
impl X86ELFPhdrs {
pub fn new() -> Self {
X86ELFPhdrs {
entries: Vec::new(),
is_shared: false,
has_tls: false,
has_eh_frame: false,
has_relro: false,
has_gnu_property: false,
exec_stack: false,
}
}
pub fn generate(&mut self, segments: &[X86OutputSegment], image_base: u64, page_size: u64) {
self.entries.clear();
let phdr_size = if self.is_shared {
56 } else {
56
};
self.entries.push(X86ELFPhdrFull {
p_type: PT_PHDR,
p_flags: PF_R,
p_offset: 64, p_vaddr: image_base + 64,
p_paddr: image_base + 64,
p_filesz: phdr_size * 6, p_memsz: phdr_size * 6,
p_align: 8,
});
if !self.is_shared {
self.entries.push(X86ELFPhdrFull {
p_type: PT_INTERP,
p_flags: PF_R,
p_offset: 0, p_vaddr: 0,
p_paddr: 0,
p_filesz: 0,
p_memsz: 0,
p_align: 1,
});
}
for seg in segments {
self.entries.push(X86ELFPhdrFull {
p_type: PT_LOAD,
p_flags: seg.p_flags,
p_offset: seg.p_offset as u64,
p_vaddr: seg.p_vaddr as u64,
p_paddr: seg.p_paddr as u64,
p_filesz: seg.p_filesz as u64,
p_memsz: seg.p_memsz as u64,
p_align: seg.p_align as u64,
});
}
self.entries.push(X86ELFPhdrFull {
p_type: PT_DYNAMIC,
p_flags: PF_R | PF_W,
p_offset: 0, p_vaddr: 0,
p_paddr: 0,
p_filesz: 0,
p_memsz: 0,
p_align: 8,
});
self.entries.push(X86ELFPhdrFull {
p_type: PT_NOTE,
p_flags: PF_R,
p_offset: 0,
p_vaddr: 0,
p_paddr: 0,
p_filesz: 0,
p_memsz: 0,
p_align: 4,
});
if self.has_tls {
self.entries.push(X86ELFPhdrFull {
p_type: PT_TLS,
p_flags: PF_R,
p_offset: 0,
p_vaddr: 0,
p_paddr: 0,
p_filesz: 0,
p_memsz: 0,
p_align: 1,
});
}
if self.has_eh_frame {
self.entries.push(X86ELFPhdrFull {
p_type: PT_GNU_EH_FRAME,
p_flags: PF_R,
p_offset: 0,
p_vaddr: 0,
p_paddr: 0,
p_filesz: 0,
p_memsz: 0,
p_align: 4,
});
}
let stack_flags = if self.exec_stack {
PF_R | PF_W | PF_X
} else {
PF_R | PF_W
};
self.entries.push(X86ELFPhdrFull {
p_type: PT_GNU_STACK,
p_flags: stack_flags,
p_offset: 0,
p_vaddr: 0,
p_paddr: 0,
p_filesz: 0,
p_memsz: 0,
p_align: 16,
});
if self.has_relro {
self.entries.push(X86ELFPhdrFull {
p_type: PT_GNU_RELRO,
p_flags: PF_R,
p_offset: 0,
p_vaddr: 0,
p_paddr: 0,
p_filesz: 0,
p_memsz: 0,
p_align: 1,
});
}
if self.has_gnu_property {
self.entries.push(X86ELFPhdrFull {
p_type: PT_GNU_PROPERTY,
p_flags: PF_R,
p_offset: 0,
p_vaddr: 0,
p_paddr: 0,
p_filesz: 0,
p_memsz: 0,
p_align: 4,
});
}
self.entries.push(X86ELFPhdrFull {
p_type: PT_OPENBSD_RANDOMIZE,
p_flags: 0,
p_offset: 0,
p_vaddr: 0,
p_paddr: 0,
p_filesz: 0,
p_memsz: 0,
p_align: 0,
});
self.entries.push(X86ELFPhdrFull {
p_type: PT_OPENBSD_WXNEEDED,
p_flags: 0,
p_offset: 0,
p_vaddr: 0,
p_paddr: 0,
p_filesz: 0,
p_memsz: 0,
p_align: 0,
});
self.entries.push(X86ELFPhdrFull {
p_type: PT_SHLIB,
p_flags: 0,
p_offset: 0,
p_vaddr: 0,
p_paddr: 0,
p_filesz: 0,
p_memsz: 0,
p_align: 0,
});
}
pub fn emit_64(&self) -> Vec<u8> {
let mut buf = Vec::new();
for e in &self.entries {
buf.extend_from_slice(&e.p_type.to_le_bytes());
buf.extend_from_slice(&e.p_flags.to_le_bytes());
buf.extend_from_slice(&e.p_offset.to_le_bytes());
buf.extend_from_slice(&e.p_vaddr.to_le_bytes());
buf.extend_from_slice(&e.p_paddr.to_le_bytes());
buf.extend_from_slice(&e.p_filesz.to_le_bytes());
buf.extend_from_slice(&e.p_memsz.to_le_bytes());
buf.extend_from_slice(&e.p_align.to_le_bytes());
}
buf
}
pub fn emit_32(&self) -> Vec<u8> {
let mut buf = Vec::new();
for e in &self.entries {
buf.extend_from_slice(&e.p_type.to_le_bytes());
buf.extend_from_slice(&(e.p_offset as u32).to_le_bytes());
buf.extend_from_slice(&(e.p_vaddr as u32).to_le_bytes());
buf.extend_from_slice(&(e.p_paddr as u32).to_le_bytes());
buf.extend_from_slice(&(e.p_filesz as u32).to_le_bytes());
buf.extend_from_slice(&(e.p_memsz as u32).to_le_bytes());
buf.extend_from_slice(&e.p_flags.to_le_bytes());
buf.extend_from_slice(&(e.p_align as u32).to_le_bytes());
}
buf
}
}
impl Default for X86ELFPhdrs {
fn default() -> Self {
Self::new()
}
}
pub const DT_NULL: u64 = 0;
pub const DT_NEEDED: u64 = 1;
pub const DT_PLTRELSZ: u64 = 2;
pub const DT_PLTGOT: u64 = 3;
pub const DT_HASH: u64 = 4;
pub const DT_STRTAB: u64 = 5;
pub const DT_SYMTAB: u64 = 6;
pub const DT_RELA: u64 = 7;
pub const DT_RELASZ: u64 = 8;
pub const DT_RELAENT: u64 = 9;
pub const DT_STRSZ: u64 = 10;
pub const DT_SYMENT: u64 = 11;
pub const DT_INIT: u64 = 12;
pub const DT_FINI: u64 = 13;
pub const DT_SONAME: u64 = 14;
pub const DT_RPATH: u64 = 15;
pub const DT_SYMBOLIC: u64 = 16;
pub const DT_REL: u64 = 17;
pub const DT_RELSZ: u64 = 18;
pub const DT_RELENT: u64 = 19;
pub const DT_PLTREL: u64 = 20;
pub const DT_DEBUG: u64 = 21;
pub const DT_TEXTREL: u64 = 22;
pub const DT_JMPREL: u64 = 23;
pub const DT_BIND_NOW: u64 = 24;
pub const DT_INIT_ARRAY: u64 = 25;
pub const DT_FINI_ARRAY: u64 = 26;
pub const DT_INIT_ARRAYSZ: u64 = 27;
pub const DT_FINI_ARRAYSZ: u64 = 28;
pub const DT_RUNPATH: u64 = 29;
pub const DT_FLAGS: u64 = 30;
pub const DT_ENCODING: u64 = 32;
pub const DT_PREINIT_ARRAY: u64 = 32;
pub const DT_PREINIT_ARRAYSZ: u64 = 33;
pub const DT_SYMTAB_SHNDX: u64 = 34;
pub const DT_RELRSZ: u64 = 35;
pub const DT_RELR: u64 = 36;
pub const DT_RELRENT: u64 = 37;
pub const DT_NUM: u64 = 38;
pub const DT_LOOS: u64 = 0x60000000;
pub const DT_HIOS: u64 = 0x6ffff000;
pub const DT_LOPROC: u64 = 0x70000000;
pub const DT_HIPROC: u64 = 0x7fffffff;
pub const DT_GNU_HASH: u64 = 0x6ffffef5;
pub const DT_TLSDESC_PLT: u64 = 0x6ffffef6;
pub const DT_TLSDESC_GOT: u64 = 0x6ffffef7;
pub const DT_GNU_CONFLICT: u64 = 0x6ffffef8;
pub const DT_GNU_LIBLIST: u64 = 0x6ffffef9;
pub const DT_CONFIG: u64 = 0x6ffffefa;
pub const DT_DEPAUDIT: u64 = 0x6ffffefb;
pub const DT_AUDIT: u64 = 0x6ffffefc;
pub const DT_PLTPAD: u64 = 0x6ffffefd;
pub const DT_MOVETAB: u64 = 0x6ffffefe;
pub const DT_SYMINFO: u64 = 0x6ffffeff;
pub const DT_VERSYM: u64 = 0x6ffffff0;
pub const DT_RELACOUNT: u64 = 0x6ffffff9;
pub const DT_RELCOUNT: u64 = 0x6ffffffa;
pub const DT_FLAGS_1: u64 = 0x6ffffffb;
pub const DT_VERDEF: u64 = 0x6ffffffc;
pub const DT_VERDEFNUM: u64 = 0x6ffffffd;
pub const DT_VERNEED: u64 = 0x6ffffffe;
pub const DT_VERNEEDNUM: u64 = 0x6fffffff;
pub const DF_ORIGIN: u64 = 0x00000001;
pub const DF_SYMBOLIC: u64 = 0x00000002;
pub const DF_TEXTREL: u64 = 0x00000004;
pub const DF_BIND_NOW: u64 = 0x00000008;
pub const DF_STATIC_TLS: u64 = 0x00000010;
pub const DF_1_NOW: u64 = 0x00000001;
pub const DF_1_GLOBAL: u64 = 0x00000002;
pub const DF_1_GROUP: u64 = 0x00000004;
pub const DF_1_NODELETE: u64 = 0x00000008;
pub const DF_1_LOADFLTR: u64 = 0x00000010;
pub const DF_1_INITFIRST: u64 = 0x00000020;
pub const DF_1_NOOPEN: u64 = 0x00000040;
pub const DF_1_ORIGIN: u64 = 0x00000080;
pub const DF_1_DIRECT: u64 = 0x00000100;
pub const DF_1_TRANS: u64 = 0x00000200;
pub const DF_1_INTERPOSE: u64 = 0x00000400;
pub const DF_1_NODEFLIB: u64 = 0x00000800;
pub const DF_1_NODUMP: u64 = 0x00001000;
pub const DF_1_CONFALT: u64 = 0x00002000;
pub const DF_1_ENDFILTEE: u64 = 0x00004000;
pub const DF_1_DISPRELDNE: u64 = 0x00008000;
pub const DF_1_DISPRELPND: u64 = 0x00010000;
pub const DF_1_NODIRECT: u64 = 0x00020000;
pub const DF_1_IGNMULDEF: u64 = 0x00040000;
pub const DF_1_NOKSYMS: u64 = 0x00080000;
pub const DF_1_NOHDR: u64 = 0x00100000;
pub const DF_1_EDITED: u64 = 0x00200000;
pub const DF_1_NORELOC: u64 = 0x00400000;
pub const DF_1_SYMINTPOSE: u64 = 0x00800000;
pub const DF_1_GLOBAUDIT: u64 = 0x01000000;
pub const DF_1_SINGLETON: u64 = 0x02000000;
pub const DF_1_PIE: u64 = 0x08000000;
pub struct X86DynamicEntry {
pub d_tag: u64,
pub d_val: u64,
pub d_ptr: u64,
}
pub struct X86DynamicEntries {
pub entries: Vec<X86DynamicEntry>,
pub is_64bit: bool,
}
impl X86DynamicEntries {
pub fn new(is_64bit: bool) -> Self {
X86DynamicEntries {
entries: Vec::new(),
is_64bit,
}
}
pub fn add(&mut self, tag: u64, val: u64) {
self.entries.push(X86DynamicEntry {
d_tag: tag,
d_val: val,
d_ptr: val,
});
}
pub fn add_needed(&mut self, soname_offset: u64) {
self.add(DT_NEEDED, soname_offset);
}
pub fn add_soname(&mut self, soname_offset: u64) {
self.add(DT_SONAME, soname_offset);
}
pub fn add_rpath(&mut self, rpath_offset: u64) {
self.add(DT_RPATH, rpath_offset);
}
pub fn add_runpath(&mut self, runpath_offset: u64) {
self.add(DT_RUNPATH, runpath_offset);
}
pub fn add_init(&mut self, init_addr: u64) {
self.add(DT_INIT, init_addr);
}
pub fn add_fini(&mut self, fini_addr: u64) {
self.add(DT_FINI, fini_addr);
}
pub fn add_init_array(&mut self, addr: u64, size: u64) {
self.add(DT_INIT_ARRAY, addr);
self.add(DT_INIT_ARRAYSZ, size);
}
pub fn add_fini_array(&mut self, addr: u64, size: u64) {
self.add(DT_FINI_ARRAY, addr);
self.add(DT_FINI_ARRAYSZ, size);
}
pub fn add_hash(&mut self, addr: u64) {
self.add(DT_HASH, addr);
}
pub fn add_gnu_hash(&mut self, addr: u64) {
self.add(DT_GNU_HASH, addr);
}
pub fn add_symbol_table(&mut self, symtab_addr: u64, strtab_addr: u64, strtab_size: u64) {
let syment = if self.is_64bit { 24u64 } else { 16u64 };
self.add(DT_SYMTAB, symtab_addr);
self.add(DT_SYMENT, syment);
self.add(DT_STRTAB, strtab_addr);
self.add(DT_STRSZ, strtab_size);
}
pub fn add_pltgot(&mut self, addr: u64) {
self.add(DT_PLTGOT, addr);
}
pub fn add_plt_relocations(&mut self, is_rela: bool, jmprel_addr: u64, jmprel_size: u64) {
self.add(DT_PLTREL, if is_rela { 7 } else { 17 });
self.add(DT_PLTRELSZ, jmprel_size);
self.add(DT_JMPREL, jmprel_addr);
}
pub fn add_rela(&mut self, rela_addr: u64, rela_size: u64, relaent: u64) {
self.add(DT_RELA, rela_addr);
self.add(DT_RELASZ, rela_size);
self.add(DT_RELAENT, relaent);
}
pub fn add_rel(&mut self, rel_addr: u64, rel_size: u64) {
self.add(DT_REL, rel_addr);
self.add(DT_RELSZ, rel_size);
self.add(DT_RELENT, if self.is_64bit { 16 } else { 8 });
}
pub fn add_flags(&mut self, flags: u64) {
self.add(DT_FLAGS, flags);
}
pub fn add_flags_1(&mut self, flags_1: u64) {
self.add(DT_FLAGS_1, flags_1);
}
pub fn add_versym(&mut self, addr: u64) {
self.add(DT_VERSYM, addr);
}
pub fn add_verdef(&mut self, addr: u64, num: u64) {
self.add(DT_VERDEF, addr);
self.add(DT_VERDEFNUM, num);
}
pub fn add_verneed(&mut self, addr: u64, num: u64) {
self.add(DT_VERNEED, addr);
self.add(DT_VERNEEDNUM, num);
}
pub fn add_rel_count(&mut self, rela_count: u64, rel_count: u64) {
if rela_count > 0 {
self.add(DT_RELACOUNT, rela_count);
}
if rel_count > 0 {
self.add(DT_RELCOUNT, rel_count);
}
}
pub fn build_sysv_hash(symbol_names: &[String]) -> (Vec<u32>, u32) {
let nbuckets = next_power_of_two(symbol_names.len()) as u32;
let nchains = symbol_names.len() as u32;
let mut buckets = vec![0u32; nbuckets as usize];
let mut chains = vec![0u32; nchains as usize];
for (i, name) in symbol_names.iter().enumerate() {
let hash = elf_hash(name) % nbuckets;
chains[i] = buckets[hash as usize];
buckets[hash as usize] = i as u32;
}
let mut table = Vec::with_capacity(2 + nbuckets as usize + nchains as usize);
table.push(nbuckets);
table.push(nchains);
table.extend_from_slice(&buckets);
table.extend_from_slice(&chains);
(table, nbuckets)
}
pub fn build_gnu_hash(symbol_names: &[String], bloom_shift: u32) -> Vec<u8> {
let nbuckets = next_power_of_two(symbol_names.len()) as u32;
let bloom_size = if nbuckets < 64 {
4096 / 8
} else {
nbuckets as usize / 8
};
let mut bloom = vec![0u64; bloom_size / 8];
let mut buckets = vec![0u32; nbuckets as usize];
let mut hashvals = Vec::with_capacity(symbol_names.len());
let symoffset = nbuckets;
for (i, name) in symbol_names.iter().enumerate() {
let h = gnu_hash(name);
let h2 = h >> bloom_shift;
let idx = (h / 8) as usize % bloom.len();
let bit = h % 8;
bloom[idx] |= 1u64 << bit;
let h2_bit = h2 % 8;
bloom[h2 as usize % bloom.len()] |= 1u64 << h2_bit;
let bucket = (h % nbuckets as u64) as usize;
if buckets[bucket] == 0 {
buckets[bucket] = i as u32;
}
hashvals.push(h & !1u64); }
for i in 0..hashvals.len() {
if i < hashvals.len() {
hashvals[i as usize] |= 1;
}
}
let mut data = Vec::new();
data.extend_from_slice(&nbuckets.to_le_bytes());
data.extend_from_slice(&symoffset.to_le_bytes());
data.extend_from_slice(&(bloom.len() as u32 * 8).to_le_bytes());
data.extend_from_slice(&bloom_shift.to_le_bytes());
for &word in &bloom {
data.extend_from_slice(&word.to_le_bytes());
}
for &b in &buckets {
data.extend_from_slice(&b.to_le_bytes());
}
for &h in &hashvals {
data.extend_from_slice(&(h as u32).to_le_bytes());
}
data
}
pub fn emit(&self) -> Vec<u8> {
let mut buf = Vec::new();
for e in &self.entries {
if self.is_64bit {
buf.extend_from_slice(&e.d_tag.to_le_bytes());
buf.extend_from_slice(&e.d_val.to_le_bytes());
} else {
buf.extend_from_slice(&(e.d_tag as u32).to_le_bytes());
buf.extend_from_slice(&(e.d_val as u32).to_le_bytes());
}
}
if self.is_64bit {
buf.extend_from_slice(&0u64.to_le_bytes());
buf.extend_from_slice(&0u64.to_le_bytes());
} else {
buf.extend_from_slice(&0u32.to_le_bytes());
buf.extend_from_slice(&0u32.to_le_bytes());
}
buf
}
}
fn gnu_hash(name: &str) -> u64 {
let mut h: u64 = 5381;
for &b in name.as_bytes() {
h = h.wrapping_mul(33).wrapping_add(b as u64);
}
h
}
impl X86DynamicEntry {
pub fn tag_name(&self) -> &'static str {
match self.d_tag {
DT_NULL => "DT_NULL",
DT_NEEDED => "DT_NEEDED",
DT_PLTRELSZ => "DT_PLTRELSZ",
DT_PLTGOT => "DT_PLTGOT",
DT_HASH => "DT_HASH",
DT_STRTAB => "DT_STRTAB",
DT_SYMTAB => "DT_SYMTAB",
DT_RELA => "DT_RELA",
DT_RELASZ => "DT_RELASZ",
DT_RELAENT => "DT_RELAENT",
DT_STRSZ => "DT_STRSZ",
DT_SYMENT => "DT_SYMENT",
DT_INIT => "DT_INIT",
DT_FINI => "DT_FINI",
DT_SONAME => "DT_SONAME",
DT_RPATH => "DT_RPATH",
DT_SYMBOLIC => "DT_SYMBOLIC",
DT_REL => "DT_REL",
DT_RELSZ => "DT_RELSZ",
DT_RELENT => "DT_RELENT",
DT_PLTREL => "DT_PLTREL",
DT_DEBUG => "DT_DEBUG",
DT_TEXTREL => "DT_TEXTREL",
DT_JMPREL => "DT_JMPREL",
DT_BIND_NOW => "DT_BIND_NOW",
DT_INIT_ARRAY => "DT_INIT_ARRAY",
DT_FINI_ARRAY => "DT_FINI_ARRAY",
DT_INIT_ARRAYSZ => "DT_INIT_ARRAYSZ",
DT_FINI_ARRAYSZ => "DT_FINI_ARRAYSZ",
DT_RUNPATH => "DT_RUNPATH",
DT_FLAGS => "DT_FLAGS",
DT_PREINIT_ARRAY => "DT_PREINIT_ARRAY",
DT_PREINIT_ARRAYSZ => "DT_PREINIT_ARRAYSZ",
DT_SYMTAB_SHNDX => "DT_SYMTAB_SHNDX",
DT_GNU_HASH => "DT_GNU_HASH",
DT_VERSYM => "DT_VERSYM",
DT_FLAGS_1 => "DT_FLAGS_1",
DT_VERDEF => "DT_VERDEF",
DT_VERDEFNUM => "DT_VERDEFNUM",
DT_VERNEED => "DT_VERNEED",
DT_VERNEEDNUM => "DT_VERNEEDNUM",
DT_RELACOUNT => "DT_RELACOUNT",
DT_RELCOUNT => "DT_RELCOUNT",
_ => "UNKNOWN",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_linker_full_create_elf64() {
let linker = X86LinkerFull::new_elf_x86_64();
assert!(matches!(linker.output_format, X86OutputFormat::Elf64));
assert!(matches!(linker.arch, X86LLDArch::X86_64));
assert!(linker.diagnostics.is_empty());
}
#[test]
fn test_linker_full_create_elf32() {
let linker = X86LinkerFull::new_elf_i386();
assert!(matches!(linker.output_format, X86OutputFormat::Elf32));
assert!(matches!(linker.arch, X86LLDArch::I386));
}
#[test]
fn test_linker_full_create_coff64() {
let linker = X86LinkerFull::new_coff_x86_64();
assert!(matches!(linker.output_format, X86OutputFormat::Coff64));
}
#[test]
fn test_linker_full_create_coff32() {
let linker = X86LinkerFull::new_coff_i386();
assert!(matches!(linker.output_format, X86OutputFormat::Coff32));
}
#[test]
fn test_linker_full_pic_mode() {
let mut linker = X86LinkerFull::new_elf_x86_64();
assert!(!linker.is_pic);
linker.set_pic(true);
assert!(linker.is_pic);
assert!(linker.elf_linker.is_pic);
}
#[test]
fn test_linker_full_diag() {
let mut linker = X86LinkerFull::new_elf_x86_64();
linker.diag(X86DiagLevel::Warning, "test warning");
assert_eq!(linker.diagnostics.len(), 1);
assert!(matches!(linker.diagnostics[0].level, X86DiagLevel::Warning));
assert_eq!(linker.diagnostics[0].message, "test warning");
}
#[test]
fn test_linker_full_image_base() {
let mut linker = X86LinkerFull::new_elf_x86_64();
assert_eq!(linker.image_base, X86_64_DEFAULT_IMAGE_BASE);
linker.set_image_base(0x200000);
assert_eq!(linker.image_base, 0x200000);
}
#[test]
fn test_linker_full_entry() {
let mut linker = X86LinkerFull::new_elf_x86_64();
assert_eq!(linker.entry_point, "_start");
linker.set_entry("my_main");
assert_eq!(linker.entry_point, "my_main");
}
#[test]
fn test_linker_full_shared_lib() {
let mut linker = X86LinkerFull::new_elf_x86_64();
linker.add_shared_library("libc.so.6");
assert_eq!(linker.shared_libs.len(), 1);
assert_eq!(linker.shared_libs[0].name, "libc.so.6");
}
#[test]
fn test_linker_full_coff_import_export() {
let mut linker = X86LinkerFull::new_coff_x86_64();
linker.add_coff_import("malloc", "msvcrt.dll", None, false);
linker.add_coff_export("myfunc", 1, 0x1000);
assert_eq!(linker.imports.len(), 1);
assert_eq!(linker.exports.len(), 1);
assert_eq!(linker.imports[0].symbol_name, "malloc");
assert_eq!(linker.exports[0].name, "myfunc");
}
#[test]
fn test_linker_full_library_path() {
let mut linker = X86LinkerFull::new_elf_x86_64();
linker.add_library_path("/usr/lib");
linker.add_library_path("/usr/local/lib");
assert_eq!(linker.library_search_paths.len(), 2);
}
#[test]
fn test_linker_full_macho_create() {
let linker = X86LinkerFull::new_macho_x86_64();
assert!(matches!(linker.arch, X86LLDArch::X86_64));
}
#[test]
fn test_coff_linker_full_create() {
let linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
assert!(linker.objects.is_empty());
assert!(linker.symbols.is_empty());
assert_eq!(linker.subsystem, 3); }
#[test]
fn test_coff_linker_full_create_i386() {
let linker = X86COFFLinkerFull::new(X86LLDArch::I386);
assert!(!linker.arch.is_64bit());
assert_eq!(linker.image_base, I386_DEFAULT_IMAGE_BASE as u64);
}
#[test]
fn test_coff_linker_full_parse_minimal() {
let linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
let mut data = vec![0u8; 128];
data[0] = 0x64;
data[1] = 0x86;
data[2] = 0;
data[3] = 0;
let result = linker.parse_coff_object_full("test.obj", &data);
assert!(result.is_ok());
let obj = result.unwrap();
assert_eq!(obj.header.machine, IMAGE_FILE_MACHINE_AMD64);
assert!(obj.sections.is_empty());
}
#[test]
fn test_coff_linker_full_parse_bad() {
let linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
let data = vec![0u8; 10];
let result = linker.parse_coff_object_full("bad.obj", &data);
assert!(result.is_err());
}
#[test]
fn test_coff_linker_full_import_export() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
linker.add_import("GetProcAddress", "kernel32.dll", None, false);
linker.add_export("MyExport", 1, 0x2000);
assert_eq!(linker.imports.len(), 1);
assert_eq!(linker.exports.len(), 1);
}
#[test]
fn test_coff_linker_full_delay_import() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
linker.add_delay_import("shell32.dll");
assert_eq!(linker.delay_imports.len(), 1);
assert_eq!(linker.delay_imports[0].dll_name, "shell32.dll");
}
#[test]
fn test_coff_linker_full_bound_import() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
linker.add_bound_import("ntdll.dll", 0x12345678);
assert_eq!(linker.bound_imports.len(), 1);
}
#[test]
fn test_coff_linker_full_debug() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
let dbg_data = b"debug_info";
linker.add_debug_entry(IMAGE_DEBUG_TYPE_CODEVIEW, dbg_data);
assert_eq!(linker.debug_entries.len(), 1);
assert_eq!(
linker.debug_entries[0].debug_type,
IMAGE_DEBUG_TYPE_CODEVIEW
);
}
#[test]
fn test_coff_linker_full_tls() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
let tls = TlsDirectoryData {
start_address_of_raw_data: 0x1000,
end_address_of_raw_data: 0x2000,
address_of_index: 0x3000,
address_of_callbacks: 0,
size_of_zero_fill: 0,
characteristics: 0,
};
linker.set_tls_data(tls);
assert!(linker.has_tls);
assert!(linker.tls_data.is_some());
}
#[test]
fn test_coff_linker_full_load_config() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
let lc = LoadConfigData {
size: 256,
time_date_stamp: 0,
major_version: 0,
minor_version: 0,
global_flags_clear: 0,
global_flags_set: 0,
critical_section_default_timeout: 0,
decommit_free_block_threshold: 0,
decommit_total_free_threshold: 0,
lock_prefix_table: 0,
maximum_allocation_size: 0,
virtual_memory_threshold: 0,
process_affinity_mask: 0,
process_heap_flags: 0,
csd_version: 0,
dependent_load_flags: 0,
edit_list: 0,
security_cookie: 0,
se_handler_table: 0,
se_handler_count: 0,
guard_cf_check_function_pointer: 0,
guard_cf_dispatch_function_pointer: 0,
guard_cf_function_table: 0,
guard_cf_function_count: 0,
guard_flags: 0,
};
linker.set_load_config(lc);
assert!(linker.load_config.is_some());
}
#[test]
fn test_coff_linker_full_resource() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
let res = vec![1u8, 2, 3, 4];
linker.set_resource_data(res.clone());
assert!(linker.has_resource);
assert_eq!(linker.resource_data, res);
}
#[test]
fn test_coff_linker_full_exception() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
linker.add_exception_entry(ExceptionTableEntry {
begin_address: 0x1000,
end_address: 0x1100,
unwind_info_address: 0x2000,
});
linker.add_exception_entry(ExceptionTableEntry {
begin_address: 0x1200,
end_address: 0x1300,
unwind_info_address: 0x2100,
});
assert!(linker.has_exception);
assert_eq!(linker.exception_data.len(), 2);
}
#[test]
fn test_coff_linker_full_export_data() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
linker.add_export("test_func", 1, 0x4000);
let data = linker.generate_exports();
assert!(!data.is_empty());
}
#[test]
fn test_coff_linker_full_delay_import_table() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
linker.add_delay_import("user32.dll");
let data = linker.generate_delay_import_table();
assert!(!data.is_empty());
}
#[test]
fn test_coff_linker_full_exception_table() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
linker.add_exception_entry(ExceptionTableEntry {
begin_address: 0x1000,
end_address: 0x1100,
unwind_info_address: 0x2000,
});
let data = linker.generate_exception_table();
assert_eq!(data.len(), 12); }
#[test]
fn test_coff_linker_full_base_relocations() {
let blocks = vec![BaseRelocationBlock {
page_rva: 0x1000,
entries: vec![BaseRelocationEntry {
offset: 0x100,
rel_type: IMAGE_REL_BASED_DIR64,
}],
}];
let data = X86COFFLinkerFull::new(X86LLDArch::X86_64).generate_base_relocations(&blocks);
assert!(!data.is_empty());
}
#[test]
fn test_coff_linker_full_tls_directory() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
linker.set_tls_data(TlsDirectoryData {
start_address_of_raw_data: 0x5000,
end_address_of_raw_data: 0x5100,
address_of_index: 0x5200,
address_of_callbacks: 0,
size_of_zero_fill: 16,
characteristics: 0,
});
let data = linker.generate_tls_directory();
assert!(!data.is_empty());
}
#[test]
fn test_coff_linker_full_load_config_data() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
linker.set_load_config(LoadConfigData {
size: 256,
time_date_stamp: 0,
major_version: 0,
minor_version: 0,
global_flags_clear: 0,
global_flags_set: 0,
critical_section_default_timeout: 0,
decommit_free_block_threshold: 0,
decommit_total_free_threshold: 0,
lock_prefix_table: 0,
maximum_allocation_size: 0,
virtual_memory_threshold: 0,
process_affinity_mask: 0,
process_heap_flags: 0,
csd_version: 0,
dependent_load_flags: 0,
edit_list: 0,
security_cookie: 0,
se_handler_table: 0,
se_handler_count: 0,
guard_cf_check_function_pointer: 0,
guard_cf_dispatch_function_pointer: 0,
guard_cf_function_table: 0,
guard_cf_function_count: 0,
guard_flags: 0,
});
let data = linker.generate_load_config();
assert!(!data.is_empty());
}
#[test]
fn test_coff_linker_full_bound_import_table() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
linker.add_bound_import("ntdll.dll", 0x12345678);
let data = linker.generate_bound_import_table();
assert!(!data.is_empty());
}
#[test]
fn test_coff_linker_full_debug_directory() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
linker.add_debug_entry(IMAGE_DEBUG_TYPE_CODEVIEW, b"RSDS");
let data = linker.generate_debug_directory();
assert!(!data.is_empty());
}
#[test]
fn test_coff_linker_full_dos_stub() {
let linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
let mut buf = Vec::new();
linker.emit_dos_stub(&mut buf);
assert_eq!(buf.len(), 64);
assert_eq!(&buf[0..2], b"MZ");
}
#[test]
fn test_coff_linker_full_header_size() {
let linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
let size = linker.compute_header_size();
assert!(size > 0);
}
#[test]
fn test_coff_linker_full_section_name_to_chars() {
let cs = X86COFFLinkerFull::section_name_to_characteristics(".text");
assert!(cs & IMAGE_SCN_CNT_CODE != 0);
let cs2 = X86COFFLinkerFull::section_name_to_characteristics(".data");
assert!(cs2 & IMAGE_SCN_MEM_WRITE != 0);
let cs3 = X86COFFLinkerFull::section_name_to_characteristics(".rdata");
assert!(cs3 & IMAGE_SCN_MEM_READ != 0);
assert!(cs3 & IMAGE_SCN_MEM_WRITE == 0);
}
#[test]
fn test_coff_linker_full_add_object_empty() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
let data = vec![0u8; 64]; data[0] = 0x64;
data[1] = 0x86; let result = linker.add_object("empty.obj", data);
assert!(result.is_ok());
assert_eq!(linker.objects.len(), 1);
}
#[test]
fn test_coff_linker_full_emit() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
let mut data = vec![0u8; 20 + 40]; data[0] = 0x64;
data[1] = 0x86; data[2] = 1;
data[3] = 0; data[20] = b'.';
data[21] = b't';
data[22] = b'e';
data[23] = b'x';
data[24] = b't';
data[36] = 1;
data[37] = 0; let chars = IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ;
data[56] = (chars & 0xFF) as u8;
data[57] = ((chars >> 8) & 0xFF) as u8;
data[58] = ((chars >> 16) & 0xFF) as u8;
data[59] = ((chars >> 24) & 0xFF) as u8;
linker.add_object("test.obj", data).unwrap();
linker.link().unwrap();
let result = linker.emit();
assert!(result.is_ok());
let image = result.unwrap();
assert!(!image.is_empty());
assert_eq!(&image[0..2], b"MZ");
let pe_offset =
u32::from_le_bytes([image[0x3C], image[0x3D], image[0x3E], image[0x3F]]) as usize;
assert_eq!(&image[pe_offset..pe_offset + 4], b"PE\0\0");
}
#[test]
fn test_coff_linker_full_emit_32bit() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::I386);
linker.link().unwrap();
let result = linker.emit();
assert!(result.is_ok());
let image = result.unwrap();
assert_eq!(&image[0..2], b"MZ");
}
#[test]
fn test_macho_linker_create() {
let linker = X86MachOLinker::new(X86LLDArch::X86_64);
assert!(linker.is_64bit);
assert!(!linker.is_dylib);
assert_eq!(linker.output_type, MH_EXECUTE);
}
#[test]
fn test_macho_linker_dylib() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.set_dylib("libtest.dylib");
assert!(linker.is_dylib);
assert_eq!(linker.output_type, MH_DYLIB);
assert_eq!(linker.install_name, "libtest.dylib");
}
#[test]
fn test_macho_linker_bundle() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.set_bundle();
assert_eq!(linker.output_type, MH_BUNDLE);
}
#[test]
fn test_macho_linker_add_dylib() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.add_dylib("/usr/lib/libSystem.B.dylib", false, false);
assert_eq!(linker.dylibs.len(), 1);
assert_eq!(linker.dylibs[0].name, "/usr/lib/libSystem.B.dylib");
}
#[test]
fn test_macho_linker_add_weak_dylib() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.add_dylib("/usr/lib/libc++.dylib", true, false);
assert!(linker.dylibs[0].is_weak);
}
#[test]
fn test_macho_linker_add_reexport_dylib() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.add_dylib("/usr/lib/libSystem.B.dylib", false, true);
assert!(linker.dylibs[0].is_reexport);
}
#[test]
fn test_macho_linker_add_rpath() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.add_rpath("@executable_path/../Frameworks");
linker.add_rpath("@loader_path");
assert_eq!(linker.rpaths.len(), 2);
}
#[test]
fn test_macho_linker_uuid() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
let uuid = [
0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54,
0x32, 0x10,
];
linker.set_uuid(uuid);
assert_eq!(linker.uuid, Some(uuid));
}
#[test]
fn test_macho_linker_build_version() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.set_build_version(PLATFORM_MACOS, 0x000D0000, 0x000D0000);
assert!(linker.build_version.is_some());
}
#[test]
fn test_macho_linker_version_min() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.set_version_min(0x000A0E00, 0x000A0E00);
assert!(linker.version_min.is_some());
}
#[test]
fn test_macho_linker_source_version() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.set_source_version(0x00C8010000000000); assert_eq!(linker.source_version, Some(0x00C8010000000000));
}
#[test]
fn test_macho_linker_exports() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.add_export("_my_func");
linker.add_export("_my_data");
assert_eq!(linker.exports.len(), 2);
}
#[test]
fn test_macho_linker_bindings() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.add_binding(1, 0x100, "_printf", 1, 0);
assert_eq!(linker.bindings.len(), 1);
assert_eq!(linker.bindings[0].symbol, "_printf");
}
#[test]
fn test_macho_linker_weak_bindings() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.add_weak_binding(1, 0x200, "_optional_func");
assert_eq!(linker.weak_bindings.len(), 1);
}
#[test]
fn test_macho_linker_lazy_binding() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.add_lazy_binding("_printf", 1);
assert_eq!(linker.lazy_stubs.len(), 1);
}
#[test]
fn test_macho_linker_generate_bind_opcodes() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.add_binding(1, 0x100, "_printf", 1, 0);
let data = linker.generate_bind_opcodes();
assert!(!data.is_empty());
assert_eq!(data.last(), Some(&BIND_OPCODE_DONE));
}
#[test]
fn test_macho_linker_generate_weak_bind_opcodes() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.add_weak_binding(1, 0x200, "_opt");
let data = linker.generate_weak_bind_opcodes();
assert!(!data.is_empty());
}
#[test]
fn test_macho_linker_generate_lazy_bind_opcodes() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.add_lazy_binding("_printf", 1);
let data = linker.generate_lazy_bind_opcodes();
assert!(!data.is_empty());
}
#[test]
fn test_macho_linker_generate_export_trie() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.add_export("_my_func");
let data = linker.generate_export_trie();
assert!(!data.is_empty());
}
#[test]
fn test_macho_linker_generate_export_trie_empty() {
let linker = X86MachOLinker::new(X86LLDArch::X86_64);
let data = linker.generate_export_trie();
assert_eq!(data, vec![0u8]);
}
#[test]
fn test_macho_linker_emit() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.add_dylib("/usr/lib/libSystem.B.dylib", false, false);
linker.add_export("_main");
let result = linker.emit();
assert!(result.is_ok());
let image = result.unwrap();
assert!(!image.is_empty());
let magic = u32::from_le_bytes([image[0], image[1], image[2], image[3]]);
assert_eq!(magic, MH_MAGIC_64);
}
#[test]
fn test_macho_linker_fat_binary() {
let x86_data = vec![0xAAu8; 100];
let arm64_data = vec![0xBBu8; 100];
let fat = X86MachOLinker::generate_fat_binary(&x86_data, &arm64_data);
assert!(fat.len() > 200);
let magic = u32::from_be_bytes([fat[0], fat[1], fat[2], fat[3]]);
assert_eq!(magic, FAT_MAGIC);
}
#[test]
fn test_macho_linker_uleb128() {
let mut buf = Vec::new();
X86MachOLinker::write_uleb128(&mut buf, 0);
assert_eq!(buf, vec![0x00]);
}
#[test]
fn test_macho_linker_sleb128() {
let mut buf = Vec::new();
X86MachOLinker::write_sleb128(&mut buf, 0);
assert!(buf.len() == 1);
}
#[test]
fn test_elf_phdrs_create() {
let phdrs = X86ELFPhdrs::new();
assert!(phdrs.entries.is_empty());
assert!(!phdrs.is_shared);
assert!(!phdrs.has_tls);
}
#[test]
fn test_elf_phdrs_generate() {
let mut phdrs = X86ELFPhdrs::new();
phdrs.has_eh_frame = true;
phdrs.has_relro = true;
phdrs.has_gnu_property = true;
let segments = vec![
X86OutputSegment {
p_type: PT_LOAD,
p_flags: PF_R | PF_X,
p_align: 4096,
p_vaddr: 0x400000,
p_paddr: 0x400000,
p_offset: 0,
p_filesz: 0x1000,
p_memsz: 0x1000,
section_indices: vec![0, 1],
},
X86OutputSegment {
p_type: PT_LOAD,
p_flags: PF_R | PF_W,
p_align: 4096,
p_vaddr: 0x600000,
p_paddr: 0x600000,
p_offset: 0x1000,
p_filesz: 0x1000,
p_memsz: 0x2000,
section_indices: vec![2, 3],
},
];
phdrs.generate(&segments, 0x400000, 4096);
assert!(!phdrs.entries.is_empty());
let loads: Vec<_> = phdrs
.entries
.iter()
.filter(|e| e.p_type == PT_LOAD)
.collect();
assert_eq!(loads.len(), 2);
}
#[test]
fn test_elf_phdrs_generate_with_tls() {
let mut phdrs = X86ELFPhdrs::new();
phdrs.has_tls = true;
phdrs.generate(&[], 0x400000, 4096);
let tls: Vec<_> = phdrs
.entries
.iter()
.filter(|e| e.p_type == PT_TLS)
.collect();
assert_eq!(tls.len(), 1);
}
#[test]
fn test_elf_phdrs_generate_no_tls() {
let mut phdrs = X86ELFPhdrs::new();
phdrs.has_tls = false;
phdrs.generate(&[], 0x400000, 4096);
let tls: Vec<_> = phdrs
.entries
.iter()
.filter(|e| e.p_type == PT_TLS)
.collect();
assert_eq!(tls.len(), 1); }
#[test]
fn test_elf_phdrs_generate_exec_stack() {
let mut phdrs = X86ELFPhdrs::new();
phdrs.exec_stack = true;
phdrs.generate(&[], 0x400000, 4096);
let stacks: Vec<_> = phdrs
.entries
.iter()
.filter(|e| e.p_type == PT_GNU_STACK)
.collect();
assert_eq!(stacks.len(), 1);
assert_eq!(stacks[0].p_flags, PF_R | PF_W | PF_X);
}
#[test]
fn test_elf_phdrs_emit_64() {
let mut phdrs = X86ELFPhdrs::new();
phdrs.generate(&[], 0x400000, 4096);
let data = phdrs.emit_64();
assert!(!data.is_empty());
assert_eq!(data.len() % 56, 0);
}
#[test]
fn test_elf_phdrs_emit_32() {
let mut phdrs = X86ELFPhdrs::new();
phdrs.generate(&[], 0x400000, 4096);
let data = phdrs.emit_32();
assert!(!data.is_empty());
assert_eq!(data.len() % 32, 0);
}
#[test]
fn test_elf_phdrs_default() {
let phdrs = X86ELFPhdrs::default();
assert!(phdrs.entries.is_empty());
}
#[test]
fn test_elf_phdrs_with_relro() {
let mut phdrs = X86ELFPhdrs::new();
phdrs.has_relro = true;
phdrs.generate(&[], 0x400000, 4096);
let relro: Vec<_> = phdrs
.entries
.iter()
.filter(|e| e.p_type == PT_GNU_RELRO)
.collect();
assert_eq!(relro.len(), 1);
}
#[test]
fn test_elf_phdrs_with_property() {
let mut phdrs = X86ELFPhdrs::new();
phdrs.has_gnu_property = true;
phdrs.generate(&[], 0x400000, 4096);
let prop: Vec<_> = phdrs
.entries
.iter()
.filter(|e| e.p_type == PT_GNU_PROPERTY)
.collect();
assert_eq!(prop.len(), 1);
}
#[test]
fn test_elf_phdrs_shared() {
let mut phdrs = X86ELFPhdrs::new();
phdrs.is_shared = true;
phdrs.generate(&[], 0x0, 4096);
let interp: Vec<_> = phdrs
.entries
.iter()
.filter(|e| e.p_type == PT_INTERP)
.collect();
assert!(phdrs.entries.iter().any(|e| e.p_type == PT_PHDR));
}
#[test]
fn test_dynamic_entries_create() {
let de = X86DynamicEntries::new(true);
assert!(de.entries.is_empty());
assert!(de.is_64bit);
}
#[test]
fn test_dynamic_entries_create_32() {
let de = X86DynamicEntries::new(false);
assert!(!de.is_64bit);
}
#[test]
fn test_dynamic_entries_add_needed() {
let mut de = X86DynamicEntries::new(true);
de.add_needed(10);
assert_eq!(de.entries.len(), 1);
assert_eq!(de.entries[0].d_tag, DT_NEEDED);
}
#[test]
fn test_dynamic_entries_add_soname() {
let mut de = X86DynamicEntries::new(true);
de.add_soname(20);
assert_eq!(de.entries[0].d_tag, DT_SONAME);
}
#[test]
fn test_dynamic_entries_add_rpath() {
let mut de = X86DynamicEntries::new(true);
de.add_rpath(30);
assert_eq!(de.entries[0].d_tag, DT_RPATH);
}
#[test]
fn test_dynamic_entries_add_runpath() {
let mut de = X86DynamicEntries::new(true);
de.add_runpath(40);
assert_eq!(de.entries[0].d_tag, DT_RUNPATH);
}
#[test]
fn test_dynamic_entries_add_init_fini() {
let mut de = X86DynamicEntries::new(true);
de.add_init(0x1000);
de.add_fini(0x2000);
assert_eq!(de.entries.len(), 2);
assert_eq!(de.entries[0].d_tag, DT_INIT);
assert_eq!(de.entries[1].d_tag, DT_FINI);
}
#[test]
fn test_dynamic_entries_add_init_array() {
let mut de = X86DynamicEntries::new(true);
de.add_init_array(0x3000, 16);
assert_eq!(de.entries.len(), 2);
assert_eq!(de.entries[0].d_tag, DT_INIT_ARRAY);
assert_eq!(de.entries[1].d_tag, DT_INIT_ARRAYSZ);
}
#[test]
fn test_dynamic_entries_add_fini_array() {
let mut de = X86DynamicEntries::new(true);
de.add_fini_array(0x4000, 8);
assert_eq!(de.entries.len(), 2);
assert_eq!(de.entries[0].d_tag, DT_FINI_ARRAY);
assert_eq!(de.entries[1].d_tag, DT_FINI_ARRAYSZ);
}
#[test]
fn test_dynamic_entries_add_hash() {
let mut de = X86DynamicEntries::new(true);
de.add_hash(0x5000);
de.add_gnu_hash(0x6000);
assert_eq!(de.entries.len(), 2);
}
#[test]
fn test_dynamic_entries_add_symbol_table() {
let mut de = X86DynamicEntries::new(true);
de.add_symbol_table(0x7000, 0x8000, 256);
assert_eq!(de.entries.len(), 4); }
#[test]
fn test_dynamic_entries_add_pltgot() {
let mut de = X86DynamicEntries::new(true);
de.add_pltgot(0x9000);
assert_eq!(de.entries[0].d_tag, DT_PLTGOT);
}
#[test]
fn test_dynamic_entries_add_plt_relocations() {
let mut de = X86DynamicEntries::new(true);
de.add_plt_relocations(true, 0xA000, 48);
assert_eq!(de.entries.len(), 3);
assert_eq!(de.entries[0].d_tag, DT_PLTREL);
assert_eq!(de.entries[1].d_tag, DT_PLTRELSZ);
assert_eq!(de.entries[2].d_tag, DT_JMPREL);
}
#[test]
fn test_dynamic_entries_add_rela() {
let mut de = X86DynamicEntries::new(true);
de.add_rela(0xB000, 96, 24);
assert_eq!(de.entries.len(), 3);
assert_eq!(de.entries[0].d_tag, DT_RELA);
}
#[test]
fn test_dynamic_entries_add_rel() {
let mut de = X86DynamicEntries::new(false);
de.add_rel(0xC000, 64);
assert_eq!(de.entries.len(), 3); }
#[test]
fn test_dynamic_entries_add_flags() {
let mut de = X86DynamicEntries::new(true);
de.add_flags(DF_BIND_NOW | DF_STATIC_TLS);
de.add_flags_1(DF_1_NOW | DF_1_PIE);
assert_eq!(de.entries.len(), 2);
}
#[test]
fn test_dynamic_entries_add_versym() {
let mut de = X86DynamicEntries::new(true);
de.add_versym(0xD000);
assert_eq!(de.entries[0].d_tag, DT_VERSYM);
}
#[test]
fn test_dynamic_entries_add_verdef() {
let mut de = X86DynamicEntries::new(true);
de.add_verdef(0xE000, 2);
assert_eq!(de.entries.len(), 2);
assert_eq!(de.entries[0].d_tag, DT_VERDEF);
assert_eq!(de.entries[1].d_tag, DT_VERDEFNUM);
}
#[test]
fn test_dynamic_entries_add_verneed() {
let mut de = X86DynamicEntries::new(true);
de.add_verneed(0xF000, 1);
assert_eq!(de.entries.len(), 2);
assert_eq!(de.entries[0].d_tag, DT_VERNEED);
assert_eq!(de.entries[1].d_tag, DT_VERNEEDNUM);
}
#[test]
fn test_dynamic_entries_add_rel_count() {
let mut de = X86DynamicEntries::new(true);
de.add_rel_count(5, 3);
assert_eq!(de.entries.len(), 2);
}
#[test]
fn test_dynamic_entries_add_rel_count_zero() {
let mut de = X86DynamicEntries::new(true);
de.add_rel_count(0, 0);
assert_eq!(de.entries.len(), 0); }
#[test]
fn test_dynamic_entries_emit_64() {
let mut de = X86DynamicEntries::new(true);
de.add(DT_NEEDED, 1);
de.add(DT_INIT, 0x4000);
let data = de.emit();
assert_eq!(data.len(), 48); }
#[test]
fn test_dynamic_entries_emit_32() {
let mut de = X86DynamicEntries::new(false);
de.add(DT_NEEDED, 1);
let data = de.emit();
assert_eq!(data.len(), 16); }
#[test]
fn test_dynamic_entries_sysv_hash() {
let names = vec!["_init".to_string(), "_fini".to_string(), "main".to_string()];
let (table, nbuckets) = X86DynamicEntries::build_sysv_hash(&names);
assert!(!table.is_empty());
assert!(nbuckets > 0);
}
#[test]
fn test_dynamic_entries_gnu_hash() {
let names = vec!["_init".to_string(), "_fini".to_string(), "main".to_string()];
let data = X86DynamicEntries::build_gnu_hash(&names, 5);
assert!(!data.is_empty());
}
#[test]
fn test_dynamic_entries_tag_name() {
let e = X86DynamicEntry {
d_tag: DT_NEEDED,
d_val: 0,
d_ptr: 0,
};
assert_eq!(e.tag_name(), "DT_NEEDED");
let e2 = X86DynamicEntry {
d_tag: 0xFFFFFFFFu64,
d_val: 0,
d_ptr: 0,
};
assert_eq!(e2.tag_name(), "UNKNOWN");
}
#[test]
fn test_full_elf_pipeline_empty() {
let mut linker = X86LinkerFull::new_elf_x86_64();
linker.set_output_path("a.out");
let result = linker.link_and_emit();
assert!(result.is_ok());
}
#[test]
fn test_full_coff_pipeline_empty() {
let mut linker = X86LinkerFull::new_coff_x86_64();
linker.set_output_path("a.exe");
let _ = linker.link(); let result = linker.emit();
assert!(result.is_ok());
}
#[test]
fn test_macho_emit_dylib() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.set_dylib("@rpath/libtest.dylib");
linker.add_dylib("/usr/lib/libSystem.B.dylib", false, false);
let result = linker.emit();
assert!(result.is_ok());
}
#[test]
fn test_both_hash_styles() {
let names: Vec<String> = (0..100).map(|i| format!("sym_{}", i)).collect();
let (sysv, _) = X86DynamicEntries::build_sysv_hash(&names);
let gnu = X86DynamicEntries::build_gnu_hash(&names, 5);
assert!(!sysv.is_empty());
assert!(!gnu.is_empty());
}
#[test]
fn test_coff_dll_mode() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
linker.is_dll = true;
linker.subsystem = 2; let result = linker.emit();
assert!(result.is_ok());
}
#[test]
fn test_phdr_sizes() {
let mut phdrs = X86ELFPhdrs::new();
phdrs.generate(&[], 0x400000, 4096);
let d64 = phdrs.emit_64();
let n = d64.len() / 56;
assert_eq!(d64.len(), n * 56);
let d32 = phdrs.emit_32();
let m = d32.len() / 32;
assert_eq!(d32.len(), m * 32);
}
#[test]
fn test_dynamic_entries_comprehensive() {
let mut de = X86DynamicEntries::new(true);
de.add_needed(1);
de.add_needed(2);
de.add_soname(3);
de.add_rpath(4);
de.add_runpath(5);
de.add_init(0x1000);
de.add_fini(0x2000);
de.add_init_array(0x3000, 16);
de.add_fini_array(0x3100, 8);
de.add_hash(0x4000);
de.add_gnu_hash(0x4100);
de.add_symbol_table(0x5000, 0x6000, 512);
de.add_pltgot(0x7000);
de.add_plt_relocations(true, 0x7100, 96);
de.add_rela(0x7200, 128, 24);
de.add_flags(DF_BIND_NOW);
de.add_flags_1(DF_1_PIE);
de.add_versym(0x8000);
de.add_verdef(0x8100, 1);
de.add_verneed(0x8200, 2);
de.add_rel_count(4, 0);
let data = de.emit();
assert!(!data.is_empty());
assert_eq!(&data[data.len() - 16..data.len() - 8], &[0u8; 8]);
}
#[test]
fn test_fat_binary_roundtrip() {
let x86 = vec![0xAA; 128];
let arm = vec![0xBB; 64];
let fat = X86MachOLinker::generate_fat_binary(&x86, &arm);
let magic = u32::from_be_bytes([fat[0], fat[1], fat[2], fat[3]]);
assert_eq!(magic, FAT_MAGIC);
let num_archs = u32::from_be_bytes([fat[4], fat[5], fat[6], fat[7]]);
assert_eq!(num_archs, 2);
}
#[test]
fn test_all_phdr_types() {
let mut phdrs = X86ELFPhdrs::new();
phdrs.is_shared = true;
phdrs.has_tls = true;
phdrs.has_eh_frame = true;
phdrs.has_relro = true;
phdrs.has_gnu_property = true;
phdrs.exec_stack = false;
phdrs.generate(&[], 0x400000, 4096);
let types: Vec<u32> = phdrs.entries.iter().map(|e| e.p_type).collect();
assert!(types.contains(&PT_PHDR));
assert!(types.contains(&PT_LOAD)); assert!(types.contains(&PT_DYNAMIC));
assert!(types.contains(&PT_NOTE));
assert!(types.contains(&PT_TLS));
assert!(types.contains(&PT_GNU_EH_FRAME));
assert!(types.contains(&PT_GNU_STACK));
assert!(types.contains(&PT_GNU_RELRO));
assert!(types.contains(&PT_GNU_PROPERTY));
assert!(types.contains(&PT_SHLIB));
assert!(types.contains(&PT_OPENBSD_RANDOMIZE));
assert!(types.contains(&PT_OPENBSD_WXNEEDED));
}
#[test]
fn test_gnu_hash_implementation() {
let h1 = gnu_hash("_init");
let h2 = gnu_hash("_fini");
let h3 = gnu_hash("main");
assert_ne!(h1, h2);
assert_ne!(h2, h3);
}
#[test]
fn test_coff_parse_with_sections() {
let linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
let mut data = vec![0u8; 20 + 80]; data[0] = 0x64;
data[1] = 0x86; data[2] = 2;
data[3] = 0; data[20] = b'.';
data[21] = b't';
data[22] = b'e';
data[23] = b'x';
data[24] = b't';
data[28] = 64;
data[29] = 0;
data[30] = 0;
data[31] = 0; data[36] = 0;
data[37] = 0; let chars1 = IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ;
data[56] = (chars1 & 0xFF) as u8;
data[57] = ((chars1 >> 8) & 0xFF) as u8;
data[58] = ((chars1 >> 16) & 0xFF) as u8;
data[59] = ((chars1 >> 24) & 0xFF) as u8;
data[60] = b'.';
data[61] = b'd';
data[62] = b'a';
data[63] = b't';
data[64] = b'a';
let chars2 = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE;
data[96] = (chars2 & 0xFF) as u8;
data[97] = ((chars2 >> 8) & 0xFF) as u8;
data[98] = ((chars2 >> 16) & 0xFF) as u8;
data[99] = ((chars2 >> 24) & 0xFF) as u8;
let result = linker.parse_coff_object_full("multi.obj", &data);
assert!(result.is_ok());
let obj = result.unwrap();
assert_eq!(obj.sections.len(), 2);
assert_eq!(obj.sections[0].name, "text");
assert_eq!(obj.sections[1].name, "data");
assert_eq!(obj.header.number_of_sections, 2);
}
#[test]
fn test_coff_parse_with_relocations() {
let linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
let mut data = vec![0u8; 20 + 40 + 20]; data[0] = 0x64;
data[1] = 0x86;
data[2] = 1;
data[3] = 0; data[20] = b'.';
data[21] = b't';
data[22] = b'e';
data[23] = b'x';
data[24] = b't';
data[36] = 2;
data[37] = 0; data[40] = 60;
data[41] = 0;
data[42] = 0;
data[43] = 0; data[60] = 0x10;
data[61] = 0;
data[62] = 0;
data[63] = 0; data[64] = 0;
data[65] = 0;
data[66] = 0;
data[67] = 0; data[68] = 4;
data[69] = 0; data[70] = 0x20;
data[71] = 0;
data[72] = 0;
data[73] = 0; data[74] = 1;
data[75] = 0;
data[76] = 0;
data[77] = 0; data[78] = 1;
data[79] = 0;
let result = linker.parse_coff_object_full("reloc.obj", &data);
assert!(result.is_ok());
let obj = result.unwrap();
assert_eq!(obj.relocations.len(), 2);
assert_eq!(obj.relocations[0].rel_type, IMAGE_REL_AMD64_REL32);
assert_eq!(obj.relocations[1].rel_type, IMAGE_REL_AMD64_ADDR64);
}
#[test]
fn test_coff_parse_with_symbols() {
let linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
let symtab_offset = 20u32;
let nsyms = 2u32;
let mut data = vec![0u8; 20 + 2 * 18]; data[0] = 0x64;
data[1] = 0x86;
data[8] = (symtab_offset & 0xFF) as u8;
data[9] = ((symtab_offset >> 8) & 0xFF) as u8;
data[10] = ((symtab_offset >> 16) & 0xFF) as u8;
data[11] = ((symtab_offset >> 24) & 0xFF) as u8;
data[12] = (nsyms & 0xFF) as u8;
data[13] = ((nsyms >> 8) & 0xFF) as u8;
data[14] = ((nsyms >> 16) & 0xFF) as u8;
data[15] = ((nsyms >> 24) & 0xFF) as u8;
data[20] = b'_';
data[21] = b'm';
data[22] = b'a';
data[23] = b'i';
data[24] = b'n';
data[34] = 2; data[38] = b'.';
data[39] = b't';
data[40] = b'e';
data[41] = b'x';
data[42] = b't';
data[52] = 3;
let result = linker.parse_coff_object_full("sym.obj", &data);
assert!(result.is_ok());
let obj = result.unwrap();
assert_eq!(obj.symbols.len(), 2);
assert_eq!(obj.symbols[0].name, "_main");
assert!(obj.symbols[0].is_external);
assert_eq!(obj.symbols[1].name, ".text");
}
#[test]
fn test_coff_parse_long_section_name() {
let linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
let strtab_offset = 60u32;
let mut data = vec![0u8; 128];
data[0] = 0x64;
data[1] = 0x86;
data[2] = 1;
data[3] = 0;
data[20] = b'/';
data[21] = b'4';
let long_name = b".debug_info";
let strtab_size = 4 + long_name.len() as u32;
data[60..64].copy_from_slice(&strtab_size.to_le_bytes());
data[64..64 + long_name.len()].copy_from_slice(long_name);
let result = linker.parse_coff_object_full("long.obj", &data);
assert!(result.is_ok());
let obj = result.unwrap();
assert_eq!(obj.sections.len(), 1);
assert_eq!(obj.sections[0].name, ".debug_info");
}
#[test]
fn test_coff_linker_merge_sections() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
let obj1 = CoffObjectFull {
filename: "a.obj".to_string(),
data: vec![],
header: CoffFileHeader {
machine: IMAGE_FILE_MACHINE_AMD64,
number_of_sections: 1,
time_date_stamp: 0,
pointer_to_symbol_table: 0,
number_of_symbols: 0,
size_of_optional_header: 0,
characteristics: 0,
},
sections: vec![CoffSectionFull {
name: ".text".to_string(),
data: vec![0x90; 64],
header: CoffSectionHeader {
name: [0; 8],
virtual_size: 64,
virtual_address: 0,
size_of_raw_data: 64,
pointer_to_raw_data: 0,
pointer_to_relocations: 0,
pointer_to_line_numbers: 0,
number_of_relocations: 0,
number_of_line_numbers: 0,
characteristics: IMAGE_SCN_CNT_CODE,
},
section_index: 0,
}],
symbols: vec![],
relocations: vec![],
};
let obj2 = CoffObjectFull {
filename: "b.obj".to_string(),
data: vec![],
header: CoffFileHeader {
machine: IMAGE_FILE_MACHINE_AMD64,
number_of_sections: 1,
time_date_stamp: 0,
pointer_to_symbol_table: 0,
number_of_symbols: 0,
size_of_optional_header: 0,
characteristics: 0,
},
sections: vec![CoffSectionFull {
name: ".text".to_string(),
data: vec![0xCC; 32],
header: CoffSectionHeader {
name: [0; 8],
virtual_size: 32,
virtual_address: 0,
size_of_raw_data: 32,
pointer_to_raw_data: 0,
pointer_to_relocations: 0,
pointer_to_line_numbers: 0,
number_of_relocations: 0,
number_of_line_numbers: 0,
characteristics: IMAGE_SCN_CNT_CODE,
},
section_index: 0,
}],
symbols: vec![],
relocations: vec![],
};
linker.objects.push(obj1);
linker.objects.push(obj2);
linker.merge_coff_sections().unwrap();
let text_sec = linker.sections.iter().find(|s| s.name == ".text");
assert!(text_sec.is_some());
assert_eq!(text_sec.unwrap().data.len(), 96); }
#[test]
fn test_coff_subsystem_values() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
linker.subsystem = 2; let result = linker.emit().unwrap();
let pe_start = 64u32; let opt_hdr_start = pe_start as usize + 4 + 20; let subsystem_offset = opt_hdr_start + 68;
let sub = u16::from_le_bytes([result[subsystem_offset], result[subsystem_offset + 1]]);
assert_eq!(sub, 2);
}
#[test]
fn test_coff_resolve_symbols_defined() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
let obj = CoffObjectFull {
filename: "test.obj".to_string(),
data: vec![],
header: CoffFileHeader {
machine: IMAGE_FILE_MACHINE_AMD64,
number_of_sections: 0,
time_date_stamp: 0,
pointer_to_symbol_table: 0,
number_of_symbols: 0,
size_of_optional_header: 0,
characteristics: 0,
},
sections: vec![],
symbols: vec![CoffSymbolFull {
name: "defined_sym".to_string(),
value: 0x1000,
section_number: 1,
sym_type: 0,
storage_class: 2,
number_of_aux_symbols: 0,
is_external: true,
}],
relocations: vec![],
};
linker.objects.push(obj);
linker.resolve_symbols().unwrap();
let sym = linker.symbols.iter().find(|s| s.name == "defined_sym");
assert!(sym.is_some());
assert_eq!(sym.unwrap().section_index, 1);
assert_eq!(sym.unwrap().value, 0x1000);
}
#[test]
fn test_coff_resolve_symbols_import() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
linker.add_import("external_sym", "kernel32.dll", None, false);
linker.resolve_symbols().unwrap();
let sym = linker.symbols.iter().find(|s| s.name == "external_sym");
assert!(sym.is_some());
assert_eq!(sym.unwrap().section_index, -1); }
#[test]
fn test_macho_all_platforms() {
assert_ne!(PLATFORM_MACOS, PLATFORM_IOS);
assert_ne!(PLATFORM_IOS, PLATFORM_TVOS);
assert_ne!(PLATFORM_TVOS, PLATFORM_WATCHOS);
assert_ne!(PLATFORM_WATCHOS, PLATFORM_BRIDGEOS);
assert_ne!(PLATFORM_BRIDGEOS, PLATFORM_MACCATALYST);
}
#[test]
fn test_macho_cpu_types() {
assert_eq!(CPU_TYPE_X86, 7);
assert_eq!(CPU_TYPE_X86_64, 0x01000007);
assert_eq!(CPU_TYPE_ARM64, 0x0100000c);
}
#[test]
fn test_macho_load_command_types() {
let cmds = [
LC_SEGMENT,
LC_SYMTAB,
LC_DYSYMTAB,
LC_LOAD_DYLIB,
LC_ID_DYLIB,
LC_LOAD_WEAK_DYLIB,
LC_SEGMENT_64,
LC_UUID,
LC_RPATH,
LC_CODE_SIGNATURE,
LC_REEXPORT_DYLIB,
LC_DYLD_INFO,
LC_DYLD_INFO_ONLY,
LC_VERSION_MIN_MACOSX,
LC_VERSION_MIN_IPHONEOS,
LC_FUNCTION_STARTS,
LC_DATA_IN_CODE,
LC_SOURCE_VERSION,
LC_DYLIB_CODE_SIGN_DRS,
LC_LINKER_OPTIMIZATION_HINT,
LC_BUILD_VERSION,
LC_MAIN,
];
for i in 0..cmds.len() {
for j in (i + 1)..cmds.len() {
assert_ne!(cmds[i], cmds[j], "duplicate at indices {} {}", i, j);
}
}
}
#[test]
fn test_macho_emit_with_rpaths() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.add_rpath("@executable_path/../lib");
linker.add_rpath("/usr/local/lib");
let result = linker.emit();
assert!(result.is_ok());
}
#[test]
fn test_macho_emit_with_uuid() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.set_uuid([
0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB,
0xCD, 0xEF,
]);
let result = linker.emit();
assert!(result.is_ok());
}
#[test]
fn test_macho_emit_with_build_version() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.set_build_version(PLATFORM_MACOS, 0x000E0000, 0x000E0000); let result = linker.emit();
assert!(result.is_ok());
}
#[test]
fn test_macho_emit_with_version_min() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.set_version_min(0x000A0D00, 0x000A0D00); let result = linker.emit();
assert!(result.is_ok());
}
#[test]
fn test_macho_emit_with_source_version() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.set_source_version(0x0064000100000000); let result = linker.emit();
assert!(result.is_ok());
}
#[test]
fn test_macho_emit_with_exports() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.add_export("_my_exported_func");
linker.add_export("_my_exported_var");
let result = linker.emit();
assert!(result.is_ok());
}
#[test]
fn test_macho_emit_with_bindings() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.add_dylib("/usr/lib/libc.dylib", false, false);
linker.add_binding(2, 0x100, "_printf", 1, 0);
linker.add_binding(2, 0x108, "_malloc", 1, 0);
let result = linker.emit();
assert!(result.is_ok());
}
#[test]
fn test_macho_emit_with_lazy_bindings() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.add_dylib("/usr/lib/libSystem.dylib", false, false);
linker.add_lazy_binding("_getenv", 1);
linker.add_lazy_binding("_setenv", 1);
let result = linker.emit();
assert!(result.is_ok());
}
#[test]
fn test_macho_emit_dylib_with_install_name() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.set_dylib("@rpath/MyFramework.framework/MyFramework");
linker.add_export("_framework_init");
let result = linker.emit();
assert!(result.is_ok());
}
#[test]
fn test_macho_emit_bundle() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.set_bundle();
linker.add_export("_bundle_main");
let result = linker.emit();
assert!(result.is_ok());
}
#[test]
fn test_macho_flags() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
assert!(linker.pie);
assert!(!linker.allow_stack_execution);
assert!(linker.no_heap_execution);
assert!(linker.twolevel);
}
#[test]
fn test_macho_large_fat_binary() {
let x86 = vec![0xAAu8; 10240];
let arm = vec![0xBBu8; 8192];
let fat = X86MachOLinker::generate_fat_binary(&x86, &arm);
assert_eq!(fat.len(), 8 + 40 + 10240 + 8192);
}
#[test]
fn test_macho_uleb128_edge_cases() {
let test_vals = [
0u64,
1,
127,
128,
255,
256,
16383,
16384,
0xFFFFFFFF,
0xFFFFFFFFFFFFFFFF,
];
for &val in &test_vals {
let mut buf = Vec::new();
X86MachOLinker::write_uleb128(&mut buf, val);
assert!(!buf.is_empty(), "uleb128 encoding failed for {}", val);
}
}
#[test]
fn test_macho_sleb128_edge_cases() {
let test_vals = [
0i64, 1, -1, 127, -127, 128, -128, 16383, -16383, 16384, -16384,
];
for &val in &test_vals {
let mut buf = Vec::new();
X86MachOLinker::write_sleb128(&mut buf, val);
assert!(!buf.is_empty(), "sleb128 encoding failed for {}", val);
}
}
#[test]
fn test_phdr_pf_flags() {
assert_eq!(PF_X, 1);
assert_eq!(PF_W, 2);
assert_eq!(PF_R, 4);
assert_eq!(PF_RW, 6);
assert_eq!(PF_RX, 5);
assert_eq!(PF_RWX, 7);
}
#[test]
fn test_phdr_multiple_load_segments() {
let mut phdrs = X86ELFPhdrs::new();
let segments = vec![
X86OutputSegment {
p_type: PT_LOAD,
p_flags: PF_RX,
p_align: 4096,
p_vaddr: 0x400000,
p_paddr: 0x400000,
p_offset: 0,
p_filesz: 0x2000,
p_memsz: 0x2000,
section_indices: vec![0],
},
X86OutputSegment {
p_type: PT_LOAD,
p_flags: PF_R,
p_align: 4096,
p_vaddr: 0x600000,
p_paddr: 0x600000,
p_offset: 0x2000,
p_filesz: 0x1000,
p_memsz: 0x1000,
section_indices: vec![1],
},
X86OutputSegment {
p_type: PT_LOAD,
p_flags: PF_RW,
p_align: 4096,
p_vaddr: 0x700000,
p_paddr: 0x700000,
p_offset: 0x3000,
p_filesz: 0x1000,
p_memsz: 0x3000,
section_indices: vec![2],
},
];
phdrs.generate(&segments, 0x400000, 4096);
let loads: Vec<_> = phdrs
.entries
.iter()
.filter(|e| e.p_type == PT_LOAD)
.collect();
assert_eq!(loads.len(), 3);
assert_eq!(loads[0].p_flags, PF_RX);
assert_eq!(loads[1].p_flags, PF_R);
assert_eq!(loads[2].p_flags, PF_RW);
}
#[test]
fn test_phdr_custom_page_size() {
let mut phdrs = X86ELFPhdrs::new();
let seg = X86OutputSegment {
p_type: PT_LOAD,
p_flags: PF_RX,
p_align: 65536,
p_vaddr: 0x10000,
p_paddr: 0x10000,
p_offset: 0,
p_filesz: 0x8000,
p_memsz: 0x8000,
section_indices: vec![],
};
phdrs.generate(&[seg], 0x10000, 65536);
let load = phdrs.entries.iter().find(|e| e.p_type == PT_LOAD).unwrap();
assert_eq!(load.p_align, 65536);
}
#[test]
fn test_phdr_all_types_have_entries() {
let types = [
PT_NULL,
PT_LOAD,
PT_DYNAMIC,
PT_INTERP,
PT_NOTE,
PT_SHLIB,
PT_PHDR,
PT_TLS,
PT_GNU_EH_FRAME,
PT_GNU_STACK,
PT_GNU_RELRO,
PT_GNU_PROPERTY,
PT_OPENBSD_RANDOMIZE,
PT_OPENBSD_WXNEEDED,
];
for &t in &types {
assert!(t > 0 || t == 0); }
}
#[test]
fn test_dt_flag_values() {
assert_eq!(DF_ORIGIN, 1);
assert_eq!(DF_SYMBOLIC, 2);
assert_eq!(DF_TEXTREL, 4);
assert_eq!(DF_BIND_NOW, 8);
assert_eq!(DF_STATIC_TLS, 16);
}
#[test]
fn test_dt_flag_1_values() {
assert_eq!(DF_1_NOW, 1);
assert_eq!(DF_1_GLOBAL, 2);
assert_eq!(DF_1_NODELETE, 8);
assert_eq!(DF_1_PIE, 0x08000000);
}
#[test]
fn test_dynamic_entries_empty_emit() {
let de = X86DynamicEntries::new(true);
let data = de.emit();
assert_eq!(data.len(), 16); }
#[test]
fn test_dynamic_entries_empty_emit_32() {
let de = X86DynamicEntries::new(false);
let data = de.emit();
assert_eq!(data.len(), 8); }
#[test]
fn test_dynamic_entries_many_entries() {
let mut de = X86DynamicEntries::new(true);
for i in 0..100 {
de.add(DT_NEEDED, i);
}
let data = de.emit();
assert_eq!(data.len(), 101 * 16); }
#[test]
fn test_dynamic_entries_gnu_hash_large() {
let names: Vec<String> = (0..500).map(|i| format!("symbol_{}", i)).collect();
let data = X86DynamicEntries::build_gnu_hash(&names, 16);
assert!(!data.is_empty());
assert!(data.len() > 16);
}
#[test]
fn test_dynamic_entries_sysv_hash_large() {
let names: Vec<String> = (0..500).map(|i| format!("sym{}", i)).collect();
let (table, nbuckets) = X86DynamicEntries::build_sysv_hash(&names);
assert!(!table.is_empty());
assert!(nbuckets >= 512); }
#[test]
fn test_dynamic_entries_all_tags() {
let tags = [
DT_NULL,
DT_NEEDED,
DT_PLTRELSZ,
DT_PLTGOT,
DT_HASH,
DT_STRTAB,
DT_SYMTAB,
DT_RELA,
DT_RELASZ,
DT_RELAENT,
DT_STRSZ,
DT_SYMENT,
DT_INIT,
DT_FINI,
DT_SONAME,
DT_RPATH,
DT_SYMBOLIC,
DT_REL,
DT_RELSZ,
DT_RELENT,
DT_PLTREL,
DT_DEBUG,
DT_TEXTREL,
DT_JMPREL,
DT_BIND_NOW,
DT_INIT_ARRAY,
DT_FINI_ARRAY,
DT_INIT_ARRAYSZ,
DT_FINI_ARRAYSZ,
DT_RUNPATH,
DT_FLAGS,
DT_PREINIT_ARRAY,
DT_PREINIT_ARRAYSZ,
DT_GNU_HASH,
DT_VERSYM,
DT_FLAGS_1,
DT_VERDEF,
DT_VERDEFNUM,
DT_VERNEED,
DT_VERNEEDNUM,
DT_RELACOUNT,
DT_RELCOUNT,
];
for (i, &tag) in tags.iter().enumerate() {
let mut de = X86DynamicEntries::new(true);
de.add(tag, i as u64);
let data = de.emit();
assert!(!data.is_empty());
}
}
#[test]
fn test_linker_full_new_with_all_formats() {
let e64 = X86LinkerFull::new_elf_x86_64();
let e32 = X86LinkerFull::new_elf_i386();
let c64 = X86LinkerFull::new_coff_x86_64();
let c32 = X86LinkerFull::new_coff_i386();
let m64 = X86LinkerFull::new_macho_x86_64();
assert!(matches!(e64.output_format, X86OutputFormat::Elf64));
assert!(matches!(e32.output_format, X86OutputFormat::Elf32));
assert!(matches!(c64.output_format, X86OutputFormat::Coff64));
assert!(matches!(c32.output_format, X86OutputFormat::Coff32));
assert!(matches!(m64.arch, X86LLDArch::X86_64));
}
#[test]
fn test_linker_full_ir_linker_integration() {
let ir_linker = IRLinker::new(crate::module::Module::new("dest"));
let mut full_linker = X86LinkerFull::new_elf_x86_64();
assert!(ir_linker.diagnostics.is_empty());
assert!(full_linker.diagnostics.is_empty());
}
#[test]
fn test_linker_full_configuration_roundtrip() {
let mut linker = X86LinkerFull::new_elf_x86_64();
linker.set_pic(true);
linker.set_image_base(0x200000);
linker.set_entry("custom_start");
linker.set_output_path("/tmp/output.elf");
linker.add_library_path("/opt/lib");
linker.add_shared_library("pthread");
assert!(linker.is_pic);
assert_eq!(linker.image_base, 0x200000);
assert_eq!(linker.entry_point, "custom_start");
assert_eq!(linker.output_path, "/tmp/output.elf");
assert_eq!(linker.library_search_paths.len(), 1);
assert_eq!(linker.shared_libs.len(), 1);
}
#[test]
fn test_coff_linker_full_section_ordering() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
let obj1 = CoffObjectFull {
filename: "a.obj".to_string(),
data: vec![],
header: CoffFileHeader {
machine: IMAGE_FILE_MACHINE_AMD64,
number_of_sections: 1,
time_date_stamp: 0,
pointer_to_symbol_table: 0,
number_of_symbols: 0,
size_of_optional_header: 0,
characteristics: 0,
},
sections: vec![CoffSectionFull {
name: ".data".to_string(),
data: vec![1, 2, 3],
header: CoffSectionHeader {
name: [0; 8],
virtual_size: 3,
virtual_address: 0,
size_of_raw_data: 3,
pointer_to_raw_data: 0,
pointer_to_relocations: 0,
pointer_to_line_numbers: 0,
number_of_relocations: 0,
number_of_line_numbers: 0,
characteristics: IMAGE_SCN_CNT_INITIALIZED_DATA,
},
section_index: 0,
}],
symbols: vec![],
relocations: vec![],
};
let obj2 = CoffObjectFull {
filename: "b.obj".to_string(),
data: vec![],
header: CoffFileHeader {
machine: IMAGE_FILE_MACHINE_AMD64,
number_of_sections: 1,
time_date_stamp: 0,
pointer_to_symbol_table: 0,
number_of_symbols: 0,
size_of_optional_header: 0,
characteristics: 0,
},
sections: vec![CoffSectionFull {
name: ".text".to_string(),
data: vec![0x90; 10],
header: CoffSectionHeader {
name: [0; 8],
virtual_size: 10,
virtual_address: 0,
size_of_raw_data: 10,
pointer_to_raw_data: 0,
pointer_to_relocations: 0,
pointer_to_line_numbers: 0,
number_of_relocations: 0,
number_of_line_numbers: 0,
characteristics: IMAGE_SCN_CNT_CODE,
},
section_index: 0,
}],
symbols: vec![],
relocations: vec![],
};
linker.objects.push(obj1);
linker.objects.push(obj2);
linker.merge_coff_sections().unwrap();
let text_idx = linker.sections.iter().position(|s| s.name == ".text");
let data_idx = linker.sections.iter().position(|s| s.name == ".data");
assert!(text_idx.is_some());
assert!(data_idx.is_some());
assert!(text_idx.unwrap() < data_idx.unwrap());
}
#[test]
fn test_coff_header_size_64_vs_32() {
let l64 = X86COFFLinkerFull::new(X86LLDArch::X86_64);
let l32 = X86COFFLinkerFull::new(X86LLDArch::I386);
let s64 = l64.compute_header_size();
let s32 = l32.compute_header_size();
assert!(s64 > s32); }
#[test]
fn test_coff_dos_stub_has_pe_pointer() {
let linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
let mut buf = Vec::new();
linker.emit_dos_stub(&mut buf);
assert_eq!(buf.len(), 64);
let pe_offset = u32::from_le_bytes([buf[0x3C], buf[0x3D], buf[0x3E], buf[0x3F]]);
assert_eq!(pe_offset, 64);
}
#[test]
fn test_macho_default_output_type() {
let linker = X86MachOLinker::new(X86LLDArch::X86_64);
assert_eq!(linker.output_type, MH_EXECUTE);
assert!(!linker.is_dylib);
}
#[test]
fn test_macho_emit_has_correct_magic() {
let linker = X86MachOLinker::new(X86LLDArch::X86_64);
let result = linker.emit().unwrap();
let magic = u32::from_le_bytes([result[0], result[1], result[2], result[3]]);
assert_eq!(magic, MH_MAGIC_64);
}
#[test]
fn test_macho_emit_has_load_commands() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.add_dylib("/usr/lib/libc.dylib", false, false);
let result = linker.emit().unwrap();
let ncmds = u32::from_le_bytes([result[16], result[17], result[18], result[19]]);
assert!(ncmds >= 5); }
#[test]
fn test_elf_phdrs_count_consistent() {
let mut phdrs = X86ELFPhdrs::new();
phdrs.has_eh_frame = true;
phdrs.has_relro = true;
phdrs.has_gnu_property = true;
phdrs.has_tls = true;
phdrs.generate(&[], 0x400000, 4096);
let emit_64 = phdrs.emit_64();
let emit_32 = phdrs.emit_32();
assert_eq!(emit_64.len() / 56, emit_32.len() / 32); }
#[test]
fn test_dynamic_entries_roundtrip_tags() {
let tags = [
DT_NEEDED,
DT_INIT,
DT_FINI,
DT_HASH,
DT_GNU_HASH,
DT_FLAGS,
DT_FLAGS_1,
DT_VERSYM,
DT_VERDEF,
DT_VERNEED,
];
let mut de = X86DynamicEntries::new(true);
for &t in &tags {
de.add(t, t + 100);
}
let data = de.emit();
for i in 0..tags.len() {
let off = i * 16;
let tag = u64::from_le_bytes([
data[off],
data[off + 1],
data[off + 2],
data[off + 3],
data[off + 4],
data[off + 5],
data[off + 6],
data[off + 7],
]);
assert_eq!(tag, tags[i]);
}
}
#[test]
fn test_coff_parse_empty_file() {
let linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
let result = linker.parse_coff_object_full("empty.obj", &[]);
assert!(result.is_err());
}
#[test]
fn test_coff_parse_truncated_header() {
let linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
let data = vec![0u8; 10];
let result = linker.parse_coff_object_full("trunc.obj", &data);
assert!(result.is_err());
}
#[test]
fn test_phdr_emit_idempotent() {
let mut phdrs = X86ELFPhdrs::new();
phdrs.generate(&[], 0x400000, 4096);
let d1 = phdrs.emit_64();
let d2 = phdrs.emit_64();
assert_eq!(d1, d2);
}
#[test]
fn test_coff_find_section_rva_missing() {
let linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
assert_eq!(linker.find_section_rva(".nonexistent"), 0);
}
#[test]
fn test_coff_find_symbol_rva_missing() {
let linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
assert!(linker.find_symbol_rva("nonexistent").is_none());
}
#[test]
fn test_macho_segment_command_size() {
let linker = X86MachOLinker::new(X86LLDArch::X86_64);
let cmd = linker.build_segment_command("__TEXT", 0, 0, 0, 0, 7, 5, &[], true);
assert_eq!(cmd.len(), 72);
let sec = MachOSection64 {
sectname: [0u8; 16],
segname: [0u8; 16],
addr: 0,
size: 0,
offset: 0,
align: 0,
reloff: 0,
nreloc: 0,
flags: 0,
reserved1: 0,
reserved2: 0,
reserved3: 0,
};
let cmd2 = linker.build_segment_command("__DATA", 0, 0, 0, 0, 7, 3, &[sec], true);
assert_eq!(cmd2.len(), 152);
}
#[test]
fn test_import_library_generation() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
linker.add_import("CreateFileW", "kernel32.dll", None, false);
linker.add_import("ReadFile", "kernel32.dll", None, false);
linker.add_import("MessageBoxW", "user32.dll", None, false);
let lib = linker.generate_import_library();
assert!(!lib.is_empty());
assert_eq!(&lib[0..8], b"!<arch>\n");
}
#[test]
fn test_coff_import_library_multiple_dlls() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
linker.add_import("func_a", "a.dll", None, false);
linker.add_import("func_b", "b.dll", None, false);
linker.add_import("func_c", "c.dll", None, true);
let lib = linker.generate_import_library();
assert!(!lib.is_empty());
}
#[test]
fn test_coff_export_data_structure() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
for i in 0..10 {
linker.add_export(&format!("export_{}", i), i as u16, (i * 0x100) as u32);
}
let export_data = linker.generate_exports();
assert!(export_data.len() >= 40);
}
#[test]
fn test_coff_delay_import_multiple() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
linker.add_delay_import("comctl32.dll");
linker.add_delay_import("shell32.dll");
linker.add_delay_import("ole32.dll");
let data = linker.generate_delay_import_table();
assert!(!data.is_empty());
assert!(data.len() >= 3 * 32 + 32);
}
#[test]
fn test_coff_bound_import_table_multiple() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
linker.add_bound_import("ntdll.dll", 0x1111);
linker.add_bound_import("kernel32.dll", 0x2222);
linker.add_bound_import("advapi32.dll", 0x3333);
let data = linker.generate_bound_import_table();
assert!(!data.is_empty());
assert_eq!(&data[data.len() - 6..], &[0u8; 6]);
}
#[test]
fn test_coff_base_reloc_multiple_blocks() {
let blocks = vec![
BaseRelocationBlock {
page_rva: 0x1000,
entries: vec![
BaseRelocationEntry {
offset: 0x100,
rel_type: IMAGE_REL_BASED_DIR64,
},
BaseRelocationEntry {
offset: 0x108,
rel_type: IMAGE_REL_BASED_DIR64,
},
],
},
BaseRelocationBlock {
page_rva: 0x2000,
entries: vec![BaseRelocationEntry {
offset: 0x200,
rel_type: IMAGE_REL_BASED_HIGHLOW,
}],
},
BaseRelocationBlock {
page_rva: 0x3000,
entries: vec![
BaseRelocationEntry {
offset: 0x300,
rel_type: IMAGE_REL_BASED_DIR64,
},
BaseRelocationEntry {
offset: 0x308,
rel_type: IMAGE_REL_BASED_DIR64,
},
BaseRelocationEntry {
offset: 0x310,
rel_type: IMAGE_REL_BASED_HIGHLOW,
},
],
},
];
let data = X86COFFLinkerFull::new(X86LLDArch::X86_64).generate_base_relocations(&blocks);
assert!(!data.is_empty());
}
#[test]
fn test_coff_debug_directory_multiple_entries() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
linker.add_debug_entry(IMAGE_DEBUG_TYPE_CODEVIEW, b"RSDSabcdefgh");
linker.add_debug_entry(IMAGE_DEBUG_TYPE_COFF, b"COFFDEBUG");
linker.add_debug_entry(IMAGE_DEBUG_TYPE_FPO, b"FPO");
let data = linker.generate_debug_directory();
assert_eq!(data.len(), 3 * 28);
}
#[test]
fn test_coff_tls_directory_zero_fill() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
linker.set_tls_data(TlsDirectoryData {
start_address_of_raw_data: 0x10000,
end_address_of_raw_data: 0x11000,
address_of_index: 0x12000,
address_of_callbacks: 0x13000,
size_of_zero_fill: 256,
characteristics: 0x10,
});
let data = linker.generate_tls_directory();
assert_eq!(data.len(), 40);
}
#[test]
fn test_coff_load_config_full() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
let lc = LoadConfigData {
size: 256,
time_date_stamp: 0xABCDEF01,
major_version: 1,
minor_version: 0,
global_flags_clear: 0x1000,
global_flags_set: 0x2000,
critical_section_default_timeout: 10000,
decommit_free_block_threshold: 0x1000,
decommit_total_free_threshold: 0x10000,
lock_prefix_table: 0x50000,
maximum_allocation_size: 0x1000000,
virtual_memory_threshold: 0x8000000,
process_affinity_mask: 0xFFFF,
process_heap_flags: 3,
csd_version: 0,
dependent_load_flags: 0x800,
edit_list: 0x60000,
security_cookie: 0xDEADBEEFCAFE0000,
se_handler_table: 0x70000,
se_handler_count: 4,
guard_cf_check_function_pointer: 0x80000,
guard_cf_dispatch_function_pointer: 0x81000,
guard_cf_function_table: 0x82000,
guard_cf_function_count: 64,
guard_flags: 0x100,
};
linker.set_load_config(lc);
let data = linker.generate_load_config();
assert!(!data.is_empty());
}
#[test]
fn test_coff_exception_table_large() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
for i in 0..100 {
linker.add_exception_entry(ExceptionTableEntry {
begin_address: 0x1000 + i * 0x100,
end_address: 0x1100 + i * 0x100,
unwind_info_address: 0x2000 + i * 0x100,
});
}
let data = linker.generate_exception_table();
assert_eq!(data.len(), 100 * 12);
}
#[test]
fn test_macho_bind_opcodes_multiple_dylibs() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.add_dylib("/usr/lib/libA.dylib", false, false);
linker.add_dylib("/usr/lib/libB.dylib", false, false);
linker.add_binding(2, 0x1000, "_funcA", 1, 0);
linker.add_binding(2, 0x1008, "_funcB", 2, 0);
linker.add_binding(2, 0x1010, "_funcC", 1, 8);
let data = linker.generate_bind_opcodes();
assert!(!data.is_empty());
assert_eq!(data[data.len() - 1], BIND_OPCODE_DONE);
}
#[test]
fn test_macho_weak_bind_opcodes_multiple() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.add_weak_binding(1, 0x100, "_opt1");
linker.add_weak_binding(1, 0x108, "_opt2");
linker.add_weak_binding(2, 0x200, "_opt3");
let data = linker.generate_weak_bind_opcodes();
assert!(!data.is_empty());
}
#[test]
fn test_macho_lazy_bind_opcodes_multiple() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.add_dylib("/usr/lib/libSystem.dylib", false, false);
linker.add_lazy_binding("_printf", 1);
linker.add_lazy_binding("_scanf", 1);
linker.add_lazy_binding("_malloc", 1);
linker.add_lazy_binding("_free", 1);
let data = linker.generate_lazy_bind_opcodes();
assert!(!data.is_empty());
}
#[test]
fn test_macho_export_trie_large() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
for i in 0..50 {
linker.add_export(&format!("symbol_{}", i));
}
let data = linker.generate_export_trie();
assert!(!data.is_empty());
}
#[test]
fn test_dynamic_entries_max_needed() {
let mut de = X86DynamicEntries::new(true);
for i in 0..500 {
de.add_needed(i * 100);
}
let data = de.emit();
assert_eq!(data.len(), 501 * 16);
}
#[test]
fn test_dynamic_entries_sysv_hash_singleton() {
let names = vec!["only_sym".to_string()];
let (table, nbuckets) = X86DynamicEntries::build_sysv_hash(&names);
assert_eq!(nbuckets, 1);
assert_eq!(table[0], 1);
assert_eq!(table[1], 1);
}
#[test]
fn test_dynamic_entries_tag_names_all() {
let all = [
(DT_NULL, "DT_NULL"),
(DT_NEEDED, "DT_NEEDED"),
(DT_PLTRELSZ, "DT_PLTRELSZ"),
(DT_PLTGOT, "DT_PLTGOT"),
(DT_HASH, "DT_HASH"),
(DT_STRTAB, "DT_STRTAB"),
(DT_SYMTAB, "DT_SYMTAB"),
(DT_RELA, "DT_RELA"),
(DT_RELASZ, "DT_RELASZ"),
(DT_RELAENT, "DT_RELAENT"),
(DT_STRSZ, "DT_STRSZ"),
(DT_SYMENT, "DT_SYMENT"),
(DT_INIT, "DT_INIT"),
(DT_FINI, "DT_FINI"),
(DT_SONAME, "DT_SONAME"),
(DT_RPATH, "DT_RPATH"),
(DT_SYMBOLIC, "DT_SYMBOLIC"),
(DT_REL, "DT_REL"),
(DT_RELSZ, "DT_RELSZ"),
(DT_RELENT, "DT_RELENT"),
(DT_PLTREL, "DT_PLTREL"),
(DT_DEBUG, "DT_DEBUG"),
(DT_TEXTREL, "DT_TEXTREL"),
(DT_JMPREL, "DT_JMPREL"),
(DT_BIND_NOW, "DT_BIND_NOW"),
(DT_INIT_ARRAY, "DT_INIT_ARRAY"),
(DT_FINI_ARRAY, "DT_FINI_ARRAY"),
(DT_INIT_ARRAYSZ, "DT_INIT_ARRAYSZ"),
(DT_FINI_ARRAYSZ, "DT_FINI_ARRAYSZ"),
(DT_RUNPATH, "DT_RUNPATH"),
(DT_FLAGS, "DT_FLAGS"),
(DT_PREINIT_ARRAY, "DT_PREINIT_ARRAY"),
(DT_PREINIT_ARRAYSZ, "DT_PREINIT_ARRAYSZ"),
(DT_GNU_HASH, "DT_GNU_HASH"),
(DT_VERSYM, "DT_VERSYM"),
(DT_FLAGS_1, "DT_FLAGS_1"),
(DT_VERDEF, "DT_VERDEF"),
(DT_VERDEFNUM, "DT_VERDEFNUM"),
(DT_VERNEED, "DT_VERNEED"),
(DT_VERNEEDNUM, "DT_VERNEEDNUM"),
(DT_RELACOUNT, "DT_RELACOUNT"),
(DT_RELCOUNT, "DT_RELCOUNT"),
];
for (tag, expected) in &all {
let e = X86DynamicEntry {
d_tag: *tag,
d_val: 0,
d_ptr: 0,
};
assert_eq!(e.tag_name(), *expected, "Mismatch for tag {}", tag);
}
}
#[test]
fn test_linker_full_many_shared_libs() {
let mut linker = X86LinkerFull::new_elf_x86_64();
for i in 0..100 {
linker.add_shared_library(&format!("lib{}.so", i));
}
assert_eq!(linker.shared_libs.len(), 100);
}
#[test]
fn test_linker_full_many_imports() {
let mut linker = X86LinkerFull::new_coff_x86_64();
for i in 0..200 {
linker.add_coff_import(&format!("sym{}", i), "kernel32.dll", Some(i as u16), false);
}
assert_eq!(linker.imports.len(), 200);
}
#[test]
fn test_linker_full_many_exports() {
let mut linker = X86LinkerFull::new_coff_x86_64();
for i in 0..200 {
linker.add_coff_export(&format!("export{}", i), i as u16, (i * 4) as u32);
}
assert_eq!(linker.exports.len(), 200);
}
#[test]
fn test_phdr_generate_no_segments() {
let mut phdrs = X86ELFPhdrs::new();
phdrs.generate(&[], 0x400000, 4096);
assert!(!phdrs.entries.is_empty());
}
#[test]
fn test_phdr_generate_many_segments() {
let mut phdrs = X86ELFPhdrs::new();
let mut segments = Vec::new();
for i in 0..10 {
segments.push(X86OutputSegment {
p_type: PT_LOAD,
p_flags: PF_R,
p_align: 4096,
p_vaddr: 0x400000 + i * 0x10000,
p_paddr: 0x400000 + i * 0x10000,
p_offset: i * 0x1000,
p_filesz: 0x1000,
p_memsz: 0x1000,
section_indices: vec![i as u32],
});
}
phdrs.generate(&segments, 0x400000, 4096);
let loads: Vec<_> = phdrs
.entries
.iter()
.filter(|e| e.p_type == PT_LOAD)
.collect();
assert_eq!(loads.len(), 10);
}
#[test]
fn test_x86_64_reloc_types() {
assert_eq!(R_X86_64_NONE, 0);
assert_eq!(R_X86_64_64, 1);
assert_eq!(R_X86_64_PC32, 2);
assert_eq!(R_X86_64_32, 10);
assert_eq!(R_X86_64_32S, 11);
}
#[test]
fn test_i386_reloc_types() {
assert_eq!(R_386_NONE, 0);
assert_eq!(R_386_32, 1);
assert_eq!(R_386_PC32, 2);
assert_eq!(R_386_GOT32, 3);
assert_eq!(R_386_PLT32, 4);
}
#[test]
fn test_coff_reloc_types_amd64() {
assert_eq!(IMAGE_REL_AMD64_ABSOLUTE, 0);
assert_eq!(IMAGE_REL_AMD64_ADDR64, 1);
assert_eq!(IMAGE_REL_AMD64_ADDR32, 2);
assert_eq!(IMAGE_REL_AMD64_REL32, 4);
assert_eq!(IMAGE_REL_AMD64_REL32_1, 5);
assert_eq!(IMAGE_REL_AMD64_REL32_2, 6);
assert_eq!(IMAGE_REL_AMD64_REL32_3, 7);
assert_eq!(IMAGE_REL_AMD64_REL32_4, 8);
assert_eq!(IMAGE_REL_AMD64_REL32_5, 9);
}
#[test]
fn test_coff_reloc_types_i386() {
assert_eq!(IMAGE_REL_I386_ABSOLUTE, 0);
assert_eq!(IMAGE_REL_I386_DIR32, 6);
assert_eq!(IMAGE_REL_I386_REL32, 20);
}
#[test]
fn test_empty_objects_no_panic() {
let linker = X86LinkerFull::new_elf_x86_64();
assert!(linker.objects.is_empty());
assert!(linker.got_entries.is_empty());
assert!(linker.plt_entries.is_empty());
}
#[test]
fn test_build_id_kinds() {
let kinds = [
X86BuildIdKind::None,
X86BuildIdKind::Sha1,
X86BuildIdKind::Md5,
X86BuildIdKind::Uuid,
X86BuildIdKind::HexString,
X86BuildIdKind::Fast,
];
assert_eq!(kinds.len(), 6);
}
#[test]
fn test_hash_styles() {
let styles = [X86HashStyle::SysV, X86HashStyle::Gnu, X86HashStyle::Both];
assert_eq!(styles.len(), 3);
}
#[test]
fn test_output_formats() {
let formats = [
X86OutputFormat::Elf32,
X86OutputFormat::Elf64,
X86OutputFormat::Coff32,
X86OutputFormat::Coff64,
];
assert_eq!(formats.len(), 4);
}
#[test]
fn test_diag_levels() {
let levels = [
X86DiagLevel::Info,
X86DiagLevel::Warning,
X86DiagLevel::Error,
];
assert_eq!(levels.len(), 3);
}
#[test]
fn test_section_type_to_characteristics_all() {
let map = [
(
".text",
IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ,
),
(
".code",
IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ,
),
(
".rdata",
IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ,
),
(
".rodata",
IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ,
),
(
".edata",
IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ,
),
(
".idata",
IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ,
),
(
".pdata",
IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ,
),
(
".xdata",
IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ,
),
(
".data",
IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE,
),
(
".bss",
IMAGE_SCN_CNT_UNINITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE,
),
(".rsrc", IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ),
(
".reloc",
IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_DISCARDABLE,
),
(
".debug",
IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_DISCARDABLE,
),
(
".tls",
IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE,
),
];
for (name, expected) in &map {
let got = X86COFFLinkerFull::section_name_to_characteristics(name);
assert_eq!(got, *expected, "Mismatch for {}", name);
}
}
#[test]
fn test_coff_data_directory_order() {
assert_eq!(IMAGE_DIRECTORY_ENTRY_EXPORT, 0);
assert_eq!(IMAGE_DIRECTORY_ENTRY_IMPORT, 1);
assert_eq!(IMAGE_DIRECTORY_ENTRY_RESOURCE, 2);
assert_eq!(IMAGE_DIRECTORY_ENTRY_EXCEPTION, 3);
assert_eq!(IMAGE_DIRECTORY_ENTRY_SECURITY, 4);
assert_eq!(IMAGE_DIRECTORY_ENTRY_BASERELOC, 5);
assert_eq!(IMAGE_DIRECTORY_ENTRY_DEBUG, 6);
assert_eq!(IMAGE_DIRECTORY_ENTRY_ARCHITECTURE, 7);
assert_eq!(IMAGE_DIRECTORY_ENTRY_GLOBALPTR, 8);
assert_eq!(IMAGE_DIRECTORY_ENTRY_TLS, 9);
assert_eq!(IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG, 10);
assert_eq!(IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT, 11);
assert_eq!(IMAGE_DIRECTORY_ENTRY_IAT, 12);
assert_eq!(IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT, 13);
assert_eq!(IMAGE_NUMBEROF_DIRECTORY_ENTRIES, 16);
}
#[test]
fn test_coff_dll_characteristics() {
assert_eq!(DLL_NX_COMPAT, 0x0100);
assert_eq!(DLL_DYNAMIC_BASE, 0x0040);
assert_eq!(DLL_TERMINAL_SERVER_AWARE, 0x8000);
assert_eq!(DLL_HIGH_ENTROPY_VA, 0x0020);
}
#[test]
fn test_pe_constants() {
assert_eq!(PE32PLUS_MAGIC, 0x020b);
assert_eq!(PE32_MAGIC, 0x010b);
assert_eq!(IMAGE_FILE_MACHINE_AMD64, 0x8664);
assert_eq!(IMAGE_FILE_MACHINE_I386, 0x014c);
}
#[test]
fn test_elf_magic_bytes() {
assert_eq!(ELF_MAGIC_BYTES, [0x7f, b'E', b'L', b'F']);
}
#[test]
fn test_page_sizes() {
assert_eq!(X86_64_PAGE_SIZE, 4096);
assert!(X86_64_DEFAULT_IMAGE_BASE > 0);
assert!(I386_DEFAULT_IMAGE_BASE > 0);
}
#[test]
fn test_section_header_sizes() {
assert_eq!(CoffSectionHeader::SIZE, 40);
assert_eq!(CoffFileHeader::SIZE, 20);
assert_eq!(CoffSymbol::SIZE, 18);
assert_eq!(PE32PlusOptionalHeader::SIZE, 112);
assert_eq!(PE32OptionalHeader::SIZE, 96);
assert_eq!(ImageDataDirectory::SIZE, 8);
}
#[test]
fn test_elf_header_sizes() {
assert_eq!(ELF64_EHDR_SIZE, 64);
assert_eq!(ELF64_PHDR_SIZE, 56);
assert_eq!(ELF64_SHDR_SIZE, 64);
}
#[test]
fn test_plt_got_sizes() {
assert_eq!(GOT_ENTRY_SIZE, 8);
assert!(PLT_ENTRY_SIZE > 0);
assert!(PLT0_SIZE > 0);
}
#[test]
fn test_coff_alignment_constants() {
assert!(COFF_FILE_ALIGNMENT > 0);
assert!(COFF_SECTION_ALIGNMENT > 0);
}
#[test]
fn test_macho_magic_constants() {
assert_eq!(FAT_MAGIC, 0xcafebabe);
assert_eq!(FAT_MAGIC_64, 0xcafebabf);
assert_eq!(MH_MAGIC, 0xfeedface);
assert_eq!(MH_MAGIC_64, 0xfeedfacf);
assert_eq!(MH_CIGAM, 0xcefaedfe);
assert_eq!(MH_CIGAM_64, 0xcffaedfe);
}
#[test]
fn test_macho_file_types_all() {
let types = [
MH_OBJECT,
MH_EXECUTE,
MH_FVMLIB,
MH_CORE,
MH_PRELOAD,
MH_DYLIB,
MH_DYLINKER,
MH_BUNDLE,
MH_DYLIB_STUB,
MH_DSYM,
MH_KEXT_BUNDLE,
];
for i in 0..types.len() {
for j in (i + 1)..types.len() {
assert_ne!(types[i], types[j]);
}
}
}
#[test]
fn test_bind_opcode_constants() {
assert_eq!(BIND_OPCODE_DONE, 0x00);
assert_eq!(BIND_OPCODE_SET_DYLIB_ORDINAL_IMM, 0x10);
assert_eq!(BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB, 0x20);
assert_eq!(BIND_OPCODE_SET_DYLIB_SPECIAL_IMM, 0x30);
assert_eq!(BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM, 0x40);
assert_eq!(BIND_OPCODE_SET_TYPE_IMM, 0x50);
assert_eq!(BIND_OPCODE_SET_ADDEND_SLEB, 0x60);
assert_eq!(BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB, 0x70);
assert_eq!(BIND_OPCODE_ADD_ADDR_ULEB, 0x80);
assert_eq!(BIND_OPCODE_DO_BIND, 0x90);
assert_eq!(BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB, 0xA0);
assert_eq!(BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED, 0xB0);
assert_eq!(BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB, 0xC0);
}
#[test]
fn test_export_symbol_flags() {
assert_eq!(EXPORT_SYMBOL_FLAGS_KIND_MASK, 0x03);
assert_eq!(EXPORT_SYMBOL_FLAGS_KIND_REGULAR, 0x00);
assert_eq!(EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL, 0x01);
assert_eq!(EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE, 0x02);
assert_eq!(EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION, 0x04);
assert_eq!(EXPORT_SYMBOL_FLAGS_REEXPORT, 0x08);
assert_eq!(EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER, 0x10);
}
#[test]
fn test_compute_fnv1a_hash_helper() {
let h = compute_fnv1a_hash(b"test");
assert_ne!(h, 0);
let h2 = compute_fnv1a_hash(b"test");
assert_eq!(h, h2);
let h3 = compute_fnv1a_hash(b"other");
assert_ne!(h, h3);
}
#[test]
fn test_gnu_hash_bytes() {
let bytes = gnu_hash_bytes(b"test_symbol");
assert!(!bytes.is_empty());
}
#[test]
fn test_align_up_helper() {
assert_eq!(align_up(0, 4), 0);
assert_eq!(align_up(1, 4), 4);
assert_eq!(align_up(4, 4), 4);
assert_eq!(align_up(5, 4), 8);
assert_eq!(align_up(4095, 4096), 4096);
assert_eq!(align_up(4096, 4096), 4096);
}
#[test]
fn test_align_down_helper() {
assert_eq!(align_down(0, 4), 0);
assert_eq!(align_down(1, 4), 0);
assert_eq!(align_down(4, 4), 4);
assert_eq!(align_down(5, 4), 4);
assert_eq!(align_down(4095, 4096), 0);
assert_eq!(align_down(4096, 4096), 4096);
}
#[test]
fn test_next_power_of_two_helper() {
assert_eq!(next_power_of_two(0), 1);
assert_eq!(next_power_of_two(1), 1);
assert_eq!(next_power_of_two(2), 2);
assert_eq!(next_power_of_two(3), 4);
assert_eq!(next_power_of_two(5), 8);
assert_eq!(next_power_of_two(500), 512);
assert_eq!(next_power_of_two(1000), 1024);
}
#[test]
fn test_wildcard_match_helper() {
assert!(wildcard_match("*.o", "file.o"));
assert!(wildcard_match("file.*", "file.o"));
assert!(wildcard_match("*.?", "file.o"));
assert!(!wildcard_match("*.a", "file.o"));
assert!(wildcard_match("*", "anything"));
}
#[test]
fn test_visibility_to_elf() {
let v = visibility_to_elf(&X86SymbolVisibility::Default);
assert_eq!(v, 0);
let v2 = visibility_to_elf(&X86SymbolVisibility::Hidden);
assert_eq!(v2, 2);
let v3 = visibility_to_elf(&X86SymbolVisibility::Internal);
assert_eq!(v3, 1);
let v4 = visibility_to_elf(&X86SymbolVisibility::Protected);
assert_eq!(v4, 3);
}
#[test]
fn test_read_u16_le_helper() {
let data = [0x34, 0x12, 0x00, 0x00];
assert_eq!(read_u16_le(&data, 0), 0x1234);
}
#[test]
fn test_read_u32_le_helper() {
let data = [0x78, 0x56, 0x34, 0x12, 0x00];
assert_eq!(read_u32_le(&data, 0), 0x12345678);
}
#[test]
fn test_read_u64_le_helper() {
let data = [0xEF, 0xCD, 0xAB, 0x90, 0x78, 0x56, 0x34, 0x12, 0x00];
assert_eq!(read_u64_le(&data, 0), 0x1234567890ABCDEF);
}
#[test]
fn test_get_c_string_helper() {
let data = b"hello\0world";
assert_eq!(get_c_string(data, 0), Some("hello".to_string()));
assert_eq!(get_c_string(data, 6), Some("world".to_string()));
assert_eq!(get_c_string(data, 100), None);
}
#[test]
fn test_hash_sha1_helper() {
let h = hash_sha1(b"test data");
assert_eq!(h.len(), 20);
}
#[test]
fn test_hash_md5_helper() {
let h = hash_md5(b"test data");
assert_eq!(h.len(), 16);
}
#[test]
fn test_uuid_v4_helper() {
let uuid = uuid_v4();
assert_eq!(uuid.len(), 16);
assert_eq!((uuid[6] >> 4) & 0x0F, 4);
}
#[test]
fn test_hash_fast_helper() {
let h = hash_fast(b"data");
assert!(h.len() > 0);
}
#[test]
fn test_linker_diagnostic_creation() {
let d = X86LinkerDiagnostic {
level: X86DiagLevel::Warning,
message: "test".to_string(),
};
assert!(matches!(d.level, X86DiagLevel::Warning));
assert_eq!(d.message, "test");
}
#[test]
fn test_linker_diagnostic_info_error() {
let mut linker = X86LinkerFull::new_elf_x86_64();
linker.diag(X86DiagLevel::Info, "info msg");
linker.diag(X86DiagLevel::Error, "error msg");
assert_eq!(linker.diagnostics.len(), 2);
assert!(matches!(linker.diagnostics[0].level, X86DiagLevel::Info));
assert!(matches!(linker.diagnostics[1].level, X86DiagLevel::Error));
}
#[test]
fn test_coff_linker_emit_twice_consistent() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
linker.link().unwrap();
let r1 = linker.emit().unwrap();
let r2 = linker.emit().unwrap();
assert_eq!(r1, r2);
}
#[test]
fn test_macho_emit_twice_consistent() {
let linker = X86MachOLinker::new(X86LLDArch::X86_64);
let r1 = linker.emit().unwrap();
let r2 = linker.emit().unwrap();
assert_eq!(r1, r2);
}
#[test]
fn test_elf_phdr_emit_twice_consistent() {
let mut phdrs = X86ELFPhdrs::new();
phdrs.generate(&[], 0x400000, 4096);
let d1 = phdrs.emit_64();
let d2 = phdrs.emit_64();
assert_eq!(d1, d2);
}
#[test]
fn test_dynamic_entries_emit_twice_consistent() {
let mut de = X86DynamicEntries::new(true);
de.add_needed(1);
de.add_init(0x1000);
let d1 = de.emit();
let d2 = de.emit();
assert_eq!(d1, d2);
}
#[test]
fn test_coff_linker_emit_64_with_all_features() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
linker.imports.push(CoffImportFull {
symbol_name: "malloc".into(),
dll_name: "msvcrt.dll".into(),
ordinal: None,
is_data: false,
});
linker.exports.push(CoffExportFull {
name: "MyFunc".into(),
ordinal: 1,
rva: 0x2000,
forwarder: None,
});
linker.delay_imports.push(DelayImportDescriptor {
dll_name: "shell32.dll".into(),
module_handle_rva: 0,
import_address_table_rva: 0,
import_name_table_rva: 0,
bound_iat_rva: 0,
unload_iat_rva: 0,
timestamp: 0,
});
linker.bound_imports.push(BoundImportEntry {
name: "ntdll.dll".into(),
timestamp: 0x12345678,
offset_module_name: 0,
number_of_module_forwarder_refs: 0,
});
linker.debug_entries.push(DebugDirectoryEntry {
characteristics: 0,
time_date_stamp: 0,
major_version: 0,
minor_version: 0,
debug_type: IMAGE_DEBUG_TYPE_CODEVIEW,
size_of_data: 16,
address_of_raw_data: 0,
pointer_to_raw_data: 0,
});
linker.tls_data = Some(TlsDirectoryData {
start_address_of_raw_data: 0x10000,
end_address_of_raw_data: 0x11000,
address_of_index: 0x12000,
address_of_callbacks: 0,
size_of_zero_fill: 32,
characteristics: 0,
});
linker.has_tls = true;
linker.load_config = Some(LoadConfigData {
size: 256,
time_date_stamp: 0,
major_version: 0,
minor_version: 0,
global_flags_clear: 0,
global_flags_set: 0,
critical_section_default_timeout: 0,
decommit_free_block_threshold: 0x1000,
decommit_total_free_threshold: 0x10000,
lock_prefix_table: 0,
maximum_allocation_size: 0x100000,
virtual_memory_threshold: 0,
process_affinity_mask: 0,
process_heap_flags: 0,
csd_version: 0,
dependent_load_flags: 0,
edit_list: 0,
security_cookie: 0,
se_handler_table: 0,
se_handler_count: 0,
guard_cf_check_function_pointer: 0,
guard_cf_dispatch_function_pointer: 0,
guard_cf_function_table: 0,
guard_cf_function_count: 0,
guard_flags: 0,
});
linker.resource_data = vec![0, 0, 0, 0, 1, 0, 0, 0];
linker.has_resource = true;
linker.exception_data.push(ExceptionTableEntry {
begin_address: 0x1000,
end_address: 0x1100,
unwind_info_address: 0x2000,
});
linker.has_exception = true;
let result = linker.emit().unwrap();
assert!(!result.is_empty());
assert_eq!(&result[0..2], b"MZ");
}
#[test]
fn test_coff_optional_header_64_has_magic() {
let linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
let mut buf = Vec::new();
linker.emit_dos_stub(&mut buf);
buf.extend_from_slice(b"PE\0\0");
buf.extend_from_slice(&[0u8; 20]);
let before = buf.len();
linker.emit_optional_header_64(&mut buf, 4096, 512);
assert!(buf.len() > before);
let magic = u16::from_le_bytes([buf[before], buf[before + 1]]);
assert_eq!(magic, PE32PLUS_MAGIC);
}
#[test]
fn test_coff_optional_header_32_has_magic() {
let linker = X86COFFLinkerFull::new(X86LLDArch::I386);
let mut buf = Vec::new();
linker.emit_dos_stub(&mut buf);
buf.extend_from_slice(b"PE\0\0");
buf.extend_from_slice(&[0u8; 20]);
let before = buf.len();
linker.emit_optional_header_32(&mut buf, 4096, 512);
assert!(buf.len() > before);
let magic = u16::from_le_bytes([buf[before], buf[before + 1]]);
assert_eq!(magic, PE32_MAGIC);
}
#[test]
fn test_coff_data_directories_section_count() {
let linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
let mut buf = Vec::new();
for _ in 0..(PE32PlusOptionalHeader::SIZE / 8) {
buf.push(0u8);
}
let before = buf.len();
linker.emit_data_directories(&mut buf);
assert_eq!(
buf.len() - before,
IMAGE_NUMBEROF_DIRECTORY_ENTRIES * ImageDataDirectory::SIZE
);
}
#[test]
fn test_coff_section_headers_64() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
linker.sections.push(CoffOutputSectionFull {
name: ".text".into(),
data: vec![0x90; 64],
characteristics: IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ,
virtual_address: 0x1000,
virtual_size: 64,
raw_offset: 0x200,
align: 16,
});
linker.sections.push(CoffOutputSectionFull {
name: ".data".into(),
data: vec![0; 32],
characteristics: IMAGE_SCN_CNT_INITIALIZED_DATA
| IMAGE_SCN_MEM_READ
| IMAGE_SCN_MEM_WRITE,
virtual_address: 0x2000,
virtual_size: 32,
raw_offset: 0x300,
align: 16,
});
let mut buf = Vec::new();
linker.emit_section_headers_full(&mut buf, 512);
assert_eq!(buf.len(), 2 * CoffSectionHeader::SIZE);
}
#[test]
fn test_coff_find_section_rva_by_name() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
linker.sections.push(CoffOutputSectionFull {
name: ".text".into(),
data: vec![],
characteristics: 0,
virtual_address: 0x1000,
virtual_size: 0,
raw_offset: 0,
align: 16,
});
linker.sections.push(CoffOutputSectionFull {
name: ".rdata".into(),
data: vec![],
characteristics: 0,
virtual_address: 0x2000,
virtual_size: 0,
raw_offset: 0,
align: 16,
});
linker.section_map.insert(".text".into(), 0);
linker.section_map.insert(".rdata".into(), 1);
assert_eq!(linker.find_section_rva(".text"), 0x1000);
assert_eq!(linker.find_section_rva(".rdata"), 0x2000);
assert_eq!(linker.find_section_rva(".bss"), 0);
}
#[test]
fn test_coff_find_entry_rva_with_sections() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
linker.sections.push(CoffOutputSectionFull {
name: ".text".into(),
data: vec![0x90; 16],
characteristics: IMAGE_SCN_CNT_CODE,
virtual_address: 0x1000,
virtual_size: 16,
raw_offset: 0x400,
align: 16,
});
linker.section_map.insert(".text".into(), 0);
assert_eq!(linker.find_entry_rva(), 0x1000);
}
#[test]
fn test_coff_find_symbol_rva_by_name() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
linker.symbols.push(CoffResolvedSymbolFull {
name: "_main".into(),
value: 0x5000,
section_index: 1,
is_external: true,
is_weak: false,
});
linker.symbols.push(CoffResolvedSymbolFull {
name: "_data".into(),
value: 0x8000,
section_index: 3,
is_external: false,
is_weak: false,
});
assert_eq!(linker.find_symbol_rva("_main"), Some(0x5000));
assert_eq!(linker.find_symbol_rva("_data"), Some(0x8000));
assert_eq!(linker.find_symbol_rva("_missing"), None);
}
#[test]
fn test_coff_output_section_ordering() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
for name in &[".bss", ".data", ".rdata", ".text"] {
let obj = CoffObjectFull {
filename: format!("{}.obj", name),
data: vec![],
header: CoffFileHeader {
machine: IMAGE_FILE_MACHINE_AMD64,
number_of_sections: 1,
time_date_stamp: 0,
pointer_to_symbol_table: 0,
number_of_symbols: 0,
size_of_optional_header: 0,
characteristics: 0,
},
sections: vec![CoffSectionFull {
name: name.to_string(),
data: vec![0; 8],
header: CoffSectionHeader {
name: [0; 8],
virtual_size: 8,
virtual_address: 0,
size_of_raw_data: 8,
pointer_to_raw_data: 0,
pointer_to_relocations: 0,
pointer_to_line_numbers: 0,
number_of_relocations: 0,
number_of_line_numbers: 0,
characteristics: 0,
},
section_index: 0,
}],
symbols: vec![],
relocations: vec![],
};
linker.objects.push(obj);
}
linker.merge_coff_sections().unwrap();
let names: Vec<&str> = linker.sections.iter().map(|s| s.name.as_str()).collect();
assert_eq!(names, vec![".text", ".rdata", ".data", ".bss"]);
}
#[test]
fn test_macho_emit_with_all_dylib_types() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.add_dylib("/usr/lib/libA.dylib", false, false);
linker.add_dylib("/usr/lib/libB.dylib", true, false); linker.add_dylib("/usr/lib/libC.dylib", false, true); linker.add_export("_test");
let result = linker.emit().unwrap();
assert!(!result.is_empty());
}
#[test]
fn test_macho_emit_with_many_rpaths() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
for i in 0..20 {
linker.add_rpath(&format!("@rpath/framework{}", i));
}
let result = linker.emit().unwrap();
assert!(!result.is_empty());
}
#[test]
fn test_macho_emit_dylib_with_exports() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.set_dylib("@rpath/libmylib.dylib");
linker.add_export("_public_func1");
linker.add_export("_public_func2");
linker.add_export("_public_var");
let result = linker.emit().unwrap();
assert!(!result.is_empty());
}
#[test]
fn test_macho_segment_command_structure() {
let linker = X86MachOLinker::new(X86LLDArch::X86_64);
let sections = vec![
MachOSection64 {
sectname: {
let mut s = [0u8; 16];
s[..14].copy_from_slice(b"__text\0\0\0\0\0\0\0\0\0");
s
},
segname: {
let mut s = [0u8; 16];
s[..7].copy_from_slice(b"__TEXT\0");
s
},
addr: 0x100003f80,
size: 256,
offset: 0x3f80,
align: 4,
reloff: 0,
nreloc: 0,
flags: 0x80000400, reserved1: 0,
reserved2: 0,
reserved3: 0,
},
MachOSection64 {
sectname: {
let mut s = [0u8; 16];
s[..12].copy_from_slice(b"__cstring\0\0\0");
s
},
segname: {
let mut s = [0u8; 16];
s[..7].copy_from_slice(b"__TEXT\0");
s
},
addr: 0x100004000,
size: 128,
offset: 0x4080,
align: 0,
reloff: 0,
nreloc: 0,
flags: 0x00000002, reserved1: 0,
reserved2: 0,
reserved3: 0,
},
];
let cmd = linker.build_segment_command(
"__TEXT\0\0\0\0\0\0\0\0\0",
0x100000000,
0x5000,
0,
0x5000,
7,
5,
§ions,
true,
);
assert_eq!(cmd.len(), 232);
}
#[test]
fn test_macho_lc_main_command() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.output_type = MH_EXECUTE;
let result = linker.emit().unwrap();
let header_size = 32; let mut found = false;
let mut i = header_size;
while i + 8 <= result.len() {
let cmd = u32::from_le_bytes([result[i], result[i + 1], result[i + 2], result[i + 3]]);
let cmdsize =
u32::from_le_bytes([result[i + 4], result[i + 5], result[i + 6], result[i + 7]]);
if cmd == LC_MAIN {
found = true;
break;
}
i += cmdsize as usize;
if cmdsize == 0 {
break;
}
}
assert!(found, "LC_MAIN not found in Mach-O output");
}
#[test]
fn test_macho_lc_build_version() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.set_build_version(PLATFORM_MACOS, 0x000E0000, 0x000E0000);
let result = linker.emit().unwrap();
let header_size = 32;
let mut found = false;
let mut i = header_size;
while i + 8 <= result.len() {
let cmd = u32::from_le_bytes([result[i], result[i + 1], result[i + 2], result[i + 3]]);
let cmdsize =
u32::from_le_bytes([result[i + 4], result[i + 5], result[i + 6], result[i + 7]]);
if cmd == LC_BUILD_VERSION {
found = true;
break;
}
i += cmdsize as usize;
if cmdsize == 0 {
break;
}
}
assert!(found, "LC_BUILD_VERSION not found in Mach-O output");
}
#[test]
fn test_macho_lc_uuid() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.set_uuid([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
let result = linker.emit().unwrap();
let header_size = 32;
let mut found = false;
let mut i = header_size;
while i + 8 <= result.len() {
let cmd = u32::from_le_bytes([result[i], result[i + 1], result[i + 2], result[i + 3]]);
let cmdsize =
u32::from_le_bytes([result[i + 4], result[i + 5], result[i + 6], result[i + 7]]);
if cmd == LC_UUID {
found = true;
break;
}
i += cmdsize as usize;
if cmdsize == 0 {
break;
}
}
assert!(found, "LC_UUID not found in Mach-O output");
}
#[test]
fn test_macho_lc_rpath() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.add_rpath("@executable_path/../lib");
let result = linker.emit().unwrap();
let header_size = 32;
let mut found = false;
let mut i = header_size;
while i + 8 <= result.len() {
let cmd = u32::from_le_bytes([result[i], result[i + 1], result[i + 2], result[i + 3]]);
let cmdsize =
u32::from_le_bytes([result[i + 4], result[i + 5], result[i + 6], result[i + 7]]);
if cmd == LC_RPATH {
found = true;
break;
}
i += cmdsize as usize;
if cmdsize == 0 {
break;
}
}
assert!(found, "LC_RPATH not found in Mach-O output");
}
#[test]
fn test_phdr_emit_64_size() {
let mut phdrs = X86ELFPhdrs::new();
phdrs.generate(&[], 0x400000, 4096);
let d = phdrs.emit_64();
assert_eq!(d.len() % 56, 0);
}
#[test]
fn test_phdr_emit_32_size() {
let mut phdrs = X86ELFPhdrs::new();
phdrs.generate(&[], 0x400000, 4096);
let d = phdrs.emit_32();
assert_eq!(d.len() % 32, 0);
}
#[test]
fn test_dt_constants_unique() {
assert_ne!(DT_NEEDED, DT_SONAME);
assert_ne!(DT_RPATH, DT_RUNPATH);
assert_ne!(DT_INIT, DT_FINI);
assert_ne!(DT_HASH, DT_GNU_HASH);
assert_ne!(DT_FLAGS, DT_FLAGS_1);
assert_ne!(DT_VERDEF, DT_VERNEED);
}
#[test]
fn test_coff_characteristics_combinations() {
let base = IMAGE_SCN_CNT_CODE;
assert_eq!(base, 0x00000020);
let exec_read = base | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ;
assert_eq!(exec_read, 0x60000020);
}
#[test]
fn test_linker_full_gc_and_icf_flags() {
let mut linker = X86LinkerFull::new_elf_x86_64();
assert!(!linker.enable_gc_sections);
assert!(!linker.enable_icf);
linker.enable_gc_sections = true;
linker.enable_icf = true;
assert!(linker.enable_gc_sections);
assert!(linker.enable_icf);
}
#[test]
fn test_linker_full_build_id_modes() {
let mut linker = X86LinkerFull::new_elf_x86_64();
assert!(matches!(linker.build_id, X86BuildIdKind::None));
linker.build_id = X86BuildIdKind::Sha1;
assert!(matches!(linker.build_id, X86BuildIdKind::Sha1));
linker.build_id = X86BuildIdKind::Md5;
assert!(matches!(linker.build_id, X86BuildIdKind::Md5));
linker.build_id = X86BuildIdKind::Uuid;
assert!(matches!(linker.build_id, X86BuildIdKind::Uuid));
linker.build_id = X86BuildIdKind::HexString;
assert!(matches!(linker.build_id, X86BuildIdKind::HexString));
linker.build_id = X86BuildIdKind::Fast;
assert!(matches!(linker.build_id, X86BuildIdKind::Fast));
}
#[test]
fn test_linker_full_hash_style_modes() {
let mut linker = X86LinkerFull::new_elf_x86_64();
assert!(matches!(linker.hash_style, X86HashStyle::Both));
linker.hash_style = X86HashStyle::SysV;
assert!(matches!(linker.hash_style, X86HashStyle::SysV));
linker.hash_style = X86HashStyle::Gnu;
assert!(matches!(linker.hash_style, X86HashStyle::Gnu));
}
#[test]
fn test_linker_full_strip_debug() {
let mut linker = X86LinkerFull::new_elf_x86_64();
assert!(!linker.strip_debug);
linker.strip_debug = true;
assert!(linker.strip_debug);
}
#[test]
fn test_linker_full_is_shared() {
let mut linker = X86LinkerFull::new_elf_x86_64();
assert!(!linker.is_shared);
linker.is_shared = true;
assert!(linker.is_shared);
}
#[test]
fn test_phdr_elf_program_types() {
assert!(PT_LOAD > 0);
assert!(PT_DYNAMIC > 0);
assert!(PT_INTERP > 0);
assert!(PT_NOTE > 0);
assert!(PT_SHLIB > 0);
assert!(PT_PHDR > 0);
assert!(PT_TLS > 0);
assert!(PT_GNU_EH_FRAME > 0x60000000);
assert!(PT_GNU_STACK > 0x60000000);
assert!(PT_GNU_RELRO > 0x60000000);
assert!(PT_GNU_PROPERTY > 0x60000000);
}
#[test]
fn test_dynamic_entry_tag_values_range() {
assert!(DT_LOOS >= 0x60000000);
assert!(DT_HIOS <= 0x6ffff000);
assert!(DT_LOPROC >= 0x70000000);
assert!(DT_HIPROC <= 0x7fffffff);
assert!(DT_GNU_HASH >= DT_LOOS && DT_GNU_HASH <= DT_HIOS);
assert!(DT_VERSYM >= DT_LOOS && DT_VERSYM <= DT_HIOS);
assert!(DT_FLAGS_1 >= DT_LOOS && DT_FLAGS_1 <= DT_HIOS);
}
#[test]
fn test_sysv_hash_consistency() {
let names: Vec<String> = vec!["sym1".into(), "sym2".into(), "sym3".into()];
let (table1, nb1) = X86DynamicEntries::build_sysv_hash(&names);
let (table2, nb2) = X86DynamicEntries::build_sysv_hash(&names);
assert_eq!(table1, table2);
assert_eq!(nb1, nb2);
}
#[test]
fn test_gnu_hash_consistency() {
let names: Vec<String> = vec!["sym1".into(), "sym2".into(), "sym3".into()];
let d1 = X86DynamicEntries::build_gnu_hash(&names, 8);
let d2 = X86DynamicEntries::build_gnu_hash(&names, 8);
assert_eq!(d1, d2);
}
#[test]
fn test_coff_section_chars_nonstandard_name() {
let chars = X86COFFLinkerFull::section_name_to_characteristics(".mycustom");
assert_eq!(chars, IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ);
}
#[test]
fn test_delay_import_attributes() {
let d = DelayImportDescriptor {
dll_name: "test.dll".into(),
module_handle_rva: 0,
import_address_table_rva: 0,
import_name_table_rva: 0,
bound_iat_rva: 0,
unload_iat_rva: 0,
timestamp: 0,
};
assert_eq!(d.attributes(), 1);
}
#[test]
fn test_base_relocation_types() {
assert_eq!(IMAGE_REL_BASED_ABSOLUTE, 0);
assert_eq!(IMAGE_REL_BASED_HIGH, 1);
assert_eq!(IMAGE_REL_BASED_LOW, 2);
assert_eq!(IMAGE_REL_BASED_HIGHLOW, 3);
assert_eq!(IMAGE_REL_BASED_HIGHADJ, 4);
assert_eq!(IMAGE_REL_BASED_DIR64, 10);
}
#[test]
fn test_debug_type_constants() {
assert_eq!(IMAGE_DEBUG_TYPE_UNKNOWN, 0);
assert_eq!(IMAGE_DEBUG_TYPE_COFF, 1);
assert_eq!(IMAGE_DEBUG_TYPE_CODEVIEW, 2);
assert_eq!(IMAGE_DEBUG_TYPE_FPO, 3);
assert_eq!(IMAGE_DEBUG_TYPE_MISC, 4);
assert_eq!(IMAGE_DEBUG_TYPE_EXCEPTION, 5);
assert_eq!(IMAGE_DEBUG_TYPE_FIXUP, 6);
assert_eq!(IMAGE_DEBUG_TYPE_REPRO, 16);
}
#[test]
fn test_macho_platform_constants() {
assert_eq!(PLATFORM_MACOS, 1);
assert_eq!(PLATFORM_IOS, 2);
assert_eq!(PLATFORM_TVOS, 3);
assert_eq!(PLATFORM_WATCHOS, 4);
assert_eq!(PLATFORM_BRIDGEOS, 5);
assert_eq!(PLATFORM_MACCATALYST, 6);
assert_eq!(PLATFORM_IOSSIMULATOR, 7);
assert_eq!(PLATFORM_TVOSSIMULATOR, 8);
assert_eq!(PLATFORM_WATCHOSSIMULATOR, 9);
assert_eq!(PLATFORM_DRIVERKIT, 10);
}
#[test]
fn test_macho_flag_bits() {
assert_eq!(MH_PIE & MH_NO_HEAP_EXECUTION, 0);
assert_eq!(MH_DYLDLINK & MH_TWOLEVEL, 0); assert!(MH_NO_HEAP_EXECUTION > 0);
assert!(MH_APP_EXTENSION_SAFE > 0);
}
#[test]
fn test_bind_type_constants() {
assert_eq!(BIND_TYPE_POINTER, 1);
assert_eq!(BIND_TYPE_TEXT_ABSOLUTE32, 2);
assert_eq!(BIND_TYPE_TEXT_PCREL32, 3);
}
#[test]
fn test_lazy_binding_stub_structure() {
let stub = MachOLazyBindingStub {
symbol: "_malloc".into(),
dylib_ordinal: 1,
};
assert_eq!(stub.symbol, "_malloc");
assert_eq!(stub.dylib_ordinal, 1);
}
#[test]
fn test_macho_binding_structure() {
let b = MachOBinding {
segment: 2,
offset: 0x1000,
symbol: "_printf".into(),
dylib_ordinal: 1,
addend: -8,
bind_type: BIND_TYPE_POINTER,
};
assert_eq!(b.segment, 2);
assert_eq!(b.offset, 0x1000);
assert_eq!(b.addend, -8);
}
#[test]
fn test_elf_phdr_full_structure() {
let phdr = X86ELFPhdrFull {
p_type: PT_LOAD,
p_flags: PF_RX,
p_offset: 0,
p_vaddr: 0x400000,
p_paddr: 0x400000,
p_filesz: 0x1000,
p_memsz: 0x1000,
p_align: 4096,
};
assert_eq!(phdr.p_type, PT_LOAD);
assert_eq!(phdr.p_flags, PF_RX);
}
#[test]
fn test_dynamic_entry_structure() {
let d = X86DynamicEntry {
d_tag: DT_INIT,
d_val: 0x1000,
d_ptr: 0x1000,
};
assert_eq!(d.d_tag, DT_INIT);
assert_eq!(d.d_val, 0x1000);
}
#[test]
fn test_arch_pointer_sizes() {
let a64 = X86LLDArch::X86_64;
let a32 = X86LLDArch::I386;
assert_eq!(a64.pointer_size(), 8);
assert_eq!(a32.pointer_size(), 4);
}
#[test]
fn test_arch_elf_machine() {
assert_eq!(X86LLDArch::X86_64.elf_machine(), 62); assert_eq!(X86LLDArch::I386.elf_machine(), 3); }
#[test]
fn test_arch_coff_machine() {
assert_eq!(X86LLDArch::X86_64.coff_machine(), 0x8664);
assert_eq!(X86LLDArch::I386.coff_machine(), 0x014c);
}
#[test]
fn test_comprehensive_constant_sanity() {
let checks = [
ELF64_EHDR_SIZE as u64,
ELF64_PHDR_SIZE as u64,
ELF64_SHDR_SIZE as u64,
GOT_ENTRY_SIZE as u64,
PLT_ENTRY_SIZE as u64,
PLT0_SIZE as u64,
COFF_FILE_ALIGNMENT as u64,
COFF_SECTION_ALIGNMENT as u64,
];
for i in 0..checks.len() {
assert!(checks[i] > 0);
for j in 0..i {
let _ = (checks[i], checks[j]);
}
}
}
#[test]
fn test_gnu_hash_function_deterministic() {
let h1 = gnu_hash("very_long_symbol_name_for_testing_purposes");
let h2 = gnu_hash("very_long_symbol_name_for_testing_purposes");
assert_eq!(h1, h2);
let h3 = gnu_hash("very_long_symbol_name_for_testing_purposes!");
assert_ne!(h1, h3);
}
#[test]
fn test_elf_hash_deterministic() {
let h1 = elf_hash("symbol");
let h2 = elf_hash("symbol");
assert_eq!(h1, h2);
}
#[test]
fn test_linker_full_output_path_roundtrip() {
let mut linker = X86LinkerFull::new_elf_x86_64();
linker.set_output_path("/custom/path/output.elf");
assert_eq!(linker.output_path, "/custom/path/output.elf");
}
#[test]
fn test_macho_emit_no_crash() {
for filetype in &[MH_OBJECT, MH_EXECUTE, MH_DYLIB, MH_BUNDLE] {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.output_type = *filetype;
if *filetype == MH_DYLIB {
linker.install_name = "test.dylib".into();
linker.is_dylib = true;
}
let result = linker.emit();
assert!(result.is_ok(), "Emit failed for filetype {}", filetype);
}
}
#[test]
fn test_phdr_all_features_enabled() {
let mut phdrs = X86ELFPhdrs::new();
phdrs.is_shared = true;
phdrs.has_tls = true;
phdrs.has_eh_frame = true;
phdrs.has_relro = true;
phdrs.has_gnu_property = true;
phdrs.exec_stack = false;
let seg = X86OutputSegment {
p_type: PT_LOAD,
p_flags: PF_R,
p_align: 4096,
p_vaddr: 0,
p_paddr: 0,
p_offset: 0,
p_filesz: 0x1000,
p_memsz: 0x1000,
section_indices: vec![],
};
phdrs.generate(&[seg], 0, 4096);
let d64 = phdrs.emit_64();
let d32 = phdrs.emit_32();
assert!(!d64.is_empty());
assert!(!d32.is_empty());
}
#[test]
fn test_dynamic_entries_all_possible_additions() {
let mut de = X86DynamicEntries::new(true);
de.add(DT_NULL, 0); de.add(DT_NEEDED, 1);
de.add(DT_PLTRELSZ, 2);
de.add(DT_PLTGOT, 3);
de.add(DT_HASH, 4);
de.add(DT_STRTAB, 5);
de.add(DT_SYMTAB, 6);
de.add(DT_RELA, 7);
de.add(DT_RELASZ, 8);
de.add(DT_RELAENT, 9);
de.add(DT_STRSZ, 10);
de.add(DT_SYMENT, 11);
de.add(DT_INIT, 12);
de.add(DT_FINI, 13);
de.add(DT_SONAME, 14);
de.add(DT_RPATH, 15);
de.add(DT_SYMBOLIC, 16);
de.add(DT_REL, 17);
de.add(DT_RELSZ, 18);
de.add(DT_RELENT, 19);
de.add(DT_PLTREL, 20);
de.add(DT_DEBUG, 21);
de.add(DT_TEXTREL, 22);
de.add(DT_JMPREL, 23);
de.add(DT_BIND_NOW, 24);
de.add(DT_INIT_ARRAY, 25);
de.add(DT_FINI_ARRAY, 26);
de.add(DT_INIT_ARRAYSZ, 27);
de.add(DT_FINI_ARRAYSZ, 28);
de.add(DT_RUNPATH, 29);
de.add(DT_FLAGS, 30);
de.add(DT_PREINIT_ARRAY, 32);
de.add(DT_PREINIT_ARRAYSZ, 33);
de.add(DT_GNU_HASH, 34);
de.add(DT_VERSYM, 35);
de.add(DT_FLAGS_1, 36);
de.add(DT_VERDEF, 37);
de.add(DT_VERDEFNUM, 38);
de.add(DT_VERNEED, 39);
de.add(DT_VERNEEDNUM, 40);
de.add(DT_RELACOUNT, 41);
de.add(DT_RELCOUNT, 42);
let data = de.emit();
assert!(!data.is_empty());
}
#[test]
fn test_all_x86_reloc_types_coverage() {
let types = [
R_X86_64_DTPMOD64,
R_X86_64_DTPOFF64,
R_X86_64_TPOFF64,
R_X86_64_TLSGD,
R_X86_64_TLSLD,
R_X86_64_DTPOFF32,
R_X86_64_GOTTPOFF,
R_X86_64_TPOFF32,
R_X86_64_IRELATIVE,
];
for &t in &types {
assert!(t > 0);
}
let i386_types = [
R_386_TLS_TPOFF,
R_386_TLS_IE,
R_386_TLS_GOTIE,
R_386_TLS_LE,
R_386_TLS_GD,
R_386_TLS_LDM,
R_386_IRELATIVE,
R_386_GOT32X,
];
for &t in &i386_types {
assert!(t > 0);
}
}
#[test]
fn test_coff_parser_bad_magic() {
let linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
let data = vec![0xFFu8; 64];
let result = linker.parse_coff_object_full("bad.obj", &data);
match result {
Ok(obj) => {
assert_eq!(obj.header.machine, 0xFFFF);
}
Err(_) => {
}
}
}
#[test]
fn test_macho_linker_default_is_64bit() {
let linker = X86MachOLinker::new(X86LLDArch::X86_64);
assert!(linker.is_64bit);
}
#[test]
fn test_coff_linker_64_has_large_address_aware() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
let result = linker.emit().unwrap();
let pe_start = 64u32 as usize;
let coff_header_start = pe_start + 4;
let chars_offset = coff_header_start + 18;
let chars = u16::from_le_bytes([result[chars_offset], result[chars_offset + 1]]);
assert!(chars & IMAGE_FILE_LARGE_ADDRESS_AWARE != 0);
}
#[test]
fn test_coff_linker_32_has_32bit_flag() {
let mut linker = X86COFFLinkerFull::new(X86LLDArch::I386);
let result = linker.emit().unwrap();
let pe_start = 64u32 as usize;
let coff_header_start = pe_start + 4;
let chars_offset = coff_header_start + 18;
let chars = u16::from_le_bytes([result[chars_offset], result[chars_offset + 1]]);
assert!(chars & IMAGE_FILE_32BIT_MACHINE != 0);
}
#[test]
fn test_data_directory_entry_size() {
assert_eq!(ImageDataDirectory::SIZE, 8);
}
#[test]
fn test_all_structs_instantiable() {
let _ = X86LinkerFull::new_elf_x86_64();
let _ = X86COFFLinkerFull::new(X86LLDArch::X86_64);
let _ = X86MachOLinker::new(X86LLDArch::X86_64);
let _ = X86ELFPhdrs::new();
let _ = X86DynamicEntries::new(true);
let _ = CoffFileHeader {
machine: 0,
number_of_sections: 0,
time_date_stamp: 0,
pointer_to_symbol_table: 0,
number_of_symbols: 0,
size_of_optional_header: 0,
characteristics: 0,
};
let _ = MachOSegment64 {
segname: [0; 16],
vmaddr: 0,
vmsize: 0,
fileoff: 0,
filesize: 0,
maxprot: 0,
initprot: 0,
nsects: 0,
flags: 0,
};
let _ = DelayImportDescriptor {
dll_name: String::new(),
module_handle_rva: 0,
import_address_table_rva: 0,
import_name_table_rva: 0,
bound_iat_rva: 0,
unload_iat_rva: 0,
timestamp: 0,
};
}
#[test]
fn test_comprehensive_linker_full_usage() {
let mut full = X86LinkerFull::new_elf_x86_64();
full.set_pic(true);
full.set_image_base(0x400000);
full.set_entry("_start");
full.set_output_path("/tmp/test.elf");
full.add_library_path("/lib");
full.add_library_path("/usr/lib");
full.add_shared_library("libc.so.6");
full.add_shared_library("libm.so.6");
assert!(full.is_pic);
assert_eq!(full.image_base, 0x400000);
assert_eq!(full.entry_point, "_start");
assert_eq!(full.output_path, "/tmp/test.elf");
assert_eq!(full.library_search_paths.len(), 2);
assert_eq!(full.shared_libs.len(), 2);
}
#[test]
fn test_comprehensive_coff_full_usage() {
let mut coff = X86COFFLinkerFull::new(X86LLDArch::X86_64);
coff.is_dll = true;
coff.subsystem = 2;
coff.entry_point = "DllMain".into();
coff.add_import("GetProcAddress", "kernel32.dll", None, false);
coff.add_export("MyExport", 1, 0x3000);
coff.set_tls_data(TlsDirectoryData {
start_address_of_raw_data: 0x5000,
end_address_of_raw_data: 0x5100,
address_of_index: 0x5200,
address_of_callbacks: 0,
size_of_zero_fill: 64,
characteristics: 0,
});
coff.has_tls = true;
coff.add_exception_entry(ExceptionTableEntry {
begin_address: 0x1000,
end_address: 0x1100,
unwind_info_address: 0x2000,
});
coff.has_exception = true;
coff.set_resource_data(vec![0; 64]);
coff.has_resource = true;
coff.add_debug_entry(IMAGE_DEBUG_TYPE_CODEVIEW, b"RSDS");
coff.has_debug = true;
coff.add_delay_import("shell32.dll");
coff.add_bound_import("ntdll.dll", 0x12345678);
let result = coff.emit().unwrap();
assert!(!result.is_empty());
}
#[test]
fn test_comprehensive_macho_full_usage() {
let mut macho = X86MachOLinker::new(X86LLDArch::X86_64);
macho.set_dylib("@rpath/libexample.dylib");
macho.add_dylib("/usr/lib/libSystem.B.dylib", false, false);
macho.add_dylib("/usr/lib/libobjc.dylib", false, false);
macho.add_rpath("@executable_path/../Frameworks");
macho.add_rpath("@loader_path");
macho.set_uuid([0; 16]);
macho.set_build_version(PLATFORM_MACOS, 0x000E0000, 0x000E0000);
macho.set_source_version(0x0100020300000000);
macho.add_export("_init");
macho.add_export("_fini");
macho.add_export("_main");
macho.add_binding(2, 0x1000, "_printf", 1, 0);
macho.add_weak_binding(2, 0x1008, "_optional");
macho.add_lazy_binding("_malloc", 1);
macho.pie = true;
macho.no_heap_execution = true;
macho.app_extension_safe = true;
let result = macho.emit().unwrap();
assert!(!result.is_empty());
}
#[test]
fn test_comprehensive_phdr_usage() {
let mut phdrs = X86ELFPhdrs::new();
phdrs.is_shared = true;
phdrs.has_tls = true;
phdrs.has_eh_frame = true;
phdrs.has_relro = true;
phdrs.has_gnu_property = true;
phdrs.exec_stack = false;
let segments = vec![
X86OutputSegment {
p_type: PT_LOAD,
p_flags: PF_RX,
p_align: 4096,
p_vaddr: 0x100000,
p_paddr: 0x100000,
p_offset: 0,
p_filesz: 0x3000,
p_memsz: 0x3000,
section_indices: vec![0, 1, 2],
},
X86OutputSegment {
p_type: PT_LOAD,
p_flags: PF_RW,
p_align: 4096,
p_vaddr: 0x200000,
p_paddr: 0x200000,
p_offset: 0x3000,
p_filesz: 0x2000,
p_memsz: 0x4000,
section_indices: vec![3, 4],
},
];
phdrs.generate(&segments, 0x100000, 4096);
let d64 = phdrs.emit_64();
let d32 = phdrs.emit_32();
assert!(!d64.is_empty());
assert!(!d32.is_empty());
let types: Vec<u32> = phdrs.entries.iter().map(|e| e.p_type).collect();
assert!(types.contains(&PT_PHDR));
assert!(types.contains(&PT_LOAD));
assert!(types.contains(&PT_DYNAMIC));
assert!(types.contains(&PT_NOTE));
assert!(types.contains(&PT_TLS));
assert!(types.contains(&PT_GNU_EH_FRAME));
assert!(types.contains(&PT_GNU_STACK));
assert!(types.contains(&PT_GNU_RELRO));
assert!(types.contains(&PT_GNU_PROPERTY));
}
#[test]
fn test_comprehensive_dynamic_entries_usage() {
let mut de = X86DynamicEntries::new(true);
de.add_needed(10);
de.add_soname(20);
de.add_rpath(30);
de.add_runpath(40);
de.add_init(0x1000);
de.add_fini(0x2000);
de.add_init_array(0x3000, 16);
de.add_fini_array(0x3100, 8);
de.add_hash(0x4000);
de.add_gnu_hash(0x4100);
de.add_symbol_table(0x5000, 0x6000, 512);
de.add_pltgot(0x7000);
de.add_plt_relocations(true, 0x7100, 96);
de.add_rela(0x7200, 128, 24);
de.add_rel(0x7300, 64);
de.add_flags(DF_BIND_NOW | DF_STATIC_TLS);
de.add_flags_1(DF_1_NOW | DF_1_PIE);
de.add_versym(0x8000);
de.add_verdef(0x8100, 2);
de.add_verneed(0x8200, 1);
de.add_rel_count(4, 2);
let data = de.emit();
assert!(!data.is_empty());
}
#[test]
fn test_coff_fat_header_fields() {
let linker = X86COFFLinkerFull::new(X86LLDArch::X86_64);
let result = linker.emit().unwrap();
assert_eq!(&result[0..2], b"MZ");
let pe_offset =
u32::from_le_bytes([result[0x3C], result[0x3D], result[0x3E], result[0x3F]]) as usize;
assert_eq!(&result[pe_offset..pe_offset + 4], b"PE\0\0");
let coff_start = pe_offset + 4;
let machine = u16::from_le_bytes([result[coff_start], result[coff_start + 1]]);
assert_eq!(machine, IMAGE_FILE_MACHINE_AMD64);
}
#[test]
fn test_macho_load_command_walk() {
let mut linker = X86MachOLinker::new(X86LLDArch::X86_64);
linker.set_dylib("@rpath/lib.dylib");
linker.add_dylib("/usr/lib/libSystem.dylib", false, false);
let data = linker.emit().unwrap();
let ncmds = u32::from_le_bytes([data[16], data[17], data[18], data[19]]) as usize;
let mut offset = 32;
let mut found_load_dylib = false;
let mut found_id_dylib = false;
let mut found_dyld_info = false;
for _ in 0..ncmds {
if offset + 8 > data.len() {
break;
}
let cmd = u32::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
]);
let cmdsize = u32::from_le_bytes([
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
]) as usize;
if cmd == LC_LOAD_DYLIB {
found_load_dylib = true;
}
if cmd == LC_ID_DYLIB {
found_id_dylib = true;
}
if cmd == LC_DYLD_INFO_ONLY || cmd == LC_DYLD_INFO {
found_dyld_info = true;
}
offset += cmdsize;
}
assert!(found_load_dylib);
assert!(found_id_dylib);
assert!(found_dyld_info);
}
#[test]
fn test_fat_binary_endianness() {
let x86 = vec![0xAAu8; 64];
let arm = vec![0xBBu8; 64];
let fat = X86MachOLinker::generate_fat_binary(&x86, &arm);
let magic = u32::from_be_bytes([fat[0], fat[1], fat[2], fat[3]]);
assert_eq!(magic, FAT_MAGIC);
}
#[test]
fn test_macho_fat_binary_magic() {
let x86 = vec![0u8; 32];
let arm = vec![0u8; 32];
let fat = X86MachOLinker::generate_fat_binary(&x86, &arm);
let be = [fat[0], fat[1], fat[2], fat[3]];
let magic = u32::from_be_bytes(be);
assert_eq!(magic, 0xcafebabe);
}
}