Skip to content

Commit 0fed1e5

Browse files
committed
Look up Swift's metadata sections via ELF notes.
This PR leverages the change at swiftlang/swift#78411 to let us look up Swift's metadata sections (specifically `"swift5_tests"`) by looking for the emitted ELF notes rather than by calling the Swift runtime function `swift_enumerateAllMetadataSections()` (which we'd like to remove.) Resolves #735.
1 parent bc3e04b commit 0fed1e5

File tree

4 files changed

+128
-23
lines changed

4 files changed

+128
-23
lines changed

Sources/Testing/Discovery+Platform.swift

Lines changed: 94 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,79 @@ private func _testContentSectionBounds() -> [SectionBounds] {
8989
#elseif os(Linux) || os(FreeBSD) || os(Android)
9090
// MARK: - ELF implementation
9191

92+
extension UnsafePointer<SWTElfWNhdr> {
93+
/// The size of the implied `n_name` field, in bytes.
94+
///
95+
/// This value is rounded up to ensure 32-bit alignment of the fields in the
96+
/// test content header and record.
97+
fileprivate var n_namesz: Int {
98+
Int(max(0, pointee.n_namesz)).alignedUp(for: UInt32.self)
99+
}
100+
101+
/// Get the implied `n_name` field.
102+
///
103+
/// If this test content header has no name, or if the name is not
104+
/// null-terminated, the value of this property is `nil`.
105+
fileprivate var n_name: UnsafePointer<CChar>? {
106+
if n_namesz <= 0 {
107+
return nil
108+
}
109+
return (self + 1).withMemoryRebound(to: CChar.self, capacity: n_namesz) { name in
110+
if strnlen(name, n_namesz) >= n_namesz {
111+
// There is no trailing null byte within the provided length.
112+
return nil
113+
}
114+
return name
115+
}
116+
}
117+
118+
/// The size of the implied `n_name` field, in bytes.
119+
///
120+
/// This value is rounded up to ensure 32-bit alignment of the fields in the
121+
/// test content header and record.
122+
fileprivate var n_descsz: Int {
123+
Int(max(0, pointee.n_descsz)).alignedUp(for: UInt32.self)
124+
}
125+
126+
/// The implied `n_desc` field.
127+
///
128+
/// If this test content header has no description (payload), the value of
129+
/// this property is `nil`.
130+
fileprivate var n_desc: UnsafeRawPointer? {
131+
if n_descsz <= 0 {
132+
return nil
133+
}
134+
return UnsafeRawPointer(self + 1) + n_namesz
135+
}
136+
137+
/// The number of bytes in this test content header, including all fields and
138+
/// padding.
139+
///
140+
/// The address at `UnsafeRawPointer(self) + self.byteCount` is the start of
141+
/// the next test content header in the same section (if there is one.)
142+
fileprivate var byteCount: Int {
143+
MemoryLayout<Pointee>.stride + n_namesz + n_descsz
144+
}
145+
}
146+
147+
/// All test content headers found in this test content section.
148+
func _noteHeaders(in buffer: UnsafeRawBufferPointer) -> some Sequence<UnsafePointer<SWTElfWNhdr>> {
149+
let start = buffer.baseAddress!
150+
let end: UnsafeRawPointer = start + buffer.count
151+
let firstHeader = start.assumingMemoryBound(to: SWTElfWNhdr.self)
152+
153+
// Generate an infinite sequence of (possible) header addresses, then prefix
154+
// it to those that are actually contained within the section. This way we can
155+
// bounds-check even the first header while maintaining an opaque return type.
156+
return sequence(first: firstHeader) { header in
157+
(UnsafeRawPointer(header) + header.byteCount).assumingMemoryBound(to: SWTElfWNhdr.self)
158+
}.lazy.prefix { header in
159+
header >= start && header < end
160+
&& (header + 1) <= end
161+
&& UnsafeRawPointer(header) + header.byteCount <= end
162+
}
163+
}
164+
92165
/// The ELF-specific implementation of ``SectionBounds/all``.
93166
///
94167
/// - Returns: An array of structures describing the bounds of all known test
@@ -97,20 +170,28 @@ private func _testContentSectionBounds() -> [SectionBounds] {
97170
var result = [SectionBounds]()
98171

99172
withUnsafeMutablePointer(to: &result) { result in
100-
swift_enumerateAllMetadataSections({ sections, context in
101-
let sections = sections.load(as: MetadataSections.self)
102-
let result = context.assumingMemoryBound(to: [SectionBounds].self)
103-
104-
let start = UnsafeRawPointer(bitPattern: sections.swift5_tests.start)
105-
let size = Int(clamping: sections.swift5_tests.length)
106-
if let start, size > 0 {
107-
let buffer = UnsafeRawBufferPointer(start: start, count: size)
108-
let sb = SectionBounds(imageAddress: sections.baseAddress, buffer: buffer)
109-
result.pointee.append(sb)
110-
}
173+
_ = swt_dl_iterate_phdr(result) { dlpi_addr, dlpi_phdr, dlpi_phnum, context in
174+
let result = context!.assumingMemoryBound(to: [SectionBounds].self)
111175

112-
return true
113-
}, result)
176+
let buffer = UnsafeBufferPointer(start: dlpi_phdr, count: dlpi_phnum)
177+
let sectionBoundsNotes: some Sequence<UnsafePointer<SWTElfWNhdr>> = buffer.lazy
178+
.filter { $0.p_type == PT_NOTE }
179+
.map { phdr in
180+
UnsafeRawBufferPointer(
181+
start: dlpi_addr + Int(clamping: UInt(clamping: phdr.p_vaddr)),
182+
count: Int(clamping: phdr.p_memsz)
183+
)
184+
}.flatMap(_noteHeaders(in:))
185+
.filter { $0.pointee.n_type == 0 }
186+
.filter { 0 == $0.n_name.map { strcmp($0, "swift5_tests") } }
187+
188+
result.pointee += sectionBoundsNotes.lazy
189+
.compactMap { $0.n_desc?.assumingMemoryBound(to: UnsafePointer<UnsafeRawPointer>.self) }
190+
.map { UnsafeRawBufferPointer(start: $0[0], count: $0[1] - $0[0]) }
191+
.map { SectionBounds(imageAddress: dlpi_addr, buffer: $0) }
192+
193+
return 0
194+
}
114195
}
115196

116197
return result

Sources/_TestingInternals/Stubs.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,22 @@ int swt_posix_spawn_file_actions_addclosefrom_np(posix_spawn_file_actions_t *fil
4343
return result;
4444
}
4545
#endif
46+
47+
#if defined(__ELF__)
48+
int swt_dl_iterate_phdr(void *context, int (*callback)(const void *dlpi_addr, const ElfW(Phdr) *dlpi_phdr, size_t dlpi_phnum, void *context)) {
49+
struct Context {
50+
void *context;
51+
decltype(callback) callback;
52+
};
53+
Context ctx = { context, callback };
54+
return dl_iterate_phdr([] (struct dl_phdr_info *info, size_t size, void *ctx) -> int {
55+
auto [context, callback] = *reinterpret_cast<const Context *>(ctx);
56+
return callback(
57+
reinterpret_cast<const void *>(info->dlpi_addr),
58+
info->dlpi_phdr,
59+
info->dlpi_phnum,
60+
context
61+
);
62+
}, &ctx);
63+
}
64+
#endif

Sources/_TestingInternals/include/Discovery.h

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -50,16 +50,9 @@ static SWTTestContentAccessor swt_resignTestContentAccessor(SWTTestContentAccess
5050
return accessor;
5151
}
5252

53-
#if defined(__ELF__) && defined(__swift__)
54-
/// A function exported by the Swift runtime that enumerates all metadata
55-
/// sections loaded into the current process.
56-
///
57-
/// This function is needed on ELF-based platforms because they do not preserve
58-
/// section information that we can discover at runtime.
59-
SWT_IMPORT_FROM_STDLIB void swift_enumerateAllMetadataSections(
60-
bool (* body)(const void *sections, void *context),
61-
void *context
62-
);
53+
#if defined(__ELF__)
54+
/// An ELF note header.
55+
typedef ElfW(Nhdr) SWTElfWNhdr;
6356
#endif
6457

6558
#if defined(SWT_NO_DYNAMIC_LINKING)

Sources/_TestingInternals/include/Stubs.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,18 @@ SWT_EXTERN int swt_pthread_setname_np(pthread_t thread, const char *name);
136136
SWT_EXTERN int swt_posix_spawn_file_actions_addclosefrom_np(posix_spawn_file_actions_t *fileActions, int from);
137137
#endif
138138

139+
#if defined(__ELF__)
140+
/// Enumerate loaded ELF images and their program headers.
141+
///
142+
/// This function is provided because `dl_iterate_phdr()` is only declared if
143+
/// `_GNU_SOURCE` is set, but setting it causes build errors due to conflicts
144+
/// with Swift's Glibc module.
145+
///
146+
/// ELF-based platforms that do not use glibc (such as FreeBSD) also use this
147+
/// function for the sake of simplicity.
148+
SWT_EXTERN int swt_dl_iterate_phdr(void *_Null_unspecified context, int (*callback)(const void *dlpi_addr, const ElfW(Phdr) *dlpi_phdr, size_t dlpi_phnum, void *_Null_unspecified context));
149+
#endif
150+
139151
#if !defined(__ANDROID__)
140152
#if __has_include(<signal.h>) && defined(si_pid)
141153
/// Get the value of the `si_pid` field of a `siginfo_t` structure.

0 commit comments

Comments
 (0)