Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 51 additions & 21 deletions lib/compiler/src/engine/unwind/systemv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//! Module for System V ABI unwind registry.

use core::sync::atomic::{AtomicUsize, Ordering::Relaxed};
use std::sync::Mutex;

use crate::types::unwind::CompiledFunctionUnwindInfoReference;

Expand All @@ -21,6 +22,16 @@ unsafe extern "C" {
fn __deregister_frame(fde: *const u8);
}

/// The GLOBAL_UNREGISTRY fullfills two purposes:
/// 1. It keeps track of all the frames that should be deregistered. We don't want to
/// deregister frames in UnwindRegistry::Drop as that could be called during
/// program shutdown and can collide with release_registered_frames and lead to
/// crashes.
/// 2. It serves as a lock to ensure that registrations and deregistrations are not
/// interleaved when multiple threads are registering/deregistering frames at the
/// same time because that can also lead to crashes.
static GLOBAL_UNREGISTRY: Mutex<Vec<usize>> = Mutex::new(Vec::new());

// Apple-specific unwind functions - the following is taken from LLVM's libunwind itself.
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
mod compact_unwind;
Expand Down Expand Up @@ -141,12 +152,26 @@ impl UnwindRegistry {
unsafe fn register_frames(&mut self, eh_frame: &[u8]) {
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
{
// Aquire the unregistry lock to avoid interleaved registrations/deregistrations.
let mut unregistry = GLOBAL_UNREGISTRY.lock().unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we would iterate the PublishedUnwindRegistry, and call PublishedUnwindRegistry::unregister?


for registration in unregistry.iter_mut() {
unsafe {
compact_unwind::__unw_remove_dynamic_eh_frame_section(*registration);
}
self.compact_unwind_mgr.deregister();
}
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The self.compact_unwind_mgr.deregister() call is inside the loop but appears to be called on the same manager object for each item in the unregistry. This means if there are N registrations in the unregistry, deregister() will be called N times on the same manager. This seems incorrect - it should likely be called once after the loop, similar to how self.compact_unwind_mgr.register() is called once in the publish() method (line 143). Consider moving this call outside the loop.

Suggested change
self.compact_unwind_mgr.deregister();
}
}
self.compact_unwind_mgr.deregister();

Copilot uses AI. Check for mistakes.
unregistry.clear();

// Special call for macOS on aarch64 to register the `.eh_frame` section.
// TODO: I am not 100% sure if it's correct to never deregister the `.eh_frame` section. It was this way before
// I started working on this, so I kept it that way.
unsafe {
compact_unwind::__unw_add_dynamic_eh_frame_section(eh_frame.as_ptr() as usize);
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On macOS aarch64, the code registers frames via __unw_add_dynamic_eh_frame_section at line 170, but unlike the non-macOS path (line 245), it doesn't add the registration to self.registrations. This means when Drop is called (line 294), self.registrations will be empty on macOS, so nothing gets queued in the unregistry for later deregistration. This could lead to the registered frames never being deregistered. Consider adding the registration to self.registrations after line 170, similar to how it's done at line 245 for non-macOS platforms.

Suggested change
compact_unwind::__unw_add_dynamic_eh_frame_section(eh_frame.as_ptr() as usize);
let registration = eh_frame.as_ptr() as usize;
compact_unwind::__unw_add_dynamic_eh_frame_section(registration);
self.registrations.push(registration);

Copilot uses AI. Check for mistakes.
}

// Drop the lock here to make sure it is held during the entire registration process.
drop(unregistry);
}

#[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
Expand Down Expand Up @@ -202,13 +227,26 @@ impl UnwindRegistry {
"The `.eh_frame` must be finished after the last record",
);

// Aquire the unregistry lock to avoid interleaved registrations/deregistrations.
let mut unregistry = GLOBAL_UNREGISTRY.lock().unwrap();

// Unregister previously registered frames to avoid memory leaks.
for registration in unregistry.iter_mut() {
unsafe {
__deregister_frame(*registration as *const _);
}
}
unregistry.clear();
for record in records_to_register {
// Register the CFI with libgcc
unsafe {
__register_frame(record as *const u8);
}
self.registrations.push(record);
}

// Drop the lock here to make sure it is held during the entire registration process.
drop(unregistry);
}
}

Expand Down Expand Up @@ -240,28 +278,20 @@ impl UnwindRegistry {
impl Drop for UnwindRegistry {
fn drop(&mut self) {
if self.published {
unsafe {
// libgcc stores the frame entries as a linked list in decreasing sort order
// based on the PC value of the registered entry.
//
// As we store the registrations in increasing order, it would be O(N^2) to
// deregister in that order.
//
// To ensure that we just pop off the first element in the list upon every
// deregistration, walk our list of registrations backwards.
for registration in self.registrations.iter().rev() {
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
{
compact_unwind::__unw_remove_dynamic_eh_frame_section(*registration);
self.compact_unwind_mgr.deregister();
}
let mut unregistry = GLOBAL_UNREGISTRY.lock().unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and this will simply push to the GLOBAL_UNREGISTRY vector.


#[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
{
__deregister_frame(*registration as *const _);
}
}
}
// libgcc stores the frame entries as a linked list in decreasing sort order
// based on the PC value of the registered entry.
//
// As we store the registrations in increasing order, it would be O(N^2) to
// deregister in that order.
//
// To ensure that we just pop off the first element in the list upon every
// deregistration, walk our list of registrations backwards.

// Queue up all registrations for deregistration.
unregistry.reserve(self.registrations.len());
unregistry.extend(self.registrations.iter().rev());
}
}
}
Loading