]> git.saurik.com Git - apple/system_cmds.git/blob - gcore.tproj/convert.c
system_cmds-880.100.5.tar.gz
[apple/system_cmds.git] / gcore.tproj / convert.c
1 /*
2 * Copyright (c) 2016 Apple Inc. All rights reserved.
3 */
4
5 #include "convert.h"
6 #include "corefile.h"
7 #include "vanilla.h"
8 #include "threads.h"
9 #include "vm.h"
10 #include "dyld_shared_cache.h"
11 #include "utils.h"
12
13 #include <sys/types.h>
14 #include <sys/mman.h>
15 #include <sys/stat.h>
16 #include <sys/queue.h>
17 #include <sys/param.h>
18 #include <mach-o/fat.h>
19 #include <uuid/uuid.h>
20 #include <fcntl.h>
21 #include <stdlib.h>
22 #include <stdio.h>
23 #include <unistd.h>
24 #include <errno.h>
25 #include <err.h>
26 #include <sysexits.h>
27 #include <time.h>
28 #include <glob.h>
29 #include <spawn.h>
30 #include <signal.h>
31 #include <xpc/xpc.h>
32 #include <xpc/private.h>
33 #include <sys/event.h>
34 #include <sys/time.h>
35
36 #if defined(CONFIG_GCORE_MAP) || defined(CONFIG_GCORE_CONV) || defined(CONFIG_GCORE_FREF)
37
38 static const void *
39 mmapfile(int fd, off_t off, off_t *filesize)
40 {
41 struct stat st;
42 if (-1 == fstat(fd, &st))
43 errc(EX_OSERR, errno, "can't stat input file");
44
45 const size_t size = (size_t)(st.st_size - off);
46 if ((off_t)size != (st.st_size - off))
47 errc(EX_OSERR, EOVERFLOW, "input file too large?");
48
49 const void *addr = mmap(0, size, PROT_READ, MAP_PRIVATE, fd, off);
50 if ((void *)-1 == addr)
51 errc(EX_OSERR, errno, "can't mmap input file");
52 *filesize = st.st_size;
53 return addr;
54 }
55
56 static void
57 walkcore(
58 const native_mach_header_t *mh,
59 void (^coreinfo)(const struct proto_coreinfo_command *),
60 void (^frefdata)(const struct proto_fileref_command *),
61 void (^coredata)(const struct proto_coredata_command *),
62 void (^segdata)(const native_segment_command_t *),
63 void (^thrdata)(const struct thread_command *))
64 {
65 const struct load_command *lc = (const void *)(mh + 1);
66 for (unsigned i = 0; i < mh->ncmds; i++) {
67 switch (lc->cmd) {
68 case proto_LC_COREINFO:
69 if (coreinfo)
70 coreinfo((const void *)lc);
71 break;
72 case proto_LC_FILEREF:
73 if (frefdata)
74 frefdata((const void *)lc);
75 break;
76 case proto_LC_COREDATA:
77 if (coredata)
78 coredata((const void *)lc);
79 break;
80 case NATIVE_LC_SEGMENT:
81 if (segdata)
82 segdata((const void *)lc);
83 break;
84 case LC_THREAD:
85 if (thrdata)
86 thrdata((const void *)lc);
87 break;
88 default:
89 break;
90 }
91 if (NULL == (lc = next_lc(lc)))
92 break;
93 }
94 }
95
96 #endif
97
98 #ifdef CONFIG_GCORE_FREF
99
100 int
101 gcore_fref(int fd)
102 {
103 off_t filesize;
104 const void *corebase = mmapfile(fd, 0, &filesize);
105
106 close(fd);
107 struct flist {
108 STAILQ_ENTRY(flist) f_linkage;
109 const char *f_nm;
110 unsigned long f_nmhash;
111 };
112 STAILQ_HEAD(flisthead, flist) __flh, *flh = &__flh;
113 STAILQ_INIT(flh);
114
115 walkcore(corebase, NULL, ^(const struct proto_fileref_command *fc) {
116 const char *nm = fc->filename.offset + (const char *)fc;
117 const unsigned long nmhash = simple_namehash(nm);
118 struct flist *f;
119 STAILQ_FOREACH(f, flh, f_linkage) {
120 if (nmhash == f->f_nmhash && 0 == strcmp(f->f_nm, nm))
121 return; /* skip duplicates */
122 }
123 struct flist *nf = calloc(1, sizeof (*nf));
124 nf->f_nm = nm;
125 nf->f_nmhash = nmhash;
126 STAILQ_INSERT_TAIL(flh, nf, f_linkage);
127 }, NULL, NULL, NULL);
128
129 struct flist *f, *tf;
130 STAILQ_FOREACH_SAFE(f, flh, f_linkage, tf) {
131 printf("%s\n", f->f_nm);
132 free(f);
133 f = NULL;
134 }
135
136 munmap((void *)corebase, (size_t)filesize);
137 return 0;
138 }
139
140 #endif /* CONFIG_GCORE_FREF */
141
142 #ifdef CONFIG_GCORE_MAP
143
144 /*
145 * A pale imitation of vmmap, but for core files
146 */
147 int
148 gcore_map(int fd)
149 {
150 off_t filesize;
151 const void *corebase = mmapfile(fd, 0, &filesize);
152
153 __block int coreversion = 0;
154
155 walkcore(corebase, ^(const struct proto_coreinfo_command *ci) {
156 coreversion = ci->version;
157 }, NULL, NULL, NULL, NULL);
158
159 if (0 == coreversion) {
160 const char titlfmt[] = "%16s-%-16s [%7s] %3s/%3s\n";
161 const char *segcfmt = "%016llx-%016llx [%7s] %3s/%3s\n";
162
163 printf(titlfmt, "start ", " end", "vsize", "prt", "max");
164 walkcore(corebase, NULL, NULL, NULL, ^(const native_segment_command_t *sc) {
165 hsize_str_t vstr;
166 printf(segcfmt, (mach_vm_offset_t)sc->vmaddr, (mach_vm_offset_t)sc->vmaddr + sc->vmsize, str_hsize(vstr, sc->vmsize), str_prot(sc->initprot), str_prot(sc->maxprot));
167 }, NULL);
168 } else {
169 const char titlfmt[] = "%-23s %16s-%-16s [%7s] %3s/%3s %6s %4s %-14s\n";
170 const char *freffmt = "%-23s %016llx-%016llx [%7s] %3s/%3s %6s %4s %-14s @%lld\n";
171 const char *datafmt = "%-23s %016llx-%016llx [%7s] %3s/%3s %6s %4s %-14s\n";
172
173 printf(titlfmt, "region type", "start ", " end", "vsize", "prt", "max", "shrmod", "purge", "region detail");
174 walkcore(corebase, NULL, ^(const struct proto_fileref_command *fc) {
175 const char *nm = fc->filename.offset + (const char *)fc;
176 tag_str_t tstr;
177 hsize_str_t vstr;
178 printf(freffmt, str_tag(tstr, fc->tag, fc->share_mode, fc->prot, fc->extp),
179 fc->vmaddr, fc->vmaddr + fc->vmsize,
180 str_hsize(vstr, fc->vmsize), str_prot(fc->prot),
181 str_prot(fc->maxprot), str_shared(fc->share_mode),
182 str_purgable(fc->purgable, fc->share_mode), nm, fc->fileoff);
183 }, ^(const struct proto_coredata_command *cc) {
184 tag_str_t tstr;
185 hsize_str_t vstr;
186 printf(datafmt, str_tag(tstr, cc->tag, cc->share_mode, cc->prot, cc->extp),
187 cc->vmaddr, cc->vmaddr + cc->vmsize,
188 str_hsize(vstr, cc->vmsize), str_prot(cc->prot),
189 str_prot(cc->maxprot), str_shared(cc->share_mode),
190 str_purgable(cc->purgable, cc->share_mode),
191 cc->vmsize && 0 == cc->filesize ? "(zfod)" : "");
192 }, ^(const native_segment_command_t *sc) {
193 hsize_str_t vstr;
194 printf(datafmt, "", (mach_vm_offset_t)sc->vmaddr,
195 (mach_vm_offset_t)sc->vmaddr + sc->vmsize,
196 str_hsize(vstr, sc->vmsize), str_prot(sc->initprot),
197 str_prot(sc->maxprot), "", "",
198 sc->vmsize && 0 == sc->filesize ? "(zfod)" : "");
199 }, NULL);
200 }
201 close(fd);
202 munmap((void *)corebase, (size_t)filesize);
203 return 0;
204 }
205
206 #endif
207
208 #ifdef CONFIG_GCORE_CONV
209
210 /*
211 * Convert an input core file into an "old" format core file
212 * (a) convert all fileref segments into regular segments
213 * (b) uncompress anything we find compressed.
214 * This should be equivalent to a copy for an "old" format core file.
215 */
216
217 static int
218 machocmp(const native_mach_header_t *tmh, const native_mach_header_t *mh, const struct proto_fileref_command *fr)
219 {
220 if (tmh->magic == mh->magic) {
221 const struct load_command *lc = (const void *)(tmh + 1);
222 for (unsigned i = 0; i < tmh->ncmds; i++) {
223 if (LC_UUID == lc->cmd && lc->cmdsize >= sizeof (struct uuid_command)) {
224 const struct uuid_command *uc = (const void *)lc;
225 return uuid_compare(uc->uuid, fr->id);
226 }
227 if (NULL == (lc = next_lc(lc)))
228 break;
229 }
230 }
231 return -1;
232 }
233
234 static int
235 fat_machocmp(const struct fat_header *fh, const native_mach_header_t *mh, const struct proto_fileref_command *fr, off_t *reloff)
236 {
237 const uint32_t (^get32)(uint32_t);
238
239 if (FAT_MAGIC == fh->magic) {
240 get32 = ^(uint32_t val) {
241 return val;
242 };
243 } else {
244 get32 = ^(uint32_t val) {
245 uint32_t result = 0;
246 for (unsigned i = 0; i < sizeof (uint32_t); i++)
247 ((uint8_t *)&result)[i] = ((uint8_t *)&val)[3-i];
248 return result;
249 };
250 }
251
252 assert(FAT_MAGIC == get32(fh->magic));
253 assert(kFREF_ID_UUID == FREF_ID_TYPE(fr->flags) && !uuid_is_null(fr->id));
254
255 const struct fat_arch *fa = (const struct fat_arch *)(fh + 1);
256 uint32_t narch = get32(fh->nfat_arch);
257 for (unsigned n = 0; n < narch; n++, fa++) {
258 const native_mach_header_t *tmh = (const void *)(((const char *)fh) + get32(fa->offset));
259 if (tmh->magic == mh->magic && 0 == machocmp(tmh, mh, fr)) {
260 *reloff = get32(fa->offset);
261 return 0;
262 }
263 }
264 return -1;
265 }
266
267 struct output_info {
268 int oi_fd;
269 off_t oi_foffset;
270 bool oi_nocache;
271 };
272
273 static struct convstats {
274 int64_t copied;
275 int64_t added;
276 int64_t compressed;
277 int64_t uncompressed;
278 } cstat, *cstats = &cstat;
279
280 /*
281 * A fileref segment references a read-only file that contains pages from
282 * the image. The file may be a Mach binary or dylib identified with a uuid.
283 */
284 static int
285 convert_fileref_with_file(const char *filename, const native_mach_header_t *inmh, const struct proto_fileref_command *infr, const struct vm_range *invr, struct load_command *lc, struct output_info *oi)
286 {
287 assert(invr->addr == infr->vmaddr && invr->size == infr->vmsize);
288
289 struct stat st;
290 const int rfd = open(filename, O_RDONLY);
291 if (-1 == rfd || -1 == fstat(rfd, &st)) {
292 warnc(errno, "%s: open", filename);
293 return EX_IOERR;
294 }
295 const size_t rlen = (size_t)st.st_size;
296 void *raddr = mmap(NULL, rlen, PROT_READ, MAP_PRIVATE, rfd, 0);
297 if ((void *)-1 == raddr) {
298 warnc(errno, "%s: mmap", filename);
299 close(rfd);
300 return EX_IOERR;
301 }
302 close(rfd);
303
304 off_t fatoff = 0; /* for FAT objects */
305 int ecode = EX_DATAERR;
306
307 switch (FREF_ID_TYPE(infr->flags)) {
308 case kFREF_ID_UUID: {
309 /* file should be a mach binary: check that uuid matches */
310 const uint32_t magic = *(uint32_t *)raddr;
311 switch (magic) {
312 case FAT_MAGIC:
313 case FAT_CIGAM:
314 if (0 == fat_machocmp(raddr, inmh, infr, &fatoff))
315 ecode = 0;
316 break;
317 case NATIVE_MH_MAGIC:
318 if (0 == machocmp(raddr, inmh, infr))
319 ecode = 0;
320 break;
321 default: {
322 /*
323 * Maybe this is the shared cache?
324 */
325 uuid_t uu;
326 if (get_uuid_from_shared_cache_mapping(raddr, rlen, uu) && uuid_compare(uu, infr->id) == 0)
327 ecode = 0;
328 break;
329 }
330 }
331 break;
332 }
333 case kFREF_ID_MTIMESPEC_LE:
334 /* file should have the same mtime */
335 if (0 == memcmp(&st.st_mtimespec, infr->id, sizeof (infr->id)))
336 ecode = 0;
337 break;
338 case kFREF_ID_NONE:
339 /* file has no uniquifier, copy it anyway */
340 break;
341 }
342
343 if (0 != ecode) {
344 munmap(raddr, rlen);
345 warnx("%s doesn't match corefile content", filename);
346 return ecode;
347 }
348
349 const off_t fileoff = fatoff + infr->fileoff;
350 const void *start = (const char *)raddr + fileoff;
351 const size_t len = (size_t)infr->filesize;
352 void *zaddr = NULL;
353 size_t zlen = 0;
354
355 if (fileoff + (off_t)infr->filesize > (off_t)rlen) {
356 /*
357 * the file content needed (as described on machine with
358 * larger pagesize) extends beyond the end of the mapped
359 * file using our smaller pagesize. Zero pad it.
360 */
361 const size_t pagesize_host = 1ul << pageshift_host;
362 void *endaddr = (caddr_t)raddr + roundup(rlen, pagesize_host);
363 zlen = (size_t)(fileoff + infr->filesize - rlen);
364 zaddr = mmap(endaddr, zlen, PROT_READ, MAP_FIXED | MAP_PRIVATE | MAP_ANON, -1, 0);
365 if ((void *)-1 == zaddr) {
366 hsize_str_t hstr;
367 warnc(errno, "cannot zero-pad %s mapping for %s", str_hsize(hstr, zlen),filename);
368 munmap(raddr, rlen);
369 return EX_IOERR;
370 }
371 }
372
373 if (-1 == madvise((void *)start, len, MADV_SEQUENTIAL))
374 warnc(errno, "%s: madvise", filename);
375
376 const int error = bounded_pwrite(oi->oi_fd, start, len, oi->oi_foffset, &oi->oi_nocache, NULL);
377
378 if (zlen) {
379 if (-1 == munmap(zaddr, zlen))
380 warnc(errno, "%s: munmap zero pad", filename);
381 }
382 if (-1 == munmap(raddr, rlen))
383 warnc(errno, "%s: munmap", filename);
384 if (error) {
385 warnc(error, "while copying %s to core file", filename);
386 return EX_IOERR;
387 }
388
389 const struct file_range fr = {
390 .off = oi->oi_foffset,
391 .size = infr->filesize,
392 };
393 make_native_segment_command(lc, invr, &fr, infr->maxprot, infr->prot);
394 oi->oi_foffset += fr.size;
395 cstats->added += infr->filesize;
396 return 0;
397 }
398
399 /*
400 * expanduser tries to expand the leading '~' (if there is any) in the given
401 * path and returns a copy of the expanded path; it returns NULL on failures.
402 * The caller is responsible for freeing the returned string.
403 */
404 static char *
405 expanduser(const char *path)
406 {
407 if (path == NULL) {
408 return NULL;
409 }
410 if (path[0] != '~') {
411 /*
412 * For consistency, still dup the string so that the caller always
413 * needs to free the string.
414 */
415 return strdup(path);
416 }
417
418 char *expanded = NULL;
419 glob_t globbuf = {};
420 if (OPTIONS_DEBUG(opt, 1)) {
421 printf("Expanding %s\n", path);
422 }
423 int rc = glob(path, GLOB_TILDE, NULL, &globbuf);
424 if (rc == 0) {
425 if (OPTIONS_DEBUG(opt, 3)) {
426 printf("expanduser - gl_pathc: %zu\n", globbuf.gl_pathc);
427 for (size_t i = 0; i < globbuf.gl_pathc; i++) {
428 printf("expanduser - gl_pathv[%zu]: %s\n", i, globbuf.gl_pathv[i]);
429 }
430 }
431 if (globbuf.gl_pathc == 1) {
432 expanded = strdup(globbuf.gl_pathv[0]);
433 if (OPTIONS_DEBUG(opt, 1)) {
434 printf("Expanded path: %s\n", expanded);
435 }
436 }
437 globfree(&globbuf);
438 }
439
440 return expanded;
441 }
442
443 #define RESPONSE_BUFF_SIZE (2048)
444
445 /*
446 * read_response dynamically allocates buffer for reading bytes from the given
447 * fd. Upon success, this function sets response to point to the buffer and
448 * returns bytes being read; otherwise, it returns -1. The caller is
449 * responsible for freeing the response buffer.
450 */
451 static ssize_t
452 read_response(int fd, char **response)
453 {
454 if (response == NULL || *response) {
455 warnx("Invalid response buffer pointer");
456 return -1;
457 }
458
459 ssize_t bytes_read = 0;
460 size_t buff_size = RESPONSE_BUFF_SIZE;
461
462 if (OPTIONS_DEBUG(opt, 3)) {
463 printf("Allocating response buffer (%zu)\n", buff_size);
464 }
465 char *buff = malloc(buff_size);
466 if (buff == NULL) {
467 warn("Failed to allocate response buffer (%zu)", buff_size);
468 return -1;
469 }
470
471 size_t total = 0;
472 bool failed = false;
473
474 do {
475 bytes_read = read(fd, buff + total, buff_size - total);
476 if (bytes_read == -1) {
477 if (errno == EINTR) {
478 continue;
479 }
480 failed = true;
481 break;
482 }
483
484 total += (size_t)bytes_read;
485 if (total == buff_size) {
486 size_t new_buff_size = buff_size * 2;
487 if (OPTIONS_DEBUG(opt, 3)) {
488 printf("Reallocating response buffer (%zu)\n", new_buff_size);
489 }
490 char *new_buff = realloc(buff, new_buff_size);
491 if (new_buff == NULL) {
492 warn("Failed to reallocate response buffer (%zu)", new_buff_size);
493 failed = true;
494 break;
495 }
496 buff_size = new_buff_size;
497 buff = new_buff;
498 }
499 } while (bytes_read != 0);
500
501 if (failed) {
502 if (buff != NULL) {
503 free(buff);
504 }
505 return -1;
506 }
507
508 assert(total < buff_size);
509 buff[total] = '\0';
510 *response = buff;
511
512 return (ssize_t)total;
513 }
514
515 #define WAITPID_WTO_SIGALRM (100) /* alternative for SIGALRM for kevent timeout */
516 #define WAITPID_WTO_SIGERR (101) /* sig for error when waiting for pid */
517
518 /*
519 * waitpid_with_timeout returns true if the process exits successfully within
520 * timeout; otherwise, it returns false along with setting exitstatus and
521 * signal_no if the pointers are given.
522 */
523 static bool
524 waitpid_with_timeout(pid_t pid, int *exitstatus, int *signal_no, int timeout)
525 {
526 int status;
527 int kq = -1;
528
529 if (timeout > 0) {
530 kq = kqueue();
531 struct kevent64_s event = {
532 .ident = (uint64_t)pid,
533 .filter = EVFILT_PROC,
534 .flags = EV_ADD | EV_ONESHOT,
535 .fflags = NOTE_EXIT
536 };
537 struct timespec tmout = {
538 .tv_sec = timeout
539 };
540 int ret = kevent64(kq, &event, 1, &event, 1, 0, &tmout);
541 int kevent64_errno = errno;
542
543 close(kq);
544 if (ret == 0) { /* timeout */
545 if (exitstatus) {
546 *exitstatus = 0;
547 }
548 if (signal_no) {
549 *signal_no = WAITPID_WTO_SIGALRM;
550 }
551 return false;
552 }
553
554 if (ret == -1) {
555 warnx("kevent64(): errno=%d (%s)\n", kevent64_errno, strerror(kevent64_errno));
556 goto waitpid_error;
557 }
558
559 if (event.flags == EV_ERROR && event.data != ESRCH) {
560 warnx("event.data (%lld) is not ESRCH when event.flags is EV_ERROR\n", event.data);
561 goto waitpid_error;
562 }
563
564 if (event.ident != (uint64_t)pid) {
565 warnx("event.ident is %lld (should be pid %d)\n", event.ident, pid);
566 goto waitpid_error;
567 }
568
569 if (event.filter != EVFILT_PROC) {
570 warnx("event.filter (%d) is not EVFILT_PROC\n", event.filter);
571 goto waitpid_error;
572 }
573 }
574
575 while (waitpid(pid, &status, 0) < 0) {
576 if (errno == EINTR) {
577 continue;
578 }
579 warnx("waitpid(): errno=%d (%s)\n", errno, strerror(errno));
580 goto waitpid_error;
581 }
582 if (WIFEXITED(status)) {
583 if (exitstatus) {
584 *exitstatus = WEXITSTATUS(status);
585 }
586 if (signal_no) {
587 *signal_no = 0;
588 }
589 return WEXITSTATUS(status) == 0;
590 }
591 if (WIFSIGNALED(status)) {
592 if (exitstatus) {
593 *exitstatus = 0;
594 }
595 if (signal_no) {
596 *signal_no = WTERMSIG(status);
597 }
598 return false;
599 }
600
601 waitpid_error:
602 if (exitstatus) *exitstatus = 0;
603 if (signal_no) *signal_no = WAITPID_WTO_SIGERR;
604 return false;
605 }
606
607 #define DSYMFORUUID_PATH "/usr/local/bin/dsymForUUID"
608
609 /*
610 * exec_dsymForUUID spawns dsymForUUID to query dsym UUID info and responds the
611 * result plist. Upon success, this function sets response point to the buffer
612 * and returns bytes being read; otherwise, it returns -1. The caller is
613 * responsible for freeing the response buffer.
614 */
615 static ssize_t
616 exec_dsymForUUID(uuid_string_t id, char **response)
617 {
618 int pipe_fds[2] = {-1, -1};
619 bool file_actions_inited = false;
620 ssize_t bytes_read = -1;
621 int rc;
622
623 rc = pipe(pipe_fds);
624 if (rc == -1) {
625 goto cleanup;
626 }
627
628 posix_spawn_file_actions_t file_actions;
629 rc = posix_spawn_file_actions_init(&file_actions);
630 if (rc) {
631 goto cleanup;
632 }
633 file_actions_inited = true;
634
635 rc = posix_spawn_file_actions_addclose(&file_actions, pipe_fds[0]);
636 if (rc) {
637 goto cleanup;
638 }
639
640 rc = posix_spawn_file_actions_adddup2(&file_actions, pipe_fds[1], STDOUT_FILENO);
641 if (rc) {
642 goto cleanup;
643 }
644
645 rc = posix_spawn_file_actions_addclose(&file_actions, pipe_fds[1]);
646 if (rc) {
647 goto cleanup;
648 }
649
650 char *command[] = {DSYMFORUUID_PATH, id, NULL};
651 pid_t child;
652 rc = posix_spawn(&child, command[0], &file_actions, NULL, command, NULL);
653 if (rc) {
654 goto cleanup;
655 }
656
657 close(pipe_fds[1]);
658 pipe_fds[1] = -1;
659
660 bytes_read = read_response(pipe_fds[0], response);
661
662 waitpid_with_timeout(child, NULL, NULL, 3);
663
664 cleanup:
665 if (pipe_fds[1] != -1) {
666 close(pipe_fds[1]);
667 }
668 if (pipe_fds[0] != -1) {
669 close(pipe_fds[0]);
670 }
671 if (file_actions_inited) {
672 posix_spawn_file_actions_destroy(&file_actions);
673 }
674
675 return bytes_read;
676 }
677
678 /*
679 * get_symbol_rich_executable_path_via_dsymForUUID spawns dsymForUUID to query
680 * dsym uuid info for the given uuid and returns the string of
681 * DBGSymbolRichExecutable; otherwise, it returns NULL on failures. The caller
682 * is responsible for freeing the returned string.
683 */
684 static char *
685 get_symbol_rich_executable_path_via_dsymForUUID(const uuid_t uuid)
686 {
687 char *response;
688 ssize_t size;
689 uuid_string_t uuid_str;
690 xpc_object_t plist = NULL;
691 xpc_object_t uuid_info = NULL;
692 xpc_object_t exec_path = NULL;
693 char *expanded_exec_path = NULL;
694
695 uuid_unparse_upper(uuid, uuid_str);
696
697 size = exec_dsymForUUID(uuid_str, &response);
698 if (size <= 0) {
699 goto cleanup;
700 }
701
702 if (OPTIONS_DEBUG(opt, 3)) {
703 printf("dsymForUUID response:\n%s\n", response);
704 }
705
706 plist = xpc_create_from_plist(response, (size_t)size);
707 if (plist == NULL) {
708 goto cleanup;
709 }
710 if (xpc_get_type(plist) != XPC_TYPE_DICTIONARY) {
711 goto cleanup;
712 }
713
714 uuid_info = xpc_dictionary_get_value(plist, uuid_str);
715 if (uuid_info == NULL) {
716 goto cleanup;
717 }
718 if (xpc_get_type(uuid_info) != XPC_TYPE_DICTIONARY) {
719 goto cleanup;
720 }
721
722 exec_path = xpc_dictionary_get_value(uuid_info, "DBGSymbolRichExecutable");
723 if (exec_path == NULL) {
724 goto cleanup;
725 }
726 if (xpc_get_type(exec_path) != XPC_TYPE_STRING) {
727 goto cleanup;
728 }
729
730 expanded_exec_path = expanduser(xpc_string_get_string_ptr(exec_path));
731
732 cleanup:
733 if (plist) {
734 xpc_release(plist);
735 }
736 if (response) {
737 free(response);
738 }
739
740 return expanded_exec_path;
741 }
742
743 /*
744 * bind the file reference into the output core file.
745 * filename optionally prefixed with names from a ':'-separated PATH variable
746 */
747 static int
748 convert_fileref(const char *path, bool zf, const native_mach_header_t *inmh, const struct proto_fileref_command *infr, struct load_command *lc, struct output_info *oi)
749 {
750 const char *nm = infr->filename.offset + (const char *)infr;
751 uuid_string_t uustr;
752 const struct vm_range invr = {
753 .addr = infr->vmaddr,
754 .size = infr->vmsize,
755 };
756
757 if (opt->verbose) {
758 hsize_str_t hstr;
759 printvr(&invr, "adding %s from '%s'",
760 str_hsize(hstr, (off_t)infr->filesize), nm);
761 switch (FREF_ID_TYPE(infr->flags)) {
762 case kFREF_ID_NONE:
763 break;
764 case kFREF_ID_UUID:
765 uuid_unparse_lower(infr->id, uustr);
766 printf(" (%s)", uustr);
767 break;
768 case kFREF_ID_MTIMESPEC_LE: {
769 struct timespec mts;
770 struct tm tm;
771 char tbuf[4 + 2 + 2 + 2 + 2 + 1 + 2 + 1]; /* touch -t */
772 memcpy(&mts, &infr->id, sizeof (mts));
773 localtime_r(&mts.tv_sec, &tm);
774 strftime(tbuf, sizeof (tbuf), "%Y%m%d%H%M.%S", &tm);
775 printf(" (%s)", tbuf);
776 } break;
777 }
778 printf("\n");
779 }
780
781 int ecode = 0;
782 if (opt->dsymforuuid && (FREF_ID_TYPE(infr->flags) == kFREF_ID_UUID)) {
783 /* Try to use dsymForUUID to get the symbol-rich executable */
784 char *symrich_filepath = get_symbol_rich_executable_path_via_dsymForUUID(infr->id);
785 if (symrich_filepath) {
786 if (opt->verbose) {
787 printf("\tTrying %s from dsymForUUID\n", symrich_filepath);
788 }
789 ecode = convert_fileref_with_file(symrich_filepath, inmh, infr, &invr, lc, oi);
790 free(symrich_filepath);
791 if (ecode == 0) {
792 return (ecode);
793 }
794 warnx("Failed to convert fileref with dsymForUUID. Fall back to local paths");
795 }
796 }
797
798 const size_t pathsize = path ? strlen(path) : 0;
799 ecode = EX_DATAERR;
800 if (0 == pathsize)
801 ecode = convert_fileref_with_file(nm, inmh, infr, &invr, lc, oi);
802 else {
803 /* search the : separated path (-L) for possible matches */
804 char *pathcopy = strdup(path);
805 char *searchpath = pathcopy;
806 const char *token;
807
808 while ((token = strsep(&searchpath, ":")) != NULL) {
809 const size_t buflen = strlen(token) + 1 + strlen(nm) + 1;
810 char *buf = malloc(buflen);
811 snprintf(buf, buflen, "%s%s%s", token, '/' == nm[0] ? "" : "/", nm);
812 if (opt->verbose)
813 printf("\tTrying '%s'", buf);
814 if (0 == access(buf, R_OK)) {
815 if (opt->verbose)
816 printf("\n");
817 ecode = convert_fileref_with_file(buf, inmh, infr, &invr, lc, oi);
818 if (0 == ecode) {
819 free(buf);
820 break;
821 }
822 } else if (opt->verbose)
823 printf(": %s.\n",
824 0 == access(buf, F_OK) ? "Unreadable" : "Not present");
825 free(buf);
826 }
827 free(pathcopy);
828 }
829
830 if (0 != ecode && zf) {
831 /*
832 * Failed to find the file reference. If this was a fileref that uses
833 * a file metadata tagging method (e.g. mtime), allow the user to subsitute a
834 * zfod region: assumes that it's better to have something to debug
835 * vs. nothing. UUID-tagged filerefs are Mach-O tags, and are
836 * assumed to be never substitutable.
837 */
838 switch (FREF_ID_TYPE(infr->flags)) {
839 case kFREF_ID_NONE:
840 case kFREF_ID_MTIMESPEC_LE: { // weak tagging, allow zfod substitution
841 const struct file_range outfr = {
842 .off = oi->oi_foffset,
843 .size = 0,
844 };
845 if (opt->verbose)
846 printf("\tWARNING: no file matched. Missing content is now zfod\n");
847 else
848 printvr(&invr, "WARNING: missing content (%s) now zfod\n", nm);
849 make_native_segment_command(lc, &invr, &outfr, infr->maxprot, infr->prot);
850 ecode = 0;
851 } break;
852 default:
853 break;
854 }
855 }
856
857 return (ecode);
858 }
859
860 static int
861 segment_uncompflags(unsigned algnum, compression_algorithm *ca)
862 {
863 switch (algnum) {
864 case kCOMP_LZ4:
865 *ca = COMPRESSION_LZ4;
866 break;
867 case kCOMP_ZLIB:
868 *ca = COMPRESSION_ZLIB;
869 break;
870 case kCOMP_LZMA:
871 *ca = COMPRESSION_LZMA;
872 break;
873 case kCOMP_LZFSE:
874 *ca = COMPRESSION_LZFSE;
875 break;
876 default:
877 warnx("unknown compression flavor %d", algnum);
878 return EX_DATAERR;
879 }
880 return 0;
881 }
882
883 static int
884 convert_region(const void *inbase, const struct vm_range *invr, const struct file_range *infr, const vm_prot_t prot, const vm_prot_t maxprot, const int flavor, struct load_command *lc, struct output_info *oi)
885 {
886 int ecode = 0;
887
888 if (F_SIZE(infr)) {
889 void *input = (const caddr_t)inbase + F_OFF(infr);
890 void *buf;
891
892 if (0 == flavor) {
893 buf = input;
894 if (opt->verbose) {
895 hsize_str_t hstr;
896 printvr(invr, "copying %s\n", str_hsize(hstr, F_SIZE(infr)));
897 }
898 } else {
899 compression_algorithm ca;
900
901 if (0 != (ecode = segment_uncompflags(flavor, &ca)))
902 return ecode;
903 if (opt->verbose) {
904 hsize_str_t hstr1, hstr2;
905 printvr(invr, "uncompressing %s to %s\n",
906 str_hsize(hstr1, F_SIZE(infr)), str_hsize(hstr2, V_SIZE(invr)));
907 }
908 const size_t buflen = V_SIZEOF(invr);
909 buf = malloc(buflen);
910 const size_t dstsize = compression_decode_buffer(buf, buflen, input, (size_t)F_SIZE(infr), NULL, ca);
911 if (buflen != dstsize) {
912 warnx("failed to uncompress segment");
913 free(buf);
914 return EX_DATAERR;
915 }
916 cstats->compressed += F_SIZE(infr);
917 }
918 const int error = bounded_pwrite(oi->oi_fd, buf, V_SIZEOF(invr), oi->oi_foffset, &oi->oi_nocache, NULL);
919 if (error) {
920 warnc(error, "failed to write data to core file");
921 ecode = EX_IOERR;
922 }
923 if (buf != input)
924 free(buf);
925 if (ecode)
926 return ecode;
927
928 const struct file_range outfr = {
929 .off = oi->oi_foffset,
930 .size = V_SIZE(invr),
931 };
932 make_native_segment_command(lc, invr, &outfr, maxprot, prot);
933 oi->oi_foffset += outfr.size;
934
935 if (0 == flavor)
936 cstats->copied += outfr.size;
937 else
938 cstats->uncompressed += outfr.size;
939 } else {
940 if (opt->verbose) {
941 hsize_str_t hstr;
942 printvr(invr, "%s remains zfod\n", str_hsize(hstr, V_SIZE(invr)));
943 }
944 const struct file_range outfr = {
945 .off = oi->oi_foffset,
946 .size = 0,
947 };
948 make_native_segment_command(lc, invr, &outfr, maxprot, prot);
949 }
950 return ecode;
951 }
952
953 static int
954 convert_coredata(const void *inbase, const native_mach_header_t *__unused inmh, const struct proto_coredata_command *cc, struct load_command *lc, struct output_info *oi)
955 {
956 const struct vm_range vr = {
957 .addr = cc->vmaddr,
958 .size = cc->vmsize,
959 };
960 const struct file_range fr = {
961 .off = cc->fileoff,
962 .size = cc->filesize,
963 };
964 return convert_region(inbase, &vr, &fr, cc->prot, cc->maxprot, COMP_ALG_TYPE(cc->flags), lc, oi);
965 }
966
967 static int
968 convert_segment(const void *inbase, const native_mach_header_t *__unused inmh, const native_segment_command_t *sc, struct load_command *lc, struct output_info *oi)
969 {
970 const struct vm_range vr = {
971 .addr = sc->vmaddr,
972 .size = sc->vmsize,
973 };
974 const struct file_range fr = {
975 .off = sc->fileoff,
976 .size = sc->filesize,
977 };
978 return convert_region(inbase, &vr, &fr, sc->initprot, sc->maxprot, 0, lc, oi);
979 }
980
981 /* pass-through - content is all in the header */
982
983 static int
984 convert_thread(struct thread_command *dst, const struct thread_command *src)
985 {
986 assert(LC_THREAD == src->cmd);
987 memcpy(dst, src, src->cmdsize);
988 cstats->copied += src->cmdsize;
989 return 0;
990 }
991
992 int
993 gcore_conv(int infd, const char *searchpath, bool zf, int fd)
994 {
995 off_t filesize;
996 const void *corebase = mmapfile(infd, 0, &filesize);
997 close(infd);
998 /*
999 * Check to see if the input file is "sane" as far as we're concerned.
1000 * XXX Note that this -won't- necessarily work for other ISAs than
1001 * our own!
1002 */
1003 const native_mach_header_t *inmh = corebase;
1004 validate_core_header(inmh, filesize);
1005
1006 /*
1007 * The sparse file may have created many more segments, but there's no
1008 * attempt to change their numbers here. Just count all the segment
1009 * types needed to figure out the size of the output file header.
1010 *
1011 * (Size assertions to be deleted once data structures stable!)
1012 */
1013 __block size_t headersize = sizeof (native_mach_header_t);
1014 __block unsigned pageshift_target = pageshift_host;
1015
1016 walkcore(inmh, ^(const struct proto_coreinfo_command *ci) {
1017 assert(sizeof (*ci) == ci->cmdsize);
1018 if (opt->verbose)
1019 printf("Converting version %d core file to pre-versioned format\n", ci->version);
1020 if (0 < ci->pageshift && ci->pageshift < 31)
1021 pageshift_target = ci->pageshift;
1022 else if (CPU_TYPE_ARM64 == inmh->cputype)
1023 pageshift_target = 14; // compatibility hack, should go soon
1024 }, ^(const struct proto_fileref_command *__unused fc) {
1025 const char *nm = fc->filename.offset + (const char *)fc;
1026 size_t nmlen = strlen(nm) + 1;
1027 size_t cmdsize = sizeof (*fc) + roundup(nmlen, sizeof (long));
1028 assert(cmdsize == fc->cmdsize);
1029
1030 headersize += sizeof (native_segment_command_t);
1031 }, ^(const struct proto_coredata_command *__unused cc) {
1032 assert(sizeof (*cc) == cc->cmdsize);
1033 headersize += sizeof (native_segment_command_t);
1034 }, ^(const native_segment_command_t *sc) {
1035 headersize += sc->cmdsize;
1036 }, ^(const struct thread_command *tc) {
1037 headersize += tc->cmdsize;
1038 });
1039
1040 void *header = calloc(1, headersize);
1041 if (NULL == header)
1042 errx(EX_OSERR, "out of memory for header");
1043
1044 native_mach_header_t *mh = memcpy(header, inmh, sizeof (*mh));
1045 mh->ncmds = 0;
1046 mh->sizeofcmds = 0;
1047
1048 assert(0 < pageshift_target && pageshift_target < 31);
1049 const vm_offset_t pagesize_target = ((vm_offset_t)1 << pageshift_target);
1050 const vm_offset_t pagemask_target = pagesize_target - 1;
1051
1052 const struct load_command *inlc = (const void *)(inmh + 1);
1053 struct load_command *lc = (void *)(mh + 1);
1054 int ecode = 0;
1055
1056 struct output_info oi = {
1057 .oi_fd = fd,
1058 .oi_foffset = ((vm_offset_t)headersize + pagemask_target) & ~pagemask_target,
1059 .oi_nocache = false,
1060 };
1061
1062 for (unsigned i = 0; i < inmh->ncmds; i++) {
1063 switch (inlc->cmd) {
1064 case proto_LC_FILEREF:
1065 ecode = convert_fileref(searchpath, zf, inmh, (const void *)inlc, lc, &oi);
1066 break;
1067 case proto_LC_COREDATA:
1068 ecode = convert_coredata(corebase, inmh, (const void *)inlc, lc, &oi);
1069 break;
1070 case NATIVE_LC_SEGMENT:
1071 ecode = convert_segment(corebase, inmh, (const void *)inlc, lc, &oi);
1072 break;
1073 case LC_THREAD:
1074 ecode = convert_thread((void *)lc, (const void *)inlc);
1075 break;
1076 default:
1077 if (OPTIONS_DEBUG(opt, 1))
1078 printf("discarding load command %d\n", inlc->cmd);
1079 break;
1080 }
1081 if (0 != ecode)
1082 break;
1083 if (NATIVE_LC_SEGMENT == lc->cmd || LC_THREAD == lc->cmd) {
1084 mach_header_inc_ncmds(mh, 1);
1085 mach_header_inc_sizeofcmds(mh, lc->cmdsize);
1086 lc = (void *)next_lc(lc);
1087 }
1088 if (NULL == (inlc = next_lc(inlc)))
1089 break;
1090 }
1091
1092 /*
1093 * Even if we've encountered an error, try and write out the header
1094 */
1095 if (0 != bounded_pwrite(fd, header, headersize, 0, &oi.oi_nocache, NULL))
1096 ecode = EX_IOERR;
1097 if (0 == ecode && sizeof (*mh) + mh->sizeofcmds != headersize)
1098 ecode = EX_SOFTWARE;
1099 validate_core_header(mh, oi.oi_foffset);
1100 if (ecode)
1101 warnx("failed to write new core file correctly");
1102 else if (opt->verbose) {
1103 hsize_str_t hstr;
1104 printf("Conversion complete: %s copied", str_hsize(hstr, cstats->copied));
1105 const int64_t delta = cstats->uncompressed - cstats->compressed;
1106 if (delta > 0)
1107 printf(", %s uncompressed", str_hsize(hstr, delta));
1108 const int64_t added = cstats->added + ((int)mh->sizeofcmds - (int)inmh->sizeofcmds);
1109 if (added > 0)
1110 printf(", %s added", str_hsize(hstr, added));
1111 printf("\n");
1112 }
1113 free(header);
1114 munmap((void *)corebase, (size_t)filesize);
1115 return ecode;
1116 }
1117 #endif