use crate::elf::{ElfClass, ElfFileType, ElfMachine};
use alloc::{borrow::Cow, boxed::Box};
use core::fmt::{self, Display};
const TLS_DISABLED_MESSAGE: &str = if cfg!(feature = "tls") {
"TLS is not supported by this resolver. Use `with_default_tls_resolver()` to enable TLS support."
} else {
"TLS support is not compiled into this build. Enable the `tls` cargo feature."
};
const STATIC_TLS_DISABLED_MESSAGE: &str = if cfg!(feature = "tls") {
"Static TLS is not supported by this resolver. Use `with_default_tls_resolver()` to enable TLS support."
} else {
"TLS support is not compiled into this build. Enable the `tls` cargo feature."
};
pub enum IoError {
NullByteInPath,
OpenFailed {
path: Box<str>,
code: u32,
},
SeekFailed {
code: u32,
},
ReadFailed {
code: u32,
},
FailedToFillBuffer,
ReadOutOfBounds(Box<ReadBoundsError>),
CloseFailed,
}
#[derive(Debug)]
pub struct ReadBoundsError {
offset: usize,
len: usize,
available: usize,
}
impl ReadBoundsError {
#[inline]
pub(crate) fn new(offset: usize, len: usize, available: usize) -> Self {
Self {
offset,
len,
available,
}
}
}
impl Display for IoError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NullByteInPath => f.write_str("path contains an interior NUL byte"),
Self::OpenFailed { path, code } => {
write!(f, "open failed for {path} with error: {code}")
}
Self::SeekFailed { code } => write!(f, "seek failed with error: {code}"),
Self::ReadFailed { code } => write!(f, "read failed with error: {code}"),
Self::FailedToFillBuffer => f.write_str("failed to fill buffer"),
Self::ReadOutOfBounds(err) => write!(
f,
"read offset out of bounds: offset {}, len {}, available {}",
err.offset, err.len, err.available
),
Self::CloseFailed => f.write_str("close failed"),
}
}
}
pub enum MmapError {
InvalidPageSize {
configured: usize,
required: usize,
},
#[cfg(not(windows))]
MmapFailed {
code: u32,
},
#[cfg(not(windows))]
MmapAnonymousFailed {
code: u32,
},
#[cfg(not(windows))]
MunmapFailed {
code: u32,
},
#[cfg(windows)]
MapViewOfFile3 {
code: u32,
},
#[cfg(windows)]
VirtualAlloc {
code: u32,
},
Mprotect {
code: u32,
},
Madvise {
code: u32,
},
#[cfg(windows)]
CreateFileMappingW {
code: u32,
},
#[cfg(windows)]
VirtualFree {
code: u32,
},
}
impl Display for MmapError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidPageSize {
configured,
required,
} => write!(
f,
"invalid loader page size: configured {configured}, required a multiple of {required}"
),
#[cfg(not(windows))]
Self::MmapFailed { code } => write!(f, "mmap failed with error: {code}"),
#[cfg(not(windows))]
Self::MmapAnonymousFailed { code } => {
write!(f, "mmap anonymous failed with error: {code}")
}
#[cfg(not(windows))]
Self::MunmapFailed { code } => write!(f, "munmap failed with error: {code}"),
#[cfg(windows)]
Self::MapViewOfFile3 { code } => {
write!(f, "MapViewOfFile3 failed with error: {code}")
}
#[cfg(windows)]
Self::VirtualAlloc { code } => {
write!(f, "VirtualAlloc failed with error: {code}")
}
Self::Mprotect { code } => write!(f, "mprotect failed with error: {code}"),
Self::Madvise { code } => write!(f, "madvise failed with error: {code}"),
#[cfg(windows)]
Self::CreateFileMappingW { code } => {
write!(f, "CreateFileMappingW failed with error: {code}")
}
#[cfg(windows)]
Self::VirtualFree { code } => {
write!(f, "VirtualFree failed with error: {code}")
}
}
}
}
pub enum ParseDynamicError {
MissingRequiredTag {
tag: &'static str,
},
AddressOverflow,
MalformedRelocationTable {
detail: &'static str,
},
}
impl Display for ParseDynamicError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MissingRequiredTag { tag } => {
write!(f, "dynamic section is missing required tag {tag}")
}
Self::AddressOverflow => f.write_str("dynamic section address calculation overflowed"),
Self::MalformedRelocationTable { detail } => f.write_str(detail),
}
}
}
pub enum ParseEhdrError {
InvalidMagic,
FileClassMismatch {
expected: ElfClass,
found: ElfClass,
},
InvalidVersion,
FileArchMismatch {
expected: ElfMachine,
found: ElfMachine,
},
ExpectedDylib {
found: ElfFileType,
},
ExpectedExecutable {
found: ElfFileType,
},
RelocatableObjectsDisabled,
MissingSectionHeaders,
}
impl Display for ParseEhdrError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidMagic => f.write_str("invalid ELF magic"),
Self::FileClassMismatch { expected, found } => {
write!(f, "file class mismatch: expected {expected}, found {found}")
}
Self::InvalidVersion => f.write_str("invalid ELF version"),
Self::FileArchMismatch { expected, found } => write!(
f,
"file arch mismatch: expected {}, found {}",
expected, found,
),
Self::ExpectedDylib { found } => {
write!(f, "file type mismatch: expected ET_DYN, found {found}")
}
Self::ExpectedExecutable { found } => write!(
f,
"file type mismatch: expected ET_EXEC or ET_DYN, found {}",
found,
),
Self::RelocatableObjectsDisabled => {
f.write_str("file type ET_REL requires enabling the `object` feature")
}
Self::MissingSectionHeaders => f.write_str("object file must have section headers"),
}
}
}
pub enum ParsePhdrError {
Malformed {
detail: &'static str,
},
PageAlignmentMismatch {
page_size: usize,
},
MissingDynamicSection,
InvalidUtf8 {
field: &'static str,
},
}
impl ParsePhdrError {
#[inline]
pub(crate) const fn malformed(detail: &'static str) -> Self {
Self::Malformed { detail }
}
}
impl Display for ParsePhdrError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Malformed { detail } => f.write_str(detail),
Self::PageAlignmentMismatch { page_size } => write!(
f,
"program headers are not compatible with page size {page_size}"
),
Self::MissingDynamicSection => f.write_str("program headers do not contain PT_DYNAMIC"),
Self::InvalidUtf8 { field } => write!(f, "{field} contains invalid UTF-8"),
}
}
}
#[derive(Clone, Copy)]
pub(crate) enum RelocReason {
UnknownSymbol,
Unsupported,
#[cfg(feature = "tls")]
MissingTlsModuleId,
#[cfg(feature = "tls")]
MissingTlsTpOffset,
#[cfg(not(feature = "tls"))]
TlsDisabled,
MissingEmulator,
IntConversionOutOfRange,
}
impl Display for RelocReason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnknownSymbol => f.write_str("unknown symbol"),
Self::Unsupported => f.write_str("unsupported relocation"),
#[cfg(feature = "tls")]
Self::MissingTlsModuleId => f.write_str("TLS module id is unavailable"),
#[cfg(feature = "tls")]
Self::MissingTlsTpOffset => f.write_str("TLS thread-pointer offset is unavailable"),
#[cfg(not(feature = "tls"))]
Self::TlsDisabled => f.write_str("TLS relocation support is disabled"),
Self::MissingEmulator => f.write_str("relocation requires an emulator"),
Self::IntConversionOutOfRange => {
f.write_str("out of range integral type conversion attempted")
}
}
}
}
pub struct RelocationFailure {
file: Box<str>,
r_type: &'static str,
symbol: Option<Box<str>>,
reason: RelocReason,
}
impl RelocationFailure {
#[inline]
pub(crate) fn new(
file: &str,
r_type: &'static str,
symbol: Option<&str>,
reason: RelocReason,
) -> Self {
Self {
file: file.into(),
r_type,
symbol: symbol.map(Into::into),
reason,
}
}
}
impl Display for RelocationFailure {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "file: {}, relocation type: {}, ", self.file, self.r_type)?;
if let Some(symbol) = &self.symbol {
write!(f, "symbol name: {symbol}, ")?;
} else {
f.write_str("no symbol, ")?;
}
write!(f, "error: {}", self.reason)
}
}
pub enum RelocationError {
Context(Box<RelocationFailure>),
#[cfg(feature = "lazy-binding")]
LazyBindingSetup {
detail: &'static str,
},
MissingSymbolTable,
}
impl Display for RelocationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Context(ctx) => Display::fmt(ctx, f),
#[cfg(feature = "lazy-binding")]
Self::LazyBindingSetup { detail } => {
write!(f, "lazy binding setup failed: {detail}")
}
Self::MissingSymbolTable => f.write_str("object file missing symbol table"),
}
}
}
pub enum CustomError {
Message(Cow<'static, str>),
}
impl Display for CustomError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Message(msg) => f.write_str(msg),
}
}
}
pub struct UnresolvedDependency {
owner: Box<str>,
dependency: Box<str>,
}
impl UnresolvedDependency {
#[inline]
pub(crate) fn new(owner: &str, dependency: &str) -> Self {
Self {
owner: owner.into(),
dependency: dependency.into(),
}
}
}
impl Display for UnresolvedDependency {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"unresolved dependency [{}] needed by [{}]",
self.dependency, self.owner
)
}
}
pub enum LinkerError {
UnresolvedDependency(Box<UnresolvedDependency>),
Context {
detail: &'static str,
},
Resolver {
detail: &'static str,
},
Materialization {
detail: &'static str,
},
SectionData {
detail: &'static str,
},
MappedArena {
detail: &'static str,
},
RuntimeMemory {
detail: &'static str,
},
MetadataRewrite {
detail: &'static str,
},
}
impl LinkerError {
#[inline]
pub(crate) const fn context(detail: &'static str) -> Self {
Self::Context { detail }
}
#[inline]
pub(crate) const fn resolver(detail: &'static str) -> Self {
Self::Resolver { detail }
}
#[inline]
pub(crate) const fn materialization(detail: &'static str) -> Self {
Self::Materialization { detail }
}
#[inline]
pub(crate) const fn section_data(detail: &'static str) -> Self {
Self::SectionData { detail }
}
#[inline]
pub(crate) const fn duplicate_section_data_access() -> Self {
Self::SectionData {
detail: "disjoint section data access referenced the same section more than once",
}
}
#[inline]
pub(crate) const fn missing_section_data_access() -> Self {
Self::SectionData {
detail: "disjoint section data access was not materialized",
}
}
#[inline]
pub(crate) const fn mapped_arena(detail: &'static str) -> Self {
Self::MappedArena { detail }
}
#[inline]
pub(crate) const fn runtime_memory(detail: &'static str) -> Self {
Self::RuntimeMemory { detail }
}
#[inline]
pub(crate) const fn metadata_rewrite(detail: &'static str) -> Self {
Self::MetadataRewrite { detail }
}
}
impl Display for LinkerError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnresolvedDependency(err) => Display::fmt(err, f),
Self::Context { detail }
| Self::Resolver { detail }
| Self::Materialization { detail }
| Self::SectionData { detail }
| Self::MappedArena { detail }
| Self::RuntimeMemory { detail }
| Self::MetadataRewrite { detail } => f.write_str(detail),
}
}
}
pub enum TlsError {
ResolverUnsupported,
StaticResolverUnsupported,
}
impl Display for TlsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ResolverUnsupported => f.write_str(TLS_DISABLED_MESSAGE),
Self::StaticResolverUnsupported => f.write_str(STATIC_TLS_DISABLED_MESSAGE),
}
}
}
pub enum Error {
Io(IoError),
Mmap(MmapError),
Relocation(RelocationError),
ParseDynamic(ParseDynamicError),
ParseEhdr(ParseEhdrError),
ParsePhdr(ParsePhdrError),
Linker(LinkerError),
Custom(CustomError),
Tls(TlsError),
}
impl From<IoError> for Error {
fn from(err: IoError) -> Self {
Self::Io(err)
}
}
impl From<MmapError> for Error {
fn from(err: MmapError) -> Self {
Self::Mmap(err)
}
}
impl From<RelocationFailure> for RelocationError {
fn from(err: RelocationFailure) -> Self {
Self::Context(Box::new(err))
}
}
impl From<RelocationError> for Error {
fn from(err: RelocationError) -> Self {
Self::Relocation(err)
}
}
impl From<RelocationFailure> for Error {
fn from(err: RelocationFailure) -> Self {
RelocationError::from(err).into()
}
}
impl From<ParseDynamicError> for Error {
fn from(err: ParseDynamicError) -> Self {
Self::ParseDynamic(err)
}
}
impl From<ParseEhdrError> for Error {
fn from(err: ParseEhdrError) -> Self {
Self::ParseEhdr(err)
}
}
impl From<ParsePhdrError> for Error {
fn from(err: ParsePhdrError) -> Self {
Self::ParsePhdr(err)
}
}
impl From<LinkerError> for Error {
fn from(err: LinkerError) -> Self {
Self::Linker(err)
}
}
impl From<CustomError> for Error {
fn from(err: CustomError) -> Self {
Self::Custom(err)
}
}
impl From<TlsError> for Error {
fn from(err: TlsError) -> Self {
Self::Tls(err)
}
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Io(err) => write!(f, "I/O error: {err}"),
Self::Mmap(err) => write!(f, "Memory mapping error: {err}"),
Self::Relocation(err) => write!(f, "Relocation error: {err}"),
Self::ParseDynamic(err) => write!(f, "Dynamic section parsing error: {err}"),
Self::ParseEhdr(err) => write!(f, "ELF header parsing error: {err}"),
Self::ParsePhdr(err) => write!(f, "Program header parsing error: {err}"),
Self::Linker(err) => write!(f, "Linker error: {err}"),
Self::Custom(err) => write!(f, "Custom error: {err}"),
Self::Tls(err) => write!(f, "TLS error: {err}"),
}
}
}
impl core::error::Error for Error {}
macro_rules! debug_as_display {
($($ty:ty),* $(,)?) => {
$(
impl fmt::Debug for $ty {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(self, f)
}
}
)*
};
}
debug_as_display!(
IoError,
MmapError,
ParseDynamicError,
ParseEhdrError,
ParsePhdrError,
RelocReason,
RelocationFailure,
RelocationError,
CustomError,
UnresolvedDependency,
LinkerError,
TlsError,
Error,
);
#[cold]
#[inline(never)]
pub(crate) fn relocate_context_error(
file: &str,
r_type: &'static str,
symbol: Option<&str>,
reason: RelocReason,
) -> Error {
Error::Relocation(RelocationError::Context(Box::new(RelocationFailure::new(
file, r_type, symbol, reason,
))))
}
#[cold]
#[inline(never)]
#[allow(dead_code)]
pub fn custom_error(msg: impl Into<Cow<'static, str>>) -> Error {
Error::Custom(CustomError::Message(msg.into()))
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::{format, string::ToString};
use core::mem::size_of;
#[test]
fn error_stays_compact() {
assert!(
size_of::<Error>() <= 32,
"Error grew to {} bytes",
size_of::<Error>()
);
}
#[test]
fn debug_matches_display() {
let parse_err = ParseEhdrError::InvalidMagic;
assert_eq!(format!("{parse_err:?}"), parse_err.to_string());
let err = Error::from(parse_err);
assert_eq!(format!("{err:?}"), err.to_string());
}
}