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