Skip to content

Commit 9277281

Browse files
authored
[runtime] Backwards-deployment support for class_getImageName (#18076)
Merge pull request #18060 from jrose-apple/and-you-get-a-class_getImageName https://bugs.swift.org/browse/SR-1917 rdar://problem/41535552 (cherry picked from commit 6f42fcc)
1 parent cae57f4 commit 9277281

File tree

7 files changed

+354
-67
lines changed

7 files changed

+354
-67
lines changed

lib/Driver/ToolChains.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1303,13 +1303,14 @@ static bool wantsObjCRuntime(const llvm::Triple &triple) {
13031303
// When updating the versions listed here, please record the most recent
13041304
// feature being depended on and when it was introduced:
13051305
//
1306-
// - The hook to override class_getImageName (macOS 10.14 and equivalent)
1306+
// - Make assigning 'nil' to an NSMutableDictionary subscript delete the
1307+
// entry, like it does for Swift.Dictionary, rather than trap.
13071308
if (triple.isiOS())
1308-
return triple.isOSVersionLT(12);
1309+
return triple.isOSVersionLT(9);
13091310
if (triple.isMacOSX())
1310-
return triple.isMacOSXVersionLT(10, 14);
1311+
return triple.isMacOSXVersionLT(10, 11);
13111312
if (triple.isWatchOS())
1312-
return triple.isOSVersionLT(5);
1313+
return false;
13131314
llvm_unreachable("unknown Darwin OS");
13141315
}
13151316

stdlib/public/runtime/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ set(swift_runtime_objc_sources
3131
SwiftObject.mm
3232
SwiftValue.mm
3333
ReflectionMirror.mm
34+
ObjCRuntimeGetImageNameFromClass.cpp
3435
"${SWIFT_SOURCE_DIR}/lib/Demangling/OldRemangler.cpp"
3536
"${SWIFT_SOURCE_DIR}/lib/Demangling/Remangler.cpp"
3637
"${SWIFT_SOURCE_DIR}/lib/Demangling/TypeDecoder.cpp"

stdlib/public/runtime/Metadata.cpp

Lines changed: 2 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,7 @@
5555
#endif
5656

5757
#if SWIFT_OBJC_INTEROP
58-
#include <dlfcn.h>
59-
#include <objc/runtime.h>
58+
#include "ObjCRuntimeGetImageNameFromClass.h"
6059
#endif
6160

6261
#include <cstdio>
@@ -2019,45 +2018,6 @@ swift::swift_relocateClassMetadata(ClassMetadata *self,
20192018
return self;
20202019
}
20212020

2022-
#if SWIFT_OBJC_INTEROP
2023-
2024-
// FIXME: This is from a later version of <objc/runtime.h>. Once the declaration
2025-
// is available in SDKs, we can remove this typedef.
2026-
typedef BOOL (*objc_hook_getImageName)(
2027-
Class _Nonnull cls, const char * _Nullable * _Nonnull outImageName);
2028-
2029-
/// \see customGetImageNameFromClass
2030-
static objc_hook_getImageName defaultGetImageNameFromClass = nullptr;
2031-
2032-
/// A custom implementation of Objective-C's class_getImageName for Swift
2033-
/// classes, which knows how to handle dynamically-initialized class metadata.
2034-
///
2035-
/// Per the documentation for objc_setHook_getImageName, any non-Swift classes
2036-
/// will still go through the normal implementation of class_getImageName,
2037-
/// which is stored in defaultGetImageNameFromClass.
2038-
static BOOL
2039-
customGetImageNameFromClass(Class _Nonnull objcClass,
2040-
const char * _Nullable * _Nonnull outImageName) {
2041-
auto *classAsMetadata = reinterpret_cast<const ClassMetadata *>(objcClass);
2042-
2043-
// Is this a Swift class?
2044-
if (classAsMetadata->isTypeMetadata() &&
2045-
!classAsMetadata->isArtificialSubclass()) {
2046-
const void *descriptor = classAsMetadata->getDescription();
2047-
assert(descriptor &&
2048-
"all non-artificial Swift classes should have a descriptor");
2049-
Dl_info imageInfo = {};
2050-
if (!dladdr(descriptor, &imageInfo))
2051-
return NO;
2052-
*outImageName = imageInfo.dli_fname;
2053-
return imageInfo.dli_fname != nullptr;
2054-
}
2055-
2056-
// If not, fall back to the default implementation.
2057-
return defaultGetImageNameFromClass(objcClass, outImageName);
2058-
}
2059-
#endif
2060-
20612021
/// Initialize the field offset vector for a dependent-layout class, using the
20622022
/// "Universal" layout strategy.
20632023
void
@@ -2071,15 +2031,7 @@ swift::swift_initClassMetadata(ClassMetadata *self,
20712031
static swift_once_t onceToken;
20722032
swift_once(&onceToken, [](void *unused) {
20732033
(void)unused;
2074-
// FIXME: This is from a later version of <objc/runtime.h>. Once the
2075-
// declaration is available in SDKs, we can access this directly instead of
2076-
// using dlsym.
2077-
if (void *setHookPtr = dlsym(RTLD_DEFAULT, "objc_setHook_getImageName")) {
2078-
auto setHook = reinterpret_cast<
2079-
void(*)(objc_hook_getImageName _Nonnull,
2080-
objc_hook_getImageName _Nullable * _Nonnull)>(setHookPtr);
2081-
setHook(customGetImageNameFromClass, &defaultGetImageNameFromClass);
2082-
}
2034+
setUpObjCRuntimeGetImageNameFromClass();
20832035
}, nullptr);
20842036
#endif
20852037

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
//===--- ObjCRuntimeGetImageNameFromClass.cpp - ObjC hook setup -----------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2018 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+
// Setup for the Objective-C runtime function class_getImageName, making it
14+
// understand Swift classes. This is tricky because before Apple's 2018 OSs,
15+
// this function was not designed to be hooked.
16+
//
17+
//===----------------------------------------------------------------------===//
18+
19+
#include "ObjCRuntimeGetImageNameFromClass.h"
20+
#include "swift/Runtime/Config.h"
21+
22+
#if SWIFT_OBJC_INTEROP
23+
24+
#include "swift/Runtime/Metadata.h"
25+
26+
#include <dlfcn.h>
27+
#include <objc/runtime.h>
28+
29+
// Note: There are more #includes below under "Function patching machinery".
30+
// Those are only relevant to the function patching machinery.
31+
32+
using namespace swift;
33+
34+
35+
// FIXME: This is from a later version of <objc/runtime.h>. Once the declaration
36+
// is available in SDKs, we can remove this typedef.
37+
typedef BOOL (*objc_hook_getImageName)(
38+
Class _Nonnull cls, const char * _Nullable * _Nonnull outImageName);
39+
40+
/// \see customGetImageNameFromClass
41+
static objc_hook_getImageName defaultGetImageNameFromClass = nullptr;
42+
43+
/// A custom implementation of Objective-C's class_getImageName for Swift
44+
/// classes, which knows how to handle dynamically-initialized class metadata.
45+
///
46+
/// Per the documentation for objc_setHook_getImageName, any non-Swift classes
47+
/// will still go through the normal implementation of class_getImageName,
48+
/// which is stored in defaultGetImageNameFromClass.
49+
static BOOL
50+
getImageNameFromSwiftClass(Class _Nonnull objcClass,
51+
const char * _Nullable * _Nonnull outImageName) {
52+
auto *classAsMetadata = reinterpret_cast<const ClassMetadata *>(objcClass);
53+
54+
// Is this a Swift class?
55+
if (classAsMetadata->isTypeMetadata() &&
56+
!classAsMetadata->isArtificialSubclass()) {
57+
const void *descriptor = classAsMetadata->getDescription();
58+
assert(descriptor &&
59+
"all non-artificial Swift classes should have a descriptor");
60+
Dl_info imageInfo = {};
61+
if (!dladdr(descriptor, &imageInfo))
62+
return NO;
63+
*outImageName = imageInfo.dli_fname;
64+
return imageInfo.dli_fname != nullptr;
65+
}
66+
67+
// If not, fall back to the default implementation.
68+
return defaultGetImageNameFromClass(objcClass, outImageName);
69+
}
70+
71+
/***************************************************************************/
72+
/* Function patching machinery *********************************************/
73+
/***************************************************************************/
74+
75+
#include "llvm/ADT/ArrayRef.h"
76+
77+
#include <mach-o/dyld.h>
78+
#include <mach-o/loader.h>
79+
#include <mach-o/nlist.h>
80+
81+
#include <cstring>
82+
83+
using llvm::ArrayRef;
84+
85+
namespace {
86+
87+
#if __LP64__
88+
# define LC_SEGMENT_COMMAND LC_SEGMENT_64
89+
# define LC_ROUTINES_COMMAND LC_ROUTINES_64
90+
typedef struct mach_header_64 macho_header;
91+
typedef struct section_64 macho_section;
92+
typedef struct nlist_64 macho_nlist;
93+
typedef struct segment_command_64 macho_segment_command;
94+
#else
95+
# define LC_SEGMENT_COMMAND LC_SEGMENT
96+
# define LC_ROUTINES_COMMAND LC_ROUTINES
97+
typedef struct mach_header macho_header;
98+
typedef struct section macho_section;
99+
typedef struct nlist macho_nlist;
100+
typedef struct segment_command macho_segment_command;
101+
#endif
102+
103+
struct patch_t {
104+
const char *name;
105+
const void *fn;
106+
107+
template<typename T>
108+
patch_t(const char *newName, const T *newFn)
109+
: name(newName), fn((const void*)newFn) {
110+
}
111+
};
112+
} // end anonymous namespace
113+
114+
/// Overwrite a cross-image symbol reference by directly editing symbol tables
115+
/// in a Mach-O image.
116+
///
117+
/// This technique only works for certain versions of Apple's dynamic linker;
118+
/// fortunately we only even attempt to invoke it when running on the OSs where
119+
/// it works. Newer OSs already have the hook we need; older ones don't support
120+
/// Swift at all.
121+
///
122+
/// Also, if the symbol being patched has references within the image where it
123+
/// was originaly defined, those references will \e not be patched.
124+
static void patchLazyPointers(const mach_header *mh, patch_t patch) {
125+
// Get linkEditBase
126+
const uint32_t cmd_count = mh->ncmds;
127+
const load_command * const cmds =
128+
(const load_command *)((const char *)mh + sizeof(macho_header));
129+
const load_command *cmd;
130+
131+
const uint8_t *linkEditBase = nullptr;
132+
intptr_t slide = 0;
133+
134+
cmd = cmds;
135+
for (uint32_t i = 0; i < cmd_count; ++i) {
136+
if (cmd->cmd == LC_SEGMENT_COMMAND) {
137+
const macho_segment_command *seg = (const macho_segment_command *)cmd;
138+
if (strcmp(seg->segname, "__TEXT") == 0)
139+
slide = (uintptr_t)mh - seg->vmaddr;
140+
else if (strcmp(seg->segname,"__LINKEDIT") == 0)
141+
linkEditBase = (const uint8_t *)(seg->vmaddr + slide - seg->fileoff);
142+
}
143+
cmd = (const load_command *)(((const char *)cmd)+cmd->cmdsize);
144+
}
145+
if (linkEditBase == nullptr)
146+
return;
147+
148+
// Gather symbol table info
149+
const macho_nlist *symbolTable = nullptr;
150+
const char *stringTable = nullptr;
151+
uint32_t stringTableBytes = 0;
152+
const uint32_t *indirectSymbolTable = nullptr;
153+
154+
cmd = cmds;
155+
for (uint32_t i = 0; i < cmd_count; ++i) {
156+
switch (cmd->cmd) {
157+
case LC_SYMTAB: {
158+
const symtab_command *symtab = (const symtab_command *)cmd;
159+
stringTable = (const char *)&linkEditBase[symtab->stroff];
160+
stringTableBytes = symtab->strsize;
161+
symbolTable = (const macho_nlist *)(&linkEditBase[symtab->symoff]);
162+
break;
163+
}
164+
case LC_DYSYMTAB: {
165+
const dysymtab_command *dsymtab = (const dysymtab_command *)cmd;
166+
indirectSymbolTable =
167+
(const uint32_t *)(&linkEditBase[dsymtab->indirectsymoff]);
168+
break;
169+
}
170+
default:
171+
break;
172+
}
173+
cmd = (const load_command *)(((const char *)cmd)+cmd->cmdsize);
174+
}
175+
if (symbolTable == nullptr || stringTable == nullptr ||
176+
indirectSymbolTable == nullptr) {
177+
return;
178+
}
179+
180+
// Find lazy pointer section
181+
cmd = cmds;
182+
for (uint32_t i = 0; i < cmd_count; ++i) {
183+
if (cmd->cmd == LC_SEGMENT_COMMAND) {
184+
const macho_segment_command *seg = (const macho_segment_command *)cmd;
185+
const macho_section * const sectionsStart =
186+
(const macho_section *)(seg + 1);
187+
ArrayRef<macho_section> sections(sectionsStart, seg->nsects);
188+
189+
for (const macho_section &sect : sections) {
190+
const uint8_t type = sect.flags & SECTION_TYPE;
191+
if (type != S_LAZY_SYMBOL_POINTERS)
192+
continue;
193+
194+
const size_t pointerCount = sect.size / sizeof(uintptr_t);
195+
uintptr_t * const symbolPointers = (uintptr_t *)(sect.addr + slide);
196+
const uint32_t indirectTableOffset = sect.reserved1;
197+
for (uint32_t lazyIndex = 0; lazyIndex < pointerCount; ++lazyIndex) {
198+
uint32_t symbolIndex =
199+
indirectSymbolTable[indirectTableOffset + lazyIndex];
200+
if (symbolIndex >= stringTableBytes) {
201+
// Presumably INDIRECT_SYMBOL_LOCAL or some other special value.
202+
continue;
203+
}
204+
205+
// Found symbol for this lazy pointer, now lookup address.
206+
const char *lazyTargetName =
207+
&stringTable[symbolTable[symbolIndex].n_un.n_strx];
208+
if (strcmp(patch.name, lazyTargetName) == 0) {
209+
// Can't use the value currently stored here because it may
210+
// be a dyld stub binder that will undo our patch if called.
211+
symbolPointers[lazyIndex] = (uintptr_t)patch.fn;
212+
}
213+
}
214+
}
215+
}
216+
cmd = (const load_command *)(((const char *)cmd)+cmd->cmdsize);
217+
}
218+
}
219+
220+
/// \see callUnpatchedGetImageNameFromClass
221+
static decltype(&class_getImageName) unpatchedGetImageNameFromClass = nullptr;
222+
223+
/// A fallback implementation of class_getImageName that just calls
224+
/// unpatchedGetImageNameFromClass, with the signature of
225+
/// objc_hook_getImageName.
226+
///
227+
/// This is used on older OSs where objc_setHook_getImageName isn't supported.
228+
/// In this case, we invoke the Swift implementation above
229+
/// (customGetImageNameFromClass), but set it up to fall back to this one.
230+
/// Then this one can call the system's original version, which should be stored
231+
/// in unpatchedGetImageNameFromClass.
232+
static BOOL callUnpatchedGetImageNameFromClass(
233+
Class _Nonnull objcClass, const char * _Nullable * _Nonnull outImageName) {
234+
*outImageName = unpatchedGetImageNameFromClass(objcClass);
235+
return outImageName != nullptr;
236+
}
237+
238+
/// A patched version of class_getImageName that always uses the Swift
239+
/// implementation.
240+
///
241+
/// The Swift implementation is always set up to chain to another
242+
/// implementation, so on older OSs we just have to make sure that that chained
243+
/// implementation is the original system version. See
244+
/// callUnpatchedGetImageNameFromClass.
245+
static const char *patchedGetImageNameFromClassForOldOSs(Class _Nullable cls) {
246+
if (!cls)
247+
return nullptr;
248+
const char *result;
249+
if (getImageNameFromSwiftClass(cls, &result))
250+
return result;
251+
return nullptr;
252+
}
253+
254+
/// A hook for _dyld_register_func_for_add_image that overwrites any references
255+
/// to class_getImageName with our custom implementation.
256+
static void patchGetImageNameInImage(const struct mach_header *mh,
257+
intptr_t vmaddr_slide) {
258+
(void)vmaddr_slide;
259+
patchLazyPointers(mh, patch_t("_class_getImageName",
260+
&patchedGetImageNameFromClassForOldOSs));
261+
}
262+
263+
/***************************************************************************/
264+
/* Installing the hook *****************************************************/
265+
/***************************************************************************/
266+
267+
void swift::setUpObjCRuntimeGetImageNameFromClass() {
268+
assert(defaultGetImageNameFromClass == nullptr && "already set up");
269+
270+
// FIXME: This is from a later version of <objc/runtime.h>. Once the
271+
// declaration is available in SDKs, we can access this directly instead of
272+
// using dlsym.
273+
if (void *setHookPtr = dlsym(RTLD_DEFAULT, "objc_setHook_getImageName")) {
274+
auto setHook = reinterpret_cast<
275+
void(*)(objc_hook_getImageName _Nonnull,
276+
objc_hook_getImageName _Nullable * _Nonnull)>(setHookPtr);
277+
setHook(getImageNameFromSwiftClass, &defaultGetImageNameFromClass);
278+
279+
} else {
280+
// On older OSs, manually patch in our new implementation of
281+
// class_getImageName, and set it up to chain to the original system
282+
// version.
283+
284+
// This assignment happens through a volatile pointer to make sure it occurs
285+
// before the later call to _dyld_register_func_for_add_image. (More
286+
// specifically, we need the original implementation of
287+
// 'class_getImageName', not the replaced one.)
288+
assert(unpatchedGetImageNameFromClass == nullptr);
289+
volatile auto *originalImplementationPtr = &unpatchedGetImageNameFromClass;
290+
*originalImplementationPtr = &class_getImageName;
291+
defaultGetImageNameFromClass = callUnpatchedGetImageNameFromClass;
292+
293+
_dyld_register_func_for_add_image(&patchGetImageNameInImage);
294+
}
295+
}
296+
297+
#endif // SWIFT_OBJC_INTEROP

0 commit comments

Comments
 (0)