Skip to content

Commit baddcf0

Browse files
authored
Walk PE files' sections instead of using swift_enumerateAllMetadataSections(). (#728)
On Windows, the platform provides sufficient API for us to walk images at runtime looking for those that contain Swift metadata (similar to what we do on Darwin.) Therefore, we don't need to use the `swift_enumerateAllMetadataSections()` function from the Swift runtime on Windows. This change also plumbs through an `imageAddress` argument to the discovery callback function. We're not currently using it, but it should be useful in the future for diagnostics (e.g. indicating that some data from a given image is malformed, or for passing to downstream testing tools that can make use of it.) This change _also_ also plumbs the `stop` argument _down_ into the platform implementations so that we don't keep iterating images after the caller has told us to stop. In practice this has not been a correctness problem, but it does impact performance of type discovery. ### Checklist: - [x] Code and documentation should follow the style of the [Style Guide](https://github.com/apple/swift-testing/blob/main/Documentation/StyleGuide.md). - [x] If public symbols are renamed or modified, DocC references should be updated.
1 parent f949445 commit baddcf0

File tree

4 files changed

+164
-41
lines changed

4 files changed

+164
-41
lines changed

Sources/Testing/ExitTests/ExitTest.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ extension ExitTest {
124124
public static func find(at sourceLocation: SourceLocation) -> Self? {
125125
var result: Self?
126126

127-
enumerateTypes(withNamesContaining: _exitTestContainerTypeNameMagic) { type, stop in
127+
enumerateTypes(withNamesContaining: _exitTestContainerTypeNameMagic) { _, type, stop in
128128
if let type = type as? any __ExitTestContainer.Type, type.__sourceLocation == sourceLocation {
129129
result = ExitTest(
130130
expectedExitCondition: type.__expectedExitCondition,

Sources/Testing/Test+Discovery.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ extension Test {
4848
private static var _all: some Sequence<Self> {
4949
get async {
5050
await withTaskGroup(of: [Self].self) { taskGroup in
51-
enumerateTypes(withNamesContaining: _testContainerTypeNameMagic) { type, _ in
51+
enumerateTypes(withNamesContaining: _testContainerTypeNameMagic) { _, type, _ in
5252
if let type = type as? any __TestContainer.Type {
5353
taskGroup.addTask {
5454
await type.__tests
@@ -114,11 +114,15 @@ extension Test {
114114
/// The type of callback called by ``enumerateTypes(withNamesContaining:_:)``.
115115
///
116116
/// - Parameters:
117+
/// - imageAddress: A pointer to the start of the image. This value is _not_
118+
/// equal to the value returned from `dlopen()`. On platforms that do not
119+
/// support dynamic loading (and so do not have loadable images), this
120+
/// argument is unspecified.
117121
/// - type: A Swift type.
118122
/// - stop: An `inout` boolean variable indicating whether type enumeration
119123
/// should stop after the function returns. Set `stop` to `true` to stop
120124
/// type enumeration.
121-
typealias TypeEnumerator = (_ type: Any.Type, _ stop: inout Bool) -> Void
125+
typealias TypeEnumerator = (_ imageAddress: UnsafeRawPointer?, _ type: Any.Type, _ stop: inout Bool) -> Void
122126

123127
/// Enumerate all types known to Swift found in the current process whose names
124128
/// contain a given substring.
@@ -129,11 +133,11 @@ typealias TypeEnumerator = (_ type: Any.Type, _ stop: inout Bool) -> Void
129133
func enumerateTypes(withNamesContaining nameSubstring: String, _ typeEnumerator: TypeEnumerator) {
130134
withoutActuallyEscaping(typeEnumerator) { typeEnumerator in
131135
withUnsafePointer(to: typeEnumerator) { context in
132-
swt_enumerateTypes(withNamesContaining: nameSubstring, .init(mutating: context)) { type, stop, context in
136+
swt_enumerateTypes(withNamesContaining: nameSubstring, .init(mutating: context)) { imageAddress, type, stop, context in
133137
let typeEnumerator = context!.load(as: TypeEnumerator.self)
134138
let type = unsafeBitCast(type, to: Any.Type.self)
135139
var stop2 = false
136-
typeEnumerator(type, &stop2)
140+
typeEnumerator(imageAddress, type, &stop2)
137141
stop.pointee = stop2
138142
}
139143
}

Sources/_TestingInternals/Discovery.cpp

Lines changed: 150 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,49 @@
1010

1111
#include "Discovery.h"
1212

13+
#include <algorithm>
14+
#include <array>
1315
#include <atomic>
1416
#include <cstring>
1517
#include <iterator>
18+
#include <tuple>
1619
#include <type_traits>
1720
#include <vector>
21+
#include <optional>
1822

19-
#if defined(SWT_NO_DYNAMIC_LINKING)
20-
#include <algorithm>
21-
#elif defined(__APPLE__)
23+
#if defined(__APPLE__) && !defined(SWT_NO_DYNAMIC_LINKING)
2224
#include <dispatch/dispatch.h>
2325
#include <mach-o/dyld.h>
2426
#include <mach-o/getsect.h>
2527
#include <objc/runtime.h>
2628
#include <os/lock.h>
2729
#endif
2830

31+
/// A type that acts as a C++ [Allocator](https://en.cppreference.com/w/cpp/named_req/Allocator)
32+
/// without using global `operator new` or `operator delete`.
33+
///
34+
/// This type is necessary because global `operator new` and `operator delete`
35+
/// can be overridden in developer-supplied code and cause deadlocks or crashes
36+
/// when subsequently used while holding a dyld- or libobjc-owned lock. Using
37+
/// `std::malloc()` and `std::free()` allows the use of C++ container types
38+
/// without this risk.
39+
template<typename T>
40+
struct SWTHeapAllocator {
41+
using value_type = T;
42+
43+
T *allocate(size_t count) {
44+
return reinterpret_cast<T *>(std::calloc(count, sizeof(T)));
45+
}
46+
47+
void deallocate(T *ptr, size_t count) {
48+
std::free(ptr);
49+
}
50+
};
51+
52+
/// A `std::vector` that uses `SWTHeapAllocator`.
53+
template <typename T>
54+
using SWTVector = std::vector<T, SWTHeapAllocator<T>>;
55+
2956
/// Enumerate over all Swift type metadata sections in the current process.
3057
///
3158
/// - Parameters:
@@ -199,39 +226,19 @@ extern "C" const char sectionEnd __asm("section$end$__TEXT$__swift5_types");
199226
template <typename SectionEnumerator>
200227
static void enumerateTypeMetadataSections(const SectionEnumerator& body) {
201228
auto size = std::distance(&sectionBegin, &sectionEnd);
202-
body(&sectionBegin, size);
229+
bool stop = false;
230+
body(nullptr, &sectionBegin, size, &stop);
203231
}
204232

205233
#elif defined(__APPLE__)
206234
#pragma mark - Apple implementation
207235

208-
/// A type that acts as a C++ [Allocator](https://en.cppreference.com/w/cpp/named_req/Allocator)
209-
/// without using global `operator new` or `operator delete`.
210-
///
211-
/// This type is necessary because global `operator new` and `operator delete`
212-
/// can be overridden in developer-supplied code and cause deadlocks or crashes
213-
/// when subsequently used while holding a dyld- or libobjc-owned lock. Using
214-
/// `std::malloc()` and `std::free()` allows the use of C++ container types
215-
/// without this risk.
216-
template<typename T>
217-
struct SWTHeapAllocator {
218-
using value_type = T;
219-
220-
T *allocate(size_t count) {
221-
return reinterpret_cast<T *>(std::calloc(count, sizeof(T)));
222-
}
223-
224-
void deallocate(T *ptr, size_t count) {
225-
std::free(ptr);
226-
}
227-
};
228-
229236
/// A type that acts as a C++ [Container](https://en.cppreference.com/w/cpp/named_req/Container)
230237
/// and which contains a sequence of Mach headers.
231238
#if __LP64__
232-
using SWTMachHeaderList = std::vector<const mach_header_64 *, SWTHeapAllocator<const mach_header_64 *>>;
239+
using SWTMachHeaderList = SWTVector<const mach_header_64 *>;
233240
#else
234-
using SWTMachHeaderList = std::vector<const mach_header *, SWTHeapAllocator<const mach_header *>>;
241+
using SWTMachHeaderList = SWTVector<const mach_header *>;
235242
#endif
236243

237244
/// Get a copy of the currently-loaded Mach headers list.
@@ -301,13 +308,118 @@ static void enumerateTypeMetadataSections(const SectionEnumerator& body) {
301308
unsigned long size = 0;
302309
const void *section = getsectiondata(mh, SEG_TEXT, "__swift5_types", &size);
303310
if (section && size > 0) {
304-
body(section, size);
311+
bool stop = false;
312+
body(mh, section, size, &stop);
313+
if (stop) {
314+
break;
315+
}
316+
}
317+
}
318+
}
319+
320+
#elif defined(_WIN32)
321+
#pragma mark - Windows implementation
322+
323+
/// Find the section with the given name in the given module.
324+
///
325+
/// - Parameters:
326+
/// - module: The module to inspect.
327+
/// - sectionName: The name of the section to look for. Long section names are
328+
/// not supported.
329+
///
330+
/// - Returns: A pointer to the start of the given section along with its size
331+
/// in bytes, or `std::nullopt` if the section could not be found. If the
332+
/// section was emitted by the Swift toolchain, be aware it will have leading
333+
/// and trailing bytes (`sizeof(uintptr_t)` each.)
334+
static std::optional<std::pair<const void *, size_t>> findSection(HMODULE module, const char *sectionName) {
335+
if (!module) {
336+
return std::nullopt;
337+
}
338+
339+
// Get the DOS header (to which the HMODULE directly points, conveniently!)
340+
// and check it's sufficiently valid for us to walk.
341+
auto dosHeader = reinterpret_cast<const PIMAGE_DOS_HEADER>(module);
342+
if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE || dosHeader->e_lfanew <= 0) {
343+
return std::nullopt;
344+
}
345+
346+
// Check the NT header as well as the optional header.
347+
auto ntHeader = reinterpret_cast<const PIMAGE_NT_HEADERS>(reinterpret_cast<uintptr_t>(dosHeader) + dosHeader->e_lfanew);
348+
if (!ntHeader || ntHeader->Signature != IMAGE_NT_SIGNATURE) {
349+
return std::nullopt;
350+
}
351+
if (ntHeader->FileHeader.SizeOfOptionalHeader < offsetof(decltype(ntHeader->OptionalHeader), Magic) + sizeof(decltype(ntHeader->OptionalHeader)::Magic)) {
352+
return std::nullopt;
353+
}
354+
if (ntHeader->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC) {
355+
return std::nullopt;
356+
}
357+
358+
auto sectionCount = ntHeader->FileHeader.NumberOfSections;
359+
auto section = IMAGE_FIRST_SECTION(ntHeader);
360+
for (size_t i = 0; i < sectionCount; i++, section += 1) {
361+
if (section->VirtualAddress == 0) {
362+
continue;
363+
}
364+
365+
auto start = reinterpret_cast<const void *>(reinterpret_cast<uintptr_t>(dosHeader) + section->VirtualAddress);
366+
size_t size = std::min(section->Misc.VirtualSize, section->SizeOfRawData);
367+
if (start && size > 0) {
368+
// FIXME: Handle longer names ("/%u") from string table
369+
auto thisSectionName = reinterpret_cast<const char *>(section->Name);
370+
if (0 == std::strncmp(sectionName, thisSectionName, IMAGE_SIZEOF_SHORT_NAME)) {
371+
return std::make_pair(start, size);
372+
}
373+
}
374+
}
375+
376+
return std::nullopt;
377+
}
378+
379+
template <typename SectionEnumerator>
380+
static void enumerateTypeMetadataSections(const SectionEnumerator& body) {
381+
// Find all the modules loaded in the current process. We assume there aren't
382+
// more than 1024 loaded modules (as does Microsoft sample code.)
383+
std::array<HMODULE, 1024> hModules;
384+
DWORD byteCountNeeded = 0;
385+
if (!EnumProcessModules(GetCurrentProcess(), &hModules[0], hModules.size() * sizeof(HMODULE), &byteCountNeeded)) {
386+
return;
387+
}
388+
DWORD hModuleCount = std::min(hModules.size(), byteCountNeeded / sizeof(HMODULE));
389+
390+
// Look in all the loaded modules for Swift type metadata sections and store
391+
// them in a side table.
392+
//
393+
// This two-step process is less algorithmically efficient than a single loop,
394+
// but it is safer: the callback will eventually invoke developer code that
395+
// could theoretically unload a module from the list we're enumerating. (Swift
396+
// modules do not support unloading, so we'll just not worry about them.)
397+
using SWTSectionList = SWTVector<std::tuple<HMODULE, const void *, size_t>>;
398+
SWTSectionList sectionList;
399+
for (DWORD i = 0; i < hModuleCount; i++) {
400+
if (auto section = findSection(hModules[i], ".sw5tymd")) {
401+
sectionList.emplace_back(hModules[i], section->first, section->second);
402+
}
403+
}
404+
405+
// Pass the loaded module and section info back to the body callback.
406+
// Note we ignore the leading and trailing uintptr_t values: they're both
407+
// always set to zero so we'll skip them in the callback, and in the future
408+
// the toolchain might not emit them at all in which case we don't want to
409+
// skip over real section data.
410+
bool stop = false;
411+
for (const auto& section : sectionList) {
412+
// TODO: Use C++17 unstructured binding here.
413+
body(get<0>(section), get<1>(section), get<2>(section), &stop);
414+
if (stop) {
415+
break;
305416
}
306417
}
307418
}
308419

309-
#elif defined(__linux__) || defined(__FreeBSD__) || defined(_WIN32) || defined(__wasi__) || defined(__ANDROID__)
310-
#pragma mark - Linux/Windows implementation
420+
421+
#elif defined(__linux__) || defined(__FreeBSD__) || defined(__wasi__) || defined(__ANDROID__)
422+
#pragma mark - ELF implementation
311423

312424
/// Specifies the address range corresponding to a section.
313425
struct MetadataSectionRange {
@@ -352,7 +464,11 @@ static void enumerateTypeMetadataSections(const SectionEnumerator& body) {
352464
const auto& body = *reinterpret_cast<const SectionEnumerator *>(context);
353465
MetadataSectionRange section = sections->swift5_type_metadata;
354466
if (section.start && section.length > 0) {
355-
body(reinterpret_cast<const void *>(section.start), section.length);
467+
bool stop = false;
468+
body(sections->baseAddress.load(), reinterpret_cast<const void *>(section.start), section.length, &stop);
469+
if (stop) {
470+
return false;
471+
}
356472
}
357473
return true;
358474
}, const_cast<SectionEnumerator *>(&body));
@@ -366,12 +482,11 @@ static void enumerateTypeMetadataSections(const SectionEnumerator& body) {}
366482
#pragma mark -
367483

368484
void swt_enumerateTypesWithNamesContaining(const char *nameSubstring, void *context, SWTTypeEnumerator body) {
369-
enumerateTypeMetadataSections([=] (const void *section, size_t size) {
485+
enumerateTypeMetadataSections([=] (const void *imageAddress, const void *section, size_t size, bool *stop) {
370486
auto records = reinterpret_cast<const SWTTypeMetadataRecord *>(section);
371487
size_t recordCount = size / sizeof(SWTTypeMetadataRecord);
372488

373-
bool stop = false;
374-
for (size_t i = 0; i < recordCount && !stop; i++) {
489+
for (size_t i = 0; i < recordCount && !*stop; i++) {
375490
const auto& record = records[i];
376491

377492
auto contextDescriptor = record.getContextDescriptor();
@@ -394,7 +509,7 @@ void swt_enumerateTypesWithNamesContaining(const char *nameSubstring, void *cont
394509
}
395510

396511
if (void *typeMetadata = contextDescriptor->getMetadata()) {
397-
body(typeMetadata, &stop, context);
512+
body(imageAddress, typeMetadata, stop, context);
398513
}
399514
}
400515
});

Sources/_TestingInternals/include/Discovery.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,17 @@ SWT_ASSUME_NONNULL_BEGIN
1919
/// The type of callback called by `swt_enumerateTypes()`.
2020
///
2121
/// - Parameters:
22+
/// - imageAddress: A pointer to the start of the image. This value is _not_
23+
/// equal to the value returned from `dlopen()`. On platforms that do not
24+
/// support dynamic loading (and so do not have loadable images), this
25+
/// argument is unspecified.
2226
/// - typeMetadata: A type metadata pointer that can be bitcast to `Any.Type`.
2327
/// - stop: A pointer to a boolean variable indicating whether type
2428
/// enumeration should stop after the function returns. Set `*stop` to
2529
/// `true` to stop type enumeration.
2630
/// - context: An arbitrary pointer passed by the caller to
2731
/// `swt_enumerateTypes()`.
28-
typedef void (* SWTTypeEnumerator)(void *typeMetadata, bool *stop, void *_Null_unspecified context);
32+
typedef void (* SWTTypeEnumerator)(const void *_Null_unspecified imageAddress, void *typeMetadata, bool *stop, void *_Null_unspecified context);
2933

3034
/// Enumerate all types known to Swift found in the current process.
3135
///

0 commit comments

Comments
 (0)