use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec::Vec;
use fallible_iterator::FallibleIterator;
use gimli::{EndianSlice, LittleEndian};

use crate::arch::Arch;
use crate::cache::{AllocationPolicy, Cache};
use crate::dwarf::{DwarfCfiIndex, DwarfUnwinder, DwarfUnwinding, UnwindSectionType};
use crate::error::{Error, UnwinderError};
use crate::instruction_analysis::InstructionAnalysis;

#[cfg(feature = "macho")]
use crate::macho::{
    CompactUnwindInfoUnwinder, CompactUnwindInfoUnwinding, CuiUnwindResult, TextBytes,
};
#[cfg(feature = "pe")]
use crate::pe::{DataAtRvaRange, PeUnwinding};
use crate::rule_cache::CacheResult;
use crate::unwind_result::UnwindResult;
use crate::unwind_rule::UnwindRule;
use crate::FrameAddress;

use core::marker::PhantomData;
use core::ops::{Deref, Range};
use core::sync::atomic::{AtomicU16, Ordering};

/// Unwinder is the trait that each CPU architecture's concrete unwinder type implements.
/// This trait's methods are what let you do the actual unwinding.
pub trait Unwinder: Clone {
    /// The unwind registers type for the targeted CPU architecture.
    type UnwindRegs;

    /// The unwind cache for the targeted CPU architecture.
    /// This is an associated type because the cache stores unwind rules, whose concrete
    /// type depends on the CPU arch, and because the cache can support different allocation
    /// policies.
    type Cache;

    /// The module type. This is an associated type because the concrete type varies
    /// depending on the type you use to give the module access to the unwind section data.
    type Module;

    /// Add a module that's loaded in the profiled process. This is how you provide unwind
    /// information and address ranges.
    ///
    /// This should be called whenever a new module is loaded into the process.
    fn add_module(&mut self, module: Self::Module);

    /// Remove a module that was added before using `add_module`, keyed by the start
    /// address of that module's address range. If no match is found, the call is ignored.
    /// This should be called whenever a module is unloaded from the process.
    fn remove_module(&mut self, module_avma_range_start: u64);

    /// Returns the highest code address that is known in this process based on the module
    /// address ranges. Returns 0 if no modules have been added.
    ///
    /// This method can be used together with
    /// [`PtrAuthMask::from_max_known_address`](crate::aarch64::PtrAuthMask::from_max_known_address)
    /// to make an educated guess at a pointer authentication mask for Aarch64 return addresses.
    fn max_known_code_address(&self) -> u64;

    /// Unwind a single frame, to recover return address and caller register values.
    /// This is the main entry point for unwinding.
    fn unwind_frame<F>(
        &self,
        address: FrameAddress,
        regs: &mut Self::UnwindRegs,
        cache: &mut Self::Cache,
        read_stack: &mut F,
    ) -> Result<Option<u64>, Error>
    where
        F: FnMut(u64) -> Result<u64, ()>;

    /// Return an iterator that unwinds frame by frame until the end of the stack is found.
    fn iter_frames<'u, 'c, 'r, F>(
        &'u self,
        pc: u64,
        regs: Self::UnwindRegs,
        cache: &'c mut Self::Cache,
        read_stack: &'r mut F,
    ) -> UnwindIterator<'u, 'c, 'r, Self, F>
    where
        F: FnMut(u64) -> Result<u64, ()>,
    {
        UnwindIterator::new(self, pc, regs, cache, read_stack)
    }
}

/// An iterator for unwinding the entire stack, starting from the initial register values.
///
/// The first yielded frame is the instruction pointer. Subsequent addresses are return
/// addresses.
///
/// This iterator attempts to detect if stack unwinding completed successfully, or if the
/// stack was truncated prematurely. If it thinks that it successfully found the root
/// function, it will complete with `Ok(None)`, otherwise it will complete with `Err(...)`.
/// However, the detection does not work in all cases, so you should expect `Err(...)` to
/// be returned even during normal operation. As a result, it is not recommended to use
/// this iterator as a `FallibleIterator`, because you might lose the entire stack if the
/// last iteration returns `Err(...)`.
///
/// Lifetimes:
///
///  - `'u`: The lifetime of the [`Unwinder`].
///  - `'c`: The lifetime of the unwinder cache.
///  - `'r`: The lifetime of the exclusive access to the `read_stack` callback.
pub struct UnwindIterator<'u, 'c, 'r, U: Unwinder, F: FnMut(u64) -> Result<u64, ()>> {
    unwinder: &'u U,
    state: UnwindIteratorState,
    regs: U::UnwindRegs,
    cache: &'c mut U::Cache,
    read_stack: &'r mut F,
}

enum UnwindIteratorState {
    Initial(u64),
    Unwinding(FrameAddress),
    Done,
}

impl<'u, 'c, 'r, U: Unwinder, F: FnMut(u64) -> Result<u64, ()>> UnwindIterator<'u, 'c, 'r, U, F> {
    /// Create a new iterator. You'd usually use [`Unwinder::iter_frames`] instead.
    pub fn new(
        unwinder: &'u U,
        pc: u64,
        regs: U::UnwindRegs,
        cache: &'c mut U::Cache,
        read_stack: &'r mut F,
    ) -> Self {
        Self {
            unwinder,
            state: UnwindIteratorState::Initial(pc),
            regs,
            cache,
            read_stack,
        }
    }
}

impl<U: Unwinder, F: FnMut(u64) -> Result<u64, ()>> UnwindIterator<'_, '_, '_, U, F> {
    /// Yield the next frame in the stack.
    ///
    /// The first frame is `Ok(Some(FrameAddress::InstructionPointer(...)))`.
    /// Subsequent frames are `Ok(Some(FrameAddress::ReturnAddress(...)))`.
    ///
    /// If a root function has been reached, this iterator completes with `Ok(None)`.
    /// Otherwise it completes with `Err(...)`, usually indicating that a certain stack
    /// address could not be read.
    #[allow(clippy::should_implement_trait)]
    pub fn next(&mut self) -> Result<Option<FrameAddress>, Error> {
        let next = match self.state {
            UnwindIteratorState::Initial(pc) => {
                self.state = UnwindIteratorState::Unwinding(FrameAddress::InstructionPointer(pc));
                return Ok(Some(FrameAddress::InstructionPointer(pc)));
            }
            UnwindIteratorState::Unwinding(address) => {
                self.unwinder
                    .unwind_frame(address, &mut self.regs, self.cache, self.read_stack)?
            }
            UnwindIteratorState::Done => return Ok(None),
        };
        match next {
            Some(return_address) => {
                let return_address = FrameAddress::from_return_address(return_address)
                    .ok_or(Error::ReturnAddressIsNull)?;
                self.state = UnwindIteratorState::Unwinding(return_address);
                Ok(Some(return_address))
            }
            None => {
                self.state = UnwindIteratorState::Done;
                Ok(None)
            }
        }
    }
}

impl<U: Unwinder, F: FnMut(u64) -> Result<u64, ()>> FallibleIterator
    for UnwindIterator<'_, '_, '_, U, F>
{
    type Item = FrameAddress;
    type Error = Error;

    fn next(&mut self) -> Result<Option<FrameAddress>, Error> {
        self.next()
    }
}

/// This global generation counter makes it so that the cache can be shared
/// between multiple unwinders.
/// This is a u16, so if you make it wrap around by adding / removing modules
/// more than 65535 times, then you risk collisions in the cache; meaning:
/// unwinding might not work properly if an old unwind rule was found in the
/// cache for the same address and the same (pre-wraparound) modules_generation.
static GLOBAL_MODULES_GENERATION: AtomicU16 = AtomicU16::new(0);

fn next_global_modules_generation() -> u16 {
    GLOBAL_MODULES_GENERATION.fetch_add(1, Ordering::Relaxed)
}

cfg_if::cfg_if! {
    if #[cfg(all(feature = "macho", feature = "pe"))] {
        pub trait Unwinding:
            Arch + DwarfUnwinding + InstructionAnalysis + CompactUnwindInfoUnwinding + PeUnwinding {}
        impl<T: Arch + DwarfUnwinding + InstructionAnalysis + CompactUnwindInfoUnwinding + PeUnwinding>
            Unwinding for T {}
    } else if #[cfg(feature = "macho")] {
        pub trait Unwinding:
            Arch + DwarfUnwinding + InstructionAnalysis + CompactUnwindInfoUnwinding {}
        impl<T: Arch + DwarfUnwinding + InstructionAnalysis + CompactUnwindInfoUnwinding> Unwinding for T {}
    } else if #[cfg(feature = "pe")] {
        pub trait Unwinding:
            Arch + DwarfUnwinding + InstructionAnalysis  + PeUnwinding {}
        impl<T: Arch + DwarfUnwinding + InstructionAnalysis + PeUnwinding> Unwinding for T {}
    } else {
        pub trait Unwinding: Arch + DwarfUnwinding + InstructionAnalysis {}
        impl<T: Arch + DwarfUnwinding + InstructionAnalysis> Unwinding for T {}
    }
}

pub struct UnwinderInternal<D, A, P> {
    /// sorted by avma_range.start
    modules: Vec<Module<D>>,
    /// Incremented every time modules is changed.
    modules_generation: u16,
    _arch: PhantomData<A>,
    _allocation_policy: PhantomData<P>,
}

impl<D, A, P> Default for UnwinderInternal<D, A, P> {
    fn default() -> Self {
        Self::new()
    }
}

impl<D, A, P> Clone for UnwinderInternal<D, A, P> {
    fn clone(&self) -> Self {
        Self {
            modules: self.modules.clone(),
            modules_generation: self.modules_generation,
            _arch: PhantomData,
            _allocation_policy: PhantomData,
        }
    }
}

impl<D, A, P> UnwinderInternal<D, A, P> {
    pub fn new() -> Self {
        Self {
            modules: Vec::new(),
            modules_generation: next_global_modules_generation(),
            _arch: PhantomData,
            _allocation_policy: PhantomData,
        }
    }
}

impl<D: Deref<Target = [u8]>, A: Unwinding, P: AllocationPolicy> UnwinderInternal<D, A, P> {
    pub fn add_module(&mut self, module: Module<D>) {
        let insertion_index = match self
            .modules
            .binary_search_by_key(&module.avma_range.start, |module| module.avma_range.start)
        {
            Ok(i) => i, // unexpected
            Err(i) => i,
        };
        self.modules.insert(insertion_index, module);
        self.modules_generation = next_global_modules_generation();
    }

    pub fn remove_module(&mut self, module_address_range_start: u64) {
        if let Ok(index) = self
            .modules
            .binary_search_by_key(&module_address_range_start, |module| {
                module.avma_range.start
            })
        {
            self.modules.remove(index);
            self.modules_generation = next_global_modules_generation();
        };
    }

    pub fn max_known_code_address(&self) -> u64 {
        self.modules.last().map_or(0, |m| m.avma_range.end)
    }

    fn find_module_for_address(&self, address: u64) -> Option<(usize, u32)> {
        let (module_index, module) = match self
            .modules
            .binary_search_by_key(&address, |m| m.avma_range.start)
        {
            Ok(i) => (i, &self.modules[i]),
            Err(insertion_index) => {
                if insertion_index == 0 {
                    // address is before first known module
                    return None;
                }
                let i = insertion_index - 1;
                let module = &self.modules[i];
                if module.avma_range.end <= address {
                    // address is after this module
                    return None;
                }
                (i, module)
            }
        };
        if address < module.base_avma {
            // Invalid base address
            return None;
        }
        let relative_address = u32::try_from(address - module.base_avma).ok()?;
        Some((module_index, relative_address))
    }

    fn with_cache<F, G>(
        &self,
        address: FrameAddress,
        regs: &mut A::UnwindRegs,
        cache: &mut Cache<A::UnwindRule, P>,
        read_stack: &mut F,
        callback: G,
    ) -> Result<Option<u64>, Error>
    where
        F: FnMut(u64) -> Result<u64, ()>,
        G: FnOnce(
            &Module<D>,
            FrameAddress,
            u32,
            &mut A::UnwindRegs,
            &mut Cache<A::UnwindRule, P>,
            &mut F,
        ) -> Result<UnwindResult<A::UnwindRule>, UnwinderError>,
    {
        let lookup_address = address.address_for_lookup();
        let is_first_frame = !address.is_return_address();
        let cache_handle = match cache
            .rule_cache
            .lookup(lookup_address, self.modules_generation)
        {
            CacheResult::Hit(unwind_rule) => {
                return unwind_rule.exec(is_first_frame, regs, read_stack);
            }
            CacheResult::Miss(handle) => handle,
        };

        let unwind_rule = match self.find_module_for_address(lookup_address) {
            None => A::UnwindRule::fallback_rule(),
            Some((module_index, relative_lookup_address)) => {
                let module = &self.modules[module_index];
                match callback(
                    module,
                    address,
                    relative_lookup_address,
                    regs,
                    cache,
                    read_stack,
                ) {
                    Ok(UnwindResult::ExecRule(rule)) => rule,
                    Ok(UnwindResult::Uncacheable(return_address)) => {
                        return Ok(Some(return_address))
                    }
                    Err(_err) => {
                        // eprintln!("Unwinder error: {}", err);
                        A::UnwindRule::fallback_rule()
                    }
                }
            }
        };
        cache.rule_cache.insert(cache_handle, unwind_rule);
        unwind_rule.exec(is_first_frame, regs, read_stack)
    }

    pub fn unwind_frame<F>(
        &self,
        address: FrameAddress,
        regs: &mut A::UnwindRegs,
        cache: &mut Cache<A::UnwindRule, P>,
        read_stack: &mut F,
    ) -> Result<Option<u64>, Error>
    where
        F: FnMut(u64) -> Result<u64, ()>,
    {
        self.with_cache(address, regs, cache, read_stack, Self::unwind_frame_impl)
    }

    fn unwind_frame_impl<F>(
        module: &Module<D>,
        address: FrameAddress,
        rel_lookup_address: u32,
        regs: &mut A::UnwindRegs,
        cache: &mut Cache<A::UnwindRule, P>,
        read_stack: &mut F,
    ) -> Result<UnwindResult<A::UnwindRule>, UnwinderError>
    where
        F: FnMut(u64) -> Result<u64, ()>,
    {
        let is_first_frame = !address.is_return_address();
        let unwind_result = match &*module.unwind_data {
            #[cfg(feature = "macho")]
            ModuleUnwindDataInternal::CompactUnwindInfoAndEhFrame {
                unwind_info,
                eh_frame,
                stubs_svma: stubs,
                stub_helper_svma: stub_helper,
                base_addresses,
                text_data,
            } => {
                // eprintln!("unwinding with cui and eh_frame in module {}", module.name);
                let text_bytes = text_data.as_ref().and_then(|data| {
                    let offset_from_base =
                        u32::try_from(data.svma_range.start.checked_sub(module.base_svma)?).ok()?;
                    Some(TextBytes::new(offset_from_base, &data.bytes[..]))
                });
                let stubs_range = if let Some(stubs_range) = stubs {
                    (
                        (stubs_range.start - module.base_svma) as u32,
                        (stubs_range.end - module.base_svma) as u32,
                    )
                } else {
                    (0, 0)
                };
                let stub_helper_range = if let Some(stub_helper_range) = stub_helper {
                    (
                        (stub_helper_range.start - module.base_svma) as u32,
                        (stub_helper_range.end - module.base_svma) as u32,
                    )
                } else {
                    (0, 0)
                };
                let mut unwinder = CompactUnwindInfoUnwinder::<A>::new(
                    &unwind_info[..],
                    text_bytes,
                    stubs_range,
                    stub_helper_range,
                );

                let unwind_result = unwinder.unwind_frame(rel_lookup_address, is_first_frame)?;
                match unwind_result {
                    CuiUnwindResult::ExecRule(rule) => UnwindResult::ExecRule(rule),
                    CuiUnwindResult::NeedDwarf(fde_offset) => {
                        let eh_frame_data =
                            eh_frame.as_deref().ok_or(UnwinderError::NoDwarfData)?;
                        let mut dwarf_unwinder = DwarfUnwinder::<_, A, _>::new(
                            EndianSlice::new(eh_frame_data, LittleEndian),
                            UnwindSectionType::EhFrame,
                            None,
                            &mut cache.gimli_unwind_context,
                            base_addresses.clone(),
                            module.base_svma,
                        );
                        dwarf_unwinder.unwind_frame_with_fde::<_, P::GimliEvaluationStorage<_>>(
                            regs,
                            is_first_frame,
                            rel_lookup_address,
                            fde_offset,
                            read_stack,
                        )?
                    }
                }
            }
            ModuleUnwindDataInternal::EhFrameHdrAndEhFrame {
                eh_frame_hdr,
                eh_frame,
                base_addresses,
            } => {
                let eh_frame_hdr_data = &eh_frame_hdr[..];
                let mut dwarf_unwinder = DwarfUnwinder::<_, A, _>::new(
                    EndianSlice::new(eh_frame, LittleEndian),
                    UnwindSectionType::EhFrame,
                    Some(eh_frame_hdr_data),
                    &mut cache.gimli_unwind_context,
                    base_addresses.clone(),
                    module.base_svma,
                );
                let fde_offset = dwarf_unwinder
                    .get_fde_offset_for_relative_address(rel_lookup_address)
                    .ok_or(UnwinderError::EhFrameHdrCouldNotFindAddress)?;
                dwarf_unwinder.unwind_frame_with_fde::<_, P::GimliEvaluationStorage<_>>(
                    regs,
                    is_first_frame,
                    rel_lookup_address,
                    fde_offset,
                    read_stack,
                )?
            }
            ModuleUnwindDataInternal::DwarfCfiIndexAndEhFrame {
                index,
                eh_frame,
                base_addresses,
            } => {
                let mut dwarf_unwinder = DwarfUnwinder::<_, A, _>::new(
                    EndianSlice::new(eh_frame, LittleEndian),
                    UnwindSectionType::EhFrame,
                    None,
                    &mut cache.gimli_unwind_context,
                    base_addresses.clone(),
                    module.base_svma,
                );
                let fde_offset = index
                    .fde_offset_for_relative_address(rel_lookup_address)
                    .ok_or(UnwinderError::DwarfCfiIndexCouldNotFindAddress)?;
                dwarf_unwinder.unwind_frame_with_fde::<_, P::GimliEvaluationStorage<_>>(
                    regs,
                    is_first_frame,
                    rel_lookup_address,
                    fde_offset,
                    read_stack,
                )?
            }
            ModuleUnwindDataInternal::DwarfCfiIndexAndDebugFrame {
                index,
                debug_frame,
                base_addresses,
            } => {
                let mut dwarf_unwinder = DwarfUnwinder::<_, A, _>::new(
                    EndianSlice::new(debug_frame, LittleEndian),
                    UnwindSectionType::DebugFrame,
                    None,
                    &mut cache.gimli_unwind_context,
                    base_addresses.clone(),
                    module.base_svma,
                );
                let fde_offset = index
                    .fde_offset_for_relative_address(rel_lookup_address)
                    .ok_or(UnwinderError::DwarfCfiIndexCouldNotFindAddress)?;
                dwarf_unwinder.unwind_frame_with_fde::<_, P::GimliEvaluationStorage<_>>(
                    regs,
                    is_first_frame,
                    rel_lookup_address,
                    fde_offset,
                    read_stack,
                )?
            }
            #[cfg(feature = "pe")]
            ModuleUnwindDataInternal::PeUnwindInfo {
                pdata,
                rdata,
                xdata,
                text,
            } => <A as PeUnwinding>::unwind_frame(
                crate::pe::PeSections {
                    pdata,
                    rdata: rdata.as_ref(),
                    xdata: xdata.as_ref(),
                    text: text.as_ref(),
                },
                rel_lookup_address,
                regs,
                is_first_frame,
                read_stack,
            )?,
            ModuleUnwindDataInternal::None => return Err(UnwinderError::NoModuleUnwindData),
        };
        Ok(unwind_result)
    }
}

/// The unwind data that should be used when unwinding addresses inside this module.
/// Unwind data describes how to recover register values of the caller frame.
///
/// The type of unwind information you use depends on the platform and what's available
/// in the binary.
///
/// Type arguments:
///
///  - `D`: The type for unwind section data. This allows carrying owned data on the
///    module, e.g. `Vec<u8>`. But it could also be a wrapper around mapped memory from
///    a file or a different process, for example. It just needs to provide a slice of
///    bytes via its `Deref` implementation.
enum ModuleUnwindDataInternal<D> {
    /// Used on macOS, with mach-O binaries. Compact unwind info is in the `__unwind_info`
    /// section and is sometimes supplemented with DWARF CFI information in the `__eh_frame`
    /// section. `__stubs` and `__stub_helper` ranges are used by the unwinder.
    #[cfg(feature = "macho")]
    CompactUnwindInfoAndEhFrame {
        unwind_info: D,
        eh_frame: Option<D>,
        stubs_svma: Option<Range<u64>>,
        stub_helper_svma: Option<Range<u64>>,
        base_addresses: crate::dwarf::BaseAddresses,
        text_data: Option<TextByteData<D>>,
    },
    /// Used with ELF binaries (Linux and friends), in the `.eh_frame_hdr` and `.eh_frame`
    /// sections. Contains an index and DWARF CFI.
    EhFrameHdrAndEhFrame {
        eh_frame_hdr: D,
        eh_frame: D,
        base_addresses: crate::dwarf::BaseAddresses,
    },
    /// Used with ELF binaries (Linux and friends), in the `.eh_frame` section. Contains
    /// DWARF CFI. We create a binary index for the FDEs when a module with this unwind
    /// data type is added.
    DwarfCfiIndexAndEhFrame {
        index: DwarfCfiIndex,
        eh_frame: D,
        base_addresses: crate::dwarf::BaseAddresses,
    },
    /// Used with ELF binaries (Linux and friends), in the `.debug_frame` section. Contains
    /// DWARF CFI. We create a binary index for the FDEs when a module with this unwind
    /// data type is added.
    DwarfCfiIndexAndDebugFrame {
        index: DwarfCfiIndex,
        debug_frame: D,
        base_addresses: crate::dwarf::BaseAddresses,
    },
    /// Used with PE binaries (Windows).
    #[cfg(feature = "pe")]
    PeUnwindInfo {
        pdata: D,
        rdata: Option<DataAtRvaRange<D>>,
        xdata: Option<DataAtRvaRange<D>>,
        text: Option<DataAtRvaRange<D>>,
    },
    /// No unwind information is used. Unwinding in this module will use a fallback rule
    /// (usually frame pointer unwinding).
    None,
}

impl<D: Deref<Target = [u8]>> ModuleUnwindDataInternal<D> {
    fn new(section_info: &mut impl ModuleSectionInfo<D>) -> Self {
        use crate::dwarf::base_addresses_for_sections;

        #[cfg(feature = "macho")]
        if let Some(unwind_info) = section_info.section_data(b"__unwind_info") {
            let eh_frame = section_info.section_data(b"__eh_frame");
            let stubs = section_info.section_svma_range(b"__stubs");
            let stub_helper = section_info.section_svma_range(b"__stub_helper");
            // Get the bytes of the executable code (instructions).
            //
            // In mach-O objects, executable code is stored in the `__TEXT` segment, which contains
            // multiple executable sections such as `__text`, `__stubs`, and `__stub_helper`. If we
            // don't have the full `__TEXT` segment contents, we can fall back to the contents of
            // just the `__text` section.
            let text_data = if let (Some(bytes), Some(svma_range)) = (
                section_info.segment_data(b"__TEXT"),
                section_info.segment_svma_range(b"__TEXT"),
            ) {
                Some(TextByteData { bytes, svma_range })
            } else if let (Some(bytes), Some(svma_range)) = (
                section_info.section_data(b"__text"),
                section_info.section_svma_range(b"__text"),
            ) {
                Some(TextByteData { bytes, svma_range })
            } else {
                None
            };
            return ModuleUnwindDataInternal::CompactUnwindInfoAndEhFrame {
                unwind_info,
                eh_frame,
                stubs_svma: stubs,
                stub_helper_svma: stub_helper,
                base_addresses: base_addresses_for_sections(section_info),
                text_data,
            };
        }

        #[cfg(feature = "pe")]
        if let Some(pdata) = section_info.section_data(b".pdata") {
            let mut range_and_data = |name| {
                let rva_range = section_info.section_svma_range(name).and_then(|range| {
                    Some(Range {
                        start: (range.start - section_info.base_svma()).try_into().ok()?,
                        end: (range.end - section_info.base_svma()).try_into().ok()?,
                    })
                })?;
                let data = section_info.section_data(name)?;
                Some(DataAtRvaRange { data, rva_range })
            };
            return ModuleUnwindDataInternal::PeUnwindInfo {
                pdata,
                rdata: range_and_data(b".rdata"),
                xdata: range_and_data(b".xdata"),
                text: range_and_data(b".text"),
            };
        }

        if let Some(eh_frame) = section_info
            .section_data(b".eh_frame")
            .or_else(|| section_info.section_data(b"__eh_frame"))
        {
            if let Some(eh_frame_hdr) = section_info
                .section_data(b".eh_frame_hdr")
                .or_else(|| section_info.section_data(b"__eh_frame_hdr"))
            {
                ModuleUnwindDataInternal::EhFrameHdrAndEhFrame {
                    eh_frame_hdr,
                    eh_frame,
                    base_addresses: base_addresses_for_sections(section_info),
                }
            } else {
                match DwarfCfiIndex::try_new_eh_frame(&eh_frame, section_info) {
                    Ok(index) => ModuleUnwindDataInternal::DwarfCfiIndexAndEhFrame {
                        index,
                        eh_frame,
                        base_addresses: base_addresses_for_sections(section_info),
                    },
                    Err(_) => ModuleUnwindDataInternal::None,
                }
            }
        } else if let Some(debug_frame) = section_info.section_data(b".debug_frame") {
            match DwarfCfiIndex::try_new_debug_frame(&debug_frame, section_info) {
                Ok(index) => ModuleUnwindDataInternal::DwarfCfiIndexAndDebugFrame {
                    index,
                    debug_frame,
                    base_addresses: base_addresses_for_sections(section_info),
                },
                Err(_) => ModuleUnwindDataInternal::None,
            }
        } else {
            ModuleUnwindDataInternal::None
        }
    }
}

/// Used to supply raw instruction bytes to the unwinder, which uses it to analyze
/// instructions in order to provide high quality unwinding inside function prologues and
/// epilogues.
///
/// This is only needed on macOS, because mach-O `__unwind_info` and `__eh_frame` only
/// cares about accuracy in function bodies, not in function prologues and epilogues.
///
/// On Linux, compilers produce `.eh_frame` and `.debug_frame` which provides correct
/// unwind information for all instructions including those in function prologues and
/// epilogues, so instruction analysis is not needed.
///
/// Type arguments:
///
///  - `D`: The type for unwind section data. This allows carrying owned data on the
///    module, e.g. `Vec<u8>`. But it could also be a wrapper around mapped memory from
///    a file or a different process, for example. It just needs to provide a slice of
///    bytes via its `Deref` implementation.
#[cfg(feature = "macho")]
struct TextByteData<D> {
    pub bytes: D,
    pub svma_range: Range<u64>,
}

/// Information about a module that is loaded in a process. You might know this under a
/// different name, for example: (Shared) library, binary image, DSO ("Dynamic shared object")
///
/// The unwinder needs to have an up-to-date list of modules so that it can match an
/// absolute address to the right module, and so that it can find that module's unwind
/// information.
///
/// Type arguments:
///
///  - `D`: The type for unwind section data. This allows carrying owned data on the
///    module, e.g. `Vec<u8>`. But it could also be a wrapper around mapped memory from
///    a file or a different process, for example. It just needs to provide a slice of
///    bytes via its `Deref` implementation.
pub struct Module<D> {
    /// The name or file path of the module. Unused, it's just there for easier debugging.
    #[allow(unused)]
    name: String,
    /// The address range where this module is mapped into the process.
    avma_range: Range<u64>,
    /// The base address of this module, in the process's address space. On Linux, the base
    /// address can sometimes be different from the start address of the mapped range.
    base_avma: u64,
    /// The base address of this module, according to the module.
    base_svma: u64,
    /// The unwind data that should be used for unwinding addresses from this module.
    unwind_data: Arc<ModuleUnwindDataInternal<D>>,
}

impl<D> Clone for Module<D> {
    fn clone(&self) -> Self {
        Self {
            name: self.name.clone(),
            avma_range: self.avma_range.clone(),
            base_avma: self.base_avma,
            base_svma: self.base_svma,
            unwind_data: self.unwind_data.clone(),
        }
    }
}

/// Information about a module's sections (and segments).
///
/// This trait is used as an interface to module information, and each function with `&mut self` is
/// called at most once with a particular argument (e.g., `section_data(b".text")` will be called
/// at most once, so it can move data out of the underlying type if desired).
///
/// Type arguments:
///
///  - `D`: The type for section data. This allows carrying owned data on the module, e.g.
///    `Vec<u8>`. But it could also be a wrapper around mapped memory from a file or a different
///    process, for example.
pub trait ModuleSectionInfo<D> {
    /// Return the base address stated in the module.
    ///
    /// For mach-O objects, this is the vmaddr of the __TEXT segment. For ELF objects, this is
    /// zero. For PE objects, this is the image base address.
    ///
    /// This is used to convert between SVMAs and relative addresses.
    fn base_svma(&self) -> u64;

    /// Get the given section's memory range, as stated in the module.
    fn section_svma_range(&mut self, name: &[u8]) -> Option<Range<u64>>;

    /// Get the given section's data. This will only be called once per section.
    fn section_data(&mut self, name: &[u8]) -> Option<D>;

    /// Get the given segment's memory range, as stated in the module.
    fn segment_svma_range(&mut self, _name: &[u8]) -> Option<Range<u64>> {
        None
    }

    /// Get the given segment's data. This will only be called once per segment.
    fn segment_data(&mut self, _name: &[u8]) -> Option<D> {
        None
    }
}

/// Explicit addresses and data of various sections in the module. This implements
/// the `ModuleSectionInfo` trait.
///
/// Unless otherwise stated, these are SVMAs, "stated virtual memory addresses", i.e. addresses as
/// stated in the object, as opposed to AVMAs, "actual virtual memory addresses", i.e. addresses in
/// the virtual memory of the profiled process.
///
/// Code addresses inside a module's unwind information are usually written down as SVMAs,
/// or as relative addresses. For example, DWARF CFI can have code addresses expressed as
/// relative-to-.text addresses or as absolute SVMAs. And mach-O compact unwind info
/// contains addresses relative to the image base address.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ExplicitModuleSectionInfo<D> {
    /// The image base address, as stated in the object. For mach-O objects, this is the
    /// vmaddr of the `__TEXT` segment. For ELF objects, this is zero.
    ///
    /// This is used to convert between SVMAs and relative addresses.
    pub base_svma: u64,
    /// The address range of the `__text` or `.text` section. This is where most of the compiled
    /// code is stored.
    ///
    /// This is used to detect whether we need to do instruction analysis for an address.
    pub text_svma: Option<Range<u64>>,
    /// The data of the `__text` or `.text` section. This is where most of the compiled code is
    /// stored. For mach-O binaries, this does not need to be supplied if `text_segment` is supplied.
    ///
    /// This is used to handle function prologues and epilogues in some cases.
    pub text: Option<D>,
    /// The address range of the mach-O `__stubs` section. Contains small pieces of
    /// executable code for calling imported functions. Code inside this section is not
    /// covered by the unwind information in `__unwind_info`.
    ///
    /// This is used to exclude addresses in this section from incorrectly applying
    /// `__unwind_info` opcodes. It is also used to infer unwind rules for the known
    /// structure of stub functions.
    pub stubs_svma: Option<Range<u64>>,
    /// The address range of the mach-O `__stub_helper` section. Contains small pieces of
    /// executable code for calling imported functions. Code inside this section is not
    /// covered by the unwind information in `__unwind_info`.
    ///
    /// This is used to exclude addresses in this section from incorrectly applying
    /// `__unwind_info` opcodes. It is also used to infer unwind rules for the known
    /// structure of stub helper
    /// functions.
    pub stub_helper_svma: Option<Range<u64>>,
    /// The address range of the `.got` section (Global Offset Table). This is used
    /// during DWARF CFI processing, to resolve got-relative addresses.
    pub got_svma: Option<Range<u64>>,
    /// The data of the `__unwind_info` section of mach-O binaries.
    pub unwind_info: Option<D>,
    /// The address range of the `__eh_frame` or `.eh_frame` section. This is used during DWARF CFI
    /// processing, to resolve eh_frame-relative addresses.
    pub eh_frame_svma: Option<Range<u64>>,
    /// The data of the `__eh_frame` or `.eh_frame` section. This is used during DWARF CFI
    /// processing, to resolve eh_frame-relative addresses.
    pub eh_frame: Option<D>,
    /// The address range of the `.eh_frame_hdr` section. This is used during DWARF CFI processing,
    /// to resolve eh_frame_hdr-relative addresses.
    pub eh_frame_hdr_svma: Option<Range<u64>>,
    /// The data of the `.eh_frame_hdr` section. This is used during DWARF CFI processing, to
    /// resolve eh_frame_hdr-relative addresses.
    pub eh_frame_hdr: Option<D>,
    /// The data of the `.debug_frame` section. The related address range is not needed.
    pub debug_frame: Option<D>,
    /// The address range of the `__TEXT` segment of mach-O binaries, if available.
    pub text_segment_svma: Option<Range<u64>>,
    /// The data of the `__TEXT` segment of mach-O binaries, if available.
    pub text_segment: Option<D>,
}

impl<D> ModuleSectionInfo<D> for ExplicitModuleSectionInfo<D>
where
    D: Deref<Target = [u8]>,
{
    fn base_svma(&self) -> u64 {
        self.base_svma
    }

    fn section_svma_range(&mut self, name: &[u8]) -> Option<Range<u64>> {
        match name {
            b"__text" | b".text" => self.text_svma.clone(),
            b"__stubs" => self.stubs_svma.clone(),
            b"__stub_helper" => self.stub_helper_svma.clone(),
            b"__eh_frame" | b".eh_frame" => self.eh_frame_svma.clone(),
            b"__eh_frame_hdr" | b".eh_frame_hdr" => self.eh_frame_hdr_svma.clone(),
            b"__got" | b".got" => self.got_svma.clone(),
            _ => None,
        }
    }
    fn section_data(&mut self, name: &[u8]) -> Option<D> {
        match name {
            b"__text" | b".text" => self.text.take(),
            b"__unwind_info" => self.unwind_info.take(),
            b"__eh_frame" | b".eh_frame" => self.eh_frame.take(),
            b"__eh_frame_hdr" | b".eh_frame_hdr" => self.eh_frame_hdr.take(),
            b"__debug_frame" | b".debug_frame" => self.debug_frame.take(),
            _ => None,
        }
    }
    fn segment_svma_range(&mut self, name: &[u8]) -> Option<Range<u64>> {
        match name {
            b"__TEXT" => self.text_segment_svma.clone(),
            _ => None,
        }
    }
    fn segment_data(&mut self, name: &[u8]) -> Option<D> {
        match name {
            b"__TEXT" => self.text_segment.take(),
            _ => None,
        }
    }
}

impl<D: Deref<Target = [u8]>> Module<D> {
    pub fn new(
        name: String,
        avma_range: core::ops::Range<u64>,
        base_avma: u64,
        mut section_info: impl ModuleSectionInfo<D>,
    ) -> Self {
        let unwind_data = ModuleUnwindDataInternal::new(&mut section_info);

        Self {
            name,
            avma_range,
            base_avma,
            base_svma: section_info.base_svma(),
            unwind_data: Arc::new(unwind_data),
        }
    }

    pub fn avma_range(&self) -> core::ops::Range<u64> {
        self.avma_range.clone()
    }

    pub fn base_avma(&self) -> u64 {
        self.base_avma
    }

    pub fn name(&self) -> &str {
        &self.name
    }
}
