]> git.saurik.com Git - apple/objc4.git/blob - runtime/objc-block-trampolines.mm
objc4-818.2.tar.gz
[apple/objc4.git] / runtime / objc-block-trampolines.mm
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 /***********************************************************************
30 * Imports.
31 **********************************************************************/
32 #include "objc-private.h"
33 #include "runtime.h"
34
35 #include <Block.h>
36 #include <Block_private.h>
37 #include <mach/mach.h>
38 #include <objc/objc-block-trampolines.h>
39
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
43
44 // 8 bytes of text and data per trampoline on all architectures.
45 #define SLOT_SIZE 8
46
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.
50
51 // Trampoline addresses are lazily looked up.
52 // All of them are hidden behind a single atomic pointer for lock-free init.
53
54 #ifdef __PTRAUTH_INTRINSICS__
55 # define TrampolinePtrauth __ptrauth(ptrauth_key_function_pointer, 1, 0x3af1)
56 #else
57 # define TrampolinePtrauth
58 #endif
59
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.
64 #ifdef __i386__
65 #define TRAMPOLINE_PAGE_SIZE PAGE_MIN_SIZE
66 #else
67 #define TRAMPOLINE_PAGE_SIZE PAGE_MAX_SIZE
68 #endif
69
70 class TrampolinePointerWrapper {
71 struct TrampolinePointers {
72 class TrampolineAddress {
73 const void * TrampolinePtrauth storage;
74
75 public:
76 TrampolineAddress(void *dylib, const char *name) {
77 #define PREFIX "_objc_blockTrampoline"
78 char symbol[strlen(PREFIX) + strlen(name) + 1];
79 strcpy(symbol, PREFIX);
80 strcat(symbol, name);
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);
85 if (!storage) {
86 _objc_fatal("couldn't dlsym %s", symbol);
87 }
88 }
89
90 uintptr_t address() {
91 return (uintptr_t)(void*)storage;
92 }
93 };
94
95 TrampolineAddress impl; // trampoline header code
96 TrampolineAddress start; // first trampoline
97 #if DEBUG
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.
104 # if SUPPORT_STRET
105 TrampolineAddress impl_stret;
106 TrampolineAddress start_stret;
107 TrampolineAddress last_stret;
108 # endif
109 #endif
110
111 uintptr_t textSegment;
112 uintptr_t textSegmentSize;
113
114 void check() {
115 #if DEBUG
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);
122 # if SUPPORT_STRET
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())
132 % SLOT_SIZE == 0);
133 # endif
134 #endif
135 }
136
137
138 TrampolinePointers(void *dylib)
139 : impl(dylib, "Impl")
140 , start(dylib, "Start")
141 #if DEBUG
142 , last(dylib, "Last")
143 # if SUPPORT_STRET
144 , impl_stret(dylib, "Impl_stret")
145 , start_stret(dylib, "Start_stret")
146 , last_stret(dylib, "Last_stret")
147 # endif
148 #endif
149 {
150 const auto *mh =
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;
156
157 check();
158 }
159 };
160
161 std::atomic<TrampolinePointers *> trampolines{nil};
162
163 TrampolinePointers *get() {
164 return trampolines.load(MEMORY_ORDER_CONSUME);
165 }
166
167 public:
168 void Initialize() {
169 if (get()) return;
170
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);
175 if (!dylib) {
176 _objc_fatal("couldn't dlopen libobjc-trampolines.dylib: %s",
177 dlerror());
178 }
179
180 auto t = new TrampolinePointers(dylib);
181 TrampolinePointers *old = nil;
182 if (! trampolines.compare_exchange_strong(old, t, memory_order_release))
183 {
184 delete t; // Lost an initialization race.
185 }
186 }
187
188 uintptr_t textSegment() { return get()->textSegment; }
189 uintptr_t textSegmentSize() { return get()->textSegmentSize; }
190
191 uintptr_t dataSize() { return TRAMPOLINE_PAGE_SIZE; }
192
193 uintptr_t impl() { return get()->impl.address(); }
194 uintptr_t start() { return get()->start.address(); }
195 };
196
197 static TrampolinePointerWrapper Trampolines;
198
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.
202 typedef enum {
203 ReturnValueInRegisterArgumentMode,
204 #if SUPPORT_STRET
205 ReturnValueOnStackArgumentMode,
206 #endif
207
208 ArgumentModeCount
209 } ArgumentMode;
210
211 // We must take care with our data layout on architectures that support
212 // multiple page sizes.
213 //
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.
218 //
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.
221 //
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.
226
227 struct TrampolineBlockPageGroup
228 {
229 TrampolineBlockPageGroup *nextPageGroup; // linked list of all pages
230 TrampolineBlockPageGroup *nextAvailablePage; // linked list of pages with available slots
231
232 uintptr_t nextAvailable; // index of next available slot, endIndex() if no more available
233
234 const void * TrampolinePtrauth const text; // text VM region; stored only for the benefit of the leaks tool
235
236 TrampolineBlockPageGroup()
237 : nextPageGroup(nil)
238 , nextAvailablePage(nil)
239 , nextAvailable(startIndex())
240 , text((const void *)((uintptr_t)this + Trampolines.dataSize()))
241 { }
242
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)]
246
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];
253
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
258
259 union Payload {
260 id block;
261 uintptr_t nextAvailable; // free list
262 };
263
264 static uintptr_t headerSize() {
265 return (uintptr_t) (Trampolines.start() - Trampolines.impl());
266 }
267
268 static uintptr_t slotSize() {
269 return SLOT_SIZE;
270 }
271
272 static uintptr_t startIndex() {
273 // headerSize is assumed to be slot-aligned
274 return headerSize() / slotSize();
275 }
276
277 static uintptr_t endIndex() {
278 return (uintptr_t)Trampolines.dataSize() / slotSize();
279 }
280
281 static bool validIndex(uintptr_t index) {
282 return (index >= startIndex() && index < endIndex());
283 }
284
285 Payload *payload(uintptr_t index) {
286 ASSERT(validIndex(index));
287 return (Payload *)((char *)this + index*slotSize());
288 }
289
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);
295 }
296
297 IMP trampoline(int aMode, uintptr_t index) {
298 ASSERT(validIndex(index));
299 char *base = (char *)trampolinesForMode(aMode);
300 char *imp = base + index*slotSize();
301 #if __arm__
302 imp++; // trampoline is Thumb instructions
303 #endif
304 #if __has_feature(ptrauth_calls)
305 imp = ptrauth_sign_unauthenticated(imp,
306 ptrauth_key_function_pointer, 0);
307 #endif
308 return (IMP)imp;
309 }
310
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();
318 }
319 }
320 return 0;
321 }
322
323 static void check() {
324 ASSERT(TrampolineBlockPageGroup::headerSize() >= sizeof(TrampolineBlockPageGroup));
325 ASSERT(TrampolineBlockPageGroup::headerSize() % TrampolineBlockPageGroup::slotSize() == 0);
326 }
327
328 };
329
330 static TrampolineBlockPageGroup *HeadPageGroup;
331
332 #pragma mark Utility Functions
333
334 #if !__OBJC2__
335 #define runtimeLock classLock
336 #endif
337
338 #pragma mark Trampoline Management Functions
339 static TrampolineBlockPageGroup *_allocateTrampolinesAndData()
340 {
341 runtimeLock.assertLocked();
342
343 vm_address_t dataAddress;
344
345 TrampolineBlockPageGroup::check();
346
347 // Our final mapping will look roughly like this:
348 // r/w data
349 // r/o text mapped from libobjc-trampolines.dylib
350 // with fixed offsets from the text to the data embedded in the text.
351 //
352 // More precisely it will look like this:
353 // 1 page r/w data
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.
360
361 ASSERT(HeadPageGroup == nil || HeadPageGroup->nextAvailablePage == nil);
362
363 auto textSource = Trampolines.textSegment();
364 auto textSourceSize = Trampolines.textSegmentSize();
365 auto dataSize = Trampolines.dataSize();
366
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);
374 }
375
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,
381 textSourceSize,
382 0, VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE,
383 mach_task_self(), textSource, TRUE,
384 &currentProtection, &maxProtection, VM_INHERIT_SHARE);
385 if (result != KERN_SUCCESS) {
386 _objc_fatal("vm_remap trampolines failed (%d)", result);
387 }
388
389 auto *pageGroup = new ((void*)dataAddress) TrampolineBlockPageGroup;
390
391 if (HeadPageGroup) {
392 TrampolineBlockPageGroup *lastPageGroup = HeadPageGroup;
393 while(lastPageGroup->nextPageGroup) {
394 lastPageGroup = lastPageGroup->nextPageGroup;
395 }
396 lastPageGroup->nextPageGroup = pageGroup;
397 HeadPageGroup->nextAvailablePage = pageGroup;
398 } else {
399 HeadPageGroup = pageGroup;
400 }
401
402 return pageGroup;
403 }
404
405 static TrampolineBlockPageGroup *
406 getOrAllocatePageGroupWithNextAvailable()
407 {
408 runtimeLock.assertLocked();
409
410 if (!HeadPageGroup)
411 return _allocateTrampolinesAndData();
412
413 // make sure head page is filled first
414 if (HeadPageGroup->nextAvailable != HeadPageGroup->endIndex())
415 return HeadPageGroup;
416
417 if (HeadPageGroup->nextAvailablePage) // check if there is a page w/a hole
418 return HeadPageGroup->nextAvailablePage;
419
420 return _allocateTrampolinesAndData(); // tack on a new one
421 }
422
423 static TrampolineBlockPageGroup *
424 pageAndIndexContainingIMP(IMP anImp, uintptr_t *outIndex)
425 {
426 runtimeLock.assertLocked();
427
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);
432
433 for (TrampolineBlockPageGroup *pageGroup = HeadPageGroup;
434 pageGroup;
435 pageGroup = pageGroup->nextPageGroup)
436 {
437 uintptr_t index = pageGroup->indexForTrampoline(trampAddress);
438 if (index) {
439 if (outIndex) *outIndex = index;
440 return pageGroup;
441 }
442 }
443
444 return nil;
445 }
446
447
448 static ArgumentMode
449 argumentModeForBlock(id block)
450 {
451 ArgumentMode aMode = ReturnValueInRegisterArgumentMode;
452
453 #if SUPPORT_STRET
454 if (_Block_has_signature(block) && _Block_use_stret(block))
455 aMode = ReturnValueOnStackArgumentMode;
456 #else
457 ASSERT(! (_Block_has_signature(block) && _Block_use_stret(block)));
458 #endif
459
460 return aMode;
461 }
462
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.
466 void
467 _imp_implementationWithBlock_init(void)
468 {
469 #if TARGET_OS_OSX
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.
477 //
478 // This fixes EA Origin (rdar://problem/50813789)
479 // and Steam (rdar://problem/55286131)
480 if (__progname &&
481 (strcmp(__progname, "QtWebEngineProcess") == 0 ||
482 strcmp(__progname, "Steam Helper") == 0)) {
483 Trampolines.Initialize();
484 }
485 #endif
486 }
487
488
489 // `block` must already have been copied
490 IMP
491 _imp_implementationWithBlockNoCopy(id block)
492 {
493 runtimeLock.assertLocked();
494
495 TrampolineBlockPageGroup *pageGroup =
496 getOrAllocatePageGroupWithNextAvailable();
497
498 uintptr_t index = pageGroup->nextAvailable;
499 ASSERT(index >= pageGroup->startIndex() && index < pageGroup->endIndex());
500 TrampolineBlockPageGroup::Payload *payload = pageGroup->payload(index);
501
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;
507 }
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;
515 }
516 if (iterator) {
517 iterator->nextAvailablePage = pageGroup->nextAvailablePage;
518 pageGroup->nextAvailablePage = nil;
519 }
520 }
521
522 payload->block = block;
523 return pageGroup->trampoline(argumentModeForBlock(block), index);
524 }
525
526
527 #pragma mark Public API
528 IMP imp_implementationWithBlock(id block)
529 {
530 // Block object must be copied outside runtimeLock
531 // because it performs arbitrary work.
532 block = Block_copy(block);
533
534 // Trampolines must be initialized outside runtimeLock
535 // because it calls dlopen().
536 Trampolines.Initialize();
537
538 mutex_locker_t lock(runtimeLock);
539
540 return _imp_implementationWithBlockNoCopy(block);
541 }
542
543
544 id imp_getBlock(IMP anImp) {
545 uintptr_t index;
546 TrampolineBlockPageGroup *pageGroup;
547
548 if (!anImp) return nil;
549
550 mutex_locker_t lock(runtimeLock);
551
552 pageGroup = pageAndIndexContainingIMP(anImp, &index);
553
554 if (!pageGroup) {
555 return nil;
556 }
557
558 TrampolineBlockPageGroup::Payload *payload = pageGroup->payload(index);
559
560 if (payload->nextAvailable <= TrampolineBlockPageGroup::endIndex()) {
561 // unallocated
562 return nil;
563 }
564
565 return payload->block;
566 }
567
568 BOOL imp_removeBlock(IMP anImp) {
569
570 if (!anImp) return NO;
571
572 id block;
573
574 {
575 mutex_locker_t lock(runtimeLock);
576
577 uintptr_t index;
578 TrampolineBlockPageGroup *pageGroup =
579 pageAndIndexContainingIMP(anImp, &index);
580
581 if (!pageGroup) {
582 return NO;
583 }
584
585 TrampolineBlockPageGroup::Payload *payload = pageGroup->payload(index);
586 block = payload->block;
587 // block is released below, outside the lock
588
589 payload->nextAvailable = pageGroup->nextAvailable;
590 pageGroup->nextAvailable = index;
591
592 // make sure this page is on available linked list
593 TrampolineBlockPageGroup *pageGroupIterator = HeadPageGroup;
594
595 // see if page is the next available page for any existing pages
596 while (pageGroupIterator->nextAvailablePage &&
597 pageGroupIterator->nextAvailablePage != pageGroup)
598 {
599 pageGroupIterator = pageGroupIterator->nextAvailablePage;
600 }
601
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;
607 }
608 }
609
610 // do this AFTER dropping the lock
611 Block_release(block);
612 return YES;
613 }