]> git.saurik.com Git - apple/objc4.git/blob - test/preopt-caches.mm
objc4-818.2.tar.gz
[apple/objc4.git] / test / preopt-caches.mm
1 /*
2 TEST_ENTITLEMENTS preopt-caches.entitlements
3 TEST_CONFIG OS=iphoneos MEM=mrc
4 TEST_BUILD
5 mkdir -p $T{OBJDIR}
6 /usr/sbin/dtrace -h -s $DIR/../runtime/objc-probes.d -o $T{OBJDIR}/objc-probes.h
7 $C{COMPILE} $DIR/preopt-caches.mm -std=gnu++17 -isystem $C{SDK_PATH}/System/Library/Frameworks/System.framework/PrivateHeaders -I$T{OBJDIR} -ldsc -o preopt-caches.exe
8 END
9 */
10 //
11 // check_preopt_caches.m
12 // check-preopt-caches
13 //
14 // Created by Thomas Deniau on 11/06/2020.
15 //
16
17 #define TEST_CALLS_OPERATOR_NEW
18
19 #include "test-defines.h"
20 #include "../runtime/objc-private.h"
21 #include <objc/objc-internal.h>
22
23 #include <dlfcn.h>
24 #include <dirent.h>
25 #include <objc/runtime.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <mach-o/dyld.h>
29 #include <mach-o/dyld_process_info.h>
30 #include <mach-o/dyld_cache_format.h>
31 #include <mach-o/dsc_iterator.h>
32 #include <unordered_map>
33 #include <string>
34 #include <vector>
35 #include <set>
36 #include <spawn.h>
37 #include <sys/poll.h>
38
39 #include "test.h"
40
41 int validate_dylib_in_forked_process(const char * const toolPath, const char * const dylib)
42 {
43 int out_pipe[2] = {-1};
44 int err_pipe[2] = {-1};
45 int exit_code = -1;
46 pid_t pid = 0;
47 int rval = 0;
48
49 std::string child_stdout;
50 std::string child_stderr;
51
52 posix_spawn_file_actions_t actions = NULL;
53 const char * const args[] = {toolPath, dylib, NULL};
54 int ret = 0;
55
56 if (pipe(out_pipe)) {
57 exit(3);
58 }
59
60 if (pipe(err_pipe)) {
61 exit(3);
62 }
63
64 //Do-si-do the FDs
65 posix_spawn_file_actions_init(&actions);
66 posix_spawn_file_actions_addclose(&actions, out_pipe[0]);
67 posix_spawn_file_actions_addclose(&actions, err_pipe[0]);
68 posix_spawn_file_actions_adddup2(&actions, out_pipe[1], 1);
69 posix_spawn_file_actions_adddup2(&actions, err_pipe[1], 2);
70 posix_spawn_file_actions_addclose(&actions, out_pipe[1]);
71 posix_spawn_file_actions_addclose(&actions, err_pipe[1]);
72
73 // Fork so that we can dlopen the dylib in a clean context
74 ret = posix_spawnp(&pid, args[0], &actions, NULL, (char * const *)args, NULL);
75
76 if (ret != 0) {
77 fail("posix_spawn for %s failed: returned %d, %s\n", dylib, ret, strerror(ret));
78 exit(3);
79 }
80
81 posix_spawn_file_actions_destroy(&actions);
82 close(out_pipe[1]);
83 close(err_pipe[1]);
84
85 std::string buffer(4096,' ');
86 std::vector<pollfd> plist = { {out_pipe[0],POLLIN,0}, {err_pipe[0],POLLIN,0} };
87 while (( (rval = poll(&plist[0],(nfds_t)plist.size(), 100000)) > 0 ) || ((rval < 0) && (errno == EINTR))) {
88 if (rval < 0) {
89 // EINTR
90 continue;
91 }
92
93 ssize_t bytes_read = 0;
94
95 if (plist[0].revents&(POLLERR|POLLHUP) || plist[1].revents&(POLLERR|POLLHUP)) {
96 bytes_read = read(out_pipe[0], &buffer[0], buffer.length());
97 bytes_read = read(err_pipe[0], &buffer[0], buffer.length());
98 break;
99 }
100
101 if (plist[0].revents&POLLIN) {
102 bytes_read = read(out_pipe[0], &buffer[0], buffer.length());
103 child_stdout += buffer.substr(0, static_cast<size_t>(bytes_read));
104 }
105 else if ( plist[1].revents&POLLIN ) {
106 bytes_read = read(err_pipe[0], &buffer[0], buffer.length());
107 child_stderr += buffer.substr(0, static_cast<size_t>(bytes_read));
108 }
109 else break; // nothing left to read
110
111 plist[0].revents = 0;
112 plist[1].revents = 0;
113 }
114 if (rval == 0) {
115 // Early timeout so try to clean up.
116 fail("Failed to validate dylib %s: timeout!\n", dylib);
117 return 1;
118 }
119
120
121 if (err_pipe[0] != -1) {
122 close(err_pipe[0]);
123 }
124
125 if (out_pipe[0] != -1) {
126 close(out_pipe[0]);
127 }
128
129 if (pid != 0) {
130 if (waitpid(pid, &exit_code, 0) < 0) {
131 fail("Could not wait for PID %d (dylib %s): err %s\n", pid, dylib, strerror(errno));
132 }
133
134 if (!WIFEXITED(exit_code)) {
135 fail("PID %d (%s) did not exit: %d. stdout: %s\n stderr: %s\n", pid, dylib, exit_code, child_stdout.c_str(), child_stderr.c_str());
136 }
137 if (WEXITSTATUS(exit_code) != 0) {
138 fail("Failed to validate dylib %s\nstdout: %s\nstderr: %s\n", dylib, child_stdout.c_str(), child_stderr.c_str());
139 }
140 }
141
142 testprintf("%s", child_stdout.c_str());
143
144 return 0;
145 }
146
147 bool check_class(Class cls, unsigned & cacheCount) {
148 // printf("%s %s\n", class_getName(cls), class_isMetaClass(cls) ? "(metaclass)" : "");
149
150 // For the initialization of the cache so that we setup the constant cache if any
151 class_getMethodImplementation(cls, @selector(initialize));
152
153 if (objc_cache_isConstantOptimizedCache(&(cls->cache), true, (uintptr_t)&_objc_empty_cache)) {
154 cacheCount++;
155 // printf("%s has a preopt cache\n", class_getName(cls));
156
157 // Make the union of all selectors until the preopt fallback class
158 const class_ro_t * fallback = ((const objc_class *) objc_cache_preoptFallbackClass(&(cls->cache)))->data()->ro();
159
160 std::unordered_map<SEL, IMP> methods;
161
162 Method *methodList;
163 unsigned count;
164 Class currentClass = cls;
165 unsigned dynamicCount = 0;
166 while (currentClass->data()->ro() != fallback) {
167 methodList = class_copyMethodList(currentClass, &count);
168 // printf("%d methods in method list for %s\n", count, class_getName(currentClass));
169 for (unsigned i = 0 ; i < count ; i++) {
170 SEL sel = method_getName(methodList[i]);
171 if (methods.find(sel) == methods.end()) {
172 const char *name = sel_getName(sel);
173 // printf("[dynamic] %s -> %p\n", name, method_getImplementation(methodList[i]));
174 methods[sel] = ptrauth_strip(method_getImplementation(methodList[i]), ptrauth_key_function_pointer);
175 if ( (currentClass == cls) ||
176 ( (strcmp(name, ".cxx_construct") != 0)
177 && (strcmp(name, ".cxx_destruct") != 0))) {
178 dynamicCount++;
179 }
180 }
181 }
182 if (count > 0) {
183 free(methodList);
184 }
185 currentClass = class_getSuperclass(currentClass);
186 }
187
188 // Check we have an equality between the two caches
189
190 // Count the methods in the preopt cache
191 unsigned preoptCacheCount = 0;
192 unsigned capacity = objc_cache_preoptCapacity(&(cls->cache));
193 const preopt_cache_entry_t *buckets = objc_cache_preoptCache(&(cls->cache))->entries;
194
195 #pragma clang diagnostic push
196 #pragma clang diagnostic ignored "-Wcast-of-sel-type"
197 const uint8_t *selOffsetsBase = (const uint8_t*)@selector(🤯);
198 #pragma clang diagnostic pop
199 for (unsigned i = 0 ; i < capacity ; i++) {
200 uint32_t selOffset = buckets[i].sel_offs;
201 if (selOffset != 0xFFFFFFFF) {
202 SEL sel = (SEL)(selOffsetsBase + selOffset);
203 IMP imp = (IMP)((uint8_t*)cls - buckets[i].imp_offs);
204 if (methods.find(sel) == methods.end()) {
205 fail("ERROR: %s: %s not found in dynamic method list\n", class_getName(cls), sel_getName(sel));
206 return false;
207 }
208 IMP dynamicImp = methods.at(sel);
209 // printf("[static] %s -> %p\n", sel_getName(sel), imp);
210 if (imp != dynamicImp) {
211 fail("ERROR: %s: %s has different implementations %p vs %p in static and dynamic caches", class_getName(cls), sel_getName(sel), imp, dynamicImp);
212 return false;
213 }
214 preoptCacheCount++;
215 }
216 }
217
218 if (preoptCacheCount != dynamicCount) {
219 testwarn("Methods in preopt cache:\n");
220
221 for (unsigned i = 0 ; i < capacity ; i++) {
222 uint32_t selOffset = buckets[i].sel_offs;
223 if (selOffset != 0xFFFFFFFF) {
224 SEL sel = (SEL)(selOffsetsBase + selOffset);
225 testwarn("%s\n", sel_getName(sel));
226 }
227 }
228
229 testwarn("Methods in dynamic cache:\n");
230
231 for (const auto & [sel, imp] : methods) {
232 testwarn("%s\n", sel_getName(sel));
233 }
234
235 fail("ERROR: %s's preoptimized cache is missing some methods\n", class_getName(cls));
236
237 return false;
238 }
239
240 } else {
241 // printf("%s does NOT have a preopt cache\n", class_getName(cls));
242 }
243
244 return true;
245 }
246
247 bool check_library(const char *path) {
248 std::set<std::string> blacklistedClasses {
249 "PNPWizardScratchpadInkView", // Can only be +initialized on Pencil-capable devices
250 "CACDisplayManager", // rdar://64929282 (CACDisplayManager does layout in +initialize!)
251 };
252
253 testprintf("Checking %s… ", path);
254
255 __unused void *lib = dlopen(path, RTLD_NOW);
256 extern uint32_t _dyld_image_count(void) __OSX_AVAILABLE_STARTING(__MAC_10_1, __IPHONE_2_0);
257 unsigned outCount = 0;
258
259 // Realize all classes first.
260 Class *allClasses = objc_copyClassList(&outCount);
261 if (allClasses != NULL) {
262 free(allClasses);
263 }
264
265 allClasses = objc_copyClassesForImage(path, &outCount);
266 if (allClasses != NULL) {
267 unsigned classCount = 0;
268 unsigned cacheCount = 0;
269
270 for (const Class * clsPtr = allClasses ; *clsPtr != nil ; clsPtr++) {
271 classCount++;
272 Class cls = *clsPtr;
273
274 if (blacklistedClasses.find(class_getName(cls)) != blacklistedClasses.end()) {
275 continue;
276 }
277
278 if (!check_class(cls, cacheCount)) {
279 return false;
280 }
281
282 if (!class_isMetaClass(cls)) {
283 if (!check_class(object_getClass(cls), cacheCount)) {
284 return false;
285 }
286 }
287 }
288 testprintf("checked %d caches in %d classes\n", cacheCount, classCount);
289 free(allClasses);
290 } else {
291 testprintf("could not find %s or no class names inside\n", path);
292 }
293
294 return true;
295 }
296
297 size_t size_of_shared_cache_with_uuid(uuid_t uuid) {
298 DIR* dfd = opendir(IPHONE_DYLD_SHARED_CACHE_DIR);
299 if (!dfd) {
300 fail("Error: unable to open shared cache dir %s\n",
301 IPHONE_DYLD_SHARED_CACHE_DIR);
302 exit(1);
303 }
304
305 uint64_t shared_cache_size = 0;
306
307 struct dirent *dp;
308 while ((dp = readdir(dfd))) {
309 char full_filename[512];
310 snprintf(full_filename, sizeof(full_filename), "%s%s",
311 IPHONE_DYLD_SHARED_CACHE_DIR, dp->d_name);
312
313 struct stat stat_buf;
314 if (stat(full_filename, &stat_buf) != 0)
315 continue;
316
317 if ((stat_buf.st_mode & S_IFMT) == S_IFDIR)
318 continue;
319
320 int fd = open(full_filename, O_RDONLY);
321 if (fd < 0) {
322 fprintf(stderr, "Error: unable to open file %s\n", full_filename);
323 continue;
324 }
325
326 struct dyld_cache_header header;
327 if (read(fd, &header, sizeof(header)) != sizeof(header)) {
328 fprintf(stderr, "Error: unable to read dyld shared cache header from %s\n",
329 full_filename);
330 close(fd);
331 continue;
332 }
333
334 if (uuid_compare(header.uuid, uuid) == 0) {
335 shared_cache_size = stat_buf.st_size;
336 break;
337 }
338 }
339
340 closedir(dfd);
341
342 return shared_cache_size;
343 }
344
345 int main (int argc, const char * argv[])
346 {
347 if (argc == 1) {
348 int err = 0;
349 dyld_process_info process_info = _dyld_process_info_create(mach_task_self(), 0, &err);
350 if (NULL == process_info) {
351 mach_error("_dyld_process_info_create", err);
352 fail("_dyld_process_info_create");
353 return 2;
354 }
355 dyld_process_cache_info cache_info;
356 _dyld_process_info_get_cache(process_info, &cache_info);
357
358 __block std::set<std::string> dylibsSet;
359 size_t size = size_of_shared_cache_with_uuid(cache_info.cacheUUID);
360 dyld_shared_cache_iterate((void*)cache_info.cacheBaseAddress, (uint32_t)size, ^(const dyld_shared_cache_dylib_info* dylibInfo, __unused const dyld_shared_cache_segment_info* segInfo) {
361 if (dylibInfo->isAlias) return;
362 std::string path(dylibInfo->path);
363 dylibsSet.insert(path);
364 });
365 std::vector<std::string> dylibs(dylibsSet.begin(), dylibsSet.end());
366
367 dispatch_apply(dylibs.size(), DISPATCH_APPLY_AUTO, ^(size_t idx) {
368 validate_dylib_in_forked_process(argv[0], dylibs[idx].c_str());
369 });
370 } else {
371 const char *libraryName = argv[1];
372 if (!check_library(libraryName)) {
373 fail("checking library %s\n", libraryName);
374 return 1;
375 }
376 }
377
378 succeed(__FILE__);
379 return 0;
380 }