]> git.saurik.com Git - apple/system_cmds.git/blob - gcore.tproj/corefile.c
system_cmds-735.20.1.tar.gz
[apple/system_cmds.git] / gcore.tproj / corefile.c
1 /*
2 * Copyright (c) 2016 Apple Inc. All rights reserved.
3 */
4
5 #include "options.h"
6 #include "corefile.h"
7 #include "sparse.h"
8 #include "utils.h"
9 #include "vm.h"
10
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <stdint.h>
14 #include <inttypes.h>
15 #include <unistd.h>
16 #include <errno.h>
17 #include <assert.h>
18 #include <compression.h>
19 #include <sys/param.h>
20 #include <libgen.h>
21
22 native_mach_header_t *
23 make_corefile_mach_header(void *data)
24 {
25 native_mach_header_t *mh = data;
26 mh->magic = NATIVE_MH_MAGIC;
27 mh->filetype = MH_CORE;
28 #if defined(__LP64__)
29 const int is64 = 1;
30 #else
31 const int is64 = 0;
32 #endif
33 #if defined(__i386__) || defined(__x86_64__)
34 mh->cputype = is64 ? CPU_TYPE_X86_64 : CPU_TYPE_I386;
35 mh->cpusubtype = is64 ? CPU_SUBTYPE_X86_64_ALL : CPU_SUBTYPE_I386_ALL;
36 #elif defined(__arm__) || defined(__arm64__)
37 mh->cputype = is64 ? CPU_TYPE_ARM64 : CPU_TYPE_ARM;
38 mh->cpusubtype = is64 ? CPU_SUBTYPE_ARM64_ALL : CPU_SUBTYPE_ARM_ALL;
39 #else
40 #error undefined
41 #endif
42 return mh;
43 }
44
45 struct proto_coreinfo_command *
46 make_coreinfo_command(native_mach_header_t *mh, void *data, const uuid_t aoutid, uint64_t address, uint64_t dyninfo)
47 {
48 struct proto_coreinfo_command *cc = data;
49 cc->cmd = proto_LC_COREINFO;
50 cc->cmdsize = sizeof (*cc);
51 cc->version = 1;
52 cc->type = proto_CORETYPE_USER;
53 cc->address = address;
54 uuid_copy(cc->uuid, aoutid);
55 cc->dyninfo = dyninfo;
56 mach_header_inc_ncmds(mh, 1);
57 mach_header_inc_sizeofcmds(mh, cc->cmdsize);
58 return cc;
59 }
60
61 static native_segment_command_t *
62 make_native_segment_command(void *data, mach_vm_offset_t vmaddr, mach_vm_size_t vmsize, off_t fileoff, size_t filesize, unsigned maxprot, unsigned initprot, unsigned comptype)
63 {
64 native_segment_command_t *sc = data;
65 sc->cmd = NATIVE_LC_SEGMENT;
66 sc->cmdsize = sizeof (*sc);
67 assert(vmsize);
68 #if defined(__LP64__)
69 sc->vmaddr = vmaddr;
70 sc->vmsize = vmsize;
71 sc->fileoff = fileoff;
72 #else
73 sc->vmaddr = (uintptr_t)vmaddr;
74 sc->vmsize = (size_t)vmsize;
75 sc->fileoff = (long)fileoff;
76 #endif
77 sc->filesize = filesize;
78 sc->maxprot = maxprot;
79 sc->initprot = initprot;
80 sc->nsects = 0;
81 sc->flags = proto_SG_COMP_MAKE_FLAGS(comptype);
82 return sc;
83 }
84
85 /*
86 * Increment the mach-o header data when we succeed
87 */
88 static void
89 commit_load_command(struct write_segment_data *wsd, const struct load_command *lc)
90 {
91 wsd->wsd_lc = (caddr_t)lc + lc->cmdsize;
92 native_mach_header_t *mh = wsd->wsd_mh;
93 mach_header_inc_ncmds(mh, 1);
94 mach_header_inc_sizeofcmds(mh, lc->cmdsize);
95 }
96
97 #pragma mark -- Regions written as "file references" --
98
99 static size_t
100 cmdsize_fileref_command(const char *nm)
101 {
102 size_t cmdsize = sizeof (struct proto_fileref_command);
103 size_t len;
104 if (0 != (len = strlen(nm))) {
105 len++; // NUL-terminated for mmap sanity
106 cmdsize += roundup(len, sizeof (long));
107 }
108 return cmdsize;
109 }
110
111 static void
112 size_fileref_subregion(const struct subregion *s, struct size_core *sc)
113 {
114 assert(S_LIBENT(s));
115
116 size_t cmdsize = cmdsize_fileref_command(S_PATHNAME(s));
117 sc->headersize += cmdsize;
118 sc->count++;
119 sc->memsize += S_SIZE(s);
120 }
121
122 #ifdef CONFIG_REFSC
123 static void
124 size_fileref_region(const struct region *r, struct size_core *sc)
125 {
126 assert(0 == r->r_nsubregions);
127 assert(!r->r_inzfodregion);
128
129 size_t cmdsize = cmdsize_fileref_command(r->r_fileref->fr_libent->le_pathname);
130 sc->headersize += cmdsize;
131 sc->count++;
132 sc->memsize += R_SIZE(r);
133 }
134 #endif
135
136 static struct proto_fileref_command *
137 make_fileref_command(void *data, const struct libent *le, mach_vm_offset_t vmaddr, mach_vm_size_t vmsize, off_t fileoff, off_t filesize, unsigned maxprot, unsigned initprot)
138 {
139 struct proto_fileref_command *fr = data;
140 size_t len;
141
142 fr->cmd = proto_LC_FILEREF;
143 fr->cmdsize = sizeof (*fr);
144 if (0 != (len = strlen(le->le_pathname))) {
145 /*
146 * Strings live immediately after the
147 * command, and are included in the cmdsize
148 */
149 fr->filename.offset = sizeof (*fr);
150 void *s = fr + 1;
151 strlcpy(s, le->le_pathname, ++len); // NUL-terminated for mmap sanity
152 fr->cmdsize += roundup(len, sizeof (long));
153 assert(cmdsize_fileref_command(le->le_pathname) == fr->cmdsize);
154 }
155 uuid_copy(fr->uuid, le->le_uuid);
156
157 fr->vmaddr = vmaddr;
158
159 assert(vmsize);
160 fr->vmsize = vmsize;
161 assert(fileoff >= 0);
162 fr->fileoff = fileoff;
163 fr->filesize = filesize;
164
165 assert(maxprot & VM_PROT_READ);
166 fr->maxprot = maxprot;
167 fr->initprot = initprot;
168 return fr;
169 }
170
171 /*
172 * It's almost always more efficient to write out a reference to the
173 * data than write out the data itself.
174 */
175 static walk_return_t
176 write_fileref_subregion(const struct region *r, const struct subregion *s, struct write_segment_data *wsd)
177 {
178 assert(S_LIBENT(s));
179 if (opt->debug && !issubregiontype(s, SEG_TEXT) && !issubregiontype(s, SEG_LINKEDIT))
180 printf("%s: unusual segment type %s from %s\n", __func__, S_MACHO_TYPE(s), S_FILENAME(s));
181 assert((r->r_info.max_protection & VM_PROT_READ) == VM_PROT_READ);
182 assert((r->r_info.protection & VM_PROT_WRITE) == 0);
183
184 const struct libent *le = S_LIBENT(s);
185 const struct proto_fileref_command *fc = make_fileref_command(wsd->wsd_lc, le, S_ADDR(s), S_SIZE(s), S_MACHO_FILEOFF(s), S_SIZE(s), r->r_info.max_protection, r->r_info.protection);
186 commit_load_command(wsd, (const void *)fc);
187 if (opt->debug > 1) {
188 hsize_str_t hstr;
189 printr(r, "ref '%s' %s (vm %llx-%llx, file offset %lld for %s)\n", S_FILENAME(s), S_MACHO_TYPE(s), (uint64_t)fc->vmaddr, (uint64_t)fc->vmaddr + fc->vmsize, (int64_t)fc->fileoff, str_hsize(hstr, fc->filesize));
190 }
191 return WALK_CONTINUE;
192 }
193
194 #ifdef CONFIG_REFSC
195
196 /*
197 * Note that we may be asked to write reference segments whose protections
198 * are rw- -- this -should- be ok as we don't convert the region to a file
199 * reference unless we know it hasn't been modified.
200 */
201 static walk_return_t
202 write_fileref_region(const struct region *r, struct write_segment_data *wsd)
203 {
204 assert(0 == r->r_nsubregions);
205 assert(r->r_info.user_tag != VM_MEMORY_IOKIT);
206 assert((r->r_info.max_protection & VM_PROT_READ) == VM_PROT_READ);
207 assert(!r->r_inzfodregion);
208
209 const struct libent *le = r->r_fileref->fr_libent;
210 const struct proto_fileref_command *fc = make_fileref_command(wsd->wsd_lc, le, R_ADDR(r), R_SIZE(r), r->r_fileref->fr_offset, (size_t)R_SIZE(r), r->r_info.max_protection, r->r_info.protection);
211 commit_load_command(wsd, (const void *)fc);
212 if (opt->debug > 1) {
213 hsize_str_t hstr;
214 printr(r, "ref '%s' %s (vm %llx-%llx, file offset %lld for %s)\n", le->le_filename, "(type?)", (uint64_t)fc->vmaddr, (uint64_t)fc->vmaddr + fc->vmsize, (int64_t)fc->fileoff, str_hsize(hstr, fc->filesize));
215 }
216 return WALK_CONTINUE;
217 }
218
219 const struct regionop fileref_ops = {
220 print_memory_region,
221 write_fileref_region,
222 del_fileref_region,
223 };
224
225 #endif /* CONFIG_REFSC */
226
227 #pragma mark -- ZFOD segments written only to the header --
228
229 static void
230 size_zfod_region(const struct region *r, struct size_core *sc)
231 {
232 assert(0 == r->r_nsubregions);
233 assert(r->r_inzfodregion);
234 sc->headersize += sizeof (native_segment_command_t);
235 sc->count++;
236 sc->memsize += R_SIZE(r);
237 }
238
239 static walk_return_t
240 write_zfod_region(const struct region *r, struct write_segment_data *wsd)
241 {
242 assert(r->r_info.user_tag != VM_MEMORY_IOKIT);
243 assert((r->r_info.max_protection & VM_PROT_READ) == VM_PROT_READ);
244
245 const void *sc = make_native_segment_command(wsd->wsd_lc, R_ADDR(r), R_SIZE(r), wsd->wsd_foffset, 0, r->r_info.max_protection, r->r_info.protection, 0);
246 commit_load_command(wsd, sc);
247 return WALK_CONTINUE;
248 }
249
250 const struct regionop zfod_ops = {
251 print_memory_region,
252 write_zfod_region,
253 del_zfod_region,
254 };
255
256 #pragma mark -- Regions containing data --
257
258 static walk_return_t
259 pwrite_memory(struct write_segment_data *wsd, const void *addr, size_t size, mach_vm_offset_t memaddr, size_t memsize)
260 {
261 assert(size);
262
263 int error = 0;
264 ssize_t nwritten = 0;
265
266 if (opt->sizebound > 0 &&
267 wsd->wsd_foffset + (off_t)size > opt->sizebound) {
268 error = EFBIG;
269 } else {
270 nwritten = pwrite(wsd->wsd_fd, addr, size, wsd->wsd_foffset);
271 if (nwritten < 0)
272 error = errno;
273 }
274
275 if (error || opt->debug > 1) {
276 hsize_str_t hsz;
277 printf("%llx-%llx writing %ld bytes at offset %lld -> ",
278 memaddr, memaddr+memsize, size, wsd->wsd_foffset);
279 if (error)
280 printf("err #%d - %s ", error, strerror(error));
281 else {
282 printf("%s ", str_hsize(hsz, nwritten));
283 if (size != (size_t)nwritten)
284 printf("[%zd - incomplete write!] ", nwritten);
285 else if (size != memsize)
286 printf("(%s in memory) ",
287 str_hsize(hsz, memsize));
288 }
289 printf("\n");
290 }
291
292 walk_return_t step = WALK_CONTINUE;
293 switch (error) {
294 case 0:
295 if (size != (size_t)nwritten)
296 step = WALK_ERROR;
297 else {
298 wsd->wsd_foffset += nwritten;
299 wsd->wsd_nwritten += nwritten;
300 }
301 break;
302 case EFAULT: // transient mapping failure?
303 break;
304 default: // EROFS, ENOSPC, EFBIG etc. */
305 step = WALK_ERROR;
306 break;
307 }
308 return step;
309 }
310
311
312 /*
313 * Write a contiguous range of memory into the core file.
314 * Apply compression, and chunk if necessary.
315 */
316 static int
317 segment_compflags(compression_algorithm ca, unsigned *algnum)
318 {
319 switch (ca) {
320 case COMPRESSION_LZ4:
321 *algnum = proto_SG_COMP_LZ4;
322 break;
323 case COMPRESSION_ZLIB:
324 *algnum = proto_SG_COMP_ZLIB;
325 break;
326 case COMPRESSION_LZMA:
327 *algnum = proto_SG_COMP_LZMA;
328 break;
329 case COMPRESSION_LZFSE:
330 *algnum = proto_SG_COMP_LZFSE;
331 break;
332 default:
333 err(EX_SOFTWARE, "unsupported compression algorithm %x", ca);
334 }
335 return 0;
336 }
337
338 static walk_return_t
339 write_memory_range(struct write_segment_data *wsd, const struct region *r, mach_vm_offset_t vmaddr, mach_vm_offset_t vmsize)
340 {
341 assert(R_ADDR(r) <= vmaddr && R_ENDADDR(r) >= vmaddr + vmsize);
342
343 mach_vm_offset_t resid = vmsize;
344 walk_return_t step = WALK_CONTINUE;
345
346 do {
347 unsigned algorithm = 0;
348 void *dstbuf = NULL;
349 size_t filesize;
350
351 vmsize = resid;
352
353 /*
354 * Since some regions can be inconveniently large,
355 * chop them into multiple chunks as we compress them.
356 * (mach_vm_read has 32-bit limitations too).
357 */
358 vmsize = vmsize > INT32_MAX ? INT32_MAX : vmsize;
359 if (opt->chunksize > 0 && vmsize > opt->chunksize)
360 vmsize = opt->chunksize;
361 assert(vmsize <= INT32_MAX);
362
363 mach_vm_offset_t data;
364 mach_vm_offset_t data_count;
365
366 kern_return_t kr;
367 const void *srcaddr;
368
369 if (r->r_incommregion) {
370 /*
371 * For commpage access, we just copy from our own address space.
372 */
373 data = 0;
374 data_count = vmsize;
375 kr = mach_vm_allocate(mach_task_self(), &data, data_count, VM_FLAGS_ANYWHERE);
376 if (KERN_SUCCESS != kr || data == 0) {
377 err_mach(kr, r, "subregion %llx-%llx, mach_vm_allocate()", vmaddr, vmaddr + vmsize);
378 if (opt->debug) {
379 print_memory_region_header();
380 ROP_PRINT(r);
381 }
382 break;
383 }
384 if (opt->debug)
385 printr(r, "subregion %llx-%llx, copying from self\n", vmaddr, vmaddr+vmsize);
386 srcaddr = (const void *)memcpy((void *)data, (void *)vmaddr, vmsize);
387 } else {
388 /*
389 * Most segments with data are mapped here
390 */
391 vm_offset_t data32 = 0;
392 mach_msg_type_number_t data32_count;
393 kr = mach_vm_read(wsd->wsd_task, vmaddr, vmsize, &data32, &data32_count);
394 if (KERN_SUCCESS != kr || data32 == 0 || data32_count < vmsize) {
395 err_mach(kr, r, "subregion %llx-%llx, mach_vm_read()", vmaddr, vmaddr + vmsize);
396 if (opt->debug) {
397 print_memory_region_header();
398 ROP_PRINT(r);
399 }
400 break;
401 }
402 data = data32;
403 data_count = data32_count;
404 mach_vm_behavior_set(mach_task_self(), data, data_count, VM_BEHAVIOR_SEQUENTIAL);
405 srcaddr = (const void *)data;
406 }
407
408 assert(vmsize);
409
410 if (opt->compress) {
411 dstbuf = malloc((size_t)vmsize);
412 if (dstbuf) {
413
414 filesize = compression_encode_buffer(dstbuf, (size_t)vmsize, srcaddr, (size_t)vmsize, NULL, opt->calgorithm);
415
416 if (filesize > 0 && filesize < vmsize) {
417 srcaddr = dstbuf;
418 if (segment_compflags(opt->calgorithm, &algorithm) != 0) {
419 free(dstbuf);
420 mach_vm_deallocate(mach_task_self(), data, data_count);
421 return WALK_ERROR;
422 }
423 } else {
424 free(dstbuf);
425 dstbuf = NULL;
426 filesize = (size_t)vmsize;
427 }
428 } else
429 filesize = (size_t)vmsize;
430 } else
431 filesize = (size_t)vmsize;
432
433 assert(filesize);
434
435 native_segment_command_t *sc = make_native_segment_command(wsd->wsd_lc, vmaddr, vmsize, wsd->wsd_foffset, filesize, r->r_info.max_protection, r->r_info.protection, algorithm);
436
437 assert((sc->flags == 0) ^ (sc->filesize < sc->vmsize));
438
439 step = pwrite_memory(wsd, srcaddr, sc->filesize, vmaddr, sc->vmsize);
440 if (dstbuf)
441 free(dstbuf);
442 mach_vm_deallocate(mach_task_self(), data, data_count);
443
444 if (WALK_ERROR == step)
445 break;
446 commit_load_command(wsd, (const void *)sc);
447 resid -= vmsize;
448 vmaddr += vmsize;
449 } while (resid);
450
451 return step;
452 }
453
454 #ifdef RDAR_23744374
455 /*
456 * Sigh. This is a workaround.
457 * Find the vmsize as if the VM system manages ranges in host pagesize units
458 * rather than application pagesize units.
459 */
460 static mach_vm_size_t
461 getvmsize_host(const task_t task, const struct region *r)
462 {
463 mach_vm_size_t vmsize_host = R_SIZE(r);
464
465 if (pageshift_host != pageshift_app) {
466 is_actual_size(task, r, &vmsize_host);
467 if (opt->debug && R_SIZE(r) != vmsize_host)
468 printr(r, "(region size tweak: was %llx, is %llx)\n", R_SIZE(r), vmsize_host);
469 }
470 return vmsize_host;
471 }
472 #else
473 static __inline mach_vm_size_t
474 getvmsize_host(__unused const task_t task, const struct region *r)
475 {
476 return R_SIZE(r);
477 }
478 #endif
479
480 static walk_return_t
481 write_sparse_region(const struct region *r, struct write_segment_data *wsd)
482 {
483 assert(r->r_nsubregions);
484 assert(!r->r_inzfodregion);
485 #ifdef CONFIG_REFSC
486 assert(NULL == r->r_fileref);
487 #endif
488
489 const mach_vm_size_t vmsize_host = getvmsize_host(wsd->wsd_task, r);
490 walk_return_t step = WALK_CONTINUE;
491
492 for (unsigned i = 0; i < r->r_nsubregions; i++) {
493 const struct subregion *s = r->r_subregions[i];
494
495 if (s->s_isfileref)
496 step = write_fileref_subregion(r, s, wsd);
497 else {
498 /* Write this one out as real data */
499 mach_vm_size_t vmsize = S_SIZE(s);
500 if (R_SIZE(r) != vmsize_host) {
501 if (S_ADDR(s) + vmsize > R_ADDR(r) + vmsize_host) {
502 vmsize = R_ADDR(r) + vmsize_host - S_ADDR(s);
503 if (opt->debug)
504 printr(r, "(subregion size tweak: was %llx, is %llx)\n",
505 S_SIZE(s), vmsize);
506 }
507 }
508 step = write_memory_range(wsd, r, S_ADDR(s), vmsize);
509 }
510 if (WALK_ERROR == step)
511 break;
512 }
513 return step;
514 }
515
516 static walk_return_t
517 write_vanilla_region(const struct region *r, struct write_segment_data *wsd)
518 {
519 assert(0 == r->r_nsubregions);
520 assert(!r->r_inzfodregion);
521 #ifdef CONFIG_REFSC
522 assert(NULL == r->r_fileref);
523 #endif
524
525 const mach_vm_size_t vmsize_host = getvmsize_host(wsd->wsd_task, r);
526 return write_memory_range(wsd, r, R_ADDR(r), vmsize_host);
527 }
528
529 walk_return_t
530 region_write_memory(struct region *r, void *arg)
531 {
532 assert(r->r_info.user_tag != VM_MEMORY_IOKIT); // elided in walk_regions()
533 assert((r->r_info.max_protection & VM_PROT_READ) == VM_PROT_READ);
534 return ROP_WRITE(r, arg);
535 }
536
537 /*
538 * Handles the cases where segments are broken into chunks i.e. when
539 * writing compressed segments.
540 */
541 static unsigned long
542 count_memory_range(mach_vm_offset_t vmsize)
543 {
544 unsigned long count;
545 if (opt->compress && opt->chunksize > 0) {
546 count = (size_t)vmsize / opt->chunksize;
547 if (vmsize != (mach_vm_offset_t)count * opt->chunksize)
548 count++;
549 } else
550 count = 1;
551 return count;
552 }
553
554 /*
555 * A sparse region is likely a writable data segment described by
556 * native_segment_command_t somewhere in the address space.
557 */
558 static void
559 size_sparse_subregion(const struct subregion *s, struct size_core *sc)
560 {
561 const unsigned long count = count_memory_range(S_SIZE(s));
562 sc->headersize += sizeof (native_segment_command_t) * count;
563 sc->count += count;
564 sc->memsize += S_SIZE(s);
565 }
566
567 static void
568 size_sparse_region(const struct region *r, struct size_core *sc_sparse, struct size_core *sc_fileref)
569 {
570 assert(0 != r->r_nsubregions);
571
572 unsigned long entry_total = sc_sparse->count + sc_fileref->count;
573 for (unsigned i = 0; i < r->r_nsubregions; i++) {
574 const struct subregion *s = r->r_subregions[i];
575 if (s->s_isfileref)
576 size_fileref_subregion(s, sc_fileref);
577 else
578 size_sparse_subregion(s, sc_sparse);
579 }
580 if (opt->debug) {
581 /* caused by compression breaking a large region into chunks */
582 entry_total = (sc_fileref->count + sc_sparse->count) - entry_total;
583 if (entry_total > r->r_nsubregions)
584 printr(r, "range contains %u subregions requires %lu segment commands\n",
585 r->r_nsubregions, entry_total);
586 }
587 }
588
589 const struct regionop sparse_ops = {
590 print_memory_region,
591 write_sparse_region,
592 del_sparse_region,
593 };
594
595 static void
596 size_vanilla_region(const struct region *r, struct size_core *sc)
597 {
598 assert(0 == r->r_nsubregions);
599
600 const unsigned long count = count_memory_range(R_SIZE(r));
601 sc->headersize += sizeof (native_segment_command_t) * count;
602 sc->count += count;
603 sc->memsize += R_SIZE(r);
604
605 if (opt->debug && count > 1)
606 printr(r, "range with 1 region, but requires %lu segment commands\n", count);
607 }
608
609 const struct regionop vanilla_ops = {
610 print_memory_region,
611 write_vanilla_region,
612 del_vanilla_region,
613 };
614
615 walk_return_t
616 region_size_memory(struct region *r, void *arg)
617 {
618 struct size_segment_data *ssd = arg;
619
620 if (&zfod_ops == r->r_op)
621 size_zfod_region(r, &ssd->ssd_zfod);
622 #ifdef CONFIG_REFSC
623 else if (&fileref_ops == r->r_op)
624 size_fileref_region(r, &ssd->ssd_fileref);
625 #endif
626 else if (&sparse_ops == r->r_op)
627 size_sparse_region(r, &ssd->ssd_sparse, &ssd->ssd_fileref);
628 else if (&vanilla_ops == r->r_op)
629 size_vanilla_region(r, &ssd->ssd_vanilla);
630 else
631 errx(EX_SOFTWARE, "%s: bad op", __func__);
632
633 return WALK_CONTINUE;
634 }