diff --git a/tools/swift-inspect/Package.swift b/tools/swift-inspect/Package.swift index 5d86f737045bd..8f5e28757e160 100644 --- a/tools/swift-inspect/Package.swift +++ b/tools/swift-inspect/Package.swift @@ -1,10 +1,13 @@ -// swift-tools-version:5.2 +// swift-tools-version:5.3 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "swift-inspect", + products: [ + .library(name: "SwiftInspectClient", type: .dynamic, targets: ["SwiftInspectClient"]), + ], dependencies: [ .package(url: "https://github.com/apple/swift-argument-parser", from: "0.0.1"), ], @@ -16,12 +19,18 @@ let package = Package( dependencies: [ "SymbolicationShims", .product(name: "ArgumentParser", package: "swift-argument-parser"), + .target(name: "SwiftInspectClient", condition: .when(platforms: [.windows])), + .target(name: "SwiftInspectClientInterface", condition: .when(platforms: [.windows])), ], swiftSettings: [ .unsafeFlags([ "-parse-as-library", ]), ]), + .target( + name: "SwiftInspectClient"), + .systemLibrary( + name: "SwiftInspectClientInterface"), .testTarget( name: "swiftInspectTests", dependencies: ["swift-inspect"]), diff --git a/tools/swift-inspect/Sources/SwiftInspectClient/SwiftInspectClient.cpp b/tools/swift-inspect/Sources/SwiftInspectClient/SwiftInspectClient.cpp new file mode 100644 index 0000000000000..78492a6e00bee --- /dev/null +++ b/tools/swift-inspect/Sources/SwiftInspectClient/SwiftInspectClient.cpp @@ -0,0 +1,222 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#if defined(_WIN32) + +#pragma comment(lib, "swiftCore.lib") + +#include "../SwiftInspectClientInterface/SwiftInspectClientInterface.h" +#include +#include +#include +#include +#include +#include + +namespace { + +struct ScopedHandle { + HANDLE Handle; + explicit ScopedHandle(HANDLE Handle) noexcept : Handle(Handle) {} + ~ScopedHandle() noexcept { + if (Handle != NULL && Handle != INVALID_HANDLE_VALUE) { + CloseHandle(Handle); + } + } + operator HANDLE() const { return Handle; } +}; + +struct ScopedViewOfFile { + void *View; + explicit ScopedViewOfFile(void *View) noexcept : View(View) {} + ~ScopedViewOfFile() noexcept { + if (View != NULL) { + UnmapViewOfFile(View); + } + } + void *get() const { return View; } + template T *as() const { return reinterpret_cast(View); } +}; + +struct ScopedHeapLock { + HANDLE Heap; + bool Failure = false; + explicit ScopedHeapLock(HANDLE Heap) noexcept : Heap(Heap) { + if (!HeapLock(Heap)) { + OutputDebugStringA("Failed to lock heap\n"); + Failure = true; + } + } + ~ScopedHeapLock() noexcept { + if (Heap != NULL && !Failure) { + if (!HeapUnlock(Heap)) { + OutputDebugStringA("Failed to lock heap\n"); + } + } + } +}; + +} // anonymous namespace + +#define BUF_NUM_ENTRIES (BUF_SIZE / sizeof(HeapEntry)) + +static int heapWalk() { + // Format the shared mem and event object names + DWORD Pid = GetCurrentProcessId(); + char SharedMemName[128]; + char ReadEventName[128]; + char WriteEventName[128]; + if (StringCbPrintfA(SharedMemName, sizeof(SharedMemName), "%hS-%lu", + SHARED_MEM_NAME_PREFIX, Pid) != S_OK) { + OutputDebugStringA("StringCbPrintfA for SharedMemName failed\n"); + return 1; + } + if (StringCbPrintfA(ReadEventName, sizeof(ReadEventName), "%hS-%lu", + READ_EVENT_NAME_PREFIX, Pid) != S_OK) { + OutputDebugStringA("StringCbPrintfA for ReadEventName failed\n"); + return 1; + } + if (StringCbPrintfA(WriteEventName, sizeof(WriteEventName), "%hS-%lu", + WRITE_EVENT_NAME_PREFIX, Pid) != S_OK) { + OutputDebugStringA("StringCbPrintfA for WriteEventName failed\n"); + return 1; + } + + ScopedHandle MapFile( + OpenFileMappingA(FILE_MAP_ALL_ACCESS, false, SharedMemName)); + if (MapFile == NULL) { + OutputDebugStringA("OpenFileMapping failed\n"); + return 1; + } + ScopedViewOfFile Buf( + MapViewOfFile(MapFile.Handle, FILE_MAP_ALL_ACCESS, 0, 0, BUF_SIZE)); + if (Buf.get() == NULL) { + OutputDebugStringA("MapViewOfFile failed\n"); + return 1; + } + std::memset(Buf.get(), 0, BUF_SIZE); + ScopedHandle WriteEvent(OpenEventA(EVENT_ALL_ACCESS, false, WriteEventName)); + if (WriteEvent == NULL) { + OutputDebugStringA("OpenEventA failed\n"); + return 1; + } + ScopedHandle ReadEvent(OpenEventA(EVENT_ALL_ACCESS, false, ReadEventName)); + if (ReadEvent == NULL) { + OutputDebugStringA("OpenEventA failed\n"); + return 1; + } + + // Collect heaps. This is a loop because GetProcessHeaps requires + // specifying the max number of heaps to get upfront. + std::vector Heaps; + while (TRUE) { + DWORD ActualHeapCount = GetProcessHeaps(Heaps.size(), Heaps.data()); + if (ActualHeapCount <= Heaps.size()) { + Heaps.resize(ActualHeapCount); + break; + } + Heaps.resize(ActualHeapCount); + } + + // Iterate heaps and heap entries + size_t Count = 0; + for (HANDLE Heap : Heaps) { + PROCESS_HEAP_ENTRY Entry; + + // NOTE: Be careful not to reenter the heap lock while holding the + // heap lock or else it would hang. + ScopedHeapLock HeapLock(Heap); + if (HeapLock.Failure) { + continue; + } + + Entry.lpData = NULL; + while (HeapWalk(Heap, &Entry)) { + if ((Entry.wFlags & PROCESS_HEAP_REGION) || + (Entry.wFlags & PROCESS_HEAP_UNCOMMITTED_RANGE) || + (!(Entry.wFlags & PROCESS_HEAP_ENTRY_BUSY))) { + continue; + } + assert(Count < BUF_NUM_ENTRIES); + Buf.as()[Count] = { + reinterpret_cast(Entry.lpData), + Entry.cbData + Entry.cbOverhead + }; + if (++Count == BUF_NUM_ENTRIES) { + if (!SetEvent(ReadEvent)) { + OutputDebugStringA("SetEvent on ReadEvent failed\n"); + return 1; + } + DWORD Wait = WaitForSingleObject(WriteEvent, WAIT_TIMEOUT_MS); + if (Wait != WAIT_OBJECT_0) { + char Msg[128]; + if (StringCbPrintfA(Msg, sizeof(Msg), + "WaitForSingleObject failed %lu\n", + Wait) == S_OK) { + OutputDebugStringA(Msg); + } + return 1; + } + std::memset(Buf.get(), 0, BUF_SIZE); + Count = 0; + } + } + + if (Count > 0) { + // Write the remaining entries. + if (!SetEvent(ReadEvent)) { + OutputDebugStringA("SetEvent on ReadEvent failed\n"); + return 1; + } + DWORD Wait = WaitForSingleObject(WriteEvent, WAIT_TIMEOUT_MS); + if (Wait != WAIT_OBJECT_0) { + char Msg[128]; + if (StringCbPrintfA(Msg, sizeof(Msg), + "WaitForSingleObject failed %lu\n", Wait) == S_OK) { + OutputDebugStringA(Msg); + } + return 1; + } + std::memset(Buf.get(), 0, BUF_SIZE); + Count = 0; + } + } + + // Indicate the end of iteration with one last write. + std::memset(Buf.get(), 0, BUF_SIZE); + Buf.as()[0].Address = -1; + if (!SetEvent(ReadEvent)) { + OutputDebugStringA("SetEvent at the end of heap iteration failed\n"); + return 1; + } + DWORD Wait = WaitForSingleObject(WriteEvent, WAIT_TIMEOUT_MS); + if (Wait != WAIT_OBJECT_0) { + char Msg[128]; + if (StringCbPrintfA(Msg, sizeof(Msg), "WaitForSingleObject failed %lu\n", + Wait) == S_OK) { + OutputDebugStringA(Msg); + } + return 1; + } + + return 0; +} + +BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, + LPVOID lpReserved) { + if (ul_reason_for_call == DLL_PROCESS_ATTACH) { + heapWalk(); + } + return TRUE; +} + +#endif diff --git a/tools/swift-inspect/Sources/SwiftInspectClientInterface/SwiftInspectClientInterface.h b/tools/swift-inspect/Sources/SwiftInspectClientInterface/SwiftInspectClientInterface.h new file mode 100644 index 0000000000000..0ee0992df7f9a --- /dev/null +++ b/tools/swift-inspect/Sources/SwiftInspectClientInterface/SwiftInspectClientInterface.h @@ -0,0 +1,28 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#if defined(_WIN32) + +#include + +#define BUF_SIZE 512 +#define SHARED_MEM_NAME_PREFIX "Local\\SwiftInspectFileMapping" +#define READ_EVENT_NAME_PREFIX "Local\\SwiftInspectReadEvent" +#define WRITE_EVENT_NAME_PREFIX "Local\\SwiftInspectWriteEvent" +#define WAIT_TIMEOUT_MS 30000 + +struct HeapEntry { + uintptr_t Address; + uintptr_t Size; +}; + +#endif diff --git a/tools/swift-inspect/Sources/SwiftInspectClientInterface/module.modulemap b/tools/swift-inspect/Sources/SwiftInspectClientInterface/module.modulemap new file mode 100644 index 0000000000000..bcc0f286bdecb --- /dev/null +++ b/tools/swift-inspect/Sources/SwiftInspectClientInterface/module.modulemap @@ -0,0 +1,4 @@ +module SwiftInspectClientInterface { + header "SwiftInspectClientInterface.h" + export * +} diff --git a/tools/swift-inspect/Sources/swift-inspect/WinSDK+Extentions.swift b/tools/swift-inspect/Sources/swift-inspect/WinSDK+Extentions.swift new file mode 100644 index 0000000000000..46bb7add7fc4a --- /dev/null +++ b/tools/swift-inspect/Sources/swift-inspect/WinSDK+Extentions.swift @@ -0,0 +1,21 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#if os(Windows) + +import WinSDK + +internal let FILE_MAP_ALL_ACCESS = DWORD( + STANDARD_RIGHTS_REQUIRED | SECTION_QUERY | SECTION_MAP_WRITE | SECTION_MAP_READ + | SECTION_MAP_EXECUTE | SECTION_EXTEND_SIZE) + +#endif diff --git a/tools/swift-inspect/Sources/swift-inspect/WindowsRemoteProcess.swift b/tools/swift-inspect/Sources/swift-inspect/WindowsRemoteProcess.swift index 2550bff748af8..f4310064a909d 100644 --- a/tools/swift-inspect/Sources/swift-inspect/WindowsRemoteProcess.swift +++ b/tools/swift-inspect/Sources/swift-inspect/WindowsRemoteProcess.swift @@ -14,6 +14,8 @@ import WinSDK import SwiftRemoteMirror +import Foundation +import SwiftInspectClientInterface internal final class WindowsRemoteProcess: RemoteProcess { public typealias ProcessIdentifier = DWORD @@ -60,11 +62,13 @@ internal final class WindowsRemoteProcess: RemoteProcess { static var ReadBytes: ReadBytesFunction { return { (context, address, size, _) in let process: WindowsRemoteProcess = - WindowsRemoteProcess.fromOpaque(context!) + WindowsRemoteProcess.fromOpaque(context!) guard let buffer = malloc(Int(size)) else { return nil } - if !ReadProcessMemory(process.process, LPVOID(bitPattern: UInt(address)), - buffer, size, nil) { + if !ReadProcessMemory( + process.process, LPVOID(bitPattern: UInt(address)), + buffer, size, nil) + { free(buffer) return nil } @@ -75,15 +79,17 @@ internal final class WindowsRemoteProcess: RemoteProcess { static var GetStringLength: GetStringLengthFunction { return { (context, address) in let process: WindowsRemoteProcess = - WindowsRemoteProcess.fromOpaque(context!) + WindowsRemoteProcess.fromOpaque(context!) var information: WIN32_MEMORY_REGION_INFORMATION = - WIN32_MEMORY_REGION_INFORMATION() - if !QueryVirtualMemoryInformation(process.process, - LPVOID(bitPattern: UInt(address)), - MemoryRegionInfo, &information, - SIZE_T(MemoryLayout.size(ofValue: information)), - nil) { + WIN32_MEMORY_REGION_INFORMATION() + if !QueryVirtualMemoryInformation( + process.process, + LPVOID(bitPattern: UInt(address)), + MemoryRegionInfo, &information, + SIZE_T(MemoryLayout.size(ofValue: information)), + nil) + { return 0 } @@ -93,12 +99,17 @@ internal final class WindowsRemoteProcess: RemoteProcess { // proper remote `strlen` implementation. // // Read 64-bytes, though limit it to the size of the memory region. - let length: Int = Int(min(UInt(information.RegionSize) - (UInt(address) - UInt(bitPattern: information.AllocationBase)), 64)) - let string: String = Array(unsafeUninitializedCapacity: length) { + let length: Int = Int( + min( + UInt(information.RegionSize) + - (UInt(address) - UInt(bitPattern: information.AllocationBase)), 64)) + let string: String = [CChar](unsafeUninitializedCapacity: length) { $1 = 0 var NumberOfBytesRead: SIZE_T = 0 - if ReadProcessMemory(process.process, LPVOID(bitPattern: UInt(address)), - $0.baseAddress, SIZE_T($0.count), &NumberOfBytesRead) { + if ReadProcessMemory( + process.process, LPVOID(bitPattern: UInt(address)), + $0.baseAddress, SIZE_T($0.count), &NumberOfBytesRead) + { $1 = Int(NumberOfBytesRead) } }.withUnsafeBufferPointer { @@ -112,7 +123,7 @@ internal final class WindowsRemoteProcess: RemoteProcess { static var GetSymbolAddress: GetSymbolAddressFunction { return { (context, symbol, length) in let process: WindowsRemoteProcess = - WindowsRemoteProcess.fromOpaque(context!) + WindowsRemoteProcess.fromOpaque(context!) guard let symbol = symbol else { return 0 } let name: String = symbol.withMemoryRebound(to: UInt8.self, capacity: Int(length)) { @@ -127,54 +138,44 @@ internal final class WindowsRemoteProcess: RemoteProcess { init?(processId: ProcessIdentifier) { // Get process handle. self.process = - OpenProcess(DWORD(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ), false, - processId) - - // Locate swiftCore.dll in the target process - let hSnapshot: HANDLE = - CreateToolhelp32Snapshot(DWORD(TH32CS_SNAPMODULE), processId) - if hSnapshot == INVALID_HANDLE_VALUE { - // FIXME(compnerd) log error - return nil - } - defer { CloseHandle(hSnapshot) } + OpenProcess( + DWORD( + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION), + false, + processId) // Initialize SwiftReflectionContextRef - guard let context = - swift_reflection_createReflectionContextWithDataLayout(self.toOpaqueRef(), - Self.QueryDataLayout, - Self.Free, - Self.ReadBytes, - Self.GetStringLength, - Self.GetSymbolAddress) else { + guard + let context = + swift_reflection_createReflectionContextWithDataLayout( + self.toOpaqueRef(), + Self.QueryDataLayout, + Self.Free, + Self.ReadBytes, + Self.GetStringLength, + Self.GetSymbolAddress) + else { // FIXME(compnerd) log error return nil } self.context = context - // Load modules. - var entry: MODULEENTRY32W = MODULEENTRY32W() - entry.dwSize = DWORD(MemoryLayout.size) - - if !Module32FirstW(hSnapshot, &entry) { + // Locate swiftCore.dll in the target process and load modules. + iterateRemoteModules( + dwProcessId: processId, + closure: { (entry, module) in + // FIXME(compnerd) support static linking at some point + if module == "swiftCore.dll" { + self.hSwiftCore = entry.hModule + } + _ = swift_reflection_addImage( + context, unsafeBitCast(entry.modBaseAddr, to: swift_addr_t.self)) + }) + if self.hSwiftCore == HMODULE(bitPattern: -1) { // FIXME(compnerd) log error return nil } - repeat { - let module: String = withUnsafePointer(to: entry.szModule) { - $0.withMemoryRebound(to: WCHAR.self, - capacity: MemoryLayout.size(ofValue: $0) / MemoryLayout.size) { - String(decodingCString: $0, as: UTF16.self) - } - } - // FIXME(compnerd) support static linking at some point - if module == "swiftCore.dll" { - self.hSwiftCore = entry.hModule - } - _ = swift_reflection_addImage(context, unsafeBitCast(entry.modBaseAddr, to: swift_addr_t.self)) - } while Module32NextW(hSnapshot, &entry) - // Initialize DbgHelp. if !SymInitialize(self.process, nil, true) { // FIXME(compnerd) log error @@ -195,11 +196,11 @@ internal final class WindowsRemoteProcess: RemoteProcess { let byteCount = MemoryLayout.size + kMaxSymbolNameLength + 1 let buffer: UnsafeMutableRawPointer = - UnsafeMutableRawPointer.allocate(byteCount: byteCount, alignment: 1) + UnsafeMutableRawPointer.allocate(byteCount: byteCount, alignment: 1) defer { buffer.deallocate() } let pSymbolInfo: UnsafeMutablePointer = - buffer.bindMemory(to: SYMBOL_INFO.self, capacity: 1) + buffer.bindMemory(to: SYMBOL_INFO.self, capacity: 1) pSymbolInfo.pointee.SizeOfStruct = ULONG(MemoryLayout.size) pSymbolInfo.pointee.MaxNameLen = ULONG(kMaxSymbolNameLength) @@ -212,16 +213,18 @@ internal final class WindowsRemoteProcess: RemoteProcess { } var context: (DWORD64, String?) = (pSymbolInfo.pointee.ModBase, nil) - _ = SymEnumerateModules64(self.process, { ModuleName, BaseOfDll, UserContext in - let pContext: UnsafeMutablePointer<(DWORD64, String?)> = + _ = SymEnumerateModules64( + self.process, + { ModuleName, BaseOfDll, UserContext in + let pContext: UnsafeMutablePointer<(DWORD64, String?)> = UserContext!.bindMemory(to: (DWORD64, String?).self, capacity: 1) - if BaseOfDll == pContext.pointee.0 { - pContext.pointee.1 = String(cString: ModuleName!) - return false - } - return true - }, &context) + if BaseOfDll == pContext.pointee.0 { + pContext.pointee.1 = String(cString: ModuleName!) + return false + } + return true + }, &context) return (context.1, symbol) } @@ -233,38 +236,326 @@ internal final class WindowsRemoteProcess: RemoteProcess { return } - let hSnapshot: HANDLE = - CreateToolhelp32Snapshot(DWORD(TH32CS_SNAPHEAPLIST), dwProcessId) - if hSnapshot == INVALID_HANDLE_VALUE { - // FIXME(compnerd) log error + // We use a shared memory and two event objects to send heap entries data + // from the remote process to this process. A high-level structure looks + // like below: + // + // Swift inspect (this process): + // + // Setup the shared memory and event objects + // Create a remote thread to invoke the heap walk on the remote process + // Loop { + // Wait on ReadEvent to wait for heap entries in the shared memory + // If no entries, break + // Inspect and dump heap entries from the shared memory + // Notify (SetEvent) on WriteEvent to have more heap entries written + // } + // + // Remote process: + // + // Open the shared memory and event objects + // Heap walk loop { + // Write heap entries in the shared memory until full or done + // Notify (SetEvent) ReadEvent to have them read + // Wait on WriteEvent until they are read + // } + // + + // Exclude the self-inspect case. We use IPC + HeapWalk in the remote + // process, which doesn't work on itself. + if dwProcessId == GetCurrentProcessId() { + print("Cannot inspect the process itself") return } - defer { CloseHandle(hSnapshot) } - var heap: HEAPLIST32 = HEAPLIST32() - heap.dwSize = SIZE_T(MemoryLayout.size) - if !Heap32ListFirst(hSnapshot, &heap) { - // FIXME(compnerd) log error + // The size of the shared memory buffer and the names of shared + // memory and event objects + let bufSize = Int(BUF_SIZE) + let sharedMemoryName = "\(SHARED_MEM_NAME_PREFIX)-\(String(dwProcessId))" + let waitTimeoutMs = DWORD(WAIT_TIMEOUT_MS) + + // Set up the shared memory + let hMapFile = CreateFileMappingA( + INVALID_HANDLE_VALUE, + nil, + DWORD(PAGE_READWRITE), + 0, + DWORD(bufSize), + sharedMemoryName) + if hMapFile == HANDLE(bitPattern: 0) { + print("CreateFileMapping failed \(GetLastError())") return } + defer { CloseHandle(hMapFile) } + let buf: LPVOID = MapViewOfFile( + hMapFile, + FILE_MAP_ALL_ACCESS, + 0, + 0, + SIZE_T(bufSize)) + if buf == LPVOID(bitPattern: 0) { + print("MapViewOfFile failed \(GetLastError())") + return + } + defer { UnmapViewOfFile(buf) } - repeat { - var entry: HEAPENTRY32 = HEAPENTRY32() - entry.dwSize = SIZE_T(MemoryLayout.size) + // Set up the event objects + guard let (hReadEvent, hWriteEvent) = createEventPair(dwProcessId) else { + return + } + defer { + CloseHandle(hReadEvent) + CloseHandle(hWriteEvent) + } + + // Allocate the dll path string in the remote process. + guard let dllPathRemote = allocateDllPathRemote() else { + return + } + + // Load the dll and start the heap walk + guard + let remoteAddrs = findRemoteAddresses( + dwProcessId: dwProcessId, moduleName: "KERNEL32.DLL", + symbols: ["LoadLibraryW", "FreeLibrary"]) + else { + print("Failed to find remote LoadLibraryW/FreeLibrary addresses") + return + } + let (loadLibraryAddr, freeLibraryAddr) = (remoteAddrs[0], remoteAddrs[1]) + let hThread: HANDLE = CreateRemoteThread( + self.process, nil, 0, loadLibraryAddr, + dllPathRemote, 0, nil) + if hThread == HANDLE(bitPattern: 0) { + print("CreateRemoteThread failed \(GetLastError())") + return + } + defer { CloseHandle(hThread) } + + // The main heap iteration loop. + outer: while true { + let wait = WaitForSingleObject(hReadEvent, waitTimeoutMs) + if wait != WAIT_OBJECT_0 { + print("WaitForSingleObject failed \(wait)") + return + } + + let entryCount = bufSize / MemoryLayout.size + + for entry in UnsafeMutableBufferPointer( + start: buf.bindMemory( + to: HeapEntry.self, + capacity: entryCount), + count: entryCount) + { + if entry.Address == UInt.max { + // The buffer containing zero entries, indicated by the first entry + // contains -1, means, we are done. Break out of loop. + if !SetEvent(hWriteEvent) { + print("SetEvent failed: \(GetLastError())") + return + } + break outer + } + if entry.Address == 0 { + // Done. Break out of loop. + break + } + body(swift_addr_t(entry.Address), UInt64(entry.Size)) + } + + if !SetEvent(hWriteEvent) { + print("SetEvent failed \(GetLastError())") + return + } + } + + let wait = WaitForSingleObject(hThread, waitTimeoutMs) + if wait != WAIT_OBJECT_0 { + print("WaitForSingleObject on LoadLibrary failed \(wait)") + return + } + + var threadExitCode: DWORD = 0 + GetExitCodeThread(hThread, &threadExitCode) + if threadExitCode == 0 { + print("LoadLibraryW failed \(threadExitCode)") + return + } - if !Heap32First(&entry, dwProcessId, heap.th32HeapID) { - // FIXME(compnerd) log error - continue + // Unload the dll and deallocate the dll path from the remote process + if !unloadDllAndPathRemote( + dwProcessId: dwProcessId, dllPathRemote: dllPathRemote, freeLibraryAddr: freeLibraryAddr) + { + print("Failed to unload the remote dll") + return + } + } + + private func allocateDllPathRemote() -> UnsafeMutableRawPointer? { + // The path to the dll assuming it's in the same directory as swift-inspect. + let swiftInspectPath = ProcessInfo.processInfo.arguments[0] + return URL(fileURLWithPath: swiftInspectPath) + .deletingLastPathComponent() + .appendingPathComponent("SwiftInspectClient.dll") + .withUnsafeFileSystemRepresentation { + #"\\?\\#(String(decodingCString: unsafeBitCast($0!, to: UnsafePointer.self), as: UTF8.self))"# + .withCString(encodedAs: UTF16.self) { + // Check that the dll file exists + var faAttributes: WIN32_FILE_ATTRIBUTE_DATA = .init() + guard GetFileAttributesExW($0, GetFileExInfoStandard, &faAttributes), + faAttributes.dwFileAttributes & DWORD(FILE_ATTRIBUTE_REPARSE_POINT) == 0 + else { + print("\($0) doesn't exist") + return nil + } + // Allocate memory in the remote process + let szLength = SIZE_T(wcslen($0) * MemoryLayout.size + 1) + guard + let allocation = VirtualAllocEx( + self.process, nil, szLength, + DWORD(MEM_COMMIT), DWORD(PAGE_READWRITE)) + else { + print("VirtualAllocEx failed \(GetLastError())") + return nil + } + // Write the path in the allocated memory + if !WriteProcessMemory(self.process, allocation, $0, szLength, nil) { + print("WriteProcessMemory failed \(GetLastError())") + VirtualFreeEx(self.process, allocation, 0, DWORD(MEM_RELEASE)) + return nil + } + + return allocation + } } + } + + private func unloadDllAndPathRemote( + dwProcessId: DWORD, dllPathRemote: UnsafeMutableRawPointer, + freeLibraryAddr: LPTHREAD_START_ROUTINE + ) -> Bool { + // Get the dll module handle in the remote process to use it to + // unload it below. + // GetExitCodeThread returns a DWORD (32-bit) but the HMODULE + // returned from LoadLibraryW is a 64-bit pointer and may be truncated. + // So, search for it using the snapshot instead. + guard + let hDllModule = findRemoteModule( + dwProcessId: dwProcessId, moduleName: "SwiftInspectClient.dll") + else { + print("Failed to find the client dll") + return false + } + // Unload the dll from the remote process + let hUnloadThread = CreateRemoteThread( + self.process, nil, 0, freeLibraryAddr, + unsafeBitCast(hDllModule, to: LPVOID.self), 0, nil) + if hUnloadThread == HANDLE(bitPattern: 0) { + print("CreateRemoteThread for unload failed \(GetLastError())") + return false + } + defer { CloseHandle(hUnloadThread) } + let unload_wait = WaitForSingleObject(hUnloadThread, DWORD(WAIT_TIMEOUT_MS)) + if unload_wait != WAIT_OBJECT_0 { + print("WaitForSingleObject on FreeLibrary failed \(unload_wait)") + return false + } + var unloadExitCode: DWORD = 0 + GetExitCodeThread(hUnloadThread, &unloadExitCode) + if unloadExitCode == 0 { + print("FreeLibrary failed") + return false + } - repeat { - if entry.dwFlags & DWORD(LF32_FREE) == DWORD(LF32_FREE) { continue } - body(swift_addr_t(entry.dwAddress), UInt64(entry.dwBlockSize)) - } while Heap32Next(&entry) + // Deallocate the dll path string in the remote process + if !VirtualFreeEx(self.process, dllPathRemote, 0, DWORD(MEM_RELEASE)) { + print("VirtualFreeEx failed GLE=\(GetLastError())") + return false + } - heap.dwSize = SIZE_T(MemoryLayout.size) - } while Heap32ListNext(hSnapshot, &heap) + return true } + + private func iterateRemoteModules(dwProcessId: DWORD, closure: (MODULEENTRY32W, String) -> Void) { + let hModuleSnapshot: HANDLE = + CreateToolhelp32Snapshot(DWORD(TH32CS_SNAPMODULE), dwProcessId) + if hModuleSnapshot == INVALID_HANDLE_VALUE { + print("CreateToolhelp32Snapshot failed \(GetLastError())") + return + } + defer { CloseHandle(hModuleSnapshot) } + var entry: MODULEENTRY32W = MODULEENTRY32W() + entry.dwSize = DWORD(MemoryLayout.size) + guard Module32FirstW(hModuleSnapshot, &entry) else { + print("Module32FirstW failed \(GetLastError())") + return + } + repeat { + let module: String = withUnsafePointer(to: entry.szModule) { + $0.withMemoryRebound( + to: WCHAR.self, + capacity: MemoryLayout.size(ofValue: $0) / MemoryLayout.size + ) { + String(decodingCString: $0, as: UTF16.self) + } + } + closure(entry, module) + } while Module32NextW(hModuleSnapshot, &entry) + } + + private func findRemoteModule(dwProcessId: DWORD, moduleName: String) -> HMODULE? { + var hDllModule: HMODULE? = nil + iterateRemoteModules( + dwProcessId: dwProcessId, + closure: { (entry, module) in + if module == moduleName { + hDllModule = entry.hModule + } + }) + return hDllModule + } + + private func findRemoteAddresses(dwProcessId: DWORD, moduleName: String, symbols: [String]) + -> [LPTHREAD_START_ROUTINE]? + { + guard let hDllModule = findRemoteModule(dwProcessId: dwProcessId, moduleName: moduleName) else { + print("Failed to find remote module \(moduleName)") + return nil + } + var addresses: [LPTHREAD_START_ROUTINE] = [] + for sym in symbols { + addresses.append( + unsafeBitCast(GetProcAddress(hDllModule, sym), to: LPTHREAD_START_ROUTINE.self)) + } + return addresses + } + + private func createEventPair(_ dwProcessId: DWORD) -> (HANDLE, HANDLE)? { + let readEventName = READ_EVENT_NAME_PREFIX + "-" + String(dwProcessId) + let writeEventName = WRITE_EVENT_NAME_PREFIX + "-" + String(dwProcessId) + let hReadEvent: HANDLE = CreateEventA( + LPSECURITY_ATTRIBUTES(bitPattern: 0), + false, // Auto-reset + false, // Initial state is nonsignaled + readEventName) + if hReadEvent == HANDLE(bitPattern: 0) { + print("CreateEvent failed \(GetLastError())") + return nil + } + let hWriteEvent: HANDLE = CreateEventA( + LPSECURITY_ATTRIBUTES(bitPattern: 0), + false, // Auto-reset + false, // Initial state is nonsignaled + writeEventName) + if hWriteEvent == HANDLE(bitPattern: 0) { + print("CreateEvent failed \(GetLastError())") + CloseHandle(hReadEvent) + return nil + } + return (hReadEvent, hWriteEvent) + } + } #endif