use std::collections::HashMap;
pub const ELF_MAGIC: [u8; 4] = [0x7F, b'E', b'L', b'F'];
pub const MACHO_MAGIC_64: u32 = 0xFEEDFACF;
pub const MACHO_MAGIC_32: u32 = 0xFEEDFACE;
pub const MACHO_FAT_MAGIC: u32 = 0xCAFEBABE;
pub const MACHO_FAT_CIGAM: u32 = 0xBEBAFECA;
pub const COFF_MAGIC_PE: u16 = 0x5A4D;
pub const WASM_MAGIC: [u8; 4] = [0x00, 0x61, 0x73, 0x6D];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ElfClass {
Elf32,
Elf64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ElfEndian {
Little,
Big,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ElfOsAbi {
SystemV,
Linux,
None,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ElfMachine {
X86_64,
AArch64,
ARM,
X86,
RiscV,
None,
}
#[derive(Debug, Clone)]
pub struct ElfIdent {
pub class: ElfClass,
pub endian: ElfEndian,
pub version: u8,
pub os_abi: ElfOsAbi,
pub abi_version: u8,
}
#[derive(Debug, Clone)]
pub struct ElfHeader {
pub ident: ElfIdent,
pub obj_type: u16,
pub machine: ElfMachine,
pub version: u32,
pub entry: u64,
pub phoff: u64, pub shoff: u64, pub flags: u32,
pub ehsize: u16, pub phentsize: u16, pub phnum: u16, pub shentsize: u16, pub shnum: u16, pub shstrndx: u16, }
#[derive(Debug, Clone)]
pub struct ElfSectionHeader {
pub name: String,
pub sh_type: u32,
pub flags: u64,
pub addr: u64,
pub offset: u64,
pub size: u64,
pub link: u32,
pub info: u32,
pub addralign: u64,
pub entsize: u64,
}
#[derive(Debug, Clone)]
pub struct ElfSymbol {
pub name: String,
pub value: u64,
pub size: u64,
pub sym_type: u8,
pub binding: u8,
pub section_index: u16,
}
#[derive(Debug, Clone)]
pub struct ElfRelocation {
pub offset: u64,
pub sym_index: u32,
pub rel_type: u32,
pub addend: i64,
}
#[derive(Debug, Clone)]
pub struct ElfProgramHeader {
pub p_type: u32,
pub flags: u32,
pub offset: u64,
pub vaddr: u64,
pub paddr: u64,
pub filesz: u64,
pub memsz: u64,
pub align: u64,
}
#[derive(Debug, Clone)]
pub struct ElfDynamic {
pub d_tag: u64,
pub d_val: u64,
}
#[derive(Debug, Clone)]
pub struct ElfNote {
pub name: String,
pub desc_type: u32,
pub desc: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct MachOHeader {
pub magic: u32,
pub cputype: u32,
pub cpusubtype: u32,
pub filetype: u32,
pub ncmds: u32,
pub sizeofcmds: u32,
pub flags: u32,
pub reserved: u32,
}
#[derive(Debug, Clone)]
pub struct MachSegment {
pub segname: String,
pub vmaddr: u64,
pub vmsize: u64,
pub fileoff: u64,
pub filesize: u64,
pub maxprot: u32,
pub initprot: u32,
pub nsects: u32,
pub flags: u32,
}
#[derive(Debug, Clone)]
pub struct MachOSectionData {
pub sectname: String,
pub segname: String,
pub addr: u64,
pub size: u64,
pub offset: u32,
pub align: u32,
pub reloff: u32,
pub nreloc: u32,
pub flags: u32,
pub data: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct MachSymtabCmd {
pub symoff: u32,
pub nsyms: u32,
pub stroff: u32,
pub strsize: u32,
}
#[derive(Debug, Clone)]
pub struct MachDysymtabCmd {
pub ilocalsym: u32,
pub nlocalsym: u32,
pub iextdefsym: u32,
pub nextdefsym: u32,
pub iundefsym: u32,
pub nundefsym: u32,
}
#[derive(Debug, Clone)]
pub struct MachUuidCmd {
pub uuid: [u8; 16],
}
#[derive(Debug, Clone)]
pub struct MachVersionMinCmd {
pub cmd: u32,
pub version: u32,
pub sdk: u32,
}
#[derive(Debug, Clone)]
pub enum MachOLoadCommand {
Segment(MachSegment),
Symtab(MachSymtabCmd),
Dysymtab(MachDysymtabCmd),
Uuid(MachUuidCmd),
VersionMinMacosx(MachVersionMinCmd),
VersionMinIphoneos(MachVersionMinCmd),
Unknown {
cmd: u32,
cmdsize: u32,
data: Vec<u8>,
},
}
#[derive(Debug, Clone)]
pub struct MachOSymbol {
pub name: String,
pub n_type: u8,
pub n_sect: u8,
pub n_desc: u16,
pub n_value: u64,
}
#[derive(Debug, Clone)]
pub struct CoffHeader {
pub machine: u16,
pub num_sections: u16,
pub timestamp: u32,
pub symtab_offset: u32,
pub num_symbols: u32,
pub opt_header_size: u16,
pub characteristics: u16,
}
#[derive(Debug, Clone)]
pub struct CoffSectionHeader {
pub name: String,
pub virtual_size: u32,
pub virtual_address: u32,
pub raw_data_size: u32,
pub raw_data_ptr: u32,
pub reloc_ptr: u32,
pub linenum_ptr: u32,
pub num_relocs: u16,
pub num_linenums: u16,
pub characteristics: u32,
}
#[derive(Debug, Clone)]
pub struct CoffSymbol {
pub name: String,
pub value: u32,
pub section_number: i16,
pub sym_type: u16,
pub storage_class: u8,
pub num_aux: u8,
}
#[derive(Debug, Clone)]
pub struct CoffRelocation {
pub virtual_address: u32,
pub symbol_index: u32,
pub rel_type: u16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ObjectFormat {
ELF32,
ELF64,
MachO32,
MachO64,
COFF,
PE,
Wasm,
Unknown,
}
pub fn detect_format(data: &[u8]) -> Option<ObjectFormat> {
if data.len() < 4 {
return None;
}
if data[0..4] == ELF_MAGIC {
if data.len() >= 5 {
match data[4] {
1 => return Some(ObjectFormat::ELF32),
2 => return Some(ObjectFormat::ELF64),
_ => return Some(ObjectFormat::ELF64),
}
}
return Some(ObjectFormat::ELF64);
}
if data.len() >= 4 {
let magic = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
match magic {
MACHO_MAGIC_64 => return Some(ObjectFormat::MachO64),
MACHO_MAGIC_32 => return Some(ObjectFormat::MachO32),
MACHO_FAT_MAGIC | MACHO_FAT_CIGAM => return Some(ObjectFormat::MachO64),
_ => {}
}
let magic_be = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
match magic_be {
MACHO_MAGIC_64 => return Some(ObjectFormat::MachO64),
MACHO_MAGIC_32 => return Some(ObjectFormat::MachO32),
_ => {}
}
}
if data.len() >= 4 && data[0..4] == WASM_MAGIC {
return Some(ObjectFormat::Wasm);
}
if data.len() >= 2 && data[0] == 0x4D && data[1] == 0x5A {
return Some(ObjectFormat::PE);
}
if data.len() >= 2 {
let machine = u16::from_le_bytes([data[0], data[1]]);
match machine {
0x8664 | 0xAA64 | 0x14C | 0x1C0 | 0x1C4 | 0x200 | 0x166 | 0x1F0 => {
return Some(ObjectFormat::COFF);
}
_ => {}
}
}
None
}
#[derive(Debug, Clone)]
pub struct ObjectFile {
pub format: ObjectFormat,
pub machine: String,
pub sections: Vec<ObjectSection>,
pub symbols: Vec<ObjectSymbol>,
pub entry: u64,
pub flags: u32,
}
#[derive(Debug, Clone)]
pub struct ObjectSection {
pub name: String,
pub section_type: u32,
pub data: Vec<u8>,
pub vaddr: u64,
pub size: u64,
pub flags: u64,
}
#[derive(Debug, Clone)]
pub struct ObjectSymbol {
pub name: String,
pub value: u64,
pub size: u64,
pub is_global: bool,
pub is_function: bool,
pub section_index: u16,
}
#[derive(Debug, Clone)]
pub struct DisassembledInst {
pub address: u64,
pub bytes: Vec<u8>,
pub mnemonic: String,
pub operands: String,
}
impl ObjectFile {
pub fn parse(bytes: &[u8]) -> Option<Self> {
let format = detect_format(bytes)?;
match format {
ObjectFormat::ELF32 | ObjectFormat::ELF64 => Self::parse_elf(bytes),
ObjectFormat::MachO32 | ObjectFormat::MachO64 => Self::parse_macho(bytes),
ObjectFormat::COFF | ObjectFormat::PE => Self::parse_coff(bytes),
ObjectFormat::Wasm => Self::parse_wasm(bytes),
ObjectFormat::Unknown => None,
}
}
fn parse_elf(bytes: &[u8]) -> Option<Self> {
let header = Self::parse_elf_header(bytes)?;
let machine = match header.machine {
ElfMachine::X86_64 => "x86_64",
ElfMachine::AArch64 => "aarch64",
ElfMachine::ARM => "arm",
ElfMachine::X86 => "x86",
ElfMachine::RiscV => "riscv",
ElfMachine::None => "unknown",
};
let class = match header.ident.class {
ElfClass::Elf32 => ObjectFormat::ELF32,
ElfClass::Elf64 => ObjectFormat::ELF64,
};
let mut sections = Vec::new();
let mut symbols = Vec::new();
if let Ok(elf_sections) = Self::parse_elf_sections(bytes) {
for sec in &elf_sections {
let data = if sec.sh_type == sht::NOBITS {
Vec::new()
} else if sec.offset > 0 && (sec.offset as usize) < bytes.len() {
let end = std::cmp::min((sec.offset + sec.size) as usize, bytes.len());
bytes[sec.offset as usize..end].to_vec()
} else {
Vec::new()
};
sections.push(ObjectSection {
name: sec.name.clone(),
section_type: sec.sh_type,
data,
vaddr: sec.addr,
size: sec.size,
flags: sec.flags,
});
}
}
if let Ok(parsed_symbols) = Self::parse_elf_symbols(bytes) {
for sym in parsed_symbols {
symbols.push(ObjectSymbol {
name: sym.name,
value: sym.value,
size: sym.size,
is_global: sym.binding == stb::GLOBAL,
is_function: sym.sym_type == stt::FUNC,
section_index: sym.section_index,
});
}
}
Some(ObjectFile {
format: class,
machine: machine.to_string(),
sections,
symbols,
entry: header.entry,
flags: header.flags,
})
}
fn parse_macho(bytes: &[u8]) -> Option<Self> {
let header = Self::parse_macho_header(bytes).ok()?;
let machine = match header.cputype {
0x01000007 => "x86_64",
0x0100000C => "arm64",
0x0000000C => "arm",
0x00000007 => "x86",
_ => "unknown",
};
let format = match header.magic {
MACHO_MAGIC_64 => ObjectFormat::MachO64,
MACHO_MAGIC_32 => ObjectFormat::MachO32,
_ => return None,
};
let mut sections = Vec::new();
let mut symbols = Vec::new();
if let Ok(commands) = Self::parse_macho_load_commands(bytes, &header) {
for cmd in &commands {
match cmd {
MachOLoadCommand::Segment(seg) => {
let seg_sections =
Self::parse_macho_sections_in_segment(bytes, seg, header.ncmds);
for s in seg_sections {
sections.push(ObjectSection {
name: s.sectname.clone(),
section_type: s.flags,
data: s.data.clone(),
vaddr: s.addr,
size: s.size,
flags: s.flags as u64,
});
}
}
MachOLoadCommand::Symtab(symtab_cmd) => {
if let Ok(macho_syms) = Self::parse_macho_symbols(bytes, symtab_cmd) {
for sym in macho_syms {
symbols.push(ObjectSymbol {
name: sym.name,
value: sym.n_value,
size: 0,
is_global: sym.n_type & 0x0E != 0,
is_function: sym.n_type & 0x0E == 0x0E,
section_index: sym.n_sect as u16,
});
}
}
}
_ => {}
}
}
}
Some(ObjectFile {
format,
machine: machine.to_string(),
sections,
symbols,
entry: 0,
flags: header.flags,
})
}
fn parse_coff(bytes: &[u8]) -> Option<Self> {
let header = Self::parse_coff_header(bytes).ok()?;
let machine = match header.machine {
0x8664 => "x86_64",
0xAA64 => "aarch64",
0x14C => "x86",
0x1C4 => "arm",
_ => "unknown",
};
let format = ObjectFormat::COFF;
let sections = Self::parse_coff_sections(bytes, &header).unwrap_or_default();
let symbols = Self::parse_coff_symbols(bytes, &header).unwrap_or_default();
let universal_sections: Vec<ObjectSection> = sections
.into_iter()
.map(|s| ObjectSection {
name: s.name,
section_type: 1,
data: Vec::new(), vaddr: s.virtual_address as u64,
size: s.raw_data_size as u64,
flags: s.characteristics as u64,
})
.collect();
let universal_symbols: Vec<ObjectSymbol> = symbols
.into_iter()
.map(|s| ObjectSymbol {
name: s.name,
value: s.value as u64,
size: 0,
is_global: s.storage_class == 2,
is_function: s.sym_type == 0x20,
section_index: s.section_number.max(0) as u16,
})
.collect();
Some(ObjectFile {
format,
machine: machine.to_string(),
sections: universal_sections,
symbols: universal_symbols,
entry: 0,
flags: header.characteristics as u32,
})
}
fn parse_wasm(bytes: &[u8]) -> Option<Self> {
if bytes.len() < 8 || bytes[0..4] != WASM_MAGIC {
return None;
}
let _version = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
Some(ObjectFile {
format: ObjectFormat::Wasm,
machine: "wasm32".to_string(),
sections: Vec::new(),
symbols: Vec::new(),
entry: 0,
flags: 0,
})
}
pub fn parse_elf_header(bytes: &[u8]) -> Option<ElfHeader> {
if bytes.len() < 64 {
return None;
}
if bytes[0..4] != ELF_MAGIC {
return None;
}
let class = match bytes[4] {
1 => ElfClass::Elf32,
2 => ElfClass::Elf64,
_ => return None,
};
let endian = match bytes[5] {
1 => ElfEndian::Little,
2 => ElfEndian::Big,
_ => return None,
};
let os_abi = match bytes[7] {
0 => ElfOsAbi::SystemV,
3 => ElfOsAbi::Linux,
_ => ElfOsAbi::None,
};
let ident = ElfIdent {
class,
endian,
version: bytes[6],
os_abi,
abi_version: bytes[8],
};
let obj_type = u16::from_le_bytes([bytes[16], bytes[17]]);
let machine = match u16::from_le_bytes([bytes[18], bytes[19]]) {
62 => ElfMachine::X86_64,
183 => ElfMachine::AArch64,
40 => ElfMachine::ARM,
3 => ElfMachine::X86,
243 => ElfMachine::RiscV,
_ => ElfMachine::None,
};
let entry = u64::from_le_bytes(bytes[24..32].try_into().ok()?);
let phoff = u64::from_le_bytes(bytes[32..40].try_into().ok()?);
let shoff = u64::from_le_bytes(bytes[40..48].try_into().ok()?);
let flags = u32::from_le_bytes(bytes[48..52].try_into().ok()?);
let ehsize = u16::from_le_bytes([bytes[52], bytes[53]]);
let phentsize = u16::from_le_bytes([bytes[54], bytes[55]]);
let phnum = u16::from_le_bytes([bytes[56], bytes[57]]);
let shentsize = u16::from_le_bytes([bytes[58], bytes[59]]);
let shnum = u16::from_le_bytes([bytes[60], bytes[61]]);
let shstrndx = u16::from_le_bytes([bytes[62], bytes[63]]);
Some(ElfHeader {
ident,
obj_type,
machine,
version: 0,
entry,
phoff,
shoff,
flags,
ehsize,
phentsize,
phnum,
shentsize,
shnum,
shstrndx,
})
}
pub fn parse_elf_sections(data: &[u8]) -> Result<Vec<ElfSectionHeader>, String> {
let header = Self::parse_elf_header(data).ok_or("Invalid ELF header")?;
let shoff = header.shoff as usize;
let shnum = header.shnum as usize;
let shentsize = header.shentsize as usize;
if shoff == 0 || shnum == 0 || shentsize == 0 {
return Ok(Vec::new());
}
let shstrndx = header.shstrndx as usize;
let shstrtab_offset = shoff + shstrndx * shentsize;
let shstrtab = Self::_read_section_header_raw(data, shstrtab_offset);
let mut sections = Vec::new();
for i in 0..shnum {
let sec_offset = shoff + i * shentsize;
if sec_offset + shentsize > data.len() {
break;
}
let name_offset = u32::from_le_bytes([
data[sec_offset],
data[sec_offset + 1],
data[sec_offset + 2],
data[sec_offset + 3],
]) as usize;
let sh_type = u32::from_le_bytes([
data[sec_offset + 4],
data[sec_offset + 5],
data[sec_offset + 6],
data[sec_offset + 7],
]);
let flags = u64::from_le_bytes(
data[sec_offset + 8..sec_offset + 16]
.try_into()
.map_err(|_| "slice error")?,
);
let addr = u64::from_le_bytes(
data[sec_offset + 16..sec_offset + 24]
.try_into()
.map_err(|_| "slice error")?,
);
let off = u64::from_le_bytes(
data[sec_offset + 24..sec_offset + 32]
.try_into()
.map_err(|_| "slice error")?,
);
let size = u64::from_le_bytes(
data[sec_offset + 32..sec_offset + 40]
.try_into()
.map_err(|_| "slice error")?,
);
let link = u32::from_le_bytes([
data[sec_offset + 40],
data[sec_offset + 41],
data[sec_offset + 42],
data[sec_offset + 43],
]);
let info = u32::from_le_bytes([
data[sec_offset + 44],
data[sec_offset + 45],
data[sec_offset + 46],
data[sec_offset + 47],
]);
let addralign = u64::from_le_bytes(
data[sec_offset + 48..sec_offset + 56]
.try_into()
.map_err(|_| "slice error")?,
);
let entsize = u64::from_le_bytes(
data[sec_offset + 56..sec_offset + 64]
.try_into()
.map_err(|_| "slice error")?,
);
let name = if shstrtab_offset + name_offset < data.len() {
let start = shstrtab_offset + name_offset;
let end = data[start..]
.iter()
.position(|&b| b == 0)
.map(|p| start + p)
.unwrap_or(data.len());
String::from_utf8_lossy(&data[start..end]).to_string()
} else {
String::new()
};
sections.push(ElfSectionHeader {
name,
sh_type,
flags,
addr,
offset: off,
size,
link,
info,
addralign,
entsize,
});
}
Ok(sections)
}
fn _read_section_header_raw(data: &[u8], sec_offset: usize) -> usize {
if sec_offset + 32 > data.len() {
return 0;
}
u64::from_le_bytes(
data[sec_offset + 24..sec_offset + 32]
.try_into()
.unwrap_or([0; 8]),
) as usize
}
pub fn parse_elf_symbols(data: &[u8]) -> Result<Vec<ElfSymbol>, String> {
let header = Self::parse_elf_header(data).ok_or("Invalid ELF header")?;
let sections = Self::parse_elf_sections(data)?;
let symtab = sections.iter().find(|s| s.name == ".symtab");
let strtab = sections.iter().find(|s| s.name == ".strtab");
let (symtab, strtab) = match (symtab, strtab) {
(Some(s), Some(t)) => (s, t),
_ => return Ok(Vec::new()),
};
let sym_data = &data[symtab.offset as usize..(symtab.offset + symtab.size) as usize];
let str_data = &data[strtab.offset as usize..(strtab.offset + strtab.size) as usize];
let entry_size = 24; let num_symbols = sym_data.len() / entry_size;
let mut symbols = Vec::new();
for i in 0..num_symbols {
let off = i * entry_size;
if off + entry_size > sym_data.len() {
break;
}
let name_off = u32::from_le_bytes([
sym_data[off],
sym_data[off + 1],
sym_data[off + 2],
sym_data[off + 3],
]) as usize;
let info = sym_data[off + 4];
let binding = info >> 4;
let sym_type = info & 0x0F;
let section_index = u16::from_le_bytes([sym_data[off + 6], sym_data[off + 7]]);
let value = u64::from_le_bytes(
sym_data[off + 8..off + 16]
.try_into()
.map_err(|_| "slice error")?,
);
let size = u64::from_le_bytes(
sym_data[off + 16..off + 24]
.try_into()
.map_err(|_| "slice error")?,
);
let name = if name_off < str_data.len() {
let end = str_data[name_off..]
.iter()
.position(|&b| b == 0)
.map(|p| name_off + p)
.unwrap_or(str_data.len());
String::from_utf8_lossy(&str_data[name_off..end]).to_string()
} else {
String::new()
};
symbols.push(ElfSymbol {
name,
value,
size,
sym_type,
binding,
section_index,
});
}
Ok(symbols)
}
pub fn parse_elf_relocations(
data: &[u8],
section: &ElfSectionHeader,
) -> Result<Vec<ElfRelocation>, String> {
if section.sh_type != sht::RELA {
return Ok(Vec::new());
}
let entry_size = 24; let reloc_data = &data[section.offset as usize..(section.offset + section.size) as usize];
let num_relocs = reloc_data.len() / entry_size;
let mut relocs = Vec::new();
for i in 0..num_relocs {
let off = i * entry_size;
if off + entry_size > reloc_data.len() {
break;
}
let offset = u64::from_le_bytes(
reloc_data[off..off + 8]
.try_into()
.map_err(|_| "slice error")?,
);
let r_info = u64::from_le_bytes(
reloc_data[off + 8..off + 16]
.try_into()
.map_err(|_| "slice error")?,
);
let addend = i64::from_le_bytes(
reloc_data[off + 16..off + 24]
.try_into()
.map_err(|_| "slice error")?,
);
relocs.push(ElfRelocation {
offset,
sym_index: (r_info >> 32) as u32,
rel_type: (r_info & 0xFFFF_FFFF) as u32,
addend,
});
}
Ok(relocs)
}
pub fn parse_elf_program_headers(data: &[u8]) -> Result<Vec<ElfProgramHeader>, String> {
let header = Self::parse_elf_header(data).ok_or("Invalid ELF header")?;
let phoff = header.phoff as usize;
let phnum = header.phnum as usize;
let phentsize = header.phentsize as usize;
if phoff == 0 || phnum == 0 || phentsize == 0 {
return Ok(Vec::new());
}
let mut phdrs = Vec::new();
for i in 0..phnum {
let off = phoff + i * phentsize;
if off + phentsize > data.len() {
break;
}
let p_type =
u32::from_le_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]]);
let flags =
u32::from_le_bytes([data[off + 4], data[off + 5], data[off + 6], data[off + 7]]);
let (p_offset, p_vaddr, p_paddr, p_filesz, p_memsz, p_align) = if phentsize >= 56 {
(
u64::from_le_bytes(data[off + 8..off + 16].try_into().unwrap()),
u64::from_le_bytes(data[off + 16..off + 24].try_into().unwrap()),
u64::from_le_bytes(data[off + 24..off + 32].try_into().unwrap()),
u64::from_le_bytes(data[off + 32..off + 40].try_into().unwrap()),
u64::from_le_bytes(data[off + 40..off + 48].try_into().unwrap()),
u64::from_le_bytes(data[off + 48..off + 56].try_into().unwrap()),
)
} else {
(
u32::from_le_bytes([data[off + 4], data[off + 5], data[off + 6], data[off + 7]])
as u64,
u32::from_le_bytes([
data[off + 8],
data[off + 9],
data[off + 10],
data[off + 11],
]) as u64,
u32::from_le_bytes([
data[off + 12],
data[off + 13],
data[off + 14],
data[off + 15],
]) as u64,
u32::from_le_bytes([
data[off + 16],
data[off + 17],
data[off + 18],
data[off + 19],
]) as u64,
u32::from_le_bytes([
data[off + 20],
data[off + 21],
data[off + 22],
data[off + 23],
]) as u64,
u32::from_le_bytes([
data[off + 24],
data[off + 25],
data[off + 26],
data[off + 27],
]) as u64,
)
};
phdrs.push(ElfProgramHeader {
p_type,
flags,
offset: p_offset,
vaddr: p_vaddr,
paddr: p_paddr,
filesz: p_filesz,
memsz: p_memsz,
align: p_align,
});
}
Ok(phdrs)
}
pub fn parse_elf_dynamic(data: &[u8]) -> Result<Vec<ElfDynamic>, String> {
let sections = Self::parse_elf_sections(data).unwrap_or_default();
let dynamic_sec = sections.iter().find(|s| s.name == ".dynamic");
let dynamic_sec = match dynamic_sec {
Some(s) => s,
None => return Ok(Vec::new()),
};
let dyn_data =
&data[dynamic_sec.offset as usize..(dynamic_sec.offset + dynamic_sec.size) as usize];
let entry_size = 16; let num_entries = dyn_data.len() / entry_size;
let mut entries = Vec::new();
for i in 0..num_entries {
let off = i * entry_size;
if off + entry_size > dyn_data.len() {
break;
}
let d_tag = u64::from_le_bytes(dyn_data[off..off + 8].try_into().unwrap());
let d_val = u64::from_le_bytes(dyn_data[off + 8..off + 16].try_into().unwrap());
entries.push(ElfDynamic { d_tag, d_val });
}
Ok(entries)
}
pub fn parse_elf_notes(
data: &[u8],
section: &ElfSectionHeader,
) -> Result<Vec<ElfNote>, String> {
if section.sh_type != sht::NOTE {
return Ok(Vec::new());
}
let note_data = &data[section.offset as usize..(section.offset + section.size) as usize];
let mut notes = Vec::new();
let mut pos = 0usize;
while pos + 12 <= note_data.len() {
let namesz = u32::from_le_bytes([
note_data[pos],
note_data[pos + 1],
note_data[pos + 2],
note_data[pos + 3],
]) as usize;
let descsz = u32::from_le_bytes([
note_data[pos + 4],
note_data[pos + 5],
note_data[pos + 6],
note_data[pos + 7],
]) as usize;
let desc_type = u32::from_le_bytes([
note_data[pos + 8],
note_data[pos + 9],
note_data[pos + 10],
note_data[pos + 11],
]);
let name_start = pos + 12;
let name_end = std::cmp::min(name_start + namesz, note_data.len());
let name = String::from_utf8_lossy(¬e_data[name_start..name_end])
.trim_end_matches('\0')
.to_string();
let desc_start = (name_start + namesz + 3) & !3;
let desc_end = std::cmp::min(desc_start + descsz, note_data.len());
let desc = note_data[desc_start..desc_end].to_vec();
notes.push(ElfNote {
name,
desc_type,
desc,
});
pos = (desc_end + 3) & !3;
}
Ok(notes)
}
pub fn parse_macho_header(data: &[u8]) -> Result<MachOHeader, String> {
if data.len() < 32 {
return Err("Data too short for Mach-O header".to_string());
}
let magic = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
if magic != MACHO_MAGIC_64 && magic != MACHO_MAGIC_32 {
let magic_be = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
if magic_be != MACHO_MAGIC_64 && magic_be != MACHO_MAGIC_32 {
return Err(format!("Invalid Mach-O magic: 0x{:08X}", magic));
}
}
let cputype = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
let cpusubtype = u32::from_le_bytes([data[8], data[9], data[10], data[11]]);
let filetype = u32::from_le_bytes([data[12], data[13], data[14], data[15]]);
let ncmds = u32::from_le_bytes([data[16], data[17], data[18], data[19]]);
let sizeofcmds = u32::from_le_bytes([data[20], data[21], data[22], data[23]]);
let flags = u32::from_le_bytes([data[24], data[25], data[26], data[27]]);
let reserved = if magic == MACHO_MAGIC_64 {
u32::from_le_bytes([data[28], data[29], data[30], data[31]])
} else {
0
};
Ok(MachOHeader {
magic,
cputype,
cpusubtype,
filetype,
ncmds,
sizeofcmds,
flags,
reserved,
})
}
pub fn parse_macho_load_commands(
data: &[u8],
header: &MachOHeader,
) -> Result<Vec<MachOLoadCommand>, String> {
let mut commands = Vec::new();
let mut offset: usize = if header.magic == MACHO_MAGIC_64 {
32
} else {
28
};
let end_offset = offset + header.sizeofcmds as usize;
while offset + 8 <= data.len() && offset < end_offset {
let cmd = u32::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
]);
let cmdsize = u32::from_le_bytes([
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
]) as usize;
if cmdsize == 0 {
break;
}
let cmd_data = if offset + cmdsize <= data.len() {
&data[offset..offset + cmdsize]
} else {
&data[offset..]
};
match cmd {
0x19 => {
if let Some(seg) = Self::_parse_macho_segment_64(cmd_data) {
commands.push(MachOLoadCommand::Segment(seg));
}
}
0x02 => {
if cmd_data.len() >= 24 {
let symoff = u32::from_le_bytes([
cmd_data[8],
cmd_data[9],
cmd_data[10],
cmd_data[11],
]);
let nsyms = u32::from_le_bytes([
cmd_data[12],
cmd_data[13],
cmd_data[14],
cmd_data[15],
]);
let stroff = u32::from_le_bytes([
cmd_data[16],
cmd_data[17],
cmd_data[18],
cmd_data[19],
]);
let strsize = u32::from_le_bytes([
cmd_data[20],
cmd_data[21],
cmd_data[22],
cmd_data[23],
]);
commands.push(MachOLoadCommand::Symtab(MachSymtabCmd {
symoff,
nsyms,
stroff,
strsize,
}));
}
}
0x0B => {
if cmd_data.len() >= 32 {
let ilocalsym = u32::from_le_bytes([
cmd_data[8],
cmd_data[9],
cmd_data[10],
cmd_data[11],
]);
let nlocalsym = u32::from_le_bytes([
cmd_data[12],
cmd_data[13],
cmd_data[14],
cmd_data[15],
]);
let iextdefsym = u32::from_le_bytes([
cmd_data[16],
cmd_data[17],
cmd_data[18],
cmd_data[19],
]);
let nextdefsym = u32::from_le_bytes([
cmd_data[20],
cmd_data[21],
cmd_data[22],
cmd_data[23],
]);
let iundefsym = u32::from_le_bytes([
cmd_data[24],
cmd_data[25],
cmd_data[26],
cmd_data[27],
]);
let nundefsym = u32::from_le_bytes([
cmd_data[28],
cmd_data[29],
cmd_data[30],
cmd_data[31],
]);
commands.push(MachOLoadCommand::Dysymtab(MachDysymtabCmd {
ilocalsym,
nlocalsym,
iextdefsym,
nextdefsym,
iundefsym,
nundefsym,
}));
}
}
0x1B => {
if cmd_data.len() >= 24 {
let mut uuid = [0u8; 16];
uuid.copy_from_slice(&cmd_data[8..24]);
commands.push(MachOLoadCommand::Uuid(MachUuidCmd { uuid }));
}
}
0x24 => {
if cmd_data.len() >= 16 {
let version = u32::from_le_bytes([
cmd_data[8],
cmd_data[9],
cmd_data[10],
cmd_data[11],
]);
let sdk = u32::from_le_bytes([
cmd_data[12],
cmd_data[13],
cmd_data[14],
cmd_data[15],
]);
commands.push(MachOLoadCommand::VersionMinMacosx(MachVersionMinCmd {
cmd,
version,
sdk,
}));
}
}
0x25 => {
if cmd_data.len() >= 16 {
let version = u32::from_le_bytes([
cmd_data[8],
cmd_data[9],
cmd_data[10],
cmd_data[11],
]);
let sdk = u32::from_le_bytes([
cmd_data[12],
cmd_data[13],
cmd_data[14],
cmd_data[15],
]);
commands.push(MachOLoadCommand::VersionMinIphoneos(MachVersionMinCmd {
cmd,
version,
sdk,
}));
}
}
_ => {
commands.push(MachOLoadCommand::Unknown {
cmd,
cmdsize: cmdsize as u32,
data: cmd_data.to_vec(),
});
}
}
offset += cmdsize;
}
Ok(commands)
}
fn _parse_macho_segment_64(data: &[u8]) -> Option<MachSegment> {
if data.len() < 72 {
return None;
}
let segname = String::from_utf8_lossy(&data[8..24])
.trim_end_matches('\0')
.to_string();
let vmaddr = u64::from_le_bytes(data[24..32].try_into().ok()?);
let vmsize = u64::from_le_bytes(data[32..40].try_into().ok()?);
let fileoff = u64::from_le_bytes(data[40..48].try_into().ok()?);
let filesize = u64::from_le_bytes(data[48..56].try_into().ok()?);
let maxprot = u32::from_le_bytes([data[56], data[57], data[58], data[59]]);
let initprot = u32::from_le_bytes([data[60], data[61], data[62], data[63]]);
let nsects = u32::from_le_bytes([data[64], data[65], data[66], data[67]]);
let flags = u32::from_le_bytes([data[68], data[69], data[70], data[71]]);
Some(MachSegment {
segname,
vmaddr,
vmsize,
fileoff,
filesize,
maxprot,
initprot,
nsects,
flags,
})
}
fn parse_macho_sections_in_segment(
data: &[u8],
seg: &MachSegment,
_ncmds: u32,
) -> Vec<MachOSectionData> {
let mut sections = Vec::new();
let section_header_size = 80; for i in 0..seg.nsects as usize {
let sec_off = seg.fileoff as usize + i * section_header_size;
if sec_off + section_header_size > data.len() {
break;
}
let sectname = String::from_utf8_lossy(&data[sec_off..sec_off + 16])
.trim_end_matches('\0')
.to_string();
let s_segname = String::from_utf8_lossy(&data[sec_off + 16..sec_off + 32])
.trim_end_matches('\0')
.to_string();
let addr = u64::from_le_bytes(
data[sec_off + 32..sec_off + 40]
.try_into()
.unwrap_or([0; 8]),
);
let size = u64::from_le_bytes(
data[sec_off + 40..sec_off + 48]
.try_into()
.unwrap_or([0; 8]),
);
let soff = u32::from_le_bytes([
data[sec_off + 48],
data[sec_off + 49],
data[sec_off + 50],
data[sec_off + 51],
]);
let align = u32::from_le_bytes([
data[sec_off + 52],
data[sec_off + 53],
data[sec_off + 54],
data[sec_off + 55],
]);
let reloff = u32::from_le_bytes([
data[sec_off + 56],
data[sec_off + 57],
data[sec_off + 58],
data[sec_off + 59],
]);
let nreloc = u32::from_le_bytes([
data[sec_off + 60],
data[sec_off + 61],
data[sec_off + 62],
data[sec_off + 63],
]);
let sflags = u32::from_le_bytes([
data[sec_off + 64],
data[sec_off + 65],
data[sec_off + 66],
data[sec_off + 67],
]);
let sec_data = if soff > 0 && (soff as usize) < data.len() {
let end = std::cmp::min((soff + size as u32) as usize, data.len());
data[soff as usize..end].to_vec()
} else {
Vec::new()
};
sections.push(MachOSectionData {
sectname,
segname: s_segname,
addr,
size,
offset: soff,
align,
reloff,
nreloc,
flags: sflags,
data: sec_data,
});
}
sections
}
pub fn parse_macho_symbols(
data: &[u8],
symtab: &MachSymtabCmd,
) -> Result<Vec<MachOSymbol>, String> {
let sym_off = symtab.symoff as usize;
let stroff = symtab.stroff as usize;
let nsyms = symtab.nsyms as usize;
let strsize = symtab.strsize as usize;
if sym_off == 0 || stroff == 0 || nsyms == 0 {
return Ok(Vec::new());
}
let entry_size = 16; let mut symbols = Vec::new();
for i in 0..nsyms {
let off = sym_off + i * entry_size;
if off + entry_size > data.len() {
break;
}
let n_strx =
u32::from_le_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]])
as usize;
let n_type = data[off + 4];
let n_sect = data[off + 5];
let n_desc = u16::from_le_bytes([data[off + 6], data[off + 7]]);
let n_value = u64::from_le_bytes(data[off + 8..off + 16].try_into().unwrap_or([0; 8]));
let name = if n_strx < strsize {
let name_start = stroff + n_strx;
let name_end = data[name_start..]
.iter()
.position(|&b| b == 0)
.map(|p| name_start + p)
.unwrap_or(std::cmp::min(name_start + 256, data.len()));
String::from_utf8_lossy(&data[name_start..name_end]).to_string()
} else {
String::new()
};
symbols.push(MachOSymbol {
name,
n_type,
n_sect,
n_desc,
n_value,
});
}
Ok(symbols)
}
pub fn parse_coff_header(data: &[u8]) -> Result<CoffHeader, String> {
if data.len() < 20 {
return Err("Data too short for COFF header".to_string());
}
let machine = u16::from_le_bytes([data[0], data[1]]);
let num_sections = u16::from_le_bytes([data[2], data[3]]);
let timestamp = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
let symtab_offset = u32::from_le_bytes([data[8], data[9], data[10], data[11]]);
let num_symbols = u32::from_le_bytes([data[12], data[13], data[14], data[15]]);
let opt_header_size = u16::from_le_bytes([data[16], data[17]]);
let characteristics = u16::from_le_bytes([data[18], data[19]]);
Ok(CoffHeader {
machine,
num_sections,
timestamp,
symtab_offset,
num_symbols,
opt_header_size,
characteristics,
})
}
pub fn parse_coff_sections(
data: &[u8],
header: &CoffHeader,
) -> Result<Vec<CoffSectionHeader>, String> {
let mut sections = Vec::new();
let sec_start = 20 + header.opt_header_size as usize;
let sec_entry_size = 40;
for i in 0..header.num_sections as usize {
let off = sec_start + i * sec_entry_size;
if off + sec_entry_size > data.len() {
break;
}
let name = String::from_utf8_lossy(&data[off..off + 8])
.trim_end_matches('\0')
.to_string();
let virtual_size =
u32::from_le_bytes([data[off + 8], data[off + 9], data[off + 10], data[off + 11]]);
let virtual_address = u32::from_le_bytes([
data[off + 12],
data[off + 13],
data[off + 14],
data[off + 15],
]);
let raw_data_size = u32::from_le_bytes([
data[off + 16],
data[off + 17],
data[off + 18],
data[off + 19],
]);
let raw_data_ptr = u32::from_le_bytes([
data[off + 20],
data[off + 21],
data[off + 22],
data[off + 23],
]);
let reloc_ptr = u32::from_le_bytes([
data[off + 24],
data[off + 25],
data[off + 26],
data[off + 27],
]);
let linenum_ptr = u32::from_le_bytes([
data[off + 28],
data[off + 29],
data[off + 30],
data[off + 31],
]);
let num_relocs = u16::from_le_bytes([data[off + 32], data[off + 33]]);
let num_linenums = u16::from_le_bytes([data[off + 34], data[off + 35]]);
let characteristics = u32::from_le_bytes([
data[off + 36],
data[off + 37],
data[off + 38],
data[off + 39],
]);
sections.push(CoffSectionHeader {
name,
virtual_size,
virtual_address,
raw_data_size,
raw_data_ptr,
reloc_ptr,
linenum_ptr,
num_relocs,
num_linenums,
characteristics,
});
}
Ok(sections)
}
pub fn parse_coff_symbols(data: &[u8], header: &CoffHeader) -> Result<Vec<CoffSymbol>, String> {
let symtab_offset = header.symtab_offset as usize;
if symtab_offset == 0 || header.num_symbols == 0 {
return Ok(Vec::new());
}
let mut symbols = Vec::new();
let mut offset = symtab_offset;
let entry_size = 18;
let strtab_offset = symtab_offset + header.num_symbols as usize * entry_size;
for _ in 0..header.num_symbols {
if offset + entry_size > data.len() {
break;
}
let name = if data[offset] == 0
&& data[offset + 1] == 0
&& data[offset + 2] == 0
&& data[offset + 3] == 0
{
let str_off = u32::from_le_bytes([
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
]) as usize;
let name_start = strtab_offset + str_off;
if name_start < data.len() {
let name_end = data[name_start..]
.iter()
.position(|&b| b == 0)
.map(|p| name_start + p)
.unwrap_or(data.len());
String::from_utf8_lossy(&data[name_start..name_end]).to_string()
} else {
String::new()
}
} else {
String::from_utf8_lossy(&data[offset..offset + 8])
.trim_end_matches('\0')
.to_string()
};
let value = u32::from_le_bytes([
data[offset + 8],
data[offset + 9],
data[offset + 10],
data[offset + 11],
]);
let section_number = i16::from_le_bytes([data[offset + 12], data[offset + 13]]);
let sym_type = u16::from_le_bytes([data[offset + 14], data[offset + 15]]);
let storage_class = data[offset + 16];
let num_aux = data[offset + 17];
symbols.push(CoffSymbol {
name,
value,
section_number,
sym_type,
storage_class,
num_aux,
});
offset += entry_size + num_aux as usize * entry_size;
}
Ok(symbols)
}
pub fn is_valid(&self) -> bool {
self.format != ObjectFormat::Unknown
}
pub fn machine_name(&self) -> &str {
&self.machine
}
pub fn get_section(&self, name: &str) -> Option<&ObjectSection> {
self.sections.iter().find(|s| s.name == name)
}
pub fn get_symbols_in_section(&self, section_index: u16) -> Vec<&ObjectSymbol> {
self.symbols
.iter()
.filter(|s| s.section_index == section_index)
.collect()
}
pub fn get_global_symbols(&self) -> Vec<&ObjectSymbol> {
self.symbols.iter().filter(|s| s.is_global).collect()
}
pub fn num_sections(&self) -> usize {
self.sections.len()
}
pub fn num_symbols(&self) -> usize {
self.symbols.len()
}
pub fn disassemble_section(
data: &[u8],
machine: &str,
start_addr: u64,
) -> Vec<DisassembledInst> {
let mut insts = Vec::new();
let mut addr = start_addr;
let chunk_size = match machine {
"x86_64" | "x86" => 1, "aarch64" | "arm64" => 4,
"arm" => 4,
_ => 4,
};
let mut pos = 0usize;
while pos + chunk_size <= data.len() {
let actual_size = if machine == "x86_64" {
let b = data[pos];
if b == 0x0F {
std::cmp::min(4, data.len() - pos)
} else if b == 0xFF || b == 0x8B || b == 0x89 {
std::cmp::min(3, data.len() - pos)
} else if b == 0xE8 || b == 0xE9 {
std::cmp::min(5, data.len() - pos)
} else if b == 0xC3 || b == 0x90 || b == 0xCC {
1
} else {
std::cmp::min(3, data.len() - pos)
}
} else {
chunk_size
};
let end = std::cmp::min(pos + actual_size, data.len());
let bytes = data[pos..end].to_vec();
let mnemonic = if bytes.is_empty() {
"??".to_string()
} else if bytes.len() == 1 && bytes[0] == 0xC3 {
"ret".to_string()
} else if bytes.len() == 1 && bytes[0] == 0x90 {
"nop".to_string()
} else if bytes.len() >= 5 && bytes[0] == 0xE9 {
"jmp".to_string()
} else if bytes.len() >= 5 && bytes[0] == 0xE8 {
"call".to_string()
} else {
format!("data{}", bytes.len())
};
insts.push(DisassembledInst {
address: addr,
bytes,
mnemonic,
operands: String::new(),
});
addr += actual_size as u64;
pos += actual_size;
}
insts
}
pub fn find_function_symbols(&self) -> Vec<(String, u64, u64)> {
self.symbols
.iter()
.filter(|s| s.is_function)
.map(|s| (s.name.clone(), s.value, s.size))
.collect()
}
}
pub mod sht {
pub const NULL: u32 = 0;
pub const PROGBITS: u32 = 1;
pub const SYMTAB: u32 = 2;
pub const STRTAB: u32 = 3;
pub const RELA: u32 = 4;
pub const NOBITS: u32 = 8;
pub const REL: u32 = 9;
pub const DYNAMIC: u32 = 6;
pub const NOTE: u32 = 7;
}
pub mod stb {
pub const LOCAL: u8 = 0;
pub const GLOBAL: u8 = 1;
pub const WEAK: u8 = 2;
}
pub mod stt {
pub const NOTYPE: u8 = 0;
pub const OBJECT: u8 = 1;
pub const FUNC: u8 = 2;
pub const SECTION: u8 = 3;
pub const FILE: u8 = 4;
}
pub const XCOFF32_MAGIC: u16 = 0x01DF;
pub const XCOFF64_MAGIC: u16 = 0x01F7;
#[derive(Debug, Clone)]
pub struct XcoffFileHeader {
pub magic: u16,
pub num_sections: u16,
pub timestamp: i32,
pub symtab_offset: u64,
pub symtab_count: i32,
pub optional_header_size: u16,
pub flags: u16,
}
#[derive(Debug, Clone)]
pub struct XcoffOptionalHeader {
pub magic: u16, pub vstamp: u16,
pub text_size: u64,
pub data_size: u64,
pub bss_size: u64,
pub entry: u64,
pub text_start: u64,
pub data_start: u64,
pub toc: u64,
}
#[derive(Debug, Clone)]
pub struct XcoffSectionHeader {
pub name: String,
pub paddr: u64,
pub vaddr: u64,
pub size: u64,
pub scnptr: u64, pub relptr: u64, pub lnnoptr: u64, pub num_relocs: u32,
pub num_lnno: u32,
pub flags: u32,
}
#[derive(Debug, Clone)]
pub struct XcoffSymbol {
pub name: String,
pub value: u64,
pub section_number: i16,
pub sym_type: u16,
pub storage_class: u8,
pub num_aux: u8,
pub aux_entries: Vec<XcoffAuxEntry>,
}
#[derive(Debug, Clone)]
pub enum XcoffAuxEntry {
Csect {
length: u64,
parm_hash: u32,
sn_type: u8, smclas: u8,
stab: u32,
x_snstab: u16,
},
Function {
offset_to_exception_table: u64,
size_of_function: u64,
line_number_pointer: u64,
end_index: u32,
},
File { name: String },
Except,
}
#[derive(Debug, Clone)]
pub struct XcoffRelocation {
pub vaddr: u64,
pub symbol_index: u32,
pub info: u8,
pub rel_type: u8,
}
#[derive(Debug, Clone)]
pub struct XcoffLoaderHeader {
pub version: i32,
pub num_symbols: i32,
pub num_relocs: i32,
pub import_file_id: i32,
pub string_table_length: i32,
pub init_pointer: u64,
pub term_pointer: u64,
pub entry_point: u64,
}
#[derive(Debug, Clone)]
pub struct XcoffLineNumber {
pub address: u64,
pub line_number: u16,
}
impl XcoffFileHeader {
pub fn new_xcoff32() -> Self {
XcoffFileHeader {
magic: XCOFF32_MAGIC,
num_sections: 0,
timestamp: 0,
symtab_offset: 0,
symtab_count: 0,
optional_header_size: 0,
flags: 0,
}
}
pub fn new_xcoff64() -> Self {
XcoffFileHeader {
magic: XCOFF64_MAGIC,
num_sections: 0,
timestamp: 0,
symtab_offset: 0,
symtab_count: 0,
optional_header_size: 0,
flags: 0,
}
}
pub fn is_64bit(&self) -> bool {
self.magic == XCOFF64_MAGIC
}
}
pub const GOFF_ESD: u8 = 0x00;
pub const GOFF_TXT: u8 = 0x01;
pub const GOFF_RLD: u8 = 0x02;
pub const GOFF_END: u8 = 0x04;
#[derive(Debug, Clone)]
pub struct GoffHeader {
pub level: u8,
pub length: u32,
}
#[derive(Debug, Clone)]
pub struct GoffEsdRecord {
pub name: String,
pub esd_type: u8, pub symbol_id: u32,
pub binder: u8, pub amode: u8, pub rmode: u8, pub length: u64,
pub alignment: u64,
}
pub mod goff_esd {
pub const SD: u8 = 0x00; pub const LD: u8 = 0x01; pub const ED: u8 = 0x02; pub const PR: u8 = 0x04; pub const PC: u8 = 0x05; pub const CM: u8 = 0x06; pub const WX: u8 = 0x08; pub const ER: u8 = 0x0A; }
#[derive(Debug, Clone)]
pub struct GoffTxtRecord {
pub section_id: u32,
pub offset: u64,
pub length: u32,
pub data: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct GoffRldRecord {
pub symbol_id: u32,
pub position: u64,
pub length: u8,
pub flags: u8,
pub addend: i64,
}
impl GoffEsdRecord {
pub fn new_sd(name: &str, id: u32, len: u64, align: u64, amode: u8, rmode: u8) -> Self {
GoffEsdRecord {
name: name.to_string(),
esd_type: goff_esd::SD,
symbol_id: id,
binder: 0,
amode,
rmode,
length: len,
alignment: align,
}
}
pub fn new_er(name: &str, id: u32) -> Self {
GoffEsdRecord {
name: name.to_string(),
esd_type: goff_esd::ER,
symbol_id: id,
binder: 0,
amode: 0,
rmode: 0,
length: 0,
alignment: 0,
}
}
pub fn new_wx(name: &str, id: u32) -> Self {
GoffEsdRecord {
name: name.to_string(),
esd_type: goff_esd::WX,
symbol_id: id,
binder: 0,
amode: 0,
rmode: 0,
length: 0,
alignment: 0,
}
}
pub fn new_cm(name: &str, id: u32, len: u64, align: u64) -> Self {
GoffEsdRecord {
name: name.to_string(),
esd_type: goff_esd::CM,
symbol_id: id,
binder: 0,
amode: 0,
rmode: 0,
length: len,
alignment: align,
}
}
}
pub const SPIRV_MAGIC: u32 = 0x07230203;
#[derive(Debug, Clone)]
pub struct SpirvHeader {
pub magic: u32,
pub version: u32,
pub generator_magic: u32,
pub bound: u32, pub schema: u32, }
impl SpirvHeader {
pub fn is_valid(&self) -> bool {
self.magic == SPIRV_MAGIC
}
pub fn version_major(&self) -> u32 {
(self.version >> 16) & 0xFF
}
pub fn version_minor(&self) -> u32 {
(self.version >> 8) & 0xFF
}
}
#[derive(Debug, Clone)]
pub struct SpirvInstruction {
pub word_count: u16,
pub opcode: u16,
pub operands: Vec<u32>,
}
#[derive(Debug, Clone)]
pub struct SpirvModule {
pub header: SpirvHeader,
pub instructions: Vec<SpirvInstruction>,
}
impl SpirvModule {
pub fn parse(data: &[u8]) -> Result<Self, String> {
if data.len() < 20 {
return Err("SPIR-V data too short for header".to_string());
}
let magic = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
if magic != SPIRV_MAGIC {
return Err(format!("Invalid SPIR-V magic: 0x{:08X}", magic));
}
let header = SpirvHeader {
magic,
version: u32::from_le_bytes([data[4], data[5], data[6], data[7]]),
generator_magic: u32::from_le_bytes([data[8], data[9], data[10], data[11]]),
bound: u32::from_le_bytes([data[12], data[13], data[14], data[15]]),
schema: u32::from_le_bytes([data[16], data[17], data[18], data[19]]),
};
let mut instructions: Vec<SpirvInstruction> = Vec::new();
let mut offset = 20usize;
while offset + 4 <= data.len() {
let word0 = u32::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
]);
let word_count = (word0 >> 16) as u16;
let opcode = (word0 & 0xFFFF) as u16;
if word_count == 0 {
break;
}
let instr_end = offset + (word_count as usize) * 4;
if instr_end > data.len() {
return Err(format!(
"SPIR-V instruction extends past end of data at offset {}",
offset
));
}
let mut operands: Vec<u32> = Vec::with_capacity(word_count as usize - 1);
for i in 1..word_count as usize {
let word_offset = offset + i * 4;
operands.push(u32::from_le_bytes([
data[word_offset],
data[word_offset + 1],
data[word_offset + 2],
data[word_offset + 3],
]));
}
instructions.push(SpirvInstruction {
word_count,
opcode,
operands,
});
offset = instr_end;
}
Ok(SpirvModule {
header,
instructions,
})
}
pub fn instruction_count(&self) -> usize {
self.instructions.len()
}
}
pub const ARMAG: &str = "!<arch>\n";
pub const THIN_ARMAG: &str = "!<thin>\n";
#[derive(Debug, Clone)]
pub struct ArchiveMemberHeader {
pub name: [u8; 16],
pub date: [u8; 12],
pub uid: [u8; 6],
pub gid: [u8; 6],
pub mode: [u8; 8],
pub size: [u8; 10],
pub fmag: [u8; 2], }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArchiveSymbolTableFormat {
Svr4Gnu,
Bsd,
Darwin,
Aix,
}
#[derive(Debug, Clone)]
pub struct ArchiveSymbolTable {
pub format: ArchiveSymbolTableFormat,
pub symbol_map: HashMap<String, usize>, pub member_names: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct ArchiveFile {
pub is_thin: bool,
pub members: Vec<ArchiveMember>,
pub symbol_table: Option<ArchiveSymbolTable>,
pub string_table: Vec<u8>, }
#[derive(Debug, Clone)]
pub struct ArchiveMember {
pub name: String,
pub header: ArchiveMemberHeader,
pub data: Vec<u8>,
pub member_index: usize,
}
impl ArchiveFile {
pub fn detect(data: &[u8]) -> bool {
data.len() >= 8
&& (data.starts_with(ARMAG.as_bytes()) || data.starts_with(THIN_ARMAG.as_bytes()))
}
pub fn parse(data: &[u8]) -> Result<Self, String> {
if data.len() < 8 {
return Err("Archive too short".to_string());
}
let is_thin = data.starts_with(THIN_ARMAG.as_bytes());
let is_regular = data.starts_with(ARMAG.as_bytes());
if !is_thin && !is_regular {
return Err("Invalid archive magic".to_string());
}
let mut members: Vec<ArchiveMember> = Vec::new();
let mut offset = 8usize; let mut member_index = 0usize;
while offset + 60 <= data.len() {
let hdr_data = &data[offset..offset + 60];
if hdr_data[0] == 0x00 || (hdr_data[0] == b'\n' && hdr_data[1] == b'\n') {
break;
}
let mut header = ArchiveMemberHeader {
name: [0u8; 16],
date: [0u8; 12],
uid: [0u8; 6],
gid: [0u8; 6],
mode: [0u8; 8],
size: [0u8; 10],
fmag: [0u8; 2],
};
header.name.copy_from_slice(&hdr_data[0..16]);
header.date.copy_from_slice(&hdr_data[16..28]);
header.uid.copy_from_slice(&hdr_data[28..34]);
header.gid.copy_from_slice(&hdr_data[34..40]);
header.mode.copy_from_slice(&hdr_data[40..48]);
header.size.copy_from_slice(&hdr_data[48..58]);
header.fmag.copy_from_slice(&hdr_data[58..60]);
let size_str = std::str::from_utf8(&header.size)
.map_err(|_| "Invalid size field in archive header".to_string())?
.trim();
let member_size: u64 = size_str
.parse()
.map_err(|_| format!("Invalid member size: {}", size_str))?;
let name_bytes = &header.name;
let name = if name_bytes[0] == b'/' && name_bytes[1] == b'/' {
format!("//long/name/{}", member_index)
} else if name_bytes[0] == b'/' {
format!("//ext/{}", member_index)
} else if name_bytes[0] == b'#' && name_bytes[1] == b'1' && name_bytes[2] == b'/' {
String::from_utf8_lossy(&header.name).trim_end().to_string()
} else {
String::from_utf8_lossy(&header.name).trim_end().to_string()
};
offset += 60; let data_end = offset + member_size as usize;
if data_end > data.len() {
return Err(format!("Archive member '{}' extends past EOF", name));
}
let member_data = data[offset..data_end].to_vec();
members.push(ArchiveMember {
name: name.clone(),
header,
data: member_data,
member_index,
});
member_index += 1;
if member_size % 2 != 0 {
offset = data_end + 1;
} else {
offset = data_end;
}
}
Ok(ArchiveFile {
is_thin,
members,
symbol_table: None,
string_table: Vec::new(),
})
}
pub fn find_member(&self, name: &str) -> Option<&ArchiveMember> {
self.members.iter().find(|m| m.name == name)
}
pub fn member_count(&self) -> usize {
self.members.len()
}
}
pub fn parse_xcoff(data: &[u8]) -> Result<XcoffFileHeader, String> {
if data.len() < 20 {
return Err("XCOFF data too short for header".to_string());
}
let magic = u16::from_be_bytes([data[0], data[1]]);
if magic != XCOFF32_MAGIC && magic != XCOFF64_MAGIC {
return Err(format!("Invalid XCOFF magic: 0x{:04X}", magic));
}
let num_sections = u16::from_be_bytes([data[2], data[3]]);
let timestamp = i32::from_be_bytes([data[4], data[5], data[6], data[7]]);
let symtab_offset = if magic == XCOFF64_MAGIC {
u64::from_be_bytes([0, 0, 0, 0, data[8], data[9], data[10], data[11]])
} else {
u32::from_be_bytes([data[8], data[9], data[10], data[11]]) as u64
};
let symtab_count = i32::from_be_bytes([data[12], data[13], data[14], data[15]]);
let optional_header_size = u16::from_be_bytes([data[16], data[17]]);
let flags = u16::from_be_bytes([data[18], data[19]]);
Ok(XcoffFileHeader {
magic,
num_sections,
timestamp,
symtab_offset,
symtab_count,
optional_header_size,
flags,
})
}
pub fn parse_xcoff_sections(
data: &[u8],
header: &XcoffFileHeader,
) -> Result<Vec<XcoffSectionHeader>, String> {
let mut sections: Vec<XcoffSectionHeader> = Vec::new();
let hdr_size = if header.is_64bit() { 72 } else { 40 };
let mut offset = 20 + header.optional_header_size as usize;
for _ in 0..header.num_sections {
if offset + hdr_size > data.len() {
break;
}
let name = String::from_utf8_lossy(&data[offset..offset + 8])
.trim_end_matches('\0')
.to_string();
offset += 8;
let paddr = u64::from_be_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
]);
offset += 8;
let vaddr = u64::from_be_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
]);
offset += 8;
let size = u64::from_be_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
]);
offset += 8;
let scnptr = u64::from_be_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
]);
offset += 8;
let relptr = u64::from_be_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
]);
offset += 8;
let lnnoptr = u64::from_be_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
]);
offset += 8;
let num_relocs = u32::from_be_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
]);
offset += 4;
let num_lnno = u32::from_be_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
]);
offset += 4;
let flags = u32::from_be_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
]);
offset += 4;
sections.push(XcoffSectionHeader {
name,
paddr,
vaddr,
size,
scnptr,
relptr,
lnnoptr,
num_relocs,
num_lnno,
flags,
});
}
Ok(sections)
}
pub fn parse_goff_header(data: &[u8]) -> Result<GoffHeader, String> {
if data.len() < 4 {
return Err("GOFF data too short".to_string());
}
let level = data[0];
let length = u32::from_be_bytes([0, data[1], data[2], data[3]]);
Ok(GoffHeader { level, length })
}
pub fn archive_member_name(header: &ArchiveMemberHeader) -> String {
let raw = &header.name;
let end = raw
.iter()
.position(|&b| b == b' ' || b == b'/')
.unwrap_or(16);
String::from_utf8_lossy(&raw[..end])
.trim_end_matches('/')
.to_string()
}
pub fn parse_archive_symtab_svr4(data: &[u8]) -> Result<ArchiveSymbolTable, String> {
if data.len() < 4 {
return Err("Symbol table too short".to_string());
}
let num_symbols = u32::from_be_bytes([data[0], data[1], data[2], data[3]]) as usize;
let offset_end = 4 + num_symbols * 4;
if data.len() < offset_end {
return Err("Symbol table truncated".to_string());
}
let mut member_offsets: Vec<u32> = Vec::with_capacity(num_symbols);
for i in 0..num_symbols {
let off = u32::from_be_bytes([
data[4 + i * 4],
data[5 + i * 4],
data[6 + i * 4],
data[7 + i * 4],
]);
member_offsets.push(off);
}
let strtab = &data[offset_end..];
let mut symbol_map: HashMap<String, usize> = HashMap::new();
let mut member_names: Vec<String> = Vec::new();
let mut start = 0usize;
for i in 0..num_symbols {
let end = strtab[start..]
.iter()
.position(|&b| b == 0)
.map(|p| start + p)
.unwrap_or(strtab.len());
let name = String::from_utf8_lossy(&strtab[start..end]).to_string();
symbol_map.insert(name.clone(), i);
member_names.push(name);
start = end + 1;
if start >= strtab.len() {
break;
}
}
Ok(ArchiveSymbolTable {
format: ArchiveSymbolTableFormat::Svr4Gnu,
symbol_map,
member_names,
})
}
pub const BIG_ARMAG: &str = "<bigaf>\n";
#[derive(Debug, Clone)]
pub struct BigArchiveMemberHeader {
pub name: [u8; 20],
pub first_page_offset: u64,
pub next_member_offset: u64,
pub prev_member_offset: u64,
pub date: [u8; 12],
pub uid: [u8; 12],
pub gid: [u8; 12],
pub mode: [u8; 12],
pub size: u64,
}
#[derive(Debug, Clone)]
pub struct BigArchiveFile {
pub member_count: u64,
pub gst_offset: u64, pub gst64_offset: u64, pub first_member_offset: u64,
pub last_member_offset: u64,
pub members: Vec<BigArchiveMember>,
}
#[derive(Debug, Clone)]
pub struct BigArchiveMember {
pub name: String,
pub header: BigArchiveMemberHeader,
pub data: Vec<u8>,
}
impl BigArchiveFile {
pub fn detect(data: &[u8]) -> bool {
data.len() >= 8 && data.starts_with(BIG_ARMAG.as_bytes())
}
pub fn parse_header(data: &[u8]) -> Result<Self, String> {
if !Self::detect(data) {
return Err("Not a big archive".to_string());
}
if data.len() < 128 {
return Err("Big archive header too short".to_string());
}
let member_count = u64::from_be_bytes([0, 0, 0, 0, 0, 0, 0, 0]);
let gst_offset = u64::from_be_bytes([
data[68], data[69], data[70], data[71], data[72], data[73], data[74], data[75],
]);
let gst64_offset = u64::from_be_bytes([
data[76], data[77], data[78], data[79], data[80], data[81], data[82], data[83],
]);
let first_member_offset = u64::from_be_bytes([
data[84], data[85], data[86], data[87], data[88], data[89], data[90], data[91],
]);
let last_member_offset = u64::from_be_bytes([
data[92], data[93], data[94], data[95], data[96], data[97], data[98], data[99],
]);
Ok(BigArchiveFile {
member_count,
gst_offset,
gst64_offset,
first_member_offset,
last_member_offset,
members: Vec::new(),
})
}
}
#[derive(Debug, Clone)]
pub struct WasmCustomSection {
pub name: String,
pub data: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct WasmLinkingSection {
pub version: u32,
pub symbol_table: Vec<WasmSymbolInfo>,
pub segment_info: Vec<WasmSegmentInfo>,
pub init_functions: Vec<u32>,
pub comdat_info: Vec<WasmComdatInfo>,
}
#[derive(Debug, Clone)]
pub struct WasmSymbolInfo {
pub kind: u8, pub flags: u32,
pub index: u32,
}
#[derive(Debug, Clone)]
pub struct WasmSegmentInfo {
pub name: String,
pub alignment: u32,
pub flags: u32,
}
#[derive(Debug, Clone)]
pub struct WasmComdatInfo {
pub name: String,
pub flags: u32,
}
impl WasmLinkingSection {
pub fn new() -> Self {
WasmLinkingSection {
version: 2,
symbol_table: Vec::new(),
segment_info: Vec::new(),
init_functions: Vec::new(),
comdat_info: Vec::new(),
}
}
pub fn add_symbol(&mut self, info: WasmSymbolInfo) {
self.symbol_table.push(info);
}
pub fn add_segment(&mut self, info: WasmSegmentInfo) {
self.segment_info.push(info);
}
}
#[derive(Debug, Clone)]
pub struct ThinArchiveMember {
pub name: String,
pub path: String, pub header: ArchiveMemberHeader,
}
pub fn is_thin_archive(data: &[u8]) -> bool {
data.len() >= 8 && data.starts_with(THIN_ARMAG.as_bytes())
}
pub fn parse_thin_archive(data: &[u8]) -> Result<Vec<ThinArchiveMember>, String> {
if !is_thin_archive(data) {
return Err("Not a thin archive".to_string());
}
let mut members: Vec<ThinArchiveMember> = Vec::new();
let mut offset = 8usize;
while offset + 60 <= data.len() {
let hdr_data = &data[offset..offset + 60];
if hdr_data[0] == 0x00 {
break;
}
let mut header = ArchiveMemberHeader {
name: [0u8; 16],
date: [0u8; 12],
uid: [0u8; 6],
gid: [0u8; 6],
mode: [0u8; 8],
size: [0u8; 10],
fmag: [0u8; 2],
};
header.name.copy_from_slice(&hdr_data[0..16]);
header.date.copy_from_slice(&hdr_data[16..28]);
header.uid.copy_from_slice(&hdr_data[28..34]);
header.gid.copy_from_slice(&hdr_data[34..40]);
header.mode.copy_from_slice(&hdr_data[40..48]);
header.size.copy_from_slice(&hdr_data[48..58]);
header.fmag.copy_from_slice(&hdr_data[58..60]);
let name = archive_member_name(&header);
let member_size: u64 = std::str::from_utf8(&header.size)
.unwrap_or("0")
.trim()
.parse()
.unwrap_or(0);
let path_data = if offset + 60 + member_size as usize <= data.len() {
&data[offset + 60..offset + 60 + member_size as usize]
} else {
&[]
};
let path = String::from_utf8_lossy(path_data)
.trim_end_matches('\0')
.trim_end_matches('\n')
.to_string();
members.push(ThinArchiveMember { name, path, header });
offset += 60 + member_size as usize;
if member_size % 2 != 0 {
offset += 1;
}
}
Ok(members)
}
pub const XCOFF_MAGIC_32: u16 = 0x01DF;
pub const XCOFF_MAGIC_64: u16 = 0x01F7;
#[derive(Debug, Clone)]
pub struct Xcoff64Header {
pub magic: u16,
pub nscns: u16,
pub timdat: u32,
pub symptr: u64,
pub nsyms: u32,
pub opthdr: u16,
pub flags: u16,
}
#[derive(Debug, Clone)]
pub struct Xcoff64SectionHeader {
pub name: String,
pub paddr: u64,
pub vaddr: u64,
pub size: u64,
pub scnptr: u64,
pub relptr: u64,
pub lnnoptr: u64,
pub nreloc: u32,
pub nlnno: u32,
pub flags: u32,
}
#[derive(Debug, Clone)]
pub struct Xcoff64Symbol {
pub name: String,
pub value: u64,
pub n_scnum: i16,
pub n_type: u16,
pub n_sclass: u8,
pub n_numaux: u8,
}
#[derive(Debug, Clone)]
pub struct Xcoff64Relocation {
pub vaddr: u64,
pub symbol_index: u32,
pub rtype: u8,
pub rsize: u8,
}
#[derive(Debug, Clone)]
pub struct Xcoff64ObjectFile {
pub header: Xcoff64Header,
pub sections: Vec<Xcoff64SectionHeader>,
pub symbols: Vec<Xcoff64Symbol>,
pub aux_entries: Vec<Vec<XcoffAuxEntry>>,
pub relocations: Vec<Vec<Xcoff64Relocation>>,
pub string_table: Vec<u8>,
}
pub fn parse_xcoff64(data: &[u8]) -> Result<Xcoff64ObjectFile, String> {
if data.len() < 24 {
return Err("data too short for XCOFF64 header".into());
}
let magic = u16::from_be_bytes([data[0], data[1]]);
if magic != XCOFF_MAGIC_64 {
return Err(format!("invalid XCOFF64 magic: {:#06x}", magic));
}
let nscns = u16::from_be_bytes([data[2], data[3]]);
let timdat = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
let symptr = u64::from_be_bytes([
data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15],
]);
let nsyms = u32::from_be_bytes([data[16], data[17], data[18], data[19]]);
let opthdr = u16::from_be_bytes([data[20], data[21]]);
let flags = u16::from_be_bytes([data[22], data[23]]);
let header = Xcoff64Header {
magic,
nscns,
timdat,
symptr,
nsyms,
opthdr,
flags,
};
let shdr_offset: usize = 24 + opthdr as usize;
let shdr_size: usize = 72;
let mut sections: Vec<Xcoff64SectionHeader> = Vec::with_capacity(nscns as usize);
for i in 0..nscns as usize {
let off = shdr_offset + i * shdr_size;
if off + shdr_size > data.len() {
break;
}
let name_end = data[off..off + 8].iter().position(|&b| b == 0).unwrap_or(8);
let name = String::from_utf8_lossy(&data[off..off + name_end]).into_owned();
let paddr = u64::from_be_bytes([
data[off + 8],
data[off + 9],
data[off + 10],
data[off + 11],
data[off + 12],
data[off + 13],
data[off + 14],
data[off + 15],
]);
let vaddr = u64::from_be_bytes([
data[off + 16],
data[off + 17],
data[off + 18],
data[off + 19],
data[off + 20],
data[off + 21],
data[off + 22],
data[off + 23],
]);
let size = u64::from_be_bytes([
data[off + 24],
data[off + 25],
data[off + 26],
data[off + 27],
data[off + 28],
data[off + 29],
data[off + 30],
data[off + 31],
]);
let scnptr = u64::from_be_bytes([
data[off + 32],
data[off + 33],
data[off + 34],
data[off + 35],
data[off + 36],
data[off + 37],
data[off + 38],
data[off + 39],
]);
let relptr = u64::from_be_bytes([
data[off + 40],
data[off + 41],
data[off + 42],
data[off + 43],
data[off + 44],
data[off + 45],
data[off + 46],
data[off + 47],
]);
let lnnoptr = u64::from_be_bytes([
data[off + 48],
data[off + 49],
data[off + 50],
data[off + 51],
data[off + 52],
data[off + 53],
data[off + 54],
data[off + 55],
]);
let nreloc = u32::from_be_bytes([
data[off + 56],
data[off + 57],
data[off + 58],
data[off + 59],
]);
let nlnno = u32::from_be_bytes([
data[off + 60],
data[off + 61],
data[off + 62],
data[off + 63],
]);
let flags_val = u32::from_be_bytes([
data[off + 64],
data[off + 65],
data[off + 66],
data[off + 67],
]);
sections.push(Xcoff64SectionHeader {
name,
paddr,
vaddr,
size,
scnptr,
relptr,
lnnoptr,
nreloc,
nlnno,
flags: flags_val,
});
}
let mut symbols: Vec<Xcoff64Symbol> = Vec::new();
let mut aux_entries: Vec<Vec<XcoffAuxEntry>> = Vec::new();
let sym_size: usize = 18; let strtab_off = symptr as usize + nsyms as usize * sym_size;
let string_table = if strtab_off + 4 <= data.len() {
let strtab_len = u32::from_be_bytes([
data[strtab_off],
data[strtab_off + 1],
data[strtab_off + 2],
data[strtab_off + 3],
]) as usize;
if strtab_off + strtab_len <= data.len() {
data[strtab_off..strtab_off + strtab_len].to_vec()
} else {
vec![0u8; 4]
}
} else {
vec![0u8; 4]
};
let mut sym_idx: usize = 0;
while sym_idx < nsyms as usize {
let off = symptr as usize + sym_idx * sym_size;
if off + sym_size > data.len() {
break;
}
let name = if data[off] == 0
&& data[off + 1] == 0
&& data[off + 2] == 0
&& data[off + 3] == 0
{
let stroff =
u32::from_be_bytes([data[off + 4], data[off + 5], data[off + 6], data[off + 7]])
as usize;
let end = string_table[stroff..]
.iter()
.position(|&b| b == 0)
.map(|p| stroff + p)
.unwrap_or(string_table.len());
String::from_utf8_lossy(&string_table[stroff..end.min(string_table.len())]).into_owned()
} else {
let end = data[off..off + 8].iter().position(|&b| b == 0).unwrap_or(8);
String::from_utf8_lossy(&data[off..off + end]).into_owned()
};
let value = u64::from_be_bytes([
data[off + 8],
data[off + 9],
data[off + 10],
data[off + 11],
data[off + 12],
data[off + 13],
data[off + 14],
data[off + 15],
]);
let n_scnum = i16::from_be_bytes([data[off + 16], data[off + 17]]);
let n_type = u16::from_be_bytes([data[off + 18], data[off + 19]]);
let n_sclass = data[off + 20];
let n_numaux = data[off + 21];
let sym = Xcoff64Symbol {
name,
value,
n_scnum,
n_type,
n_sclass,
n_numaux,
};
symbols.push(sym);
let mut aux: Vec<XcoffAuxEntry> = Vec::new();
let sclass = data[off + 20];
for a in 0..n_numaux as usize {
let aux_off = off + (1 + a) * sym_size;
if aux_off + sym_size > data.len() {
break;
}
match sclass {
2 | 107 | 111 => {
let scnlen = u64::from_be_bytes([
data[aux_off],
data[aux_off + 1],
data[aux_off + 2],
data[aux_off + 3],
data[aux_off + 4],
data[aux_off + 5],
data[aux_off + 6],
data[aux_off + 7],
]);
let parmhash = u32::from_be_bytes([
data[aux_off + 8],
data[aux_off + 9],
data[aux_off + 10],
data[aux_off + 11],
]);
let snstype = u16::from_be_bytes([data[aux_off + 12], data[aux_off + 13]]);
let smclass = data[aux_off + 14];
let smtype = data[aux_off + 15];
let stabcnt = u16::from_be_bytes([data[aux_off + 16], data[aux_off + 17]]);
aux.push(XcoffAuxEntry::Csect {
length: scnlen,
parm_hash: parmhash,
sn_type: snstype as u8,
smclas: smclass,
stab: 0,
x_snstab: stabcnt,
});
}
_ => {
let size = u64::from_be_bytes([
data[aux_off],
data[aux_off + 1],
data[aux_off + 2],
data[aux_off + 3],
data[aux_off + 4],
data[aux_off + 5],
data[aux_off + 6],
data[aux_off + 7],
]);
let lineno = u64::from_be_bytes([
data[aux_off + 8],
data[aux_off + 9],
data[aux_off + 10],
data[aux_off + 11],
data[aux_off + 12],
data[aux_off + 13],
data[aux_off + 14],
data[aux_off + 15],
]);
let fcnptr = u64::from_be_bytes([
data[aux_off + 16],
data[aux_off + 17],
data[aux_off + 18],
data[aux_off + 19],
data[aux_off + 20],
data[aux_off + 21],
data[aux_off + 22],
data[aux_off + 23],
]);
aux.push(XcoffAuxEntry::Function {
offset_to_exception_table: 0,
size_of_function: size,
line_number_pointer: lineno,
end_index: 0,
});
}
}
}
aux_entries.push(aux);
sym_idx += 1 + n_numaux as usize;
}
let mut relocations: Vec<Vec<Xcoff64Relocation>> = Vec::new();
for sec in §ions {
let mut sec_relocs: Vec<Xcoff64Relocation> = Vec::new();
let rel_size: usize = 14;
for r in 0..sec.nreloc as usize {
let roff = sec.relptr as usize + r * rel_size;
if roff + rel_size > data.len() {
break;
}
let vaddr = u64::from_be_bytes([
data[roff],
data[roff + 1],
data[roff + 2],
data[roff + 3],
data[roff + 4],
data[roff + 5],
data[roff + 6],
data[roff + 7],
]);
let symbol_index = u32::from_be_bytes([
data[roff + 8],
data[roff + 9],
data[roff + 10],
data[roff + 11],
]);
let rtype = data[roff + 12];
let rsize = data[roff + 13];
sec_relocs.push(Xcoff64Relocation {
vaddr,
symbol_index,
rtype,
rsize,
});
}
relocations.push(sec_relocs);
}
Ok(Xcoff64ObjectFile {
header,
sections,
symbols,
aux_entries,
relocations,
string_table,
})
}
impl ObjectFile {
pub fn from_xcoff64(xcoff: &Xcoff64ObjectFile, data: &[u8]) -> Self {
let mut sections = Vec::new();
for sec in &xcoff.sections {
let sec_data = if sec.size > 0 && sec.scnptr > 0 {
let start = sec.scnptr as usize;
let end = (start + sec.size as usize).min(data.len());
if start < data.len() {
data[start..end].to_vec()
} else {
Vec::new()
}
} else {
Vec::new()
};
sections.push(ObjectSection {
name: sec.name.clone(),
section_type: sec.flags,
data: sec_data,
vaddr: sec.vaddr,
size: sec.size,
flags: sec.flags as u64,
});
}
let mut symbols = Vec::new();
for sym in &xcoff.symbols {
let is_global = matches!(sym.n_sclass, 2 | 107 | 111); symbols.push(ObjectSymbol {
name: sym.name.clone(),
value: sym.value,
size: 0,
is_global,
is_function: sym.n_type & 0x20 != 0,
section_index: sym.n_scnum as u16,
});
}
ObjectFile {
format: ObjectFormat::Unknown, machine: "powerpc64".to_string(),
sections,
symbols,
entry: 0,
flags: xcoff.header.flags as u32,
}
}
}
#[derive(Debug, Clone)]
pub struct GoffObjectFile {
pub esd_entries: Vec<GoffEsdRecord>,
pub txt_records: Vec<GoffTxtRecord>,
pub rld_records: Vec<GoffRldRecord>,
pub section_data: HashMap<u32, Vec<u8>>,
}
pub fn parse_goff(data: &[u8]) -> Result<GoffObjectFile, String> {
if data.len() < 4 {
return Err("data too short for GOFF".into());
}
let mut esd_entries: Vec<GoffEsdRecord> = Vec::new();
let mut txt_records: Vec<GoffTxtRecord> = Vec::new();
let mut rld_records: Vec<GoffRldRecord> = Vec::new();
let mut section_data: HashMap<u32, Vec<u8>> = HashMap::new();
let mut pos: usize = 0;
while pos + 4 <= data.len() {
let record_length =
u32::from_be_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]) as usize;
if record_length == 0 {
pos += 4;
continue;
}
if pos + 4 + record_length > data.len() {
break;
}
let record_type = data[pos + 4];
let record_data = &data[pos + 5..pos + 4 + record_length];
match record_type {
0x00 => {
let mut rpos: usize = 0;
while rpos + 8 <= record_data.len() {
let entry_type = record_data[rpos];
let symbol_id = u32::from_be_bytes([
record_data[rpos + 1],
record_data[rpos + 2],
record_data[rpos + 3],
record_data[rpos + 4],
]);
let name_len = record_data[rpos + 5] as usize;
let name = if rpos + 6 + name_len <= record_data.len() {
String::from_utf8_lossy(&record_data[rpos + 6..rpos + 6 + name_len])
.into_owned()
} else {
String::new()
};
let amode = *record_data.get(rpos + 6 + name_len).unwrap_or(&0);
let rmode = *record_data.get(rpos + 7 + name_len).unwrap_or(&0);
let length = if rpos + 8 + name_len + 8 <= record_data.len() {
u64::from_be_bytes([
record_data[rpos + 8 + name_len],
record_data[rpos + 9 + name_len],
record_data[rpos + 10 + name_len],
record_data[rpos + 11 + name_len],
record_data[rpos + 12 + name_len],
record_data[rpos + 13 + name_len],
record_data[rpos + 14 + name_len],
record_data[rpos + 15 + name_len],
])
} else {
0
};
let alignment = if rpos + 16 + name_len + 8 <= record_data.len() {
u64::from_be_bytes([
record_data[rpos + 16 + name_len],
record_data[rpos + 17 + name_len],
record_data[rpos + 18 + name_len],
record_data[rpos + 19 + name_len],
record_data[rpos + 20 + name_len],
record_data[rpos + 21 + name_len],
record_data[rpos + 22 + name_len],
record_data[rpos + 23 + name_len],
])
} else {
0
};
esd_entries.push(GoffEsdRecord {
name,
esd_type: entry_type,
symbol_id,
binder: 0,
amode,
rmode,
length,
alignment,
});
rpos += 13 + name_len + 3;
}
}
0x01 => {
if record_data.len() >= 12 {
let section_id = u32::from_be_bytes([
record_data[0],
record_data[1],
record_data[2],
record_data[3],
]);
let offset = u64::from_be_bytes([
record_data[4],
record_data[5],
record_data[6],
record_data[7],
0,
0,
0,
0,
]);
let data_length = u32::from_be_bytes([
record_data[8],
record_data[9],
record_data[10],
record_data[11],
]) as usize;
let txt_data = if 12 + data_length <= record_data.len() {
record_data[12..12 + data_length].to_vec()
} else {
record_data[12..].to_vec()
};
txt_records.push(GoffTxtRecord {
section_id,
offset,
length: data_length as u32,
data: txt_data.clone(),
});
let sec = section_data.entry(section_id).or_default();
let end_offset = offset as usize + txt_data.len();
if end_offset > sec.len() {
sec.resize(end_offset, 0);
}
sec[offset as usize..offset as usize + txt_data.len()]
.copy_from_slice(&txt_data);
}
}
0x02 => {
let mut rpos: usize = 0;
while rpos + 10 <= record_data.len() {
let symbol_id = u32::from_be_bytes([
record_data[rpos],
record_data[rpos + 1],
record_data[rpos + 2],
record_data[rpos + 3],
]);
let position = u64::from_be_bytes([
record_data[rpos + 4],
record_data[rpos + 5],
record_data[rpos + 6],
record_data[rpos + 7],
0,
0,
0,
0,
]);
let reloc_len = record_data[rpos + 8];
let flags_val = record_data[rpos + 9];
let addend = if rpos + 18 <= record_data.len() {
i64::from_be_bytes([
record_data[rpos + 10],
record_data[rpos + 11],
record_data[rpos + 12],
record_data[rpos + 13],
record_data[rpos + 14],
record_data[rpos + 15],
record_data[rpos + 16],
record_data[rpos + 17],
])
} else {
0
};
rld_records.push(GoffRldRecord {
symbol_id,
position,
length: reloc_len,
flags: flags_val,
addend,
});
rpos += 18;
}
}
0x03 => {
break;
}
_ => {}
}
pos += 4 + record_length;
}
Ok(GoffObjectFile {
esd_entries,
txt_records,
rld_records,
section_data,
})
}
impl ObjectFile {
pub fn from_goff(goff: &GoffObjectFile) -> Self {
use llvm_native_core::object_file::goff_esd;
let mut sections = Vec::new();
let mut symbols = Vec::new();
for esd in &goff.esd_entries {
match esd.esd_type {
goff_esd::SD => {
let sec_data = goff
.section_data
.get(&esd.symbol_id)
.cloned()
.unwrap_or_default();
sections.push(ObjectSection {
name: esd.name.clone(),
section_type: goff_esd::SD as u32,
data: sec_data,
vaddr: 0,
size: esd.length,
flags: 0,
});
}
goff_esd::LD | goff_esd::ED => {
symbols.push(ObjectSymbol {
name: esd.name.clone(),
value: 0,
size: esd.length,
is_global: esd.esd_type == goff_esd::ED,
is_function: false,
section_index: 1,
});
}
goff_esd::CM => {
symbols.push(ObjectSymbol {
name: esd.name.clone(),
value: 0,
size: esd.length,
is_global: true,
is_function: false,
section_index: 0,
});
}
goff_esd::WX => {
symbols.push(ObjectSymbol {
name: esd.name.clone(),
value: 0,
size: 0,
is_global: true,
is_function: false,
section_index: 0,
});
}
_ => {}
}
}
ObjectFile {
format: ObjectFormat::Unknown,
machine: "s390x".to_string(),
sections,
symbols,
entry: 0,
flags: 0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detect_format_elf64() {
let mut data = vec![0u8; 64];
data[0..4].copy_from_slice(&ELF_MAGIC);
data[4] = 2; let format = detect_format(&data);
assert_eq!(format, Some(ObjectFormat::ELF64));
}
#[test]
fn test_detect_format_elf32() {
let mut data = vec![0u8; 64];
data[0..4].copy_from_slice(&ELF_MAGIC);
data[4] = 1; let format = detect_format(&data);
assert_eq!(format, Some(ObjectFormat::ELF32));
}
#[test]
fn test_detect_format_macho64() {
let data = vec![0xCF, 0xFA, 0xED, 0xFE, 0, 0, 0, 0]; let format = detect_format(&data);
assert_eq!(format, Some(ObjectFormat::MachO64));
}
#[test]
fn test_detect_format_wasm() {
let data = vec![0x00, 0x61, 0x73, 0x6D, 1, 0, 0, 0]; let format = detect_format(&data);
assert_eq!(format, Some(ObjectFormat::Wasm));
}
#[test]
fn test_detect_format_coff_x86_64() {
let data = vec![0x64, 0x86, 0, 0, 0, 0, 0, 0]; let format = detect_format(&data);
assert_eq!(format, Some(ObjectFormat::COFF));
}
#[test]
fn test_detect_format_none() {
let data = vec![0xDE, 0xAD, 0xBE, 0xEF];
let format = detect_format(&data);
assert_eq!(format, None);
}
#[test]
fn test_parse_elf_header_valid() {
let mut data = vec![0u8; 64];
data[0..4].copy_from_slice(&ELF_MAGIC);
data[4] = 2; data[5] = 1; data[6] = 1;
data[7] = 0;
data[18] = 62;
data[19] = 0;
let header = ObjectFile::parse_elf_header(&data);
assert!(header.is_some());
let h = header.unwrap();
assert_eq!(h.machine, ElfMachine::X86_64);
assert_eq!(h.ident.class, ElfClass::Elf64);
}
#[test]
fn test_parse_elf_header_invalid_magic() {
let data = vec![0u8; 64];
let header = ObjectFile::parse_elf_header(&data);
assert!(header.is_none());
}
#[test]
fn test_parse_elf_header_aarch64() {
let mut data = vec![0u8; 64];
data[0..4].copy_from_slice(&ELF_MAGIC);
data[4] = 2;
data[5] = 1;
data[6] = 1;
data[7] = 0;
data[18] = 183;
data[19] = 0;
let header = ObjectFile::parse_elf_header(&data);
assert!(header.is_some());
assert_eq!(header.unwrap().machine, ElfMachine::AArch64);
}
#[test]
fn test_parse_elf_program_headers_empty() {
let mut data = vec![0u8; 64];
data[0..4].copy_from_slice(&ELF_MAGIC);
data[4] = 2;
data[5] = 1;
data[6] = 1;
data[56..58].copy_from_slice(&0u16.to_le_bytes()); let phdrs = ObjectFile::parse_elf_program_headers(&data);
assert!(phdrs.is_ok());
assert!(phdrs.unwrap().is_empty());
}
#[test]
fn test_object_file_parse_elf() {
let mut data = vec![0u8; 128];
data[0..4].copy_from_slice(&ELF_MAGIC);
data[4] = 2; data[5] = 1;
data[6] = 1;
data[7] = 0;
data[18] = 62;
data[19] = 0;
data[60] = 1;
data[61] = 0;
data[40..48].copy_from_slice(&80u64.to_le_bytes());
data[58] = 64; data[60] = 1; data[62] = 1; let obj = ObjectFile::parse(&data);
assert!(obj.is_some());
let obj = obj.unwrap();
assert_eq!(obj.machine_name(), "x86_64");
assert!(obj.is_valid());
}
#[test]
fn test_parse_macho_header_valid() {
let mut data = vec![0u8; 32];
data[0..4].copy_from_slice(&[0xCF, 0xFA, 0xED, 0xFE]); data[4..8].copy_from_slice(&0x01000007u32.to_le_bytes()); data[12..16].copy_from_slice(&1u32.to_le_bytes()); let header = ObjectFile::parse_macho_header(&data);
assert!(header.is_ok());
let h = header.unwrap();
assert_eq!(h.cputype, 0x01000007);
assert_eq!(h.filetype, 1);
}
#[test]
fn test_parse_macho_header_invalid() {
let data = vec![0u8; 32];
let header = ObjectFile::parse_macho_header(&data);
assert!(header.is_err());
}
#[test]
fn test_parse_macho_load_commands_empty() {
let mut data = vec![0u8; 32];
data[0..4].copy_from_slice(&[0xCF, 0xFA, 0xED, 0xFE]);
data[4..8].copy_from_slice(&0x01000007u32.to_le_bytes());
data[12..16].copy_from_slice(&1u32.to_le_bytes());
data[16..20].copy_from_slice(&0u32.to_le_bytes()); data[20..24].copy_from_slice(&0u32.to_le_bytes()); let header = ObjectFile::parse_macho_header(&data).unwrap();
let cmds = ObjectFile::parse_macho_load_commands(&data, &header);
assert!(cmds.is_ok());
assert!(cmds.unwrap().is_empty());
}
#[test]
fn test_parse_coff_header_valid() {
let data = vec![
0x64, 0x86, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
let header = ObjectFile::parse_coff_header(&data);
assert!(header.is_ok());
let h = header.unwrap();
assert_eq!(h.machine, 0x8664);
assert_eq!(h.num_sections, 2);
}
#[test]
fn test_parse_coff_header_invalid() {
let data = vec![0u8; 10];
let header = ObjectFile::parse_coff_header(&data);
assert!(header.is_err());
}
#[test]
fn test_object_file_get_section() {
let obj = ObjectFile {
format: ObjectFormat::ELF64,
machine: "x86_64".to_string(),
sections: vec![ObjectSection {
name: ".text".to_string(),
section_type: 1,
data: vec![0x90, 0xC3],
vaddr: 0,
size: 2,
flags: 6,
}],
symbols: Vec::new(),
entry: 0,
flags: 0,
};
let sec = obj.get_section(".text");
assert!(sec.is_some());
assert_eq!(sec.unwrap().data, vec![0x90, 0xC3]);
}
#[test]
fn test_object_file_get_global_symbols() {
let obj = ObjectFile {
format: ObjectFormat::ELF64,
machine: "x86_64".to_string(),
sections: Vec::new(),
symbols: vec![
ObjectSymbol {
name: "main".to_string(),
value: 0x1000,
size: 32,
is_global: true,
is_function: true,
section_index: 1,
},
ObjectSymbol {
name: "helper".to_string(),
value: 0x1020,
size: 16,
is_global: false,
is_function: true,
section_index: 1,
},
],
entry: 0,
flags: 0,
};
let globals = obj.get_global_symbols();
assert_eq!(globals.len(), 1);
assert_eq!(globals[0].name, "main");
}
#[test]
fn test_object_file_find_function_symbols() {
let obj = ObjectFile {
format: ObjectFormat::ELF64,
machine: "x86_64".to_string(),
sections: Vec::new(),
symbols: vec![
ObjectSymbol {
name: "main".to_string(),
value: 0x1000,
size: 32,
is_global: true,
is_function: true,
section_index: 1,
},
ObjectSymbol {
name: "data_val".to_string(),
value: 0x2000,
size: 4,
is_global: true,
is_function: false,
section_index: 2,
},
],
entry: 0,
flags: 0,
};
let funcs = obj.find_function_symbols();
assert_eq!(funcs.len(), 1);
assert_eq!(funcs[0].0, "main");
}
#[test]
fn test_disassemble_section_x86_64() {
let data = vec![0xC3, 0x90, 0xE9, 0x00, 0x00, 0x00, 0x00];
let insts = ObjectFile::disassemble_section(&data, "x86_64", 0x1000);
assert!(!insts.is_empty());
assert_eq!(insts[0].mnemonic, "ret");
assert_eq!(insts[0].address, 0x1000);
}
#[test]
fn test_disassemble_section_arm64() {
let data = vec![0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF];
let insts = ObjectFile::disassemble_section(&data, "aarch64", 0x8000);
assert_eq!(insts.len(), 2);
}
#[test]
fn test_num_sections_and_symbols() {
let obj = ObjectFile {
format: ObjectFormat::ELF64,
machine: "x86_64".to_string(),
sections: vec![ObjectSection {
name: ".text".to_string(),
section_type: 1,
data: vec![],
vaddr: 0,
size: 0,
flags: 0,
}],
symbols: vec![ObjectSymbol {
name: "main".to_string(),
value: 0,
size: 0,
is_global: true,
is_function: true,
section_index: 1,
}],
entry: 0,
flags: 0,
};
assert_eq!(obj.num_sections(), 1);
assert_eq!(obj.num_symbols(), 1);
}
#[test]
fn test_parse_elf_sections_data() {
let mut data = vec![0u8; 256];
data[0..4].copy_from_slice(&ELF_MAGIC);
data[4] = 2;
data[5] = 1;
data[6] = 1;
data[16..18].copy_from_slice(&1u16.to_le_bytes()); data[18..20].copy_from_slice(&62u16.to_le_bytes()); data[40..48].copy_from_slice(&192u64.to_le_bytes()); data[58] = 64; data[60] = 1; data[62] = 1; let sections = ObjectFile::parse_elf_sections(&data);
assert!(sections.is_ok());
}
#[test]
fn test_get_symbols_in_section() {
let obj = ObjectFile {
format: ObjectFormat::ELF64,
machine: "x86_64".to_string(),
sections: Vec::new(),
symbols: vec![
ObjectSymbol {
name: "a".to_string(),
value: 0,
size: 0,
is_global: false,
is_function: true,
section_index: 1,
},
ObjectSymbol {
name: "b".to_string(),
value: 0,
size: 0,
is_global: true,
is_function: true,
section_index: 2,
},
],
entry: 0,
flags: 0,
};
let sec1_syms = obj.get_symbols_in_section(1);
assert_eq!(sec1_syms.len(), 1);
assert_eq!(sec1_syms[0].name, "a");
}
#[test]
fn test_parse_elf_dynamic_empty() {
let data = vec![0u8; 64];
let dynamic = ObjectFile::parse_elf_dynamic(&data);
assert!(dynamic.is_ok());
}
#[test]
fn test_parse_wasm() {
let data = vec![0x00, 0x61, 0x73, 0x6D, 1, 0, 0, 0];
let obj = ObjectFile::parse(&data);
assert!(obj.is_some());
let obj = obj.unwrap();
assert_eq!(obj.format, ObjectFormat::Wasm);
}
#[test]
fn test_object_file_is_valid() {
let mut data = vec![0u8; 64];
data[0..4].copy_from_slice(&ELF_MAGIC);
data[4] = 2;
data[5] = 1;
data[6] = 1;
data[18] = 62;
let obj = ObjectFile::parse(&data);
assert!(obj.is_some());
assert!(obj.unwrap().is_valid());
}
}