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