2 * Copyright (c) 2010 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
23 /***********************************************************************
24 * objc-block-trampolines.m
27 **********************************************************************/
29 /***********************************************************************
31 **********************************************************************/
32 #include "objc-private.h"
36 #include <Block_private.h>
37 #include <mach/mach.h>
38 #include <objc/objc-block-trampolines.h>
40 // fixme C++ compilers don't implemement memory_order_consume efficiently.
41 // Use memory_order_relaxed and cross our fingers.
42 #define MEMORY_ORDER_CONSUME std::memory_order_relaxed
44 // 8 bytes of text and data per trampoline on all architectures.
47 // The trampolines are defined in assembly files in libobjc-trampolines.dylib.
48 // We can't link to libobjc-trampolines.dylib directly because
49 // for security reasons it isn't in the dyld shared cache.
51 // Trampoline addresses are lazily looked up.
52 // All of them are hidden behind a single atomic pointer for lock-free init.
54 #ifdef __PTRAUTH_INTRINSICS__
55 # define TrampolinePtrauth __ptrauth(ptrauth_key_function_pointer, 1, 0x3af1)
57 # define TrampolinePtrauth
60 // A page of trampolines is as big as the maximum supported page size
61 // everywhere except i386. i386 only exists for the watch simulator
62 // now, and we know it really only has 4kB pages. Also see comments
63 // below about PAGE_SIZE and PAGE_MAX_SIZE.
65 #define TRAMPOLINE_PAGE_SIZE PAGE_MIN_SIZE
67 #define TRAMPOLINE_PAGE_SIZE PAGE_MAX_SIZE
70 class TrampolinePointerWrapper {
71 struct TrampolinePointers {
72 class TrampolineAddress {
73 const void * TrampolinePtrauth storage;
76 TrampolineAddress(void *dylib, const char *name) {
77 #define PREFIX "_objc_blockTrampoline"
78 char symbol[strlen(PREFIX) + strlen(name) + 1];
79 strcpy(symbol, PREFIX);
81 // dlsym() from a text segment returns a signed pointer
82 // Authenticate it manually and let the compiler re-sign it.
83 storage = ptrauth_auth_data(dlsym(dylib, symbol),
84 ptrauth_key_function_pointer, 0);
86 _objc_fatal("couldn't dlsym %s", symbol);
91 return (uintptr_t)(void*)storage;
95 TrampolineAddress impl; // trampoline header code
96 TrampolineAddress start; // first trampoline
98 // These symbols are only used in assertions.
99 // fixme might be able to move the assertions to libobjc-trampolines itself
100 TrampolineAddress last; // start of the last trampoline
101 // We don't use the address after the last trampoline because that
102 // address might be in a different section, and then dlsym() would not
103 // sign it as a function pointer.
105 TrampolineAddress impl_stret;
106 TrampolineAddress start_stret;
107 TrampolineAddress last_stret;
111 uintptr_t textSegment;
112 uintptr_t textSegmentSize;
116 ASSERT(impl.address() == textSegment + TRAMPOLINE_PAGE_SIZE);
117 ASSERT(impl.address() % PAGE_SIZE == 0); // not TRAMPOLINE_PAGE_SIZE
118 ASSERT(impl.address() + TRAMPOLINE_PAGE_SIZE ==
119 last.address() + SLOT_SIZE);
120 ASSERT(last.address()+8 < textSegment + textSegmentSize);
121 ASSERT((last.address() - start.address()) % SLOT_SIZE == 0);
123 ASSERT(impl_stret.address() == textSegment + 2*TRAMPOLINE_PAGE_SIZE);
124 ASSERT(impl_stret.address() % PAGE_SIZE == 0); // not TRAMPOLINE_PAGE_SIZE
125 ASSERT(impl_stret.address() + TRAMPOLINE_PAGE_SIZE ==
126 last_stret.address() + SLOT_SIZE);
127 ASSERT(start.address() - impl.address() ==
128 start_stret.address() - impl_stret.address());
129 ASSERT(last_stret.address() + SLOT_SIZE <
130 textSegment + textSegmentSize);
131 ASSERT((last_stret.address() - start_stret.address())
138 TrampolinePointers(void *dylib)
139 : impl(dylib, "Impl")
140 , start(dylib, "Start")
142 , last(dylib, "Last")
144 , impl_stret(dylib, "Impl_stret")
145 , start_stret(dylib, "Start_stret")
146 , last_stret(dylib, "Last_stret")
151 dyld_image_header_containing_address((void *)impl.address());
152 unsigned long size = 0;
153 textSegment = (uintptr_t)
154 getsegmentdata((headerType *)mh, "__TEXT", &size);
155 textSegmentSize = size;
161 std::atomic<TrampolinePointers *> trampolines{nil};
163 TrampolinePointers *get() {
164 return trampolines.load(MEMORY_ORDER_CONSUME);
171 // This code may be called concurrently.
172 // In the worst case we perform extra dyld operations.
173 void *dylib = dlopen("/usr/lib/libobjc-trampolines.dylib",
174 RTLD_NOW | RTLD_LOCAL | RTLD_FIRST);
176 _objc_fatal("couldn't dlopen libobjc-trampolines.dylib: %s",
180 auto t = new TrampolinePointers(dylib);
181 TrampolinePointers *old = nil;
182 if (! trampolines.compare_exchange_strong(old, t, memory_order_release))
184 delete t; // Lost an initialization race.
188 uintptr_t textSegment() { return get()->textSegment; }
189 uintptr_t textSegmentSize() { return get()->textSegmentSize; }
191 uintptr_t dataSize() { return TRAMPOLINE_PAGE_SIZE; }
193 uintptr_t impl() { return get()->impl.address(); }
194 uintptr_t start() { return get()->start.address(); }
197 static TrampolinePointerWrapper Trampolines;
199 // argument mode identifier
200 // Some calculations assume that these modes are sequential starting from 0.
201 // This order must match the order of the trampoline's assembly code.
203 ReturnValueInRegisterArgumentMode,
205 ReturnValueOnStackArgumentMode,
211 // We must take care with our data layout on architectures that support
212 // multiple page sizes.
214 // The trampoline template in __TEXT is sized and aligned with PAGE_MAX_SIZE,
215 // except on i386 which is a weird special case that uses PAGE_MIN_SIZE.
216 // The TRAMPOLINE_PAGE_SIZE macro handles this difference. On some platforms,
217 // aligning to PAGE_MAX_SIZE requires additional linker flags.
219 // When we allocate a page group, we use TRAMPOLINE_PAGE_SIZE size.
220 // This allows trampoline code to find its data by subtracting TRAMPOLINE_PAGE_SIZE.
222 // When we allocate a page group, we use the process's page alignment.
223 // This simplifies allocation because we don't need to force greater than
224 // default alignment when running with small pages, but it also means
225 // the trampoline code MUST NOT look for its data by masking with PAGE_MAX_MASK.
227 struct TrampolineBlockPageGroup
229 TrampolineBlockPageGroup *nextPageGroup; // linked list of all pages
230 TrampolineBlockPageGroup *nextAvailablePage; // linked list of pages with available slots
232 uintptr_t nextAvailable; // index of next available slot, endIndex() if no more available
234 const void * TrampolinePtrauth const text; // text VM region; stored only for the benefit of the leaks tool
236 TrampolineBlockPageGroup()
238 , nextAvailablePage(nil)
239 , nextAvailable(startIndex())
240 , text((const void *)((uintptr_t)this + Trampolines.dataSize()))
243 // Payload data: block pointers and free list.
244 // Bytes parallel with trampoline header code are the fields above or unused
245 // uint8_t payloads[TRAMPOLINE_PAGE_SIZE - sizeof(TrampolineBlockPageGroup)]
247 // Code: Mach-O header, then trampoline header followed by trampolines.
248 // On platforms with struct return we have non-stret trampolines and
249 // stret trampolines. The stret and non-stret trampolines at a given
250 // index share the same data page.
251 // uint8_t macho[TRAMPOLINE_PAGE_SIZE];
252 // uint8_t trampolines[ArgumentModeCount][TRAMPOLINE_PAGE_SIZE];
254 // Per-trampoline block data format:
255 // initial value is 0 while page data is filled sequentially
256 // when filled, value is reference to Block_copy()d block
257 // when empty, value is index of next available slot OR 0 if never used yet
261 uintptr_t nextAvailable; // free list
264 static uintptr_t headerSize() {
265 return (uintptr_t) (Trampolines.start() - Trampolines.impl());
268 static uintptr_t slotSize() {
272 static uintptr_t startIndex() {
273 // headerSize is assumed to be slot-aligned
274 return headerSize() / slotSize();
277 static uintptr_t endIndex() {
278 return (uintptr_t)Trampolines.dataSize() / slotSize();
281 static bool validIndex(uintptr_t index) {
282 return (index >= startIndex() && index < endIndex());
285 Payload *payload(uintptr_t index) {
286 ASSERT(validIndex(index));
287 return (Payload *)((char *)this + index*slotSize());
290 uintptr_t trampolinesForMode(int aMode) {
291 // Skip over the data area, one page of Mach-O headers,
292 // and one text page for each mode before this one.
293 return (uintptr_t)this + Trampolines.dataSize() +
294 TRAMPOLINE_PAGE_SIZE * (1 + aMode);
297 IMP trampoline(int aMode, uintptr_t index) {
298 ASSERT(validIndex(index));
299 char *base = (char *)trampolinesForMode(aMode);
300 char *imp = base + index*slotSize();
302 imp++; // trampoline is Thumb instructions
304 #if __has_feature(ptrauth_calls)
305 imp = ptrauth_sign_unauthenticated(imp,
306 ptrauth_key_function_pointer, 0);
311 uintptr_t indexForTrampoline(uintptr_t tramp) {
312 for (int aMode = 0; aMode < ArgumentModeCount; aMode++) {
313 uintptr_t base = trampolinesForMode(aMode);
314 uintptr_t start = base + startIndex() * slotSize();
315 uintptr_t end = base + endIndex() * slotSize();
316 if (tramp >= start && tramp < end) {
317 return (uintptr_t)(tramp - base) / slotSize();
323 static void check() {
324 ASSERT(TrampolineBlockPageGroup::headerSize() >= sizeof(TrampolineBlockPageGroup));
325 ASSERT(TrampolineBlockPageGroup::headerSize() % TrampolineBlockPageGroup::slotSize() == 0);
330 static TrampolineBlockPageGroup *HeadPageGroup;
332 #pragma mark Utility Functions
335 #define runtimeLock classLock
338 #pragma mark Trampoline Management Functions
339 static TrampolineBlockPageGroup *_allocateTrampolinesAndData()
341 runtimeLock.assertLocked();
343 vm_address_t dataAddress;
345 TrampolineBlockPageGroup::check();
347 // Our final mapping will look roughly like this:
349 // r/o text mapped from libobjc-trampolines.dylib
350 // with fixed offsets from the text to the data embedded in the text.
352 // More precisely it will look like this:
354 // 1 page libobjc-trampolines.dylib Mach-O header
355 // N pages trampoline code, one for each ArgumentMode
356 // M pages for the rest of libobjc-trampolines' TEXT segment.
357 // The kernel requires that we remap the entire TEXT segment every time.
358 // We assume that our code begins on the second TEXT page, but are robust
359 // against other additions to the end of the TEXT segment.
361 ASSERT(HeadPageGroup == nil || HeadPageGroup->nextAvailablePage == nil);
363 auto textSource = Trampolines.textSegment();
364 auto textSourceSize = Trampolines.textSegmentSize();
365 auto dataSize = Trampolines.dataSize();
367 // Allocate a single contiguous region big enough to hold data+text.
368 kern_return_t result;
369 result = vm_allocate(mach_task_self(), &dataAddress,
370 dataSize + textSourceSize,
371 VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_FOUNDATION));
372 if (result != KERN_SUCCESS) {
373 _objc_fatal("vm_allocate trampolines failed (%d)", result);
376 // Remap libobjc-trampolines' TEXT segment atop all
377 // but the first of the pages we just allocated:
378 vm_address_t textDest = dataAddress + dataSize;
379 vm_prot_t currentProtection, maxProtection;
380 result = vm_remap(mach_task_self(), &textDest,
382 0, VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE,
383 mach_task_self(), textSource, TRUE,
384 ¤tProtection, &maxProtection, VM_INHERIT_SHARE);
385 if (result != KERN_SUCCESS) {
386 _objc_fatal("vm_remap trampolines failed (%d)", result);
389 auto *pageGroup = new ((void*)dataAddress) TrampolineBlockPageGroup;
392 TrampolineBlockPageGroup *lastPageGroup = HeadPageGroup;
393 while(lastPageGroup->nextPageGroup) {
394 lastPageGroup = lastPageGroup->nextPageGroup;
396 lastPageGroup->nextPageGroup = pageGroup;
397 HeadPageGroup->nextAvailablePage = pageGroup;
399 HeadPageGroup = pageGroup;
405 static TrampolineBlockPageGroup *
406 getOrAllocatePageGroupWithNextAvailable()
408 runtimeLock.assertLocked();
411 return _allocateTrampolinesAndData();
413 // make sure head page is filled first
414 if (HeadPageGroup->nextAvailable != HeadPageGroup->endIndex())
415 return HeadPageGroup;
417 if (HeadPageGroup->nextAvailablePage) // check if there is a page w/a hole
418 return HeadPageGroup->nextAvailablePage;
420 return _allocateTrampolinesAndData(); // tack on a new one
423 static TrampolineBlockPageGroup *
424 pageAndIndexContainingIMP(IMP anImp, uintptr_t *outIndex)
426 runtimeLock.assertLocked();
428 // Authenticate as a function pointer, returning an un-signed address.
429 uintptr_t trampAddress =
430 (uintptr_t)ptrauth_auth_data((const char *)anImp,
431 ptrauth_key_function_pointer, 0);
433 for (TrampolineBlockPageGroup *pageGroup = HeadPageGroup;
435 pageGroup = pageGroup->nextPageGroup)
437 uintptr_t index = pageGroup->indexForTrampoline(trampAddress);
439 if (outIndex) *outIndex = index;
449 argumentModeForBlock(id block)
451 ArgumentMode aMode = ReturnValueInRegisterArgumentMode;
454 if (_Block_has_signature(block) && _Block_use_stret(block))
455 aMode = ReturnValueOnStackArgumentMode;
457 ASSERT(! (_Block_has_signature(block) && _Block_use_stret(block)));
463 /// Initialize the trampoline machinery. Normally this does nothing, as
464 /// everything is initialized lazily, but for certain processes we eagerly load
465 /// the trampolines dylib.
467 _imp_implementationWithBlock_init(void)
470 // Eagerly load libobjc-trampolines.dylib in certain processes. Some
471 // programs (most notably QtWebEngineProcess used by older versions of
472 // embedded Chromium) enable a highly restrictive sandbox profile which
473 // blocks access to that dylib. If anything calls
474 // imp_implementationWithBlock (as AppKit has started doing) then we'll
475 // crash trying to load it. Loading it here sets it up before the sandbox
476 // profile is enabled and blocks it.
478 // This fixes EA Origin (rdar://problem/50813789)
479 // and Steam (rdar://problem/55286131)
481 (strcmp(__progname, "QtWebEngineProcess") == 0 ||
482 strcmp(__progname, "Steam Helper") == 0)) {
483 Trampolines.Initialize();
489 // `block` must already have been copied
491 _imp_implementationWithBlockNoCopy(id block)
493 runtimeLock.assertLocked();
495 TrampolineBlockPageGroup *pageGroup =
496 getOrAllocatePageGroupWithNextAvailable();
498 uintptr_t index = pageGroup->nextAvailable;
499 ASSERT(index >= pageGroup->startIndex() && index < pageGroup->endIndex());
500 TrampolineBlockPageGroup::Payload *payload = pageGroup->payload(index);
502 uintptr_t nextAvailableIndex = payload->nextAvailable;
503 if (nextAvailableIndex == 0) {
504 // First time through (unused slots are zero). Fill sequentially.
505 // If the page is now full this will now be endIndex(), handled below.
506 nextAvailableIndex = index + 1;
508 pageGroup->nextAvailable = nextAvailableIndex;
509 if (nextAvailableIndex == pageGroup->endIndex()) {
510 // PageGroup is now full (free list or wilderness exhausted)
511 // Remove from available page linked list
512 TrampolineBlockPageGroup *iterator = HeadPageGroup;
513 while(iterator && (iterator->nextAvailablePage != pageGroup)) {
514 iterator = iterator->nextAvailablePage;
517 iterator->nextAvailablePage = pageGroup->nextAvailablePage;
518 pageGroup->nextAvailablePage = nil;
522 payload->block = block;
523 return pageGroup->trampoline(argumentModeForBlock(block), index);
527 #pragma mark Public API
528 IMP imp_implementationWithBlock(id block)
530 // Block object must be copied outside runtimeLock
531 // because it performs arbitrary work.
532 block = Block_copy(block);
534 // Trampolines must be initialized outside runtimeLock
535 // because it calls dlopen().
536 Trampolines.Initialize();
538 mutex_locker_t lock(runtimeLock);
540 return _imp_implementationWithBlockNoCopy(block);
544 id imp_getBlock(IMP anImp) {
546 TrampolineBlockPageGroup *pageGroup;
548 if (!anImp) return nil;
550 mutex_locker_t lock(runtimeLock);
552 pageGroup = pageAndIndexContainingIMP(anImp, &index);
558 TrampolineBlockPageGroup::Payload *payload = pageGroup->payload(index);
560 if (payload->nextAvailable <= TrampolineBlockPageGroup::endIndex()) {
565 return payload->block;
568 BOOL imp_removeBlock(IMP anImp) {
570 if (!anImp) return NO;
575 mutex_locker_t lock(runtimeLock);
578 TrampolineBlockPageGroup *pageGroup =
579 pageAndIndexContainingIMP(anImp, &index);
585 TrampolineBlockPageGroup::Payload *payload = pageGroup->payload(index);
586 block = payload->block;
587 // block is released below, outside the lock
589 payload->nextAvailable = pageGroup->nextAvailable;
590 pageGroup->nextAvailable = index;
592 // make sure this page is on available linked list
593 TrampolineBlockPageGroup *pageGroupIterator = HeadPageGroup;
595 // see if page is the next available page for any existing pages
596 while (pageGroupIterator->nextAvailablePage &&
597 pageGroupIterator->nextAvailablePage != pageGroup)
599 pageGroupIterator = pageGroupIterator->nextAvailablePage;
602 if (! pageGroupIterator->nextAvailablePage) {
603 // if iteration stopped because nextAvail was nil
604 // add to end of list.
605 pageGroupIterator->nextAvailablePage = pageGroup;
606 pageGroup->nextAvailablePage = nil;
610 // do this AFTER dropping the lock
611 Block_release(block);