]>
Commit | Line | Data |
---|---|---|
8972963c A |
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> | |
66799735 | 38 | #include <objc/objc-block-trampolines.h> |
8972963c | 39 | |
66799735 A |
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 | |
8972963c | 43 | |
66799735 A |
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 | ||
34d5b5e8 A |
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 | ||
66799735 A |
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 | |
34d5b5e8 A |
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 == | |
66799735 | 119 | last.address() + SLOT_SIZE); |
1807f628 A |
120 | ASSERT(last.address()+8 < textSegment + textSegmentSize); |
121 | ASSERT((last.address() - start.address()) % SLOT_SIZE == 0); | |
66799735 | 122 | # if SUPPORT_STRET |
34d5b5e8 A |
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 == | |
66799735 | 126 | last_stret.address() + SLOT_SIZE); |
34d5b5e8 | 127 | ASSERT(start.address() - impl.address() == |
66799735 | 128 | start_stret.address() - impl_stret.address()); |
34d5b5e8 | 129 | ASSERT(last_stret.address() + SLOT_SIZE < |
66799735 | 130 | textSegment + textSegmentSize); |
34d5b5e8 | 131 | ASSERT((last_stret.address() - start_stret.address()) |
66799735 A |
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 | |
8070259c | 148 | #endif |
66799735 A |
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) { | |
13ba007e A |
176 | _objc_fatal("couldn't dlopen libobjc-trampolines.dylib: %s", |
177 | dlerror()); | |
66799735 A |
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 | ||
34d5b5e8 | 191 | uintptr_t dataSize() { return TRAMPOLINE_PAGE_SIZE; } |
13ba007e | 192 | |
66799735 A |
193 | uintptr_t impl() { return get()->impl.address(); } |
194 | uintptr_t start() { return get()->start.address(); } | |
195 | }; | |
196 | ||
197 | static TrampolinePointerWrapper Trampolines; | |
8972963c A |
198 | |
199 | // argument mode identifier | |
13ba007e A |
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. | |
8972963c A |
202 | typedef enum { |
203 | ReturnValueInRegisterArgumentMode, | |
8070259c | 204 | #if SUPPORT_STRET |
8972963c | 205 | ReturnValueOnStackArgumentMode, |
8070259c | 206 | #endif |
8972963c | 207 | |
8070259c | 208 | ArgumentModeCount |
8972963c A |
209 | } ArgumentMode; |
210 | ||
8070259c A |
211 | // We must take care with our data layout on architectures that support |
212 | // multiple page sizes. | |
213 | // | |
34d5b5e8 A |
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. | |
8070259c | 218 | // |
34d5b5e8 A |
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. | |
8070259c | 221 | // |
66799735 | 222 | // When we allocate a page group, we use the process's page alignment. |
8070259c A |
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 | ||
66799735 | 227 | struct TrampolineBlockPageGroup |
8070259c | 228 | { |
66799735 A |
229 | TrampolineBlockPageGroup *nextPageGroup; // linked list of all pages |
230 | TrampolineBlockPageGroup *nextAvailablePage; // linked list of pages with available slots | |
13ba007e | 231 | |
8070259c | 232 | uintptr_t nextAvailable; // index of next available slot, endIndex() if no more available |
13ba007e A |
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 | { } | |
8070259c A |
242 | |
243 | // Payload data: block pointers and free list. | |
244 | // Bytes parallel with trampoline header code are the fields above or unused | |
34d5b5e8 | 245 | // uint8_t payloads[TRAMPOLINE_PAGE_SIZE - sizeof(TrampolineBlockPageGroup)] |
66799735 A |
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. | |
34d5b5e8 A |
251 | // uint8_t macho[TRAMPOLINE_PAGE_SIZE]; |
252 | // uint8_t trampolines[ArgumentModeCount][TRAMPOLINE_PAGE_SIZE]; | |
8070259c | 253 | |
8972963c | 254 | // Per-trampoline block data format: |
8070259c | 255 | // initial value is 0 while page data is filled sequentially |
8972963c | 256 | // when filled, value is reference to Block_copy()d block |
8070259c A |
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() { | |
66799735 | 265 | return (uintptr_t) (Trampolines.start() - Trampolines.impl()); |
8070259c A |
266 | } |
267 | ||
268 | static uintptr_t slotSize() { | |
66799735 | 269 | return SLOT_SIZE; |
8070259c | 270 | } |
8972963c | 271 | |
8070259c A |
272 | static uintptr_t startIndex() { |
273 | // headerSize is assumed to be slot-aligned | |
274 | return headerSize() / slotSize(); | |
275 | } | |
8972963c | 276 | |
8070259c | 277 | static uintptr_t endIndex() { |
13ba007e | 278 | return (uintptr_t)Trampolines.dataSize() / slotSize(); |
8070259c | 279 | } |
8972963c | 280 | |
8070259c A |
281 | static bool validIndex(uintptr_t index) { |
282 | return (index >= startIndex() && index < endIndex()); | |
283 | } | |
8972963c | 284 | |
8070259c | 285 | Payload *payload(uintptr_t index) { |
1807f628 | 286 | ASSERT(validIndex(index)); |
8070259c A |
287 | return (Payload *)((char *)this + index*slotSize()); |
288 | } | |
8972963c | 289 | |
66799735 | 290 | uintptr_t trampolinesForMode(int aMode) { |
13ba007e A |
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() + | |
34d5b5e8 | 294 | TRAMPOLINE_PAGE_SIZE * (1 + aMode); |
66799735 A |
295 | } |
296 | ||
297 | IMP trampoline(int aMode, uintptr_t index) { | |
1807f628 | 298 | ASSERT(validIndex(index)); |
66799735 A |
299 | char *base = (char *)trampolinesForMode(aMode); |
300 | char *imp = base + index*slotSize(); | |
8070259c A |
301 | #if __arm__ |
302 | imp++; // trampoline is Thumb instructions | |
66799735 A |
303 | #endif |
304 | #if __has_feature(ptrauth_calls) | |
305 | imp = ptrauth_sign_unauthenticated(imp, | |
306 | ptrauth_key_function_pointer, 0); | |
cd5f04f5 | 307 | #endif |
8070259c A |
308 | return (IMP)imp; |
309 | } | |
8972963c | 310 | |
66799735 A |
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 | } | |
8070259c A |
319 | } |
320 | return 0; | |
321 | } | |
8972963c | 322 | |
8070259c | 323 | static void check() { |
1807f628 A |
324 | ASSERT(TrampolineBlockPageGroup::headerSize() >= sizeof(TrampolineBlockPageGroup)); |
325 | ASSERT(TrampolineBlockPageGroup::headerSize() % TrampolineBlockPageGroup::slotSize() == 0); | |
8070259c | 326 | } |
8972963c | 327 | |
8070259c | 328 | }; |
8972963c | 329 | |
66799735 | 330 | static TrampolineBlockPageGroup *HeadPageGroup; |
8972963c | 331 | |
8070259c | 332 | #pragma mark Utility Functions |
8972963c | 333 | |
66799735 A |
334 | #if !__OBJC2__ |
335 | #define runtimeLock classLock | |
8972963c | 336 | #endif |
8972963c A |
337 | |
338 | #pragma mark Trampoline Management Functions | |
66799735 | 339 | static TrampolineBlockPageGroup *_allocateTrampolinesAndData() |
8070259c | 340 | { |
66799735 | 341 | runtimeLock.assertLocked(); |
8972963c A |
342 | |
343 | vm_address_t dataAddress; | |
344 | ||
66799735 A |
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 | ||
1807f628 | 361 | ASSERT(HeadPageGroup == nil || HeadPageGroup->nextAvailablePage == nil); |
66799735 A |
362 | |
363 | auto textSource = Trampolines.textSegment(); | |
364 | auto textSourceSize = Trampolines.textSegmentSize(); | |
13ba007e | 365 | auto dataSize = Trampolines.dataSize(); |
66799735 A |
366 | |
367 | // Allocate a single contiguous region big enough to hold data+text. | |
8070259c | 368 | kern_return_t result; |
66799735 A |
369 | result = vm_allocate(mach_task_self(), &dataAddress, |
370 | dataSize + textSourceSize, | |
4a109af3 A |
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 | } | |
8972963c | 375 | |
66799735 A |
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; | |
4a109af3 | 379 | vm_prot_t currentProtection, maxProtection; |
66799735 A |
380 | result = vm_remap(mach_task_self(), &textDest, |
381 | textSourceSize, | |
4a109af3 | 382 | 0, VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE, |
66799735 | 383 | mach_task_self(), textSource, TRUE, |
4a109af3 | 384 | ¤tProtection, &maxProtection, VM_INHERIT_SHARE); |
8070259c | 385 | if (result != KERN_SUCCESS) { |
4a109af3 | 386 | _objc_fatal("vm_remap trampolines failed (%d)", result); |
8070259c | 387 | } |
66799735 | 388 | |
13ba007e | 389 | auto *pageGroup = new ((void*)dataAddress) TrampolineBlockPageGroup; |
8972963c | 390 | |
66799735 A |
391 | if (HeadPageGroup) { |
392 | TrampolineBlockPageGroup *lastPageGroup = HeadPageGroup; | |
393 | while(lastPageGroup->nextPageGroup) { | |
394 | lastPageGroup = lastPageGroup->nextPageGroup; | |
4a109af3 | 395 | } |
66799735 A |
396 | lastPageGroup->nextPageGroup = pageGroup; |
397 | HeadPageGroup->nextAvailablePage = pageGroup; | |
8972963c | 398 | } else { |
66799735 | 399 | HeadPageGroup = pageGroup; |
8972963c A |
400 | } |
401 | ||
66799735 | 402 | return pageGroup; |
8972963c A |
403 | } |
404 | ||
66799735 A |
405 | static TrampolineBlockPageGroup * |
406 | getOrAllocatePageGroupWithNextAvailable() | |
8070259c | 407 | { |
66799735 | 408 | runtimeLock.assertLocked(); |
8972963c | 409 | |
66799735 A |
410 | if (!HeadPageGroup) |
411 | return _allocateTrampolinesAndData(); | |
8972963c | 412 | |
8070259c | 413 | // make sure head page is filled first |
66799735 A |
414 | if (HeadPageGroup->nextAvailable != HeadPageGroup->endIndex()) |
415 | return HeadPageGroup; | |
8972963c | 416 | |
66799735 A |
417 | if (HeadPageGroup->nextAvailablePage) // check if there is a page w/a hole |
418 | return HeadPageGroup->nextAvailablePage; | |
8972963c | 419 | |
66799735 | 420 | return _allocateTrampolinesAndData(); // tack on a new one |
8972963c A |
421 | } |
422 | ||
66799735 A |
423 | static TrampolineBlockPageGroup * |
424 | pageAndIndexContainingIMP(IMP anImp, uintptr_t *outIndex) | |
8070259c | 425 | { |
66799735 | 426 | runtimeLock.assertLocked(); |
8972963c | 427 | |
66799735 A |
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; | |
8972963c A |
441 | } |
442 | } | |
443 | ||
7257e56c | 444 | return nil; |
8972963c A |
445 | } |
446 | ||
8070259c A |
447 | |
448 | static ArgumentMode | |
66799735 | 449 | argumentModeForBlock(id block) |
8070259c A |
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 | |
1807f628 | 457 | ASSERT(! (_Block_has_signature(block) && _Block_use_stret(block))); |
8070259c A |
458 | #endif |
459 | ||
460 | return aMode; | |
461 | } | |
462 | ||
1807f628 A |
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 | ||
8070259c | 488 | |
8972963c | 489 | // `block` must already have been copied |
8070259c A |
490 | IMP |
491 | _imp_implementationWithBlockNoCopy(id block) | |
8972963c | 492 | { |
66799735 | 493 | runtimeLock.assertLocked(); |
8972963c | 494 | |
66799735 A |
495 | TrampolineBlockPageGroup *pageGroup = |
496 | getOrAllocatePageGroupWithNextAvailable(); | |
8070259c | 497 | |
66799735 | 498 | uintptr_t index = pageGroup->nextAvailable; |
1807f628 | 499 | ASSERT(index >= pageGroup->startIndex() && index < pageGroup->endIndex()); |
66799735 | 500 | TrampolineBlockPageGroup::Payload *payload = pageGroup->payload(index); |
8070259c A |
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 | } | |
66799735 A |
508 | pageGroup->nextAvailable = nextAvailableIndex; |
509 | if (nextAvailableIndex == pageGroup->endIndex()) { | |
510 | // PageGroup is now full (free list or wilderness exhausted) | |
8070259c | 511 | // Remove from available page linked list |
66799735 A |
512 | TrampolineBlockPageGroup *iterator = HeadPageGroup; |
513 | while(iterator && (iterator->nextAvailablePage != pageGroup)) { | |
8070259c A |
514 | iterator = iterator->nextAvailablePage; |
515 | } | |
516 | if (iterator) { | |
66799735 A |
517 | iterator->nextAvailablePage = pageGroup->nextAvailablePage; |
518 | pageGroup->nextAvailablePage = nil; | |
8972963c | 519 | } |
8972963c A |
520 | } |
521 | ||
8070259c | 522 | payload->block = block; |
66799735 | 523 | return pageGroup->trampoline(argumentModeForBlock(block), index); |
8972963c A |
524 | } |
525 | ||
8972963c A |
526 | |
527 | #pragma mark Public API | |
cd5f04f5 | 528 | IMP imp_implementationWithBlock(id block) |
8972963c | 529 | { |
66799735 A |
530 | // Block object must be copied outside runtimeLock |
531 | // because it performs arbitrary work. | |
8972963c | 532 | block = Block_copy(block); |
66799735 A |
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); | |
8972963c A |
541 | } |
542 | ||
543 | ||
cd5f04f5 | 544 | id imp_getBlock(IMP anImp) { |
8070259c | 545 | uintptr_t index; |
66799735 | 546 | TrampolineBlockPageGroup *pageGroup; |
8972963c | 547 | |
7257e56c | 548 | if (!anImp) return nil; |
8972963c | 549 | |
66799735 | 550 | mutex_locker_t lock(runtimeLock); |
8972963c | 551 | |
66799735 | 552 | pageGroup = pageAndIndexContainingIMP(anImp, &index); |
8972963c | 553 | |
66799735 | 554 | if (!pageGroup) { |
7257e56c | 555 | return nil; |
8972963c | 556 | } |
8070259c | 557 | |
66799735 | 558 | TrampolineBlockPageGroup::Payload *payload = pageGroup->payload(index); |
8972963c | 559 | |
66799735 | 560 | if (payload->nextAvailable <= TrampolineBlockPageGroup::endIndex()) { |
8070259c | 561 | // unallocated |
7257e56c | 562 | return nil; |
8972963c A |
563 | } |
564 | ||
8070259c | 565 | return payload->block; |
8972963c A |
566 | } |
567 | ||
568 | BOOL imp_removeBlock(IMP anImp) { | |
8972963c A |
569 | |
570 | if (!anImp) return NO; | |
8070259c | 571 | |
66799735 | 572 | id block; |
8972963c | 573 | |
8070259c | 574 | { |
66799735 | 575 | mutex_locker_t lock(runtimeLock); |
8972963c | 576 | |
66799735 A |
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 | } | |
8972963c | 608 | } |
66799735 A |
609 | |
610 | // do this AFTER dropping the lock | |
8972963c A |
611 | Block_release(block); |
612 | return YES; | |
613 | } |