objc-block-trampolines.mm 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  1. /*
  2. * Copyright (c) 2010 Apple Inc. All Rights Reserved.
  3. *
  4. * @APPLE_LICENSE_HEADER_START@
  5. *
  6. * This file contains Original Code and/or Modifications of Original Code
  7. * as defined in and that are subject to the Apple Public Source License
  8. * Version 2.0 (the 'License'). You may not use this file except in
  9. * compliance with the License. Please obtain a copy of the License at
  10. * http://www.opensource.apple.com/apsl/ and read it before using this
  11. * file.
  12. *
  13. * The Original Code and all software distributed under the License are
  14. * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
  15. * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
  16. * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
  17. * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
  18. * Please see the License for the specific language governing rights and
  19. * limitations under the License.
  20. *
  21. * @APPLE_LICENSE_HEADER_END@
  22. */
  23. /***********************************************************************
  24. * objc-block-trampolines.m
  25. * Author: b.bum
  26. *
  27. **********************************************************************/
  28. /***********************************************************************
  29. * Imports.
  30. **********************************************************************/
  31. #include "objc-private.h"
  32. #include "runtime.h"
  33. #include <Block.h>
  34. #include <Block_private.h>
  35. #include <mach/mach.h>
  36. #include <objc/objc-block-trampolines.h>
  37. // fixme C++ compilers don't implemement memory_order_consume efficiently.
  38. // Use memory_order_relaxed and cross our fingers.
  39. #define MEMORY_ORDER_CONSUME std::memory_order_relaxed
  40. // 8 bytes of text and data per trampoline on all architectures.
  41. #define SLOT_SIZE 8
  42. // The trampolines are defined in assembly files in libobjc-trampolines.dylib.
  43. // We can't link to libobjc-trampolines.dylib directly because
  44. // for security reasons it isn't in the dyld shared cache.
  45. // Trampoline addresses are lazily looked up.
  46. // All of them are hidden behind a single atomic pointer for lock-free init.
  47. #ifdef __PTRAUTH_INTRINSICS__
  48. # define TrampolinePtrauth __ptrauth(ptrauth_key_function_pointer, 1, 0x3af1)
  49. #else
  50. # define TrampolinePtrauth
  51. #endif
  52. class TrampolinePointerWrapper {
  53. struct TrampolinePointers {
  54. class TrampolineAddress {
  55. const void * TrampolinePtrauth storage;
  56. public:
  57. TrampolineAddress(void *dylib, const char *name) {
  58. #define PREFIX "_objc_blockTrampoline"
  59. char symbol[strlen(PREFIX) + strlen(name) + 1];
  60. strcpy(symbol, PREFIX);
  61. strcat(symbol, name);
  62. // dlsym() from a text segment returns a signed pointer
  63. // Authenticate it manually and let the compiler re-sign it.
  64. storage = ptrauth_auth_data(dlsym(dylib, symbol),
  65. ptrauth_key_function_pointer, 0);
  66. if (!storage) {
  67. _objc_fatal("couldn't dlsym %s", symbol);
  68. }
  69. }
  70. uintptr_t address() {
  71. return (uintptr_t)(void*)storage;
  72. }
  73. };
  74. TrampolineAddress impl; // trampoline header code
  75. TrampolineAddress start; // first trampoline
  76. #if DEBUG
  77. // These symbols are only used in assertions.
  78. // fixme might be able to move the assertions to libobjc-trampolines itself
  79. TrampolineAddress last; // start of the last trampoline
  80. // We don't use the address after the last trampoline because that
  81. // address might be in a different section, and then dlsym() would not
  82. // sign it as a function pointer.
  83. # if SUPPORT_STRET
  84. TrampolineAddress impl_stret;
  85. TrampolineAddress start_stret;
  86. TrampolineAddress last_stret;
  87. # endif
  88. #endif
  89. uintptr_t textSegment;
  90. uintptr_t textSegmentSize;
  91. void check() {
  92. #if DEBUG
  93. ASSERT(impl.address() == textSegment + PAGE_MAX_SIZE);
  94. ASSERT(impl.address() % PAGE_SIZE == 0); // not PAGE_MAX_SIZE
  95. assert(impl.address() + PAGE_MAX_SIZE ==
  96. last.address() + SLOT_SIZE);
  97. ASSERT(last.address()+8 < textSegment + textSegmentSize);
  98. ASSERT((last.address() - start.address()) % SLOT_SIZE == 0);
  99. # if SUPPORT_STRET
  100. ASSERT(impl_stret.address() == textSegment + 2*PAGE_MAX_SIZE);
  101. ASSERT(impl_stret.address() % PAGE_SIZE == 0); // not PAGE_MAX_SIZE
  102. assert(impl_stret.address() + PAGE_MAX_SIZE ==
  103. last_stret.address() + SLOT_SIZE);
  104. assert(start.address() - impl.address() ==
  105. start_stret.address() - impl_stret.address());
  106. assert(last_stret.address() + SLOT_SIZE <
  107. textSegment + textSegmentSize);
  108. assert((last_stret.address() - start_stret.address())
  109. % SLOT_SIZE == 0);
  110. # endif
  111. #endif
  112. }
  113. TrampolinePointers(void *dylib)
  114. : impl(dylib, "Impl")
  115. , start(dylib, "Start")
  116. #if DEBUG
  117. , last(dylib, "Last")
  118. # if SUPPORT_STRET
  119. , impl_stret(dylib, "Impl_stret")
  120. , start_stret(dylib, "Start_stret")
  121. , last_stret(dylib, "Last_stret")
  122. # endif
  123. #endif
  124. {
  125. const auto *mh =
  126. dyld_image_header_containing_address((void *)impl.address());
  127. unsigned long size = 0;
  128. textSegment = (uintptr_t)
  129. getsegmentdata((headerType *)mh, "__TEXT", &size);
  130. textSegmentSize = size;
  131. check();
  132. }
  133. };
  134. std::atomic<TrampolinePointers *> trampolines{nil};
  135. TrampolinePointers *get() {
  136. return trampolines.load(MEMORY_ORDER_CONSUME);
  137. }
  138. public:
  139. void Initialize() {
  140. if (get()) return;
  141. // This code may be called concurrently.
  142. // In the worst case we perform extra dyld operations.
  143. void *dylib = dlopen("/usr/lib/libobjc-trampolines.dylib",
  144. RTLD_NOW | RTLD_LOCAL | RTLD_FIRST);
  145. if (!dylib) {
  146. _objc_fatal("couldn't dlopen libobjc-trampolines.dylib: %s",
  147. dlerror());
  148. }
  149. auto t = new TrampolinePointers(dylib);
  150. TrampolinePointers *old = nil;
  151. if (! trampolines.compare_exchange_strong(old, t, memory_order_release))
  152. {
  153. delete t; // Lost an initialization race.
  154. }
  155. }
  156. uintptr_t textSegment() { return get()->textSegment; }
  157. uintptr_t textSegmentSize() { return get()->textSegmentSize; }
  158. // See comments below about PAGE_SIZE and PAGE_MAX_SIZE.
  159. uintptr_t dataSize() { return PAGE_MAX_SIZE; }
  160. uintptr_t impl() { return get()->impl.address(); }
  161. uintptr_t start() { return get()->start.address(); }
  162. };
  163. static TrampolinePointerWrapper Trampolines;
  164. // argument mode identifier
  165. // Some calculations assume that these modes are sequential starting from 0.
  166. // This order must match the order of the trampoline's assembly code.
  167. typedef enum {
  168. ReturnValueInRegisterArgumentMode,
  169. #if SUPPORT_STRET
  170. ReturnValueOnStackArgumentMode,
  171. #endif
  172. ArgumentModeCount
  173. } ArgumentMode;
  174. // We must take care with our data layout on architectures that support
  175. // multiple page sizes.
  176. //
  177. // The trampoline template in __TEXT is sized and aligned with PAGE_MAX_SIZE.
  178. // On some platforms this requires additional linker flags.
  179. //
  180. // When we allocate a page group, we use PAGE_MAX_SIZE size.
  181. // This allows trampoline code to find its data by subtracting PAGE_MAX_SIZE.
  182. //
  183. // When we allocate a page group, we use the process's page alignment.
  184. // This simplifies allocation because we don't need to force greater than
  185. // default alignment when running with small pages, but it also means
  186. // the trampoline code MUST NOT look for its data by masking with PAGE_MAX_MASK.
  187. struct TrampolineBlockPageGroup
  188. {
  189. TrampolineBlockPageGroup *nextPageGroup; // linked list of all pages
  190. TrampolineBlockPageGroup *nextAvailablePage; // linked list of pages with available slots
  191. uintptr_t nextAvailable; // index of next available slot, endIndex() if no more available
  192. const void * TrampolinePtrauth const text; // text VM region; stored only for the benefit of the leaks tool
  193. TrampolineBlockPageGroup()
  194. : nextPageGroup(nil)
  195. , nextAvailablePage(nil)
  196. , nextAvailable(startIndex())
  197. , text((const void *)((uintptr_t)this + Trampolines.dataSize()))
  198. { }
  199. // Payload data: block pointers and free list.
  200. // Bytes parallel with trampoline header code are the fields above or unused
  201. // uint8_t payloads[PAGE_MAX_SIZE - sizeof(TrampolineBlockPageGroup)]
  202. // Code: Mach-O header, then trampoline header followed by trampolines.
  203. // On platforms with struct return we have non-stret trampolines and
  204. // stret trampolines. The stret and non-stret trampolines at a given
  205. // index share the same data page.
  206. // uint8_t macho[PAGE_MAX_SIZE];
  207. // uint8_t trampolines[ArgumentModeCount][PAGE_MAX_SIZE];
  208. // Per-trampoline block data format:
  209. // initial value is 0 while page data is filled sequentially
  210. // when filled, value is reference to Block_copy()d block
  211. // when empty, value is index of next available slot OR 0 if never used yet
  212. union Payload {
  213. id block;
  214. uintptr_t nextAvailable; // free list
  215. };
  216. static uintptr_t headerSize() {
  217. return (uintptr_t) (Trampolines.start() - Trampolines.impl());
  218. }
  219. static uintptr_t slotSize() {
  220. return SLOT_SIZE;
  221. }
  222. static uintptr_t startIndex() {
  223. // headerSize is assumed to be slot-aligned
  224. return headerSize() / slotSize();
  225. }
  226. static uintptr_t endIndex() {
  227. return (uintptr_t)Trampolines.dataSize() / slotSize();
  228. }
  229. static bool validIndex(uintptr_t index) {
  230. return (index >= startIndex() && index < endIndex());
  231. }
  232. Payload *payload(uintptr_t index) {
  233. ASSERT(validIndex(index));
  234. return (Payload *)((char *)this + index*slotSize());
  235. }
  236. uintptr_t trampolinesForMode(int aMode) {
  237. // Skip over the data area, one page of Mach-O headers,
  238. // and one text page for each mode before this one.
  239. return (uintptr_t)this + Trampolines.dataSize() +
  240. PAGE_MAX_SIZE * (1 + aMode);
  241. }
  242. IMP trampoline(int aMode, uintptr_t index) {
  243. ASSERT(validIndex(index));
  244. char *base = (char *)trampolinesForMode(aMode);
  245. char *imp = base + index*slotSize();
  246. #if __arm__
  247. imp++; // trampoline is Thumb instructions
  248. #endif
  249. #if __has_feature(ptrauth_calls)
  250. imp = ptrauth_sign_unauthenticated(imp,
  251. ptrauth_key_function_pointer, 0);
  252. #endif
  253. return (IMP)imp;
  254. }
  255. uintptr_t indexForTrampoline(uintptr_t tramp) {
  256. for (int aMode = 0; aMode < ArgumentModeCount; aMode++) {
  257. uintptr_t base = trampolinesForMode(aMode);
  258. uintptr_t start = base + startIndex() * slotSize();
  259. uintptr_t end = base + endIndex() * slotSize();
  260. if (tramp >= start && tramp < end) {
  261. return (uintptr_t)(tramp - base) / slotSize();
  262. }
  263. }
  264. return 0;
  265. }
  266. static void check() {
  267. ASSERT(TrampolineBlockPageGroup::headerSize() >= sizeof(TrampolineBlockPageGroup));
  268. ASSERT(TrampolineBlockPageGroup::headerSize() % TrampolineBlockPageGroup::slotSize() == 0);
  269. }
  270. };
  271. static TrampolineBlockPageGroup *HeadPageGroup;
  272. #pragma mark Utility Functions
  273. #if !__OBJC2__
  274. #define runtimeLock classLock
  275. #endif
  276. #pragma mark Trampoline Management Functions
  277. static TrampolineBlockPageGroup *_allocateTrampolinesAndData()
  278. {
  279. runtimeLock.assertLocked();
  280. vm_address_t dataAddress;
  281. TrampolineBlockPageGroup::check();
  282. // Our final mapping will look roughly like this:
  283. // r/w data
  284. // r/o text mapped from libobjc-trampolines.dylib
  285. // with fixed offsets from the text to the data embedded in the text.
  286. //
  287. // More precisely it will look like this:
  288. // 1 page r/w data
  289. // 1 page libobjc-trampolines.dylib Mach-O header
  290. // N pages trampoline code, one for each ArgumentMode
  291. // M pages for the rest of libobjc-trampolines' TEXT segment.
  292. // The kernel requires that we remap the entire TEXT segment every time.
  293. // We assume that our code begins on the second TEXT page, but are robust
  294. // against other additions to the end of the TEXT segment.
  295. ASSERT(HeadPageGroup == nil || HeadPageGroup->nextAvailablePage == nil);
  296. auto textSource = Trampolines.textSegment();
  297. auto textSourceSize = Trampolines.textSegmentSize();
  298. auto dataSize = Trampolines.dataSize();
  299. // Allocate a single contiguous region big enough to hold data+text.
  300. kern_return_t result;
  301. result = vm_allocate(mach_task_self(), &dataAddress,
  302. dataSize + textSourceSize,
  303. VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_FOUNDATION));
  304. if (result != KERN_SUCCESS) {
  305. _objc_fatal("vm_allocate trampolines failed (%d)", result);
  306. }
  307. // Remap libobjc-trampolines' TEXT segment atop all
  308. // but the first of the pages we just allocated:
  309. vm_address_t textDest = dataAddress + dataSize;
  310. vm_prot_t currentProtection, maxProtection;
  311. result = vm_remap(mach_task_self(), &textDest,
  312. textSourceSize,
  313. 0, VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE,
  314. mach_task_self(), textSource, TRUE,
  315. &currentProtection, &maxProtection, VM_INHERIT_SHARE);
  316. if (result != KERN_SUCCESS) {
  317. _objc_fatal("vm_remap trampolines failed (%d)", result);
  318. }
  319. auto *pageGroup = new ((void*)dataAddress) TrampolineBlockPageGroup;
  320. if (HeadPageGroup) {
  321. TrampolineBlockPageGroup *lastPageGroup = HeadPageGroup;
  322. while(lastPageGroup->nextPageGroup) {
  323. lastPageGroup = lastPageGroup->nextPageGroup;
  324. }
  325. lastPageGroup->nextPageGroup = pageGroup;
  326. HeadPageGroup->nextAvailablePage = pageGroup;
  327. } else {
  328. HeadPageGroup = pageGroup;
  329. }
  330. return pageGroup;
  331. }
  332. static TrampolineBlockPageGroup *
  333. getOrAllocatePageGroupWithNextAvailable()
  334. {
  335. runtimeLock.assertLocked();
  336. if (!HeadPageGroup)
  337. return _allocateTrampolinesAndData();
  338. // make sure head page is filled first
  339. if (HeadPageGroup->nextAvailable != HeadPageGroup->endIndex())
  340. return HeadPageGroup;
  341. if (HeadPageGroup->nextAvailablePage) // check if there is a page w/a hole
  342. return HeadPageGroup->nextAvailablePage;
  343. return _allocateTrampolinesAndData(); // tack on a new one
  344. }
  345. static TrampolineBlockPageGroup *
  346. pageAndIndexContainingIMP(IMP anImp, uintptr_t *outIndex)
  347. {
  348. runtimeLock.assertLocked();
  349. // Authenticate as a function pointer, returning an un-signed address.
  350. uintptr_t trampAddress =
  351. (uintptr_t)ptrauth_auth_data((const char *)anImp,
  352. ptrauth_key_function_pointer, 0);
  353. for (TrampolineBlockPageGroup *pageGroup = HeadPageGroup;
  354. pageGroup;
  355. pageGroup = pageGroup->nextPageGroup)
  356. {
  357. uintptr_t index = pageGroup->indexForTrampoline(trampAddress);
  358. if (index) {
  359. if (outIndex) *outIndex = index;
  360. return pageGroup;
  361. }
  362. }
  363. return nil;
  364. }
  365. static ArgumentMode
  366. argumentModeForBlock(id block)
  367. {
  368. ArgumentMode aMode = ReturnValueInRegisterArgumentMode;
  369. #if SUPPORT_STRET
  370. if (_Block_has_signature(block) && _Block_use_stret(block))
  371. aMode = ReturnValueOnStackArgumentMode;
  372. #else
  373. ASSERT(! (_Block_has_signature(block) && _Block_use_stret(block)));
  374. #endif
  375. return aMode;
  376. }
  377. /// Initialize the trampoline machinery. Normally this does nothing, as
  378. /// everything is initialized lazily, but for certain processes we eagerly load
  379. /// the trampolines dylib.
  380. void
  381. _imp_implementationWithBlock_init(void)
  382. {
  383. #if TARGET_OS_OSX
  384. // Eagerly load libobjc-trampolines.dylib in certain processes. Some
  385. // programs (most notably QtWebEngineProcess used by older versions of
  386. // embedded Chromium) enable a highly restrictive sandbox profile which
  387. // blocks access to that dylib. If anything calls
  388. // imp_implementationWithBlock (as AppKit has started doing) then we'll
  389. // crash trying to load it. Loading it here sets it up before the sandbox
  390. // profile is enabled and blocks it.
  391. //
  392. // This fixes EA Origin (rdar://problem/50813789)
  393. // and Steam (rdar://problem/55286131)
  394. if (__progname &&
  395. (strcmp(__progname, "QtWebEngineProcess") == 0 ||
  396. strcmp(__progname, "Steam Helper") == 0)) {
  397. Trampolines.Initialize();
  398. }
  399. #endif
  400. }
  401. // `block` must already have been copied
  402. IMP
  403. _imp_implementationWithBlockNoCopy(id block)
  404. {
  405. runtimeLock.assertLocked();
  406. TrampolineBlockPageGroup *pageGroup =
  407. getOrAllocatePageGroupWithNextAvailable();
  408. uintptr_t index = pageGroup->nextAvailable;
  409. ASSERT(index >= pageGroup->startIndex() && index < pageGroup->endIndex());
  410. TrampolineBlockPageGroup::Payload *payload = pageGroup->payload(index);
  411. uintptr_t nextAvailableIndex = payload->nextAvailable;
  412. if (nextAvailableIndex == 0) {
  413. // First time through (unused slots are zero). Fill sequentially.
  414. // If the page is now full this will now be endIndex(), handled below.
  415. nextAvailableIndex = index + 1;
  416. }
  417. pageGroup->nextAvailable = nextAvailableIndex;
  418. if (nextAvailableIndex == pageGroup->endIndex()) {
  419. // PageGroup is now full (free list or wilderness exhausted)
  420. // Remove from available page linked list
  421. TrampolineBlockPageGroup *iterator = HeadPageGroup;
  422. while(iterator && (iterator->nextAvailablePage != pageGroup)) {
  423. iterator = iterator->nextAvailablePage;
  424. }
  425. if (iterator) {
  426. iterator->nextAvailablePage = pageGroup->nextAvailablePage;
  427. pageGroup->nextAvailablePage = nil;
  428. }
  429. }
  430. payload->block = block;
  431. return pageGroup->trampoline(argumentModeForBlock(block), index);
  432. }
  433. #pragma mark Public API
  434. IMP imp_implementationWithBlock(id block)
  435. {
  436. // Block object must be copied outside runtimeLock
  437. // because it performs arbitrary work.
  438. block = Block_copy(block);
  439. // Trampolines must be initialized outside runtimeLock
  440. // because it calls dlopen().
  441. Trampolines.Initialize();
  442. mutex_locker_t lock(runtimeLock);
  443. return _imp_implementationWithBlockNoCopy(block);
  444. }
  445. id imp_getBlock(IMP anImp) {
  446. uintptr_t index;
  447. TrampolineBlockPageGroup *pageGroup;
  448. if (!anImp) return nil;
  449. mutex_locker_t lock(runtimeLock);
  450. pageGroup = pageAndIndexContainingIMP(anImp, &index);
  451. if (!pageGroup) {
  452. return nil;
  453. }
  454. TrampolineBlockPageGroup::Payload *payload = pageGroup->payload(index);
  455. if (payload->nextAvailable <= TrampolineBlockPageGroup::endIndex()) {
  456. // unallocated
  457. return nil;
  458. }
  459. return payload->block;
  460. }
  461. BOOL imp_removeBlock(IMP anImp) {
  462. if (!anImp) return NO;
  463. id block;
  464. {
  465. mutex_locker_t lock(runtimeLock);
  466. uintptr_t index;
  467. TrampolineBlockPageGroup *pageGroup =
  468. pageAndIndexContainingIMP(anImp, &index);
  469. if (!pageGroup) {
  470. return NO;
  471. }
  472. TrampolineBlockPageGroup::Payload *payload = pageGroup->payload(index);
  473. block = payload->block;
  474. // block is released below, outside the lock
  475. payload->nextAvailable = pageGroup->nextAvailable;
  476. pageGroup->nextAvailable = index;
  477. // make sure this page is on available linked list
  478. TrampolineBlockPageGroup *pageGroupIterator = HeadPageGroup;
  479. // see if page is the next available page for any existing pages
  480. while (pageGroupIterator->nextAvailablePage &&
  481. pageGroupIterator->nextAvailablePage != pageGroup)
  482. {
  483. pageGroupIterator = pageGroupIterator->nextAvailablePage;
  484. }
  485. if (! pageGroupIterator->nextAvailablePage) {
  486. // if iteration stopped because nextAvail was nil
  487. // add to end of list.
  488. pageGroupIterator->nextAvailablePage = pageGroup;
  489. pageGroup->nextAvailablePage = nil;
  490. }
  491. }
  492. // do this AFTER dropping the lock
  493. Block_release(block);
  494. return YES;
  495. }