1use std::path::Path;
21
22use crate::error::LoadError;
23
24#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct BinaryInfo {
27 pub format: BinaryFormat,
28 pub arch: Arch,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum BinaryFormat {
33 Elf,
34 MachO,
35 Pe,
36 Unknown,
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum Arch {
41 X86_64,
42 Aarch64,
43 Unknown,
44}
45
46impl std::fmt::Display for BinaryFormat {
47 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48 match self {
49 BinaryFormat::Elf => write!(f, "ELF"),
50 BinaryFormat::MachO => write!(f, "Mach-O"),
51 BinaryFormat::Pe => write!(f, "PE"),
52 BinaryFormat::Unknown => write!(f, "unknown"),
53 }
54 }
55}
56
57impl std::fmt::Display for Arch {
58 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59 match self {
60 Arch::X86_64 => write!(f, "x86_64"),
61 Arch::Aarch64 => write!(f, "aarch64"),
62 Arch::Unknown => write!(f, "unknown"),
63 }
64 }
65}
66
67pub fn detect_architecture(path: &Path) -> Result<BinaryInfo, LoadError> {
69 use std::io::Read;
70
71 let mut file = std::fs::File::open(path).map_err(|e| {
72 if e.kind() == std::io::ErrorKind::NotFound {
73 LoadError::LibraryNotFound {
74 path: path.display().to_string(),
75 }
76 } else {
77 LoadError::Io(e)
78 }
79 })?;
80
81 let mut bytes = [0u8; 20];
82 let n = file.read(&mut bytes).map_err(LoadError::Io)?;
83
84 if n < 16 {
85 return Ok(BinaryInfo {
86 format: BinaryFormat::Unknown,
87 arch: Arch::Unknown,
88 });
89 }
90 let bytes = &bytes[..n];
91
92 if bytes[0..4] == [0x7f, b'E', b'L', b'F'] {
94 let arch = if bytes.len() > 19 {
95 let e_machine = u16::from_le_bytes([bytes[18], bytes[19]]);
96 match e_machine {
97 0x3E => Arch::X86_64,
98 0xB7 => Arch::Aarch64,
99 _ => Arch::Unknown,
100 }
101 } else {
102 Arch::Unknown
103 };
104 return Ok(BinaryInfo {
105 format: BinaryFormat::Elf,
106 arch,
107 });
108 }
109
110 let magic32 = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
112 if matches!(magic32, 0xFEEDFACE | 0xFEEDFACF | 0xCEFAEDFE | 0xCFFAEDFE) {
113 let arch = if bytes.len() > 8 {
114 let is_le = matches!(magic32, 0xCEFAEDFE | 0xCFFAEDFE);
116 let cputype = if is_le {
117 u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]])
118 } else {
119 u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]])
120 };
121 match cputype {
122 0x01000007 => Arch::X86_64, 0x0100000C => Arch::Aarch64, _ => Arch::Unknown,
125 }
126 } else {
127 Arch::Unknown
128 };
129 return Ok(BinaryInfo {
130 format: BinaryFormat::MachO,
131 arch,
132 });
133 }
134
135 if bytes[0..2] == [b'M', b'Z'] {
137 return Ok(BinaryInfo {
138 format: BinaryFormat::Pe,
139 arch: Arch::Unknown, });
141 }
142
143 Ok(BinaryInfo {
144 format: BinaryFormat::Unknown,
145 arch: Arch::Unknown,
146 })
147}
148
149pub fn check_architecture(path: &Path) -> Result<(), LoadError> {
151 let info = detect_architecture(path)?;
152
153 let expected_format = if cfg!(target_os = "macos") {
154 BinaryFormat::MachO
155 } else if cfg!(target_os = "windows") {
156 BinaryFormat::Pe
157 } else {
158 BinaryFormat::Elf
159 };
160
161 let expected_arch = if cfg!(target_arch = "x86_64") {
162 Arch::X86_64
163 } else if cfg!(target_arch = "aarch64") {
164 Arch::Aarch64
165 } else {
166 Arch::Unknown
167 };
168
169 if info.format != BinaryFormat::Unknown && info.format != expected_format {
171 return Err(LoadError::ArchitectureMismatch {
172 expected: format!("{} {}", expected_format, expected_arch),
173 got: format!("{} {}", info.format, info.arch),
174 });
175 }
176
177 if info.arch != Arch::Unknown && expected_arch != Arch::Unknown && info.arch != expected_arch {
178 return Err(LoadError::ArchitectureMismatch {
179 expected: format!("{} {}", expected_format, expected_arch),
180 got: format!("{} {}", info.format, info.arch),
181 });
182 }
183
184 Ok(())
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 #[test]
192 fn detects_elf() {
193 let mut bytes = vec![0u8; 20];
195 bytes[0..4].copy_from_slice(&[0x7f, b'E', b'L', b'F']);
196 bytes[18..20].copy_from_slice(&0x3Eu16.to_le_bytes()); let tmp = tempfile::NamedTempFile::new().unwrap();
199 std::fs::write(tmp.path(), &bytes).unwrap();
200
201 let info = detect_architecture(tmp.path()).unwrap();
202 assert_eq!(info.format, BinaryFormat::Elf);
203 assert_eq!(info.arch, Arch::X86_64);
204 }
205
206 #[test]
207 fn detects_macho_le() {
208 let mut bytes = vec![0u8; 16];
210 bytes[0..4].copy_from_slice(&0xCFFAEDFEu32.to_be_bytes());
211 bytes[4..8].copy_from_slice(&0x0100000Cu32.to_le_bytes()); let tmp = tempfile::NamedTempFile::new().unwrap();
214 std::fs::write(tmp.path(), &bytes).unwrap();
215
216 let info = detect_architecture(tmp.path()).unwrap();
217 assert_eq!(info.format, BinaryFormat::MachO);
218 assert_eq!(info.arch, Arch::Aarch64);
219 }
220
221 #[test]
222 fn detects_pe() {
223 let mut bytes = vec![0u8; 16];
224 bytes[0..2].copy_from_slice(&[b'M', b'Z']);
225
226 let tmp = tempfile::NamedTempFile::new().unwrap();
227 std::fs::write(tmp.path(), &bytes).unwrap();
228
229 let info = detect_architecture(tmp.path()).unwrap();
230 assert_eq!(info.format, BinaryFormat::Pe);
231 }
232
233 #[test]
234 fn unknown_format() {
235 let bytes = vec![0u8; 16];
236
237 let tmp = tempfile::NamedTempFile::new().unwrap();
238 std::fs::write(tmp.path(), &bytes).unwrap();
239
240 let info = detect_architecture(tmp.path()).unwrap();
241 assert_eq!(info.format, BinaryFormat::Unknown);
242 }
243}