/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use ini::Ini; use libc::time; use process_reader::ProcessReader; use serde::Serialize; use serde_json::ser::to_writer; use std::convert::TryInto; use std::ffi::{c_void, OsString}; use std::fs::{read_to_string, DirBuilder, File, OpenOptions}; use std::io::{BufRead, BufReader, Write}; use std::mem::{size_of, transmute, zeroed}; use std::os::windows::ffi::{OsStrExt, OsStringExt}; use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle, RawHandle}; use std::path::{Path, PathBuf}; use std::ptr::{addr_of, null, null_mut}; use std::slice::from_raw_parts; use uuid::Uuid; use windows_sys::core::{HRESULT, PWSTR}; use windows_sys::Wdk::System::Threading::{NtQueryInformationProcess, ProcessBasicInformation}; use windows_sys::Win32::{
Foundation::{
CloseHandle, GetLastError, SetLastError, BOOL, ERROR_INSUFFICIENT_BUFFER, ERROR_SUCCESS,
EXCEPTION_BREAKPOINT, E_UNEXPECTED, FALSE, FILETIME, HANDLE, HWND, LPARAM, MAX_PATH,
STATUS_SUCCESS, S_OK, TRUE, WAIT_OBJECT_0,
},
Security::{
GetSidSubAuthority, GetSidSubAuthorityCount, GetTokenInformation, IsTokenRestricted,
TokenIntegrityLevel, TOKEN_MANDATORY_LABEL, TOKEN_QUERY,
},
System::Com::CoTaskMemFree,
System::Diagnostics::Debug::{
GetThreadContext, MiniDumpWithFullMemoryInfo, MiniDumpWithIndirectlyReferencedMemory,
MiniDumpWithProcessThreadData, MiniDumpWithUnloadedModules, MiniDumpWriteDump,
WriteProcessMemory, EXCEPTION_POINTERS, MINIDUMP_EXCEPTION_INFORMATION, MINIDUMP_TYPE,
},
System::ErrorReporting::WER_RUNTIME_EXCEPTION_INFORMATION,
System::Memory::{
VirtualAllocEx, VirtualFreeEx, MEM_COMMIT, MEM_RELEASE, MEM_RESERVE, PAGE_READWRITE,
},
System::ProcessStatus::K32GetModuleFileNameExW,
System::SystemInformation::{
VerSetConditionMask, VerifyVersionInfoW, OSVERSIONINFOEXW, VER_MAJORVERSION,
VER_MINORVERSION, VER_SERVICEPACKMAJOR, VER_SERVICEPACKMINOR,
},
System::SystemServices::{SECURITY_MANDATORY_MEDIUM_RID, VER_GREATER_EQUAL},
System::Threading::{
CreateProcessW, CreateRemoteThread, GetProcessId, GetProcessTimes, GetThreadId,
OpenProcess, OpenProcessToken, OpenThread, TerminateProcess, WaitForSingleObject,
CREATE_NO_WINDOW, CREATE_UNICODE_ENVIRONMENT, LPTHREAD_START_ROUTINE,
NORMAL_PRIORITY_CLASS, PROCESS_ALL_ACCESS, PROCESS_BASIC_INFORMATION, PROCESS_INFORMATION,
STARTUPINFOW, THREAD_GET_CONTEXT,
},
UI::Shell::{FOLDERID_RoamingAppData, SHGetKnownFolderPath},
UI::WindowsAndMessaging::{EnumWindows, GetWindowThreadProcessId, IsHungAppWindow},
};
type DWORD = u32; type ULONG = u32; type DWORDLONG = u64; type LPVOID = *mut c_void; type PVOID = LPVOID; type PBOOL = *mut BOOL; type PDWORD = *mut DWORD; #[allow(non_camel_case_types)] type PWER_RUNTIME_EXCEPTION_INFORMATION = *mut WER_RUNTIME_EXCEPTION_INFORMATION;
/* The following struct must be kept in sync with the identically named one in *nsExceptionHandler.h.WERwilluseittocommunicatewiththemainprocess
* when a child process is encountered. */ #[repr(C)] struct WindowsErrorReportingData {
child_pid: DWORD,
minidump_name: [u8; 40],
}
// This value comes from GeckoProcessTypes.h static MAIN_PROCESS_TYPE: u32 = 0;
match result {
Ok(_) => { unsafe { // Inform WER that we claim ownership of this crash
*b_ownership_claimed = TRUE; // Make sure that the process shuts down
TerminateProcess((*exception_information).hProcess, 1);
}
S_OK
}
Err(_) => E_UNEXPECTED,
}
}
fn out_of_process_exception_event_callback(
context: PVOID,
exception_information: PWER_RUNTIME_EXCEPTION_INFORMATION,
) -> Result<()> { let exception_information = unsafe { &mut *exception_information }; let is_fatal = exception_information.bIsFatal.to_bool(); letmut is_ui_hang = false; if !is_fatal { 'hang: { // Check whether this error is a hang. A hang always results in an EXCEPTION_BREAKPOINT. // Hangs may have an hThread/context that is unrelated to the hanging thread, so we get // it by searching for process windows that are hung. if exception_information.exceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT { iflet Ok(thread_id) = find_hung_window_thread(exception_information.hProcess) { // In the case of a hang, change the crashing thread to be the one that created // the hung window. // // This is all best-effort, so don't return errors (just fall through to the // Ok return). let thread_handle = unsafe { OpenThread(THREAD_GET_CONTEXT, FALSE, thread_id) }; if thread_handle != 0
&& unsafe {
GetThreadContext(thread_handle, &mut exception_information.context)
}
.to_bool()
{
exception_information.hThread = thread_handle; break'hang;
}
}
}
// A non-fatal but non-hang exception should not do anything else. return Ok(());
}
is_ui_hang = true;
}
let process = exception_information.hProcess; let application_info = ApplicationInformation::from_process(process)?; let process_type: u32 = (context as usize).try_into().map_err(|_| ())?; let startup_time = get_startup_time(process)?; let crash_report = CrashReport::new(&application_info, startup_time, is_ui_hang);
crash_report.write_minidump(exception_information)?; if process_type == MAIN_PROCESS_TYPE { match is_sandboxed_process(process) {
Ok(false) => handle_main_process_crash(crash_report, &application_info),
_ => { // The parent process should never be sandboxed, bail out so the // process which is impersonating it gets killed right away. Also // bail out if is_sandboxed_process() failed while checking.
Ok(())
}
}
} else {
handle_child_process_crash(crash_report, process)
}
}
/// Find whether the given process has a hung window, and return the thread id related to the /// window. fn find_hung_window_thread(process: HANDLE) -> Result<DWORD> { let process_id = get_process_id(process)?;
unsafeextern"system"fn enum_window_callback(wnd: HWND, data: LPARAM) -> BOOL { let data = &mut *(data as *mut WindowSearch); letmut window_proc_id = DWORD::default(); let thread_id = GetWindowThreadProcessId(wnd, &mut window_proc_id); if thread_id != 0 && window_proc_id == data.process_id && IsHungAppWindow(wnd).to_bool() {
data.ui_thread_id = Some(thread_id); FALSE
} else { TRUE
}
}
// Disregard the return value, we are trying for best-effort service (it's okay if ui_thread_id // is never set). unsafe { EnumWindows(Some(enum_window_callback), &mut search as*mut _ as LPARAM) };
search.ui_thread_id.ok_or(())
}
fn get_parent_process(process: HANDLE) -> Result<HANDLE> { let pbi = get_process_basic_information(process)?;
get_process_handle(pbi.InheritedFromUniqueProcessId as u32)
}
if thread == 0 { unsafe { VirtualFreeEx(process, address as *mut _, 0, MEM_RELEASE) }; return Err(());
}
// From this point on the memory pointed to by address is owned by the // thread we've created in the main process, so we don't free it.
let thread = unsafe { OwnedHandle::from_raw_handle(thread as RawHandle) };
// Don't wait forever as we want the process to get killed eventually let res = unsafe { WaitForSingleObject(thread.as_raw_handle() as HANDLE, 5000) }; if res != WAIT_OBJECT_0 { return Err(());
}
Ok(())
}
fn get_startup_time(process: HANDLE) -> Result<u64> { const ZERO_FILETIME: FILETIME = FILETIME {
dwLowDateTime: 0,
dwHighDateTime: 0,
}; letmut create_time: FILETIME = ZERO_FILETIME; letmut exit_time: FILETIME = ZERO_FILETIME; letmut kernel_time: FILETIME = ZERO_FILETIME; letmut user_time: FILETIME = ZERO_FILETIME; unsafe { if GetProcessTimes(
process,
&mut create_time as *mut _,
&mut exit_time as *mut _,
&mut kernel_time as *mut _,
&mut user_time as *mut _,
) == 0
{ return Err(());
}
} let start_time_in_ticks =
((create_time.dwHighDateTime as u64) << 32) + create_time.dwLowDateTime as u64; let windows_tick: u64 = 10000000; let sec_to_unix_epoch = 11644473600;
Ok((start_time_in_ticks / windows_tick) - sec_to_unix_epoch)
}
fn launch_crash_reporter_client(install_path: &Path, crash_report: &CrashReport) { // Prepare the command line let client_path = install_path.join("crashreporter.exe");
impl ApplicationData { fn load_from_disk(install_path: &Path) -> Result<ApplicationData> { let ini_path = ApplicationData::get_path(install_path); let conf = Ini::load_from_file(ini_path).map_err(|_e| ())?;
// Parse the "App" section let app_section = conf.section(Some("App")).ok_or(())?; let vendor = app_section.get("Vendor").map(|s| s.to_owned()); let name = app_section.get("Name").ok_or(())?.to_owned(); let version = app_section.get("Version").ok_or(())?.to_owned(); let build_id = app_section.get("BuildID").ok_or(())?.to_owned(); let product_id = app_section.get("ID").ok_or(())?.to_owned();
// Parse the "Crash Reporter" section let crash_reporter_section = conf.section(Some("Crash Reporter")).ok_or(())?; let server_url = crash_reporter_section
.get("ServerURL")
.ok_or(())?
.to_owned();
/// Encapsulates the information about the application that crashed. This includes the install path as well as version information struct ApplicationInformation {
install_path: PathBuf,
application_data: ApplicationData,
release_channel: String,
crash_reports_dir: PathBuf,
install_time: String,
}
impl ApplicationInformation { fn from_process(process: HANDLE) -> Result<ApplicationInformation> { letmut install_path = ApplicationInformation::get_application_path(process)?;
install_path.pop(); let application_data = ApplicationData::load_from_disk(install_path.as_ref())?; let release_channel = ApplicationInformation::get_release_channel(install_path.as_ref())?; let crash_reports_dir = ApplicationInformation::get_crash_reports_dir(&application_data)?; let install_time = ApplicationInformation::get_install_time(
&crash_reports_dir,
&application_data.build_id,
);
fn get_application_path(process: HANDLE) -> Result<PathBuf> { letmut path: [u16; MAX_PATH as usize + 1] = [0; MAX_PATH as usize + 1]; unsafe { let res = K32GetModuleFileNameExW(
process, 0,
(&mut path).as_mut_ptr(),
(MAX_PATH + 1) as DWORD,
);
if res == 0 { return Err(());
}
let application_path = PathBuf::from(OsString::from_wide(&path[0..res as usize]));
Ok(application_path)
}
}
fn get_release_channel(install_path: &Path) -> Result<String> { let channel_prefs =
File::open(install_path.join("defaults/pref/channel-prefs.js")).map_err(|_e| ())?; let lines = BufReader::new(channel_prefs).lines(); let line = lines
.filter_map(std::result::Result::ok)
.find(|line| line.contains("app.update.channel"))
.ok_or(())?;
line.split("\"").nth(3).map(|s| s.to_string()).ok_or(())
}
fn get_crash_reports_dir(application_data: &ApplicationData) -> Result<PathBuf> { letmut psz_path: PWSTR = null_mut(); unsafe { let res = SHGetKnownFolderPath(
&FOLDERID_RoamingAppData as *const _, 0, 0,
&mut psz_path as *mut _,
);
if res == S_OK { letmut len = 0; while psz_path.offset(len).read() != 0 {
len += 1;
} let str = OsString::from_wide(from_raw_parts(psz_path, len as usize));
CoTaskMemFree(psz_path as _); letmut path = PathBuf::from(str); iflet Some(vendor) = &application_data.vendor {
path.push(vendor);
}
path.push(&application_data.name);
path.push("Crash Reports");
Ok(path)
} else {
Err(())
}
}
}
fn get_install_time(crash_reports_path: &Path, build_id: &str) -> String { let file_name = "InstallTime".to_owned() + build_id; let file_path = crash_reports_path.join(file_name);
// If the file isn't present we'll attempt to atomically create it and // populate it. This code essentially matches the corresponding code in // nsExceptionHandler.cpp SetupExtraData(). iflet Ok(mut file) = OpenOptions::new()
.create_new(true)
.write(true)
.open(&file_path)
{ // SAFETY: No risks in calling `time()` with a null pointer. let _ = write!(&mut file, "{}", unsafe { time(null_mut()) }.to_string());
}
// As a last resort, if we can't read the file we fall back to the // current time. This might cause us to overstate the number of users // affected by a crash, but given it's very unlikely to hit this particular // path it won't be a problem. // // SAFETY: No risks in calling `time()` with a null pointer.
read_to_string(&file_path).unwrap_or(unsafe { time(null_mut()) }.to_string())
}
}
fn get_minidump_type(&self) -> MINIDUMP_TYPE { letmut minidump_type = MiniDumpWithFullMemoryInfo | MiniDumpWithUnloadedModules; ifself.is_nightly() { // This is Nightly only because this doubles the size of minidumps based // on the experimental data.
minidump_type = minidump_type | MiniDumpWithProcessThreadData;
// dbghelp.dll on Win7 can't handle overlapping memory regions so we only // enable this feature on Win8 or later. if is_windows8_or_later() { // This allows us to examine heap objects referenced from stack objects // at the cost of further doubling the size of minidumps.
minidump_type = minidump_type | MiniDumpWithIndirectlyReferencedMemory
}
}
minidump_type
}
fn write_minidump(
&self,
exception_information: PWER_RUNTIME_EXCEPTION_INFORMATION,
) -> Result<()> { // Make sure that the target directory is present
DirBuilder::new()
.recursive(true)
.create(self.get_pending_path())
.map_err(|_e| ())?;
let minidump_path = self.get_minidump_path(); let minidump_file = File::create(minidump_path).map_err(|_e| ())?; let minidump_type: MINIDUMP_TYPE = self.get_minidump_type();
fn write_event_file(&self) -> Result<()> { // Make that the target directory is present
DirBuilder::new()
.recursive(true)
.create(self.get_events_path())
.map_err(|_e| ())?;
letmut buffer: Vec<u8> = vec![Default::default(); buffer_size as usize]; let res = unsafe {
GetTokenInformation(
token,
TokenIntegrityLevel,
buffer.as_mut_ptr() as *mut _,
buffer_size,
&mut buffer_size as *mut _,
)
};
if res != TRUE { return Err(());
}
let token_mandatory_label = &unsafe { *(buffer.as_ptr() as *constTOKEN_MANDATORY_LABEL) }; let sid = token_mandatory_label.Label.Sid; // We're not checking for errors in the following two calls because these // functions can only fail if provided with an invalid SID and we know the // one we obtained from `GetTokenInformation()` is valid. let sid_subauthority_count = unsafe { *GetSidSubAuthorityCount(sid) - 1u8 }; let integrity_level = unsafe { *GetSidSubAuthority(sid, sid_subauthority_count.into()) };
Ok((integrity_level < SECURITY_MANDATORY_MEDIUM_RID as u32) || is_restricted)
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.16 Sekunden
(vorverarbeitet am 2026-06-18)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.