2 * Copyright (c) 2016 Apple Inc. All rights reserved.
18 #include <compression.h>
19 #include <sys/param.h>
22 native_mach_header_t
*
23 make_corefile_mach_header(void *data
)
25 native_mach_header_t
*mh
= data
;
26 mh
->magic
= NATIVE_MH_MAGIC
;
27 mh
->filetype
= MH_CORE
;
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
;
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
)
48 struct proto_coreinfo_command
*cc
= data
;
49 cc
->cmd
= proto_LC_COREINFO
;
50 cc
->cmdsize
= sizeof (*cc
);
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
);
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
)
64 native_segment_command_t
*sc
= data
;
65 sc
->cmd
= NATIVE_LC_SEGMENT
;
66 sc
->cmdsize
= sizeof (*sc
);
71 sc
->fileoff
= fileoff
;
73 sc
->vmaddr
= (uintptr_t)vmaddr
;
74 sc
->vmsize
= (size_t)vmsize
;
75 sc
->fileoff
= (long)fileoff
;
77 sc
->filesize
= filesize
;
78 sc
->maxprot
= maxprot
;
79 sc
->initprot
= initprot
;
81 sc
->flags
= proto_SG_COMP_MAKE_FLAGS(comptype
);
86 * Increment the mach-o header data when we succeed
89 commit_load_command(struct write_segment_data
*wsd
, const struct load_command
*lc
)
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
);
97 #pragma mark -- Regions written as "file references" --
100 cmdsize_fileref_command(const char *nm
)
102 size_t cmdsize
= sizeof (struct proto_fileref_command
);
104 if (0 != (len
= strlen(nm
))) {
105 len
++; // NUL-terminated for mmap sanity
106 cmdsize
+= roundup(len
, sizeof (long));
112 size_fileref_subregion(const struct subregion
*s
, struct size_core
*sc
)
116 size_t cmdsize
= cmdsize_fileref_command(S_PATHNAME(s
));
117 sc
->headersize
+= cmdsize
;
119 sc
->memsize
+= S_SIZE(s
);
124 size_fileref_region(const struct region
*r
, struct size_core
*sc
)
126 assert(0 == r
->r_nsubregions
);
127 assert(!r
->r_inzfodregion
);
129 size_t cmdsize
= cmdsize_fileref_command(r
->r_fileref
->fr_libent
->le_pathname
);
130 sc
->headersize
+= cmdsize
;
132 sc
->memsize
+= R_SIZE(r
);
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
)
139 struct proto_fileref_command
*fr
= data
;
142 fr
->cmd
= proto_LC_FILEREF
;
143 fr
->cmdsize
= sizeof (*fr
);
144 if (0 != (len
= strlen(le
->le_pathname
))) {
146 * Strings live immediately after the
147 * command, and are included in the cmdsize
149 fr
->filename
.offset
= sizeof (*fr
);
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
);
155 uuid_copy(fr
->uuid
, le
->le_uuid
);
161 assert(fileoff
>= 0);
162 fr
->fileoff
= fileoff
;
163 fr
->filesize
= filesize
;
165 assert(maxprot
& VM_PROT_READ
);
166 fr
->maxprot
= maxprot
;
167 fr
->initprot
= initprot
;
172 * It's almost always more efficient to write out a reference to the
173 * data than write out the data itself.
176 write_fileref_subregion(const struct region
*r
, const struct subregion
*s
, struct write_segment_data
*wsd
)
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);
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) {
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
));
191 return WALK_CONTINUE
;
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.
202 write_fileref_region(const struct region
*r
, struct write_segment_data
*wsd
)
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
);
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) {
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
));
216 return WALK_CONTINUE
;
219 const struct regionop fileref_ops
= {
221 write_fileref_region
,
225 #endif /* CONFIG_REFSC */
227 #pragma mark -- ZFOD segments written only to the header --
230 size_zfod_region(const struct region
*r
, struct size_core
*sc
)
232 assert(0 == r
->r_nsubregions
);
233 assert(r
->r_inzfodregion
);
234 sc
->headersize
+= sizeof (native_segment_command_t
);
236 sc
->memsize
+= R_SIZE(r
);
240 write_zfod_region(const struct region
*r
, struct write_segment_data
*wsd
)
242 assert(r
->r_info
.user_tag
!= VM_MEMORY_IOKIT
);
243 assert((r
->r_info
.max_protection
& VM_PROT_READ
) == VM_PROT_READ
);
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
;
250 const struct regionop zfod_ops
= {
256 #pragma mark -- Regions containing data --
259 pwrite_memory(struct write_segment_data
*wsd
, const void *addr
, size_t size
, mach_vm_offset_t memaddr
, size_t memsize
)
264 ssize_t nwritten
= 0;
266 if (opt
->sizebound
> 0 &&
267 wsd
->wsd_foffset
+ (off_t
)size
> opt
->sizebound
) {
270 nwritten
= pwrite(wsd
->wsd_fd
, addr
, size
, wsd
->wsd_foffset
);
275 if (error
|| opt
->debug
> 1) {
277 printf("%llx-%llx writing %ld bytes at offset %lld -> ",
278 memaddr
, memaddr
+memsize
, size
, wsd
->wsd_foffset
);
280 printf("err #%d - %s ", error
, strerror(error
));
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
));
292 walk_return_t step
= WALK_CONTINUE
;
295 if (size
!= (size_t)nwritten
)
298 wsd
->wsd_foffset
+= nwritten
;
299 wsd
->wsd_nwritten
+= nwritten
;
302 case EFAULT
: // transient mapping failure?
304 default: // EROFS, ENOSPC, EFBIG etc. */
313 * Write a contiguous range of memory into the core file.
314 * Apply compression, and chunk if necessary.
317 segment_compflags(compression_algorithm ca
, unsigned *algnum
)
320 case COMPRESSION_LZ4
:
321 *algnum
= proto_SG_COMP_LZ4
;
323 case COMPRESSION_ZLIB
:
324 *algnum
= proto_SG_COMP_ZLIB
;
326 case COMPRESSION_LZMA
:
327 *algnum
= proto_SG_COMP_LZMA
;
329 case COMPRESSION_LZFSE
:
330 *algnum
= proto_SG_COMP_LZFSE
;
333 err(EX_SOFTWARE
, "unsupported compression algorithm %x", ca
);
339 write_memory_range(struct write_segment_data
*wsd
, const struct region
*r
, mach_vm_offset_t vmaddr
, mach_vm_offset_t vmsize
)
341 assert(R_ADDR(r
) <= vmaddr
&& R_ENDADDR(r
) >= vmaddr
+ vmsize
);
343 mach_vm_offset_t resid
= vmsize
;
344 walk_return_t step
= WALK_CONTINUE
;
347 unsigned algorithm
= 0;
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).
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
);
363 mach_vm_offset_t data
;
364 mach_vm_offset_t data_count
;
369 if (r
->r_incommregion
) {
371 * For commpage access, we just copy from our own address space.
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
);
379 print_memory_region_header();
385 printr(r
, "subregion %llx-%llx, copying from self\n", vmaddr
, vmaddr
+vmsize
);
386 srcaddr
= (const void *)memcpy((void *)data
, (void *)vmaddr
, vmsize
);
389 * Most segments with data are mapped here
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
);
397 print_memory_region_header();
403 data_count
= data32_count
;
404 mach_vm_behavior_set(mach_task_self(), data
, data_count
, VM_BEHAVIOR_SEQUENTIAL
);
405 srcaddr
= (const void *)data
;
411 dstbuf
= malloc((size_t)vmsize
);
414 filesize
= compression_encode_buffer(dstbuf
, (size_t)vmsize
, srcaddr
, (size_t)vmsize
, NULL
, opt
->calgorithm
);
416 if (filesize
> 0 && filesize
< vmsize
) {
418 if (segment_compflags(opt
->calgorithm
, &algorithm
) != 0) {
420 mach_vm_deallocate(mach_task_self(), data
, data_count
);
426 filesize
= (size_t)vmsize
;
429 filesize
= (size_t)vmsize
;
431 filesize
= (size_t)vmsize
;
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
);
437 assert((sc
->flags
== 0) ^ (sc
->filesize
< sc
->vmsize
));
439 step
= pwrite_memory(wsd
, srcaddr
, sc
->filesize
, vmaddr
, sc
->vmsize
);
442 mach_vm_deallocate(mach_task_self(), data
, data_count
);
444 if (WALK_ERROR
== step
)
446 commit_load_command(wsd
, (const void *)sc
);
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.
460 static mach_vm_size_t
461 getvmsize_host(const task_t task
, const struct region
*r
)
463 mach_vm_size_t vmsize_host
= R_SIZE(r
);
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
);
473 static __inline mach_vm_size_t
474 getvmsize_host(__unused
const task_t task
, const struct region
*r
)
481 write_sparse_region(const struct region
*r
, struct write_segment_data
*wsd
)
483 assert(r
->r_nsubregions
);
484 assert(!r
->r_inzfodregion
);
486 assert(NULL
== r
->r_fileref
);
489 const mach_vm_size_t vmsize_host
= getvmsize_host(wsd
->wsd_task
, r
);
490 walk_return_t step
= WALK_CONTINUE
;
492 for (unsigned i
= 0; i
< r
->r_nsubregions
; i
++) {
493 const struct subregion
*s
= r
->r_subregions
[i
];
496 step
= write_fileref_subregion(r
, s
, wsd
);
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
);
504 printr(r
, "(subregion size tweak: was %llx, is %llx)\n",
508 step
= write_memory_range(wsd
, r
, S_ADDR(s
), vmsize
);
510 if (WALK_ERROR
== step
)
517 write_vanilla_region(const struct region
*r
, struct write_segment_data
*wsd
)
519 assert(0 == r
->r_nsubregions
);
520 assert(!r
->r_inzfodregion
);
522 assert(NULL
== r
->r_fileref
);
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
);
530 region_write_memory(struct region
*r
, void *arg
)
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
);
538 * Handles the cases where segments are broken into chunks i.e. when
539 * writing compressed segments.
542 count_memory_range(mach_vm_offset_t vmsize
)
545 if (opt
->compress
&& opt
->chunksize
> 0) {
546 count
= (size_t)vmsize
/ opt
->chunksize
;
547 if (vmsize
!= (mach_vm_offset_t
)count
* opt
->chunksize
)
555 * A sparse region is likely a writable data segment described by
556 * native_segment_command_t somewhere in the address space.
559 size_sparse_subregion(const struct subregion
*s
, struct size_core
*sc
)
561 const unsigned long count
= count_memory_range(S_SIZE(s
));
562 sc
->headersize
+= sizeof (native_segment_command_t
) * count
;
564 sc
->memsize
+= S_SIZE(s
);
568 size_sparse_region(const struct region
*r
, struct size_core
*sc_sparse
, struct size_core
*sc_fileref
)
570 assert(0 != r
->r_nsubregions
);
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
];
576 size_fileref_subregion(s
, sc_fileref
);
578 size_sparse_subregion(s
, sc_sparse
);
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
);
589 const struct regionop sparse_ops
= {
596 size_vanilla_region(const struct region
*r
, struct size_core
*sc
)
598 assert(0 == r
->r_nsubregions
);
600 const unsigned long count
= count_memory_range(R_SIZE(r
));
601 sc
->headersize
+= sizeof (native_segment_command_t
) * count
;
603 sc
->memsize
+= R_SIZE(r
);
605 if (opt
->debug
&& count
> 1)
606 printr(r
, "range with 1 region, but requires %lu segment commands\n", count
);
609 const struct regionop vanilla_ops
= {
611 write_vanilla_region
,
616 region_size_memory(struct region
*r
, void *arg
)
618 struct size_segment_data
*ssd
= arg
;
620 if (&zfod_ops
== r
->r_op
)
621 size_zfod_region(r
, &ssd
->ssd_zfod
);
623 else if (&fileref_ops
== r
->r_op
)
624 size_fileref_region(r
, &ssd
->ssd_fileref
);
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
);
631 errx(EX_SOFTWARE
, "%s: bad op", __func__
);
633 return WALK_CONTINUE
;