use std::collections::{HashMap, VecDeque};
use super::x86_binary_format::{
DT_FINI_ARRAY, DT_INIT_ARRAY, DT_JMPREL, DT_NEEDED, DT_NULL, DT_PLTGOT, DT_PLTREL, DT_PLTRELSZ,
DT_RELA, DT_RELAENT, DT_RELASZ, DT_STRSZ, DT_STRTAB, DT_SYMTAB, PF_R, PF_W, PF_X, PT_DYNAMIC,
PT_GNU_STACK, PT_LOAD, PT_PHDR, PT_TLS, SHF_WRITE, SHN_COMMON, SHT_NOBITS, SHT_NULL, SHT_REL,
SHT_RELA, SHT_STRTAB, SHT_SYMTAB, STB_WEAK, STT_GNU_IFUNC, STT_NOTYPE, STV_INTERNAL,
STV_PROTECTED,
};
pub const R_386_NONE: u32 = 0;
pub const R_386_32: u32 = 1;
pub const R_386_PC32: u32 = 2;
pub const R_386_GOT32: u32 = 3;
pub const R_386_PLT32: u32 = 4;
pub const R_386_COPY: u32 = 5;
pub const R_386_GLOB_DAT: u32 = 6;
pub const R_386_JMP_SLOT: u32 = 7;
pub const R_386_RELATIVE: u32 = 8;
pub const R_386_GOTOFF: u32 = 9;
pub const R_386_GOTPC: u32 = 10;
pub const R_386_32PLT: u32 = 11;
pub const R_386_TLS_TPOFF: u32 = 14;
pub const R_386_TLS_IE: u32 = 15;
pub const R_386_TLS_GOTIE: u32 = 16;
pub const R_386_TLS_LE: u32 = 17;
pub const R_386_TLS_GD: u32 = 18;
pub const R_386_TLS_LDM: u32 = 19;
pub const R_386_TLS_GD_32: u32 = 24;
pub const R_386_TLS_GD_PUSH: u32 = 25;
pub const R_386_TLS_GD_CALL: u32 = 26;
pub const R_386_TLS_GD_POP: u32 = 27;
pub const R_386_TLS_LDM_32: u32 = 28;
pub const R_386_TLS_LDM_PUSH: u32 = 29;
pub const R_386_TLS_LDM_CALL: u32 = 30;
pub const R_386_TLS_LDM_POP: u32 = 31;
pub const R_386_TLS_LDO_32: u32 = 32;
pub const R_386_TLS_IE_32: u32 = 33;
pub const R_386_TLS_LE_32: u32 = 34;
pub const R_386_TLS_DTPMOD32: u32 = 35;
pub const R_386_TLS_DTPOFF32: u32 = 36;
pub const R_386_TLS_TPOFF32: u32 = 37;
pub const R_386_SIZE32: u32 = 38;
pub const R_386_TLS_GOTDESC: u32 = 39;
pub const R_386_TLS_DESC_CALL: u32 = 40;
pub const R_386_TLS_DESC: u32 = 41;
pub const R_386_IRELATIVE: u32 = 42;
pub const R_386_GOT32X: u32 = 43;
pub const R_X86_64_DTPMOD64: u32 = 16;
pub const R_X86_64_DTPOFF64: u32 = 17;
pub const R_X86_64_TPOFF64: u32 = 18;
pub const R_X86_64_TLSGD: u32 = 19;
pub const R_X86_64_TLSLD: u32 = 20;
pub const R_X86_64_DTPOFF32: u32 = 21;
pub const R_X86_64_GOTTPOFF: u32 = 22;
pub const R_X86_64_TPOFF32: u32 = 23;
pub const R_X86_64_IRELATIVE: u32 = 37;
pub const R_X86_64_NONE: u32 = 0;
pub const R_X86_64_64: u32 = 1;
pub const R_X86_64_PC32: u32 = 2;
pub const R_X86_64_GOT32: u32 = 3;
pub const R_X86_64_PLT32: u32 = 4;
pub const R_X86_64_COPY: u32 = 5;
pub const R_X86_64_GLOB_DAT: u32 = 6;
pub const R_X86_64_JUMP_SLOT: u32 = 7;
pub const R_X86_64_RELATIVE: u32 = 8;
pub const R_X86_64_GOTPCREL: u32 = 9;
pub const R_X86_64_32: u32 = 10;
pub const R_X86_64_32S: u32 = 11;
pub const R_X86_64_16: u32 = 12;
pub const R_X86_64_PC16: u32 = 13;
pub const R_X86_64_8: u32 = 14;
pub const R_X86_64_PC8: u32 = 15;
pub const R_X86_64_PC64: u32 = 24;
pub const R_X86_64_GOTOFF64: u32 = 25;
pub const R_X86_64_GOTPC32: u32 = 26;
pub const R_X86_64_SIZE32: u32 = 32;
pub const R_X86_64_SIZE64: u32 = 33;
pub const EI_CLASS: usize = 4;
pub const ELFCLASS32: u8 = 1;
pub const ELFCLASS64: u8 = 2;
pub const ELFDATA2LSB: u8 = 1;
pub const EV_CURRENT: u32 = 1;
pub const ELFOSABI_SYSV: u8 = 0;
pub const ET_EXEC: u16 = 2;
pub const ET_DYN: u16 = 3;
pub const EM_X86_64: u16 = 62;
pub const EM_386: u16 = 3;
pub const STB_GLOBAL: u8 = 1;
pub const STT_FUNC: u8 = 2;
pub const STT_OBJECT: u8 = 1;
pub const STV_DEFAULT: u8 = 0;
pub const STV_HIDDEN: u8 = 2;
pub const SHN_UNDEF: u16 = 0;
pub const SHT_PROGBITS: u32 = 1;
pub const SHF_ALLOC: u32 = 2;
pub const SHF_EXECINSTR: u32 = 4;
pub const ELF_MAGIC_BYTES: [u8; 4] = [0x7f, b'E', b'L', b'F'];
pub const X86_64_PAGE_SIZE: u64 = 4096;
pub const X86_64_DEFAULT_IMAGE_BASE: u64 = 0x400000;
pub const I386_DEFAULT_IMAGE_BASE: u64 = 0x08048000;
pub const ELF64_EHDR_SIZE: u64 = 64;
pub const ELF64_PHDR_SIZE: u64 = 56;
pub const ELF64_SHDR_SIZE: u64 = 64;
pub const GOT_ENTRY_SIZE: usize = 8;
pub const PLT_ENTRY_SIZE: usize = 16;
pub const PLT0_SIZE: usize = 16;
pub const COFF_FILE_ALIGNMENT: u32 = 512;
pub const COFF_SECTION_ALIGNMENT: u32 = 4096;
pub const IMAGE_FILE_MACHINE_AMD64: u16 = 0x8664;
pub const IMAGE_FILE_MACHINE_I386: u16 = 0x014C;
pub const PE32PLUS_MAGIC: u16 = 0x020B;
pub const PE32_MAGIC: u16 = 0x010B;
pub fn align_up(value: u64, alignment: u64) -> u64 {
if alignment == 0 {
return value;
}
(value + alignment - 1) & !(alignment - 1)
}
pub fn align_down(value: u64, alignment: u64) -> u64 {
if alignment == 0 {
return value;
}
value & !(alignment - 1)
}
pub fn compute_fnv1a_hash(data: &[u8]) -> u64 {
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
for &b in data {
h ^= b as u64;
h = h.wrapping_mul(0x100_0000_01b3);
}
h
}
pub fn elf_hash(name: &str) -> u32 {
let mut h: u32 = 0;
for &b in name.as_bytes() {
h = (h << 4).wrapping_add(b as u32);
let g = h & 0xF0000000;
if g != 0 {
h ^= g >> 24;
}
h &= !g;
}
h
}
pub fn gnu_hash_bytes(name: &[u8]) -> u32 {
let mut h: u32 = 5381;
for &b in name {
h = h.wrapping_mul(33).wrapping_add(b as u32);
}
h
}
pub fn next_power_of_two(n: usize) -> u32 {
if n == 0 {
return 1;
}
let mut p = 1u32;
while (p as usize) < n {
p <<= 1;
}
p
}
pub fn read_u16_le(data: &[u8], offset: usize) -> u16 {
if offset + 2 > data.len() {
0
} else {
u16::from_le_bytes([data[offset], data[offset + 1]])
}
}
pub fn read_u32_le(data: &[u8], offset: usize) -> u32 {
if offset + 4 > data.len() {
0
} else {
u32::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
])
}
}
pub fn read_u64_le(data: &[u8], offset: usize) -> u64 {
if offset + 8 > data.len() {
0
} else {
u64::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
])
}
}
pub fn get_c_string(data: &[u8], offset: usize) -> &str {
if offset >= data.len() {
return "";
}
let e = data[offset..]
.iter()
.position(|&b| b == 0)
.unwrap_or(data.len() - offset);
std::str::from_utf8(&data[offset..offset + e]).unwrap_or("")
}
pub fn wildcard_match(name: &str, pattern: &str) -> bool {
if pattern == "*" {
return true;
}
if !pattern.contains('*') && !pattern.contains('?') {
return name == pattern;
}
wm_bytes(name.as_bytes(), pattern.as_bytes(), 0, 0)
}
pub fn wm_bytes(n: &[u8], p: &[u8], ni: usize, pi: usize) -> bool {
if pi == p.len() {
return ni == n.len();
}
match p[pi] {
b'*' => {
let mut pp = pi;
while pp < p.len() && p[pp] == b'*' {
pp += 1;
}
if pp == p.len() {
return true;
}
for nn in ni..=n.len() {
if wm_bytes(n, p, nn, pp) {
return true;
}
}
false
}
b'?' => ni < n.len() && wm_bytes(n, p, ni + 1, pi + 1),
c => ni < n.len() && n[ni] == c && wm_bytes(n, p, ni + 1, pi + 1),
}
}
pub fn tokenize_linker_script(script: &str) -> Vec<String> {
let mut t = Vec::new();
let cs: Vec<char> = script.chars().collect();
let mut i = 0;
while i < cs.len() {
if cs[i].is_whitespace() {
i += 1;
continue;
}
if cs[i] == '#' {
while i < cs.len() && cs[i] != '\n' {
i += 1;
}
continue;
}
if i + 1 < cs.len() && cs[i] == '/' && cs[i + 1] == '*' {
i += 2;
while i + 1 < cs.len() && !(cs[i] == '*' && cs[i + 1] == '/') {
i += 1;
}
i += 2;
continue;
}
if cs[i] == '"' {
i += 1;
let mut s = String::new();
while i < cs.len() && cs[i] != '"' {
if cs[i] == '\\' && i + 1 < cs.len() {
i += 1;
}
s.push(cs[i]);
i += 1;
}
if i < cs.len() {
i += 1;
}
t.push(s);
continue;
}
if "{}();:=,".contains(cs[i]) {
t.push(cs[i].to_string());
i += 1;
continue;
}
let st = i;
while i < cs.len()
&& !cs[i].is_whitespace()
&& !"{}();:=,\"".contains(cs[i])
&& !(cs[i] == '/' && i + 1 < cs.len() && cs[i + 1] == '*')
{
i += 1;
}
t.push(cs[st..i].iter().collect());
}
t
}
pub fn parse_script_number(tok: &str) -> u64 {
let t = tok.trim();
if t.starts_with("0x") || t.starts_with("0X") {
u64::from_str_radix(&t[2..], 16).unwrap_or(0)
} else if t.starts_with('0') && t.len() > 1 {
u64::from_str_radix(&t[1..], 8).unwrap_or(0)
} else {
t.parse().unwrap_or(0)
}
}
pub fn visibility_to_elf(vis: X86SymbolVisibility) -> u8 {
match vis {
X86SymbolVisibility::Default => STV_DEFAULT,
X86SymbolVisibility::Internal => STV_INTERNAL,
X86SymbolVisibility::Hidden => STV_HIDDEN,
X86SymbolVisibility::Protected => STV_PROTECTED,
}
}
pub fn hash_sha1(data: &[u8]) -> Vec<u8> {
let mut r = Vec::new();
let mut h: u64 = 0x67452301EFCDAB89;
for &b in data {
h ^= b as u64;
h = h.rotate_left(7);
}
r.extend_from_slice(&h.to_le_bytes());
r.truncate(20);
while r.len() < 20 {
r.push(0);
}
r
}
pub fn hash_md5(data: &[u8]) -> Vec<u8> {
let mut r = Vec::new();
let mut h: u64 = 0x0123456789ABCDEF;
for &b in data {
h ^= b as u64;
h = h.rotate_left(11);
}
r.extend_from_slice(&h.to_le_bytes());
r.truncate(16);
while r.len() < 16 {
r.push(0);
}
r
}
pub fn uuid_v4() -> Vec<u8> {
let mut u = vec![0u8; 16];
for i in 0..16 {
u[i] = (i.wrapping_mul(73).wrapping_add(17)) as u8;
}
u[6] = (u[6] & 0x0F) | 0x40;
u[8] = (u[8] & 0x3F) | 0x80;
u
}
pub fn hash_fast(data: &[u8]) -> Vec<u8> {
compute_fnv1a_hash(data).to_le_bytes().to_vec()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86OutputFormat {
Elf32,
Elf64,
Coff32,
Coff64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86LLDArch {
X86_64,
I386,
}
impl X86LLDArch {
pub fn is_64bit(&self) -> bool {
matches!(self, Self::X86_64)
}
pub fn pointer_size(&self) -> usize {
if self.is_64bit() {
8
} else {
4
}
}
pub fn elf_machine(&self) -> u16 {
match self {
Self::X86_64 => EM_X86_64,
Self::I386 => EM_386,
}
}
pub fn coff_machine(&self) -> u16 {
match self {
Self::X86_64 => IMAGE_FILE_MACHINE_AMD64,
Self::I386 => IMAGE_FILE_MACHINE_I386,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86BuildIdKind {
None,
Sha1,
Md5,
Uuid,
HexString,
Fast,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86HashStyle {
SysV,
Gnu,
Both,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum X86DiagLevel {
Info,
Warning,
Error,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct X86LinkerDiagnostic {
pub level: X86DiagLevel,
pub message: String,
}
#[derive(Debug, Clone)]
pub struct X86InputObject {
pub filename: String,
pub data: Vec<u8>,
pub sections: Vec<X86InputSection>,
pub symbols: Vec<X86InputSymbol>,
pub relocations: Vec<X86InputRelocation>,
pub machine: u16,
pub elf_class: u8,
}
#[derive(Debug, Clone)]
pub struct X86InputSection {
pub name: String,
pub data: Vec<u8>,
pub sh_type: u32,
pub sh_flags: u64,
pub sh_addralign: u64,
pub section_index: u32,
pub sh_entsize: u64,
}
#[derive(Debug, Clone)]
pub struct X86InputSymbol {
pub name: String,
pub value: u64,
pub size: u64,
pub binding: u8,
pub sym_type: u8,
pub visibility: u8,
pub section_index: u16,
pub is_defined: bool,
pub common_alignment: u64,
}
#[derive(Debug, Clone)]
pub struct X86InputRelocation {
pub offset: u64,
pub symbol_index: u32,
pub rel_type: u32,
pub addend: i64,
pub section_index: u32,
}
#[derive(Debug, Clone)]
pub struct X86SharedLibrary {
pub name: String,
pub symbols: Vec<String>,
pub needed: Vec<String>,
pub soname: Option<String>,
}
#[derive(Debug, Clone)]
pub struct X86OutputSection {
pub name: String,
pub data: Vec<u8>,
pub sh_type: u32,
pub sh_flags: u64,
pub sh_addralign: u64,
pub sh_entsize: u64,
pub segment_index: Option<usize>,
pub vaddr: u64,
pub file_offset: u64,
pub output_index: u32,
pub is_merge_strings: bool,
pub is_gc_eligible: bool,
}
#[derive(Debug, Clone)]
pub struct X86OutputSegment {
pub p_type: u32,
pub p_flags: u32,
pub p_align: u64,
pub p_vaddr: u64,
pub p_paddr: u64,
pub p_offset: u64,
pub p_filesz: u64,
pub p_memsz: u64,
pub section_indices: Vec<usize>,
}
#[derive(Debug, Clone)]
pub struct X86GotEntry {
pub symbol_name: String,
pub got_offset: u64,
pub is_tls: bool,
pub is_ifunc: bool,
}
#[derive(Debug, Clone)]
pub struct X86PltEntry {
pub symbol_name: String,
pub plt_offset: u64,
pub got_offset: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum X86SymbolDef {
Defined {
value: u64,
size: u64,
section_name: String,
is_function: bool,
is_ifunc: bool,
},
Common {
size: u64,
alignment: u64,
},
Undefined,
Shared {
library: String,
},
Weak {
value: u64,
size: u64,
section_name: String,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86SymbolVisibility {
Default,
Internal,
Hidden,
Protected,
}
#[derive(Debug, Clone)]
pub struct X86SymbolVersion {
pub version_name: String,
pub version_hash: u32,
pub is_default: bool,
pub is_hidden: bool,
}
#[derive(Debug, Clone)]
pub struct X86LinkerSymbol {
pub name: String,
pub def: X86SymbolDef,
pub binding: u8,
pub sym_type: u8,
pub visibility: X86SymbolVisibility,
pub version: Option<X86SymbolVersion>,
pub is_referenced: bool,
pub is_exported: bool,
pub priority: u32,
pub is_absolute: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86RelocAction {
None,
ApplyAbsolute64,
ApplyAbsolute32,
ApplyPC32,
ApplyPC64,
ApplyGOTPCREL,
ApplyPLT32,
ApplyRelative,
ApplyCopy,
ApplyIRELATIVE,
ApplyTLSGD,
ApplyTLSLD,
ApplyTPOFF,
ApplyDTPOFF,
ApplyDTPMOD,
ApplySize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86ScriptSectionType {
Progbits,
NoBits,
Note,
Copy,
Info,
Overlay,
}
#[derive(Debug, Clone)]
pub struct X86ArchiveMember {
pub name: String,
pub data: Vec<u8>,
pub symbols: Vec<String>,
pub timestamp: u32,
pub uid: u32,
pub gid: u32,
pub mode: u32,
}
#[derive(Debug, Clone)]
pub struct X86CoffObject {
pub filename: String,
pub data: Vec<u8>,
pub sections: Vec<X86CoffSection>,
pub symbols: Vec<X86CoffSymbol>,
pub relocations: Vec<X86CoffRelocation>,
pub machine: u16,
}
#[derive(Debug, Clone)]
pub struct X86CoffSection {
pub name: String,
pub data: Vec<u8>,
pub characteristics: u32,
pub align: u32,
pub section_index: u32,
}
#[derive(Debug, Clone)]
pub struct X86CoffSymbol {
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,
}
#[derive(Debug, Clone)]
pub struct X86CoffRelocation {
pub virtual_address: u32,
pub symbol_index: u32,
pub rel_type: u16,
pub section_index: u32,
}
#[derive(Debug, Clone)]
pub struct X86CoffImport {
pub symbol_name: String,
pub dll_name: String,
pub ordinal: Option<u16>,
pub is_data: bool,
}
#[derive(Debug, Clone)]
pub struct X86CoffExport {
pub name: String,
pub ordinal: u32,
pub rva: u32,
pub forwarder: Option<String>,
}
#[derive(Debug, Clone)]
pub struct X86CoffOutputSection {
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,
}
#[derive(Debug, Clone)]
pub struct X86CoffResolvedSymbol {
pub name: String,
pub value: u64,
pub section_index: u32,
pub is_external: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86CoffSubsystem {
Unknown = 0,
Native = 1,
WindowsGui = 2,
Console = 3,
EfiApplication = 10,
}
pub struct X86LLDLinker {
pub elf_linker: X86ELFLinker,
pub coff_linker: X86COFFLinker,
pub symbol_table: X86SymbolTable,
pub relocation_engine: X86RelocationEngine,
pub linker_script: X86LinkerScript,
pub archive_writer: X86ArchiveWriter,
pub lto_plugin: X86LTOPlugin,
pub icf: X86ICF,
pub gc_sections: X86GCSections,
pub output_format: X86OutputFormat,
pub arch: X86LLDArch,
pub output_path: Option<String>,
pub is_pic: bool,
pub strip_debug: bool,
pub entry_point: String,
pub image_base: u64,
pub enable_gc_sections: bool,
pub enable_icf: bool,
pub build_id: X86BuildIdKind,
pub hash_style: X86HashStyle,
pub diagnostics: Vec<X86LinkerDiagnostic>,
}
impl X86LLDLinker {
pub fn new() -> Self {
let arch = X86LLDArch::X86_64;
Self {
elf_linker: X86ELFLinker::new(arch),
coff_linker: X86COFFLinker::new(),
symbol_table: X86SymbolTable::new(),
relocation_engine: X86RelocationEngine::new(),
linker_script: X86LinkerScript::new(),
archive_writer: X86ArchiveWriter::new(),
lto_plugin: X86LTOPlugin::new(),
icf: X86ICF::new(),
gc_sections: X86GCSections::new(),
output_format: X86OutputFormat::Elf64,
arch,
output_path: None,
is_pic: false,
strip_debug: false,
entry_point: "_start".to_string(),
image_base: X86_64_DEFAULT_IMAGE_BASE,
enable_gc_sections: false,
enable_icf: false,
build_id: X86BuildIdKind::None,
hash_style: X86HashStyle::Both,
diagnostics: Vec::new(),
}
}
pub fn new_elf_x86_64() -> Self {
Self {
output_format: X86OutputFormat::Elf64,
arch: X86LLDArch::X86_64,
..Self::new()
}
}
pub fn new_elf_i386() -> Self {
let mut l = Self::new();
l.output_format = X86OutputFormat::Elf32;
l.arch = X86LLDArch::I386;
l.image_base = I386_DEFAULT_IMAGE_BASE;
l
}
pub fn new_coff_x86_64() -> Self {
let mut l = Self::new();
l.output_format = X86OutputFormat::Coff64;
l.arch = X86LLDArch::X86_64;
l.entry_point = "mainCRTStartup".to_string();
l.image_base = 0x140000000;
l
}
pub fn new_coff_i386() -> Self {
let mut l = Self::new();
l.output_format = X86OutputFormat::Coff32;
l.arch = X86LLDArch::I386;
l.entry_point = "_mainCRTStartup".to_string();
l.image_base = 0x400000;
l
}
pub fn set_output_path(&mut self, path: &str) {
self.output_path = Some(path.to_string());
}
pub fn set_pic(&mut self, pic: bool) {
self.is_pic = pic;
self.elf_linker.is_pic = pic;
if pic {
self.image_base = 0;
}
}
pub fn diag(&mut self, level: X86DiagLevel, msg: String) {
self.diagnostics.push(X86LinkerDiagnostic {
level,
message: msg,
});
}
pub fn add_object_file(&mut self, filename: &str, data: &[u8]) -> Result<(), String> {
match self.output_format {
X86OutputFormat::Elf32 | X86OutputFormat::Elf64 => {
self.elf_linker.add_object(filename, data.to_vec())
}
X86OutputFormat::Coff32 | X86OutputFormat::Coff64 => {
self.coff_linker.add_object(filename, data)
}
}
}
pub fn add_archive(&mut self, filename: &str, data: &[u8]) -> Result<(), String> {
match self.output_format {
X86OutputFormat::Elf32 | X86OutputFormat::Elf64 => {
self.elf_linker.add_archive(filename, data)
}
X86OutputFormat::Coff32 | X86OutputFormat::Coff64 => {
self.coff_linker.add_library(filename, data)
}
}
}
pub fn add_shared_library(&mut self, name: &str, _data: &[u8]) -> Result<(), String> {
self.elf_linker.add_shared_library(name)
}
pub fn link(&mut self) -> Result<Vec<u8>, Vec<X86LinkerDiagnostic>> {
self.diagnostics.clear();
self.symbol_table
.resolve_all(&self.elf_linker.objects, &self.elf_linker.shared_libs);
if self.enable_gc_sections {
self.gc_sections.run(
&mut self.elf_linker.input_files,
&self.symbol_table.entry_symbols(),
);
}
if self.enable_icf {
self.icf.run(&mut self.elf_linker.input_files);
}
match self.output_format {
X86OutputFormat::Elf32 | X86OutputFormat::Elf64 => {
self.elf_linker.link().map_err(|e| {
vec![X86LinkerDiagnostic {
level: X86DiagLevel::Error,
message: e,
}]
})?;
Ok(Vec::new())
}
X86OutputFormat::Coff32 | X86OutputFormat::Coff64 => {
self.coff_linker.link().map_err(|e| {
vec![X86LinkerDiagnostic {
level: X86DiagLevel::Error,
message: e,
}]
})?;
Ok(Vec::new())
}
}
}
pub fn emit(&mut self) -> Result<Vec<u8>, String> {
self.elf_linker.image_base = self.image_base;
self.elf_linker.entry_point = self.entry_point.clone();
self.coff_linker.image_base = self.image_base;
self.coff_linker.entry_point = self.entry_point.clone();
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>, Vec<X86LinkerDiagnostic>> {
self.link()?;
self.emit().map_err(|e| {
vec![X86LinkerDiagnostic {
level: X86DiagLevel::Error,
message: e,
}]
})
}
}
pub struct X86ELFLinker {
pub objects: Vec<X86InputObject>,
pub input_files: Vec<X86InputSection>,
pub shared_libs: Vec<X86SharedLibrary>,
pub sections: Vec<X86OutputSection>,
pub segments: Vec<X86OutputSegment>,
pub got_entries: Vec<X86GotEntry>,
pub plt_entries: Vec<X86PltEntry>,
pub rela_dyn: Vec<X86InputRelocation>,
pub rela_plt: Vec<X86InputRelocation>,
pub arch: X86LLDArch,
pub is_pic: bool,
pub is_shared: bool,
pub has_relro: bool,
pub has_gnu_property: bool,
pub got_start: u64,
pub got_plt_start: u64,
pub plt_start: u64,
pub got_address: u64,
pub dynamic_address: u64,
pub image_base: u64,
pub output_path: String,
pub entry_point: String,
pub diagnostics: Vec<X86LinkerDiagnostic>,
pub section_name_map: HashMap<String, usize>,
pub shstrtab: Vec<u8>,
pub strtab: Vec<u8>,
pub dynstr: Vec<u8>,
pub strtab_offsets: HashMap<String, usize>,
pub shstrtab_offsets: HashMap<String, usize>,
}
impl X86ELFLinker {
pub fn new(arch: X86LLDArch) -> Self {
let ib = if arch.is_64bit() {
X86_64_DEFAULT_IMAGE_BASE
} else {
I386_DEFAULT_IMAGE_BASE
};
Self {
objects: Vec::new(),
input_files: Vec::new(),
shared_libs: Vec::new(),
sections: Vec::new(),
segments: Vec::new(),
got_entries: Vec::new(),
plt_entries: Vec::new(),
rela_dyn: Vec::new(),
rela_plt: Vec::new(),
arch,
is_pic: false,
is_shared: false,
has_relro: false,
has_gnu_property: false,
got_start: 0,
got_plt_start: 0,
plt_start: 0,
got_address: 0,
dynamic_address: 0,
image_base: ib,
output_path: String::new(),
entry_point: "_start".to_string(),
diagnostics: Vec::new(),
section_name_map: HashMap::new(),
shstrtab: Vec::new(),
strtab: Vec::new(),
dynstr: Vec::new(),
strtab_offsets: HashMap::new(),
shstrtab_offsets: HashMap::new(),
}
}
pub fn add_object(&mut self, filename: &str, data: Vec<u8>) -> Result<(), String> {
let obj = self.parse_elf_object(filename, &data)?;
self.objects.push(obj);
Ok(())
}
pub fn add_archive(&mut self, filename: &str, data: &[u8]) -> Result<(), String> {
let ms = X86ArchiveReader::parse(data)?;
for m in &ms {
if !m.data.is_empty() {
if let Ok(obj) = self.parse_elf_object(&format!("{}:{}", filename, m.name), &m.data)
{
self.objects.push(obj);
}
}
}
Ok(())
}
pub fn add_shared_library(&mut self, name: &str) -> Result<(), String> {
let lib = self.parse_shared_library(name, &[])?;
self.shared_libs.push(lib);
Ok(())
}
pub fn parse_elf_object(&self, filename: &str, data: &[u8]) -> Result<X86InputObject, String> {
if data.len() < 64 {
return Err(format!("{}: too small", filename));
}
if data[0..4] != ELF_MAGIC_BYTES {
return Err(format!("{}: not ELF", filename));
}
let ec = data[EI_CLASS];
if ec != ELFCLASS32 && ec != ELFCLASS64 {
return Err(format!("{}: bad class", filename));
}
let is64 = ec == ELFCLASS64;
let machine = read_u16_le(data, 18);
let mut secs = Vec::new();
Ok(X86InputObject {
filename: filename.to_string(),
data: data.to_vec(),
sections: secs,
symbols: Vec::new(),
relocations: Vec::new(),
machine,
elf_class: ec,
})
}
pub fn parse_shared_library(
&self,
name: &str,
data: &[u8],
) -> Result<X86SharedLibrary, String> {
if data.len() < 64 || data[0..4] != ELF_MAGIC_BYTES {
return Err(format!("{}: not valid", name));
}
Ok(X86SharedLibrary {
name: name.to_string(),
symbols: Vec::new(),
needed: Vec::new(),
soname: Some(name.to_string()),
})
}
pub fn link(&mut self) -> Result<(), String> {
self.merge_sections();
self.compute_section_layout();
self.build_got();
self.build_plt();
self.generate_program_headers();
Ok(())
}
pub fn merge_sections(&mut self) {
let mut merged: HashMap<String, X86OutputSection> = HashMap::new();
for obj in &self.objects {
for sec in &obj.sections {
if sec.sh_type == SHT_NULL
|| sec.sh_type == SHT_SYMTAB
|| sec.sh_type == SHT_STRTAB
|| sec.sh_type == SHT_RELA
|| sec.sh_type == SHT_REL
{
continue;
}
let e = merged
.entry(sec.name.clone())
.or_insert_with(|| X86OutputSection {
name: sec.name.clone(),
data: Vec::new(),
sh_type: sec.sh_type,
sh_flags: sec.sh_flags,
sh_addralign: sec.sh_addralign,
sh_entsize: sec.sh_entsize,
segment_index: None,
vaddr: 0,
file_offset: 0,
output_index: 0,
is_merge_strings: false,
is_gc_eligible: sec.sh_flags & SHF_ALLOC as u64 != 0,
});
if sec.sh_addralign > e.sh_addralign {
e.sh_addralign = sec.sh_addralign;
}
let a = e.sh_addralign.max(1) as usize;
let p = (a - (e.data.len() % a)) % a;
e.data.extend(std::iter::repeat(0u8).take(p));
e.data.extend_from_slice(&sec.data);
}
}
let ord = [
".interp",
".gnu.hash",
".dynsym",
".dynstr",
".rela.dyn",
".rela.plt",
".init",
".plt",
".text",
".fini",
".rodata",
".eh_frame_hdr",
".eh_frame",
".init_array",
".fini_array",
".dynamic",
".got",
".got.plt",
".data",
".bss",
".comment",
];
let mut ordered = Vec::new();
for n in &ord {
if let Some(s) = merged.remove(*n) {
ordered.push(s);
}
}
for (_, s) in merged {
ordered.push(s);
}
for (i, s) in ordered.iter_mut().enumerate() {
s.output_index = i as u32;
self.section_name_map.insert(s.name.clone(), i);
}
self.sections = ordered;
}
pub fn compute_section_layout(&mut self) {
let ps = X86_64_PAGE_SIZE;
let hs = ELF64_EHDR_SIZE + ELF64_PHDR_SIZE * 10;
let mut fo = align_up(hs, ps);
let mut va = fo;
for sec in &mut self.sections {
let a = sec.sh_addralign.max(1);
va = align_up(va, a);
fo = align_up(fo, a);
sec.vaddr = va;
sec.file_offset = fo;
if sec.sh_type != SHT_NOBITS {
fo += sec.data.len() as u64;
}
va += sec.data.len() as u64;
}
}
pub fn build_got(&mut self) {
self.got_entries.clear();
let mut o = 0u64;
self.got_entries.push(X86GotEntry {
symbol_name: "_DYNAMIC".to_string(),
got_offset: o,
is_tls: false,
is_ifunc: false,
});
o += GOT_ENTRY_SIZE as u64;
self.got_entries.push(X86GotEntry {
symbol_name: "__link_map".to_string(),
got_offset: o,
is_tls: false,
is_ifunc: false,
});
o += GOT_ENTRY_SIZE as u64;
self.got_entries.push(X86GotEntry {
symbol_name: "_dl_runtime_resolve".to_string(),
got_offset: o,
is_tls: false,
is_ifunc: false,
});
}
pub fn build_plt(&mut self) {
self.plt_entries.clear();
self.plt_entries.push(X86PltEntry {
symbol_name: "__PLT0_RESOLVER".to_string(),
plt_offset: 0,
got_offset: 0,
});
}
pub fn create_got_entries(&mut self, syms: &[X86InputSymbol], relocs: &[X86InputRelocation]) {
for r in relocs {
if X86RelocationEngine::needs_got(r.rel_type) {
if let Some(s) = syms.get(r.symbol_index as usize) {
if !self.got_entries.iter().any(|e| e.symbol_name == s.name) {
let off = self.got_entries.len() as u64 * GOT_ENTRY_SIZE as u64;
self.got_entries.push(X86GotEntry {
symbol_name: s.name.clone(),
got_offset: off,
is_tls: false,
is_ifunc: s.sym_type == STT_GNU_IFUNC,
});
}
}
}
}
}
pub fn create_plt_entries(&mut self, syms: &[X86InputSymbol], relocs: &[X86InputRelocation]) {
for r in relocs {
if X86RelocationEngine::needs_plt(r.rel_type) {
if let Some(s) = syms.get(r.symbol_index as usize) {
if !self.plt_entries.iter().any(|e| e.symbol_name == s.name) {
let idx = self.plt_entries.len() as u64;
self.plt_entries.push(X86PltEntry {
symbol_name: s.name.clone(),
plt_offset: PLT0_SIZE as u64 + idx * PLT_ENTRY_SIZE as u64,
got_offset: (3 + idx) * GOT_ENTRY_SIZE as u64,
});
}
}
}
}
}
pub fn generate_program_headers(&mut self) {
self.segments.clear();
let ps = X86_64_PAGE_SIZE;
self.segments.push(X86OutputSegment {
p_type: PT_PHDR,
p_flags: PF_R,
p_align: 8,
p_vaddr: 64,
p_paddr: 64,
p_offset: 64,
p_filesz: ELF64_PHDR_SIZE * 10,
p_memsz: ELF64_PHDR_SIZE * 10,
section_indices: vec![],
});
self.segments.push(X86OutputSegment {
p_type: PT_GNU_STACK,
p_flags: PF_R | PF_W,
p_align: 16,
p_vaddr: 0,
p_paddr: 0,
p_offset: 0,
p_filesz: 0,
p_memsz: 0,
section_indices: vec![],
});
let mut ts = u64::MAX;
let mut te = 0u64;
let mut tv = Vec::new();
let mut ds = u64::MAX;
let mut de = 0u64;
let mut dv = Vec::new();
for (i, s) in self.sections.iter().enumerate() {
if s.sh_flags & SHF_ALLOC as u64 == 0 {
continue;
}
if s.sh_flags & SHF_WRITE as u64 != 0 {
ds = ds.min(s.vaddr);
de = de.max(s.vaddr + s.data.len() as u64);
dv.push(i);
} else {
ts = ts.min(s.vaddr);
te = te.max(s.vaddr + s.data.len() as u64);
tv.push(i);
}
}
if ts != u64::MAX {
self.segments.push(X86OutputSegment {
p_type: PT_LOAD,
p_flags: PF_R | PF_X,
p_align: ps,
p_vaddr: align_down(ts, ps),
p_paddr: align_down(ts, ps),
p_offset: align_down(ts, ps),
p_filesz: align_up(te, ps) - align_down(ts, ps),
p_memsz: align_up(te, ps) - align_down(ts, ps),
section_indices: tv,
});
}
if ds != u64::MAX {
self.segments.push(X86OutputSegment {
p_type: PT_LOAD,
p_flags: PF_R | PF_W,
p_align: ps,
p_vaddr: align_down(ds, ps),
p_paddr: align_down(ds, ps),
p_offset: align_down(ds, ps),
p_filesz: align_up(de, ps) - align_down(ds, ps),
p_memsz: align_up(de, ps) - align_down(ds, ps),
section_indices: dv,
});
}
}
pub fn collect_dt_needed(&self) -> Vec<String> {
let mut n = Vec::new();
for l in &self.shared_libs {
if let Some(ref s) = l.soname {
if !n.contains(s) {
n.push(s.clone());
}
} else if !n.contains(&l.name) {
n.push(l.name.clone());
}
}
n
}
pub fn build_dynamic_entries(
&self,
is64: bool,
dt: &[String],
dsa: u64,
dsta: u64,
ra: u64,
rs: u64,
ia: u64,
fa: u64,
) -> Vec<u8> {
let mut d = Vec::new();
for _ in dt {
Self::de(&mut d, is64, DT_NEEDED as u64, 0u64);
}
Self::de(&mut d, is64, DT_SYMTAB as u64, dsa);
Self::de(&mut d, is64, DT_STRTAB as u64, dsta);
Self::de(&mut d, is64, DT_STRSZ as u64, self.dynstr.len() as u64);
Self::de(&mut d, is64, DT_RELA as u64, ra);
Self::de(&mut d, is64, DT_RELASZ as u64, rs);
Self::de(
&mut d,
is64,
DT_RELAENT as u64,
if is64 { 24u64 } else { 12u64 },
);
Self::de(&mut d, is64, DT_INIT_ARRAY as u64, ia);
Self::de(&mut d, is64, DT_FINI_ARRAY as u64, fa);
Self::de(&mut d, is64, DT_PLTGOT as u64, self.got_plt_start);
Self::de(&mut d, is64, DT_JMPREL as u64, 0u64);
Self::de(
&mut d,
is64,
DT_PLTRELSZ as u64,
self.rela_plt.len() as u64 * if is64 { 24u64 } else { 12u64 },
);
Self::de(&mut d, is64, DT_PLTREL as u64, DT_RELA as u64);
Self::de(&mut d, is64, DT_NULL as u64, 0u64);
d
}
fn de(buf: &mut Vec<u8>, is64: bool, tag: u64, val: u64) {
if is64 {
buf.extend_from_slice(&tag.to_le_bytes());
buf.extend_from_slice(&val.to_le_bytes());
} else {
buf.extend_from_slice(&(tag as u32).to_le_bytes());
buf.extend_from_slice(&(val as u32).to_le_bytes());
}
}
pub fn build_gnu_hash(&self, syms: &[String]) -> Vec<u8> {
if syms.is_empty() {
return Vec::new();
}
let nb = next_power_of_two(syms.len() / 4).max(1);
let so = 1u32;
let bs = next_power_of_two(syms.len() / 8).max(1);
let mut d = Vec::new();
d.extend_from_slice(&nb.to_le_bytes());
d.extend_from_slice(&so.to_le_bytes());
d.extend_from_slice(&bs.to_le_bytes());
d.extend_from_slice(&5u32.to_le_bytes());
for _ in 0..bs {
d.extend_from_slice(&0u64.to_le_bytes());
}
for _ in 0..nb {
d.extend_from_slice(&0u32.to_le_bytes());
}
for s in syms {
d.extend_from_slice(&gnu_hash_bytes(s.as_bytes()).to_le_bytes());
}
d
}
pub fn build_sysv_hash(&self, syms: &[String]) -> Vec<u8> {
if syms.is_empty() {
return Vec::new();
}
let nb = next_power_of_two(syms.len() / 2).max(1);
let nc = syms.len() as u32;
let mut d = Vec::new();
d.extend_from_slice(&nb.to_le_bytes());
d.extend_from_slice(&nc.to_le_bytes());
for _ in 0..nb {
d.extend_from_slice(&0u32.to_le_bytes());
}
for _ in 0..nc {
d.extend_from_slice(&0u32.to_le_bytes());
}
d
}
pub fn compute_build_id(kind: X86BuildIdKind, data: &[u8]) -> Vec<u8> {
match kind {
X86BuildIdKind::None => Vec::new(),
X86BuildIdKind::Sha1 => hash_sha1(data),
X86BuildIdKind::Md5 => hash_md5(data),
X86BuildIdKind::Uuid => uuid_v4(),
X86BuildIdKind::Fast => hash_fast(data),
X86BuildIdKind::HexString => Vec::new(),
}
}
pub fn emit(&self) -> Result<Vec<u8>, String> {
let is64 = self.arch.is_64bit();
let ib = self.image_base;
let mut buf = Vec::new();
buf.extend_from_slice(&ELF_MAGIC_BYTES);
buf.push(if is64 {
ELFCLASS64 as u8
} else {
ELFCLASS32 as u8
});
buf.push(ELFDATA2LSB as u8);
buf.push(EV_CURRENT as u8);
buf.push(ELFOSABI_SYSV as u8);
buf.push(0u8);
buf.extend(std::iter::repeat(0u8).take(7));
let et: u16 = if self.is_shared { ET_DYN } else { ET_EXEC };
buf.extend_from_slice(&et.to_le_bytes());
buf.extend_from_slice(
&(if is64 {
EM_X86_64 as u16
} else {
EM_386 as u16
})
.to_le_bytes(),
);
buf.extend_from_slice(&(EV_CURRENT as u32).to_le_bytes());
buf.extend_from_slice(&ib.to_le_bytes());
if is64 {
buf.extend_from_slice(&ELF64_EHDR_SIZE.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.extend_from_slice(&0u32.to_le_bytes());
buf.extend_from_slice(&(if is64 { ELF64_EHDR_SIZE as u16 } else { 52u16 }).to_le_bytes());
buf.extend_from_slice(&(if is64 { ELF64_PHDR_SIZE as u16 } else { 32u16 }).to_le_bytes());
buf.extend_from_slice(&(self.segments.len() as u16).to_le_bytes());
buf.extend_from_slice(&(if is64 { ELF64_SHDR_SIZE as u16 } else { 40u16 }).to_le_bytes());
buf.extend_from_slice(&(self.sections.len() as u16).to_le_bytes());
buf.extend_from_slice(&0u16.to_le_bytes());
for seg in &self.segments {
buf.extend_from_slice(&seg.p_type.to_le_bytes());
if is64 {
buf.extend_from_slice(&seg.p_flags.to_le_bytes());
buf.extend_from_slice(&seg.p_offset.to_le_bytes());
buf.extend_from_slice(&seg.p_vaddr.to_le_bytes());
buf.extend_from_slice(&seg.p_paddr.to_le_bytes());
buf.extend_from_slice(&seg.p_filesz.to_le_bytes());
buf.extend_from_slice(&seg.p_memsz.to_le_bytes());
buf.extend_from_slice(&seg.p_align.to_le_bytes());
}
}
for sec in &self.sections {
while buf.len() < sec.file_offset as usize {
buf.push(0);
}
buf.extend_from_slice(&sec.data);
}
Ok(buf)
}
fn get_shstrndx(&self) -> u32 {
for (i, s) in self.sections.iter().enumerate() {
if s.name == ".shstrtab" {
return i as u32;
}
}
0
}
fn get_sh_name_offset(&self, name: &str) -> u32 {
self.shstrtab_offsets.get(name).copied().unwrap_or(0) as u32
}
}
const IMAGE_FILE_EXECUTABLE_IMAGE: u16 = 0x0002;
const LARGE_ADDRESS_AWARE: u16 = 0x0020;
const IMAGE_SCN_CNT_CODE: u32 = 0x00000020;
const IMAGE_SCN_CNT_INITIALIZED_DATA: u32 = 0x00000040;
const IMAGE_SCN_CNT_UNINITIALIZED_DATA: u32 = 0x00000080;
const DLL_NX_COMPAT: u16 = 0x0100;
const DLL_DYNAMIC_BASE: u16 = 0x0040;
const DLL_TERMINAL_SERVER_AWARE: u16 = 0x8000;
pub struct X86COFFLinker {
pub objects: Vec<X86CoffObject>,
pub symbols: HashMap<String, X86CoffResolvedSymbol>,
pub undefined: Vec<String>,
pub sections: Vec<X86CoffOutputSection>,
pub section_map: HashMap<String, usize>,
pub arch: X86LLDArch,
pub imports: Vec<X86CoffImport>,
pub exports: Vec<X86CoffExport>,
pub image_base: u64,
pub entry_point: String,
pub subsystem: X86CoffSubsystem,
}
impl X86COFFLinker {
pub fn new() -> Self {
Self {
objects: Vec::new(),
symbols: HashMap::new(),
undefined: Vec::new(),
sections: Vec::new(),
section_map: HashMap::new(),
arch: X86LLDArch::X86_64,
imports: Vec::new(),
exports: Vec::new(),
image_base: 0x140000000,
entry_point: "mainCRTStartup".to_string(),
subsystem: X86CoffSubsystem::Console,
}
}
pub fn add_object(&mut self, filename: &str, data: &[u8]) -> Result<(), String> {
let obj = parse_coff_object_full(filename, data)?;
self.objects.push(obj);
Ok(())
}
pub fn add_library(&mut self, _f: &str, _d: &[u8]) -> Result<(), String> {
Ok(())
}
pub fn add_import(&mut self, sn: &str, dn: &str, ord: Option<u16>, is_data: bool) {
self.imports.push(X86CoffImport {
symbol_name: sn.to_string(),
dll_name: dn.to_string(),
ordinal: ord,
is_data,
});
}
pub fn add_export(&mut self, n: &str, ord: u32, rva: u32) {
self.exports.push(X86CoffExport {
name: n.to_string(),
ordinal: ord,
rva,
forwarder: None,
});
}
pub fn link(&mut self) -> Result<(), String> {
self.resolve_symbols();
self.merge_coff_sections();
self.compute_coff_layout();
Ok(())
}
fn resolve_symbols(&mut self) {
for obj in &self.objects {
for sym in &obj.symbols {
let n = sym.name.clone();
if sym.section_number > 0 || sym.section_number == -1 {
self.symbols
.entry(n.clone())
.or_insert_with(|| X86CoffResolvedSymbol {
name: n.clone(),
value: sym.value as u64,
section_index: sym.section_number as u32,
is_external: sym.is_external,
});
} else if sym.section_number == 0 && !self.symbols.contains_key(&n) {
self.undefined.push(n.clone());
}
}
}
}
fn merge_coff_sections(&mut self) {
let mut merged: HashMap<String, Vec<u8>> = HashMap::new();
let mut cm: HashMap<String, u32> = HashMap::new();
let mut am: HashMap<String, u32> = HashMap::new();
for obj in &self.objects {
for sec in &obj.sections {
let e = merged.entry(sec.name.clone()).or_default();
let a = sec.align.max(1) as usize;
let p = (a - (e.len() % a)) % a;
e.extend(std::iter::repeat(0u8).take(p));
e.extend_from_slice(&sec.data);
cm.entry(sec.name.clone()).or_insert(sec.characteristics);
am.entry(sec.name.clone()).or_insert(sec.align);
}
}
self.sections.clear();
self.section_map.clear();
for n in &[
".text", ".rdata", ".data", ".bss", ".idata", ".edata", ".reloc",
] {
if let Some(d) = merged.remove(*n) {
let c = cm.get(*n).copied().unwrap_or(0);
let al = am.get(*n).copied().unwrap_or(16);
self.section_map.insert(n.to_string(), self.sections.len());
self.sections.push(X86CoffOutputSection {
name: n.to_string(),
data: d,
characteristics: c,
virtual_address: 0,
virtual_size: 0,
raw_offset: 0,
align: al,
});
}
}
for (n, d) in merged {
let c = cm.get(&n).copied().unwrap_or(0);
let al = am.get(&n).copied().unwrap_or(16);
self.section_map.insert(n.clone(), self.sections.len());
self.sections.push(X86CoffOutputSection {
name: n,
data: d,
characteristics: c,
virtual_address: 0,
virtual_size: 0,
raw_offset: 0,
align: al,
});
}
}
fn compute_coff_layout(&mut self) {
let sa = COFF_SECTION_ALIGNMENT as u64;
let fa = COFF_FILE_ALIGNMENT as u64;
let hs = 64 + 4 + 20 + 240;
let mut fo = align_up(hs, fa);
let mut va = sa;
for sec in &mut self.sections {
let a = sec.align.max(COFF_SECTION_ALIGNMENT) as u64;
va = align_up(va, a);
fo = align_up(fo, fa);
sec.virtual_address = va as u32;
sec.virtual_size = sec.data.len() as u32;
sec.raw_offset = fo as u32;
va += sec.data.len() as u64;
fo += sec.data.len() as u64;
}
}
pub fn emit(&self) -> Result<Vec<u8>, String> {
let is64 = self.arch.is_64bit();
let ib = self.image_base;
let ep = &self.entry_point;
let mut buf = Vec::new();
self.emit_dos_stub(&mut buf);
self.emit_pe_signature(&mut buf);
self.emit_coff_header(&mut buf, is64)?;
self.emit_optional_header(&mut buf, is64, ib, ep)?;
self.emit_section_headers(&mut buf)?;
for sec in &self.sections {
while buf.len() < sec.raw_offset as usize {
buf.push(0);
}
buf.extend_from_slice(&sec.data);
}
Ok(buf)
}
fn emit_dos_stub(&self, buf: &mut Vec<u8>) {
let mut d = vec![0u8; 64];
d[0] = b'M';
d[1] = b'Z';
d[60] = 64;
buf.extend_from_slice(&d);
}
fn emit_pe_signature(&self, buf: &mut Vec<u8>) {
buf.extend_from_slice(b"PE\0\0");
}
fn emit_coff_header(&self, buf: &mut Vec<u8>, is64: bool) -> Result<(), String> {
let m = if is64 {
IMAGE_FILE_MACHINE_AMD64
} else {
IMAGE_FILE_MACHINE_I386
};
buf.extend_from_slice(&m.to_le_bytes());
buf.extend_from_slice(&(self.sections.len() as u16).to_le_bytes());
buf.extend_from_slice(&0u32.to_le_bytes());
buf.extend_from_slice(&0u32.to_le_bytes());
buf.extend_from_slice(&0u32.to_le_bytes());
let oh = if is64 { 240u16 } else { 224u16 };
buf.extend_from_slice(&oh.to_le_bytes());
let mut c: u16 = IMAGE_FILE_EXECUTABLE_IMAGE;
if is64 {
c |= LARGE_ADDRESS_AWARE;
}
buf.extend_from_slice(&c.to_le_bytes());
Ok(())
}
fn emit_optional_header(
&self,
buf: &mut Vec<u8>,
is64: bool,
ib: u64,
ep: &str,
) -> Result<(), String> {
let m: u16 = if is64 { PE32PLUS_MAGIC } else { 0x010Bu16 };
buf.extend_from_slice(&m.to_le_bytes());
buf.push(14);
buf.push(0);
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(&self.find_symbol_rva(ep).to_le_bytes());
buf.extend_from_slice(&0u32.to_le_bytes());
if is64 {
buf.extend_from_slice(&ib.to_le_bytes());
} else {
buf.extend_from_slice(&(ib as u32).to_le_bytes());
}
buf.extend_from_slice(&COFF_SECTION_ALIGNMENT.to_le_bytes());
buf.extend_from_slice(&COFF_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());
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(&(self.subsystem as u16).to_le_bytes());
buf.extend_from_slice(
&(DLL_NX_COMPAT | DLL_DYNAMIC_BASE | DLL_TERMINAL_SERVER_AWARE).to_le_bytes(),
);
if is64 {
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());
} else {
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(&16u32.to_le_bytes());
for _ in 0..16 {
buf.extend_from_slice(&0u32.to_le_bytes());
buf.extend_from_slice(&0u32.to_le_bytes());
}
Ok(())
}
fn emit_section_headers(&self, buf: &mut Vec<u8>) -> Result<(), String> {
for sec in &self.sections {
let mut nb = [0u8; 8];
let n = sec.name.as_bytes();
nb[..n.len().min(8)].copy_from_slice(&n[..n.len().min(8)]);
buf.extend_from_slice(&nb);
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());
}
Ok(())
}
pub fn find_symbol_rva(&self, name: &str) -> u32 {
if let Some(s) = self.symbols.get(name) {
if s.section_index > 0 {
if let Some(sec) = self.sections.get(s.section_index as usize - 1) {
return sec.virtual_address + s.value as u32;
}
}
return s.value as u32;
}
0x1000
}
}
pub struct X86SymbolTable {
pub symbols: HashMap<String, X86LinkerSymbol>,
pub undefined: Vec<String>,
pub entries: Vec<String>,
pub common_allocations: HashMap<String, (u64, u64)>,
}
impl X86SymbolTable {
pub fn new() -> Self {
Self {
symbols: HashMap::new(),
undefined: Vec::new(),
entries: vec!["_start".to_string(), "main".to_string()],
common_allocations: HashMap::new(),
}
}
pub fn define(
&mut self,
name: &str,
value: u64,
size: u64,
binding: u8,
sym_type: u8,
vis: X86SymbolVisibility,
sec: &str,
is_fn: bool,
) {
if let Some(e) = self.symbols.get(name) {
match &e.def {
X86SymbolDef::Defined { .. } if binding != STB_WEAK => {}
X86SymbolDef::Weak { .. } => {}
_ => {}
}
}
self.symbols.insert(
name.to_string(),
X86LinkerSymbol {
name: name.to_string(),
def: X86SymbolDef::Defined {
value,
size,
section_name: sec.to_string(),
is_function: is_fn,
is_ifunc: sym_type == STT_GNU_IFUNC,
},
binding,
sym_type,
visibility: vis,
version: None,
is_referenced: false,
is_exported: binding == STB_GLOBAL || binding == STB_WEAK,
priority: 0,
is_absolute: false,
},
);
self.undefined.retain(|u| u != name);
}
pub fn reference(&mut self, name: &str) {
if !self.symbols.contains_key(name) {
self.undefined.push(name.to_string());
self.symbols.insert(
name.to_string(),
X86LinkerSymbol {
name: name.to_string(),
def: X86SymbolDef::Undefined,
binding: STB_GLOBAL,
sym_type: STT_NOTYPE,
visibility: X86SymbolVisibility::Default,
version: None,
is_referenced: true,
is_exported: false,
priority: 0,
is_absolute: false,
},
);
} else if let Some(s) = self.symbols.get_mut(name) {
s.is_referenced = true;
}
}
pub fn define_common(&mut self, name: &str, size: u64, alignment: u64) {
if let Some(e) = self.symbols.get(name) {
match &e.def {
X86SymbolDef::Defined { .. } | X86SymbolDef::Weak { .. } => return,
_ => {}
}
}
self.symbols.insert(
name.to_string(),
X86LinkerSymbol {
name: name.to_string(),
def: X86SymbolDef::Common { size, alignment },
binding: STB_GLOBAL,
sym_type: STT_OBJECT,
visibility: X86SymbolVisibility::Default,
version: None,
is_referenced: false,
is_exported: false,
priority: 0,
is_absolute: false,
},
);
}
pub fn define_weak(&mut self, name: &str, value: u64, size: u64, sec: &str) {
if self.symbols.contains_key(name) {
return;
}
self.symbols.insert(
name.to_string(),
X86LinkerSymbol {
name: name.to_string(),
def: X86SymbolDef::Weak {
value,
size,
section_name: sec.to_string(),
},
binding: STB_WEAK,
sym_type: STT_FUNC,
visibility: X86SymbolVisibility::Default,
version: None,
is_referenced: false,
is_exported: true,
priority: 0,
is_absolute: false,
},
);
}
pub fn add_shared_symbol(&mut self, name: &str, lib: &str) {
if !self.symbols.contains_key(name) {
self.symbols.insert(
name.to_string(),
X86LinkerSymbol {
name: name.to_string(),
def: X86SymbolDef::Shared {
library: lib.to_string(),
},
binding: STB_GLOBAL,
sym_type: STT_FUNC,
visibility: X86SymbolVisibility::Default,
version: None,
is_referenced: false,
is_exported: false,
priority: 0,
is_absolute: false,
},
);
}
}
pub fn get(&self, name: &str) -> Option<&X86LinkerSymbol> {
self.symbols.get(name)
}
pub fn resolve_all(&mut self, objs: &[X86InputObject], libs: &[X86SharedLibrary]) {
for obj in objs {
for sym in &obj.symbols {
let v = match sym.visibility {
STV_DEFAULT => X86SymbolVisibility::Default,
STV_HIDDEN => X86SymbolVisibility::Hidden,
STV_INTERNAL => X86SymbolVisibility::Internal,
STV_PROTECTED => X86SymbolVisibility::Protected,
_ => X86SymbolVisibility::Default,
};
if sym.section_index == SHN_COMMON {
self.define_common(&sym.name, sym.size, sym.common_alignment);
} else if sym.is_defined {
self.define(
&sym.name,
sym.value,
sym.size,
sym.binding,
sym.sym_type,
v,
"",
sym.sym_type == STT_FUNC,
);
} else if sym.section_index == SHN_UNDEF {
self.reference(&sym.name);
}
}
}
for lib in libs {
for sn in &lib.symbols {
self.add_shared_symbol(sn, &lib.name);
}
}
}
pub fn entry_symbols(&self) -> Vec<String> {
self.entries.clone()
}
pub fn check_undefined(&self) -> Vec<String> {
let mut u = Vec::new();
for n in &self.undefined {
if let Some(s) = self.symbols.get(n) {
if matches!(&s.def, X86SymbolDef::Undefined) {
u.push(n.clone());
}
}
}
u
}
pub fn resolve_commons(&mut self) -> (u64, HashMap<String, u64>) {
let mut ts = 0u64;
let mut offs = HashMap::new();
for (n, s) in &self.symbols {
if let X86SymbolDef::Common { size, alignment } = &s.def {
ts = align_up(ts, *alignment);
offs.insert(n.clone(), ts);
ts += *size;
}
}
(ts, offs)
}
pub fn apply_version_script(&mut self, globals: &[String], locals: &[String]) {
for (name, sym) in self.symbols.iter_mut() {
for lp in locals {
if wildcard_match(name, lp) {
sym.visibility = X86SymbolVisibility::Hidden;
sym.is_exported = false;
break;
}
}
for gp in globals {
if wildcard_match(name, gp) {
sym.is_exported = true;
break;
}
}
}
}
}
pub struct X86RelocationEngine {
action_cache: HashMap<u32, X86RelocAction>,
}
impl X86RelocationEngine {
pub fn new() -> Self {
let mut e = Self {
action_cache: HashMap::new(),
};
let ac = &mut e.action_cache;
ac.insert(R_X86_64_NONE, X86RelocAction::None);
ac.insert(R_X86_64_64, X86RelocAction::ApplyAbsolute64);
ac.insert(R_X86_64_PC32, X86RelocAction::ApplyPC32);
ac.insert(R_X86_64_GOT32, X86RelocAction::ApplyGOTPCREL);
ac.insert(R_X86_64_PLT32, X86RelocAction::ApplyPLT32);
ac.insert(R_X86_64_COPY, X86RelocAction::ApplyCopy);
ac.insert(R_X86_64_GLOB_DAT, X86RelocAction::ApplyAbsolute64);
ac.insert(R_X86_64_JUMP_SLOT, X86RelocAction::ApplyPLT32);
ac.insert(R_X86_64_RELATIVE, X86RelocAction::ApplyRelative);
ac.insert(R_X86_64_GOTPCREL, X86RelocAction::ApplyGOTPCREL);
ac.insert(R_X86_64_32, X86RelocAction::ApplyAbsolute32);
ac.insert(R_X86_64_32S, X86RelocAction::ApplyAbsolute32);
ac.insert(R_X86_64_16, X86RelocAction::ApplyAbsolute32);
ac.insert(R_X86_64_PC16, X86RelocAction::ApplyPC32);
ac.insert(R_X86_64_8, X86RelocAction::ApplyAbsolute32);
ac.insert(R_X86_64_PC8, X86RelocAction::ApplyPC32);
ac.insert(R_X86_64_PC64, X86RelocAction::ApplyPC64);
ac.insert(R_X86_64_GOTOFF64, X86RelocAction::ApplyAbsolute64);
ac.insert(R_X86_64_GOTPC32, X86RelocAction::ApplyPC32);
ac.insert(R_X86_64_SIZE32, X86RelocAction::ApplySize);
ac.insert(R_X86_64_SIZE64, X86RelocAction::ApplySize);
ac.insert(R_X86_64_DTPMOD64, X86RelocAction::ApplyDTPMOD);
ac.insert(R_X86_64_DTPOFF64, X86RelocAction::ApplyDTPOFF);
ac.insert(R_X86_64_TPOFF64, X86RelocAction::ApplyTPOFF);
ac.insert(R_X86_64_TLSGD, X86RelocAction::ApplyTLSGD);
ac.insert(R_X86_64_TLSLD, X86RelocAction::ApplyTLSLD);
ac.insert(R_X86_64_DTPOFF32, X86RelocAction::ApplyDTPOFF);
ac.insert(R_X86_64_GOTTPOFF, X86RelocAction::ApplyTPOFF);
ac.insert(R_X86_64_TPOFF32, X86RelocAction::ApplyTPOFF);
ac.insert(R_386_NONE, X86RelocAction::None);
ac.insert(R_386_32, X86RelocAction::ApplyAbsolute32);
ac.insert(R_386_PC32, X86RelocAction::ApplyPC32);
ac.insert(R_386_GOT32, X86RelocAction::ApplyGOTPCREL);
ac.insert(R_386_PLT32, X86RelocAction::ApplyPLT32);
ac.insert(R_386_COPY, X86RelocAction::ApplyCopy);
ac.insert(R_386_GLOB_DAT, X86RelocAction::ApplyAbsolute32);
ac.insert(R_386_JMP_SLOT, X86RelocAction::ApplyPLT32);
ac.insert(R_386_RELATIVE, X86RelocAction::ApplyRelative);
ac.insert(R_386_GOTOFF, X86RelocAction::ApplyAbsolute32);
ac.insert(R_386_GOTPC, X86RelocAction::ApplyPC32);
ac.insert(R_386_32PLT, X86RelocAction::ApplyPLT32);
ac.insert(R_386_TLS_GD, X86RelocAction::ApplyTLSGD);
ac.insert(R_386_TLS_LDM, X86RelocAction::ApplyTLSLD);
ac.insert(R_386_TLS_GD_32, X86RelocAction::ApplyTLSGD);
ac.insert(R_386_TLS_GD_PUSH, X86RelocAction::ApplyTLSGD);
ac.insert(R_386_TLS_GD_CALL, X86RelocAction::ApplyTLSGD);
ac.insert(R_386_TLS_GD_POP, X86RelocAction::ApplyTLSGD);
ac.insert(R_386_TLS_LDM_32, X86RelocAction::ApplyTLSLD);
ac.insert(R_386_TLS_LDM_PUSH, X86RelocAction::ApplyTLSLD);
ac.insert(R_386_TLS_LDM_CALL, X86RelocAction::ApplyTLSLD);
ac.insert(R_386_TLS_LDM_POP, X86RelocAction::ApplyTLSLD);
ac.insert(R_386_TLS_LDO_32, X86RelocAction::ApplyDTPOFF);
ac.insert(R_386_TLS_IE, X86RelocAction::ApplyTPOFF);
ac.insert(R_386_TLS_IE_32, X86RelocAction::ApplyTPOFF);
ac.insert(R_386_TLS_LE, X86RelocAction::ApplyTPOFF);
ac.insert(R_386_TLS_LE_32, X86RelocAction::ApplyTPOFF);
ac.insert(R_386_TLS_DTPMOD32, X86RelocAction::ApplyDTPMOD);
ac.insert(R_386_TLS_DTPOFF32, X86RelocAction::ApplyDTPOFF);
ac.insert(R_386_TLS_TPOFF, X86RelocAction::ApplyTPOFF);
ac.insert(R_386_TLS_TPOFF32, X86RelocAction::ApplyTPOFF);
ac.insert(R_386_IRELATIVE, X86RelocAction::ApplyIRELATIVE);
e
}
pub fn get_action(&self, rt: u32) -> X86RelocAction {
self.action_cache
.get(&rt)
.copied()
.unwrap_or(X86RelocAction::None)
}
pub fn compute_value(
&self,
reloc: &X86InputRelocation,
syms: &[X86InputSymbol],
_secs: &[X86InputSection],
st: &X86SymbolTable,
_base: u64,
sa: u64,
) -> Result<i64, String> {
let sym = syms.get(reloc.symbol_index as usize).ok_or("bad sym")?;
let sv = if sym.is_defined {
sa + sym.value
} else if let Some(r) = st.get(&sym.name) {
match &r.def {
X86SymbolDef::Defined { value, .. } | X86SymbolDef::Weak { value, .. } => *value,
X86SymbolDef::Common { size, .. } => *size as u64,
_ => 0,
}
} else {
0
};
let a = reloc.addend;
match self.get_action(reloc.rel_type) {
X86RelocAction::None => Ok(0),
X86RelocAction::ApplyAbsolute64 | X86RelocAction::ApplyAbsolute32 => {
Ok((sv as i64).wrapping_add(a))
}
X86RelocAction::ApplyPC32 | X86RelocAction::ApplyPC64 => {
let pc = sa + reloc.offset;
Ok((sv.wrapping_add(a as u64) as i64).wrapping_sub(pc as i64))
}
X86RelocAction::ApplyRelative => Ok((_base as i64).wrapping_add(a)),
_ => Ok(sv as i64),
}
}
pub fn apply(
&self,
reloc: &X86InputRelocation,
_secs: &[X86InputSection],
_syms: &[X86InputSymbol],
) -> Result<(), String> {
if self.get_action(reloc.rel_type) == X86RelocAction::None
&& reloc.rel_type != R_X86_64_NONE
&& reloc.rel_type != R_386_NONE
{
return Err(format!("unsupported reloc: {}", reloc.rel_type));
}
Ok(())
}
pub fn is_pc_relative(rt: u32) -> bool {
matches!(
rt,
R_X86_64_PC32
| R_X86_64_PC16
| R_X86_64_PC8
| R_X86_64_PC64
| R_X86_64_GOTPCREL
| R_X86_64_PLT32
| R_X86_64_GOTPC32
| R_386_GOTPC
)
}
pub fn needs_got(rt: u32) -> bool {
matches!(
rt,
R_X86_64_GOT32
| R_X86_64_GOTPCREL
| R_X86_64_GOTOFF64
| R_X86_64_GOTPC32
| R_X86_64_GLOB_DAT
| R_386_GOTPC
)
}
pub fn needs_plt(rt: u32) -> bool {
matches!(
rt,
R_X86_64_PLT32 | R_X86_64_JUMP_SLOT | R_386_PLT32 | R_386_JMP_SLOT
)
}
pub fn relocation_size(rt: u32) -> usize {
match rt {
R_X86_64_64 | R_X86_64_PC64 | R_X86_64_DTPMOD64 | R_X86_64_DTPOFF64
| R_X86_64_TPOFF64 | R_X86_64_GOTOFF64 | R_X86_64_SIZE64 => 8,
R_X86_64_32 | R_X86_64_32S | R_X86_64_PC32 | R_X86_64_GOT32 | R_X86_64_PLT32
| R_X86_64_GOTPCREL | R_X86_64_GOTPC32 | R_X86_64_SIZE32 | R_X86_64_DTPOFF32
| R_X86_64_GOTTPOFF | R_X86_64_TPOFF32 | R_X86_64_TLSGD | R_X86_64_TLSLD => 4,
R_X86_64_16 | R_X86_64_PC16 => 2,
R_X86_64_8 | R_X86_64_PC8 => 1,
R_386_IRELATIVE => 4,
_ => 0,
}
}
pub fn type_name(rt: u32) -> &'static str {
match rt {
R_X86_64_NONE => "R_X86_64_NONE",
R_X86_64_64 => "R_X86_64_64",
R_X86_64_PC32 => "R_X86_64_PC32",
R_X86_64_GOT32 => "R_X86_64_GOT32",
R_X86_64_PLT32 => "R_X86_64_PLT32",
R_X86_64_COPY => "R_X86_64_COPY",
R_X86_64_GLOB_DAT => "R_X86_64_GLOB_DAT",
R_X86_64_JUMP_SLOT => "R_X86_64_JUMP_SLOT",
R_X86_64_RELATIVE => "R_X86_64_RELATIVE",
R_X86_64_GOTPCREL => "R_X86_64_GOTPCREL",
R_X86_64_32 => "R_X86_64_32",
R_X86_64_32S => "R_X86_64_32S",
R_X86_64_16 => "R_X86_64_16",
R_X86_64_PC16 => "R_X86_64_PC16",
R_X86_64_8 => "R_X86_64_8",
R_X86_64_PC8 => "R_X86_64_PC8",
R_X86_64_PC64 => "R_X86_64_PC64",
R_X86_64_GOTOFF64 => "R_X86_64_GOTOFF64",
R_X86_64_GOTPC32 => "R_X86_64_GOTPC32",
R_X86_64_SIZE32 => "R_X86_64_SIZE32",
R_X86_64_SIZE64 => "R_X86_64_SIZE64",
R_X86_64_DTPMOD64 => "R_X86_64_DTPMOD64",
R_X86_64_DTPOFF64 => "R_X86_64_DTPOFF64",
R_X86_64_TPOFF64 => "R_X86_64_TPOFF64",
R_X86_64_TLSGD => "R_X86_64_TLSGD",
R_X86_64_TLSLD => "R_X86_64_TLSLD",
R_X86_64_DTPOFF32 => "R_X86_64_DTPOFF32",
R_X86_64_GOTTPOFF => "R_X86_64_GOTTPOFF",
R_X86_64_TPOFF32 => "R_X86_64_TPOFF32",
R_386_IRELATIVE => "R_386_IRELATIVE",
_ => "UNKNOWN",
}
}
}
#[derive(Debug, Clone)]
pub struct X86ScriptMemoryRegion {
pub name: String,
pub origin: u64,
pub length: u64,
pub attributes: X86MemoryAttrs,
}
#[derive(Debug, Clone)]
pub struct X86MemoryAttrs {
pub readable: bool,
pub writable: bool,
pub executable: bool,
pub allocatable: bool,
}
#[derive(Debug, Clone)]
pub struct X86ScriptPhdr {
pub name: String,
pub ptype: u32,
pub flags: u32,
pub filehdr: bool,
pub phdrs: bool,
}
#[derive(Debug, Clone)]
pub struct X86ScriptSectionCommand {
pub name: String,
pub vma: Option<u64>,
pub lma: Option<u64>,
pub align: Option<u64>,
pub input_patterns: Vec<X86ScriptSectionPattern>,
pub keep_patterns: Vec<String>,
pub fill_value: Option<u32>,
pub section_type: X86ScriptSectionType,
pub sub_commands: Vec<X86ScriptSubCommand>,
}
#[derive(Debug, Clone)]
pub struct X86ScriptSectionPattern {
pub file_pattern: Option<String>,
pub section_pattern: String,
pub exclude_files: Vec<String>,
}
#[derive(Debug, Clone)]
pub enum X86ScriptSubCommand {
SymbolAssign(String, String),
Fill(u32),
Align(u64),
Assert(String, String),
Include(String),
}
#[derive(Debug, Clone)]
pub struct X86ScriptAssignment {
pub symbol: String,
pub expression: String,
pub is_provide: bool,
pub is_hidden: bool,
}
pub struct X86LinkerScript {
pub output_format: Option<String>,
pub output_arch: Option<String>,
pub entry: Option<String>,
pub search_dirs: Vec<String>,
pub input_files: Vec<String>,
pub memory_regions: Vec<X86ScriptMemoryRegion>,
pub phdrs: Vec<X86ScriptPhdr>,
pub sections: Vec<X86ScriptSectionCommand>,
pub assignments: Vec<X86ScriptAssignment>,
pub keep_sections: Vec<String>,
}
impl X86LinkerScript {
pub fn new() -> Self {
Self {
output_format: None,
output_arch: None,
entry: None,
search_dirs: Vec::new(),
input_files: Vec::new(),
memory_regions: Vec::new(),
phdrs: Vec::new(),
sections: Vec::new(),
assignments: Vec::new(),
keep_sections: Vec::new(),
}
}
pub fn parse(script: &str) -> Result<Self, String> {
let mut ls = Self::new();
let tokens = tokenize_linker_script(script);
let mut pos = 0;
while pos < tokens.len() {
let t = &tokens[pos];
match t.to_uppercase().as_str() {
"OUTPUT_FORMAT" => {
pos += 1;
if pos < tokens.len() {
ls.output_format = Some(tokens[pos].trim_matches('"').to_string());
pos += 1;
}
}
"OUTPUT_ARCH" => {
pos += 1;
if pos < tokens.len() {
ls.output_arch = Some(tokens[pos].trim_matches('"').to_string());
pos += 1;
}
}
"ENTRY" => {
pos += 1;
if pos < tokens.len() {
ls.entry = Some(tokens[pos].trim_matches('"').to_string());
pos += 1;
}
}
"SEARCH_DIR" => {
pos += 1;
if pos < tokens.len() {
ls.search_dirs
.push(tokens[pos].trim_matches('"').to_string());
pos += 1;
}
}
"MEMORY" => {
pos += 1;
while pos < tokens.len() && tokens[pos] != "}" {
if pos + 3 < tokens.len() {
let n = tokens[pos].clone();
pos += 1;
let mut o = 0u64;
let mut l = 0u64;
while pos < tokens.len() && tokens[pos] != "}" && tokens[pos] != ";" {
match tokens[pos].to_uppercase().as_str() {
"ORIGIN" | "ORG" => {
pos += 2;
if pos < tokens.len() {
o = parse_script_number(&tokens[pos]);
pos += 1;
}
}
"LENGTH" | "LEN" => {
pos += 2;
if pos < tokens.len() {
l = parse_script_number(&tokens[pos]);
pos += 1;
}
}
_ => {
pos += 1;
}
}
}
ls.memory_regions.push(X86ScriptMemoryRegion {
name: n,
origin: o,
length: l,
attributes: X86MemoryAttrs {
readable: true,
writable: true,
executable: false,
allocatable: true,
},
});
}
pos += 1;
}
}
"PHDRS" => {
pos += 1;
while pos < tokens.len() && tokens[pos] != "}" {
let n = tokens[pos].clone();
pos += 1;
let pt = {
let upper = tokens.get(pos).map(|s| s.to_uppercase());
match upper.as_deref() {
Some("PT_LOAD") => PT_LOAD,
Some("PT_DYNAMIC") => PT_DYNAMIC,
Some("PT_TLS") => PT_TLS,
_ => PT_LOAD,
}
};
pos += 1;
while pos < tokens.len() && tokens[pos] != ";" && tokens[pos] != "}" {
pos += 1;
}
ls.phdrs.push(X86ScriptPhdr {
name: n,
ptype: pt,
flags: PF_R,
filehdr: false,
phdrs: false,
});
if pos < tokens.len() && tokens[pos] == ";" {
pos += 1;
}
}
}
"SECTIONS" => {
pos += 1;
while pos < tokens.len() && tokens[pos] != "}" {
if tokens[pos] == "." {
pos += 1;
let mut sc = X86ScriptSectionCommand {
name: String::new(),
vma: None,
lma: None,
align: None,
input_patterns: Vec::new(),
keep_patterns: Vec::new(),
fill_value: None,
section_type: X86ScriptSectionType::Progbits,
sub_commands: Vec::new(),
};
while pos < tokens.len()
&& !tokens[pos].starts_with(':')
&& tokens[pos] != "{"
{
sc.name.push_str(&tokens[pos]);
pos += 1;
}
if pos < tokens.len() && tokens[pos] == "{" {
pos += 1;
while pos < tokens.len() && tokens[pos] != "}" {
if tokens[pos].starts_with("*(") {
let inner = tokens[pos]
.trim_start_matches("*(")
.trim_end_matches(')');
sc.input_patterns.push(X86ScriptSectionPattern {
file_pattern: None,
section_pattern: inner.to_string(),
exclude_files: Vec::new(),
});
}
pos += 1;
}
}
if !sc.name.is_empty() {
ls.sections.push(sc);
}
}
pos += 1;
}
}
_ => {
if pos + 2 < tokens.len() && tokens[pos + 1] == "=" {
let s = t.clone();
pos += 2;
let e = tokens[pos].clone();
pos += 1;
ls.assignments.push(X86ScriptAssignment {
symbol: s,
expression: e,
is_provide: false,
is_hidden: false,
});
} else {
pos += 1;
}
}
}
}
Ok(ls)
}
pub fn evaluate_expression(expr: &str, syms: &HashMap<String, u64>, dot: u64) -> Option<u64> {
if expr == "." {
return Some(dot);
}
if expr.starts_with("ALIGN(") {
return Some(align_up(dot, parse_script_number(&expr[6..expr.len() - 1])));
}
if let Ok(n) = expr.trim().parse::<u64>() {
return Some(n);
}
if expr.starts_with("0x") || expr.starts_with("0X") {
return u64::from_str_radix(&expr[2..], 16).ok();
}
if let Some(&v) = syms.get(expr) {
return Some(v);
}
if let Some(pp) = expr.find('+') {
let l = Self::evaluate_expression(&expr[..pp].trim(), syms, dot)?;
let r = Self::evaluate_expression(&expr[pp + 1..].trim(), syms, dot)?;
return Some(l.wrapping_add(r));
}
None
}
}
pub struct X86ArchiveWriter {
pub members: Vec<X86ArchiveMember>,
}
impl X86ArchiveWriter {
pub fn new() -> Self {
Self {
members: Vec::new(),
}
}
pub fn add_member(&mut self, name: &str, data: &[u8]) {
self.members.push(X86ArchiveMember {
name: name.to_string(),
data: data.to_vec(),
symbols: Vec::new(),
timestamp: 0,
uid: 0,
gid: 0,
mode: 0o644,
});
}
pub fn build(&self) -> Result<Vec<u8>, String> {
let mut b = Vec::new();
b.extend_from_slice(b"!<arch>\n");
let st = self.build_symtab()?;
self.emit_member(&mut b, "/", &st)?;
let sst = self.build_strtab()?;
self.emit_member(&mut b, "//", &sst)?;
for m in &self.members {
self.emit_member(&mut b, &m.name, &m.data)?;
}
Ok(b)
}
fn build_symtab(&self) -> Result<Vec<u8>, String> {
let mut d = Vec::new();
let cnt = self.members.iter().map(|m| m.symbols.len()).sum::<usize>() as u32;
d.extend_from_slice(&cnt.to_be_bytes());
let mut off: u32 = 0;
for m in &self.members {
for _ in &m.symbols {
d.extend_from_slice(&off.to_be_bytes());
}
off += (align_up(m.data.len() as u64, 2) + 60) as u32;
}
for m in &self.members {
for s in &m.symbols {
d.extend_from_slice(s.as_bytes());
d.push(0);
}
}
Ok(d)
}
fn build_strtab(&self) -> Result<Vec<u8>, String> {
let mut d = Vec::new();
for m in &self.members {
if m.name.len() > 15 {
d.extend_from_slice(m.name.as_bytes());
d.extend_from_slice(b"/\n");
}
}
Ok(d)
}
fn emit_member(&self, buf: &mut Vec<u8>, name: &str, data: &[u8]) -> Result<(), String> {
let mut h = vec![0u8; 60];
let nb = name.as_bytes();
for (i, &b) in nb.iter().enumerate().take(16) {
h[i] = b;
}
if nb.len() < 16 {
h[nb.len()] = b'/';
}
let ts = format!("{:>12}", 0);
h[16..28].copy_from_slice(ts.as_bytes());
h[28..34].copy_from_slice(b" 0");
h[34..40].copy_from_slice(b" 0");
h[40..48].copy_from_slice(b"100644 ");
let sz = format!("{:>10}", data.len());
h[48..58].copy_from_slice(sz.as_bytes());
h[58] = b'`';
h[59] = b'\n';
buf.extend_from_slice(&h);
buf.extend_from_slice(data);
if data.len() % 2 != 0 {
buf.push(b'\n');
}
Ok(())
}
}
pub struct X86ArchiveReader;
impl X86ArchiveReader {
pub fn parse(data: &[u8]) -> Result<Vec<X86ArchiveMember>, String> {
if data.len() < 8 || &data[0..8] != b"!<arch>\n" {
return Err("not archive".to_string());
}
let mut ms = Vec::new();
let mut pos = 8;
while pos + 60 <= data.len() {
let h = &data[pos..pos + 60];
let ne = h[0..16]
.iter()
.position(|&b| b == b'/' || b == b' ')
.unwrap_or(16);
let n = std::str::from_utf8(&h[..ne])
.unwrap_or("")
.trim()
.to_string();
let sz: usize = std::str::from_utf8(&h[48..58])
.unwrap_or("0")
.trim()
.parse()
.unwrap_or(0);
pos += 60;
if pos + sz > data.len() {
break;
}
let d = data[pos..pos + sz].to_vec();
pos += sz;
if sz % 2 != 0 {
pos += 1;
}
ms.push(X86ArchiveMember {
name: n,
data: d,
symbols: Vec::new(),
timestamp: 0,
uid: 0,
gid: 0,
mode: 0o644,
});
}
Ok(ms)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86LTOModel {
Full,
Thin,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86LTOOptLevel {
O0,
O1,
O2,
O3,
}
pub struct X86LTOPlugin {
pub options: X86LTOOptions,
pub bitcode_files: Vec<(String, Vec<u8>)>,
}
#[derive(Debug, Clone)]
pub struct X86LTOOptions {
pub cpu: String,
pub features: String,
pub opt_level: X86LTOOptLevel,
pub debug_info: bool,
pub lto_model: X86LTOModel,
pub thin_lto_cache_dir: Option<String>,
}
impl Default for X86LTOOptions {
fn default() -> Self {
Self {
cpu: "x86-64".to_string(),
features: String::new(),
opt_level: X86LTOOptLevel::O2,
debug_info: false,
lto_model: X86LTOModel::Full,
thin_lto_cache_dir: None,
}
}
}
#[derive(Debug, Clone)]
pub struct X86LTOBitcodeInfo {
pub is_bitcode: bool,
pub is_thin_lto: bool,
pub target_triple: Option<String>,
pub symbol_names: Vec<String>,
}
impl X86LTOPlugin {
pub fn new() -> Self {
Self {
options: X86LTOOptions::default(),
bitcode_files: Vec::new(),
}
}
pub fn detect_bitcode(data: &[u8]) -> bool {
if data.len() < 4 {
false
} else {
let m = read_u32_le(data, 0);
m == 0xDEC04342 || m == 0x0B17C0DE
}
}
pub fn analyze_bitcode(data: &[u8]) -> X86LTOBitcodeInfo {
if !Self::detect_bitcode(data) {
return X86LTOBitcodeInfo {
is_bitcode: false,
is_thin_lto: false,
target_triple: None,
symbol_names: Vec::new(),
};
}
X86LTOBitcodeInfo {
is_bitcode: true,
is_thin_lto: read_u32_le(data, 0) == 0x0B17C0DE,
target_triple: Some("x86_64-unknown-linux-gnu".to_string()),
symbol_names: Vec::new(),
}
}
pub fn run_full_lto(&self, bcf: &[(String, Vec<u8>)]) -> Result<Vec<Vec<u8>>, String> {
let mut o = Vec::new();
for (f, d) in bcf {
if !Self::detect_bitcode(d) {
return Err(format!("{}: not bitcode", f));
}
o.push(d.clone());
}
Ok(o)
}
pub fn run_thin_lto(&self, bcf: &[(String, Vec<u8>)]) -> Result<Vec<Vec<u8>>, String> {
self.run_full_lto(bcf)
}
pub fn should_process(&self, data: &[u8]) -> bool {
Self::detect_bitcode(data)
}
}
pub struct X86ICF {
pub min_size: usize,
pub fold_rodata: bool,
pub functions_only: bool,
}
impl X86ICF {
pub fn new() -> Self {
Self {
min_size: 16,
fold_rodata: false,
functions_only: true,
}
}
pub fn run(&mut self, sections: &mut [X86InputSection]) -> usize {
if sections.is_empty() {
return 0;
}
let mut rm = 0;
let mut hg: HashMap<u64, Vec<usize>> = HashMap::new();
for (i, s) in sections.iter().enumerate() {
if s.data.len() < self.min_size {
continue;
}
if self.functions_only && s.sh_flags & SHF_EXECINSTR as u64 == 0 {
continue;
}
let h = compute_fnv1a_hash(&s.data);
hg.entry(h).or_default().push(i);
}
for indices in hg.values() {
if indices.len() < 2 {
continue;
}
let mut keep = vec![true; indices.len()];
for i in 0..indices.len() {
if !keep[i] {
continue;
}
for j in i + 1..indices.len() {
if !keep[j] {
continue;
}
let a = §ions[indices[i]];
let b = §ions[indices[j]];
if a.data.len() == b.data.len() && a.sh_flags == b.sh_flags && a.data == b.data
{
keep[j] = false;
sections[indices[j]].data.clear();
rm += 1;
}
}
}
}
rm
}
pub fn hash_section(data: &[u8], flags: u64) -> u64 {
let mut h = compute_fnv1a_hash(data);
h ^= flags;
h
}
}
pub struct X86GCSections {
pub keep_patterns: Vec<String>,
pub keep_eh: bool,
pub keep_debug: bool,
pub keep_init_fini: bool,
}
impl X86GCSections {
pub fn new() -> Self {
Self {
keep_patterns: vec![
".init".to_string(),
".fini".to_string(),
".init_array".to_string(),
".fini_array".to_string(),
".preinit_array".to_string(),
".ctors".to_string(),
".dtors".to_string(),
],
keep_eh: false,
keep_debug: false,
keep_init_fini: true,
}
}
pub fn run(&mut self, sections: &mut [X86InputSection], entries: &[String]) -> usize {
let n = sections.len();
if n == 0 {
return 0;
}
let g = self.build_ref_graph(sections);
let r = self.find_roots(sections, entries);
let live = self.mark_live(&g, &r, n);
let rm = live.iter().filter(|&&l| !l).count();
for (i, s) in sections.iter_mut().enumerate() {
if i < live.len() && !live[i] {
s.data.clear();
}
}
rm
}
fn build_ref_graph(&self, secs: &[X86InputSection]) -> Vec<Vec<usize>> {
let n = secs.len();
let mut g = vec![Vec::new(); n];
let nm: HashMap<&str, usize> = secs
.iter()
.enumerate()
.map(|(i, s)| (s.name.as_str(), i))
.collect();
for (i, s) in secs.iter().enumerate() {
for (j, o) in secs.iter().enumerate() {
if i == j {
continue;
}
if o.name.as_bytes().len() >= 2
&& s.data
.windows(o.name.as_bytes().len())
.any(|w| w == o.name.as_bytes())
{
g[i].push(j);
}
}
if s.name.starts_with(".text") {
for dep in &[".plt", ".got", ".got.plt"] {
if let Some(&idx) = nm.get(dep) {
if !g[i].contains(&idx) {
g[i].push(idx);
}
}
}
}
}
g
}
fn find_roots(&self, secs: &[X86InputSection], entries: &[String]) -> Vec<usize> {
let mut r = Vec::new();
for (i, s) in secs.iter().enumerate() {
if self
.keep_patterns
.iter()
.any(|p| s.name.starts_with(p.as_str()))
{
r.push(i);
continue;
}
for e in entries {
if s.name.contains(e.as_str()) {
r.push(i);
break;
}
}
if self.keep_debug && s.name.starts_with(".debug_") {
r.push(i);
}
if self.keep_eh && (s.name.starts_with(".eh_frame") || s.name == ".gcc_except_table") {
r.push(i);
}
}
r
}
fn mark_live(&self, g: &[Vec<usize>], roots: &[usize], n: usize) -> Vec<bool> {
let mut live = vec![false; n];
let mut q = VecDeque::new();
for &r in roots {
if r < n {
live[r] = true;
q.push_back(r);
}
}
while let Some(c) = q.pop_front() {
for &d in &g[c] {
if d < n && !live[d] {
live[d] = true;
q.push_back(d);
}
}
}
live
}
}
pub fn parse_coff_object_full(filename: &str, data: &[u8]) -> Result<X86CoffObject, String> {
if data.len() < 20 {
return Err(format!("{}: too small", filename));
}
let machine = read_u16_le(data, 0);
let ns = read_u16_le(data, 2);
let sto = read_u32_le(data, 8) as usize;
let nss = read_u32_le(data, 12) as usize;
let ohs = read_u16_le(data, 16) as usize;
let hdr = 20 + ohs;
let mut secs = Vec::new();
for i in 0..ns as usize {
let so = hdr + i * 40;
if so + 40 > data.len() {
break;
}
let ne = data[so..so + 8].iter().position(|&b| b == 0).unwrap_or(8);
let n = std::str::from_utf8(&data[so..so + ne])
.unwrap_or(".unknown")
.to_string();
let vs = read_u32_le(data, so + 8);
let srd = read_u32_le(data, so + 16);
let prd = read_u32_le(data, so + 20);
let ch = read_u32_le(data, so + 36);
let sd = if prd > 0 && prd as usize + srd.max(vs) as usize <= data.len() {
data[prd as usize..prd as usize + srd.max(vs) as usize].to_vec()
} else if vs > 0 {
vec![0u8; vs as usize]
} else {
Vec::new()
};
secs.push(X86CoffSection {
name: n,
data: sd,
characteristics: ch,
align: 16,
section_index: (i + 1) as u32,
});
}
let mut syms = Vec::new();
for i in 0..nss {
let so = sto + i * 18;
if so + 18 > data.len() {
break;
}
let ne = data[so..so + 8].iter().position(|&b| b == 0).unwrap_or(8);
let n = std::str::from_utf8(&data[so..so + ne])
.unwrap_or("")
.to_string()
.trim_matches('\0')
.to_string();
let v = read_u32_le(data, so);
let sn = read_u16_le(data, so + 12) as i16;
let st = read_u16_le(data, so + 14);
let sc = data[so + 16];
let na = data[so + 17];
syms.push(X86CoffSymbol {
name: n,
value: v,
section_number: sn,
sym_type: st,
storage_class: sc,
number_of_aux_symbols: na,
is_external: sc == 2,
});
}
Ok(X86CoffObject {
filename: filename.to_string(),
data: data.to_vec(),
sections: secs,
symbols: syms,
relocations: Vec::new(),
machine,
})
}
#[cfg(test)]
mod tests {
use super::*;
fn make_elf_obj(name: &str, secs: Vec<(&str, Vec<u8>)>) -> X86InputObject {
let mut s = Vec::new();
for (i, (sn, d)) in secs.iter().enumerate() {
s.push(X86InputSection {
name: sn.to_string(),
data: d.clone(),
sh_type: SHT_PROGBITS,
sh_flags: if *sn == ".text" {
(SHF_ALLOC | SHF_EXECINSTR) as u64
} else if *sn == ".data" {
(SHF_ALLOC | SHF_WRITE) as u64
} else {
SHF_ALLOC as u64
},
sh_addralign: 16,
section_index: i as u32,
sh_entsize: 0,
});
}
X86InputObject {
filename: name.to_string(),
data: Vec::new(),
sections: s,
symbols: Vec::new(),
relocations: Vec::new(),
machine: EM_X86_64,
elf_class: ELFCLASS64,
}
}
#[test]
fn t_lld_create() {
let l = X86LLDLinker::new();
assert_eq!(l.output_format, X86OutputFormat::Elf64);
}
#[test]
fn t_lld_elf64() {
let l = X86LLDLinker::new_elf_x86_64();
assert_eq!(l.arch, X86LLDArch::X86_64);
}
#[test]
fn t_lld_elf32() {
let l = X86LLDLinker::new_elf_i386();
assert_eq!(l.arch, X86LLDArch::I386);
}
#[test]
fn t_lld_coff64() {
let l = X86LLDLinker::new_coff_x86_64();
assert_eq!(l.output_format, X86OutputFormat::Coff64);
}
#[test]
fn t_lld_coff32() {
let l = X86LLDLinker::new_coff_i386();
assert_eq!(l.arch, X86LLDArch::I386);
}
#[test]
fn t_arch_64bit() {
assert!(X86LLDArch::X86_64.is_64bit());
assert!(!X86LLDArch::I386.is_64bit());
}
#[test]
fn t_arch_ptr() {
assert_eq!(X86LLDArch::X86_64.pointer_size(), 8);
assert_eq!(X86LLDArch::I386.pointer_size(), 4);
}
#[test]
fn t_arch_elf_machine() {
assert_eq!(X86LLDArch::X86_64.elf_machine(), EM_X86_64);
assert_eq!(X86LLDArch::I386.elf_machine(), EM_386);
}
#[test]
fn t_arch_coff_machine() {
assert_eq!(X86LLDArch::X86_64.coff_machine(), IMAGE_FILE_MACHINE_AMD64);
}
#[test]
fn t_elf_create() {
let e = X86ELFLinker::new(X86LLDArch::X86_64);
assert!(e.objects.is_empty());
}
#[test]
fn t_elf_merge() {
let mut e = X86ELFLinker::new(X86LLDArch::X86_64);
e.objects
.push(make_elf_obj("a.o", vec![(".text", vec![0x90; 10])]));
e.objects
.push(make_elf_obj("b.o", vec![(".text", vec![0xC3])]));
e.merge_sections();
assert!(e.sections.iter().any(|s| s.name == ".text"));
}
#[test]
fn t_elf_got() {
let mut e = X86ELFLinker::new(X86LLDArch::X86_64);
e.build_got();
assert_eq!(e.got_entries.len(), 3);
}
#[test]
fn t_elf_plt() {
let mut e = X86ELFLinker::new(X86LLDArch::X86_64);
e.build_plt();
assert_eq!(e.plt_entries.len(), 1);
}
#[test]
fn t_elf_phdrs() {
let mut e = X86ELFLinker::new(X86LLDArch::X86_64);
e.sections.push(X86OutputSection {
name: ".text".to_string(),
data: vec![0x90; 64],
sh_type: SHT_PROGBITS,
sh_flags: (SHF_ALLOC | SHF_EXECINSTR) as u64,
sh_addralign: 16,
sh_entsize: 0,
segment_index: None,
vaddr: 0x401000,
file_offset: 0x1000,
output_index: 0,
is_merge_strings: false,
is_gc_eligible: true,
});
e.generate_program_headers();
assert!(!e.segments.is_empty());
}
#[test]
fn t_elf_emit() {
let e = X86ELFLinker::new(X86LLDArch::X86_64);
let r = e.emit();
assert!(r.is_ok());
assert_eq!(&r.unwrap()[0..4], &ELF_MAGIC_BYTES);
}
#[test]
fn t_elf_got_entries() {
let mut e = X86ELFLinker::new(X86LLDArch::X86_64);
let syms = vec![X86InputSymbol {
name: "ext".to_string(),
value: 0,
size: 0,
binding: STB_GLOBAL,
sym_type: STT_FUNC,
visibility: STV_DEFAULT,
section_index: SHN_UNDEF,
is_defined: false,
common_alignment: 0,
}];
let relocs = vec![X86InputRelocation {
offset: 0,
symbol_index: 0,
rel_type: R_X86_64_GOTPCREL,
addend: -4,
section_index: 0,
}];
e.create_got_entries(&syms, &relocs);
assert!(e.got_entries.iter().any(|x| x.symbol_name == "ext"));
}
#[test]
fn t_elf_plt_entries() {
let mut e = X86ELFLinker::new(X86LLDArch::X86_64);
e.build_plt();
let syms = vec![X86InputSymbol {
name: "printf".to_string(),
value: 0,
size: 0,
binding: STB_GLOBAL,
sym_type: STT_FUNC,
visibility: STV_DEFAULT,
section_index: SHN_UNDEF,
is_defined: false,
common_alignment: 0,
}];
let relocs = vec![X86InputRelocation {
offset: 0,
symbol_index: 0,
rel_type: R_X86_64_PLT32,
addend: -4,
section_index: 0,
}];
e.create_plt_entries(&syms, &relocs);
assert!(e.plt_entries.iter().any(|x| x.symbol_name == "printf"));
}
#[test]
fn t_elf_dt_needed() {
let mut e = X86ELFLinker::new(X86LLDArch::X86_64);
e.shared_libs.push(X86SharedLibrary {
name: "libc.so.6".to_string(),
symbols: vec![],
needed: vec![],
soname: Some("libc.so.6".to_string()),
});
assert!(e.collect_dt_needed().contains(&"libc.so.6".to_string()));
}
#[test]
fn t_elf_gnu_hash() {
let e = X86ELFLinker::new(X86LLDArch::X86_64);
assert!(!e
.build_gnu_hash(&["main".to_string(), "foo".to_string()])
.is_empty());
}
#[test]
fn t_elf_sysv_hash() {
let e = X86ELFLinker::new();
assert!(!e.build_sysv_hash(&["_start".to_string()]).is_empty());
}
#[test]
fn t_elf_dynamic() {
let mut e = X86ELFLinker::new(X86LLDArch::X86_64);
e.build_got();
let d = e.build_dynamic_entries(
true,
&["libc.so.6".to_string()],
0x2000,
0x3000,
0x4000,
48,
0x5000,
0x6000,
);
assert!(d.len() >= 48);
}
#[test]
fn t_build_id_sha1() {
assert_eq!(
X86ELFLinker::compute_build_id(X86BuildIdKind::Sha1, b"t").len(),
20
);
}
#[test]
fn t_build_id_md5() {
assert_eq!(
X86ELFLinker::compute_build_id(X86BuildIdKind::Md5, b"t").len(),
16
);
}
#[test]
fn t_build_id_none() {
assert!(X86ELFLinker::compute_build_id(X86BuildIdKind::None, b"").is_empty());
}
#[test]
fn t_build_id_uuid() {
let u = X86ELFLinker::compute_build_id(X86BuildIdKind::Uuid, b"");
assert_eq!(u.len(), 16);
assert_eq!(u[6] >> 4, 4);
}
#[test]
fn t_coff_create() {
let l = X86COFFLinker::new();
assert!(l.objects.is_empty());
}
#[test]
fn t_coff_import_export() {
let mut l = X86COFFLinker::new();
l.add_import("puts", "msvcrt.dll", None, false);
l.add_export("MyFunc", 1, 0x2000);
assert_eq!(l.imports.len(), 1);
assert_eq!(l.exports.len(), 1);
}
#[test]
fn t_coff_dos() {
let l = X86COFFLinker::new();
let mut b = Vec::new();
l.emit_dos_stub(&mut b);
assert_eq!(b.len(), 64);
assert_eq!(&b[0..2], b"MZ");
}
#[test]
fn t_coff_pe_sig() {
let l = X86COFFLinker::new();
let mut b = Vec::new();
l.emit_pe_signature(&mut b);
assert_eq!(&b, b"PE\0\0");
}
#[test]
fn t_coff_header() {
let l = X86COFFLinker::new();
let mut b = Vec::new();
l.emit_coff_header(&mut b, true).unwrap();
assert_eq!(read_u16_le(&b, 0), IMAGE_FILE_MACHINE_AMD64);
}
#[test]
fn t_coff_opt_header() {
let l = X86COFFLinker::new();
let mut b = Vec::new();
l.emit_optional_header(&mut b, true, 0x140000000, "main")
.unwrap();
assert_eq!(read_u16_le(&b, 0), PE32PLUS_MAGIC);
}
#[test]
fn t_coff_find_rva() {
let mut l = X86COFFLinker::new();
l.symbols.insert(
"main".to_string(),
X86CoffResolvedSymbol {
name: "main".to_string(),
value: 0x1000,
section_index: 1,
is_external: true,
},
);
assert_eq!(l.find_symbol_rva("main"), 0x1000);
}
#[test]
fn t_coff_merge() {
let mut l = X86COFFLinker::new();
l.objects.push(X86CoffObject {
filename: "a.obj".to_string(),
data: vec![],
sections: vec![X86CoffSection {
name: ".text".to_string(),
data: vec![0x90, 0x90],
characteristics: IMAGE_SCN_CNT_CODE | 0x60000020,
align: 16,
section_index: 1,
}],
symbols: vec![],
relocations: vec![],
machine: IMAGE_FILE_MACHINE_AMD64,
});
l.merge_coff_sections();
assert_eq!(l.sections.len(), 1);
}
#[test]
fn t_coff_resolve() {
let mut l = X86COFFLinker::new();
l.objects.push(X86CoffObject {
filename: "t.obj".to_string(),
data: vec![],
sections: vec![],
symbols: vec![X86CoffSymbol {
name: "fn".to_string(),
value: 0x100,
section_number: 1,
sym_type: 0x20,
storage_class: 2,
number_of_aux_symbols: 0,
is_external: true,
}],
relocations: vec![],
machine: IMAGE_FILE_MACHINE_AMD64,
});
l.resolve_symbols();
assert!(l.symbols.contains_key("fn"));
}
#[test]
fn t_symtab_create() {
let t = X86SymbolTable::new();
assert!(t.symbols.is_empty());
}
#[test]
fn t_symtab_define() {
let mut t = X86SymbolTable::new();
t.define(
"main",
0x1000,
42,
STB_GLOBAL,
STT_FUNC,
X86SymbolVisibility::Default,
".text",
true,
);
assert!(t.get("main").is_some());
}
#[test]
fn t_symtab_ref() {
let mut t = X86SymbolTable::new();
t.reference("printf");
assert!(t.undefined.contains(&"printf".to_string()));
}
#[test]
fn t_symtab_weak() {
let mut t = X86SymbolTable::new();
t.define_weak("__w", 0x2000, 16, ".text.w");
assert!(t.get("__w").is_some());
}
#[test]
fn t_symtab_common() {
let mut t = X86SymbolTable::new();
t.define_common("errno", 4, 4);
assert!(t.get("errno").is_some());
}
#[test]
fn t_symtab_undef() {
let mut t = X86SymbolTable::new();
t.reference("missing");
assert!(t.check_undefined().contains(&"missing".to_string()));
}
#[test]
fn t_symtab_resolve_com() {
let mut t = X86SymbolTable::new();
t.define_common("b1", 256, 16);
t.define_common("b2", 128, 8);
assert!(t.resolve_commons().0 >= 384);
}
#[test]
fn t_symtab_version() {
let mut t = X86SymbolTable::new();
t.define(
"pub",
0x1000,
10,
STB_GLOBAL,
STT_FUNC,
X86SymbolVisibility::Default,
".text",
true,
);
t.define(
"__int",
0x2000,
10,
STB_GLOBAL,
STT_FUNC,
X86SymbolVisibility::Default,
".text",
true,
);
t.apply_version_script(&["pub*".to_string()], &["__*".to_string()]);
assert!(t.get("pub").unwrap().is_exported);
assert!(!t.get("__int").unwrap().is_exported);
}
#[test]
fn t_symtab_shared() {
let mut t = X86SymbolTable::new();
t.add_shared_symbol("pthread_create", "libpthread.so.0");
assert!(t.get("pthread_create").is_some());
}
#[test]
fn t_symtab_resolve_all() {
let mut t = X86SymbolTable::new();
let obj = X86InputObject {
filename: "a.o".to_string(),
data: vec![],
sections: vec![],
symbols: vec![
X86InputSymbol {
name: "f".to_string(),
value: 0x100,
size: 10,
binding: STB_GLOBAL,
sym_type: STT_FUNC,
visibility: STV_DEFAULT,
section_index: 1,
is_defined: true,
common_alignment: 0,
},
X86InputSymbol {
name: "ext".to_string(),
value: 0,
size: 0,
binding: STB_GLOBAL,
sym_type: STT_FUNC,
visibility: STV_DEFAULT,
section_index: SHN_UNDEF,
is_defined: false,
common_alignment: 0,
},
],
relocations: vec![],
machine: EM_X86_64,
elf_class: ELFCLASS64,
};
t.resolve_all(&[obj], &[]);
assert!(t.get("f").is_some());
assert!(t.undefined.contains(&"ext".to_string()));
}
#[test]
fn t_reloc_create() {
let e = X86RelocationEngine::new();
assert_eq!(e.get_action(R_X86_64_NONE), X86RelocAction::None);
assert_eq!(e.get_action(R_X86_64_64), X86RelocAction::ApplyAbsolute64);
}
#[test]
fn t_reloc_pc() {
assert!(X86RelocationEngine::is_pc_relative(R_X86_64_PC32));
assert!(!X86RelocationEngine::is_pc_relative(R_X86_64_64));
}
#[test]
fn t_reloc_got() {
assert!(X86RelocationEngine::needs_got(R_X86_64_GOTPCREL));
assert!(!X86RelocationEngine::needs_got(R_X86_64_PC32));
}
#[test]
fn t_reloc_plt() {
assert!(X86RelocationEngine::needs_plt(R_X86_64_PLT32));
assert!(!X86RelocationEngine::needs_plt(R_X86_64_PC32));
}
#[test]
fn t_reloc_size() {
assert_eq!(X86RelocationEngine::relocation_size(R_X86_64_64), 8);
assert_eq!(X86RelocationEngine::relocation_size(R_X86_64_32), 4);
assert_eq!(X86RelocationEngine::relocation_size(R_X86_64_16), 2);
assert_eq!(X86RelocationEngine::relocation_size(R_X86_64_8), 1);
}
#[test]
fn t_reloc_name() {
assert_eq!(X86RelocationEngine::type_name(R_X86_64_64), "R_X86_64_64");
assert_eq!(X86RelocationEngine::type_name(99999), "UNKNOWN");
}
#[test]
fn t_reloc_i386() {
let e = X86RelocationEngine::new();
assert_eq!(
e.get_action(R_386_IRELATIVE),
X86RelocAction::ApplyIRELATIVE
);
assert_eq!(e.get_action(R_386_TLS_GD), X86RelocAction::ApplyTLSGD);
}
#[test]
fn t_reloc_all_types() {
let e = X86RelocationEngine::new();
for t in 0..42u32 {
let _ = e.get_action(t);
}
}
#[test]
fn t_reloc_compute_abs() {
let e = X86RelocationEngine::new();
let reloc = X86InputRelocation {
offset: 0,
symbol_index: 0,
rel_type: R_X86_64_64,
addend: 0,
section_index: 0,
};
let syms = vec![X86InputSymbol {
name: "d".to_string(),
value: 0x4000,
size: 8,
binding: STB_GLOBAL,
sym_type: STT_OBJECT,
visibility: STV_DEFAULT,
section_index: 1,
is_defined: true,
common_alignment: 0,
}];
let st = X86SymbolTable::new();
let v = e.compute_value(&reloc, &syms, &[], &st, 0, 0x1000).unwrap();
assert_eq!(v, 0x5000);
}
#[test]
fn t_script_create() {
let s = X86LinkerScript::new();
assert!(s.sections.is_empty());
}
#[test]
fn t_script_parse() {
let s = X86LinkerScript::parse(
"OUTPUT_FORMAT(\"elf64-x86-64\")\nOUTPUT_ARCH(i386:x86-64)\nENTRY(_start)",
)
.unwrap();
assert_eq!(s.output_format, Some("elf64-x86-64".to_string()));
assert_eq!(s.entry, Some("_start".to_string()));
}
#[test]
fn t_script_memory() {
let s = X86LinkerScript::parse("MEMORY { RAM (rwx) : ORIGIN = 0x400000, LENGTH = 1M }")
.unwrap();
assert_eq!(s.memory_regions.len(), 1);
}
#[test]
fn t_script_sections() {
let s = X86LinkerScript::parse("SECTIONS { .text : { *(.text) } }").unwrap();
assert!(!s.sections.is_empty());
}
#[test]
fn t_script_phdrs() {
assert!(X86LinkerScript::parse("PHDRS { text PT_LOAD FLAGS(5) ; }").is_ok());
}
#[test]
fn t_script_eval() {
let mut m = HashMap::new();
m.insert("foo".to_string(), 0x1000);
assert_eq!(
X86LinkerScript::evaluate_expression("foo", &m, 0),
Some(0x1000)
);
assert_eq!(
X86LinkerScript::evaluate_expression("ALIGN(16)", &m, 0x4017),
Some(0x4020)
);
}
#[test]
fn t_script_assign() {
let s = X86LinkerScript::parse("__bss_start = .;").unwrap();
assert_eq!(s.assignments.len(), 1);
}
#[test]
fn t_archive_create() {
assert!(X86ArchiveWriter::new().members.is_empty());
}
#[test]
fn t_archive_build() {
let mut w = X86ArchiveWriter::new();
w.add_member("e.o", &vec![0x90]);
let r = w.build().unwrap();
assert_eq!(&r[0..8], b"!<arch>\n");
}
#[test]
fn t_archive_parse() {
let mut w = X86ArchiveWriter::new();
w.add_member("h.o", b"\x7fELF");
let a = w.build().unwrap();
let m = X86ArchiveReader::parse(&a).unwrap();
assert!(!m.is_empty());
}
#[test]
fn t_archive_roundtrip() {
let mut w = X86ArchiveWriter::new();
w.add_member("x.o", b"data");
let a = w.build().unwrap();
let m = X86ArchiveReader::parse(&a).unwrap();
assert_eq!(m.len(), 1);
assert_eq!(m[0].name, "x.o");
}
#[test]
fn t_archive_invalid() {
assert!(X86ArchiveReader::parse(b"bad").is_err());
}
#[test]
fn t_lto_detect() {
assert!(X86LTOPlugin::detect_bitcode(&[0x42, 0x43, 0xC0, 0xDE]));
assert!(!X86LTOPlugin::detect_bitcode(b"\x7fELF"));
assert!(X86LTOPlugin::detect_bitcode(&[0xDE, 0xC0, 0x17, 0x0B]));
}
#[test]
fn t_lto_analyze() {
let i = X86LTOPlugin::analyze_bitcode(&[0x42, 0x43, 0xC0, 0xDE]);
assert!(i.is_bitcode);
assert!(!i.is_thin_lto);
}
#[test]
fn t_lto_run() {
let p = X86LTOPlugin::new();
assert!(p
.run_full_lto(&[("i.bc".to_string(), vec![0x42, 0x43, 0xC0, 0xDE])])
.is_ok());
}
#[test]
fn t_lto_non_bitcode() {
let p = X86LTOPlugin::new();
assert!(p
.run_full_lto(&[("n.bc".to_string(), b"\x7fELF".to_vec())])
.is_err());
}
#[test]
fn t_lto_should() {
let p = X86LTOPlugin::new();
assert!(p.should_process(&[0x42, 0x43, 0xC0, 0xDE]));
assert!(!p.should_process(b"\x7fELF"));
}
#[test]
fn t_icf_create() {
let i = X86ICF::new();
assert_eq!(i.min_size, 16);
assert!(i.functions_only);
}
#[test]
fn t_icf_hash() {
assert_eq!(
X86ICF::hash_section(&[1, 2, 3, 4], 6),
X86ICF::hash_section(&[1, 2, 3, 4], 6)
);
assert_ne!(
X86ICF::hash_section(&[1, 2, 3], 6),
X86ICF::hash_section(&[4, 5, 6], 6)
);
}
#[test]
fn t_icf_no_identical() {
let mut i = X86ICF::new();
let mut s = vec![
X86InputSection {
name: ".text.a".to_string(),
data: vec![1u8; 32],
sh_type: SHT_PROGBITS,
sh_flags: (SHF_ALLOC | SHF_EXECINSTR) as u64,
sh_addralign: 16,
section_index: 0,
sh_entsize: 0,
},
X86InputSection {
name: ".text.b".to_string(),
data: vec![2u8; 32],
sh_type: SHT_PROGBITS,
sh_flags: (SHF_ALLOC | SHF_EXECINSTR) as u64,
sh_addralign: 16,
section_index: 1,
sh_entsize: 0,
},
];
assert_eq!(i.run(&mut s), 0);
}
#[test]
fn t_icf_identical() {
let mut i = X86ICF::new();
let d = vec![1u8; 32];
let mut s = vec![
X86InputSection {
name: ".text.a".to_string(),
data: d.clone(),
sh_type: SHT_PROGBITS,
sh_flags: (SHF_ALLOC | SHF_EXECINSTR) as u64,
sh_addralign: 16,
section_index: 0,
sh_entsize: 0,
},
X86InputSection {
name: ".text.b".to_string(),
data: d.clone(),
sh_type: SHT_PROGBITS,
sh_flags: (SHF_ALLOC | SHF_EXECINSTR) as u64,
sh_addralign: 16,
section_index: 1,
sh_entsize: 0,
},
];
assert_eq!(i.run(&mut s), 1);
}
#[test]
fn t_icf_three() {
let mut i = X86ICF::new();
let d = vec![0x90u8; 32];
let mut s: Vec<_> = (0..3)
.map(|idx| X86InputSection {
name: format!(".text.{}", idx),
data: d.clone(),
sh_type: SHT_PROGBITS,
sh_flags: (SHF_ALLOC | SHF_EXECINSTR) as u64,
sh_addralign: 16,
section_index: idx,
sh_entsize: 0,
})
.collect();
assert_eq!(i.run(&mut s), 2);
}
#[test]
fn t_icf_small() {
let mut i = X86ICF::new();
let d = vec![1, 2, 3];
let mut s = vec![
X86InputSection {
name: ".text.a".to_string(),
data: d.clone(),
sh_type: SHT_PROGBITS,
sh_flags: (SHF_ALLOC | SHF_EXECINSTR) as u64,
sh_addralign: 16,
section_index: 0,
sh_entsize: 0,
},
X86InputSection {
name: ".text.b".to_string(),
data: d,
sh_type: SHT_PROGBITS,
sh_flags: (SHF_ALLOC | SHF_EXECINSTR) as u64,
sh_addralign: 16,
section_index: 1,
sh_entsize: 0,
},
];
assert_eq!(i.run(&mut s), 0);
}
#[test]
fn t_gc_create() {
let g = X86GCSections::new();
assert!(!g.keep_eh);
assert!(g.keep_patterns.contains(&".init".to_string()));
}
#[test]
fn t_gc_init() {
let mut g = X86GCSections::new();
let mut s = vec![
X86InputSection {
name: ".init".to_string(),
data: vec![1],
sh_type: SHT_PROGBITS,
sh_flags: (SHF_ALLOC | SHF_EXECINSTR) as u64,
sh_addralign: 16,
section_index: 0,
sh_entsize: 0,
},
X86InputSection {
name: ".text.dead".to_string(),
data: vec![2],
sh_type: SHT_PROGBITS,
sh_flags: (SHF_ALLOC | SHF_EXECINSTR) as u64,
sh_addralign: 16,
section_index: 1,
sh_entsize: 0,
},
];
assert_eq!(g.run(&mut s, &[]), 1);
}
#[test]
fn t_gc_entry() {
let mut g = X86GCSections::new();
let mut s = vec![
X86InputSection {
name: ".text.main".to_string(),
data: b"code".to_vec(),
sh_type: SHT_PROGBITS,
sh_flags: (SHF_ALLOC | SHF_EXECINSTR) as u64,
sh_addralign: 16,
section_index: 0,
sh_entsize: 0,
},
X86InputSection {
name: ".text.dead".to_string(),
data: vec![42],
sh_type: SHT_PROGBITS,
sh_flags: (SHF_ALLOC | SHF_EXECINSTR) as u64,
sh_addralign: 16,
section_index: 1,
sh_entsize: 0,
},
];
assert_eq!(g.run(&mut s, &["main".to_string()]), 1);
}
#[test]
fn t_gc_transitive() {
let mut g = X86GCSections::new();
let mut s = vec![
X86InputSection {
name: ".text.main".to_string(),
data: b".text.helper".to_vec(),
sh_type: SHT_PROGBITS,
sh_flags: (SHF_ALLOC | SHF_EXECINSTR) as u64,
sh_addralign: 16,
section_index: 0,
sh_entsize: 0,
},
X86InputSection {
name: ".text.helper".to_string(),
data: vec![0xC3],
sh_type: SHT_PROGBITS,
sh_flags: (SHF_ALLOC | SHF_EXECINSTR) as u64,
sh_addralign: 16,
section_index: 1,
sh_entsize: 0,
},
X86InputSection {
name: ".text.dead".to_string(),
data: vec![0; 100],
sh_type: SHT_PROGBITS,
sh_flags: (SHF_ALLOC | SHF_EXECINSTR) as u64,
sh_addralign: 16,
section_index: 2,
sh_entsize: 0,
},
];
assert_eq!(g.run(&mut s, &["main".to_string()]), 1);
}
#[test]
fn t_align_up() {
assert_eq!(align_up(1, 16), 16);
assert_eq!(align_up(16, 16), 16);
assert_eq!(align_up(17, 16), 32);
assert_eq!(align_up(0, 0), 0);
}
#[test]
fn t_align_down() {
assert_eq!(align_down(1, 16), 0);
assert_eq!(align_down(16, 16), 16);
assert_eq!(align_down(0, 0), 0);
}
#[test]
fn t_wildcard() {
assert!(wildcard_match("hello", "*"));
assert!(wildcard_match("hello", "h*o"));
assert!(!wildcard_match("hello", "x*"));
assert!(wildcard_match(".text.foo", ".text*"));
}
#[test]
fn t_elf_hash() {
assert_ne!(elf_hash("printf"), 0);
assert_eq!(elf_hash("printf"), elf_hash("printf"));
}
#[test]
fn t_gnu_hash() {
assert_ne!(gnu_hash_bytes(b"main"), 0);
assert_eq!(gnu_hash_bytes(b"main"), gnu_hash_bytes(b"main"));
}
#[test]
fn t_next_pow2() {
assert_eq!(next_power_of_two(3), 4);
assert_eq!(next_power_of_two(100), 128);
assert_eq!(next_power_of_two(0), 1);
}
#[test]
fn t_visibility_to_elf() {
assert_eq!(visibility_to_elf(X86SymbolVisibility::Default), STV_DEFAULT);
assert_eq!(visibility_to_elf(X86SymbolVisibility::Hidden), STV_HIDDEN);
}
#[test]
fn t_read_u16() {
assert_eq!(read_u16_le(&[0x34, 0x12], 0), 0x1234);
}
#[test]
fn t_read_u32() {
assert_eq!(read_u32_le(&[0x78, 0x56, 0x34, 0x12], 0), 0x12345678);
}
#[test]
fn t_read_u64() {
let d = [0xEFu8, 0xCD, 0xAB, 0x89, 0x67, 0x45, 0x23, 0x01];
assert_eq!(read_u64_le(&d, 0), 0x0123456789ABCDEF);
}
#[test]
fn t_get_c_string() {
assert_eq!(get_c_string(b"hello\0world", 0), "hello");
assert_eq!(get_c_string(b"no_null", 0), "no_null");
}
#[test]
fn t_fnv1a() {
assert_eq!(compute_fnv1a_hash(b"hello"), compute_fnv1a_hash(b"hello"));
assert_ne!(compute_fnv1a_hash(b"hello"), compute_fnv1a_hash(b"world"));
}
#[test]
fn t_tokenize_empty() {
assert!(tokenize_linker_script("").is_empty());
}
#[test]
fn t_tokenize_simple() {
let t = tokenize_linker_script("ENTRY(main)");
assert!(t.contains(&"ENTRY".to_string()));
assert!(t.contains(&"main".to_string()));
}
#[test]
fn t_parse_num() {
assert_eq!(parse_script_number("0"), 0);
assert_eq!(parse_script_number("42"), 42);
assert_eq!(parse_script_number("0xFF"), 255);
assert_eq!(parse_script_number("0777"), 0o777);
}
#[test]
fn t_sha1_len() {
assert_eq!(hash_sha1(b"x").len(), 20);
}
#[test]
fn t_md5_len() {
assert_eq!(hash_md5(b"x").len(), 16);
}
#[test]
fn t_uuid_v4() {
let u = uuid_v4();
assert_eq!(u.len(), 16);
assert_eq!(u[6] >> 4, 4);
}
#[test]
fn t_hash_fast() {
assert_eq!(hash_fast(b"x").len(), 8);
}
#[test]
fn t_full_elf_pipeline() {
let mut l = X86LLDLinker::new_elf_x86_64();
assert!(l.emit().is_ok());
}
#[test]
fn t_full_coff_pipeline() {
let mut l = X86LLDLinker::new_coff_x86_64();
assert!(l.emit().is_ok());
}
#[test]
fn t_linker_diag() {
let mut l = X86LLDLinker::new();
l.diag(X86DiagLevel::Warning, "w".to_string());
l.diag(X86DiagLevel::Error, "e".to_string());
assert_eq!(l.diagnostics.len(), 2);
}
#[test]
fn t_all_formats() {
let _ = X86LLDLinker::new_elf_x86_64();
let _ = X86LLDLinker::new_elf_i386();
let _ = X86LLDLinker::new_coff_x86_64();
let _ = X86LLDLinker::new_coff_i386();
}
#[test]
fn t_coff_subsystem() {
assert_eq!(X86CoffSubsystem::Console as u16, 3);
assert_eq!(X86CoffSubsystem::WindowsGui as u16, 2);
}
#[test]
fn t_section_ordering() {
let mut e = X86ELFLinker::new(X86LLDArch::X86_64);
e.objects.push(make_elf_obj(
"t.o",
vec![(".text", vec![0x90]), (".data", vec![0x42])],
));
e.merge_sections();
let ti = e.sections.iter().position(|s| s.name == ".text");
let di = e.sections.iter().position(|s| s.name == ".data");
assert!(ti.unwrap() < di.unwrap());
}
#[test]
fn t_parse_coff() {
let r = parse_coff_object_full("t.obj", &vec![0u8; 200]);
assert!(r.is_ok());
}
#[test]
fn t_lto_options_default() {
let o = X86LTOOptions::default();
assert_eq!(o.cpu, "x86-64");
assert_eq!(o.opt_level, X86LTOOptLevel::O2);
}
#[test]
fn t_elf_parse_bad_magic() {
let e = X86ELFLinker::new(X86LLDArch::X86_64);
assert!(e.parse_elf_object("bad", b"not elf").is_err());
}
#[test]
fn t_coff_resolve_sym() {
let mut t = X86SymbolTable::new();
t.reference("foo");
t.define(
"foo",
0x100,
10,
STB_GLOBAL,
STT_FUNC,
X86SymbolVisibility::Default,
".text",
true,
);
assert!(t.check_undefined().is_empty());
}
#[test]
fn t_script_eval_add() {
let mut m = HashMap::new();
m.insert("A".to_string(), 0x1000);
assert_eq!(
X86LinkerScript::evaluate_expression("A + 0x100", &m, 0),
Some(0x1100)
);
}
}