|
| 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 § : 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