Skip to content

Commit 6aa4a95

Browse files
authored
feat!: Return also architecture and platform (#8)
BREAKING CHANGE: The result is now non-nullable and it is a struct with the fields `arch`, `platform`, `runtime`. Runtime is nullable as we can only detect it for ELF binaries for now. If `platform` or `arch` cannot be detected it will throw. Release-As: 1.0.0
1 parent 04e0eb8 commit 6aa4a95

File tree

19 files changed

+316
-101
lines changed

19 files changed

+316
-101
lines changed

Cargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,17 @@ description = "elf-cam is a WebAssembly(WASM) module to extract very specific in
1111
crate-type = ["cdylib", "rlib"]
1212

1313
[features]
14-
default = ["console_error_panic_hook"]
14+
default = ["console_error_panic_hook", "wee_alloc"]
1515

1616
[dependencies]
17-
wasm-bindgen = "0.2.63"
18-
goblin = { version = "0.2", default-features = false, features = ["alloc", "elf32", "elf64", "endian_fd"] }
17+
wasm-bindgen = "0.2.81"
18+
goblin = { version = "0.5.2", default-features = false, features = ["archive", "std", "elf32", "elf64", "mach32", "mach64", "pe32", "pe64"] }
1919

2020
# The `console_error_panic_hook` crate provides better debugging of panics by
2121
# logging them with `console.error`. This is great for development, but requires
2222
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
2323
# code size when deploying.
24-
console_error_panic_hook = { version = "0.1.6", optional = true }
24+
console_error_panic_hook = { version = "0.1.7", optional = true }
2525

2626
# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
2727
# compared to the default allocator's ~10K. It is slower than the default

src/elf.rs

Lines changed: 0 additions & 77 deletions
This file was deleted.

src/error.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use std::error::Error;
2+
use std::fmt;
3+
4+
#[derive(Debug)]
5+
pub struct InfoError {
6+
details: String,
7+
}
8+
9+
impl InfoError {
10+
pub fn new(msg: impl ToString) -> InfoError {
11+
InfoError {
12+
details: msg.to_string(),
13+
}
14+
}
15+
}
16+
17+
impl fmt::Display for InfoError {
18+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
19+
write!(f, "{}", self.details)
20+
}
21+
}
22+
23+
impl Error for InfoError {
24+
fn description(&self) -> &str {
25+
&self.details
26+
}
27+
}
28+
29+
impl From<goblin::error::Error> for InfoError {
30+
fn from(err: goblin::error::Error) -> Self {
31+
InfoError::new(err)
32+
}
33+
}

src/info.rs

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
use crate::error::InfoError;
2+
use goblin::{
3+
elf::sym,
4+
elf::Elf,
5+
elf64::header::{EM_386, EM_AARCH64, EM_ARM, EM_X86_64},
6+
mach::{
7+
cputype::{CPU_TYPE_ARM64, CPU_TYPE_X86_64},
8+
Mach,
9+
},
10+
pe::header::{COFF_MACHINE_ARM64, COFF_MACHINE_X86, COFF_MACHINE_X86_64},
11+
Object as Obj,
12+
};
13+
use wasm_bindgen::prelude::*;
14+
15+
const RUST_PERSONALITY: &str = "rust_eh_personality";
16+
const GO_SECTION: &str = ".note.go.buildid";
17+
18+
#[wasm_bindgen]
19+
#[derive(Copy, Clone, Debug, PartialEq)]
20+
pub enum Runtime {
21+
Go,
22+
Rust,
23+
}
24+
25+
#[wasm_bindgen]
26+
#[derive(Copy, Clone, Debug, PartialEq)]
27+
pub enum Platform {
28+
Win32,
29+
Darwin,
30+
Linux,
31+
}
32+
33+
#[wasm_bindgen]
34+
#[derive(Copy, Clone, Debug, PartialEq)]
35+
pub enum Arch {
36+
X86,
37+
Amd64,
38+
Arm,
39+
Arm64,
40+
}
41+
42+
#[wasm_bindgen]
43+
pub struct BinaryInfo {
44+
pub platform: Platform,
45+
pub arch: Arch,
46+
pub runtime: Option<Runtime>,
47+
}
48+
49+
pub fn get_runtime_from_elf(elf: Elf) -> Result<Option<Runtime>, goblin::error::Error> {
50+
for s in elf.shdr_strtab.to_vec()? {
51+
if s == GO_SECTION {
52+
return Ok(Some(Runtime::Go));
53+
}
54+
}
55+
56+
for s in elf.strtab.to_vec()? {
57+
if s == RUST_PERSONALITY {
58+
return Ok(Some(Runtime::Rust));
59+
}
60+
}
61+
62+
for s in elf.syms.iter() {
63+
if s.is_function() && s.st_bind() == sym::STB_GLOBAL {
64+
if let Some(sym_name) = elf.strtab.get_at(s.st_name) {
65+
if sym_name == RUST_PERSONALITY {
66+
return Ok(Some(Runtime::Rust));
67+
}
68+
}
69+
}
70+
}
71+
72+
Ok(None)
73+
}
74+
75+
// Implementation initially based on timfish/binary-info
76+
// https://github.com/timfish/binary-info/blob/v0.0.3/LICENSE
77+
pub fn get_info(buffer: &[u8]) -> Result<BinaryInfo, InfoError> {
78+
match Obj::parse(buffer)? {
79+
Obj::Elf(elf) => {
80+
let arch = match elf.header.e_machine {
81+
EM_AARCH64 => Arch::Arm64,
82+
EM_X86_64 => Arch::Amd64,
83+
EM_ARM => Arch::Arm,
84+
EM_386 => Arch::X86,
85+
_ => return Err(InfoError::new("Unknown architecture")),
86+
};
87+
88+
let runtime = get_runtime_from_elf(elf)?;
89+
90+
Ok(BinaryInfo {
91+
platform: Platform::Linux,
92+
arch,
93+
runtime,
94+
})
95+
}
96+
Obj::PE(pe) => {
97+
let arch = match pe.header.coff_header.machine {
98+
COFF_MACHINE_ARM64 => Arch::Arm64,
99+
COFF_MACHINE_X86 => Arch::X86,
100+
COFF_MACHINE_X86_64 => Arch::Amd64,
101+
_ => return Err(InfoError::new("Unknown architecture")),
102+
};
103+
104+
Ok(BinaryInfo {
105+
platform: Platform::Win32,
106+
arch: arch,
107+
runtime: None,
108+
})
109+
}
110+
Obj::Mach(mach) => match mach {
111+
Mach::Fat(_) => return Err(InfoError::new("Unsupported binary")),
112+
Mach::Binary(mach_o) => {
113+
let arch = match mach_o.header.cputype() {
114+
CPU_TYPE_X86_64 => Arch::Amd64,
115+
CPU_TYPE_ARM64 => Arch::Arm64,
116+
_ => return Err(InfoError::new("Unknown architecture")),
117+
};
118+
119+
Ok(BinaryInfo {
120+
platform: Platform::Darwin,
121+
arch: arch,
122+
runtime: None,
123+
})
124+
}
125+
},
126+
_ => Err(InfoError::new("Not a binary")),
127+
}
128+
}
129+
130+
#[cfg(test)]
131+
mod tests {
132+
use super::*;
133+
134+
#[test]
135+
fn test_detect_go_runtime_darwin_amd64() {
136+
let buffer =
137+
std::fs::read("tests/data/darwin/go-amd64").expect("failed to load binary file");
138+
139+
let info = get_info(&buffer).expect("failed to detect runtime");
140+
assert_eq!(Arch::Amd64, info.arch);
141+
assert_eq!(Platform::Darwin, info.platform);
142+
assert!(info.runtime.is_none())
143+
}
144+
145+
#[test]
146+
fn test_detect_go_runtime_darwin_arm64() {
147+
let buffer =
148+
std::fs::read("tests/data/darwin/go-arm64").expect("failed to load binary file");
149+
150+
let info = get_info(&buffer).expect("failed to detect runtime");
151+
assert_eq!(Arch::Arm64, info.arch);
152+
assert_eq!(Platform::Darwin, info.platform);
153+
assert!(info.runtime.is_none())
154+
}
155+
156+
#[test]
157+
fn test_detect_go_runtime_linux_x86() {
158+
let buffer = std::fs::read("tests/data/linux/go-x86").expect("failed to load binary file");
159+
160+
let info = get_info(&buffer).expect("failed to detect runtime");
161+
assert_eq!(Arch::X86, info.arch);
162+
assert_eq!(Platform::Linux, info.platform);
163+
assert_eq!(Runtime::Go, info.runtime.unwrap());
164+
}
165+
166+
#[test]
167+
fn test_detect_go_runtime_linux_amd64() {
168+
let buffer =
169+
std::fs::read("tests/data/linux/go-amd64").expect("failed to load binary file");
170+
171+
let info = get_info(&buffer).expect("failed to detect runtime");
172+
assert_eq!(Arch::Amd64, info.arch);
173+
assert_eq!(Platform::Linux, info.platform);
174+
assert_eq!(Runtime::Go, info.runtime.unwrap());
175+
}
176+
177+
#[test]
178+
fn test_detect_go_runtime_linux_arm() {
179+
let buffer = std::fs::read("tests/data/linux/go-arm").expect("failed to load binary file");
180+
181+
let info = get_info(&buffer).expect("failed to detect runtime");
182+
assert_eq!(Arch::Arm, info.arch);
183+
assert_eq!(Platform::Linux, info.platform);
184+
assert_eq!(Runtime::Go, info.runtime.unwrap());
185+
}
186+
187+
#[test]
188+
fn test_detect_go_runtime_linux_arm64() {
189+
let buffer =
190+
std::fs::read("tests/data/linux/go-arm64").expect("failed to load binary file");
191+
192+
let info = get_info(&buffer).expect("failed to detect runtime");
193+
assert_eq!(Arch::Arm64, info.arch);
194+
assert_eq!(Platform::Linux, info.platform);
195+
assert_eq!(Runtime::Go, info.runtime.unwrap());
196+
}
197+
198+
#[test]
199+
fn test_detect_rust_runtime_darwin_amd64() {
200+
let buffer =
201+
std::fs::read("tests/data/darwin/rust-amd64").expect("failed to load binary file");
202+
203+
let info = get_info(&buffer).expect("failed to detect runtime");
204+
assert_eq!(Arch::Amd64, info.arch);
205+
assert_eq!(Platform::Darwin, info.platform);
206+
assert!(info.runtime.is_none())
207+
}
208+
209+
#[test]
210+
fn test_detect_rust_runtime_linux_amd64() {
211+
let buffer =
212+
std::fs::read("tests/data/linux/rust-amd64").expect("failed to load binary file");
213+
214+
let info = get_info(&buffer).expect("failed to detect runtime");
215+
assert_eq!(Arch::Amd64, info.arch);
216+
assert_eq!(Platform::Linux, info.platform);
217+
assert_eq!(Runtime::Rust, info.runtime.unwrap());
218+
}
219+
220+
#[test]
221+
fn test_detect_go_runtime_windows_amd64() {
222+
let buffer =
223+
std::fs::read("tests/data/windows/go-amd64.exe").expect("failed to load binary file");
224+
225+
let info = get_info(&buffer).expect("failed to detect runtime");
226+
assert_eq!(Arch::Amd64, info.arch);
227+
assert_eq!(Platform::Win32, info.platform);
228+
assert!(info.runtime.is_none())
229+
}
230+
231+
#[test]
232+
fn test_detect_go_runtime_windows_x86() {
233+
let buffer =
234+
std::fs::read("tests/data/windows/go-x86.exe").expect("failed to load binary file");
235+
236+
let info = get_info(&buffer).expect("failed to detect runtime");
237+
assert_eq!(Arch::X86, info.arch);
238+
assert_eq!(Platform::Win32, info.platform);
239+
assert!(info.runtime.is_none())
240+
}
241+
242+
#[test]
243+
#[should_panic]
244+
fn test_detect_ignores_invalid_file() {
245+
let buffer = std::fs::read("tests/data/text").expect("failed to load binary file");
246+
247+
get_info(&buffer).expect("failed to detect runtime");
248+
}
249+
}

0 commit comments

Comments
 (0)