From 17acdfabb923190ad74b2e6d9e58a88ced9af66e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Moreau?= Date: Mon, 13 Jul 2026 13:26:29 -0400 Subject: [PATCH] Replace CoreCLR DAC bridge with Rust Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- crates/windbg-dbgeng/Cargo.toml | 1 + crates/windbg-dbgeng/src/coreclr_dac.rs | 1407 ++++++++++---- docs/architecture.md | 2 +- docs/cli.md | 5 +- docs/development.md | 3 +- native/coreclr-dac-bridge/CMakeLists.txt | 40 - .../windbg_coreclr_dac_bridge.cpp | 1628 ----------------- .../windbg_coreclr_dac_bridge.h | 110 -- xtask/src/main.rs | 65 - 9 files changed, 1057 insertions(+), 2204 deletions(-) delete mode 100644 native/coreclr-dac-bridge/CMakeLists.txt delete mode 100644 native/coreclr-dac-bridge/windbg_coreclr_dac_bridge.cpp delete mode 100644 native/coreclr-dac-bridge/windbg_coreclr_dac_bridge.h diff --git a/crates/windbg-dbgeng/Cargo.toml b/crates/windbg-dbgeng/Cargo.toml index 01e7837..afde4f5 100644 --- a/crates/windbg-dbgeng/Cargo.toml +++ b/crates/windbg-dbgeng/Cargo.toml @@ -23,5 +23,6 @@ windows = { workspace = true, features = [ "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", + "Win32_System_WinRT_Metadata", "implement", ] } diff --git a/crates/windbg-dbgeng/src/coreclr_dac.rs b/crates/windbg-dbgeng/src/coreclr_dac.rs index 2a29422..91c9981 100644 --- a/crates/windbg-dbgeng/src/coreclr_dac.rs +++ b/crates/windbg-dbgeng/src/coreclr_dac.rs @@ -1,105 +1,334 @@ +#![allow(non_snake_case)] + use std::{ - env, - ffi::{c_void, OsStr}, - mem, + ffi::c_void, + mem::size_of, + os::windows::ffi::OsStrExt, path::{Path, PathBuf}, - ptr::NonNull, }; use anyhow::{bail, ensure, Context}; -use libloading::Library; use serde::Serialize; -use windows::core::Interface; +use windows::{ + core::{IUnknown, Interface, GUID, HRESULT, PCWSTR}, + Win32::{ + Foundation::{ + FreeLibrary, E_ACCESSDENIED, E_INVALIDARG, E_NOTIMPL, E_POINTER, E_UNEXPECTED, HMODULE, + S_FALSE, + }, + Storage::FileSystem::{ + GetFileVersionInfoSizeW, GetFileVersionInfoW, VerQueryValueW, VS_FFI_SIGNATURE, + VS_FIXEDFILEINFO, + }, + System::{ + Diagnostics::Debug::{Extensions::*, CONTEXT}, + LibraryLoader::{ + GetProcAddress, LoadLibraryExW, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR, + LOAD_LIBRARY_SEARCH_SYSTEM32, + }, + Memory::{VirtualAllocEx, VirtualFreeEx}, + SystemInformation::IMAGE_FILE_MACHINE_AMD64, + }, + }, +}; +use windows_core::IUnknown_Vtbl; use crate::DebuggerSession; -const BRIDGE_DLL_NAME: &str = "windbg_coreclr_dac_bridge.dll"; -const BRIDGE_DLL_ENV: &str = "WINDBG_CORECLR_DAC_BRIDGE_DLL"; -const WINDBG_DAC_OK: u32 = 0; -const WINDBG_DAC_NOT_FOUND: u32 = 3; -const WINDBG_DAC_AMBIGUOUS: u32 = 4; -const WINDBG_DAC_CODE_UNAVAILABLE: u32 = 5; +const CLRDATA_METHNOTIFY_GENERATED: u32 = 0x0000_0001; +const CLRDATA_METHNOTIFY_DISCARDED: u32 = 0x0000_0002; +const CLRDATA_NOTIFY_ON_MODULE_LOAD: u32 = 0x0000_0001; const MAX_WIDE_CHARS: usize = 1024; const MAX_SIGNATURE_HEX_CHARS: usize = 1024; const MAX_METHOD_CANDIDATES: usize = 8; -#[repr(C)] -#[derive(Clone, Copy)] -struct NativeRuntimeInfo { - coreclr_path: [u16; MAX_WIDE_CHARS], - dac_path: [u16; MAX_WIDE_CHARS], - coreclr_version_ms: u32, - coreclr_version_ls: u32, - dac_version_ms: u32, - dac_version_ls: u32, +// These declarations are the exact prefixes used from the MIT-licensed CoreCLR +// clrdata.idl/xclrdata.idl contracts. DAC interfaces are COM ABI contracts, so every +// preceding method must remain in its original order even when this module does not call it. +#[windows::core::interface("3E11CCEE-D08B-43E5-AF01-32717A64DA03")] +unsafe trait ICLRDataTarget: IUnknown { + unsafe fn GetMachineType(&self, machine_type: *mut u32) -> HRESULT; + unsafe fn GetPointerSize(&self, pointer_size: *mut u32) -> HRESULT; + unsafe fn GetImageBase(&self, image_path: PCWSTR, base_address: *mut u64) -> HRESULT; + unsafe fn ReadVirtual( + &self, + address: u64, + buffer: *mut u8, + bytes_requested: u32, + bytes_read: *mut u32, + ) -> HRESULT; + unsafe fn WriteVirtual( + &self, + address: u64, + buffer: *mut u8, + bytes_requested: u32, + bytes_written: *mut u32, + ) -> HRESULT; + unsafe fn GetTLSValue(&self, thread_id: u32, index: u32, value: *mut u64) -> HRESULT; + unsafe fn SetTLSValue(&self, thread_id: u32, index: u32, value: u64) -> HRESULT; + unsafe fn GetCurrentThreadID(&self, thread_id: *mut u32) -> HRESULT; + unsafe fn GetThreadContext( + &self, + thread_id: u32, + context_flags: u32, + context_size: u32, + context: *mut u8, + ) -> HRESULT; + unsafe fn SetThreadContext( + &self, + thread_id: u32, + context_size: u32, + context: *mut u8, + ) -> HRESULT; + unsafe fn Request( + &self, + request_code: u32, + input_size: u32, + input: *mut u8, + output_size: u32, + output: *mut u8, + ) -> HRESULT; } -impl Default for NativeRuntimeInfo { - fn default() -> Self { - // The C ABI consists entirely of integer fields and fixed UTF-16 buffers. - unsafe { mem::zeroed() } - } +#[windows::core::interface("6D05FAE3-189C-4630-A6DC-1C251E1C01AB")] +unsafe trait ICLRDataTarget2: ICLRDataTarget { + unsafe fn AllocVirtual( + &self, + address: u64, + size: u32, + type_flags: u32, + protect_flags: u32, + allocation: *mut u64, + ) -> HRESULT; + unsafe fn FreeVirtual(&self, address: u64, size: u32, type_flags: u32) -> HRESULT; } -#[repr(C)] -#[derive(Clone, Copy)] -struct NativeMethodCandidate { - method_token: u32, - signature_truncated: u8, - reserved: [u8; 3], - signature_hex: [u16; MAX_SIGNATURE_HEX_CHARS], +#[windows::core::interface("5C552AB6-FC09-4CB3-8E36-22FA03C798B7")] +unsafe trait IXCLRDataProcess: IUnknown { + unsafe fn Flush(&self) -> HRESULT; + unsafe fn StartEnumTasks(&self, handle: *mut u64) -> HRESULT; + unsafe fn EnumTask(&self, handle: *mut u64, task: *mut *mut c_void) -> HRESULT; + unsafe fn EndEnumTasks(&self, handle: u64) -> HRESULT; + unsafe fn GetTaskByOSThreadID(&self, thread_id: u32, task: *mut *mut c_void) -> HRESULT; + unsafe fn GetTaskByUniqueID(&self, task_id: u64, task: *mut *mut c_void) -> HRESULT; + unsafe fn GetFlags(&self, flags: *mut u32) -> HRESULT; + unsafe fn IsSameObject(&self, process: *mut c_void) -> HRESULT; + unsafe fn GetManagedObject(&self, value: *mut *mut c_void) -> HRESULT; + unsafe fn GetDesiredExecutionState(&self, state: *mut u32) -> HRESULT; + unsafe fn SetDesiredExecutionState(&self, state: u32) -> HRESULT; + unsafe fn GetAddressType(&self, address: u64, address_type: *mut u32) -> HRESULT; + unsafe fn GetRuntimeNameByAddress( + &self, + address: u64, + flags: u32, + buffer_length: u32, + name_length: *mut u32, + name: *mut u16, + displacement: *mut u64, + ) -> HRESULT; + unsafe fn StartEnumAppDomains(&self, handle: *mut u64) -> HRESULT; + unsafe fn EnumAppDomain(&self, handle: *mut u64, app_domain: *mut *mut c_void) -> HRESULT; + unsafe fn EndEnumAppDomains(&self, handle: u64) -> HRESULT; + unsafe fn GetAppDomainByUniqueID(&self, id: u64, app_domain: *mut *mut c_void) -> HRESULT; + unsafe fn StartEnumAssemblies(&self, handle: *mut u64) -> HRESULT; + unsafe fn EnumAssembly(&self, handle: *mut u64, assembly: *mut *mut c_void) -> HRESULT; + unsafe fn EndEnumAssemblies(&self, handle: u64) -> HRESULT; + unsafe fn StartEnumModules(&self, handle: *mut u64) -> HRESULT; + unsafe fn EnumModule(&self, handle: *mut u64, module: *mut *mut c_void) -> HRESULT; + unsafe fn EndEnumModules(&self, handle: u64) -> HRESULT; + unsafe fn GetModuleByAddress(&self, address: u64, module: *mut *mut c_void) -> HRESULT; + unsafe fn Reserved24(&self) -> HRESULT; + unsafe fn Reserved25(&self) -> HRESULT; + unsafe fn Reserved26(&self) -> HRESULT; + unsafe fn Reserved27(&self) -> HRESULT; + unsafe fn Reserved28(&self) -> HRESULT; + unsafe fn Reserved29(&self) -> HRESULT; + unsafe fn Reserved30(&self) -> HRESULT; + unsafe fn Reserved31(&self) -> HRESULT; + unsafe fn Reserved32(&self) -> HRESULT; + unsafe fn Reserved33(&self) -> HRESULT; + unsafe fn Reserved34(&self) -> HRESULT; + unsafe fn Reserved35(&self) -> HRESULT; + unsafe fn Reserved36(&self) -> HRESULT; + unsafe fn GetOtherNotificationFlags(&self, flags: *mut u32) -> HRESULT; + unsafe fn SetOtherNotificationFlags(&self, flags: u32) -> HRESULT; } -#[repr(C)] -#[derive(Clone, Copy)] -struct NativeMethodInfo { - method_token: u32, - matching_method_count: u32, - representative_entry_address: u64, - code_notification_flags: u32, - code_available: u8, - reserved: [u8; 3], - resolved_method: [u16; MAX_WIDE_CHARS], - resolved_signature_hex: [u16; MAX_SIGNATURE_HEX_CHARS], - reported_candidate_count: u32, - candidates_truncated: u8, - reserved2: [u8; 3], - candidates: [NativeMethodCandidate; MAX_METHOD_CANDIDATES], +#[windows::core::interface("88E32849-0A0A-4CB0-9022-7CD2E9E139E2")] +unsafe trait IXCLRDataModule: IUnknown { + unsafe fn StartEnumAssemblies(&self, handle: *mut u64) -> HRESULT; + unsafe fn EnumAssembly(&self, handle: *mut u64, assembly: *mut *mut c_void) -> HRESULT; + unsafe fn EndEnumAssemblies(&self, handle: u64) -> HRESULT; + unsafe fn StartEnumTypeDefinitions(&self, handle: *mut u64) -> HRESULT; + unsafe fn EnumTypeDefinition( + &self, + handle: *mut u64, + type_definition: *mut *mut c_void, + ) -> HRESULT; + unsafe fn EndEnumTypeDefinitions(&self, handle: u64) -> HRESULT; + unsafe fn StartEnumTypeInstances(&self, app_domain: *mut c_void, handle: *mut u64) -> HRESULT; + unsafe fn EnumTypeInstance(&self, handle: *mut u64, type_instance: *mut *mut c_void) + -> HRESULT; + unsafe fn EndEnumTypeInstances(&self, handle: u64) -> HRESULT; + unsafe fn StartEnumTypeDefinitionsByName( + &self, + name: PCWSTR, + flags: u32, + handle: *mut u64, + ) -> HRESULT; + unsafe fn EnumTypeDefinitionByName( + &self, + handle: *mut u64, + type_definition: *mut *mut c_void, + ) -> HRESULT; + unsafe fn EndEnumTypeDefinitionsByName(&self, handle: u64) -> HRESULT; + unsafe fn StartEnumTypeInstancesByName( + &self, + name: PCWSTR, + flags: u32, + app_domain: *mut c_void, + handle: *mut u64, + ) -> HRESULT; + unsafe fn EnumTypeInstanceByName( + &self, + handle: *mut u64, + type_instance: *mut *mut c_void, + ) -> HRESULT; + unsafe fn EndEnumTypeInstancesByName(&self, handle: u64) -> HRESULT; + unsafe fn GetTypeDefinitionByToken( + &self, + token: u32, + type_definition: *mut *mut c_void, + ) -> HRESULT; + unsafe fn StartEnumMethodDefinitionsByName( + &self, + name: PCWSTR, + flags: u32, + handle: *mut u64, + ) -> HRESULT; + unsafe fn EnumMethodDefinitionByName( + &self, + handle: *mut u64, + method: *mut *mut c_void, + ) -> HRESULT; + unsafe fn EndEnumMethodDefinitionsByName(&self, handle: u64) -> HRESULT; + unsafe fn StartEnumMethodInstancesByName( + &self, + name: PCWSTR, + flags: u32, + app_domain: *mut c_void, + handle: *mut u64, + ) -> HRESULT; + unsafe fn EnumMethodInstanceByName( + &self, + handle: *mut u64, + method: *mut *mut c_void, + ) -> HRESULT; + unsafe fn EndEnumMethodInstancesByName(&self, handle: u64) -> HRESULT; + unsafe fn GetMethodDefinitionByToken(&self, token: u32, method: *mut *mut c_void) -> HRESULT; + unsafe fn StartEnumDataByName( + &self, + name: PCWSTR, + flags: u32, + app_domain: *mut c_void, + task: *mut c_void, + handle: *mut u64, + ) -> HRESULT; + unsafe fn EnumDataByName(&self, handle: *mut u64, value: *mut *mut c_void) -> HRESULT; + unsafe fn EndEnumDataByName(&self, handle: u64) -> HRESULT; + unsafe fn GetName(&self, buffer_length: u32, name_length: *mut u32, name: *mut u16) -> HRESULT; + unsafe fn GetFileName( + &self, + buffer_length: u32, + name_length: *mut u32, + name: *mut u16, + ) -> HRESULT; } -impl Default for NativeMethodInfo { - fn default() -> Self { - // The C ABI consists entirely of integer fields and a fixed UTF-16 buffer. - unsafe { mem::zeroed() } - } +#[windows::core::interface("AAF60008-FB2C-420B-8FB1-42D244A54A97")] +unsafe trait IXCLRDataMethodDefinition: IUnknown { + unsafe fn GetTypeDefinition(&self, type_definition: *mut *mut c_void) -> HRESULT; + unsafe fn StartEnumInstances(&self, app_domain: *mut c_void, handle: *mut u64) -> HRESULT; + unsafe fn EnumInstance(&self, handle: *mut u64, instance: *mut *mut c_void) -> HRESULT; + unsafe fn EndEnumInstances(&self, handle: u64) -> HRESULT; + unsafe fn GetName( + &self, + flags: u32, + buffer_length: u32, + name_length: *mut u32, + name: *mut u16, + ) -> HRESULT; + unsafe fn GetTokenAndScope(&self, token: *mut u32, module: *mut *mut c_void) -> HRESULT; + unsafe fn GetFlags(&self, flags: *mut u32) -> HRESULT; + unsafe fn IsSameObject(&self, method: *mut c_void) -> HRESULT; + unsafe fn GetLatestEnCVersion(&self, version: *mut u32) -> HRESULT; + unsafe fn StartEnumExtents(&self, handle: *mut u64) -> HRESULT; + unsafe fn EnumExtent(&self, handle: *mut u64, extent: *mut c_void) -> HRESULT; + unsafe fn EndEnumExtents(&self, handle: u64) -> HRESULT; + unsafe fn GetCodeNotification(&self, flags: *mut u32) -> HRESULT; + unsafe fn SetCodeNotification(&self, flags: u32) -> HRESULT; + unsafe fn Request( + &self, + request_code: u32, + input_size: u32, + input: *mut u8, + output_size: u32, + output: *mut u8, + ) -> HRESULT; + unsafe fn GetRepresentativeEntryAddress(&self, address: *mut u64) -> HRESULT; } -type CreateBridge = unsafe extern "C" fn( - debug_client: *mut c_void, - coreclr_path: *const u16, - allow_target_writes: u8, - bridge: *mut *mut c_void, - runtime_info: *mut NativeRuntimeInfo, -) -> u32; -type DestroyBridge = unsafe extern "C" fn(bridge: *mut c_void); -type EnableModuleLoadNotifications = unsafe extern "C" fn(bridge: *mut c_void) -> u32; -type DisableModuleLoadNotifications = unsafe extern "C" fn(bridge: *mut c_void) -> u32; -type IsModuleLoaded = unsafe extern "C" fn( - bridge: *mut c_void, - managed_module_path: *const u16, - loaded: *mut u8, -) -> u32; -type ResolveMethod = unsafe extern "C" fn( - bridge: *mut c_void, - managed_module_path: *const u16, - fully_qualified_method: *const u16, - signature_blob: *const u8, - signature_blob_length: u32, - method_info: *mut NativeMethodInfo, -) -> u32; -type RefreshMethodCode = - unsafe extern "C" fn(bridge: *mut c_void, method_info: *mut NativeMethodInfo) -> u32; -type LastError = unsafe extern "C" fn() -> *const u16; +#[windows::core::interface("ECD73800-22CA-4B0D-AB55-E9BA7E6318A5")] +unsafe trait IXCLRDataMethodInstance: IUnknown { + unsafe fn GetTypeInstance(&self, type_instance: *mut *mut c_void) -> HRESULT; + unsafe fn GetDefinition(&self, method: *mut *mut c_void) -> HRESULT; + unsafe fn GetTokenAndScope(&self, token: *mut u32, module: *mut *mut c_void) -> HRESULT; + unsafe fn GetName( + &self, + flags: u32, + buffer_length: u32, + name_length: *mut u32, + name: *mut u16, + ) -> HRESULT; + unsafe fn GetFlags(&self, flags: *mut u32) -> HRESULT; + unsafe fn IsSameObject(&self, method: *mut c_void) -> HRESULT; + unsafe fn GetEnCVersion(&self, version: *mut u32) -> HRESULT; + unsafe fn GetNumTypeArguments(&self, count: *mut u32) -> HRESULT; + unsafe fn GetTypeArgumentByIndex(&self, index: u32, type_instance: *mut *mut c_void) + -> HRESULT; + unsafe fn GetILOffsetsByAddress( + &self, + address: u64, + offset_count: u32, + offsets_needed: *mut u32, + offsets: *mut u32, + ) -> HRESULT; + unsafe fn GetAddressRangesByILOffset( + &self, + offset: u32, + range_count: u32, + ranges_needed: *mut u32, + ranges: *mut c_void, + ) -> HRESULT; + unsafe fn GetILAddressMap( + &self, + map_count: u32, + maps_needed: *mut u32, + maps: *mut c_void, + ) -> HRESULT; + unsafe fn StartEnumExtents(&self, handle: *mut u64) -> HRESULT; + unsafe fn EnumExtent(&self, handle: *mut u64, extent: *mut c_void) -> HRESULT; + unsafe fn EndEnumExtents(&self, handle: u64) -> HRESULT; + unsafe fn Request( + &self, + request_code: u32, + input_size: u32, + input: *mut u8, + output_size: u32, + output: *mut u8, + ) -> HRESULT; + unsafe fn GetRepresentativeEntryAddress(&self, address: *mut u64) -> HRESULT; +} #[derive(Debug, Clone, PartialEq, Eq, Serialize)] pub struct ManagedRuntimeInfo { @@ -134,18 +363,228 @@ pub enum ManagedCodeAvailability { PendingJit, } +#[windows::core::implement(ICLRDataTarget, ICLRDataTarget2)] +struct DbgEngDataTarget { + data_spaces: IDebugDataSpaces4, + symbols: IDebugSymbols5, + system_objects: IDebugSystemObjects, + advanced: IDebugAdvanced3, + allow_target_writes: bool, +} + +impl ICLRDataTarget_Impl for DbgEngDataTarget_Impl { + unsafe fn GetMachineType(&self, machine_type: *mut u32) -> HRESULT { + if machine_type.is_null() { + return E_POINTER; + } + unsafe { *machine_type = IMAGE_FILE_MACHINE_AMD64.0 as u32 }; + HRESULT(0) + } + + unsafe fn GetPointerSize(&self, pointer_size: *mut u32) -> HRESULT { + if pointer_size.is_null() { + return E_POINTER; + } + unsafe { *pointer_size = size_of::() as u32 }; + HRESULT(0) + } + + unsafe fn GetImageBase(&self, image_path: PCWSTR, base_address: *mut u64) -> HRESULT { + if image_path.is_null() || base_address.is_null() { + return E_INVALIDARG; + } + let mut index = 0; + let mut base = 0; + match unsafe { + self.symbols + .GetModuleByModuleNameWide(image_path, 0, Some(&mut index), Some(&mut base)) + } { + Ok(()) => { + unsafe { *base_address = base }; + HRESULT(0) + } + Err(error) => error.code(), + } + } + + unsafe fn ReadVirtual( + &self, + address: u64, + buffer: *mut u8, + bytes_requested: u32, + bytes_read: *mut u32, + ) -> HRESULT { + if buffer.is_null() || bytes_read.is_null() { + return E_POINTER; + } + match unsafe { + self.data_spaces + .ReadVirtual(address, buffer.cast(), bytes_requested, Some(bytes_read)) + } { + Ok(()) => HRESULT(0), + Err(_) if unsafe { *bytes_read } != 0 => HRESULT(0), + Err(error) => error.code(), + } + } + + unsafe fn WriteVirtual( + &self, + address: u64, + buffer: *mut u8, + bytes_requested: u32, + bytes_written: *mut u32, + ) -> HRESULT { + if buffer.is_null() || bytes_written.is_null() { + return E_POINTER; + } + if !self.allow_target_writes { + return E_ACCESSDENIED; + } + match unsafe { + self.data_spaces.WriteVirtual( + address, + buffer.cast(), + bytes_requested, + Some(bytes_written), + ) + } { + Ok(()) => HRESULT(0), + Err(_) if unsafe { *bytes_written } != 0 => HRESULT(0), + Err(error) => error.code(), + } + } + + unsafe fn GetTLSValue(&self, _: u32, _: u32, _: *mut u64) -> HRESULT { + E_NOTIMPL + } + + unsafe fn SetTLSValue(&self, _: u32, _: u32, _: u64) -> HRESULT { + E_ACCESSDENIED + } + + unsafe fn GetCurrentThreadID(&self, thread_id: *mut u32) -> HRESULT { + if thread_id.is_null() { + return E_POINTER; + } + match unsafe { self.system_objects.GetCurrentThreadSystemId() } { + Ok(id) => { + unsafe { *thread_id = id }; + HRESULT(0) + } + Err(error) => error.code(), + } + } + + unsafe fn GetThreadContext( + &self, + thread_id: u32, + context_flags: u32, + context_size: u32, + context: *mut u8, + ) -> HRESULT { + if context.is_null() { + return E_POINTER; + } + let mut current_thread = 0; + let thread_result = unsafe { self.GetCurrentThreadID(&mut current_thread) }; + if thread_result.is_err() { + return thread_result; + } + if thread_id != current_thread || context_size < size_of::() as u32 { + return E_INVALIDARG; + } + let native_context = unsafe { &mut *context.cast::() }; + native_context.ContextFlags = + windows::Win32::System::Diagnostics::Debug::CONTEXT_FLAGS(context_flags); + match unsafe { + self.advanced + .GetThreadContext(native_context as *mut CONTEXT as *mut c_void, context_size) + } { + Ok(()) => HRESULT(0), + Err(error) => error.code(), + } + } + + unsafe fn SetThreadContext(&self, _: u32, _: u32, _: *mut u8) -> HRESULT { + E_ACCESSDENIED + } + + unsafe fn Request(&self, _: u32, _: u32, _: *mut u8, _: u32, _: *mut u8) -> HRESULT { + E_NOTIMPL + } +} + +impl ICLRDataTarget2_Impl for DbgEngDataTarget_Impl { + unsafe fn AllocVirtual( + &self, + address: u64, + size: u32, + type_flags: u32, + protect_flags: u32, + allocation: *mut u64, + ) -> HRESULT { + if allocation.is_null() { + return E_POINTER; + } + if !self.allow_target_writes { + return E_ACCESSDENIED; + } + let handle = match unsafe { self.system_objects.GetCurrentProcessHandle() } { + Ok(handle) => handle, + Err(error) => return error.code(), + }; + let allocation_result = unsafe { + VirtualAllocEx( + windows::Win32::Foundation::HANDLE(handle as *mut c_void), + Some(address as *const c_void), + size as usize, + windows::Win32::System::Memory::VIRTUAL_ALLOCATION_TYPE(type_flags), + windows::Win32::System::Memory::PAGE_PROTECTION_FLAGS(protect_flags), + ) + }; + if allocation_result.is_null() { + return windows::core::Error::from_win32().code(); + } + unsafe { *allocation = allocation_result as u64 }; + HRESULT(0) + } + + unsafe fn FreeVirtual(&self, address: u64, size: u32, type_flags: u32) -> HRESULT { + if !self.allow_target_writes { + return E_ACCESSDENIED; + } + let handle = match unsafe { self.system_objects.GetCurrentProcessHandle() } { + Ok(handle) => handle, + Err(error) => return error.code(), + }; + match unsafe { + VirtualFreeEx( + windows::Win32::Foundation::HANDLE(handle as *mut c_void), + address as *mut c_void, + size as usize, + windows::Win32::System::Memory::VIRTUAL_FREE_TYPE(type_flags), + ) + } { + Ok(()) => HRESULT(0), + Err(error) => error.code(), + } + } +} + pub struct CoreClrDacBridge { - _library: Library, - bridge: NonNull, - destroy: DestroyBridge, - enable_module_load_notifications: EnableModuleLoadNotifications, - disable_module_load_notifications: DisableModuleLoadNotifications, - is_module_loaded: IsModuleLoaded, - resolve_and_notify: ResolveMethod, - resolve_read_only: ResolveMethod, - refresh_method_code: RefreshMethodCode, - last_error: LastError, + process: IXCLRDataProcess, + method: Option, + method_instance: Option, + managed_module_path: Option, + method_token: u32, + method_signature: Vec, + matching_method_count: u32, + matching_method_candidates: Vec, + matching_method_candidates_truncated: bool, runtime_info: ManagedRuntimeInfo, + // COM interfaces must release before the callback object and its DAC module unload. + _target: ICLRDataTarget2, + _dac_module: LoadedModule, } impl CoreClrDacBridge { @@ -154,78 +593,72 @@ impl CoreClrDacBridge { coreclr_path: &Path, allow_target_writes: bool, ) -> anyhow::Result { + ensure!( + size_of::() == size_of::(), + "the CoreCLR DAC bridge supports only x64 debugger hosts" + ); ensure!( coreclr_path.is_file(), "the selected CoreCLR module path does not exist: {}", coreclr_path.display() ); + let dac_path = coreclr_path + .parent() + .map(|parent| parent.join("mscordaccore.dll")) + .context("the CoreCLR module path has no parent directory")?; + ensure!( + dac_path.is_file(), + "an exact CoreCLR sibling mscordaccore.dll was not found" + ); - let library_path = bridge_dll_path()?; - let library = unsafe { Library::new(&library_path) }.with_context(|| { - format!( - "loading the CoreCLR DAC bridge from {}", - library_path.display() - ) - })?; - - let create = unsafe { load_symbol::(&library, b"windbg_dac_create\0")? }; - let destroy = unsafe { load_symbol::(&library, b"windbg_dac_destroy\0")? }; - let enable_module_load_notifications = unsafe { - load_symbol::( - &library, - b"windbg_dac_enable_module_load_notifications\0", - )? - }; - let disable_module_load_notifications = unsafe { - load_symbol::( - &library, - b"windbg_dac_disable_module_load_notifications\0", - )? - }; - let is_module_loaded = - unsafe { load_symbol::(&library, b"windbg_dac_is_module_loaded\0")? }; - let resolve_and_notify = - unsafe { load_symbol::(&library, b"windbg_dac_resolve_and_notify\0")? }; - let resolve_read_only = - unsafe { load_symbol::(&library, b"windbg_dac_resolve_read_only\0")? }; - let refresh_method_code = unsafe { - load_symbol::(&library, b"windbg_dac_refresh_method_code\0")? - }; - let last_error = unsafe { load_symbol::(&library, b"windbg_dac_last_error\0")? }; - - let coreclr_path = to_wide(coreclr_path.as_os_str())?; - let mut raw_bridge = std::ptr::null_mut(); - let mut native_runtime = NativeRuntimeInfo::default(); - let status = unsafe { - create( - session.client.as_raw(), - coreclr_path.as_ptr(), - u8::from(allow_target_writes), - &mut raw_bridge, - &mut native_runtime, - ) - }; - if status != WINDBG_DAC_OK { - bail!( - "initializing the CoreCLR DAC bridge failed: {}", - bridge_error(last_error) - ); + let coreclr_version = file_version(coreclr_path)?; + let dac_version = file_version(&dac_path)?; + ensure!( + coreclr_version == dac_version, + "CoreCLR and mscordaccore.dll file versions do not match exactly" + ); + + let target: ICLRDataTarget2 = DbgEngDataTarget { + data_spaces: session.data_spaces.clone(), + symbols: session.symbols.clone(), + system_objects: session.system_objects.clone(), + advanced: session.client.cast().context("querying IDebugAdvanced3")?, + allow_target_writes, } - let bridge = NonNull::new(raw_bridge) - .context("the CoreCLR DAC bridge reported success without returning a bridge handle")?; + .into(); + let dac_module = LoadedModule::load(&dac_path, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR)?; + let create_instance: ClrDataCreateInstance = unsafe { + let procedure = + GetProcAddress(dac_module.0, windows::core::s!("CLRDataCreateInstance")) + .context("the matching DAC does not export CLRDataCreateInstance")?; + std::mem::transmute(procedure) + }; + let mut raw_process = std::ptr::null_mut(); + unsafe { create_instance(&IXCLRDataProcess::IID, target.as_raw(), &mut raw_process).ok() } + .context("CLRDataCreateInstance failed")?; + let process = ensure_interface::( + raw_process, + "CLRDataCreateInstance reported success without returning IXCLRDataProcess", + )?; Ok(Self { - _library: library, - bridge, - destroy, - enable_module_load_notifications, - disable_module_load_notifications, - is_module_loaded, - resolve_and_notify, - resolve_read_only, - refresh_method_code, - last_error, - runtime_info: managed_runtime_info(&native_runtime)?, + process, + method: None, + method_instance: None, + managed_module_path: None, + method_token: 0, + method_signature: Vec::new(), + matching_method_count: 0, + matching_method_candidates: Vec::new(), + matching_method_candidates_truncated: false, + runtime_info: ManagedRuntimeInfo { + coreclr_path: coreclr_path.to_path_buf(), + dac_path, + coreclr_file_version: coreclr_version, + dac_file_version: dac_version, + }, + _target: target, + _dac_module: dac_module, }) } @@ -234,44 +667,21 @@ impl CoreClrDacBridge { } pub fn enable_module_load_notifications(&self) -> anyhow::Result<()> { - let status = unsafe { (self.enable_module_load_notifications)(self.bridge.as_ptr()) }; - if status != WINDBG_DAC_OK { - bail!( - "requesting CLR managed-module load notifications failed: {}", - bridge_error(self.last_error) - ); + unsafe { + self.process + .SetOtherNotificationFlags(CLRDATA_NOTIFY_ON_MODULE_LOAD) + .ok() } - Ok(()) + .context("requesting CLR managed-module load notifications") } pub fn disable_module_load_notifications(&self) -> anyhow::Result<()> { - let status = unsafe { (self.disable_module_load_notifications)(self.bridge.as_ptr()) }; - if status != WINDBG_DAC_OK { - bail!( - "disabling CLR managed-module load notifications failed: {}", - bridge_error(self.last_error) - ); - } - Ok(()) + unsafe { self.process.SetOtherNotificationFlags(0).ok() } + .context("disabling CLR managed-module load notifications") } pub fn is_module_loaded(&self, managed_module_path: &Path) -> anyhow::Result { - let managed_module_path = to_wide(managed_module_path.as_os_str())?; - let mut loaded = 0u8; - let status = unsafe { - (self.is_module_loaded)( - self.bridge.as_ptr(), - managed_module_path.as_ptr(), - &mut loaded, - ) - }; - if status != WINDBG_DAC_OK { - bail!( - "checking whether the managed module is available through the DAC failed: {}", - bridge_error(self.last_error) - ); - } - Ok(loaded != 0) + Ok(self.find_module_by_path(managed_module_path)?.is_some()) } pub fn resolve_and_notify( @@ -281,11 +691,10 @@ impl CoreClrDacBridge { signature_blob: Option<&[u8]>, ) -> anyhow::Result<(ManagedMethodInfo, ManagedCodeAvailability)> { self.resolve_method( - self.resolve_and_notify, managed_module_path, fully_qualified_method, signature_blob, - "resolving the managed method", + true, ) } @@ -296,222 +705,485 @@ impl CoreClrDacBridge { signature_blob: Option<&[u8]>, ) -> anyhow::Result<(ManagedMethodInfo, ManagedCodeAvailability)> { self.resolve_method( - self.resolve_read_only, managed_module_path, fully_qualified_method, signature_blob, - "resolving the managed method without CLR notification registration", + false, ) } + pub fn refresh_method_code(&mut self) -> anyhow::Result { + let module_path = self + .managed_module_path + .as_deref() + .context("no managed method has been resolved for this bridge")?; + let module = self + .find_module_by_path(module_path)? + .context("the resolved managed module is no longer available through the DAC")?; + let method = unsafe { + let mut raw = std::ptr::null_mut(); + module + .GetMethodDefinitionByToken(self.method_token, &mut raw) + .ok()?; + ensure_interface::( + raw, + "the DAC returned no method definition for the resolved token", + ) + } + .context("reopening the managed method definition after CLR code generation")?; + self.method = Some(method); + self.method_instance = None; + let mut info = self.populate_method_info()?; + info.matching_method_count = self.matching_method_count; + info.matching_method_candidates = self.matching_method_candidates.clone(); + info.matching_method_candidates_truncated = self.matching_method_candidates_truncated; + info.signature_hex = signature_hex(&self.method_signature).0; + if info.representative_entry_address.is_none() { + bail!("the managed method does not have a representative native entry address yet"); + } + Ok(info) + } + fn resolve_method( &mut self, - resolve_method: ResolveMethod, managed_module_path: &Path, fully_qualified_method: &str, signature_blob: Option<&[u8]>, - operation: &str, + request_code_notification: bool, ) -> anyhow::Result<(ManagedMethodInfo, ManagedCodeAvailability)> { - let managed_module_path = to_wide(managed_module_path.as_os_str())?; - let method = to_wide(OsStr::new(fully_qualified_method))?; - let (signature_blob, signature_blob_length) = signature_blob - .map(|signature| { - ensure!( - !signature.is_empty(), - "the CoreCLR DAC bridge signature selector must not be empty" - ); - Ok((signature.as_ptr(), signature.len() as u32)) - }) - .transpose()? - .unwrap_or((std::ptr::null(), 0)); - let mut native_method = NativeMethodInfo::default(); - let status = unsafe { - resolve_method( - self.bridge.as_ptr(), - managed_module_path.as_ptr(), - method.as_ptr(), - signature_blob, - signature_blob_length, - &mut native_method, - ) - }; - self.handle_method_status(status, &native_method, operation)?; - let availability = if native_method.code_available == 0 { - ManagedCodeAvailability::PendingJit - } else { + if let Some(signature) = signature_blob { + ensure!( + !signature.is_empty(), + "the CoreCLR DAC bridge signature selector must not be empty" + ); + } + let module = self + .find_module_by_path(managed_module_path)? + .context("the DbgEng-selected managed module is not present in the matching DAC module enumeration")?; + let metadata = MetadataImport::open(managed_module_path)?; + let method_name = wide(fully_qualified_method)?; + let mut enumeration = 0; + unsafe { + module + .StartEnumMethodDefinitionsByName(PCWSTR(method_name.as_ptr()), 0, &mut enumeration) + .ok() + } + .context("starting managed method definition enumeration")?; + + let result = (|| -> anyhow::Result<_> { + let mut candidates = Vec::new(); + let mut candidates_truncated = false; + let mut selected = None; + let mut matching_count = 0; + loop { + let mut raw = std::ptr::null_mut(); + let status = + unsafe { module.EnumMethodDefinitionByName(&mut enumeration, &mut raw) }; + if status == S_FALSE { + break; + } + status + .ok() + .context("enumerating managed method definitions")?; + let method = ensure_interface::( + raw, + "the DAC returned a null managed method definition", + )?; + let mut token = 0; + unsafe { + method + .GetTokenAndScope(&mut token, std::ptr::null_mut()) + .ok() + } + .context("obtaining a managed method definition token")?; + ensure!(token != 0, "the DAC returned a zero managed method token"); + let signature = metadata.method_signature(token)?; + if candidates.len() < MAX_METHOD_CANDIDATES { + let (signature_hex, signature_truncated) = signature_hex(&signature); + candidates.push(ManagedMethodCandidate { + token, + signature_hex, + signature_truncated, + }); + } else { + candidates_truncated = true; + } + let matches = match signature_blob { + Some(expected) => expected == signature.as_slice(), + None => true, + }; + if matches { + matching_count += 1; + if matching_count == 1 { + selected = Some((method, token, signature)); + } + } + } + Ok((candidates, candidates_truncated, matching_count, selected)) + })(); + unsafe { module.EndEnumMethodDefinitionsByName(enumeration).ok() } + .context("ending managed method definition enumeration")?; + let (candidates, candidates_truncated, matching_count, selected) = result?; + ensure!( + matching_count != 0, + "the requested managed method was not found in the selected module" + ); + if signature_blob.is_none() && matching_count != 1 { + bail!( + "the requested managed method is ambiguous. Supply --signature with the exact ECMA-335 MethodDef signature bytes" + ); + } + if signature_blob.is_some() && matching_count != 1 { + bail!("more than one managed method definition matched the supplied exact metadata signature"); + } + let (method, token, signature) = + selected.context("no managed method matched the supplied signature")?; + self.method = Some(method); + self.method_instance = None; + let mut info = self.populate_method_info()?; + info.matching_method_count = matching_count; + info.matching_method_candidates = candidates.clone(); + info.matching_method_candidates_truncated = candidates_truncated; + info.signature_hex = signature_hex(&signature).0; + self.managed_module_path = Some(managed_module_path.to_path_buf()); + self.method_token = token; + self.method_signature = signature; + self.matching_method_count = matching_count; + self.matching_method_candidates = candidates; + self.matching_method_candidates_truncated = candidates_truncated; + if request_code_notification { + unsafe { + self.method + .as_ref() + .expect("method was assigned") + .SetCodeNotification(info.code_notification_flags) + .ok() + } + .context("requesting CLR code-generation notification")?; + } + let availability = if info.representative_entry_address.is_some() { ManagedCodeAvailability::Available + } else { + ManagedCodeAvailability::PendingJit }; - Ok((managed_method_info(&native_method)?, availability)) - } - - pub fn refresh_method_code(&self) -> anyhow::Result { - let mut native_method = NativeMethodInfo::default(); - let status = - unsafe { (self.refresh_method_code)(self.bridge.as_ptr(), &mut native_method) }; - self.handle_method_status(status, &native_method, "querying generated managed code")?; - managed_method_info(&native_method) + Ok((info, availability)) } - fn handle_method_status( - &self, - status: u32, - native_method: &NativeMethodInfo, - operation: &str, - ) -> anyhow::Result<()> { - match status { - WINDBG_DAC_OK => Ok(()), - WINDBG_DAC_NOT_FOUND => bail!("{operation} failed: {}", bridge_error(self.last_error)), - WINDBG_DAC_AMBIGUOUS => bail!( - "{operation} failed because {} method definitions matched: {}. Supply --signature with an exact ECMA-335 MethodDef signature blob", - native_method.matching_method_count, - method_candidate_summary(native_method) - ), - WINDBG_DAC_CODE_UNAVAILABLE => { - bail!("{operation} failed: {}", bridge_error(self.last_error)) - } - _ => bail!("{operation} failed: {}", bridge_error(self.last_error)), + fn find_module_by_path(&self, expected_path: &Path) -> anyhow::Result> { + unsafe { self.process.Flush().ok() }.context("refreshing the DAC process state")?; + let mut enumeration = 0; + let start_status = unsafe { self.process.StartEnumModules(&mut enumeration) }; + if start_status == S_FALSE { + return Ok(None); } + start_status + .ok() + .context("starting managed module enumeration")?; + let result = (|| -> anyhow::Result<_> { + loop { + let mut raw = std::ptr::null_mut(); + let status = unsafe { self.process.EnumModule(&mut enumeration, &mut raw) }; + if status == S_FALSE { + return Ok(None); + } + status.ok().context("enumerating managed modules")?; + let module = ensure_interface::( + raw, + "the DAC returned a null managed module", + )?; + let mut path = vec![0u16; 32 * 1024]; + let mut path_length = 0; + unsafe { + module + .GetFileName(path.len() as u32, &mut path_length, path.as_mut_ptr()) + .ok() + } + .context("getting a managed module file name")?; + let actual_path = utf16_path(&path)?; + if module_paths_match(expected_path, &actual_path) { + return Ok(Some(module)); + } + } + })(); + unsafe { self.process.EndEnumModules(enumeration).ok() } + .context("ending managed module enumeration")?; + result } -} -impl Drop for CoreClrDacBridge { - fn drop(&mut self) { + fn populate_method_info(&mut self) -> anyhow::Result { + let method = self + .method + .as_ref() + .context("no managed method is selected")?; + let mut name = vec![0u16; MAX_WIDE_CHARS]; + let mut name_length = 0; + unsafe { + method + .GetName(0, name.len() as u32, &mut name_length, name.as_mut_ptr()) + .ok() + } + .context("reading the managed method name")?; + let mut token = 0; unsafe { - (self.destroy)(self.bridge.as_ptr()); + method + .GetTokenAndScope(&mut token, std::ptr::null_mut()) + .ok() } + .context("reading the managed method token")?; + let method_instance = find_method_instance(method)?; + let representative_entry_address = if let Some(instance) = method_instance.as_ref() { + let mut address = 0; + match unsafe { instance.GetRepresentativeEntryAddress(&mut address) } { + status if status == E_UNEXPECTED => None, + status => { + status + .ok() + .context("reading a managed method native entry address")?; + (address != 0).then_some(address) + } + } + } else { + None + }; + self.method_instance = method_instance; + Ok(ManagedMethodInfo { + token, + matching_method_count: 0, + matching_method_candidates: Vec::new(), + matching_method_candidates_truncated: false, + resolved_method: utf16_string(&name, "managed method name")?, + signature_hex: String::new(), + code_notification_flags: CLRDATA_METHNOTIFY_GENERATED | CLRDATA_METHNOTIFY_DISCARDED, + representative_entry_address, + }) } } -fn bridge_dll_path() -> anyhow::Result { - if let Some(path) = env::var_os(BRIDGE_DLL_ENV).map(PathBuf::from) { - ensure!( - path.is_file(), - "{BRIDGE_DLL_ENV} must name the CoreCLR DAC bridge DLL: {}", - path.display() - ); - return Ok(path); +struct LoadedModule(HMODULE); + +impl LoadedModule { + fn load( + path: &Path, + flags: windows::Win32::System::LibraryLoader::LOAD_LIBRARY_FLAGS, + ) -> anyhow::Result { + let path = wide_path(path)?; + let module = unsafe { LoadLibraryExW(PCWSTR(path.as_ptr()), None, flags) } + .context("loading the requested module")?; + Ok(Self(module)) } - let executable_path = env::current_exe().context("locating the windbg-tool executable")?; - let candidate = executable_path - .parent() - .context("the windbg-tool executable has no parent directory")? - .join(BRIDGE_DLL_NAME); - ensure!( - candidate.is_file(), - "the CoreCLR DAC bridge is unavailable at {}. Run `cargo xtask native-build` and stage {} beside windbg-tool.exe, or set {BRIDGE_DLL_ENV}.", - candidate.display(), - BRIDGE_DLL_NAME - ); - Ok(candidate) + fn load_system(name: &str) -> anyhow::Result { + let name = wide(name)?; + let module = + unsafe { LoadLibraryExW(PCWSTR(name.as_ptr()), None, LOAD_LIBRARY_SEARCH_SYSTEM32) } + .context("loading the requested system module")?; + Ok(Self(module)) + } } -unsafe fn load_symbol(library: &Library, symbol: &[u8]) -> anyhow::Result { - Ok(*unsafe { library.get::(symbol) } - .with_context(|| format!("loading bridge export {}", String::from_utf8_lossy(symbol)))?) +impl Drop for LoadedModule { + fn drop(&mut self) { + unsafe { + let _ = FreeLibrary(self.0); + } + } } -fn to_wide(value: impl AsRef) -> anyhow::Result> { - use std::os::windows::ffi::OsStrExt; +type ClrDataCreateInstance = + unsafe extern "system" fn(*const GUID, *mut c_void, *mut *mut c_void) -> HRESULT; +type MetaDataGetDispenser = + unsafe extern "system" fn(*const GUID, *const GUID, *mut *mut c_void) -> HRESULT; - let mut wide = value.as_ref().encode_wide().collect::>(); - ensure!( - !wide.contains(&0), - "the CoreCLR DAC bridge input must not contain an embedded NUL" - ); - wide.push(0); - Ok(wide) +struct MetadataImport { + importer: windows::Win32::System::WinRT::Metadata::IMetaDataImport, + _mscoree: LoadedModule, } -fn bridge_error(last_error: LastError) -> String { - let pointer = unsafe { last_error() }; - if pointer.is_null() { - return "the native bridge did not provide an error message".to_string(); +impl MetadataImport { + fn open(module_path: &Path) -> anyhow::Result { + use windows::Win32::System::WinRT::Metadata::{ + CLSID_CorMetaDataDispenser, IMetaDataDispenser, IMetaDataImport, + }; + + let mscoree = LoadedModule::load_system("mscoree.dll").context("loading mscoree.dll")?; + let get_dispenser: MetaDataGetDispenser = unsafe { + let procedure = GetProcAddress(mscoree.0, windows::core::s!("MetaDataGetDispenser")) + .context("mscoree.dll does not export MetaDataGetDispenser")?; + std::mem::transmute(procedure) + }; + let mut raw_dispenser = std::ptr::null_mut(); + unsafe { + get_dispenser( + &CLSID_CorMetaDataDispenser, + &IMetaDataDispenser::IID, + &mut raw_dispenser, + ) + .ok() + } + .context("creating the metadata dispenser")?; + let dispenser = ensure_interface::( + raw_dispenser, + "MetaDataGetDispenser returned a null metadata dispenser", + )?; + let path = wide_path(module_path)?; + let importer = unsafe { + dispenser + .OpenScope(PCWSTR(path.as_ptr()), 0, &IMetaDataImport::IID) + .and_then(|unknown| unknown.cast()) + } + .context("opening the selected managed module metadata")?; + Ok(Self { + importer, + _mscoree: mscoree, + }) } - let mut length = 0usize; - while length < MAX_WIDE_CHARS { - if unsafe { *pointer.add(length) } == 0 { - break; + fn method_signature(&self, method: u32) -> anyhow::Result> { + let mut signature = std::ptr::null_mut(); + let mut signature_size = 0; + unsafe { + self.importer.GetMethodProps( + method, + std::ptr::null_mut(), + None, + std::ptr::null_mut(), + std::ptr::null_mut(), + &mut signature, + &mut signature_size, + std::ptr::null_mut(), + std::ptr::null_mut(), + ) } - length += 1; + .context("reading a managed MethodDef signature")?; + ensure!( + !signature.is_null() && signature_size != 0, + "the managed MethodDef had no metadata signature" + ); + Ok(unsafe { std::slice::from_raw_parts(signature, signature_size as usize).to_vec() }) } - String::from_utf16_lossy(unsafe { std::slice::from_raw_parts(pointer, length) }) } -fn managed_runtime_info(native: &NativeRuntimeInfo) -> anyhow::Result { - Ok(ManagedRuntimeInfo { - coreclr_path: PathBuf::from(utf16_field(&native.coreclr_path, "CoreCLR path")?), - dac_path: PathBuf::from(utf16_field(&native.dac_path, "DAC path")?), - coreclr_file_version: (native.coreclr_version_ms, native.coreclr_version_ls), - dac_file_version: (native.dac_version_ms, native.dac_version_ls), - }) +fn find_method_instance( + method: &IXCLRDataMethodDefinition, +) -> anyhow::Result> { + let mut enumeration = 0; + let start_status = unsafe { method.StartEnumInstances(std::ptr::null_mut(), &mut enumeration) }; + if start_status == S_FALSE { + return Ok(None); + } + start_status + .ok() + .context("starting managed method instance enumeration")?; + let result = (|| -> anyhow::Result<_> { + let mut raw = std::ptr::null_mut(); + let status = unsafe { method.EnumInstance(&mut enumeration, &mut raw) }; + if status == S_FALSE { + return Ok(None); + } + status + .ok() + .context("enumerating managed method instances")?; + Ok(Some(ensure_interface::( + raw, + "the DAC returned a null managed method instance", + )?)) + })(); + unsafe { method.EndEnumInstances(enumeration).ok() } + .context("ending managed method instance enumeration")?; + result } -fn managed_method_info(native: &NativeMethodInfo) -> anyhow::Result { - Ok(ManagedMethodInfo { - token: native.method_token, - matching_method_count: native.matching_method_count, - matching_method_candidates: native_method_candidates(native)?, - matching_method_candidates_truncated: native.candidates_truncated != 0, - resolved_method: utf16_field(&native.resolved_method, "managed method name")?, - signature_hex: utf16_field( - &native.resolved_signature_hex, - "managed method metadata signature", - )?, - code_notification_flags: native.code_notification_flags, - representative_entry_address: (native.code_available != 0) - .then_some(native.representative_entry_address) - .filter(|address| *address != 0), - }) +fn ensure_interface(raw: *mut c_void, message: &str) -> anyhow::Result { + ensure!(!raw.is_null(), "{message}"); + Ok(unsafe { T::from_raw(raw) }) } -fn native_method_candidates( - native: &NativeMethodInfo, -) -> anyhow::Result> { - let count = native.reported_candidate_count as usize; +fn file_version(path: &Path) -> anyhow::Result<(u32, u32)> { + let path = wide_path(path)?; + let mut ignored = 0; + let size = unsafe { GetFileVersionInfoSizeW(PCWSTR(path.as_ptr()), Some(&mut ignored)) }; + ensure!(size != 0, "reading the file-version resource size failed"); + let mut buffer = vec![0u8; size as usize]; + unsafe { GetFileVersionInfoW(PCWSTR(path.as_ptr()), 0, size, buffer.as_mut_ptr().cast()) } + .context("reading the file-version resource")?; + let root = [b'\\' as u16, 0]; + let mut value = std::ptr::null_mut(); + let mut value_size = 0; ensure!( - count <= MAX_METHOD_CANDIDATES, - "the native bridge reported too many managed method candidates" + unsafe { + VerQueryValueW( + buffer.as_ptr().cast(), + PCWSTR(root.as_ptr()), + &mut value, + &mut value_size, + ) + .as_bool() + }, + "reading the fixed file-version resource failed" ); - native.candidates[..count] - .iter() - .map(|candidate| { - Ok(ManagedMethodCandidate { - token: candidate.method_token, - signature_hex: utf16_field( - &candidate.signature_hex, - "managed method candidate metadata signature", - )?, - signature_truncated: candidate.signature_truncated != 0, - }) - }) - .collect() + ensure!( + !value.is_null() && value_size as usize >= size_of::(), + "the fixed file-version resource was missing or truncated" + ); + let version = unsafe { &*value.cast::() }; + ensure!( + version.dwSignature == VS_FFI_SIGNATURE as u32, + "the fixed file-version resource had an invalid signature" + ); + Ok((version.dwFileVersionMS, version.dwFileVersionLS)) } -fn method_candidate_summary(native: &NativeMethodInfo) -> String { - match native_method_candidates(native) { - Ok(candidates) if candidates.is_empty() => { - "no candidate signatures were returned".to_string() - } - Ok(candidates) => candidates - .into_iter() - .map(|candidate| format!("0x{:08X}:{}", candidate.token, candidate.signature_hex)) - .collect::>() - .join(", "), - Err(error) => format!("candidate signature diagnostics were unavailable: {error}"), - } +fn wide(value: &str) -> anyhow::Result> { + ensure!( + !value.encode_utf16().any(|character| character == 0), + "CoreCLR DAC input must not contain an embedded NUL" + ); + Ok(value.encode_utf16().chain(std::iter::once(0)).collect()) +} + +fn wide_path(path: &Path) -> anyhow::Result> { + let mut value = path.as_os_str().encode_wide().collect::>(); + ensure!( + !value.contains(&0), + "CoreCLR DAC paths must not contain an embedded NUL" + ); + value.push(0); + Ok(value) } -fn utf16_field(value: &[u16], name: &str) -> anyhow::Result { +fn utf16_string(value: &[u16], name: &str) -> anyhow::Result { let length = value .iter() .position(|character| *character == 0) - .context(format!( - "{name} from the native bridge was not NUL terminated" - ))?; - String::from_utf16(&value[..length]) - .with_context(|| format!("{name} from the native bridge was invalid UTF-16")) + .with_context(|| format!("{name} was not NUL terminated"))?; + String::from_utf16(&value[..length]).with_context(|| format!("{name} was invalid UTF-16")) +} + +fn utf16_path(value: &[u16]) -> anyhow::Result { + Ok(PathBuf::from(utf16_string(value, "managed module path")?)) +} + +fn module_paths_match(expected: &Path, actual: &Path) -> bool { + let expected = expected.to_string_lossy(); + let actual = actual.to_string_lossy(); + expected.eq_ignore_ascii_case(&actual) + || expected + .rsplit(['\\', '/']) + .next() + .zip(actual.rsplit(['\\', '/']).next()) + .is_some_and(|(expected, actual)| expected.eq_ignore_ascii_case(actual)) +} + +fn signature_hex(signature: &[u8]) -> (String, bool) { + let maximum_bytes = (MAX_SIGNATURE_HEX_CHARS - 1) / 2; + let copied = signature.len().min(maximum_bytes); + ( + signature[..copied] + .iter() + .map(|byte| format!("{byte:02X}")) + .collect(), + copied != signature.len(), + ) } #[cfg(test)] @@ -520,14 +1192,41 @@ mod tests { #[test] fn reads_nul_terminated_utf16() { - let mut value = [0u16; 8]; - value[..4].copy_from_slice(&['t' as u16, 'e' as u16, 's' as u16, 't' as u16]); - - assert_eq!(utf16_field(&value, "test").unwrap(), "test"); + assert_eq!( + utf16_string(&['t' as u16, 'e' as u16, 's' as u16, 't' as u16, 0], "test").unwrap(), + "test" + ); } #[test] fn rejects_unterminated_utf16() { - assert!(utf16_field(&[1u16; 4], "test").is_err()); + assert!(utf16_string(&[1u16; 4], "test").is_err()); + } + + #[test] + fn truncates_signature_hex_at_abi_limit() { + let (hex, truncated) = signature_hex(&vec![0xAB; MAX_SIGNATURE_HEX_CHARS]); + assert_eq!(hex.len(), MAX_SIGNATURE_HEX_CHARS - 2); + assert!(truncated); + } + + #[test] + fn coreclr_interface_iids_match_the_pinned_idl() { + assert_eq!( + IXCLRDataProcess::IID, + GUID::from_u128(0x5c552ab6_fc09_4cb3_8e36_22fa03c798b7) + ); + assert_eq!( + IXCLRDataModule::IID, + GUID::from_u128(0x88e32849_0a0a_4cb0_9022_7cd2e9e139e2) + ); + assert_eq!( + IXCLRDataMethodDefinition::IID, + GUID::from_u128(0xaaf60008_fb2c_420b_8fb1_42d244a54a97) + ); + assert_eq!( + IXCLRDataMethodInstance::IID, + GUID::from_u128(0xecd73800_22ca_4b0d_ab55_e9ba7e6318a5) + ); } } diff --git a/docs/architecture.md b/docs/architecture.md index f3a8926..961bd51 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -29,7 +29,7 @@ or run [scripts/Get-TtdReplayRuntime.ps1](../scripts/Get-TtdReplayRuntime.ps1) d The native replay bridge has a CMake project at [native/ttd-replay-bridge/CMakeLists.txt](../native/ttd-replay-bridge/CMakeLists.txt). `cargo xtask native-build` configures it against the restored `Microsoft.TimeTravelDebugging.Apis` package and emits the bridge under `target/native/ttd-replay-bridge`. Release packaging can pass `--arch amd64` or `--arch arm64` plus `--static-crt` so cross-compiled packages use the matching native bridge and statically link the MSVC runtime. -Live managed breakpoints use a separate [CoreCLR DAC bridge](../native/coreclr-dac-bridge/CMakeLists.txt), never the TTD bridge. It is a narrow C ABI that receives the active DbgEng client, hosts a version- and architecture-matched `mscordaccore.dll`, and implements `ICLRDataTarget` using DbgEng module, memory, thread, and context services. It uses the stable CLR metadata import contract to compare exact raw ECMA-335 `MethodDef` signature bytes when the caller selects an overload, while reporting bounded token/signature diagnostics for name matches. It resolves metadata through a method definition, then obtains the matching method instance after code-generation notification; only that instance supplies the JIT-native breakpoint address. Its opt-in `ICLRDataTarget2` implementation uses DbgEng's active process handle solely for the CLR-requested JIT-notification-table allocation; the CLR then owns that allocation. A separate read-only resolver never calls notification registration and can report an existing native method instance, but cannot observe a module or method first registered/JIT-compiled after the stopped native load event. It exposes only opaque handles and POD output for module/method resolution, CLR notification registration, and representative generated-code addresses. It does not attach `ICorDebug`, load SOS, invoke debugger extension commands, hollow the process, or inject managed code. The x64 bridge is staged separately under `target/native/coreclr-dac-bridge`; the target DAC remains runtime-provided and is not redistributed. +Live managed breakpoints use an in-process Rust CoreCLR DAC host, never the TTD bridge. It loads a version- and architecture-matched `mscordaccore.dll` and implements `ICLRDataTarget2` through typed Rust COM interfaces over DbgEng module, memory, thread, and context services. The CoreCLR COM declarations are the exact prefixes required from the MIT-licensed `clrdata.idl` and `xclrdata.idl` contracts. It uses the Windows metadata bindings to compare exact raw ECMA-335 `MethodDef` signature bytes when the caller selects an overload, while reporting bounded token/signature diagnostics for name matches. It resolves metadata through a method definition, then obtains the matching method instance after code-generation notification; only that instance supplies the JIT-native breakpoint address. Its opt-in `ICLRDataTarget2` implementation uses DbgEng's active process handle solely for the CLR-requested JIT-notification-table allocation; the CLR then owns that allocation. A separate read-only resolver never calls notification registration and can report an existing native method instance, but cannot observe a module or method first registered/JIT-compiled after the stopped native load event. It does not attach `ICorDebug`, load SOS, invoke debugger extension commands, hollow the process, or inject managed code. The target DAC remains runtime-provided and is not redistributed. `live startup-profile` is intentionally outside that DAC path. It owns a short-lived DbgEng session, begins at a create-process event, applies only DbgEng lifecycle event filters, and accumulates host-monotonic wall time while the target is resumed between observed stops. It can complete at a requested image-load boundary or after a bounded interval without a further configured lifecycle stop; the latter is explicitly an observed lifecycle-quiet interval, not a UI-ready or target-quiescence signal. The profiler records bounded process/thread/module/exception event metadata, lifecycle first/last summaries, ranked fully observed inter-event gaps, optional read-only stop context, and opt-in validity-gated per-thread `IDebugAdvanced2` accounting. The fixture verified that DbgEng `KernelTime` and `UserTime` are 100 ns counters; their per-thread millisecond projections remain separate from lifecycle wall-time phases and do not create causal attribution. Its opt-in host-side output callback captures only bounded debuggee categories and restores the preceding callback/mask; opt-in module provenance reads bounded metadata only from DbgEng-observed absolute image paths. Bounded `IDebugSymbols5` module parameters and native symbol-entry regions are also available for observed bases/instruction addresses; symbol-path resolution is host-side I/O, not target behavior. None is target-memory evidence or a timing source. The profiler does not use DbgEng breakpoint APIs, target-memory APIs, CLR notifications, SOS, a DAC, or a second debugging engine. Its phase derivation accepts only explicit observed lifecycle boundaries and labels the result as wall-clock observation rather than CPU attribution or managed execution evidence. The daemon's `target memory-map` is a separate bounded, direct `IDebugDataSpaces4::QueryVirtual` wrapper for stopped live user-mode targets; it reports partial/unavailable coverage explicitly and does not substitute an OS process-memory API. diff --git a/docs/cli.md b/docs/cli.md index e08e2fb..0740e39 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -135,7 +135,7 @@ Processor breakpoints are a constrained CPU resource. [Microsoft's processor-bre `live managed-break` does not load SOS or execute `!bpmd`. It starts with a DbgEng create-process event, stops on CoreCLR and the requested managed module load, dynamically loads the exact x64 `mscordaccore.dll` that is a sibling of the target's loaded `coreclr.dll`, resolves the selected `MethodDef` through the DAC, requests CLR code-generation notification, then creates a DbgEng **code** breakpoint at the DAC-mapped native entry. `managed_breakpoint.hit` is true only when both the DbgEng breakpoint ID and current instruction pointer match that DAC-mapped address. -Build and stage `windbg_coreclr_dac_bridge.dll` with `cargo xtask native-build`; `cargo xtask package` places it beside `windbg-tool.exe`. A development build can set `WINDBG_CORECLR_DAC_BRIDGE_DLL` to the bridge DLL. The target DAC is never bundled: it must exactly match the target CoreCLR architecture and file version. +The DAC host and `ICLRDataTarget2` callback are implemented in-process in Rust. No separate bridge DLL is built, staged, or configured. The target DAC is never bundled: it must exactly match the target CoreCLR architecture and file version. CLR's notification mechanism writes debugger-notification state into the target. The DAC first requests a small JIT-notification table through `ICLRDataTarget2::AllocVirtual`; the bridge obtains the active target process handle from DbgEng and calls `VirtualAllocEx` only for that CLR-owned allocation. DbgEng then applies its normal code-breakpoint byte at the resolved entry. Therefore `--allow-runtime-write` is explicit and should be used only in an approved test VM. Without it the command fails clearly before any target allocation or write; it does not disable endpoint protection, hollow the process, or silently fall back to SOS. @@ -150,8 +150,6 @@ The command starts from create-process and native module-load events. It resolve The safe .NET 10.0.9 x64 fixture established that the native `ManagedBreakpointFixture.dll` load event occurs before the module is visible to the read-only DAC. This invocation therefore correctly returned `managed_breakpoint.binding_state: "module_not_observable_without_runtime_write"` and `hit: false`: ```powershell -$env:WINDBG_CORECLR_DAC_BRIDGE_DLL = ` - (Resolve-Path target\native\coreclr-dac-bridge\bin\Release\windbg_coreclr_dac_bridge.dll) $fixture = Resolve-Path crates\windbg-tool\tests\fixtures\ManagedBreakpointFixture target\debug\windbg-tool.exe --compact live managed-break ` --command-line "`"$fixture\bin\Release\net10.0\win-x64\ManagedBreakpointFixture.exe`"" ` @@ -413,7 +411,6 @@ The approved .NET 10.0.9 x64 test VM used this direct, no-SOS invocation: ```powershell $root = 'C:\Users\Public\windbg-tool-rdm-dac-24dd653' $env:WINDBG_DBGENG_RUNTIME_DIR = "$root\tool" -$env:WINDBG_CORECLR_DAC_BRIDGE_DLL = "$root\tool\windbg_coreclr_dac_bridge.dll" & "$root\tool\windbg-tool.exe" --compact live managed-break ` --command-line "`"$root\rdm\RemoteDesktopManager_x64.exe`" /AutoCloseAfter:60" ` diff --git a/docs/development.md b/docs/development.md index 22a05a9..725af12 100644 --- a/docs/development.md +++ b/docs/development.md @@ -13,7 +13,6 @@ This page keeps the deeper setup and contributor-oriented material out of the ma | `crates\windbg-install` | WinDbg package install/update/launch support | | `xtask` | Developer workflow commands | | `native\ttd-replay-bridge` | C++ bridge to the TTD Replay API | -| `native\coreclr-dac-bridge` | C++ bridge hosting a CoreCLR DAC over an active DbgEng client | | `scripts\Get-TtdReplayRuntime.ps1` | Runtime acquisition helper | | `docs\architecture.md` | Architecture notes and layering details | @@ -45,7 +44,7 @@ cargo xtask native-build - stages DbgEng runtime DLLs into `target\dbgeng-runtime` - downloads `TTDReplay.dll` and `TTDReplayCPU.dll` into `target\ttd-runtime` -`cargo xtask native-build` configures and builds the TTD bridge under `target\native\ttd-replay-bridge` and the x64 CoreCLR DAC bridge under `target\native\coreclr-dac-bridge`. Packaging copies both bridge DLLs beside `windbg-tool.exe`. +`cargo xtask native-build` configures and builds the TTD bridge under `target\native\ttd-replay-bridge`. The CoreCLR DAC host is implemented in the Rust DbgEng crate and does not require a separately staged bridge DLL. For release packaging, use explicit target architecture inputs so the Rust binary, native bridge, and staged debugger runtime DLLs all match: diff --git a/native/coreclr-dac-bridge/CMakeLists.txt b/native/coreclr-dac-bridge/CMakeLists.txt deleted file mode 100644 index fed46ad..0000000 --- a/native/coreclr-dac-bridge/CMakeLists.txt +++ /dev/null @@ -1,40 +0,0 @@ -cmake_minimum_required(VERSION 3.16) - -project(windbg_coreclr_dac_bridge LANGUAGES CXX) - -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) - -if(NOT DEFINED DBGENG_PACKAGE_DIR) - set(DBGENG_PACKAGE_DIR "${CMAKE_CURRENT_LIST_DIR}/../../target/nuget/Microsoft.Debugging.Platform.DbgEng.20260319.1511.0") -endif() - -set(DBGENG_INCLUDE_DIR "${DBGENG_PACKAGE_DIR}/build/native/inc") -if(NOT EXISTS "${DBGENG_INCLUDE_DIR}/dbgeng.h") - message(FATAL_ERROR "DbgEng headers were not found under ${DBGENG_INCLUDE_DIR}") -endif() - -add_library(windbg_coreclr_dac_bridge SHARED windbg_coreclr_dac_bridge.cpp) - -target_compile_definitions(windbg_coreclr_dac_bridge - PRIVATE - NOMINMAX - WIN32_LEAN_AND_MEAN -) - -target_include_directories(windbg_coreclr_dac_bridge - PRIVATE - "${DBGENG_INCLUDE_DIR}" -) - -target_link_libraries(windbg_coreclr_dac_bridge - PRIVATE - version -) - -set_target_properties(windbg_coreclr_dac_bridge PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" - ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" -) diff --git a/native/coreclr-dac-bridge/windbg_coreclr_dac_bridge.cpp b/native/coreclr-dac-bridge/windbg_coreclr_dac_bridge.cpp deleted file mode 100644 index 9bfa065..0000000 --- a/native/coreclr-dac-bridge/windbg_coreclr_dac_bridge.cpp +++ /dev/null @@ -1,1628 +0,0 @@ -// This narrow ABI declaration is derived from the MIT-licensed CLR data contracts in -// dotnet/diagnostics (clrdata.idl and xclrdata.idl). Keep it isolated from Rust: these -// interfaces are version-sensitive runtime/DAC contracts rather than a stable Rust ABI. -#include "windbg_coreclr_dac_bridge.h" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -using CLRDATA_ADDRESS = ULONG64; -using CLRDATA_ENUM = ULONG64; -using HCORENUM = void*; -using mdToken = ULONG32; -using mdTypeDef = ULONG32; -using mdTypeRef = ULONG32; -using mdInterfaceImpl = ULONG32; -using mdFieldDef = ULONG32; -using mdMethodDef = ULONG32; -using mdParamDef = ULONG32; -using mdMemberRef = ULONG32; -using mdPermission = ULONG32; -using PCCOR_SIGNATURE = const BYTE*; - -static const CLSID CLSID_CorMetaDataDispenser = - {0xe5cb7a31, 0x7512, 0x11d2, {0x89, 0xce, 0x00, 0x80, 0xc7, 0x92, 0xe5, 0xd8}}; -static const IID IID_IMetaDataDispenser = - {0x809c652e, 0x7396, 0x11d2, {0x97, 0x71, 0x00, 0xa0, 0xc9, 0xb4, 0xd5, 0x0c}}; -static const IID IID_IMetaDataImport = - {0x7dac8207, 0xd3ae, 0x4c75, {0x9b, 0x67, 0x92, 0x80, 0x1a, 0x49, 0x7d, 0x44}}; - -// This prefix follows the stable, MIT-licensed CoreCLR metadata contract in cor.h. -// Only methods through GetMethodProps are declared because the bridge never mutates metadata. -struct IMetaDataDispenser : IUnknown { - virtual HRESULT STDMETHODCALLTYPE DefineScope( - REFCLSID rclsid, - DWORD create_flags, - REFIID iid, - IUnknown** scope) = 0; - virtual HRESULT STDMETHODCALLTYPE OpenScope( - LPCWSTR path, - DWORD open_flags, - REFIID iid, - IUnknown** scope) = 0; - virtual HRESULT STDMETHODCALLTYPE OpenScopeOnMemory( - LPCVOID data, - ULONG size, - DWORD open_flags, - REFIID iid, - IUnknown** scope) = 0; -}; - -struct IMetaDataImport : IUnknown { - virtual void STDMETHODCALLTYPE CloseEnum(HCORENUM enumeration) = 0; - virtual HRESULT STDMETHODCALLTYPE CountEnum(HCORENUM enumeration, ULONG* count) = 0; - virtual HRESULT STDMETHODCALLTYPE ResetEnum(HCORENUM enumeration, ULONG position) = 0; - virtual HRESULT STDMETHODCALLTYPE EnumTypeDefs( - HCORENUM* enumeration, - mdTypeDef types[], - ULONG maximum, - ULONG* count) = 0; - virtual HRESULT STDMETHODCALLTYPE EnumInterfaceImpls( - HCORENUM* enumeration, - mdTypeDef type, - mdInterfaceImpl implementations[], - ULONG maximum, - ULONG* count) = 0; - virtual HRESULT STDMETHODCALLTYPE EnumTypeRefs( - HCORENUM* enumeration, - mdTypeRef references[], - ULONG maximum, - ULONG* count) = 0; - virtual HRESULT STDMETHODCALLTYPE FindTypeDefByName( - LPCWSTR type_name, - mdToken enclosing_type, - mdTypeDef* type) = 0; - virtual HRESULT STDMETHODCALLTYPE GetScopeProps( - LPWSTR name, - ULONG name_capacity, - ULONG* name_length, - GUID* mvid) = 0; - virtual HRESULT STDMETHODCALLTYPE GetModuleFromScope(mdToken* module) = 0; - virtual HRESULT STDMETHODCALLTYPE GetTypeDefProps( - mdTypeDef type, - LPWSTR name, - ULONG name_capacity, - ULONG* name_length, - DWORD* attributes, - mdToken* extends) = 0; - virtual HRESULT STDMETHODCALLTYPE GetInterfaceImplProps( - mdInterfaceImpl implementation, - mdTypeDef* type, - mdToken* interface_type) = 0; - virtual HRESULT STDMETHODCALLTYPE GetTypeRefProps( - mdTypeRef reference, - mdToken* resolution_scope, - LPWSTR name, - ULONG name_capacity, - ULONG* name_length) = 0; - virtual HRESULT STDMETHODCALLTYPE ResolveTypeRef( - mdTypeRef reference, - REFIID iid, - IUnknown** scope, - mdTypeDef* type) = 0; - virtual HRESULT STDMETHODCALLTYPE EnumMembers( - HCORENUM* enumeration, - mdTypeDef type, - mdToken members[], - ULONG maximum, - ULONG* count) = 0; - virtual HRESULT STDMETHODCALLTYPE EnumMembersWithName( - HCORENUM* enumeration, - mdTypeDef type, - LPCWSTR name, - mdToken members[], - ULONG maximum, - ULONG* count) = 0; - virtual HRESULT STDMETHODCALLTYPE EnumMethods( - HCORENUM* enumeration, - mdTypeDef type, - mdMethodDef methods[], - ULONG maximum, - ULONG* count) = 0; - virtual HRESULT STDMETHODCALLTYPE EnumMethodsWithName( - HCORENUM* enumeration, - mdTypeDef type, - LPCWSTR name, - mdMethodDef methods[], - ULONG maximum, - ULONG* count) = 0; - virtual HRESULT STDMETHODCALLTYPE EnumFields( - HCORENUM* enumeration, - mdTypeDef type, - mdFieldDef fields[], - ULONG maximum, - ULONG* count) = 0; - virtual HRESULT STDMETHODCALLTYPE EnumFieldsWithName( - HCORENUM* enumeration, - mdTypeDef type, - LPCWSTR name, - mdFieldDef fields[], - ULONG maximum, - ULONG* count) = 0; - virtual HRESULT STDMETHODCALLTYPE EnumParams( - HCORENUM* enumeration, - mdMethodDef method, - mdParamDef parameters[], - ULONG maximum, - ULONG* count) = 0; - virtual HRESULT STDMETHODCALLTYPE EnumMemberRefs( - HCORENUM* enumeration, - mdToken parent, - mdMemberRef references[], - ULONG maximum, - ULONG* count) = 0; - virtual HRESULT STDMETHODCALLTYPE EnumMethodImpls( - HCORENUM* enumeration, - mdTypeDef type, - mdToken bodies[], - mdToken declarations[], - ULONG maximum, - ULONG* count) = 0; - virtual HRESULT STDMETHODCALLTYPE EnumPermissionSets( - HCORENUM* enumeration, - mdToken token, - DWORD actions, - mdPermission permissions[], - ULONG maximum, - ULONG* count) = 0; - virtual HRESULT STDMETHODCALLTYPE FindMember( - mdTypeDef type, - LPCWSTR name, - PCCOR_SIGNATURE signature, - ULONG signature_size, - mdToken* member) = 0; - virtual HRESULT STDMETHODCALLTYPE FindMethod( - mdTypeDef type, - LPCWSTR name, - PCCOR_SIGNATURE signature, - ULONG signature_size, - mdMethodDef* method) = 0; - virtual HRESULT STDMETHODCALLTYPE FindField( - mdTypeDef type, - LPCWSTR name, - PCCOR_SIGNATURE signature, - ULONG signature_size, - mdFieldDef* field) = 0; - virtual HRESULT STDMETHODCALLTYPE FindMemberRef( - mdTypeRef type, - LPCWSTR name, - PCCOR_SIGNATURE signature, - ULONG signature_size, - mdMemberRef* reference) = 0; - virtual HRESULT STDMETHODCALLTYPE GetMethodProps( - mdMethodDef method, - mdTypeDef* type, - LPWSTR name, - ULONG name_capacity, - ULONG* name_length, - DWORD* attributes, - PCCOR_SIGNATURE* signature, - ULONG* signature_size, - ULONG* code_rva, - DWORD* implementation_flags) = 0; -}; - -struct CLRDATA_ADDRESS_RANGE { - CLRDATA_ADDRESS start_address; - CLRDATA_ADDRESS end_address; -}; - -struct IXCLRDataProcess; -struct IXCLRDataModule; -struct IXCLRDataMethodDefinition; -struct IXCLRDataTask; -struct IXCLRDataValue; -struct IXCLRDataAppDomain; -struct IXCLRDataAssembly; -struct IXCLRDataTypeDefinition; -struct IXCLRDataTypeInstance; -struct IXCLRDataMethodInstance; -struct IXCLRDataExceptionState; -struct IXCLRDataExceptionNotification; - -static constexpr ULONG32 CLRDATA_METHNOTIFY_GENERATED = 0x00000001; -static constexpr ULONG32 CLRDATA_METHNOTIFY_DISCARDED = 0x00000002; -static constexpr ULONG32 CLRDATA_NOTIFY_ON_MODULE_LOAD = 0x00000001; - -static const IID IID_ICLRDataTarget = - {0x3e11ccee, 0xd08b, 0x43e5, {0xaf, 0x01, 0x32, 0x71, 0x7a, 0x64, 0xda, 0x03}}; -static const IID IID_ICLRDataTarget2 = - {0x6d05fae3, 0x189c, 0x4630, {0xa6, 0xdc, 0x1c, 0x25, 0x1e, 0x1c, 0x01, 0xab}}; -static const IID IID_IXCLRDataProcess = - {0x5c552ab6, 0xfc09, 0x4cb3, {0x8e, 0x36, 0x22, 0xfa, 0x03, 0xc7, 0x98, 0xb7}}; - -struct ICLRDataTarget : IUnknown { - virtual HRESULT STDMETHODCALLTYPE GetMachineType(ULONG32* machine_type) = 0; - virtual HRESULT STDMETHODCALLTYPE GetPointerSize(ULONG32* pointer_size) = 0; - virtual HRESULT STDMETHODCALLTYPE GetImageBase(LPCWSTR image_path, CLRDATA_ADDRESS* base_address) = 0; - virtual HRESULT STDMETHODCALLTYPE ReadVirtual( - CLRDATA_ADDRESS address, - BYTE* buffer, - ULONG32 bytes_requested, - ULONG32* bytes_read) = 0; - virtual HRESULT STDMETHODCALLTYPE WriteVirtual( - CLRDATA_ADDRESS address, - BYTE* buffer, - ULONG32 bytes_requested, - ULONG32* bytes_written) = 0; - virtual HRESULT STDMETHODCALLTYPE GetTLSValue(ULONG32 thread_id, ULONG32 index, CLRDATA_ADDRESS* value) = 0; - virtual HRESULT STDMETHODCALLTYPE SetTLSValue(ULONG32 thread_id, ULONG32 index, CLRDATA_ADDRESS value) = 0; - virtual HRESULT STDMETHODCALLTYPE GetCurrentThreadID(ULONG32* thread_id) = 0; - virtual HRESULT STDMETHODCALLTYPE GetThreadContext( - ULONG32 thread_id, - ULONG32 context_flags, - ULONG32 context_size, - BYTE* context) = 0; - virtual HRESULT STDMETHODCALLTYPE SetThreadContext( - ULONG32 thread_id, - ULONG32 context_size, - BYTE* context) = 0; - virtual HRESULT STDMETHODCALLTYPE Request( - ULONG32 request_code, - ULONG32 input_size, - BYTE* input, - ULONG32 output_size, - BYTE* output) = 0; -}; - -struct ICLRDataTarget2 : ICLRDataTarget { - virtual HRESULT STDMETHODCALLTYPE AllocVirtual( - CLRDATA_ADDRESS address, - ULONG32 size, - ULONG32 type_flags, - ULONG32 protect_flags, - CLRDATA_ADDRESS* allocation) = 0; - virtual HRESULT STDMETHODCALLTYPE FreeVirtual( - CLRDATA_ADDRESS address, - ULONG32 size, - ULONG32 type_flags) = 0; -}; - -// Only the prefix used by this bridge is declared. Method ordering is the ABI. -struct IXCLRDataProcess : IUnknown { - virtual HRESULT STDMETHODCALLTYPE Flush() = 0; - virtual HRESULT STDMETHODCALLTYPE StartEnumTasks(CLRDATA_ENUM* handle) = 0; - virtual HRESULT STDMETHODCALLTYPE EnumTask(CLRDATA_ENUM* handle, IXCLRDataTask** task) = 0; - virtual HRESULT STDMETHODCALLTYPE EndEnumTasks(CLRDATA_ENUM handle) = 0; - virtual HRESULT STDMETHODCALLTYPE GetTaskByOSThreadID(ULONG32 thread_id, IXCLRDataTask** task) = 0; - virtual HRESULT STDMETHODCALLTYPE GetTaskByUniqueID(ULONG64 task_id, IXCLRDataTask** task) = 0; - virtual HRESULT STDMETHODCALLTYPE GetFlags(ULONG32* flags) = 0; - virtual HRESULT STDMETHODCALLTYPE IsSameObject(IXCLRDataProcess* process) = 0; - virtual HRESULT STDMETHODCALLTYPE GetManagedObject(IXCLRDataValue** value) = 0; - virtual HRESULT STDMETHODCALLTYPE GetDesiredExecutionState(ULONG32* state) = 0; - virtual HRESULT STDMETHODCALLTYPE SetDesiredExecutionState(ULONG32 state) = 0; - virtual HRESULT STDMETHODCALLTYPE GetAddressType(CLRDATA_ADDRESS address, ULONG32* type) = 0; - virtual HRESULT STDMETHODCALLTYPE GetRuntimeNameByAddress( - CLRDATA_ADDRESS address, - ULONG32 flags, - ULONG32 buffer_length, - ULONG32* name_length, - WCHAR* name, - CLRDATA_ADDRESS* displacement) = 0; - virtual HRESULT STDMETHODCALLTYPE StartEnumAppDomains(CLRDATA_ENUM* handle) = 0; - virtual HRESULT STDMETHODCALLTYPE EnumAppDomain(CLRDATA_ENUM* handle, IXCLRDataAppDomain** app_domain) = 0; - virtual HRESULT STDMETHODCALLTYPE EndEnumAppDomains(CLRDATA_ENUM handle) = 0; - virtual HRESULT STDMETHODCALLTYPE GetAppDomainByUniqueID(ULONG64 id, IXCLRDataAppDomain** app_domain) = 0; - virtual HRESULT STDMETHODCALLTYPE StartEnumAssemblies(CLRDATA_ENUM* handle) = 0; - virtual HRESULT STDMETHODCALLTYPE EnumAssembly(CLRDATA_ENUM* handle, IXCLRDataAssembly** assembly) = 0; - virtual HRESULT STDMETHODCALLTYPE EndEnumAssemblies(CLRDATA_ENUM handle) = 0; - virtual HRESULT STDMETHODCALLTYPE StartEnumModules(CLRDATA_ENUM* handle) = 0; - virtual HRESULT STDMETHODCALLTYPE EnumModule(CLRDATA_ENUM* handle, IXCLRDataModule** module) = 0; - virtual HRESULT STDMETHODCALLTYPE EndEnumModules(CLRDATA_ENUM handle) = 0; - virtual HRESULT STDMETHODCALLTYPE GetModuleByAddress(CLRDATA_ADDRESS address, IXCLRDataModule** module) = 0; - virtual HRESULT STDMETHODCALLTYPE Reserved24() = 0; - virtual HRESULT STDMETHODCALLTYPE Reserved25() = 0; - virtual HRESULT STDMETHODCALLTYPE Reserved26() = 0; - virtual HRESULT STDMETHODCALLTYPE Reserved27() = 0; - virtual HRESULT STDMETHODCALLTYPE Reserved28() = 0; - virtual HRESULT STDMETHODCALLTYPE Reserved29() = 0; - virtual HRESULT STDMETHODCALLTYPE Reserved30() = 0; - virtual HRESULT STDMETHODCALLTYPE Reserved31() = 0; - virtual HRESULT STDMETHODCALLTYPE Reserved32() = 0; - virtual HRESULT STDMETHODCALLTYPE Reserved33() = 0; - virtual HRESULT STDMETHODCALLTYPE Reserved34() = 0; - virtual HRESULT STDMETHODCALLTYPE Reserved35() = 0; - virtual HRESULT STDMETHODCALLTYPE Reserved36() = 0; - virtual HRESULT STDMETHODCALLTYPE Reserved37() = 0; - virtual HRESULT STDMETHODCALLTYPE GetOtherNotificationFlags(ULONG32* flags) = 0; - virtual HRESULT STDMETHODCALLTYPE SetOtherNotificationFlags(ULONG32 flags) = 0; -}; - -struct IXCLRDataModule : IUnknown { - virtual HRESULT STDMETHODCALLTYPE StartEnumAssemblies(CLRDATA_ENUM* handle) = 0; - virtual HRESULT STDMETHODCALLTYPE EnumAssembly(CLRDATA_ENUM* handle, IXCLRDataAssembly** assembly) = 0; - virtual HRESULT STDMETHODCALLTYPE EndEnumAssemblies(CLRDATA_ENUM handle) = 0; - virtual HRESULT STDMETHODCALLTYPE StartEnumTypeDefinitions(CLRDATA_ENUM* handle) = 0; - virtual HRESULT STDMETHODCALLTYPE EnumTypeDefinition(CLRDATA_ENUM* handle, IXCLRDataTypeDefinition** type) = 0; - virtual HRESULT STDMETHODCALLTYPE EndEnumTypeDefinitions(CLRDATA_ENUM handle) = 0; - virtual HRESULT STDMETHODCALLTYPE StartEnumTypeInstances(IXCLRDataAppDomain* app_domain, CLRDATA_ENUM* handle) = 0; - virtual HRESULT STDMETHODCALLTYPE EnumTypeInstance(CLRDATA_ENUM* handle, IXCLRDataTypeInstance** type) = 0; - virtual HRESULT STDMETHODCALLTYPE EndEnumTypeInstances(CLRDATA_ENUM handle) = 0; - virtual HRESULT STDMETHODCALLTYPE StartEnumTypeDefinitionsByName( - LPCWSTR name, - ULONG32 flags, - CLRDATA_ENUM* handle) = 0; - virtual HRESULT STDMETHODCALLTYPE EnumTypeDefinitionByName( - CLRDATA_ENUM* handle, - IXCLRDataTypeDefinition** type) = 0; - virtual HRESULT STDMETHODCALLTYPE EndEnumTypeDefinitionsByName(CLRDATA_ENUM handle) = 0; - virtual HRESULT STDMETHODCALLTYPE StartEnumTypeInstancesByName( - LPCWSTR name, - ULONG32 flags, - IXCLRDataAppDomain* app_domain, - CLRDATA_ENUM* handle) = 0; - virtual HRESULT STDMETHODCALLTYPE EnumTypeInstanceByName( - CLRDATA_ENUM* handle, - IXCLRDataTypeInstance** type) = 0; - virtual HRESULT STDMETHODCALLTYPE EndEnumTypeInstancesByName(CLRDATA_ENUM handle) = 0; - virtual HRESULT STDMETHODCALLTYPE GetTypeDefinitionByToken(ULONG32 token, IXCLRDataTypeDefinition** type) = 0; - virtual HRESULT STDMETHODCALLTYPE StartEnumMethodDefinitionsByName( - LPCWSTR name, - ULONG32 flags, - CLRDATA_ENUM* handle) = 0; - virtual HRESULT STDMETHODCALLTYPE EnumMethodDefinitionByName( - CLRDATA_ENUM* handle, - IXCLRDataMethodDefinition** method) = 0; - virtual HRESULT STDMETHODCALLTYPE EndEnumMethodDefinitionsByName(CLRDATA_ENUM handle) = 0; - virtual HRESULT STDMETHODCALLTYPE StartEnumMethodInstancesByName( - LPCWSTR name, - ULONG32 flags, - IXCLRDataAppDomain* app_domain, - CLRDATA_ENUM* handle) = 0; - virtual HRESULT STDMETHODCALLTYPE EnumMethodInstanceByName( - CLRDATA_ENUM* handle, - IXCLRDataMethodInstance** method) = 0; - virtual HRESULT STDMETHODCALLTYPE EndEnumMethodInstancesByName(CLRDATA_ENUM handle) = 0; - virtual HRESULT STDMETHODCALLTYPE GetMethodDefinitionByToken( - mdMethodDef token, - IXCLRDataMethodDefinition** method) = 0; - virtual HRESULT STDMETHODCALLTYPE StartEnumDataByName( - LPCWSTR name, - ULONG32 flags, - IXCLRDataAppDomain* app_domain, - IXCLRDataTask* task, - CLRDATA_ENUM* handle) = 0; - virtual HRESULT STDMETHODCALLTYPE EnumDataByName(CLRDATA_ENUM* handle, IXCLRDataValue** value) = 0; - virtual HRESULT STDMETHODCALLTYPE EndEnumDataByName(CLRDATA_ENUM handle) = 0; - virtual HRESULT STDMETHODCALLTYPE GetName(ULONG32 buffer_length, ULONG32* name_length, WCHAR* name) = 0; - virtual HRESULT STDMETHODCALLTYPE GetFileName( - ULONG32 buffer_length, - ULONG32* name_length, - WCHAR* name) = 0; -}; - -struct IXCLRDataMethodDefinition : IUnknown { - virtual HRESULT STDMETHODCALLTYPE GetTypeDefinition(IXCLRDataTypeDefinition** type) = 0; - virtual HRESULT STDMETHODCALLTYPE StartEnumInstances(IXCLRDataAppDomain* app_domain, CLRDATA_ENUM* handle) = 0; - virtual HRESULT STDMETHODCALLTYPE EnumInstance(CLRDATA_ENUM* handle, IXCLRDataMethodInstance** instance) = 0; - virtual HRESULT STDMETHODCALLTYPE EndEnumInstances(CLRDATA_ENUM handle) = 0; - virtual HRESULT STDMETHODCALLTYPE GetName( - ULONG32 flags, - ULONG32 buffer_length, - ULONG32* name_length, - WCHAR* name) = 0; - virtual HRESULT STDMETHODCALLTYPE GetTokenAndScope(mdMethodDef* token, IXCLRDataModule** module) = 0; - virtual HRESULT STDMETHODCALLTYPE GetFlags(ULONG32* flags) = 0; - virtual HRESULT STDMETHODCALLTYPE IsSameObject(IXCLRDataMethodDefinition* method) = 0; - virtual HRESULT STDMETHODCALLTYPE GetLatestEnCVersion(ULONG32* version) = 0; - virtual HRESULT STDMETHODCALLTYPE StartEnumExtents(CLRDATA_ENUM* handle) = 0; - virtual HRESULT STDMETHODCALLTYPE EnumExtent(CLRDATA_ENUM* handle, void* extent) = 0; - virtual HRESULT STDMETHODCALLTYPE EndEnumExtents(CLRDATA_ENUM handle) = 0; - virtual HRESULT STDMETHODCALLTYPE GetCodeNotification(ULONG32* flags) = 0; - virtual HRESULT STDMETHODCALLTYPE SetCodeNotification(ULONG32 flags) = 0; - virtual HRESULT STDMETHODCALLTYPE Request( - ULONG32 request_code, - ULONG32 input_size, - BYTE* input, - ULONG32 output_size, - BYTE* output) = 0; - virtual HRESULT STDMETHODCALLTYPE GetRepresentativeEntryAddress(CLRDATA_ADDRESS* address) = 0; -}; - -struct IXCLRDataMethodInstance : IUnknown { - virtual HRESULT STDMETHODCALLTYPE GetTypeInstance(IXCLRDataTypeInstance** type) = 0; - virtual HRESULT STDMETHODCALLTYPE GetDefinition(IXCLRDataMethodDefinition** method) = 0; - virtual HRESULT STDMETHODCALLTYPE GetTokenAndScope(mdMethodDef* token, IXCLRDataModule** module) = 0; - virtual HRESULT STDMETHODCALLTYPE GetName( - ULONG32 flags, - ULONG32 buffer_length, - ULONG32* name_length, - WCHAR* name) = 0; - virtual HRESULT STDMETHODCALLTYPE GetFlags(ULONG32* flags) = 0; - virtual HRESULT STDMETHODCALLTYPE IsSameObject(IXCLRDataMethodInstance* method) = 0; - virtual HRESULT STDMETHODCALLTYPE GetEnCVersion(ULONG32* version) = 0; - virtual HRESULT STDMETHODCALLTYPE GetNumTypeArguments(ULONG32* argument_count) = 0; - virtual HRESULT STDMETHODCALLTYPE GetTypeArgumentByIndex( - ULONG32 index, - IXCLRDataTypeInstance** type) = 0; - virtual HRESULT STDMETHODCALLTYPE GetILOffsetsByAddress( - CLRDATA_ADDRESS address, - ULONG32 offset_count, - ULONG32* offsets_needed, - ULONG32* offsets) = 0; - virtual HRESULT STDMETHODCALLTYPE GetAddressRangesByILOffset( - ULONG32 il_offset, - ULONG32 range_count, - ULONG32* ranges_needed, - CLRDATA_ADDRESS_RANGE* ranges) = 0; - virtual HRESULT STDMETHODCALLTYPE GetILAddressMap( - ULONG32 map_count, - ULONG32* maps_needed, - void* maps) = 0; - virtual HRESULT STDMETHODCALLTYPE StartEnumExtents(CLRDATA_ENUM* handle) = 0; - virtual HRESULT STDMETHODCALLTYPE EnumExtent( - CLRDATA_ENUM* handle, - CLRDATA_ADDRESS_RANGE* extent) = 0; - virtual HRESULT STDMETHODCALLTYPE EndEnumExtents(CLRDATA_ENUM handle) = 0; - virtual HRESULT STDMETHODCALLTYPE Request( - ULONG32 request_code, - ULONG32 input_size, - BYTE* input, - ULONG32 output_size, - BYTE* output) = 0; - virtual HRESULT STDMETHODCALLTYPE GetRepresentativeEntryAddress(CLRDATA_ADDRESS* address) = 0; -}; - -using CLRDataCreateInstanceFn = - HRESULT(STDAPICALLTYPE*)(REFIID iid, ICLRDataTarget* target, void** interface_pointer); - -template -class ComReference { -public: - ComReference() = default; - explicit ComReference(T* pointer) : pointer_(pointer) {} - ~ComReference() { reset(); } - - ComReference(const ComReference&) = delete; - ComReference& operator=(const ComReference&) = delete; - - T* get() const { return pointer_; } - T** put() { - reset(); - return &pointer_; - } - - T* detach() { - T* pointer = pointer_; - pointer_ = nullptr; - return pointer; - } - - void reset(T* pointer = nullptr) { - if (pointer_ != nullptr) { - pointer_->Release(); - } - pointer_ = pointer; - } - -private: - T* pointer_ = nullptr; -}; - -using MetaDataGetDispenserFn = - HRESULT(STDAPICALLTYPE*)(REFCLSID dispenser_class, REFIID iid, LPVOID* interface_pointer); - -class MetadataImport final { -public: - ~MetadataImport() { - importer_.reset(); - dispenser_.reset(); - if (mscoree_ != nullptr) { - FreeLibrary(mscoree_); - } - } - - static HRESULT Open(const wchar_t* managed_module_path, std::unique_ptr* metadata) { - if (managed_module_path == nullptr || metadata == nullptr) { - return E_INVALIDARG; - } - *metadata = nullptr; - - auto result = std::make_unique(); - result->mscoree_ = LoadLibraryExW( - L"mscoree.dll", - nullptr, - LOAD_LIBRARY_SEARCH_SYSTEM32); - if (result->mscoree_ == nullptr) { - return HRESULT_FROM_WIN32(GetLastError()); - } - const auto get_dispenser = reinterpret_cast( - GetProcAddress(result->mscoree_, "MetaDataGetDispenser")); - if (get_dispenser == nullptr) { - return HRESULT_FROM_WIN32(ERROR_PROC_NOT_FOUND); - } - - HRESULT open_result = get_dispenser( - CLSID_CorMetaDataDispenser, - IID_IMetaDataDispenser, - reinterpret_cast(result->dispenser_.put())); - if (FAILED(open_result)) { - return open_result; - } - open_result = result->dispenser_.get()->OpenScope( - managed_module_path, - 0, - IID_IMetaDataImport, - reinterpret_cast(result->importer_.put())); - if (FAILED(open_result)) { - return open_result; - } - - *metadata = std::move(result); - return S_OK; - } - - HRESULT GetMethodSignature(mdMethodDef method, std::vector* signature) const { - if (signature == nullptr) { - return E_POINTER; - } - signature->clear(); - PCCOR_SIGNATURE signature_data = nullptr; - ULONG signature_length = 0; - const HRESULT result = importer_.get()->GetMethodProps( - method, - nullptr, - nullptr, - 0, - nullptr, - nullptr, - &signature_data, - &signature_length, - nullptr, - nullptr); - if (FAILED(result)) { - return result; - } - if (signature_data == nullptr || signature_length == 0) { - return E_UNEXPECTED; - } - signature->assign(signature_data, signature_data + signature_length); - return S_OK; - } - -private: - HMODULE mscoree_ = nullptr; - ComReference dispenser_; - ComReference importer_; -}; - -thread_local std::wstring g_last_error; - -void set_error(const std::wstring& error) { - g_last_error = error; -} - -std::wstring format_hresult(const wchar_t* operation, HRESULT result) { - wchar_t message[256]{}; - swprintf_s(message, L"%s failed with HRESULT 0x%08X.", operation, static_cast(result)); - return message; -} - -WindbgDacStatus fail(WindbgDacStatus status, const std::wstring& error) { - set_error(error); - return status; -} - -bool read_file_version(const wchar_t* path, uint32_t* version_ms, uint32_t* version_ls) { - DWORD ignored = 0; - const DWORD size = GetFileVersionInfoSizeW(path, &ignored); - if (size == 0) { - return false; - } - - std::vector buffer(size); - if (!GetFileVersionInfoW(path, 0, size, buffer.data())) { - return false; - } - - VS_FIXEDFILEINFO* version = nullptr; - UINT version_size = 0; - if (!VerQueryValueW(buffer.data(), L"\\", reinterpret_cast(&version), &version_size) || - version == nullptr || - version_size < sizeof(*version) || - version->dwSignature != VS_FFI_SIGNATURE) { - return false; - } - - *version_ms = version->dwFileVersionMS; - *version_ls = version->dwFileVersionLS; - return true; -} - -void copy_string(wchar_t* destination, size_t destination_count, const std::wstring& source) { - wcsncpy_s(destination, destination_count, source.c_str(), _TRUNCATE); -} - -void copy_signature_hex( - wchar_t* destination, - size_t destination_count, - const std::vector& signature, - uint8_t* truncated) { - static constexpr wchar_t hexadecimal[] = L"0123456789ABCDEF"; - if (destination == nullptr || destination_count == 0 || truncated == nullptr) { - return; - } - - const size_t maximum_bytes = (destination_count - 1) / 2; - const size_t copied_bytes = std::min(signature.size(), maximum_bytes); - for (size_t index = 0; index < copied_bytes; ++index) { - destination[index * 2] = hexadecimal[signature[index] >> 4]; - destination[index * 2 + 1] = hexadecimal[signature[index] & 0x0f]; - } - destination[copied_bytes * 2] = L'\0'; - *truncated = static_cast(copied_bytes != signature.size()); -} - -void record_method_candidate( - WindbgDacMethodInfo* method_info, - mdMethodDef method_token, - const std::vector& signature) { - if (method_info->reported_candidate_count >= WINDBG_DAC_MAX_METHOD_CANDIDATES) { - method_info->candidates_truncated = 1; - return; - } - - auto* candidate = &method_info->candidates[method_info->reported_candidate_count++]; - candidate->method_token = method_token; - copy_signature_hex( - candidate->signature_hex, - std::size(candidate->signature_hex), - signature, - &candidate->signature_truncated); -} - -void record_resolved_signature(WindbgDacMethodInfo* method_info, const std::vector& signature) { - uint8_t ignored = 0; - copy_signature_hex( - method_info->resolved_signature_hex, - std::size(method_info->resolved_signature_hex), - signature, - &ignored); -} - -std::wstring sibling_dac_path(const wchar_t* coreclr_path) { - std::wstring path(coreclr_path); - const size_t separator = path.find_last_of(L"\\/"); - if (separator == std::wstring::npos) { - return {}; - } - path.resize(separator + 1); - path += L"mscordaccore.dll"; - return path; -} - -class DbgEngDataTarget final : public ICLRDataTarget2 { -public: - DbgEngDataTarget(IDebugClient5* client, bool allow_target_writes) - : client_(client), allow_target_writes_(allow_target_writes) { - client_.get()->AddRef(); - const HRESULT data_spaces_result = - client_.get()->QueryInterface(__uuidof(IDebugDataSpaces4), reinterpret_cast(data_spaces_.put())); - const HRESULT symbols_result = - client_.get()->QueryInterface(__uuidof(IDebugSymbols5), reinterpret_cast(symbols_.put())); - const HRESULT system_objects_result = - client_.get()->QueryInterface(__uuidof(IDebugSystemObjects4), reinterpret_cast(system_objects_.put())); - const HRESULT advanced_result = - client_.get()->QueryInterface(__uuidof(IDebugAdvanced3), reinterpret_cast(advanced_.put())); - if (FAILED(data_spaces_result) || FAILED(symbols_result) || FAILED(system_objects_result) || - FAILED(advanced_result)) { - wchar_t message[512]{}; - swprintf_s( - message, - L"DbgEng interface queries: data spaces=0x%08X, symbols=0x%08X, system objects=0x%08X, advanced=0x%08X.", - static_cast(data_spaces_result), - static_cast(symbols_result), - static_cast(system_objects_result), - static_cast(advanced_result)); - diagnostic_ = message; - } - } - - const std::wstring& diagnostic() const { return diagnostic_; } - - HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void** object) override { - if (object == nullptr) { - return E_POINTER; - } - *object = nullptr; - if (iid == IID_IUnknown || iid == IID_ICLRDataTarget) { - *object = static_cast(this); - AddRef(); - return S_OK; - } - if (iid == IID_ICLRDataTarget2) { - *object = static_cast(this); - AddRef(); - return S_OK; - } - return E_NOINTERFACE; - } - - ULONG STDMETHODCALLTYPE AddRef() override { - return static_cast(InterlockedIncrement(&references_)); - } - - ULONG STDMETHODCALLTYPE Release() override { - const LONG references = InterlockedDecrement(&references_); - if (references == 0) { - delete this; - } - return static_cast(references); - } - - HRESULT STDMETHODCALLTYPE GetMachineType(ULONG32* machine_type) override { - if (machine_type == nullptr) { - diagnostic_ = L"ICLRDataTarget::GetMachineType received a null output pointer."; - return E_POINTER; - } - *machine_type = IMAGE_FILE_MACHINE_AMD64; - diagnostic_ = L"ICLRDataTarget::GetMachineType returned IMAGE_FILE_MACHINE_AMD64."; - return S_OK; - } - - HRESULT STDMETHODCALLTYPE GetPointerSize(ULONG32* pointer_size) override { - if (pointer_size == nullptr) { - diagnostic_ = L"ICLRDataTarget::GetPointerSize received a null output pointer."; - return E_POINTER; - } - *pointer_size = sizeof(uint64_t); - diagnostic_ = L"ICLRDataTarget::GetPointerSize returned 8."; - return S_OK; - } - - HRESULT STDMETHODCALLTYPE GetImageBase(LPCWSTR image_path, CLRDATA_ADDRESS* base_address) override { - if (image_path == nullptr || base_address == nullptr || symbols_.get() == nullptr) { - diagnostic_ = L"ICLRDataTarget::GetImageBase received invalid arguments or has no IDebugSymbols5."; - return E_INVALIDARG; - } - - ULONG index = 0; - ULONG64 base = 0; - HRESULT result = symbols_.get()->GetModuleByModuleNameWide(image_path, 0, &index, &base); - if (FAILED(result)) { - const wchar_t* file_name = wcsrchr(image_path, L'\\'); - file_name = file_name == nullptr ? image_path : file_name + 1; - result = symbols_.get()->GetModuleByModuleNameWide(file_name, 0, &index, &base); - if (FAILED(result)) { - std::wstring module_name(file_name); - const size_t extension = module_name.find_last_of(L'.'); - if (extension != std::wstring::npos) { - module_name.resize(extension); - result = symbols_.get()->GetModuleByModuleNameWide( - module_name.c_str(), - 0, - &index, - &base); - } - } - } - if (SUCCEEDED(result)) { - *base_address = base; - } - wchar_t message[1200]{}; - swprintf_s( - message, - L"ICLRDataTarget::GetImageBase(%s) returned 0x%08X (base=0x%llX).", - image_path, - static_cast(result), - static_cast(base)); - diagnostic_ = message; - return result; - } - - HRESULT STDMETHODCALLTYPE ReadVirtual( - CLRDATA_ADDRESS address, - BYTE* buffer, - ULONG32 bytes_requested, - ULONG32* bytes_read) override { - if (buffer == nullptr || bytes_read == nullptr || data_spaces_.get() == nullptr) { - diagnostic_ = L"ICLRDataTarget::ReadVirtual received invalid arguments or has no IDebugDataSpaces4."; - return E_POINTER; - } - ULONG read = 0; - const HRESULT result = data_spaces_.get()->ReadVirtual(address, buffer, bytes_requested, &read); - *bytes_read = read; - wchar_t message[256]{}; - swprintf_s( - message, - L"ICLRDataTarget::ReadVirtual(0x%llX, %u) returned 0x%08X (%u bytes).", - static_cast(address), - bytes_requested, - static_cast(result), - read); - diagnostic_ = message; - return read != 0 && FAILED(result) ? S_OK : result; - } - - HRESULT STDMETHODCALLTYPE WriteVirtual( - CLRDATA_ADDRESS address, - BYTE* buffer, - ULONG32 bytes_requested, - ULONG32* bytes_written) override { - if (buffer == nullptr || bytes_written == nullptr || data_spaces_.get() == nullptr) { - diagnostic_ = L"ICLRDataTarget::WriteVirtual received invalid arguments or has no IDebugDataSpaces4."; - return E_POINTER; - } - if (!allow_target_writes_) { - diagnostic_ = - L"ICLRDataTarget::WriteVirtual was rejected because runtime writes were not explicitly enabled."; - return E_ACCESSDENIED; - } - - ULONG written = 0; - const HRESULT result = - data_spaces_.get()->WriteVirtual(address, buffer, bytes_requested, &written); - *bytes_written = written; - wchar_t message[256]{}; - swprintf_s( - message, - L"ICLRDataTarget::WriteVirtual(0x%llX, %u) returned 0x%08X (%u bytes).", - static_cast(address), - bytes_requested, - static_cast(result), - written); - diagnostic_ = message; - return written != 0 && FAILED(result) ? S_OK : result; - } - - HRESULT STDMETHODCALLTYPE GetTLSValue(ULONG32, ULONG32, CLRDATA_ADDRESS*) override { - diagnostic_ = L"ICLRDataTarget::GetTLSValue is not implemented."; - return E_NOTIMPL; - } - - HRESULT STDMETHODCALLTYPE SetTLSValue(ULONG32, ULONG32, CLRDATA_ADDRESS) override { - diagnostic_ = L"ICLRDataTarget::SetTLSValue was rejected because this bridge is read-only."; - return E_ACCESSDENIED; - } - - HRESULT STDMETHODCALLTYPE GetCurrentThreadID(ULONG32* thread_id) override { - if (thread_id == nullptr || system_objects_.get() == nullptr) { - diagnostic_ = L"ICLRDataTarget::GetCurrentThreadID received invalid arguments or has no IDebugSystemObjects4."; - return E_POINTER; - } - ULONG native_thread_id = 0; - const HRESULT result = system_objects_.get()->GetCurrentThreadSystemId(&native_thread_id); - *thread_id = native_thread_id; - wchar_t message[256]{}; - swprintf_s( - message, - L"ICLRDataTarget::GetCurrentThreadID returned 0x%08X (%u).", - static_cast(result), - native_thread_id); - diagnostic_ = message; - return result; - } - - HRESULT STDMETHODCALLTYPE GetThreadContext( - ULONG32 thread_id, - ULONG32 context_flags, - ULONG32 context_size, - BYTE* context) override { - if (context == nullptr || advanced_.get() == nullptr) { - diagnostic_ = L"ICLRDataTarget::GetThreadContext received invalid arguments or has no IDebugAdvanced3."; - return E_POINTER; - } - - ULONG32 current_thread = 0; - HRESULT result = GetCurrentThreadID(¤t_thread); - if (FAILED(result)) { - diagnostic_ = format_hresult(L"ICLRDataTarget::GetCurrentThreadID", result); - return result; - } - if (thread_id != current_thread || context_size < sizeof(CONTEXT)) { - diagnostic_ = L"ICLRDataTarget::GetThreadContext only supports the current x64 thread and a full CONTEXT."; - return E_INVALIDARG; - } - - auto* native_context = reinterpret_cast(context); - native_context->ContextFlags = context_flags; - result = advanced_.get()->GetThreadContext(native_context, context_size); - diagnostic_ = format_hresult(L"ICLRDataTarget::GetThreadContext", result); - return result; - } - - HRESULT STDMETHODCALLTYPE SetThreadContext(ULONG32, ULONG32, BYTE*) override { - diagnostic_ = L"ICLRDataTarget::SetThreadContext was rejected because this bridge is read-only."; - return E_ACCESSDENIED; - } - - HRESULT STDMETHODCALLTYPE Request(ULONG32, ULONG32, BYTE*, ULONG32, BYTE*) override { - diagnostic_ = L"ICLRDataTarget::Request is not implemented."; - return E_NOTIMPL; - } - - HRESULT STDMETHODCALLTYPE AllocVirtual( - CLRDATA_ADDRESS address, - ULONG32 size, - ULONG32 type_flags, - ULONG32 protect_flags, - CLRDATA_ADDRESS* allocation) override { - if (allocation == nullptr) { - diagnostic_ = L"ICLRDataTarget2::AllocVirtual received a null allocation output pointer."; - return E_POINTER; - } - if (!allow_target_writes_) { - diagnostic_ = - L"ICLRDataTarget2::AllocVirtual was rejected because runtime writes were not explicitly enabled."; - return E_ACCESSDENIED; - } - - HANDLE process_handle = nullptr; - HRESULT result = current_process_handle(&process_handle); - if (FAILED(result)) { - return result; - } - void* const allocated = - VirtualAllocEx(process_handle, reinterpret_cast(address), size, type_flags, protect_flags); - if (allocated == nullptr) { - const DWORD error = GetLastError(); - result = HRESULT_FROM_WIN32(error); - wchar_t message[320]{}; - swprintf_s( - message, - L"ICLRDataTarget2::AllocVirtual(0x%llX, %u, 0x%X, 0x%X) failed with Win32 error %lu.", - static_cast(address), - size, - type_flags, - protect_flags, - error); - diagnostic_ = message; - return result; - } - *allocation = reinterpret_cast(allocated); - wchar_t message[320]{}; - swprintf_s( - message, - L"ICLRDataTarget2::AllocVirtual(0x%llX, %u, 0x%X, 0x%X) allocated 0x%llX.", - static_cast(address), - size, - type_flags, - protect_flags, - static_cast(*allocation)); - diagnostic_ = message; - return S_OK; - } - - HRESULT STDMETHODCALLTYPE FreeVirtual( - CLRDATA_ADDRESS address, - ULONG32 size, - ULONG32 type_flags) override { - if (!allow_target_writes_) { - diagnostic_ = - L"ICLRDataTarget2::FreeVirtual was rejected because runtime writes were not explicitly enabled."; - return E_ACCESSDENIED; - } - - HANDLE process_handle = nullptr; - HRESULT result = current_process_handle(&process_handle); - if (FAILED(result)) { - return result; - } - if (!VirtualFreeEx(process_handle, reinterpret_cast(address), size, type_flags)) { - const DWORD error = GetLastError(); - result = HRESULT_FROM_WIN32(error); - wchar_t message[256]{}; - swprintf_s( - message, - L"ICLRDataTarget2::FreeVirtual(0x%llX, %u, 0x%X) failed with Win32 error %lu.", - static_cast(address), - size, - type_flags, - error); - diagnostic_ = message; - return result; - } - wchar_t message[256]{}; - swprintf_s( - message, - L"ICLRDataTarget2::FreeVirtual(0x%llX, %u, 0x%X) succeeded.", - static_cast(address), - size, - type_flags); - diagnostic_ = message; - return S_OK; - } - -private: - HRESULT current_process_handle(HANDLE* process_handle) { - if (process_handle == nullptr || system_objects_.get() == nullptr) { - diagnostic_ = - L"ICLRDataTarget2 requires an output process handle and IDebugSystemObjects4."; - return E_POINTER; - } - ULONG64 raw_process_handle = 0; - const HRESULT result = system_objects_.get()->GetCurrentProcessHandle(&raw_process_handle); - if (FAILED(result)) { - diagnostic_ = format_hresult(L"IDebugSystemObjects4::GetCurrentProcessHandle", result); - return result; - } - *process_handle = reinterpret_cast(raw_process_handle); - return S_OK; - } - - ~DbgEngDataTarget() = default; - - LONG references_ = 1; - ComReference client_; - ComReference data_spaces_; - ComReference symbols_; - ComReference system_objects_; - ComReference advanced_; - bool allow_target_writes_; - std::wstring diagnostic_; -}; - -struct WindbgDacBridge { - WindbgDacBridge(IDebugClient5* client, bool allow_target_writes) - : target(new DbgEngDataTarget(client, allow_target_writes)) {} - - ~WindbgDacBridge() { - method_instance.reset(); - method.reset(); - process.reset(); - target->Release(); - if (dac_module != nullptr) { - FreeLibrary(dac_module); - } - } - - DbgEngDataTarget* target; - ComReference process; - ComReference method; - ComReference method_instance; - HMODULE dac_module = nullptr; - std::wstring dac_path; - std::wstring managed_module_path; - mdMethodDef method_token = 0; - std::vector method_signature; - uint32_t matching_method_count = 0; - uint32_t reported_candidate_count = 0; - uint8_t candidates_truncated = 0; - std::array method_candidates{}; -}; - -HRESULT find_method_instance( - IXCLRDataMethodDefinition* method, - ComReference* instance) { - instance->reset(); - CLRDATA_ENUM enumeration = 0; - HRESULT result = method->StartEnumInstances(nullptr, &enumeration); - if (result == S_FALSE || FAILED(result)) { - return result; - } - - ComReference selected; - result = method->EnumInstance(&enumeration, selected.put()); - const HRESULT end_result = method->EndEnumInstances(enumeration); - if (FAILED(result) || result == S_FALSE) { - return result; - } - if (FAILED(end_result)) { - return end_result; - } - instance->reset(selected.detach()); - return S_OK; -} - -HRESULT populate_method_info( - IXCLRDataMethodDefinition* method, - ComReference* method_instance, - WindbgDacMethodInfo* info) { - std::array name{}; - ULONG32 name_length = 0; - if (SUCCEEDED(method->GetName(0, static_cast(name.size()), &name_length, name.data()))) { - copy_string(info->resolved_method, std::size(info->resolved_method), name.data()); - } - - ComReference scope; - method->GetTokenAndScope(&info->method_token, scope.put()); - info->code_notification_flags = CLRDATA_METHNOTIFY_GENERATED | CLRDATA_METHNOTIFY_DISCARDED; - - // Method definitions expose IL extents; only a method instance exposes JIT-native code. - const HRESULT instance_result = find_method_instance(method, method_instance); - if (instance_result == S_FALSE) { - return S_OK; - } - if (FAILED(instance_result)) { - return instance_result; - } - - CLRDATA_ADDRESS entry_address = 0; - const HRESULT entry_result = - method_instance->get()->GetRepresentativeEntryAddress(&entry_address); - if (entry_result == E_UNEXPECTED) { - method_instance->reset(); - return S_OK; - } - if (FAILED(entry_result)) { - return entry_result; - } - if (entry_address != 0) { - info->representative_entry_address = entry_address; - info->code_available = 1; - } - return S_OK; -} - -bool module_paths_match(const std::wstring& expected, const std::wstring& actual) { - if (_wcsicmp(expected.c_str(), actual.c_str()) == 0) { - return true; - } - - const wchar_t* expected_name = wcsrchr(expected.c_str(), L'\\'); - expected_name = expected_name == nullptr ? expected.c_str() : expected_name + 1; - const wchar_t* actual_name = wcsrchr(actual.c_str(), L'\\'); - actual_name = actual_name == nullptr ? actual.c_str() : actual_name + 1; - return _wcsicmp(expected_name, actual_name) == 0; -} - -WindbgDacStatus find_module_by_path( - IXCLRDataProcess* process, - const wchar_t* managed_module_path, - ComReference* module) { - const HRESULT flush_result = process->Flush(); - if (FAILED(flush_result)) { - return fail(WINDBG_DAC_ERROR, format_hresult(L"Refreshing the DAC process state", flush_result)); - } - - CLRDATA_ENUM enumeration = 0; - HRESULT result = process->StartEnumModules(&enumeration); - if (FAILED(result) || result == S_FALSE) { - return fail(WINDBG_DAC_NOT_FOUND, L"The DAC did not enumerate any managed modules."); - } - - const std::wstring expected_path(managed_module_path); - std::vector observed_module_paths; - while (true) { - ComReference candidate; - result = process->EnumModule(&enumeration, candidate.put()); - if (result == S_FALSE) { - break; - } - if (FAILED(result)) { - process->EndEnumModules(enumeration); - return fail(WINDBG_DAC_ERROR, format_hresult(L"Enumerating DAC modules", result)); - } - - std::array file_name{}; - ULONG32 file_name_length = 0; - result = candidate.get()->GetFileName( - static_cast(file_name.size()), - &file_name_length, - file_name.data()); - if (SUCCEEDED(result) && file_name[0] != L'\0' && observed_module_paths.size() < 16) { - observed_module_paths.emplace_back(file_name.data()); - } - if (SUCCEEDED(result) && module_paths_match(expected_path, file_name.data())) { - process->EndEnumModules(enumeration); - module->reset(candidate.detach()); - return WINDBG_DAC_OK; - } - } - process->EndEnumModules(enumeration); - std::wstring observed = L" No module file names were available."; - if (!observed_module_paths.empty()) { - observed = L" Observed modules: "; - for (size_t index = 0; index < observed_module_paths.size(); ++index) { - if (index != 0) { - observed += L"; "; - } - observed += observed_module_paths[index]; - } - observed += L"."; - } - return fail( - WINDBG_DAC_NOT_FOUND, - L"The DbgEng-selected managed module is not present in the matching DAC module enumeration." + - observed); -} - -extern "C" WindbgDacStatus windbg_dac_create( - void* debug_client, - const wchar_t* coreclr_path, - uint8_t allow_target_writes, - WindbgDacBridge** bridge, - WindbgDacRuntimeInfo* runtime_info) { - g_last_error.clear(); - if (debug_client == nullptr || coreclr_path == nullptr || bridge == nullptr || runtime_info == nullptr) { - return fail(WINDBG_DAC_INVALID_ARGUMENT, L"A DbgEng client, CoreCLR path, bridge output, and runtime output are required."); - } - *bridge = nullptr; - memset(runtime_info, 0, sizeof(*runtime_info)); - - if (sizeof(void*) != sizeof(uint64_t)) { - return fail(WINDBG_DAC_ERROR, L"The CoreCLR DAC bridge supports only x64 debugger hosts."); - } - - const std::wstring coreclr(coreclr_path); - const std::wstring dac_path = sibling_dac_path(coreclr_path); - if (dac_path.empty() || GetFileAttributesW(coreclr_path) == INVALID_FILE_ATTRIBUTES || - GetFileAttributesW(dac_path.c_str()) == INVALID_FILE_ATTRIBUTES) { - return fail(WINDBG_DAC_NOT_FOUND, L"An exact CoreCLR sibling mscordaccore.dll was not found."); - } - - if (!read_file_version( - coreclr_path, - &runtime_info->coreclr_version_ms, - &runtime_info->coreclr_version_ls) || - !read_file_version( - dac_path.c_str(), - &runtime_info->dac_version_ms, - &runtime_info->dac_version_ls)) { - return fail(WINDBG_DAC_ERROR, L"The CoreCLR or DAC file version could not be read."); - } - - copy_string(runtime_info->coreclr_path, std::size(runtime_info->coreclr_path), coreclr); - copy_string(runtime_info->dac_path, std::size(runtime_info->dac_path), dac_path); - - if (runtime_info->coreclr_version_ms != runtime_info->dac_version_ms || - runtime_info->coreclr_version_ls != runtime_info->dac_version_ls) { - return fail(WINDBG_DAC_ERROR, L"CoreCLR and mscordaccore.dll file versions do not match exactly."); - } - - auto native_bridge = std::make_unique( - reinterpret_cast(debug_client), - allow_target_writes != 0); - const HMODULE dac_module = LoadLibraryExW( - dac_path.c_str(), - nullptr, - LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32); - if (dac_module == nullptr) { - wchar_t message[256]{}; - swprintf_s(message, L"Loading the matching DAC failed with Win32 error %lu.", GetLastError()); - return fail(WINDBG_DAC_ERROR, message); - } - - const auto create_instance = reinterpret_cast( - GetProcAddress(dac_module, "CLRDataCreateInstance")); - if (create_instance == nullptr) { - FreeLibrary(dac_module); - return fail(WINDBG_DAC_ERROR, L"The matching DAC does not export CLRDataCreateInstance."); - } - - native_bridge->dac_module = dac_module; - const HRESULT result = create_instance( - IID_IXCLRDataProcess, - native_bridge->target, - reinterpret_cast(native_bridge->process.put())); - if (FAILED(result)) { - return fail( - WINDBG_DAC_ERROR, - format_hresult(L"CLRDataCreateInstance", result) + L" DAC callback diagnostics: " + - native_bridge->target->diagnostic()); - } - if (native_bridge->process.get() == nullptr) { - return fail( - WINDBG_DAC_ERROR, - L"CLRDataCreateInstance reported success without returning IXCLRDataProcess."); - } - - native_bridge->dac_path = dac_path; - *bridge = native_bridge.release(); - return WINDBG_DAC_OK; -} - -extern "C" void windbg_dac_destroy(WindbgDacBridge* bridge) { - delete bridge; -} - -extern "C" WindbgDacStatus windbg_dac_enable_module_load_notifications(WindbgDacBridge* bridge) { - g_last_error.clear(); - if (bridge == nullptr) { - return fail(WINDBG_DAC_INVALID_ARGUMENT, L"A bridge is required."); - } - - const HRESULT result = - bridge->process.get()->SetOtherNotificationFlags(CLRDATA_NOTIFY_ON_MODULE_LOAD); - if (FAILED(result)) { - return fail( - WINDBG_DAC_ERROR, - format_hresult(L"Requesting CLR managed-module load notifications", result) + - L" DAC callback diagnostics: " + bridge->target->diagnostic()); - } - return WINDBG_DAC_OK; -} - -extern "C" WindbgDacStatus windbg_dac_disable_module_load_notifications(WindbgDacBridge* bridge) { - g_last_error.clear(); - if (bridge == nullptr) { - return fail(WINDBG_DAC_INVALID_ARGUMENT, L"A bridge is required."); - } - - const HRESULT result = bridge->process.get()->SetOtherNotificationFlags(0); - if (FAILED(result)) { - return fail( - WINDBG_DAC_ERROR, - format_hresult(L"Disabling CLR managed-module load notifications", result) + - L" DAC callback diagnostics: " + bridge->target->diagnostic()); - } - return WINDBG_DAC_OK; -} - -extern "C" WindbgDacStatus windbg_dac_is_module_loaded( - WindbgDacBridge* bridge, - const wchar_t* managed_module_path, - uint8_t* loaded) { - g_last_error.clear(); - if (bridge == nullptr || managed_module_path == nullptr || loaded == nullptr) { - return fail( - WINDBG_DAC_INVALID_ARGUMENT, - L"A bridge, managed module path, and loaded output are required."); - } - - *loaded = 0; - ComReference module; - const WindbgDacStatus status = - find_module_by_path(bridge->process.get(), managed_module_path, &module); - if (status == WINDBG_DAC_NOT_FOUND) { - g_last_error.clear(); - return WINDBG_DAC_OK; - } - if (status != WINDBG_DAC_OK) { - return status; - } - - *loaded = 1; - return WINDBG_DAC_OK; -} - -WindbgDacStatus resolve_managed_method( - WindbgDacBridge* bridge, - const wchar_t* managed_module_path, - const wchar_t* fully_qualified_method, - const uint8_t* signature_blob, - uint32_t signature_blob_length, - WindbgDacMethodInfo* method_info, - bool request_code_notification) { - if (bridge == nullptr || managed_module_path == nullptr || fully_qualified_method == nullptr || - method_info == nullptr) { - return fail( - WINDBG_DAC_INVALID_ARGUMENT, - L"A bridge, managed module path, fully-qualified method name, and method output are required."); - } - memset(method_info, 0, sizeof(*method_info)); - if ((signature_blob == nullptr) != (signature_blob_length == 0)) { - return fail( - WINDBG_DAC_INVALID_ARGUMENT, - L"An exact metadata signature must provide both a non-null byte buffer and a non-zero length."); - } - - ComReference module; - const WindbgDacStatus module_status = - find_module_by_path(bridge->process.get(), managed_module_path, &module); - if (module_status != WINDBG_DAC_OK) { - return module_status; - } - - std::unique_ptr metadata; - const HRESULT metadata_result = MetadataImport::Open(managed_module_path, &metadata); - if (FAILED(metadata_result)) { - return fail( - WINDBG_DAC_ERROR, - format_hresult(L"Opening the selected managed module metadata", metadata_result)); - } - - CLRDATA_ENUM enumeration = 0; - HRESULT result = - module.get()->StartEnumMethodDefinitionsByName(fully_qualified_method, 0, &enumeration); - if (FAILED(result) || result == S_FALSE) { - return fail(WINDBG_DAC_NOT_FOUND, L"The requested managed method was not found in the selected module."); - } - - ComReference selected; - uint32_t count = 0; - uint32_t selected_count = 0; - std::vector selected_signature; - while (true) { - ComReference candidate; - result = module.get()->EnumMethodDefinitionByName(&enumeration, candidate.put()); - if (result == S_FALSE) { - break; - } - if (FAILED(result)) { - module.get()->EndEnumMethodDefinitionsByName(enumeration); - return fail(WINDBG_DAC_ERROR, format_hresult(L"Enumerating managed method definitions", result)); - } - - ++count; - mdMethodDef candidate_token = 0; - ComReference candidate_scope; - result = candidate.get()->GetTokenAndScope(&candidate_token, candidate_scope.put()); - if (FAILED(result) || candidate_token == 0) { - module.get()->EndEnumMethodDefinitionsByName(enumeration); - return fail( - WINDBG_DAC_ERROR, - format_hresult(L"Obtaining a managed method definition token", result)); - } - - std::vector candidate_signature; - result = metadata->GetMethodSignature(candidate_token, &candidate_signature); - if (FAILED(result)) { - module.get()->EndEnumMethodDefinitionsByName(enumeration); - return fail( - WINDBG_DAC_ERROR, - format_hresult(L"Reading a managed MethodDef signature", result)); - } - record_method_candidate(method_info, candidate_token, candidate_signature); - - const bool selected_by_name = signature_blob == nullptr; - const bool selected_by_signature = signature_blob != nullptr && - candidate_signature.size() == signature_blob_length && - std::equal(candidate_signature.begin(), candidate_signature.end(), signature_blob); - if (selected_by_name || selected_by_signature) { - ++selected_count; - if (selected_count == 1) { - selected_signature = std::move(candidate_signature); - selected.reset(candidate.detach()); - } - } - } - module.get()->EndEnumMethodDefinitionsByName(enumeration); - - method_info->matching_method_count = count; - if (count == 0) { - return fail(WINDBG_DAC_NOT_FOUND, L"The requested managed method was not found in the selected module."); - } - if (signature_blob == nullptr && count != 1) { - return fail( - WINDBG_DAC_AMBIGUOUS, - L"The requested managed method is ambiguous. Supply --signature with the exact ECMA-335 MethodDef signature bytes."); - } - if (signature_blob != nullptr && selected_count == 0) { - return fail( - WINDBG_DAC_NOT_FOUND, - L"No managed method definition with the requested name has the supplied exact metadata signature."); - } - if (selected_count != 1) { - return fail( - WINDBG_DAC_AMBIGUOUS, - L"More than one managed method definition matched the supplied exact metadata signature."); - } - - bridge->method.reset(selected.detach()); - bridge->method_instance.reset(); - result = populate_method_info( - bridge->method.get(), - &bridge->method_instance, - method_info); - if (FAILED(result)) { - bridge->method.reset(); - bridge->method_instance.reset(); - return fail( - WINDBG_DAC_ERROR, - format_hresult(L"Resolving a native code instance for the managed method", result)); - } - method_info->matching_method_count = count; - record_resolved_signature(method_info, selected_signature); - bridge->managed_module_path = managed_module_path; - bridge->method_token = method_info->method_token; - bridge->method_signature = std::move(selected_signature); - bridge->matching_method_count = method_info->matching_method_count; - bridge->reported_candidate_count = method_info->reported_candidate_count; - bridge->candidates_truncated = method_info->candidates_truncated; - memcpy( - bridge->method_candidates.data(), - method_info->candidates, - sizeof(method_info->candidates)); - if (!request_code_notification) { - return WINDBG_DAC_OK; - } - result = bridge->method.get()->SetCodeNotification(method_info->code_notification_flags); - if (FAILED(result)) { - bridge->method.reset(); - bridge->method_signature.clear(); - bridge->matching_method_count = 0; - bridge->reported_candidate_count = 0; - bridge->candidates_truncated = 0; - bridge->method_candidates.fill({}); - return fail( - WINDBG_DAC_ERROR, - format_hresult(L"Requesting CLR code-generation notification", result) + - L" DAC callback diagnostics: " + bridge->target->diagnostic()); - } - - return WINDBG_DAC_OK; -} - -extern "C" WindbgDacStatus windbg_dac_resolve_and_notify( - WindbgDacBridge* bridge, - const wchar_t* managed_module_path, - const wchar_t* fully_qualified_method, - const uint8_t* signature_blob, - uint32_t signature_blob_length, - WindbgDacMethodInfo* method_info) { - g_last_error.clear(); - return resolve_managed_method( - bridge, - managed_module_path, - fully_qualified_method, - signature_blob, - signature_blob_length, - method_info, - true); -} - -extern "C" WindbgDacStatus windbg_dac_resolve_read_only( - WindbgDacBridge* bridge, - const wchar_t* managed_module_path, - const wchar_t* fully_qualified_method, - const uint8_t* signature_blob, - uint32_t signature_blob_length, - WindbgDacMethodInfo* method_info) { - g_last_error.clear(); - return resolve_managed_method( - bridge, - managed_module_path, - fully_qualified_method, - signature_blob, - signature_blob_length, - method_info, - false); -} - -extern "C" WindbgDacStatus windbg_dac_refresh_method_code( - WindbgDacBridge* bridge, - WindbgDacMethodInfo* method_info) { - g_last_error.clear(); - if (bridge == nullptr || method_info == nullptr) { - return fail(WINDBG_DAC_INVALID_ARGUMENT, L"A bridge and method output are required."); - } - if (bridge->method.get() == nullptr) { - return fail(WINDBG_DAC_NOT_FOUND, L"No managed method has been resolved for this bridge."); - } - - if (bridge->managed_module_path.empty() || bridge->method_token == 0) { - return fail( - WINDBG_DAC_ERROR, - L"The resolved managed method does not retain a module path and metadata token."); - } - memset(method_info, 0, sizeof(*method_info)); - method_info->matching_method_count = bridge->matching_method_count; - method_info->reported_candidate_count = bridge->reported_candidate_count; - method_info->candidates_truncated = bridge->candidates_truncated; - memcpy( - method_info->candidates, - bridge->method_candidates.data(), - sizeof(method_info->candidates)); - record_resolved_signature(method_info, bridge->method_signature); - ComReference module; - const WindbgDacStatus module_status = - find_module_by_path(bridge->process.get(), bridge->managed_module_path.c_str(), &module); - if (module_status != WINDBG_DAC_OK) { - return module_status; - } - bridge->method_instance.reset(); - bridge->method.reset(); - const HRESULT definition_result = - module.get()->GetMethodDefinitionByToken(bridge->method_token, bridge->method.put()); - if (FAILED(definition_result) || bridge->method.get() == nullptr) { - return fail( - WINDBG_DAC_ERROR, - format_hresult(L"Reopening the managed method definition after CLR code generation", definition_result)); - } - const HRESULT result = populate_method_info( - bridge->method.get(), - &bridge->method_instance, - method_info); - if (FAILED(result)) { - return fail( - WINDBG_DAC_ERROR, - format_hresult(L"Refreshing the managed method native code instance", result)); - } - if (method_info->code_available == 0) { - return fail(WINDBG_DAC_CODE_UNAVAILABLE, L"The managed method does not have a representative native entry address yet."); - } - return WINDBG_DAC_OK; -} - -extern "C" const wchar_t* windbg_dac_last_error(void) { - return g_last_error.c_str(); -} diff --git a/native/coreclr-dac-bridge/windbg_coreclr_dac_bridge.h b/native/coreclr-dac-bridge/windbg_coreclr_dac_bridge.h deleted file mode 100644 index 4b84499..0000000 --- a/native/coreclr-dac-bridge/windbg_coreclr_dac_bridge.h +++ /dev/null @@ -1,110 +0,0 @@ -#pragma once - -#include -#include - -#ifdef _WIN32 -#define WINDBG_DAC_EXPORT __declspec(dllexport) -#else -#define WINDBG_DAC_EXPORT -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct WindbgDacBridge WindbgDacBridge; - -typedef enum WindbgDacStatus { - WINDBG_DAC_OK = 0, - WINDBG_DAC_ERROR = 1, - WINDBG_DAC_INVALID_ARGUMENT = 2, - WINDBG_DAC_NOT_FOUND = 3, - WINDBG_DAC_AMBIGUOUS = 4, - WINDBG_DAC_CODE_UNAVAILABLE = 5, -} WindbgDacStatus; - -typedef struct WindbgDacRuntimeInfo { - wchar_t coreclr_path[1024]; - wchar_t dac_path[1024]; - uint32_t coreclr_version_ms; - uint32_t coreclr_version_ls; - uint32_t dac_version_ms; - uint32_t dac_version_ls; -} WindbgDacRuntimeInfo; - -enum { - WINDBG_DAC_MAX_METHOD_CANDIDATES = 8, - WINDBG_DAC_MAX_SIGNATURE_HEX_CHARS = 1024, -}; - -typedef struct WindbgDacMethodCandidate { - uint32_t method_token; - uint8_t signature_truncated; - uint8_t reserved[3]; - wchar_t signature_hex[WINDBG_DAC_MAX_SIGNATURE_HEX_CHARS]; -} WindbgDacMethodCandidate; - -typedef struct WindbgDacMethodInfo { - uint32_t method_token; - uint32_t matching_method_count; - uint64_t representative_entry_address; - uint32_t code_notification_flags; - uint8_t code_available; - uint8_t reserved[3]; - wchar_t resolved_method[1024]; - wchar_t resolved_signature_hex[WINDBG_DAC_MAX_SIGNATURE_HEX_CHARS]; - uint32_t reported_candidate_count; - uint8_t candidates_truncated; - uint8_t reserved2[3]; - WindbgDacMethodCandidate candidates[WINDBG_DAC_MAX_METHOD_CANDIDATES]; -} WindbgDacMethodInfo; - -// The debug_client parameter is an IDebugClient5 pointer retained by the caller for the bridge lifetime. -WINDBG_DAC_EXPORT WindbgDacStatus windbg_dac_create( - void* debug_client, - const wchar_t* coreclr_path, - uint8_t allow_target_writes, - WindbgDacBridge** bridge, - WindbgDacRuntimeInfo* runtime_info); - -WINDBG_DAC_EXPORT void windbg_dac_destroy(WindbgDacBridge* bridge); - -WINDBG_DAC_EXPORT WindbgDacStatus windbg_dac_enable_module_load_notifications( - WindbgDacBridge* bridge); - -WINDBG_DAC_EXPORT WindbgDacStatus windbg_dac_disable_module_load_notifications( - WindbgDacBridge* bridge); - -WINDBG_DAC_EXPORT WindbgDacStatus windbg_dac_is_module_loaded( - WindbgDacBridge* bridge, - const wchar_t* managed_module_path, - uint8_t* loaded); - -WINDBG_DAC_EXPORT WindbgDacStatus windbg_dac_resolve_and_notify( - WindbgDacBridge* bridge, - const wchar_t* managed_module_path, - const wchar_t* fully_qualified_method, - const uint8_t* signature_blob, - uint32_t signature_blob_length, - WindbgDacMethodInfo* method_info); - -// Resolves metadata and any already-generated native method instance without -// registering CLR notifications or invoking any target-write callback. -WINDBG_DAC_EXPORT WindbgDacStatus windbg_dac_resolve_read_only( - WindbgDacBridge* bridge, - const wchar_t* managed_module_path, - const wchar_t* fully_qualified_method, - const uint8_t* signature_blob, - uint32_t signature_blob_length, - WindbgDacMethodInfo* method_info); - -WINDBG_DAC_EXPORT WindbgDacStatus windbg_dac_refresh_method_code( - WindbgDacBridge* bridge, - WindbgDacMethodInfo* method_info); - -WINDBG_DAC_EXPORT const wchar_t* windbg_dac_last_error(void); - -#ifdef __cplusplus -} -#endif diff --git a/xtask/src/main.rs b/xtask/src/main.rs index db3fd7b..bf6b709 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -9,7 +9,6 @@ const DEBUGGING_PLATFORM_VERSION: &str = "20260319.1511.0"; const DEFAULT_SYMBOL_CACHE: &str = ".ttd-symbol-cache"; const MICROSOFT_SYMBOL_SERVER: &str = "https://msdl.microsoft.com/download/symbols"; const NATIVE_BRIDGE_DLL: &str = "ttd_replay_bridge.dll"; -const CORECLR_DAC_BRIDGE_DLL: &str = "windbg_coreclr_dac_bridge.dll"; const TTD_RUNTIME_FILES: &[&str] = &["TTDReplay.dll", "TTDReplayCPU.dll"]; const DBGENG_RUNTIME_FILES: &[&str] = &[ "dbgeng.dll", @@ -336,7 +335,6 @@ fn native_build(options: &XtaskOptions) -> anyhow::Result<()> { let root = workspace_root()?; let packages_dir = root.join("target/nuget"); let ttd_apis_package = package_dir(&packages_dir, "Microsoft.TimeTravelDebugging.Apis")?; - let dbgeng_package = package_dir(&packages_dir, "Microsoft.Debugging.Platform.DbgEng")?; let source_dir = root.join("native/ttd-replay-bridge"); let build_dir = native_build_dir(&root, options); fs::create_dir_all(&build_dir).context("creating native bridge build directory")?; @@ -370,40 +368,6 @@ fn native_build(options: &XtaskOptions) -> anyhow::Result<()> { .arg("Release")) .context("building native TTD replay bridge")?; - if options.arch == PackageArch::Amd64 { - let dac_source_dir = root.join("native/coreclr-dac-bridge"); - let dac_build_dir = coreclr_dac_bridge_build_dir(&root, options); - fs::create_dir_all(&dac_build_dir) - .context("creating CoreCLR DAC bridge build directory")?; - - let mut dac_configure = Command::new("cmake"); - dac_configure - .arg("-S") - .arg(&dac_source_dir) - .arg("-B") - .arg(&dac_build_dir) - .arg(format!("-DDBGENG_PACKAGE_DIR={}", dbgeng_package.display())) - .env("Platform", options.arch.msvc_platform()); - - if options.static_crt { - dac_configure.arg("-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded$<$:Debug>"); - } - - if cfg!(windows) && cmake_generator_accepts_platform() { - dac_configure.arg("-A").arg(options.arch.msvc_platform()); - } - - run(&mut dac_configure).context("configuring CoreCLR DAC bridge")?; - run(Command::new("cmake") - .arg("--build") - .arg(&dac_build_dir) - .arg("--config") - .arg("Release")) - .context("building CoreCLR DAC bridge")?; - } else { - println!(" skipping CoreCLR DAC bridge: direct managed breakpoints are x64-only"); - } - Ok(()) } @@ -426,12 +390,6 @@ fn package(options: &XtaskOptions) -> anyhow::Result<()> { native_bridge_candidates(&root, options), &package_dir.join(NATIVE_BRIDGE_DLL), )?; - if options.arch == PackageArch::Amd64 { - copy_first_existing( - coreclr_dac_bridge_candidates(&root, options), - &package_dir.join(CORECLR_DAC_BRIDGE_DLL), - )?; - } copy_runtime_files( &ttd_runtime_dir(&root, options), &package_dir, @@ -697,15 +655,6 @@ fn native_build_dir(root: &Path, options: &XtaskOptions) -> PathBuf { } } -fn coreclr_dac_bridge_build_dir(root: &Path, options: &XtaskOptions) -> PathBuf { - if options.explicit_target_layout { - root.join("target/native") - .join(format!("coreclr-dac-bridge-{}", options.arch.nuget_arch())) - } else { - root.join("target/native/coreclr-dac-bridge") - } -} - fn default_package_dir(root: &Path, options: &XtaskOptions) -> PathBuf { if options.explicit_target_layout { root.join("target/package") @@ -742,20 +691,6 @@ fn native_bridge_candidates(root: &Path, options: &XtaskOptions) -> Vec candidates } -fn coreclr_dac_bridge_candidates(root: &Path, options: &XtaskOptions) -> Vec { - let mut candidates = Vec::new(); - if let Some(path) = env::var_os("WINDBG_CORECLR_DAC_BRIDGE_DLL").map(PathBuf::from) { - candidates.push(path); - } - - let build_dir = coreclr_dac_bridge_build_dir(root, options); - candidates.push(build_dir.join("bin/Release").join(CORECLR_DAC_BRIDGE_DLL)); - candidates.push(build_dir.join("bin/Debug").join(CORECLR_DAC_BRIDGE_DLL)); - candidates.push(build_dir.join("Release").join(CORECLR_DAC_BRIDGE_DLL)); - candidates.push(build_dir.join("Debug").join(CORECLR_DAC_BRIDGE_DLL)); - candidates -} - fn copy_runtime_files( source_dir: &Path, package_dir: &Path,