Skip to content

Commit 4299093

Browse files
authored
Merge pull request #41496 from compnerd/windows-port
swift-inspect: port to windows
2 parents 57f05d6 + 7f789ba commit 4299093

File tree

3 files changed

+275
-0
lines changed

3 files changed

+275
-0
lines changed

tools/swift-inspect/Sources/swift-inspect/Process.swift

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,44 @@ internal typealias ProcessIdentifier = DarwinRemoteProcess.ProcessIdentifier
1818
internal func process(matching: String) -> ProcessIdentifier? {
1919
return pidFromHint(matching)
2020
}
21+
#elseif os(Windows)
22+
import WinSDK
23+
24+
internal typealias ProcessIdentifier = WindowsRemoteProcess.ProcessIdentifier
25+
26+
internal func process(matching: String) -> ProcessIdentifier? {
27+
if let dwProcess = DWORD(matching) {
28+
return dwProcess
29+
}
30+
31+
let hSnapshot = CreateToolhelp32Snapshot(DWORD(TH32CS_SNAPPROCESS), 0)
32+
if hSnapshot == INVALID_HANDLE_VALUE {
33+
return nil
34+
}
35+
defer { CloseHandle(hSnapshot) }
36+
37+
var entry: PROCESSENTRY32W = PROCESSENTRY32W()
38+
entry.dwSize = DWORD(MemoryLayout<PROCESSENTRY32W>.size)
39+
40+
if !Process32FirstW(hSnapshot, &entry) {
41+
return nil
42+
}
43+
44+
var matches: [(ProcessIdentifier, String)] = []
45+
repeat {
46+
let executable: String = withUnsafePointer(to: entry.szExeFile) {
47+
$0.withMemoryRebound(to: WCHAR.self,
48+
capacity: MemoryLayout.size(ofValue: $0) / MemoryLayout<WCHAR>.size) {
49+
String(decodingCString: $0, as: UTF16.self)
50+
}
51+
}
52+
if executable.hasPrefix(matching) {
53+
matches.append((entry.th32ProcessID, executable))
54+
}
55+
} while Process32NextW(hSnapshot, &entry)
56+
57+
return matches.first?.0
58+
}
59+
#else
60+
#error("Unsupported platform")
2161
#endif
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#if os(Windows)
14+
15+
import WinSDK
16+
import SwiftRemoteMirror
17+
18+
internal final class WindowsRemoteProcess: RemoteProcess {
19+
public typealias ProcessIdentifier = DWORD
20+
public typealias ProcessHandle = HANDLE
21+
22+
public private(set) var process: ProcessHandle
23+
public private(set) var context: SwiftReflectionContextRef!
24+
25+
private var hSwiftCore: HMODULE = HMODULE(bitPattern: -1)!
26+
27+
static var QueryDataLayout: QueryDataLayoutFunction {
28+
return { (context, type, _, output) in
29+
let _ = WindowsRemoteProcess.fromOpaque(context!)
30+
31+
switch type {
32+
case DLQ_GetPointerSize:
33+
let size = UInt8(MemoryLayout<UnsafeRawPointer>.stride)
34+
output?.storeBytes(of: size, toByteOffset: 0, as: UInt8.self)
35+
return 1
36+
37+
case DLQ_GetSizeSize:
38+
// FIXME(compnerd) support 32-bit processes
39+
let size = UInt8(MemoryLayout<UInt64>.stride)
40+
output?.storeBytes(of: size, toByteOffset: 0, as: UInt8.self)
41+
return 1
42+
43+
case DLQ_GetLeastValidPointerValue:
44+
let value: UInt64 = 0x1000
45+
output?.storeBytes(of: value, toByteOffset: 0, as: UInt64.self)
46+
return 1
47+
48+
default:
49+
return 0
50+
}
51+
}
52+
}
53+
54+
static var Free: FreeFunction {
55+
return { (_, bytes, _) in
56+
free(UnsafeMutableRawPointer(mutating: bytes))
57+
}
58+
}
59+
60+
static var ReadBytes: ReadBytesFunction {
61+
return { (context, address, size, _) in
62+
let process: WindowsRemoteProcess =
63+
WindowsRemoteProcess.fromOpaque(context!)
64+
65+
guard let buffer = malloc(Int(size)) else { return nil }
66+
if !ReadProcessMemory(process.process, LPVOID(bitPattern: UInt(address)),
67+
buffer, size, nil) {
68+
free(buffer)
69+
return nil
70+
}
71+
return UnsafeRawPointer(buffer)
72+
}
73+
}
74+
75+
static var GetStringLength: GetStringLengthFunction {
76+
return { (context, address) in
77+
let process: WindowsRemoteProcess =
78+
WindowsRemoteProcess.fromOpaque(context!)
79+
80+
var information: WIN32_MEMORY_REGION_INFORMATION =
81+
WIN32_MEMORY_REGION_INFORMATION()
82+
_ = QueryVirtualMemoryInformation(process.process,
83+
LPVOID(bitPattern: UInt(address)),
84+
MemoryRegionInfo, &information,
85+
SIZE_T(MemoryLayout.size(ofValue: information)),
86+
nil)
87+
88+
// FIXME(compnerd) mapping in the memory region from the remote process
89+
// would be ideal to avoid a round-trip for each byte. This seems to work
90+
// well enough for now in practice, but we should fix this to provide a
91+
// proper remote `strlen` implementation.
92+
//
93+
// Read 64-bytes, though limit it to the size of the memory region.
94+
let length: Int = Int(min(UInt(information.RegionSize) - (UInt(address) - UInt(bitPattern: information.AllocationBase)), 64))
95+
let string: String = Array<CChar>(unsafeUninitializedCapacity: length) {
96+
$1 = 0
97+
var NumberOfBytesRead: SIZE_T = 0
98+
if ReadProcessMemory(process.process, LPVOID(bitPattern: UInt(address)),
99+
$0.baseAddress, SIZE_T($0.count), &NumberOfBytesRead) {
100+
$1 = Int(NumberOfBytesRead)
101+
}
102+
}.withUnsafeBufferPointer {
103+
String(cString: $0.baseAddress!)
104+
}
105+
106+
return UInt64(string.count)
107+
}
108+
}
109+
110+
static var GetSymbolAddress: GetSymbolAddressFunction {
111+
return { (context, symbol, length) in
112+
let process: WindowsRemoteProcess =
113+
WindowsRemoteProcess.fromOpaque(context!)
114+
115+
guard let symbol = symbol else { return 0 }
116+
let name: String = symbol.withMemoryRebound(to: UInt8.self, capacity: Int(length)) {
117+
let buffer = UnsafeBufferPointer(start: $0, count: Int(length))
118+
return String(decoding: buffer, as: UTF8.self)
119+
}
120+
121+
return unsafeBitCast(GetProcAddress(process.hSwiftCore, name), to: swift_addr_t.self)
122+
}
123+
}
124+
125+
init?(processId: ProcessIdentifier) {
126+
// Setup process handle.
127+
self.process =
128+
OpenProcess(DWORD(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ), false,
129+
processId)
130+
131+
// Locate swiftCore.dll in the target process
132+
let hSnapshot: HANDLE =
133+
CreateToolhelp32Snapshot(DWORD(TH32CS_SNAPMODULE), processId)
134+
if hSnapshot == INVALID_HANDLE_VALUE {
135+
// FIXME(compnerd) log error
136+
return nil
137+
}
138+
defer { CloseHandle(hSnapshot) }
139+
140+
// Initialize SwiftReflectionContextRef
141+
guard let context =
142+
swift_reflection_createReflectionContextWithDataLayout(self.toOpaqueRef(),
143+
Self.QueryDataLayout,
144+
Self.Free,
145+
Self.ReadBytes,
146+
Self.GetStringLength,
147+
Self.GetSymbolAddress) else {
148+
// FIXME(compnerd) log error
149+
return nil
150+
}
151+
self.context = context
152+
153+
// Load modules.
154+
var entry: MODULEENTRY32W = MODULEENTRY32W()
155+
entry.dwSize = DWORD(MemoryLayout<MODULEENTRY32W>.size)
156+
157+
if !Module32FirstW(hSnapshot, &entry) {
158+
// FIXME(compnerd) log error
159+
return nil
160+
}
161+
162+
repeat {
163+
let module: String = withUnsafePointer(to: entry.szModule) {
164+
$0.withMemoryRebound(to: WCHAR.self,
165+
capacity: MemoryLayout.size(ofValue: $0) / MemoryLayout<WCHAR>.size) {
166+
String(decodingCString: $0, as: UTF16.self)
167+
}
168+
}
169+
// FIXME(compnerd) support static linking at some point
170+
if module == "swiftCore.dll" {
171+
self.hSwiftCore = entry.hModule
172+
}
173+
_ = swift_reflection_addImage(context, unsafeBitCast(entry.modBaseAddr, to: swift_addr_t.self))
174+
} while Module32NextW(hSnapshot, &entry)
175+
176+
// Initialize DbgHelp.
177+
if !SymInitialize(self.process, nil, true) {
178+
// FIXME(compnerd) log error
179+
return nil
180+
}
181+
}
182+
183+
deinit {
184+
swift_reflection_destroyReflectionContext(self.context)
185+
_ = SymCleanup(self.process)
186+
_ = CloseHandle(self.process)
187+
self.release()
188+
}
189+
190+
func symbolicate(_ address: swift_addr_t) -> (module: String?, symbol: String?) {
191+
let kMaxSymbolNameLength: Int = 1024
192+
193+
let byteCount = MemoryLayout<SYMBOL_INFO>.size + kMaxSymbolNameLength + 1
194+
195+
let buffer: UnsafeMutableRawPointer =
196+
UnsafeMutableRawPointer.allocate(byteCount: byteCount, alignment: 1)
197+
defer { buffer.deallocate() }
198+
199+
let pSymbolInfo: UnsafeMutablePointer<SYMBOL_INFO> =
200+
buffer.bindMemory(to: SYMBOL_INFO.self, capacity: 1)
201+
pSymbolInfo.pointee.SizeOfStruct = ULONG(MemoryLayout<SYMBOL_INFO>.size)
202+
pSymbolInfo.pointee.MaxNameLen = ULONG(kMaxSymbolNameLength)
203+
204+
guard SymFromAddr(self.process, DWORD64(address), nil, pSymbolInfo) else {
205+
return (nil, nil)
206+
}
207+
208+
let symbol: String = withUnsafePointer(to: &pSymbolInfo.pointee.Name) {
209+
String(cString: $0)
210+
}
211+
212+
var context: (DWORD64, String?) = (pSymbolInfo.pointee.ModBase, nil)
213+
_ = SymEnumerateModules64(self.process, { ModuleName, BaseOfDll, UserContext in
214+
let pContext: UnsafeMutablePointer<(DWORD64, String?)> =
215+
UserContext!.bindMemory(to: (DWORD64, String?).self, capacity: 1)
216+
217+
if BaseOfDll == pContext.pointee.0 {
218+
pContext.pointee.1 = String(cString: ModuleName!)
219+
return false
220+
}
221+
return true
222+
}, &context)
223+
224+
return (context.1, symbol)
225+
}
226+
}
227+
228+
#endif

tools/swift-inspect/Sources/swift-inspect/main.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ internal func inspect(process pattern: String,
4646
print("Failed to create inspector for process id \(processId)")
4747
return
4848
}
49+
#elseif os(Windows)
50+
guard let process = WindowsRemoteProcess(processId: processId) else {
51+
print("Failed to create inspector for process id \(processId)")
52+
return
53+
}
54+
#else
55+
#error("Unsupported platform")
4956
#endif
5057

5158
try body(process)

0 commit comments

Comments
 (0)