Skip to content

Commit

Permalink
feat!: Return also architecture and platform (#8)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
danez authored Jun 23, 2022
1 parent 04e0eb8 commit 6aa4a95
Show file tree
Hide file tree
Showing 19 changed files with 316 additions and 101 deletions.
8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ description = "elf-cam is a WebAssembly(WASM) module to extract very specific in
crate-type = ["cdylib", "rlib"]

[features]
default = ["console_error_panic_hook"]
default = ["console_error_panic_hook", "wee_alloc"]

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

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

# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
# compared to the default allocator's ~10K. It is slower than the default
Expand Down
77 changes: 0 additions & 77 deletions src/elf.rs

This file was deleted.

33 changes: 33 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use std::error::Error;
use std::fmt;

#[derive(Debug)]
pub struct InfoError {
details: String,
}

impl InfoError {
pub fn new(msg: impl ToString) -> InfoError {
InfoError {
details: msg.to_string(),
}
}
}

impl fmt::Display for InfoError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.details)
}
}

impl Error for InfoError {
fn description(&self) -> &str {
&self.details
}
}

impl From<goblin::error::Error> for InfoError {
fn from(err: goblin::error::Error) -> Self {
InfoError::new(err)
}
}
249 changes: 249 additions & 0 deletions src/info.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
use crate::error::InfoError;
use goblin::{
elf::sym,
elf::Elf,
elf64::header::{EM_386, EM_AARCH64, EM_ARM, EM_X86_64},
mach::{
cputype::{CPU_TYPE_ARM64, CPU_TYPE_X86_64},
Mach,
},
pe::header::{COFF_MACHINE_ARM64, COFF_MACHINE_X86, COFF_MACHINE_X86_64},
Object as Obj,
};
use wasm_bindgen::prelude::*;

const RUST_PERSONALITY: &str = "rust_eh_personality";
const GO_SECTION: &str = ".note.go.buildid";

#[wasm_bindgen]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Runtime {
Go,
Rust,
}

#[wasm_bindgen]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Platform {
Win32,
Darwin,
Linux,
}

#[wasm_bindgen]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Arch {
X86,
Amd64,
Arm,
Arm64,
}

#[wasm_bindgen]
pub struct BinaryInfo {
pub platform: Platform,
pub arch: Arch,
pub runtime: Option<Runtime>,
}

pub fn get_runtime_from_elf(elf: Elf) -> Result<Option<Runtime>, goblin::error::Error> {
for s in elf.shdr_strtab.to_vec()? {
if s == GO_SECTION {
return Ok(Some(Runtime::Go));
}
}

for s in elf.strtab.to_vec()? {
if s == RUST_PERSONALITY {
return Ok(Some(Runtime::Rust));
}
}

for s in elf.syms.iter() {
if s.is_function() && s.st_bind() == sym::STB_GLOBAL {
if let Some(sym_name) = elf.strtab.get_at(s.st_name) {
if sym_name == RUST_PERSONALITY {
return Ok(Some(Runtime::Rust));
}
}
}
}

Ok(None)
}

// Implementation initially based on timfish/binary-info
// https://github.com/timfish/binary-info/blob/v0.0.3/LICENSE
pub fn get_info(buffer: &[u8]) -> Result<BinaryInfo, InfoError> {
match Obj::parse(buffer)? {
Obj::Elf(elf) => {
let arch = match elf.header.e_machine {
EM_AARCH64 => Arch::Arm64,
EM_X86_64 => Arch::Amd64,
EM_ARM => Arch::Arm,
EM_386 => Arch::X86,
_ => return Err(InfoError::new("Unknown architecture")),
};

let runtime = get_runtime_from_elf(elf)?;

Ok(BinaryInfo {
platform: Platform::Linux,
arch,
runtime,
})
}
Obj::PE(pe) => {
let arch = match pe.header.coff_header.machine {
COFF_MACHINE_ARM64 => Arch::Arm64,
COFF_MACHINE_X86 => Arch::X86,
COFF_MACHINE_X86_64 => Arch::Amd64,
_ => return Err(InfoError::new("Unknown architecture")),
};

Ok(BinaryInfo {
platform: Platform::Win32,
arch: arch,
runtime: None,
})
}
Obj::Mach(mach) => match mach {
Mach::Fat(_) => return Err(InfoError::new("Unsupported binary")),
Mach::Binary(mach_o) => {
let arch = match mach_o.header.cputype() {
CPU_TYPE_X86_64 => Arch::Amd64,
CPU_TYPE_ARM64 => Arch::Arm64,
_ => return Err(InfoError::new("Unknown architecture")),
};

Ok(BinaryInfo {
platform: Platform::Darwin,
arch: arch,
runtime: None,
})
}
},
_ => Err(InfoError::new("Not a binary")),
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_detect_go_runtime_darwin_amd64() {
let buffer =
std::fs::read("tests/data/darwin/go-amd64").expect("failed to load binary file");

let info = get_info(&buffer).expect("failed to detect runtime");
assert_eq!(Arch::Amd64, info.arch);
assert_eq!(Platform::Darwin, info.platform);
assert!(info.runtime.is_none())
}

#[test]
fn test_detect_go_runtime_darwin_arm64() {
let buffer =
std::fs::read("tests/data/darwin/go-arm64").expect("failed to load binary file");

let info = get_info(&buffer).expect("failed to detect runtime");
assert_eq!(Arch::Arm64, info.arch);
assert_eq!(Platform::Darwin, info.platform);
assert!(info.runtime.is_none())
}

#[test]
fn test_detect_go_runtime_linux_x86() {
let buffer = std::fs::read("tests/data/linux/go-x86").expect("failed to load binary file");

let info = get_info(&buffer).expect("failed to detect runtime");
assert_eq!(Arch::X86, info.arch);
assert_eq!(Platform::Linux, info.platform);
assert_eq!(Runtime::Go, info.runtime.unwrap());
}

#[test]
fn test_detect_go_runtime_linux_amd64() {
let buffer =
std::fs::read("tests/data/linux/go-amd64").expect("failed to load binary file");

let info = get_info(&buffer).expect("failed to detect runtime");
assert_eq!(Arch::Amd64, info.arch);
assert_eq!(Platform::Linux, info.platform);
assert_eq!(Runtime::Go, info.runtime.unwrap());
}

#[test]
fn test_detect_go_runtime_linux_arm() {
let buffer = std::fs::read("tests/data/linux/go-arm").expect("failed to load binary file");

let info = get_info(&buffer).expect("failed to detect runtime");
assert_eq!(Arch::Arm, info.arch);
assert_eq!(Platform::Linux, info.platform);
assert_eq!(Runtime::Go, info.runtime.unwrap());
}

#[test]
fn test_detect_go_runtime_linux_arm64() {
let buffer =
std::fs::read("tests/data/linux/go-arm64").expect("failed to load binary file");

let info = get_info(&buffer).expect("failed to detect runtime");
assert_eq!(Arch::Arm64, info.arch);
assert_eq!(Platform::Linux, info.platform);
assert_eq!(Runtime::Go, info.runtime.unwrap());
}

#[test]
fn test_detect_rust_runtime_darwin_amd64() {
let buffer =
std::fs::read("tests/data/darwin/rust-amd64").expect("failed to load binary file");

let info = get_info(&buffer).expect("failed to detect runtime");
assert_eq!(Arch::Amd64, info.arch);
assert_eq!(Platform::Darwin, info.platform);
assert!(info.runtime.is_none())
}

#[test]
fn test_detect_rust_runtime_linux_amd64() {
let buffer =
std::fs::read("tests/data/linux/rust-amd64").expect("failed to load binary file");

let info = get_info(&buffer).expect("failed to detect runtime");
assert_eq!(Arch::Amd64, info.arch);
assert_eq!(Platform::Linux, info.platform);
assert_eq!(Runtime::Rust, info.runtime.unwrap());
}

#[test]
fn test_detect_go_runtime_windows_amd64() {
let buffer =
std::fs::read("tests/data/windows/go-amd64.exe").expect("failed to load binary file");

let info = get_info(&buffer).expect("failed to detect runtime");
assert_eq!(Arch::Amd64, info.arch);
assert_eq!(Platform::Win32, info.platform);
assert!(info.runtime.is_none())
}

#[test]
fn test_detect_go_runtime_windows_x86() {
let buffer =
std::fs::read("tests/data/windows/go-x86.exe").expect("failed to load binary file");

let info = get_info(&buffer).expect("failed to detect runtime");
assert_eq!(Arch::X86, info.arch);
assert_eq!(Platform::Win32, info.platform);
assert!(info.runtime.is_none())
}

#[test]
#[should_panic]
fn test_detect_ignores_invalid_file() {
let buffer = std::fs::read("tests/data/text").expect("failed to load binary file");

get_info(&buffer).expect("failed to detect runtime");
}
}
Loading

0 comments on commit 6aa4a95

Please sign in to comment.