1use super::{KEPT, Ordering};
2use std::{path::Path, sync::OnceLock};
3
4pub(super) static HEADING: OnceLock<String> = OnceLock::new();
5// Encoding the path before a fault keeps allocation out of the handler.
6#[cfg(unix)]
7static PATH: OnceLock<std::ffi::CString> = OnceLock::new();
8#[cfg(windows)]
9static PATH: OnceLock<Vec<u16>> = OnceLock::new();
10
11pub(super) fn prepare(path: &Path) -> std::io::Result<()> {
12 #[cfg(unix)]
13 {
14 use std::os::unix::ffi::OsStrExt;
15 let path = std::ffi::CString::new(path.as_os_str().as_bytes())?;
16 let _ = PATH.set(path);
17 // Linux forwards its existing signal handler, preserving its symbolized log.
18 #[cfg(any(target_os = "macos", target_os = "ios"))]
19 for signal in [
20 libc::SIGSEGV,
21 libc::SIGBUS,
22 libc::SIGILL,
23 libc::SIGFPE,
24 libc::SIGABRT,
25 ] {
26 let mut action: libc::sigaction = unsafe { std::mem::zeroed() };
27 action.sa_sigaction = fatal as *const () as libc::sighandler_t;
28 action.sa_flags = libc::SA_SIGINFO | libc::SA_RESETHAND;
29 unsafe {
30 libc::sigemptyset(&mut action.sa_mask);
31 libc::sigaction(signal, &action, std::ptr::null_mut());
32 }
33 }
34 }
35 #[cfg(windows)]
36 {
37 use std::os::windows::ffi::OsStrExt;
38 let path: Vec<_> = path.as_os_str().encode_wide().chain([0]).collect();
39 if path[..path.len() - 1].contains(&0) {
40 return Err(std::io::ErrorKind::InvalidInput.into());
41 }
42 let _ = PATH.set(path);
43 unsafe {
44 windows_sys::Win32::System::Diagnostics::Debug::SetUnhandledExceptionFilter(Some(
45 fault,
46 ));
47 }
48 }
49 Ok(())
50}
51
52fn hex(mut value: u64) -> [u8; 16] {
53 let mut digits = [b'0'; 16];
54 for digit in digits.iter_mut().rev() {
55 *digit = b"0123456789abcdef"[(value & 15) as usize];
56 value >>= 4;
57 }
58 digits
59}
60
61/// Records the fatal signal without allocating, locking, or reading note data.
62///
63/// # Safety
64/// `info` is the live `siginfo_t` supplied to a fatal signal handler.
65#[cfg(unix)]
66pub unsafe fn record_signal(signal: i32, info: *const libc::siginfo_t) {
67 if PATH.get().is_none() || KEPT.swap(true, Ordering::Relaxed) {
68 return;
69 }
70 let Some(path) = PATH.get() else {
71 return;
72 };
73 let fd = unsafe {
74 libc::open(
75 path.as_ptr(),
76 libc::O_WRONLY | libc::O_CREAT | libc::O_TRUNC | libc::O_NOFOLLOW,
77 0o600,
78 )
79 };
80 if fd < 0 {
81 return;
82 }
83 let code = hex(signal as u64);
84 let write = |parts: &[&[u8]]| {
85 for mut bytes in parts.iter().copied() {
86 while !bytes.is_empty() {
87 let wrote = unsafe { libc::write(fd, bytes.as_ptr().cast(), bytes.len()) };
88 if wrote <= 0 {
89 break;
90 }
91 bytes = &bytes[wrote as usize..];
92 }
93 }
94 };
95 write(&[
96 HEADING
97 .get()
98 .map_or(&b"Snowbound\n"[..], |heading| heading.as_bytes()),
99 b"Native signal: 0x",
100 &code,
101 b"\n",
102 ]);
103 if unsafe { (*info).si_code } > 0
104 && matches!(
105 signal,
106 libc::SIGSEGV | libc::SIGBUS | libc::SIGILL | libc::SIGFPE
107 )
108 {
109 let address = hex(unsafe { (*info).si_addr() } as usize as u64);
110 write(&[b"Fault address: 0x", &address, b"\n"]);
111 }
112 unsafe {
113 libc::fsync(fd);
114 libc::close(fd);
115 }
116}
117
118#[cfg(any(target_os = "macos", target_os = "ios"))]
119extern "C" fn fatal(signal: i32, info: *mut libc::siginfo_t, _: *mut libc::c_void) {
120 unsafe {
121 record_signal(signal, info);
122 libc::raise(signal);
123 }
124}
125
126#[cfg(windows)]
127unsafe extern "system" fn fault(
128 pointers: *const windows_sys::Win32::System::Diagnostics::Debug::EXCEPTION_POINTERS,
129) -> i32 {
130 use windows_sys::Win32::{
131 Foundation::{GENERIC_WRITE, INVALID_HANDLE_VALUE},
132 Storage::FileSystem::{
133 CREATE_ALWAYS, CreateFileW, FILE_ATTRIBUTE_NORMAL, FILE_FLAG_WRITE_THROUGH, WriteFile,
134 },
135 };
136 let Some(path) = PATH.get() else {
137 return 0;
138 };
139 if KEPT.swap(true, Ordering::Relaxed) {
140 return 0;
141 }
142 let file = unsafe {
143 CreateFileW(
144 path.as_ptr(),
145 GENERIC_WRITE,
146 0,
147 std::ptr::null(),
148 CREATE_ALWAYS,
149 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH,
150 std::ptr::null_mut(),
151 )
152 };
153 if file == INVALID_HANDLE_VALUE {
154 return 0;
155 }
156 let record = unsafe { &*(*pointers).ExceptionRecord };
157 let code = hex(record.ExceptionCode as u32 as u64);
158 let address = hex(record.ExceptionAddress as usize as u64);
159 for bytes in [
160 HEADING
161 .get()
162 .map_or(&b"Snowbound\n"[..], |heading| heading.as_bytes()),
163 b"Native exception: 0x",
164 &code,
165 b"\nInstruction address: 0x",
166 &address,
167 b"\n",
168 ] {
169 let mut wrote = 0;
170 unsafe {
171 WriteFile(
172 file,
173 bytes.as_ptr(),
174 bytes.len() as u32,
175 &mut wrote,
176 std::ptr::null_mut(),
177 );
178 }
179 }
180 unsafe {
181 windows_sys::Win32::Foundation::CloseHandle(file);
182 }
183 0
184}