25
25
26
26
#include < dlfcn.h>
27
27
#include < objc/runtime.h>
28
+ #include < objc/message.h>
29
+ #include < TargetConditionals.h>
28
30
29
31
// Note: There are more #includes below under "Function patching machinery".
30
32
// Those are only relevant to the function patching machinery.
31
33
34
+ // On "embedded" targets (i.e. iOS/tvOS/watchOS devices), we need to
35
+ // patch +[NSBundle bundleForClass:] directly. The symbol table patch
36
+ // does not work for calls within the shared cache on those platforms,
37
+ // so the call within +bundleForClass: does not get patched. Instead,
38
+ // swizzle out the whole method with one that does the appropriate
39
+ // lookup for Swift classes. The symbol table patch handles this on Mac
40
+ // and simulators so this is not necessary there.
41
+ #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
42
+ #define PATCH_NSBUNDLE 1
43
+ #endif
44
+
32
45
using namespace swift ;
33
46
34
47
@@ -40,12 +53,8 @@ typedef BOOL (*objc_hook_getImageName)(
40
53
// / \see customGetImageNameFromClass
41
54
static objc_hook_getImageName defaultGetImageNameFromClass = nullptr ;
42
55
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.
56
+ // / Get the image name corresponding to a Swift class, accounting for
57
+ // / dynamically-initialized class metadata. Returns NO for ObjC classes.
49
58
static BOOL
50
59
getImageNameFromSwiftClass (Class _Nonnull objcClass,
51
60
const char * _Nullable * _Nonnull outImageName) {
@@ -63,8 +72,21 @@ getImageNameFromSwiftClass(Class _Nonnull objcClass,
63
72
*outImageName = imageInfo.dli_fname ;
64
73
return imageInfo.dli_fname != nullptr ;
65
74
}
75
+
76
+ return NO ;
77
+ }
66
78
67
- // If not, fall back to the default implementation.
79
+ // / A custom implementation of Objective-C's class_getImageName for Swift
80
+ // / classes, which knows how to handle dynamically-initialized class metadata.
81
+ // /
82
+ // / Per the documentation for objc_setHook_getImageName, any non-Swift classes
83
+ // / will still go through the normal implementation of class_getImageName,
84
+ // / which is stored in defaultGetImageNameFromClass.
85
+ static BOOL
86
+ replacementGetImageNameFromClass (Class _Nonnull objcClass,
87
+ const char * _Nullable * _Nonnull outImageName) {
88
+ if (getImageNameFromSwiftClass (objcClass, outImageName))
89
+ return YES ;
68
90
return defaultGetImageNameFromClass (objcClass, outImageName);
69
91
}
70
92
@@ -100,15 +122,6 @@ namespace {
100
122
typedef struct segment_command macho_segment_command;
101
123
#endif
102
124
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
125
} // end anonymous namespace
113
126
114
127
// / Overwrite a cross-image symbol reference by directly editing symbol tables
@@ -121,7 +134,8 @@ namespace {
121
134
// /
122
135
// / Also, if the symbol being patched has references within the image where it
123
136
// / was originaly defined, those references will \e not be patched.
124
- static void patchLazyPointers (const mach_header *mh, patch_t patch) {
137
+ static void patchLazyPointers (const mach_header *mh, const char *symbolName,
138
+ const void *newValue) {
125
139
// Get linkEditBase
126
140
const uint32_t cmd_count = mh->ncmds ;
127
141
const load_command * const cmds =
@@ -205,10 +219,10 @@ static void patchLazyPointers(const mach_header *mh, patch_t patch) {
205
219
// Found symbol for this lazy pointer, now lookup address.
206
220
const char *lazyTargetName =
207
221
&stringTable[symbolTable[symbolIndex].n_un.n_strx];
208
- if (strcmp (patch. name , lazyTargetName) == 0 ) {
222
+ if (strcmp (symbolName , lazyTargetName) == 0 ) {
209
223
// Can't use the value currently stored here because it may
210
224
// be a dyld stub binder that will undo our patch if called.
211
- symbolPointers[lazyIndex] = (uintptr_t )patch. fn ;
225
+ symbolPointers[lazyIndex] = (uintptr_t )newValue ;
212
226
}
213
227
}
214
228
}
@@ -246,18 +260,73 @@ static const char *patchedGetImageNameFromClassForOldOSs(Class _Nullable cls) {
246
260
if (!cls)
247
261
return nullptr ;
248
262
const char *result;
249
- if (getImageNameFromSwiftClass (cls, &result))
263
+ if (replacementGetImageNameFromClass (cls, &result))
250
264
return result;
251
265
return nullptr ;
252
266
}
253
267
268
+ #if PATCH_NSBUNDLE
269
+ // / Selectors for the target method, patch method, and helper method.
270
+ #define BUNDLE_FOR_CLASS_SEL @selector (bundleForClass: )
271
+ #define PATCHED_BUNDLE_FOR_CLASS_SEL @selector (_swift_bundleForClass: )
272
+ #define BUNDLE_WITH_EXECUTABLE_PATH_SEL @selector (_swift_bundleWithExecutablePath: )
273
+
274
+ // / Whether the patch has already been done.
275
+ static bool didPatchNSBundle = false ;
276
+
277
+ // / The patched version of +[NSBundle bundleForClass:]. If the class is
278
+ // / actually a Swift class and an image name can be retrieved from it,
279
+ // / look up the bundle based on that image name. Otherwise fall back to
280
+ // / the original version.
281
+ static id patchedBundleForClass (id self, SEL _cmd, Class objcClass) {
282
+ const char *imageName;
283
+ if (getImageNameFromSwiftClass (objcClass, &imageName)) {
284
+ return ((id (*)(id , SEL , const char *))objc_msgSend)(
285
+ self, BUNDLE_WITH_EXECUTABLE_PATH_SEL, imageName);
286
+ }
287
+
288
+ // Call through to the original, which is now found under the patched
289
+ // selector.
290
+ return ((id (*)(id , SEL , Class ))objc_msgSend)(
291
+ self, PATCHED_BUNDLE_FOR_CLASS_SEL, objcClass);
292
+ }
293
+
294
+ // / Install the patched +[NSBundle bundleForClass:].
295
+ static void patchNSBundle (void ) {
296
+ if (didPatchNSBundle) return ;
297
+
298
+ Class NSBundle = objc_getClass (" NSBundle" );
299
+ if (!NSBundle ) return ;
300
+
301
+ Method origMethod = class_getClassMethod (NSBundle , BUNDLE_FOR_CLASS_SEL);
302
+ if (!origMethod) return ;
303
+
304
+ // Stuff can fail below, but if it does then we can't reasonably try again.
305
+ didPatchNSBundle = true ;
306
+
307
+ BOOL success = class_addMethod (
308
+ object_getClass (NSBundle ), PATCHED_BUNDLE_FOR_CLASS_SEL,
309
+ reinterpret_cast <IMP >(patchedBundleForClass), method_getTypeEncoding (origMethod));
310
+ if (!success) return ;
311
+
312
+ Method patchMethod = class_getClassMethod (NSBundle , PATCHED_BUNDLE_FOR_CLASS_SEL);
313
+ if (!patchMethod) return ;
314
+
315
+ method_exchangeImplementations (origMethod, patchMethod);
316
+ }
317
+ #endif
318
+
254
319
// / A hook for _dyld_register_func_for_add_image that overwrites any references
255
320
// / to class_getImageName with our custom implementation.
256
321
static void patchGetImageNameInImage (const struct mach_header *mh,
257
322
intptr_t vmaddr_slide) {
258
323
(void )vmaddr_slide;
259
- patchLazyPointers (mh, patch_t (" _class_getImageName" ,
260
- &patchedGetImageNameFromClassForOldOSs));
324
+ const void *newImplementationAddr =
325
+ reinterpret_cast <const void *>(&patchedGetImageNameFromClassForOldOSs);
326
+ patchLazyPointers (mh, " _class_getImageName" , newImplementationAddr);
327
+ #if PATCH_NSBUNDLE
328
+ patchNSBundle ();
329
+ #endif
261
330
}
262
331
263
332
/* **************************************************************************/
@@ -274,7 +343,7 @@ void swift::setUpObjCRuntimeGetImageNameFromClass() {
274
343
auto setHook = reinterpret_cast <
275
344
void (*)(objc_hook_getImageName _Nonnull,
276
345
objc_hook_getImageName _Nullable * _Nonnull)>(setHookPtr);
277
- setHook (getImageNameFromSwiftClass , &defaultGetImageNameFromClass);
346
+ setHook (replacementGetImageNameFromClass , &defaultGetImageNameFromClass);
278
347
279
348
} else {
280
349
// On older OSs, manually patch in our new implementation of
0 commit comments