use std::io;
use thiserror::Error;
#[cfg(feature = "emulation")]
use crate::emulation::EmulationError;
use crate::metadata::{tables::TableId, token::Token};
#[cfg(not(feature = "emulation"))]
#[derive(Debug, Clone, PartialEq)]
pub struct EmulationError(String);
#[cfg(not(feature = "emulation"))]
impl std::fmt::Display for EmulationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Emulation not available: {}", self.0)
}
}
#[macro_export]
macro_rules! malformed_error {
($msg:expr) => {
$crate::Error::Parse($crate::ParseFailure::Other {
stage: $crate::ParseStage::Generic,
message: format!("{} ({}:{})", $msg, file!(), line!()),
})
};
($fmt:expr, $($arg:tt)*) => {
$crate::Error::Parse($crate::ParseFailure::Other {
stage: $crate::ParseStage::Generic,
message: format!("{} ({}:{})", format!($fmt, $($arg)*), file!(), line!()),
})
};
}
#[macro_export]
macro_rules! out_of_bounds_error {
() => {
$crate::Error::Parse($crate::ParseFailure::OutOfBounds {
stage: $crate::ParseStage::Generic,
})
};
}
#[derive(Error, Debug)]
pub enum Error {
#[error("This file type is not supported")]
NotSupported,
#[error("{0}")]
Io(#[from] io::Error),
#[error("{0}")]
Other(String),
#[error("{0}")]
Goblin(#[from] goblin::error::Error),
#[error("Failed to find type in TypeSystem - {0}")]
TypeNotFound(Token),
#[error(transparent)]
LookupMethod(#[from] MethodLookupError),
#[error(transparent)]
Parse(#[from] ParseFailure),
#[error("{0}")]
TypeError(String),
#[error("The parent of the current type is missing")]
TypeMissingParent,
#[error("This type can not be converted to a primitive")]
TypeNotPrimitive,
#[error("The requested type conversion is not possible")]
TypeConversionInvalid,
#[error("Reach the maximum recursion level allowed - {0}")]
RecursionLimit(usize),
#[error("Marshalling error: {0}")]
MarshallingError(String),
#[error("Reached the maximum nesting depth allowed - {0}")]
DepthLimitExceeded(usize),
#[error("{0}")]
GraphError(String),
#[error("SSA error: {0}")]
SsaError(String),
#[error("Codegen error: {0}")]
CodegenFailed(String),
#[error("Cannot modify replaced table")]
CannotModifyReplacedTable,
#[error("Invalid modification: {0}")]
ModificationInvalid(String),
#[error("Invalid RID {rid} for table {table:?}")]
InvalidRid {
table: TableId,
rid: u32,
},
#[error("Cross-reference error: {0}")]
CrossReferenceError(String),
#[error("Conflict resolution failed: {0}")]
ConflictResolution(String),
#[error("Validation Stage 1 failed: {message}")]
ValidationStage1Failed {
#[source]
source: Box<Error>,
message: String,
},
#[error("Validation Stage 2 failed with {error_count} errors: {summary}")]
ValidationStage2Failed {
errors: Vec<Error>,
error_count: usize,
summary: String,
},
#[error("Raw validation failed in {validator}: {message}")]
ValidationRawFailed {
validator: String,
message: String,
},
#[error("Owned validation failed in {validator}: {message}")]
ValidationOwnedFailed {
validator: String,
message: String,
},
#[error("Validation engine initialization failed: {message}")]
ValidationEngineInitFailed {
message: String,
},
#[error("Invalid token {token}: {message}")]
InvalidToken {
token: Token,
message: String,
},
#[error("Layout failed: {0}")]
LayoutFailed(String),
#[error("Memory mapping failed: {0}")]
MmapFailed(String),
#[error("Finalization failed: {0}")]
FinalizationFailed(String),
#[error("Invalid instruction mnemonic: {0}")]
InvalidMnemonic(String),
#[error("Wrong operand type for instruction - expected {expected}")]
WrongOperandType {
expected: String,
},
#[error("Unexpected operand provided for instruction that expects none")]
UnexpectedOperand,
#[error("Invalid branch: {0}")]
InvalidBranch(String),
#[error("Undefined label referenced: {0}")]
UndefinedLabel(String),
#[error("Duplicate label definition: {0}")]
DuplicateLabel(String),
#[error("Lock error: {0}")]
LockError(String),
#[error("Configuration error: {0}")]
Configuration(String),
#[error("{0}")]
Emulation(Box<EmulationError>),
#[error("Deobfuscation error: {0}")]
Deobfuscation(String),
#[error("x86 error: {0}")]
X86Error(String),
#[error("Tracing error: {0}")]
TracingError(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Error)]
#[non_exhaustive]
pub enum MethodLookupError {
#[error("MethodDef token {0} not found")]
NotFound(Token),
#[error("MethodSpec token {0} not found")]
SpecNotFound(Token),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ParseStage {
DosHeader,
PeSignature,
CoffHeader,
OptionalHeader,
SectionTable,
Cor20Header,
MetadataRoot,
StreamHeader,
TildeStream,
TableRow,
Heap,
DataDirectory,
Resources,
VTableFixup,
StrongName,
Signature,
MethodBody,
CustomAttribute,
PermissionSet,
InstructionDecoder,
InstructionEncoder,
AssemblyWriter,
ImportsExports,
TypeSystem,
Validation,
EmulationLoader,
Generic,
}
impl std::fmt::Display for ParseStage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
ParseStage::DosHeader => "DOS header",
ParseStage::PeSignature => "PE signature",
ParseStage::CoffHeader => "COFF header",
ParseStage::OptionalHeader => "PE optional header",
ParseStage::SectionTable => "PE section table",
ParseStage::Cor20Header => "COR20 header",
ParseStage::MetadataRoot => "metadata root",
ParseStage::StreamHeader => "stream header",
ParseStage::TildeStream => "tilde (#~) stream",
ParseStage::TableRow => "metadata table row",
ParseStage::Heap => "heap",
ParseStage::DataDirectory => "data directory",
ParseStage::Resources => "resources",
ParseStage::VTableFixup => "VTable fixup",
ParseStage::StrongName => "strong-name signature",
ParseStage::Signature => "signature blob",
ParseStage::MethodBody => "method body",
ParseStage::CustomAttribute => "custom attribute",
ParseStage::PermissionSet => "permission set",
ParseStage::InstructionDecoder => "CIL decoder",
ParseStage::InstructionEncoder => "CIL encoder",
ParseStage::AssemblyWriter => "assembly writer",
ParseStage::ImportsExports => "imports/exports",
ParseStage::TypeSystem => "type system",
ParseStage::Validation => "validation",
ParseStage::EmulationLoader => "emulation loader",
ParseStage::Generic => "generic byte parser",
};
f.write_str(s)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum HeapKind {
Strings,
UserStrings,
Blob,
Guid,
}
impl std::fmt::Display for HeapKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
HeapKind::Strings => "#Strings",
HeapKind::UserStrings => "#US",
HeapKind::Blob => "#Blob",
HeapKind::Guid => "#GUID",
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum StreamKind {
Tilde,
TildeUncompressed,
Strings,
UserStrings,
Blob,
Guid,
Pdb,
}
impl std::fmt::Display for StreamKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
StreamKind::Tilde => "#~",
StreamKind::TildeUncompressed => "#-",
StreamKind::Strings => "#Strings",
StreamKind::UserStrings => "#US",
StreamKind::Blob => "#Blob",
StreamKind::Guid => "#GUID",
StreamKind::Pdb => "#Pdb",
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Error)]
#[non_exhaustive]
pub enum ParseFailure {
#[error("truncated {stage} (expected {expected} bytes, found {found})")]
Truncated {
stage: ParseStage,
expected: usize,
found: usize,
},
#[error("bad {stage} magic: expected 0x{expected:08X}, found 0x{found:08X}")]
BadMagic {
stage: ParseStage,
expected: u32,
found: u32,
},
#[error("invalid {stage} field `{field}`: {reason}")]
InvalidField {
stage: ParseStage,
field: &'static str,
reason: String,
},
#[error("unsupported {stage} schema version `{version}`")]
UnsupportedSchema {
stage: ParseStage,
version: String,
},
#[error("read past end of buffer in {stage}")]
OutOfBounds {
stage: ParseStage,
},
#[error("truncated stream `{stream}` at offset {offset}")]
TruncatedStream {
stream: StreamKind,
offset: u32,
},
#[error("heap `{heap}` index {index} out of bounds")]
HeapOutOfBounds {
heap: HeapKind,
index: u32,
},
#[error("heap `{heap}` corrupt: {reason}")]
HeapCorrupt {
heap: HeapKind,
reason: String,
},
#[error("{stage}: {message}")]
Other {
stage: ParseStage,
message: String,
},
}
impl Clone for Error {
fn clone(&self) -> Self {
match self {
Error::Io(io_err) => Error::Other(io_err.to_string()),
Error::Goblin(goblin_err) => Error::Other(goblin_err.to_string()),
Error::ValidationStage1Failed { source, message } => Error::ValidationStage1Failed {
source: source.clone(),
message: message.clone(),
},
Error::ValidationRawFailed { validator, message } => Error::ValidationRawFailed {
validator: validator.clone(),
message: message.clone(),
},
Error::ValidationOwnedFailed { validator, message } => Error::ValidationOwnedFailed {
validator: validator.clone(),
message: message.clone(),
},
Error::Emulation(e) => Error::Emulation(e.clone()),
Error::Deobfuscation(s) => Error::Deobfuscation(s.clone()),
Error::X86Error(s) => Error::X86Error(s.clone()),
Error::TracingError(s) => Error::TracingError(s.clone()),
Error::LookupMethod(e) => Error::LookupMethod(e.clone()),
Error::Parse(e) => Error::Parse(e.clone()),
other => Error::Other(other.to_string()),
}
}
}
impl From<cowfile::Error> for Error {
fn from(err: cowfile::Error) -> Self {
match err {
cowfile::Error::Io(io_err) => Error::Io(io_err),
cowfile::Error::OutOfBounds { .. } => Error::Parse(ParseFailure::OutOfBounds {
stage: ParseStage::Generic,
}),
cowfile::Error::LockPoisoned(msg) => Error::LockError(msg),
}
}
}
#[cfg(feature = "emulation")]
impl From<EmulationError> for Error {
fn from(err: EmulationError) -> Self {
Error::Emulation(Box::new(err))
}
}
impl From<analyssa::GraphError> for Error {
fn from(err: analyssa::GraphError) -> Self {
Error::GraphError(err.0)
}
}
impl From<Error> for analyssa::Error {
fn from(err: Error) -> Self {
analyssa::Error::new(err.to_string())
}
}