]> git.saurik.com Git - apple/objc4.git/blob - runtime/objc-block-trampolines.mm
objc4-779.1.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 class TrampolinePointerWrapper {
61 struct TrampolinePointers {
62 class TrampolineAddress {
63 const void * TrampolinePtrauth storage;
64
65 public:
66 TrampolineAddress(void *dylib, const char *name) {
67 #define PREFIX "_objc_blockTrampoline"
68 char symbol[strlen(PREFIX) + strlen(name) + 1];
69 strcpy(symbol, PREFIX);
70 strcat(symbol, name);
71 // dlsym() from a text segment returns a signed pointer
72 // Authenticate it manually and let the compiler re-sign it.
73 storage = ptrauth_auth_data(dlsym(dylib, symbol),
74 ptrauth_key_function_pointer, 0);
75 if (!storage) {
76 _objc_fatal("couldn't dlsym %s", symbol);
77 }
78 }
79
80 uintptr_t address() {
81 return (uintptr_t)(void*)storage;
82 }
83 };
84
85 TrampolineAddress impl; // trampoline header code
86 TrampolineAddress start; // first trampoline
87 #if DEBUG
88 // These symbols are only used in assertions.
89 // fixme might be able to move the assertions to libobjc-trampolines itself
90 TrampolineAddress last; // start of the last trampoline
91 // We don't use the address after the last trampoline because that
92 // address might be in a different section, and then dlsym() would not
93 // sign it as a function pointer.
94 # if SUPPORT_STRET
95 TrampolineAddress impl_stret;
96 TrampolineAddress start_stret;
97 TrampolineAddress last_stret;
98 # endif
99 #endif
100
101 uintptr_t textSegment;
102 uintptr_t textSegmentSize;
103
104 void check() {
105 #if DEBUG
106 ASSERT(impl.address() == textSegment + PAGE_MAX_SIZE);
107 ASSERT(impl.address() % PAGE_SIZE == 0); // not PAGE_MAX_SIZE
108 assert(impl.address() + PAGE_MAX_SIZE ==
109 last.address() + SLOT_SIZE);
110 ASSERT(last.address()+8 < textSegment + textSegmentSize);
111 ASSERT((last.address() - start.address()) % SLOT_SIZE == 0);
112 # if SUPPORT_STRET
113 ASSERT(impl_stret.address() == textSegment + 2*PAGE_MAX_SIZE);
114 ASSERT(impl_stret.address() % PAGE_SIZE == 0); // not PAGE_MAX_SIZE
115 assert(impl_stret.address() + PAGE_MAX_SIZE ==
116 last_stret.address() + SLOT_SIZE);
117 assert(start.address() - impl.address() ==
118 start_stret.address() - impl_stret.address());
119 assert(last_stret.address() + SLOT_SIZE <
120 textSegment + textSegmentSize);
121 assert((last_stret.address() - start_stret.address())
122 % SLOT_SIZE == 0);
123 # endif
124 #endif
125 }
126
127
128 TrampolinePointers(void *dylib)
129 : impl(dylib, "Impl")
130 , start(dylib, "Start")
131 #if DEBUG
132 , last(dylib, "Last")
133 # if SUPPORT_STRET
134 , impl_stret(dylib, "Impl_stret")
135 , start_stret(dylib, "Start_stret")
136 , last_stret(dylib, "Last_stret")
137 # endif
138 #endif
139 {
140 const auto *mh =
141 dyld_image_header_containing_address((void *)impl.address());
142 unsigned long size = 0;
143 textSegment = (uintptr_t)
144 getsegmentdata((headerType *)mh, "__TEXT", &size);
145 textSegmentSize = size;
146
147 check();
148 }
149 };
150
151 std::atomic<TrampolinePointers *> trampolines{nil};
152
153 TrampolinePointers *get() {
154 return trampolines.load(MEMORY_ORDER_CONSUME);
155 }
156
157 public:
158 void Initialize() {
159 if (get()) return;
160
161 // This code may be called concurrently.
162 // In the worst case we perform extra dyld operations.
163 void *dylib = dlopen("/usr/lib/libobjc-trampolines.dylib",
164 RTLD_NOW | RTLD_LOCAL | RTLD_FIRST);
165 if (!dylib) {
166 _objc_fatal("couldn't dlopen libobjc-trampolines.dylib: %s",
167 dlerror());
168 }
169
170 auto t = new TrampolinePointers(dylib);
171 TrampolinePointers *old = nil;
172 if (! trampolines.compare_exchange_strong(old, t, memory_order_release))
173 {
174 delete t; // Lost an initialization race.
175 }
176 }
177
178 uintptr_t textSegment() { return get()->textSegment; }
179 uintptr_t textSegmentSize() { return get()->textSegmentSize; }
180
181 // See comments below about PAGE_SIZE and PAGE_MAX_SIZE.
182 uintptr_t dataSize() { return PAGE_MAX_SIZE; }
183
184 uintptr_t impl() { return get()->impl.address(); }
185 uintptr_t start() { return get()->start.address(); }
186 };
187
188 static TrampolinePointerWrapper Trampolines;
189
190 // argument mode identifier
191 // Some calculations assume that these modes are sequential starting from 0.
192 // This order must match the order of the trampoline's assembly code.
193 typedef enum {
194 ReturnValueInRegisterArgumentMode,
195 #if SUPPORT_STRET
196 ReturnValueOnStackArgumentMode,
197 #endif
198
199 ArgumentModeCount
200 } ArgumentMode;
201
202 // We must take care with our data layout on architectures that support
203 // multiple page sizes.
204 //
205 // The trampoline template in __TEXT is sized and aligned with PAGE_MAX_SIZE.
206 // On some platforms this requires additional linker flags.
207 //
208 // When we allocate a page group, we use PAGE_MAX_SIZE size.
209 // This allows trampoline code to find its data by subtracting PAGE_MAX_SIZE.
210 //
211 // When we allocate a page group, we use the process's page alignment.
212 // This simplifies allocation because we don't need to force greater than
213 // default alignment when running with small pages, but it also means
214 // the trampoline code MUST NOT look for its data by masking with PAGE_MAX_MASK.
215
216 struct TrampolineBlockPageGroup
217 {
218 TrampolineBlockPageGroup *nextPageGroup; // linked list of all pages
219 TrampolineBlockPageGroup *nextAvailablePage; // linked list of pages with available slots
220
221 uintptr_t nextAvailable; // index of next available slot, endIndex() if no more available
222
223 const void * TrampolinePtrauth const text; // text VM region; stored only for the benefit of the leaks tool
224
225 TrampolineBlockPageGroup()
226 : nextPageGroup(nil)
227 , nextAvailablePage(nil)
228 , nextAvailable(startIndex())
229 , text((const void *)((uintptr_t)this + Trampolines.dataSize()))
230 { }
231
232 // Payload data: block pointers and free list.
233 // Bytes parallel with trampoline header code are the fields above or unused
234 // uint8_t payloads[PAGE_MAX_SIZE - sizeof(TrampolineBlockPageGroup)]
235
236 // Code: Mach-O header, then trampoline header followed by trampolines.
237 // On platforms with struct return we have non-stret trampolines and
238 // stret trampolines. The stret and non-stret trampolines at a given
239 // index share the same data page.
240 // uint8_t macho[PAGE_MAX_SIZE];
241 // uint8_t trampolines[ArgumentModeCount][PAGE_MAX_SIZE];
242
243 // Per-trampoline block data format:
244 // initial value is 0 while page data is filled sequentially
245 // when filled, value is reference to Block_copy()d block
246 // when empty, value is index of next available slot OR 0 if never used yet
247
248 union Payload {
249 id block;
250 uintptr_t nextAvailable; // free list
251 };
252
253 static uintptr_t headerSize() {
254 return (uintptr_t) (Trampolines.start() - Trampolines.impl());
255 }
256
257 static uintptr_t slotSize() {
258 return SLOT_SIZE;
259 }
260
261 static uintptr_t startIndex() {
262 // headerSize is assumed to be slot-aligned
263 return headerSize() / slotSize();
264 }
265
266 static uintptr_t endIndex() {
267 return (uintptr_t)Trampolines.dataSize() / slotSize();
268 }
269
270 static bool validIndex(uintptr_t index) {
271 return (index >= startIndex() && index < endIndex());
272 }
273
274 Payload *payload(uintptr_t index) {
275 ASSERT(validIndex(index));
276 return (Payload *)((char *)this + index*slotSize());
277 }
278
279 uintptr_t trampolinesForMode(int aMode) {
280 // Skip over the data area, one page of Mach-O headers,
281 // and one text page for each mode before this one.
282 return (uintptr_t)this + Trampolines.dataSize() +
283 PAGE_MAX_SIZE * (1 + aMode);
284 }
285
286 IMP trampoline(int aMode, uintptr_t index) {
287 ASSERT(validIndex(index));
288 char *base = (char *)trampolinesForMode(aMode);
289 char *imp = base + index*slotSize();
290 #if __arm__
291 imp++; // trampoline is Thumb instructions
292 #endif
293 #if __has_feature(ptrauth_calls)
294 imp = ptrauth_sign_unauthenticated(imp,
295 ptrauth_key_function_pointer, 0);
296 #endif
297 return (IMP)imp;
298 }
299
300 uintptr_t indexForTrampoline(uintptr_t tramp) {
301 for (int aMode = 0; aMode < ArgumentModeCount; aMode++) {
302 uintptr_t base = trampolinesForMode(aMode);
303 uintptr_t start = base + startIndex() * slotSize();
304 uintptr_t end = base + endIndex() * slotSize();
305 if (tramp >= start && tramp < end) {
306 return (uintptr_t)(tramp - base) / slotSize();
307 }
308 }
309 return 0;
310 }
311
312 static void check() {
313 ASSERT(TrampolineBlockPageGroup::headerSize() >= sizeof(TrampolineBlockPageGroup));
314 ASSERT(TrampolineBlockPageGroup::headerSize() % TrampolineBlockPageGroup::slotSize() == 0);
315 }
316
317 };
318
319 static TrampolineBlockPageGroup *HeadPageGroup;
320
321 #pragma mark Utility Functions
322
323 #if !__OBJC2__
324 #define runtimeLock classLock
325 #endif
326
327 #pragma mark Trampoline Management Functions
328 static TrampolineBlockPageGroup *_allocateTrampolinesAndData()
329 {
330 runtimeLock.assertLocked();
331
332 vm_address_t dataAddress;
333
334 TrampolineBlockPageGroup::check();
335
336 // Our final mapping will look roughly like this:
337 // r/w data
338 // r/o text mapped from libobjc-trampolines.dylib
339 // with fixed offsets from the text to the data embedded in the text.
340 //
341 // More precisely it will look like this:
342 // 1 page r/w data
343 // 1 page libobjc-trampolines.dylib Mach-O header
344 // N pages trampoline code, one for each ArgumentMode
345 // M pages for the rest of libobjc-trampolines' TEXT segment.
346 // The kernel requires that we remap the entire TEXT segment every time.
347 // We assume that our code begins on the second TEXT page, but are robust
348 // against other additions to the end of the TEXT segment.
349
350 ASSERT(HeadPageGroup == nil || HeadPageGroup->nextAvailablePage == nil);
351
352 auto textSource = Trampolines.textSegment();
353 auto textSourceSize = Trampolines.textSegmentSize();
354 auto dataSize = Trampolines.dataSize();
355
356 // Allocate a single contiguous region big enough to hold data+text.
357 kern_return_t result;
358 result = vm_allocate(mach_task_self(), &dataAddress,
359 dataSize + textSourceSize,
360 VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_FOUNDATION));
361 if (result != KERN_SUCCESS) {
362 _objc_fatal("vm_allocate trampolines failed (%d)", result);
363 }
364
365 // Remap libobjc-trampolines' TEXT segment atop all
366 // but the first of the pages we just allocated:
367 vm_address_t textDest = dataAddress + dataSize;
368 vm_prot_t currentProtection, maxProtection;
369 result = vm_remap(mach_task_self(), &textDest,
370 textSourceSize,
371 0, VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE,
372 mach_task_self(), textSource, TRUE,
373 &currentProtection, &maxProtection, VM_INHERIT_SHARE);
374 if (result != KERN_SUCCESS) {
375 _objc_fatal("vm_remap trampolines failed (%d)", result);
376 }
377
378 auto *pageGroup = new ((void*)dataAddress) TrampolineBlockPageGroup;
379
380 if (HeadPageGroup) {
381 TrampolineBlockPageGroup *lastPageGroup = HeadPageGroup;
382 while(lastPageGroup->nextPageGroup) {
383 lastPageGroup = lastPageGroup->nextPageGroup;
384 }
385 lastPageGroup->nextPageGroup = pageGroup;
386 HeadPageGroup->nextAvailablePage = pageGroup;
387 } else {
388 HeadPageGroup = pageGroup;
389 }
390
391 return pageGroup;
392 }
393
394 static TrampolineBlockPageGroup *
395 getOrAllocatePageGroupWithNextAvailable()
396 {
397 runtimeLock.assertLocked();
398
399 if (!HeadPageGroup)
400 return _allocateTrampolinesAndData();
401
402 // make sure head page is filled first
403 if (HeadPageGroup->nextAvailable != HeadPageGroup->endIndex())
404 return HeadPageGroup;
405
406 if (HeadPageGroup->nextAvailablePage) // check if there is a page w/a hole
407 return HeadPageGroup->nextAvailablePage;
408
409 return _allocateTrampolinesAndData(); // tack on a new one
410 }
411
412 static TrampolineBlockPageGroup *
413 pageAndIndexContainingIMP(IMP anImp, uintptr_t *outIndex)
414 {
415 runtimeLock.assertLocked();
416
417 // Authenticate as a function pointer, returning an un-signed address.
418 uintptr_t trampAddress =
419 (uintptr_t)ptrauth_auth_data((const char *)anImp,
420 ptrauth_key_function_pointer, 0);
421
422 for (TrampolineBlockPageGroup *pageGroup = HeadPageGroup;
423 pageGroup;
424 pageGroup = pageGroup->nextPageGroup)
425 {
426 uintptr_t index = pageGroup->indexForTrampoline(trampAddress);
427 if (index) {
428 if (outIndex) *outIndex = index;
429 return pageGroup;
430 }
431 }
432
433 return nil;
434 }
435
436
437 static ArgumentMode
438 argumentModeForBlock(id block)
439 {
440 ArgumentMode aMode = ReturnValueInRegisterArgumentMode;
441
442 #if SUPPORT_STRET
443 if (_Block_has_signature(block) && _Block_use_stret(block))
444 aMode = ReturnValueOnStackArgumentMode;
445 #else
446 ASSERT(! (_Block_has_signature(block) && _Block_use_stret(block)));
447 #endif
448
449 return aMode;
450 }
451
452 /// Initialize the trampoline machinery. Normally this does nothing, as
453 /// everything is initialized lazily, but for certain processes we eagerly load
454 /// the trampolines dylib.
455 void
456 _imp_implementationWithBlock_init(void)
457 {
458 #if TARGET_OS_OSX
459 // Eagerly load libobjc-trampolines.dylib in certain processes. Some
460 // programs (most notably QtWebEngineProcess used by older versions of
461 // embedded Chromium) enable a highly restrictive sandbox profile which
462 // blocks access to that dylib. If anything calls
463 // imp_implementationWithBlock (as AppKit has started doing) then we'll
464 // crash trying to load it. Loading it here sets it up before the sandbox
465 // profile is enabled and blocks it.
466 //
467 // This fixes EA Origin (rdar://problem/50813789)
468 // and Steam (rdar://problem/55286131)
469 if (__progname &&
470 (strcmp(__progname, "QtWebEngineProcess") == 0 ||
471 strcmp(__progname, "Steam Helper") == 0)) {
472 Trampolines.Initialize();
473 }
474 #endif
475 }
476
477
478 // `block` must already have been copied
479 IMP
480 _imp_implementationWithBlockNoCopy(id block)
481 {
482 runtimeLock.assertLocked();
483
484 TrampolineBlockPageGroup *pageGroup =
485 getOrAllocatePageGroupWithNextAvailable();
486
487 uintptr_t index = pageGroup->nextAvailable;
488 ASSERT(index >= pageGroup->startIndex() && index < pageGroup->endIndex());
489 TrampolineBlockPageGroup::Payload *payload = pageGroup->payload(index);
490
491 uintptr_t nextAvailableIndex = payload->nextAvailable;
492 if (nextAvailableIndex == 0) {
493 // First time through (unused slots are zero). Fill sequentially.
494 // If the page is now full this will now be endIndex(), handled below.
495 nextAvailableIndex = index + 1;
496 }
497 pageGroup->nextAvailable = nextAvailableIndex;
498 if (nextAvailableIndex == pageGroup->endIndex()) {
499 // PageGroup is now full (free list or wilderness exhausted)
500 // Remove from available page linked list
501 TrampolineBlockPageGroup *iterator = HeadPageGroup;
502 while(iterator && (iterator->nextAvailablePage != pageGroup)) {
503 iterator = iterator->nextAvailablePage;
504 }
505 if (iterator) {
506 iterator->nextAvailablePage = pageGroup->nextAvailablePage;
507 pageGroup->nextAvailablePage = nil;
508 }
509 }
510
511 payload->block = block;
512 return pageGroup->trampoline(argumentModeForBlock(block), index);
513 }
514
515
516 #pragma mark Public API
517 IMP imp_implementationWithBlock(id block)
518 {
519 // Block object must be copied outside runtimeLock
520 // because it performs arbitrary work.
521 block = Block_copy(block);
522
523 // Trampolines must be initialized outside runtimeLock
524 // because it calls dlopen().
525 Trampolines.Initialize();
526
527 mutex_locker_t lock(runtimeLock);
528
529 return _imp_implementationWithBlockNoCopy(block);
530 }
531
532
533 id imp_getBlock(IMP anImp) {
534 uintptr_t index;
535 TrampolineBlockPageGroup *pageGroup;
536
537 if (!anImp) return nil;
538
539 mutex_locker_t lock(runtimeLock);
540
541 pageGroup = pageAndIndexContainingIMP(anImp, &index);
542
543 if (!pageGroup) {
544 return nil;
545 }
546
547 TrampolineBlockPageGroup::Payload *payload = pageGroup->payload(index);
548
549 if (payload->nextAvailable <= TrampolineBlockPageGroup::endIndex()) {
550 // unallocated
551 return nil;
552 }
553
554 return payload->block;
555 }
556
557 BOOL imp_removeBlock(IMP anImp) {
558
559 if (!anImp) return NO;
560
561 id block;
562
563 {
564 mutex_locker_t lock(runtimeLock);
565
566 uintptr_t index;
567 TrampolineBlockPageGroup *pageGroup =
568 pageAndIndexContainingIMP(anImp, &index);
569
570 if (!pageGroup) {
571 return NO;
572 }
573
574 TrampolineBlockPageGroup::Payload *payload = pageGroup->payload(index);
575 block = payload->block;
576 // block is released below, outside the lock
577
578 payload->nextAvailable = pageGroup->nextAvailable;
579 pageGroup->nextAvailable = index;
580
581 // make sure this page is on available linked list
582 TrampolineBlockPageGroup *pageGroupIterator = HeadPageGroup;
583
584 // see if page is the next available page for any existing pages
585 while (pageGroupIterator->nextAvailablePage &&
586 pageGroupIterator->nextAvailablePage != pageGroup)
587 {
588 pageGroupIterator = pageGroupIterator->nextAvailablePage;
589 }
590
591 if (! pageGroupIterator->nextAvailablePage) {
592 // if iteration stopped because nextAvail was nil
593 // add to end of list.
594 pageGroupIterator->nextAvailablePage = pageGroup;
595 pageGroup->nextAvailablePage = nil;
596 }
597 }
598
599 // do this AFTER dropping the lock
600 Block_release(block);
601 return YES;
602 }