///|
/// The executable format of a binary, from its header. The payload is the machine field:
/// `e_machine` for ELF, `cputype` for Mach-O and `Machine` for PE.
pub(all) enum Format {
Elf(Int32)
MachO(Int32)
Pe(Int32)
Unknown
} derive(Eq, Debug)
///|
/// The text that the summary shows, for example `Elf(62)`.
fn Format::describe(self : Format) -> String {
match self {
Elf(machine) => "Elf(\{machine})"
MachO(cputype) => "MachO(\{cputype})"
Pe(machine) => "Pe(\{machine})"
Unknown => "Unknown"
}
}
///|
fn u16_le(bytes : Bytes, offset : Int32) -> Int32? {
if offset < 0 || offset + 2 > bytes.length() {
return None
}
Some(bytes[offset].to_int() | (bytes[offset + 1].to_int() << 8))
}
///|
fn u32_le(bytes : Bytes, offset : Int32) -> Int32? {
guard u16_le(bytes, offset) is Some(low) else { return None }
guard u16_le(bytes, offset + 2) is Some(high) else { return None }
Some(low | (high << 16))
}
///|
fn starts_with(bytes : Bytes, magic : Bytes) -> Bool {
if bytes.length() < magic.length() {
return false
}
for i in 0.. Format {
if starts_with(bytes, b"\x7FELF") {
return match u16_le(bytes, 18) {
Some(machine) => Elf(machine)
None => Unknown
}
}
// 64-bit little-endian Mach-O (MH_MAGIC_64 as it is stored on disk).
if starts_with(bytes, b"\xCF\xFA\xED\xFE") {
return match u32_le(bytes, 4) {
Some(cputype) => MachO(cputype)
None => Unknown
}
}
if starts_with(bytes, b"MZ") {
guard u32_le(bytes, 0x3C) is Some(lfanew) else { return Unknown }
if lfanew < 0 || !starts_with(bytes[lfanew:].to_owned(), b"PE\x00\x00") {
return Unknown
}
return match u16_le(bytes, lfanew + 4) {
Some(machine) => Pe(machine)
None => Unknown
}
}
Unknown
}
///|
/// The format that a binary for `target` must have. `Host` has none, so it is not checked.
pub fn expected_format(target : Target) -> Format? {
match target {
LinuxX64 => Some(Elf(62))
LinuxArm64 => Some(Elf(183))
DarwinArm64 => Some(MachO(0x0100000C))
DarwinX64 => Some(MachO(0x01000007))
WindowsX64 => Some(Pe(0x8664))
Host => None
}
}
///|
/// `None` when the header in `bytes` is right for `target`, else the message for the summary.
pub fn check_format(target : Target, bytes : Bytes) -> String? {
guard expected_format(target) is Some(expected) else { return None }
let found = detect_format(bytes)
if found == expected {
None
} else {
Some(
"\{target.name()}: expected \{expected.describe()}, found \{found.describe()}",
)
}
}