]>
Commit | Line | Data |
---|---|---|
cf37c299 A |
1 | /* |
2 | * Copyright (c) 2016 Apple Inc. All rights reserved. | |
3 | */ | |
4 | ||
5 | #include "options.h" | |
6 | #include "vm.h" | |
7 | #include "region.h" | |
8 | #include "utils.h" | |
9 | #include "dyld.h" | |
10 | #include "threads.h" | |
11 | #include "vanilla.h" | |
12 | #include "sparse.h" | |
13 | ||
14 | #include <sys/types.h> | |
15 | #include <sys/sysctl.h> | |
16 | #include <sys/stat.h> | |
17 | #include <sys/mman.h> | |
18 | #include <libproc.h> | |
19 | ||
20 | #include <stdio.h> | |
21 | #include <string.h> | |
22 | #include <strings.h> | |
23 | #include <stdlib.h> | |
24 | #include <stdarg.h> | |
25 | #include <signal.h> | |
26 | #include <unistd.h> | |
27 | #include <errno.h> | |
28 | #include <ctype.h> | |
29 | #include <fcntl.h> | |
30 | #include <assert.h> | |
31 | #include <sysexits.h> | |
32 | ||
33 | #include <mach/mach.h> | |
34 | ||
35 | walk_return_t | |
36 | vanilla_region_optimization(struct region *r, __unused void *arg) | |
37 | { | |
38 | assert(0 != R_SIZE(r)); | |
39 | ||
40 | /* | |
41 | * Elide unreadable regions | |
42 | */ | |
43 | if ((r->r_info.max_protection & VM_PROT_READ) != VM_PROT_READ) { | |
44 | if (opt->debug) | |
45 | printr(r, "eliding unreadable region\n"); | |
46 | return WALK_DELETE_REGION; | |
47 | } | |
48 | #ifdef CONFIG_SUBMAP | |
49 | /* | |
50 | * Elide submaps (here for debugging purposes?) | |
51 | */ | |
52 | if (r->r_info.is_submap) { | |
53 | if (opt->debug) | |
54 | printr(r, "eliding submap\n"); | |
55 | return WALK_DELETE_REGION; | |
56 | } | |
57 | #endif | |
58 | /* | |
59 | * Elide guard regions | |
60 | */ | |
61 | if (r->r_info.protection == VM_PROT_NONE && | |
62 | (VM_MEMORY_STACK == r->r_info.user_tag || | |
63 | VM_MEMORY_MALLOC == r->r_info.user_tag)) { | |
64 | if (opt->debug) { | |
65 | hsize_str_t hstr; | |
66 | printr(r, "eliding %s - guard\n", | |
67 | str_hsize(hstr, R_SIZE(r))); | |
68 | } | |
69 | return WALK_DELETE_REGION; | |
70 | } | |
71 | return WALK_CONTINUE; | |
72 | } | |
73 | ||
74 | /* | |
75 | * (Paranoid validation + debugging assistance.) | |
76 | */ | |
77 | static void | |
78 | validate_core_header(const native_mach_header_t *mh, off_t corefilesize) | |
79 | { | |
80 | if (opt->debug) | |
81 | printf("Core file: mh %p ncmds %u sizeofcmds %u\n", | |
82 | mh, mh->ncmds, mh->sizeofcmds); | |
83 | ||
84 | const struct load_command *lc = (const void *)(mh + 1); | |
85 | for (unsigned i = 0; i < mh->ncmds; i++) { | |
86 | ||
87 | if ((uintptr_t)lc < (uintptr_t)mh || | |
88 | (uintptr_t)lc > (uintptr_t)mh + mh->sizeofcmds) { | |
89 | warnx("load command %p outside mach header range [%p, 0x%lx]?", | |
90 | lc, mh, (uintptr_t)mh + mh->sizeofcmds); | |
91 | abort(); | |
92 | } | |
93 | if (opt->debug) | |
94 | printf("lc %p cmd %u cmdsize %u ", lc, lc->cmd, lc->cmdsize); | |
95 | ||
96 | const native_segment_command_t *sc; | |
97 | const struct proto_coreinfo_command *cic; | |
98 | const struct proto_fileref_command *frc; | |
99 | const struct thread_command *tc; | |
100 | ||
101 | switch (lc->cmd) { | |
102 | case NATIVE_LC_SEGMENT: | |
103 | sc = (const void *)lc; | |
104 | if (opt->debug) { | |
105 | printf("%8s: mem %llx-%llx file %lld-%lld %x/%x flags %x\n", | |
106 | "SEGMENT", | |
107 | (mach_vm_offset_t)sc->vmaddr, | |
108 | (mach_vm_offset_t)sc->vmaddr + sc->vmsize, | |
109 | (off_t)sc->fileoff, | |
110 | (off_t)sc->fileoff + (off_t)sc->filesize, | |
111 | sc->initprot, sc->maxprot, sc->flags); | |
112 | } | |
113 | if ((off_t)sc->fileoff < mh->sizeofcmds || | |
114 | (off_t)sc->filesize < 0) { | |
115 | warnx("bad segment command"); | |
116 | abort(); | |
117 | } | |
118 | if ((off_t)sc->fileoff > corefilesize || | |
119 | (off_t)sc->fileoff + (off_t)sc->filesize > corefilesize) { | |
120 | /* | |
121 | * We may have run out of space to write the data | |
122 | */ | |
123 | warnx("segment command points beyond end of file"); | |
124 | } | |
125 | break; | |
126 | ||
127 | case proto_LC_COREINFO: | |
128 | cic = (const void *)lc; | |
129 | if (opt->debug) { | |
130 | uuid_string_t uustr; | |
131 | uuid_unparse_lower(cic->uuid, uustr); | |
132 | printf("%8s: version %d type %d uuid %s addr %llx dyninfo %llx\n", | |
133 | "COREINFO", cic->version, cic->type, uustr, cic->address, cic->dyninfo); | |
134 | } | |
135 | if (cic->version < 1 || | |
136 | cic->type != proto_CORETYPE_USER) { | |
137 | warnx("bad coreinfo command"); | |
138 | abort(); | |
139 | } | |
140 | break; | |
141 | ||
142 | case proto_LC_FILEREF: | |
143 | frc = (const void *)lc; | |
144 | const char *nm = frc->filename.offset + (char *)lc; | |
145 | if (opt->debug) { | |
146 | uuid_string_t uustr; | |
147 | uuid_unparse_lower(frc->uuid, uustr); | |
148 | printf("%8s: mem %llx-%llx file %lld-%lld %x/%x '%s' %.12s..\n", | |
149 | "FILEREF", | |
150 | frc->vmaddr, | |
151 | frc->vmaddr + frc->vmsize, | |
152 | (off_t)frc->fileoff, | |
153 | (off_t)frc->fileoff + (off_t)frc->filesize, | |
154 | frc->initprot, frc->maxprot, nm, uustr); | |
155 | } | |
156 | if (nm <= (caddr_t)lc || | |
157 | nm > (caddr_t)lc + lc->cmdsize || | |
158 | (off_t)frc->fileoff < 0 || (off_t)frc->filesize < 0) { | |
159 | warnx("bad fileref command"); | |
160 | abort(); | |
161 | } | |
162 | break; | |
163 | ||
164 | case LC_THREAD: | |
165 | tc = (const void *)lc; | |
166 | if (opt->debug) | |
167 | printf("%8s:\n", "THREAD"); | |
168 | uint32_t *wbuf = (void *)(tc + 1); | |
169 | do { | |
170 | const uint32_t flavor = *wbuf++; | |
171 | const uint32_t count = *wbuf++; | |
172 | ||
173 | if (opt->debug) { | |
174 | printf(" flavor %u count %u\n", flavor, count); | |
175 | if (count) { | |
176 | boolean_t nl = false; | |
177 | for (unsigned k = 0; k < count; k++) { | |
178 | if (0 == (k & 7)) | |
179 | printf(" [%3u] ", k); | |
180 | printf("%08x ", *wbuf++); | |
181 | if (7 == (k & 7)) { | |
182 | printf("\n"); | |
183 | nl = true; | |
184 | } else | |
185 | nl = false; | |
186 | } | |
187 | if (!nl) | |
188 | printf("\n"); | |
189 | } | |
190 | } else | |
191 | wbuf += count; | |
192 | ||
193 | if (!VALID_THREAD_STATE_FLAVOR(flavor)) { | |
194 | warnx("bad thread state flavor"); | |
195 | abort(); | |
196 | } | |
197 | } while ((caddr_t) wbuf < (caddr_t)tc + tc->cmdsize); | |
198 | break; | |
199 | ||
200 | default: | |
201 | warnx("unknown cmd %u in header\n", lc->cmd); | |
202 | abort(); | |
203 | } | |
204 | if (lc->cmdsize) | |
205 | lc = (const void *)((caddr_t)lc + lc->cmdsize); | |
206 | else | |
207 | break; | |
208 | } | |
209 | } | |
210 | ||
211 | /* | |
212 | * The vanilla Mach-O core file consists of: | |
213 | * | |
214 | * - A Mach-O header of type MH_CORE | |
215 | * | |
216 | * A set of load commands of the following types: | |
217 | * | |
218 | * - LC_SEGMENT{,_64} pointing at memory content in the file, | |
219 | * each chunk consisting of a contiguous region. Regions may be zfod | |
220 | * (no file content present) or content may be compressed (experimental) | |
221 | * | |
222 | * - prototype_LC_COREINFO (experimental), pointing at dyld (10.12 onwards) | |
223 | * | |
224 | * - prototype_LC_FILEREF (experimental) pointing at memory | |
225 | * content to be mapped in from another file at various offsets | |
226 | * | |
227 | * - LC_THREAD commands with state for each thread | |
228 | * | |
229 | * These load commands are followed by the relevant contents of memory, | |
230 | * pointed to by the various commands. | |
231 | */ | |
232 | ||
233 | int | |
234 | coredump_write( | |
235 | const task_t task, | |
236 | const int fd, | |
237 | struct regionhead *rhead, | |
238 | const uuid_t aout_uuid, | |
239 | mach_vm_offset_t aout_load_addr, | |
240 | mach_vm_offset_t dyld_aii_addr) | |
241 | { | |
242 | struct size_segment_data ssda; | |
243 | bzero(&ssda, sizeof (ssda)); | |
244 | ||
245 | if (walk_region_list(rhead, region_size_memory, &ssda) < 0) { | |
246 | warnx(0, "cannot count segments"); | |
247 | return EX_OSERR; | |
248 | } | |
249 | ||
250 | unsigned thread_count = 0; | |
251 | mach_port_t *threads = NULL; | |
252 | kern_return_t ret = task_threads(task, &threads, &thread_count); | |
253 | if (KERN_SUCCESS != ret || thread_count < 1) { | |
254 | err_mach(ret, NULL, "cannot retrieve threads"); | |
255 | thread_count = 0; | |
256 | } | |
257 | ||
258 | if (opt->debug) { | |
259 | print_memory_region_header(); | |
260 | walk_region_list(rhead, region_print_memory, NULL); | |
261 | } | |
262 | ||
263 | size_t headersize = sizeof (native_mach_header_t) + | |
264 | thread_count * sizeof_LC_THREAD() + | |
265 | ssda.ssd_fileref.headersize + | |
266 | ssda.ssd_zfod.headersize + | |
267 | ssda.ssd_vanilla.headersize + | |
268 | ssda.ssd_sparse.headersize; | |
269 | if (opt->coreinfo) | |
270 | headersize += sizeof (struct proto_coreinfo_command); | |
271 | ||
272 | void *header = calloc(1, headersize); | |
273 | if (NULL == header) | |
274 | errx(EX_OSERR, "out of memory for header"); | |
275 | ||
276 | native_mach_header_t *mh = make_corefile_mach_header(header); | |
277 | struct load_command *lc = (void *)(mh + 1); | |
278 | ||
279 | if (opt->coreinfo) { | |
280 | const struct proto_coreinfo_command *cc = | |
281 | make_coreinfo_command(mh, lc, aout_uuid, aout_load_addr, dyld_aii_addr); | |
282 | lc = (void *)((caddr_t)cc + cc->cmdsize); | |
283 | } | |
284 | ||
285 | if (opt->debug) { | |
286 | const unsigned long fileref_count = ssda.ssd_fileref.count; | |
287 | const unsigned long segment_count = fileref_count + | |
288 | ssda.ssd_zfod.count + ssda.ssd_vanilla.count + ssda.ssd_sparse.count; | |
289 | printf("Dumping %lu memory segments", segment_count); | |
290 | if (0 != fileref_count) | |
291 | printf(" (including %lu file reference%s (%lu bytes))", | |
292 | fileref_count, 1 == fileref_count ? "" : "s", | |
293 | ssda.ssd_fileref.headersize); | |
294 | printf("\n"); | |
295 | } | |
296 | ||
297 | vm_size_t pagesize = ((vm_offset_t)1 << pageshift_host); | |
298 | vm_offset_t pagemask = (vm_offset_t)(pagesize - 1); | |
299 | ||
300 | struct write_segment_data wsda = { | |
301 | .wsd_task = task, | |
302 | .wsd_mh = mh, | |
303 | .wsd_lc = lc, | |
304 | .wsd_fd = fd, | |
305 | .wsd_foffset = ((vm_offset_t)headersize + pagemask) & ~pagemask, | |
306 | .wsd_nwritten = 0, | |
307 | }; | |
308 | ||
309 | int ecode = 0; | |
310 | if (0 != walk_region_list(rhead, region_write_memory, &wsda)) | |
311 | ecode = EX_IOERR; | |
312 | ||
313 | del_region_list(rhead); | |
314 | ||
315 | struct thread_command *tc = (void *)wsda.wsd_lc; | |
316 | ||
317 | for (unsigned t = 0; t < thread_count; t++) { | |
318 | dump_thread_state(mh, tc, threads[t]); | |
319 | mach_port_deallocate(mach_task_self(), threads[t]); | |
320 | tc = (void *)((caddr_t)tc + tc->cmdsize); | |
321 | } | |
322 | ||
323 | /* | |
324 | * Even if we've run out of space, try our best to | |
325 | * write out the header. | |
326 | */ | |
327 | if (-1 == pwrite(fd, header, headersize, 0)) | |
328 | ecode = EX_IOERR; | |
329 | else | |
330 | wsda.wsd_nwritten += headersize; | |
331 | ||
332 | validate_core_header(mh, wsda.wsd_foffset); | |
333 | ||
334 | if (ecode) | |
335 | warnx("failed to write core file correctly"); | |
336 | else if (opt->verbose) { | |
337 | hsize_str_t hsz; | |
338 | printf("Wrote %s to corefile ", str_hsize(hsz, wsda.wsd_nwritten)); | |
339 | printf("(memory image %s", str_hsize(hsz, ssda.ssd_vanilla.memsize)); | |
340 | if (ssda.ssd_sparse.memsize) | |
341 | printf("+%s", str_hsize(hsz, ssda.ssd_sparse.memsize)); | |
342 | if (ssda.ssd_fileref.memsize) | |
343 | printf(", referenced %s", str_hsize(hsz, ssda.ssd_fileref.memsize)); | |
344 | if (ssda.ssd_zfod.memsize) | |
345 | printf(", zfod %s", str_hsize(hsz, ssda.ssd_zfod.memsize)); | |
346 | printf(")\n"); | |
347 | } | |
348 | free(header); | |
349 | return ecode; | |
350 | } | |
351 | ||
352 | int | |
353 | coredump(task_t task, int fd) | |
354 | { | |
355 | /* this is the shared cache id, if any */ | |
356 | uuid_t sc_uuid; | |
357 | uuid_clear(sc_uuid); | |
358 | ||
359 | dyld_process_info dpi = get_task_dyld_info(task); | |
360 | if (dpi) { | |
361 | get_sc_uuid(dpi, sc_uuid); | |
362 | } | |
363 | ||
364 | /* this group is for LC_COREINFO */ | |
365 | mach_vm_offset_t dyld_addr = 0; // all_image_infos -or- dyld mach header | |
366 | mach_vm_offset_t aout_load_addr = 0; | |
367 | uuid_t aout_uuid; | |
368 | uuid_clear(aout_uuid); | |
369 | ||
370 | /* | |
371 | * Walk the address space | |
372 | */ | |
373 | int ecode = 0; | |
374 | struct regionhead *rhead = coredump_prepare(task, sc_uuid); | |
375 | if (NULL == rhead) { | |
376 | ecode = EX_OSERR; | |
377 | goto done; | |
378 | } | |
379 | ||
380 | if (opt->debug) | |
381 | printf("Optimizing dump content\n"); | |
382 | walk_region_list(rhead, vanilla_region_optimization, NULL); | |
383 | ||
384 | if (dpi) { | |
385 | if (opt->coreinfo || opt->sparse) { | |
386 | /* | |
387 | * Snapshot dyld's info .. | |
388 | */ | |
389 | if (!libent_build_nametable(task, dpi)) | |
390 | warnx("error parsing dyld data => ignored"); | |
391 | else { | |
392 | if (opt->coreinfo) { | |
393 | /* | |
394 | * Find the a.out load address and uuid, and the dyld mach header for the coreinfo | |
395 | */ | |
396 | const struct libent *le; | |
397 | if (NULL != (le = libent_lookup_first_bytype(MH_EXECUTE))) { | |
398 | aout_load_addr = le->le_mhaddr; | |
399 | uuid_copy(aout_uuid, le->le_uuid); | |
400 | } | |
401 | if (NULL != (le = libent_lookup_first_bytype(MH_DYLINKER))) { | |
402 | dyld_addr = le->le_mhaddr; | |
403 | } | |
404 | } | |
405 | if (opt->sparse) { | |
406 | /* | |
407 | * Use dyld's view of what's being used in the address | |
408 | * space to shrink the dump. | |
409 | */ | |
410 | if (0 == walk_region_list(rhead, decorate_memory_region, (void *)dpi)) { | |
411 | if (opt->debug) | |
412 | printf("Performing sparse dump optimization(s)\n"); | |
413 | walk_region_list(rhead, sparse_region_optimization, NULL); | |
414 | } else { | |
415 | walk_region_list(rhead, undecorate_memory_region, NULL); | |
416 | warnx("error parsing dyld data => ignored"); | |
417 | } | |
418 | } | |
419 | } | |
420 | } | |
421 | free_task_dyld_info(dpi); | |
422 | } | |
423 | ||
424 | if (opt->debug) | |
425 | printf("Optimization(s) done\n"); | |
426 | done: | |
427 | if (0 == ecode) | |
428 | ecode = coredump_write(task, fd, rhead, aout_uuid, aout_load_addr, dyld_addr); | |
429 | return ecode; | |
430 | } | |
431 | ||
432 | #ifdef CONFIG_REFSC | |
433 | ||
434 | struct find_shared_cache_args { | |
435 | task_t fsc_task; | |
436 | vm_object_id_t fsc_object_id; | |
437 | vm32_object_id_t fsc_region_object_id; | |
438 | uuid_t fsc_uuid; | |
439 | const struct libent *fsc_le; | |
440 | int fsc_fd; | |
441 | }; | |
442 | ||
443 | /* | |
444 | * This is "find the objid of the first shared cache" in the shared region. | |
445 | */ | |
446 | static walk_return_t | |
447 | find_shared_cache(struct region *r, void *arg) | |
448 | { | |
449 | struct find_shared_cache_args *fsc = arg; | |
450 | ||
451 | if (!r->r_insharedregion) | |
452 | return WALK_CONTINUE; /* wrong address range! */ | |
453 | if (0 != r->r_info.user_tag) | |
454 | return WALK_CONTINUE; /* must be tag zero */ | |
455 | if ((VM_PROT_READ | VM_PROT_EXECUTE) != r->r_info.protection || | |
456 | r->r_info.protection != r->r_info.max_protection) | |
457 | return WALK_CONTINUE; /* must be r-x / r-x */ | |
458 | if (r->r_pageinfo.offset != 0) | |
459 | return WALK_CONTINUE; /* must map beginning of file */ | |
460 | ||
461 | if (opt->debug) { | |
462 | hsize_str_t hstr; | |
463 | printf("Examining shared cache candidate %llx-%llx (%s)\n", | |
464 | R_ADDR(r), R_ENDADDR(r), str_hsize(hstr, R_SIZE(r))); | |
465 | } | |
466 | ||
467 | struct copied_dyld_cache_header *ch; | |
468 | mach_msg_type_number_t chlen = sizeof (*ch); | |
469 | kern_return_t ret = mach_vm_read(fsc->fsc_task, R_ADDR(r), sizeof (*ch), (vm_offset_t *)&ch, &chlen); | |
470 | ||
471 | if (KERN_SUCCESS != ret) { | |
472 | err_mach(ret, NULL, "mapping candidate shared region"); | |
473 | return WALK_CONTINUE; | |
474 | } | |
475 | ||
476 | uuid_t scuuid; | |
477 | if (get_uuid_from_shared_cache_mapping(ch, chlen, scuuid) && | |
478 | uuid_compare(scuuid, fsc->fsc_uuid) == 0) { | |
479 | if (opt->debug > 2) { | |
480 | uuid_string_t uustr; | |
481 | uuid_unparse_lower(fsc->fsc_uuid, uustr); | |
482 | printr(r, "found shared cache %s here\n", uustr); | |
483 | } | |
484 | if (!r->r_info.external_pager) { | |
485 | if (opt->debug) | |
486 | printf("Hmm. Found shared cache magic# + uuid, but not externally paged?\n"); | |
487 | #if 0 | |
488 | return WALK_CONTINUE; /* should be "paged" from a file */ | |
489 | #endif | |
490 | } | |
491 | // This is the ID associated with the first page of the mapping | |
492 | fsc->fsc_object_id = r->r_pageinfo.object_id; | |
493 | // This is the ID associated with the region | |
494 | fsc->fsc_region_object_id = r->r_info.object_id; | |
495 | } | |
496 | mach_vm_deallocate(mach_task_self(), (vm_offset_t)ch, chlen); | |
497 | if (fsc->fsc_object_id) { | |
498 | if (opt->debug) { | |
499 | uuid_string_t uu; | |
500 | uuid_unparse_lower(fsc->fsc_uuid, uu); | |
501 | printf("Shared cache objid %llx uuid %s\n", | |
502 | fsc->fsc_object_id, uu); | |
503 | } | |
504 | return WALK_TERMINATE; | |
505 | } | |
506 | return WALK_CONTINUE; | |
507 | } | |
508 | ||
509 | static boolean_t | |
510 | compare_region_with_shared_cache(const struct region *r, struct find_shared_cache_args *fsc) | |
511 | { | |
512 | struct stat st; | |
513 | if (-1 == fstat(fsc->fsc_fd, &st)) { | |
514 | if (opt->debug) | |
515 | printr(r, "%s - %s\n", | |
516 | fsc->fsc_le->le_filename, strerror(errno)); | |
517 | return false; | |
518 | } | |
519 | void *file = mmap(NULL, (size_t)R_SIZE(r), PROT_READ, MAP_PRIVATE, fsc->fsc_fd, r->r_pageinfo.offset); | |
520 | if ((void *)-1L == file) { | |
521 | if (opt->debug) | |
522 | printr(r, "mmap %s - %s\n", fsc->fsc_le->le_filename, strerror(errno)); | |
523 | return false; | |
524 | } | |
525 | madvise(file, (size_t)R_SIZE(r), MADV_SEQUENTIAL); | |
526 | ||
527 | vm_offset_t data = 0; | |
528 | mach_msg_type_number_t data_count; | |
529 | const kern_return_t kr = mach_vm_read(fsc->fsc_task, R_ADDR(r), R_SIZE(r), &data, &data_count); | |
530 | ||
531 | if (KERN_SUCCESS != kr || data_count < R_SIZE(r)) { | |
532 | err_mach(kr, r, "mach_vm_read()"); | |
533 | munmap(file, (size_t)R_SIZE(r)); | |
534 | return false; | |
535 | } | |
536 | ||
537 | mach_vm_size_t cmpsize = data_count; | |
538 | ||
539 | #ifdef RDAR_23744374 | |
540 | /* | |
541 | * Now we have the corresponding regions mapped, we should be | |
542 | * able to compare them. There's just one last twist that relates | |
543 | * to heterogenous pagesize systems: rdar://23744374 | |
544 | */ | |
545 | if (st.st_size < (off_t)(r->r_pageinfo.offset + cmpsize) && | |
546 | pageshift_host < pageshift_app) { | |
547 | /* | |
548 | * Looks like we're about to map close to the end of the object. | |
549 | * Check what's really mapped there and reduce the size accordingly. | |
550 | */ | |
551 | if (!is_actual_size(fsc->fsc_task, r, &cmpsize)) { | |
552 | if (opt->debug) | |
553 | printr(r, "narrowing the comparison (%llu " | |
554 | "-> %llu)\n", R_SIZE(r), cmpsize); | |
555 | } | |
556 | } | |
557 | #endif | |
558 | ||
559 | mach_vm_behavior_set(mach_task_self(), data, data_count, VM_BEHAVIOR_SEQUENTIAL); | |
560 | ||
561 | const boolean_t thesame = memcmp(file, (void *)data, (size_t)cmpsize) == 0; | |
562 | #if 0 | |
563 | if (!thesame) { | |
564 | int diffcount = 0; | |
565 | int samecount = 0; | |
566 | const char *f = file; | |
567 | const char *d = (void *)data; | |
568 | for (mach_vm_size_t off = 0; off < cmpsize; off += 4096) { | |
569 | if (memcmp(f, d, 4096) != 0) { | |
570 | diffcount++; | |
571 | } else samecount++; | |
572 | f += 4096; | |
573 | d += 4096; | |
574 | } | |
575 | if (diffcount) | |
576 | printr(r, "%d of %d pages different\n", diffcount, diffcount + samecount); | |
577 | } | |
578 | #endif | |
579 | mach_vm_deallocate(mach_task_self(), data, data_count); | |
580 | munmap(file, (size_t)R_SIZE(r)); | |
581 | ||
582 | if (!thesame && opt->debug) | |
583 | printr(r, "mapped file (%s) region is modified\n", fsc->fsc_le->le_filename); | |
584 | return thesame; | |
585 | } | |
586 | ||
587 | static walk_return_t | |
588 | label_shared_cache(struct region *r, void *arg) | |
589 | { | |
590 | struct find_shared_cache_args *fsc = arg; | |
591 | ||
592 | if (!r->r_insharedregion) | |
593 | return WALK_CONTINUE; | |
594 | if (!r->r_info.external_pager) | |
595 | return WALK_CONTINUE; | |
596 | if (r->r_pageinfo.object_id != fsc->fsc_object_id) { | |
597 | /* wrong object, or first page already modified */ | |
598 | return WALK_CONTINUE; | |
599 | } | |
600 | if (((r->r_info.protection | r->r_info.max_protection) & VM_PROT_WRITE) != 0) { | |
601 | /* writable, but was it written? */ | |
602 | if (r->r_info.pages_dirtied + r->r_info.pages_swapped_out != 0) | |
603 | return WALK_CONTINUE; // a heuristic .. | |
604 | if (!compare_region_with_shared_cache(r, fsc)) { | |
605 | /* bits don't match */ | |
606 | return WALK_CONTINUE; | |
607 | } | |
608 | } | |
609 | ||
610 | if (opt->debug > 2) { | |
611 | /* this validation is -really- expensive */ | |
612 | if (!compare_region_with_shared_cache(r, fsc)) | |
613 | printr(r, "WARNING: region should match, but doesn't\n"); | |
614 | } | |
615 | ||
616 | /* | |
617 | * This mapped file segment will be represented as a reference | |
618 | * to the file, rather than as a copy of the file. | |
619 | */ | |
620 | const struct libent *le = libent_lookup_byuuid(fsc->fsc_uuid); | |
621 | r->r_fileref = calloc(1, sizeof (*r->r_fileref)); | |
622 | if (r->r_fileref) { | |
623 | r->r_fileref->fr_libent = le; | |
624 | if (r->r_fileref->fr_libent) { | |
625 | r->r_fileref->fr_offset = r->r_pageinfo.offset; | |
626 | r->r_op = &fileref_ops; | |
627 | } else { | |
628 | free(r->r_fileref); | |
629 | r->r_fileref = NULL; | |
630 | } | |
631 | } | |
632 | return WALK_CONTINUE; | |
633 | } | |
634 | #endif /* CONFIG_REFSC */ | |
635 | ||
636 | struct regionhead * | |
637 | coredump_prepare(task_t task, uuid_t sc_uuid) | |
638 | { | |
639 | struct regionhead *rhead = build_region_list(task); | |
640 | ||
641 | if (opt->debug) { | |
642 | print_memory_region_header(); | |
643 | walk_region_list(rhead, region_print_memory, NULL); | |
644 | } | |
645 | ||
646 | if (uuid_is_null(sc_uuid)) | |
647 | return rhead; | |
648 | ||
649 | /* | |
650 | * Name the shared cache, if we can | |
651 | */ | |
652 | char *nm = shared_cache_filename(sc_uuid); | |
653 | const struct libent *le; | |
654 | ||
655 | if (NULL != nm) | |
656 | le = libent_insert(nm, sc_uuid, 0, NULL); | |
657 | else { | |
658 | le = libent_insert("(shared cache)", sc_uuid, 0, NULL); | |
659 | if (opt->verbose){ | |
660 | uuid_string_t uustr; | |
661 | uuid_unparse_lower(sc_uuid, uustr); | |
662 | printf("Shared cache UUID: %s, but no filename => ignored\n", uustr); | |
663 | return rhead; | |
664 | } | |
665 | } | |
666 | ||
667 | #ifdef CONFIG_REFSC | |
668 | if (opt->scfileref) { | |
669 | /* | |
670 | * See if we can replace entire regions with references to the shared cache | |
671 | * by looking at the VM meta-data about those regions. | |
672 | */ | |
673 | if (opt->debug) { | |
674 | uuid_string_t uustr; | |
675 | uuid_unparse_lower(sc_uuid, uustr); | |
676 | printf("Searching for shared cache with uuid %s\n", uustr); | |
677 | } | |
678 | ||
679 | /* | |
680 | * Identify the regions mapping the shared cache by comparing the UUID via | |
681 | * dyld with the UUID of likely-looking mappings in the right address range | |
682 | */ | |
683 | struct find_shared_cache_args fsca; | |
684 | bzero(&fsca, sizeof (fsca)); | |
685 | fsca.fsc_task = task; | |
686 | uuid_copy(fsca.fsc_uuid, sc_uuid); | |
687 | fsca.fsc_fd = -1; | |
688 | ||
689 | walk_region_list(rhead, find_shared_cache, &fsca); | |
690 | ||
691 | if (0 == fsca.fsc_object_id) { | |
692 | printf("Cannot identify the shared cache region(s) => ignored\n"); | |
693 | } else { | |
694 | if (opt->verbose) | |
695 | printf("Referenced %s\n", nm); | |
696 | fsca.fsc_le = le; | |
697 | fsca.fsc_fd = open(fsca.fsc_le->le_filename, O_RDONLY); | |
698 | ||
699 | walk_region_list(rhead, label_shared_cache, &fsca); | |
700 | ||
701 | close(fsca.fsc_fd); | |
702 | free(nm); | |
703 | } | |
704 | } | |
705 | #endif /* CONFIG_REFSC */ | |
706 | ||
707 | return rhead; | |
708 | } |