From 5194235b3d8156ec263cd08f84694a2bb96e0d5f Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 10 Jun 2020 08:53:57 -0700 Subject: [PATCH] Initial support for MinGW dynamic libs --- src/symbolize/gimli.rs | 94 ++++++++++++++++++++++++++++++++---------- src/windows.rs | 47 +++++++++++++++++++++ tests/accuracy/main.rs | 4 +- 3 files changed, 120 insertions(+), 25 deletions(-) diff --git a/src/symbolize/gimli.rs b/src/symbolize/gimli.rs index 9f1ed9679..2af3f3deb 100644 --- a/src/symbolize/gimli.rs +++ b/src/symbolize/gimli.rs @@ -88,36 +88,86 @@ fn mmap(path: &Path) -> Option { cfg_if::cfg_if! { if #[cfg(windows)] { - // Windows uses COFF object files and currently doesn't implement - // functionality to load a list of native libraries. This seems to work - // well enough for the main executable but seems pretty likely to not - // work for loaded DLLs. For now this seems sufficient, but we may have - // to extend this over time. - // - // Note that the native_libraries loading here simply returns one - // library encompassing the entire address space. This works naively - // but likely indicates something about ASLR is busted. Let's try to - // fix this over time if necessary! + use core::mem::MaybeUninit; + use crate::windows::*; + use std::os::windows::prelude::*; mod coff; use self::coff::Object; fn native_libraries() -> Vec { let mut ret = Vec::new(); - if let Ok(path) = std::env::current_exe() { - let mut segments = Vec::new(); - segments.push(LibrarySegment { - stated_virtual_memory_address: 0, - len: usize::max_value(), - }); - ret.push(Library { - name: path.into(), - segments, - bias: 0, - }); - } + unsafe { add_loaded_images(&mut ret); } return ret; } + + unsafe fn add_loaded_images(ret: &mut Vec) { + let mut system_info = MaybeUninit::zeroed().assume_init(); + GetSystemInfo(&mut system_info); + + let mut memory_basic_info = MaybeUninit::zeroed().assume_init(); + let query_size = mem::size_of_val(&memory_basic_info); + + let start = system_info.lpMinimumApplicationAddress; + let end = system_info.lpMaximumApplicationAddress; + let mut base = start; + while base < end { + if VirtualQuery(base, &mut memory_basic_info, query_size) == 0 { + break; + } + if !base.is_null() { + let alloc_base = memory_basic_info.AllocationBase; + + // If this `alloc_base` is the same as our base address we + // queried, then we found a new library. + if alloc_base == base { + if let Some(lib) = get_library(base, &memory_basic_info) { + ret.push(lib); + } + + // ... otherwise see if the allocation base is the same as + // the preivous library, and if so we just discovered a new + // segment for the previous library. + } else if let Some(last) = ret.last_mut() { + if last.segments[0].stated_virtual_memory_address == alloc_base as usize { + last.segments.push(LibrarySegment { + stated_virtual_memory_address: base as usize, + len: memory_basic_info.RegionSize, + }); + } + } + } + + base = ((base as usize) + memory_basic_info.RegionSize) as LPVOID; + } + } + + unsafe fn get_library(base: LPVOID, info: &MEMORY_BASIC_INFORMATION) -> Option { + let mut buf = Vec::with_capacity(128); + let module = base as HMODULE; + loop { + let len = GetModuleFileNameW(module, buf.as_mut_ptr(), buf.capacity() as u32); + if len == 0 { + return None; + } + if len as usize == buf.capacity() && GetLastError() == ERROR_INSUFFICIENT_BUFFER { + let new_cap = buf.capacity() * 2; + buf.reserve(new_cap); + } else { + buf.set_len(len as usize); + break; + } + } + let filename = OsString::from_wide(&buf[..]); + Some(Library { + name: filename.into(), + segments: vec![LibrarySegment { + stated_virtual_memory_address: base as usize, + len: info.RegionSize, + }], + bias: 0, + }) + } } else if #[cfg(target_os = "macos")] { // macOS uses the Mach-O file format and uses DYLD-specific APIs to // load a list of native libraries that are part of the appplication. diff --git a/src/windows.rs b/src/windows.rs index 17e90dafc..cee1cb58b 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -311,6 +311,37 @@ ffi! { pub Reserved1: [DWORD64; 4], } + #[repr(C)] + pub struct SYSTEM_INFO { + pub u: SYSTEM_INFO_u, + pub dwPageSize: DWORD, + pub lpMinimumApplicationAddress: LPVOID, + pub lpMaximumApplicationAddress: LPVOID, + pub dwActiveProcessorMask: *mut DWORD, + pub dwNumberOfProcessors: DWORD, + pub dwProcessorType: DWORD, + pub dwAllocationGranularity: DWORD, + pub wProcessorLevel: WORD, + pub wProcessorRevision: WORD, + } + + #[repr(C)] + pub struct SYSTEM_INFO_u { + pub dwOemId: DWORD, + } + + #[repr(C)] + pub struct MEMORY_BASIC_INFORMATION { + pub BaseAddress: PVOID, + pub AllocationBase: PVOID, + pub AllocationProtect: DWORD, + pub PartitionId: WORD, + pub RegionSize: SIZE_T, + pub State: DWORD, + pub Protect: DWORD, + pub Type: DWORD, + } + pub const MAX_SYM_NAME: usize = 2000; pub const AddrModeFlat: ADDRESS_MODE = 3; pub const TRUE: BOOL = 1; @@ -327,6 +358,7 @@ ffi! { pub const INFINITE: DWORD = !0; pub const PAGE_READONLY: DWORD = 2; pub const FILE_MAP_READ: DWORD = 4; + pub const ERROR_INSUFFICIENT_BUFFER: DWORD = 122; pub type DWORD = u32; pub type PDWORD = *mut u32; @@ -339,6 +371,7 @@ ffi! { pub type LPSTR = *mut i8; pub type LPCSTR = *const i8; pub type PWSTR = *mut u16; + pub type LPWSTR = *mut u16; pub type WORD = u16; pub type ULONG = u32; pub type ULONG64 = u64; @@ -350,6 +383,8 @@ ffi! { pub type SIZE_T = usize; pub type LPVOID = *mut c_void; pub type LPCVOID = *const c_void; + pub type LPSYSTEM_INFO = *mut SYSTEM_INFO; + pub type PMEMORY_BASIC_INFORMATION = *mut MEMORY_BASIC_INFORMATION; extern "system" { pub fn GetCurrentProcess() -> HANDLE; @@ -401,6 +436,18 @@ ffi! { dwNumberOfBytesToMap: SIZE_T, ) -> LPVOID; pub fn UnmapViewOfFile(lpBaseAddress: LPCVOID) -> BOOL; + pub fn GetSystemInfo(info: LPSYSTEM_INFO) -> (); + pub fn VirtualQuery( + lpAddress: LPCVOID, + lpBuffer: PMEMORY_BASIC_INFORMATION, + dwLength: SIZE_T, + ) -> SIZE_T; + pub fn GetModuleFileNameW( + hModule: HMODULE, + lpFilename: LPWSTR, + nSize: DWORD, + ) -> DWORD; + pub fn GetLastError() -> DWORD; } } diff --git a/tests/accuracy/main.rs b/tests/accuracy/main.rs index 56365b11d..3d2066c4a 100644 --- a/tests/accuracy/main.rs +++ b/tests/accuracy/main.rs @@ -18,9 +18,7 @@ type Pos = (&'static str, u32); fn doit() { // Skip musl which is by default statically linked and doesn't support // dynamic libraries. - // - // FIXME(#333) doesn't work on MinGW yet - if !cfg!(target_env = "musl") && !(cfg!(windows) && cfg!(target_env = "gnu")) { + if !cfg!(target_env = "musl") { // TODO(#238) this shouldn't have to happen first in this function, but // currently it does. let mut dir = std::env::current_exe().unwrap();