Skip to content

Commit a2c1aa3

Browse files
committed
[runtime] Backwards-deployment support for class_getImageName
Follow-up to 3ed3774. On Apple OSs that don't have the new Objective-C runtime function 'objc_setHook_getImageName', override the system definition of 'class_getImageName' by literally rewriting symbol tables at run time. Yes, you read that correctly. The low-level part of this patch was written by Greg Parker, then simplified and tweaked by me to fit the Swift coding style. Don't try this at home; it comes with all sorts of caveats and won't actually work on this year's iOS. (Fortunately we don't need it there, because that will have the new ObjC entry point.) The rest of the patch is pretty straightforward: the replacement implementation calls the code that supports Swift objects (the same code we use on newer OSs), which then chains back to the original system implementation of class_getImageName. May we never have to touch this again. rdar://problem/41535552
1 parent 6568feb commit a2c1aa3

File tree

5 files changed

+343
-55
lines changed

5 files changed

+343
-55
lines changed

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>
@@ -1976,45 +1975,6 @@ swift::swift_relocateClassMetadata(ClassMetadata *self,
19761975
return self;
19771976
}
19781977

1979-
#if SWIFT_OBJC_INTEROP
1980-
1981-
// FIXME: This is from a later version of <objc/runtime.h>. Once the declaration
1982-
// is available in SDKs, we can remove this typedef.
1983-
typedef BOOL (*objc_hook_getImageName)(
1984-
Class _Nonnull cls, const char * _Nullable * _Nonnull outImageName);
1985-
1986-
/// \see customGetImageNameFromClass
1987-
static objc_hook_getImageName defaultGetImageNameFromClass = nullptr;
1988-
1989-
/// A custom implementation of Objective-C's class_getImageName for Swift
1990-
/// classes, which knows how to handle dynamically-initialized class metadata.
1991-
///
1992-
/// Per the documentation for objc_setHook_getImageName, any non-Swift classes
1993-
/// will still go through the normal implementation of class_getImageName,
1994-
/// which is stored in defaultGetImageNameFromClass.
1995-
static BOOL
1996-
customGetImageNameFromClass(Class _Nonnull objcClass,
1997-
const char * _Nullable * _Nonnull outImageName) {
1998-
auto *classAsMetadata = reinterpret_cast<const ClassMetadata *>(objcClass);
1999-
2000-
// Is this a Swift class?
2001-
if (classAsMetadata->isTypeMetadata() &&
2002-
!classAsMetadata->isArtificialSubclass()) {
2003-
const void *descriptor = classAsMetadata->getDescription();
2004-
assert(descriptor &&
2005-
"all non-artificial Swift classes should have a descriptor");
2006-
Dl_info imageInfo = {};
2007-
if (!dladdr(descriptor, &imageInfo))
2008-
return NO;
2009-
*outImageName = imageInfo.dli_fname;
2010-
return imageInfo.dli_fname != nullptr;
2011-
}
2012-
2013-
// If not, fall back to the default implementation.
2014-
return defaultGetImageNameFromClass(objcClass, outImageName);
2015-
}
2016-
#endif
2017-
20181978
/// Initialize the field offset vector for a dependent-layout class, using the
20191979
/// "Universal" layout strategy.
20201980
void
@@ -2028,15 +1988,7 @@ swift::swift_initClassMetadata(ClassMetadata *self,
20281988
static swift_once_t onceToken;
20291989
swift_once(&onceToken, [](void *unused) {
20301990
(void)unused;
2031-
// FIXME: This is from a later version of <objc/runtime.h>. Once the
2032-
// declaration is available in SDKs, we can access this directly instead of
2033-
// using dlsym.
2034-
if (void *setHookPtr = dlsym(RTLD_DEFAULT, "objc_setHook_getImageName")) {
2035-
auto setHook = reinterpret_cast<
2036-
void(*)(objc_hook_getImageName _Nonnull,
2037-
objc_hook_getImageName _Nullable * _Nonnull)>(setHookPtr);
2038-
setHook(customGetImageNameFromClass, &defaultGetImageNameFromClass);
2039-
}
1991+
setUpObjCRuntimeGetImageNameFromClass();
20401992
}, nullptr);
20411993
#endif
20421994

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)