xref: /xnu-11215/tools/kt-dump/kt-dump.cpp (revision 1031c584)
1 // clang++ -o kt-dump{,.cpp} -Wall -std=c++20
2 
3 #include <assert.h>
4 #include <cstdio>
5 #include <filesystem>
6 #include <fstream>
7 #include <iostream>
8 #include <mach-o/fat.h>
9 #include <mach-o/loader.h>
10 #include <optional>
11 #include <set>
12 #include <span>
13 #include <vector>
14 #include <removefile.h>
15 #include <unistd.h>
16 #include <spawn.h>
17 #include <fcntl.h>
18 #include <uuid/uuid.h>
19 
20 /*
21  * kt-dump.cpp
22  *
23  * Tool to dump the kalloc type information from a given Mach-O binary.
24  * Usage:
25  * kt-dump [-f <simple|json|struct|stats>] <mach-o>
26  *
27  * The tool will scan the given Mach-O to find the __kalloc_type section.
28  * It will then walk that section using the kalloc_type_view definition
29  * provided below, in order to dump the type names and signatures that
30  * have been compiled into the binary.
31  *
32  * The output "format" can be specified with the -f option. The default
33  * format ("simple") will output the type name and the signature,
34  * enclosed in square brackets. The "json" format will print a JSON
35  * dictionary for each kalloc_type_view entry, including the type name,
36  * its size and the signature. The "struct" output format will use
37  * __builtin_dump_struct to dump a C-like representation of the view.
38  * Finally, if the "stats" output format is chosen, the tool will only
39  * show overall information about the __kalloc_type section.
40  *
41  * The tool supports both MH_KEXT_BUNDLE and kernel cache files. If a
42  * FAT Mach-O is provided, it must contain an arm64 slice.
43  */
44 
45 /* Note: these must be kept in sync with the defs in kalloc.h/zalloc.h */
46 
47 __options_decl(kalloc_type_flags_t, uint32_t, {
48 	KT_DEFAULT        = 0x0001,
49 	KT_PRIV_ACCT      = 0x0002,
50 	KT_SHARED_ACCT    = 0x0004,
51 	KT_DATA_ONLY      = 0x0008,
52 	KT_VM             = 0x0010,
53 	KT_CHANGED        = 0x0020,
54 	KT_CHANGED2       = 0x0040,
55 	KT_PTR_ARRAY      = 0x0080,
56 	KT_NOSHARED       = 0x2000,
57 	KT_SLID           = 0x4000,
58 	KT_PROCESSED      = 0x8000,
59 	KT_HASH           = 0xffff0000,
60 });
61 
62 __options_decl(kalloc_type_version_t, uint16_t, {
63 	KT_V1             = 0x0001,
64 });
65 
66 /* fixme we need to recognize Intel for which this is 20*/
67 #define KHEAP_NUM_ZONES         22
68 
69 struct zone_view {
70 	void *zv_zone;
71 	void *zv_stats;
72 	const char *zv_name;
73 	void *zv_next;
74 };
75 
76 struct kalloc_type_view {
77 	struct zone_view    kt_zv;
78 	const char         *kt_signature;
79 	kalloc_type_flags_t kt_flags;
80 	uint32_t            kt_size;
81 	struct zone        *kt_zshared;
82 	struct zone        *kt_zsig;
83 };
84 
85 struct kalloc_type_var_view {
86 	kalloc_type_version_t   kt_version;
87 	uint16_t                kt_size_hdr;
88 	/*
89 	 * Temporary: Needs to be 32bits cause we have many structs that use
90 	 * IONew/Delete that are larger than 32K.
91 	 */
92 	uint32_t                kt_size_type;
93 	struct zone_stats      *kt_stats;
94 	const char             *kt_name;
95 	struct zone_view       *kt_next;
96 	uint16_t                kt_heap_start;
97 	uint8_t                 kt_zones[KHEAP_NUM_ZONES];
98 	const char             *kt_sig_hdr;
99 	const char             *kt_sig_type;
100 	kalloc_type_flags_t     kt_flags;
101 };
102 
103 template <typename T> struct macho_section {
104 	section_64 section;
105 	std::span<const T> contents;
106 
macho_sectionmacho_section107 	macho_section(const section_64 &sec, std::span<uint8_t> data)
108 		: section(sec),
109 		contents(reinterpret_cast<T *>(
110 			    data.subspan(sec.offset, sec.size / sizeof(T)).data()),
111 		    sec.size / sizeof(T))
112 	{
113 	}
114 
115 	size_t
elem_sizemacho_section116 	elem_size() const
117 	{
118 		return sizeof(T);
119 	}
120 
121 	size_t
elem_countmacho_section122 	elem_count() const
123 	{
124 		return section.size / elem_size();
125 	}
126 };
127 
128 int
printf_with_indent(const char * indent,const char * format,...)129 printf_with_indent(const char *indent, const char *format, ...)
130 {
131 	int n = 0;
132 
133 	va_list ap;
134 	if (*indent) {
135 		std::cout << indent;
136 		n += strlen(indent);
137 	}
138 
139 	va_start(ap, format);
140 	n += vprintf(format, ap);
141 	va_end(ap);
142 	return n;
143 }
144 
145 static inline const char *
decode_string(const macho_section<char> & sec_cstring,const char * string)146 decode_string(const macho_section<char> &sec_cstring, const char *string)
147 {
148 	/*
149 	 * Compute the offsets into the __cstring section.
150 	 * This works for both single kexts (MH_KEXT_BUNDLE) and kernel caches.
151 	 * For the former, the __cstring section addr is the offset of the section
152 	 * into the slice. For the latter, the __cstring section addr is the virtual
153 	 * address of the section, and the fields are pointers into such space.
154 	 */
155 
156 	if (string) {
157 		uintptr_t string_p = reinterpret_cast<uintptr_t>(string);
158 		uint32_t string_off = (uint32_t)string_p;
159 
160 		return &sec_cstring.contents[string_off - sec_cstring.section.offset];
161 	}
162 
163 	return nullptr;
164 }
165 
166 static enum class out_fmt_type {
167 	SIMPLE,
168 	JSON,
169 	STRUCT,
170 	STATS
171 } out_fmt = out_fmt_type::SIMPLE;
172 
173 class image {
174 	const std::span<uint8_t> slice_contents;
175 	size_t slice_mh_offs;
176 
177 	std::optional<macho_section<kalloc_type_view> > sec_types;
178 	std::optional<macho_section<kalloc_type_var_view> > sec_types_var;
179 	std::optional<macho_section<char> > sec_cstring;
180 	uuid_t img_uuid;
181 
182 	std::set<std::pair<const char *, const char *> > dedup_entries;
183 	std::set<std::tuple<const char *, const char *, const char *> > dedup_entries_var;
184 	std::set<const char *> dedup_strings;
185 
186 	struct {
187 		size_t uniq_structs = 0;
188 		size_t uniq_structs_var = 0;
189 		size_t names_sz = 0;
190 		size_t sig_sz = 0;
191 	} stats;
192 
193 	void
dump_types(const char * indent)194 	dump_types(const char *indent)
195 	{
196 		const char *sep = "\n";
197 
198 		if (out_fmt == out_fmt_type::JSON) {
199 			std::cout << ",\n" << indent << "  \"fixed\": [";
200 		}
201 
202 		for (auto &ktv : sec_types->contents) {
203 			const char *name = decode_string(*sec_cstring, ktv.kt_zv.zv_name);
204 			const char *sig = decode_string(*sec_cstring, ktv.kt_signature);
205 
206 			/* Only output the equal entries (same name/signature) once */
207 			if (!dedup_entries.insert(std::make_tuple(name, sig)).second) {
208 				continue;
209 			}
210 
211 			if (ktv.kt_flags & KT_DATA_ONLY) {
212 				sig = "data";
213 			}
214 			if (dedup_strings.insert(name).second) {
215 				stats.names_sz += strlen(name) + 1;
216 			}
217 			if (dedup_strings.insert(sig).second) {
218 				stats.sig_sz += strlen(sig) + 1;
219 			}
220 
221 			stats.uniq_structs++;
222 			if (out_fmt != out_fmt_type::STRUCT) {
223 				name += strlen("site.");
224 			}
225 
226 			switch (out_fmt) {
227 			case out_fmt_type::SIMPLE:
228 				std::cout << indent << name << " [" << sig << "]\n";
229 				break;
230 			case out_fmt_type::JSON:
231 				std::cout << sep << indent
232 				          << "    { \"name\": \"" << name << "\", "
233 				          << "\"size\": " << ktv.kt_size << ", "
234 				          << "\"sig\": \"" << sig << '"'
235 				          << " }";
236 				sep = ",\n";
237 				break;
238 			case out_fmt_type::STRUCT: {
239 				/* Make a copy and fill in the pointers to the cstring section */
240 				kalloc_type_view printable_view = ktv;
241 				printable_view.kt_zv.zv_name = name;
242 				printable_view.kt_signature = sig;
243 				__builtin_dump_struct(&printable_view, &printf_with_indent, indent);
244 			} break;
245 			case out_fmt_type::STATS:
246 				break;
247 			}
248 		}
249 
250 		if (out_fmt == out_fmt_type::JSON) {
251 			std::cout << std::endl << indent << "  ]";
252 		}
253 	}
254 
255 	void
dump_types_var(const char * indent)256 	dump_types_var(const char *indent)
257 	{
258 		const char *sep = "\n";
259 
260 		if (out_fmt == out_fmt_type::JSON) {
261 			std::cout << ",\n" << indent << "  \"var\": [";
262 		}
263 
264 		for (auto &ktv : sec_types_var->contents) {
265 			const char *name = decode_string(*sec_cstring, ktv.kt_name);
266 			const char *sig_hdr = decode_string(*sec_cstring, ktv.kt_sig_hdr);
267 			const char *sig_type = decode_string(*sec_cstring, ktv.kt_sig_type);
268 
269 			/* Only output the equal entries (same name/signature) once */
270 			if (!dedup_entries_var.insert(std::make_tuple(name, sig_hdr, sig_type)).second) {
271 				continue;
272 			}
273 
274 			if (dedup_strings.insert(name).second) {
275 				stats.names_sz += strlen(name) + 1;
276 			}
277 			if (sig_hdr && dedup_strings.insert(sig_hdr).second) {
278 				stats.sig_sz += strlen(sig_hdr) + 1;
279 			}
280 			if (dedup_strings.insert(sig_type).second) {
281 				stats.sig_sz += strlen(sig_type) + 1;
282 			}
283 
284 			if (ktv.kt_flags & KT_DATA_ONLY) {
285 				sig_type = "data";
286 				if (ktv.kt_size_hdr) {
287 					sig_hdr = "data";
288 				}
289 			}
290 			stats.uniq_structs_var++;
291 			if (out_fmt != out_fmt_type::STRUCT) {
292 				name += strlen("site.");
293 			}
294 
295 			switch (out_fmt) {
296 			case out_fmt_type::SIMPLE:
297 				if (sig_hdr) {
298 					std::cout << indent << name
299 					          << " [" << sig_hdr << ", " << sig_type << "]\n";
300 				} else {
301 					std::cout << indent << name
302 					          << " [, " << sig_type << "]\n";
303 				}
304 				break;
305 			case out_fmt_type::JSON:
306 				std::cout << sep << indent
307 				          << "    { \"name\": \"" << name << "\", ";
308 				if (sig_hdr) {
309 					std::cout << "\"size_hdr\": " << ktv.kt_size_hdr << ", "
310 					          << "\"sig_hdr\": \"" << sig_hdr << "\", ";
311 				}
312 				std::cout << "\"size_type\": " << ktv.kt_size_type << ", "
313 				          << "\"sig_type\": \"" << sig_type << '"'
314 				          << " }";
315 				sep = ",\n";
316 				break;
317 			case out_fmt_type::STRUCT: {
318 				/* Make a copy and fill in the pointers to the cstring section */
319 				kalloc_type_var_view printable_view = ktv;
320 				printable_view.kt_name = name;
321 				printable_view.kt_sig_hdr = sig_hdr;
322 				printable_view.kt_sig_type = sig_type;
323 				__builtin_dump_struct(&printable_view, &printf_with_indent, indent);
324 			} break;
325 			case out_fmt_type::STATS:
326 				break;
327 			}
328 		}
329 
330 		if (out_fmt == out_fmt_type::JSON) {
331 			std::cout << std::endl << indent << "  ]";
332 		}
333 	}
334 
335 	const mach_header_64 *
mh_hdr() const336 	mh_hdr() const
337 	{
338 		return reinterpret_cast<const mach_header_64 *>(slice_contents.data() + slice_mh_offs);
339 	}
340 
341 public:
image(std::span<uint8_t> contents,size_t mh_offs=0)342 	image(std::span<uint8_t> contents, size_t mh_offs = 0)
343 		: slice_contents{contents}, slice_mh_offs{mh_offs}
344 	{
345 		auto *hdr = mh_hdr();
346 		std::span<uint8_t> commands = contents.subspan(mh_offs + sizeof(*hdr));
347 
348 		assert(hdr->magic == MH_MAGIC_64);
349 
350 		for (size_t i = 0; i < hdr->ncmds; i++) {
351 			auto *cmd = reinterpret_cast<const load_command *>(commands.data());
352 
353 			commands = commands.subspan(cmd->cmdsize);
354 
355 			switch (cmd->cmd) {
356 			case LC_SEGMENT_64:
357 				break;
358 			case LC_UUID:
359 				uuid_copy(img_uuid, reinterpret_cast<const uuid_command *>(cmd)->uuid);
360 				continue;
361 			default:
362 				continue;
363 			}
364 
365 			auto *seg_cmd = reinterpret_cast<const segment_command_64 *>(cmd);
366 			const std::span<section_64> sections((section_64 *)(seg_cmd + 1), seg_cmd->nsects);
367 
368 			for (auto &sec : sections) {
369 				std::string_view segname(sec.segname);
370 				std::string_view sectname(sec.sectname);
371 
372 				if (sectname == "__kalloc_type") {
373 					assert(!sec_types && "Multiple __kalloc_type sections?");
374 					sec_types = macho_section<kalloc_type_view>(sec, slice_contents);
375 					assert(sec.size % sec_types->elem_size() == 0 &&
376 					    "Check the definition of kalloc_type_view");
377 				} else if (sectname == "__kalloc_var") {
378 					assert(!sec_types_var && "Multiple __kalloc_var sections?");
379 					sec_types_var = macho_section<kalloc_type_var_view>(sec, slice_contents);
380 					assert(sec.size % sec_types_var->elem_size() == 0 &&
381 					    "Check the definition of kalloc_type_var_view");
382 				} else if (segname == "__TEXT" && sectname == "__cstring") {
383 					assert(!sec_cstring && "Multiple __kalloc_var sections?");
384 					sec_cstring = macho_section<char>(sec, slice_contents);
385 				}
386 			}
387 		}
388 	}
389 
390 	~image() = default;
391 
392 	std::string
uuid() const393 	uuid() const
394 	{
395 		uuid_string_t to_str;
396 		uuid_unparse_upper(img_uuid, to_str);
397 		return std::string{to_str};
398 	}
399 
400 	const char *
slice() const401 	slice() const
402 	{
403 		auto *hdr = mh_hdr();
404 		cpu_type_t cpu;
405 		cpu_subtype_t sub;
406 
407 		if (hdr->magic == MH_CIGAM_64) {
408 			cpu = OSSwapInt32(hdr->cputype);
409 			sub = OSSwapInt32(hdr->cpusubtype & CPU_SUBTYPE_MASK);
410 		} else {
411 			cpu = hdr->cputype;
412 			sub = hdr->cpusubtype & OSSwapInt32(CPU_SUBTYPE_MASK);
413 		}
414 
415 		if (cpu == CPU_TYPE_ARM64) {
416 			if (sub == CPU_SUBTYPE_ARM64E) {
417 				return "arm64e";
418 			}
419 			return "arm64";
420 		}
421 
422 		/* other slices unsupported for now */
423 		return nullptr;
424 	}
425 
426 	void
dump(const std::string & imgname,const char * indent="")427 	dump(const std::string &imgname, const char *indent = "")
428 	{
429 		if (out_fmt == out_fmt_type::JSON) {
430 			std::cout << indent << "{\n"
431 			          << indent << "  \"image\": \"" << imgname << "\",\n"
432 			          << indent << "  \"slice\": \"" << slice() << "\",\n"
433 			          << indent << "  \"uuid\": \"" << uuid() << '"';
434 		} else {
435 			std::cout << imgname << " (" << slice() << ", " << uuid() << ")\n";
436 		}
437 
438 		if (sec_types) {
439 			dump_types(indent);
440 		}
441 
442 		if (sec_types_var) {
443 			dump_types_var(indent);
444 		}
445 
446 		if (out_fmt == out_fmt_type::JSON) {
447 			std::cout << std::endl << indent << "}";
448 		}
449 
450 		if (out_fmt == out_fmt_type::STATS) {
451 			if (auto &sec = *sec_types; sec_types) {
452 				auto ucount = stats.uniq_structs;
453 				auto usize  = ucount * sec.elem_size();
454 
455 				std::cout << indent << "__kalloc_type:      " << std::endl;
456 				std::cout << indent << "  total structs:    " << sec.elem_count() << std::endl;
457 				std::cout << indent << "  unique structs:   " << ucount << std::endl;
458 				std::cout << indent << "  total  size:      " << sec.section.size << std::endl;
459 				std::cout << indent << "  unique size:      " << usize << std::endl;
460 			}
461 			if (auto &sec = *sec_types_var; sec_types_var) {
462 				auto ucount = stats.uniq_structs_var;
463 				auto usize  = ucount * sec.elem_size();
464 
465 				std::cout << indent << "__kalloc_var:       " << std::endl;
466 				std::cout << indent << "  total structs:    " << sec.elem_count() << std::endl;
467 				std::cout << indent << "  unique structs:   " << ucount << std::endl;
468 				std::cout << indent << "  total  size:      " << sec.section.size << std::endl;
469 				std::cout << indent << "  unique size:      " << usize << std::endl;
470 			}
471 			std::cout << indent << "names strings:      " << stats.names_sz << std::endl;
472 			std::cout << indent << "signatures strings: " << stats.sig_sz << std::endl;
473 		}
474 
475 		stats = {};
476 		dedup_entries.clear();
477 		dedup_entries_var.clear();
478 		dedup_strings.clear();
479 	}
480 };
481 
482 static int
do_simple_macho(const std::string filename,std::span<uint8_t> contents)483 do_simple_macho(const std::string filename, std::span<uint8_t> contents)
484 {
485 	image img{contents};
486 	img.dump(filename);
487 	return 0;
488 }
489 
490 static int
do_fat_macho(const std::string filename,std::span<uint8_t> contents)491 do_fat_macho(const std::string filename, std::span<uint8_t> contents)
492 {
493 	fat_header *fhdr = reinterpret_cast<fat_header *>(contents.data());
494 	std::span<fat_arch> fat_archs(
495 		reinterpret_cast<fat_arch *>(&contents[sizeof(fat_header)]),
496 		OSSwapInt32(fhdr->nfat_arch));
497 	const char *sep = "\n";
498 
499 	if (out_fmt == out_fmt_type::JSON) {
500 		std::cout << "[";
501 	}
502 
503 	for (auto &arch : fat_archs) {
504 		image img{contents.subspan(OSSwapInt32(arch.offset), OSSwapInt32(arch.size))};
505 
506 		if (out_fmt == out_fmt_type::JSON) {
507 			std::cout << sep;
508 		} else {
509 			std::cout << std::endl;
510 		}
511 		img.dump(filename, "  ");
512 		sep = ",\n";
513 	}
514 
515 	if (out_fmt == out_fmt_type::JSON) {
516 		std::cout << "\n]";
517 	}
518 
519 	return 0;
520 }
521 
522 static int
do_fileset(std::span<uint8_t> contents)523 do_fileset(std::span<uint8_t> contents)
524 {
525 	auto *hdr = reinterpret_cast<const mach_header_64 *>(contents.data());
526 	std::span<uint8_t> commands = contents.subspan(sizeof(*hdr));
527 	const char *sep = "\n";
528 
529 	if (hdr->cputype != CPU_TYPE_ARM64) {
530 		std::cerr << "unsupported cpu type";
531 		return 1;
532 	}
533 
534 	if (out_fmt == out_fmt_type::JSON) {
535 		std::cout << "[";
536 	}
537 
538 	for (size_t i = 0; i < hdr->ncmds; i++) {
539 		auto *cmd = reinterpret_cast<const segment_command_64 *>(commands.data());
540 
541 		commands = commands.subspan(cmd->cmdsize);
542 
543 		if (cmd->cmd != LC_FILESET_ENTRY) {
544 			continue;
545 		}
546 
547 		auto *fec = reinterpret_cast<const fileset_entry_command *>(cmd);
548 		const char *name = reinterpret_cast<const char *>(cmd) + fec->entry_id.offset;
549 		image img{contents, fec->fileoff};
550 
551 		if (out_fmt == out_fmt_type::JSON) {
552 			std::cout << sep;
553 		} else {
554 			std::cout << std::endl;
555 		}
556 		img.dump(name, "  ");
557 		sep = ",\n";
558 	}
559 
560 	if (out_fmt == out_fmt_type::JSON) {
561 		std::cout << "]";
562 	}
563 
564 	return 0;
565 }
566 
567 void
read_file(std::filesystem::path & path,std::vector<uint8_t> & contents)568 read_file(std::filesystem::path &path, std::vector<uint8_t> &contents)
569 {
570 	std::ifstream file(path, std::ifstream::binary);
571 	size_t size(std::filesystem::file_size(path));
572 
573 	contents.resize(size);
574 	file.read(reinterpret_cast<char *>(contents.data()), size);
575 	file.close();
576 }
577 
578 enum class file_kind {
579 	UNKNOWN,
580 	MACHO,
581 	FAT_MACHO,
582 	FILESET,
583 	IMG4,
584 };
585 
586 static file_kind
recognize_file(const std::vector<uint8_t> & contents)587 recognize_file(const std::vector<uint8_t> &contents)
588 {
589 	const mach_header_64 *hdr;
590 
591 	if (contents.size() < sizeof(mach_header_64)) {
592 		return file_kind::UNKNOWN;
593 	}
594 
595 	hdr = reinterpret_cast<const mach_header_64 *>(contents.data());
596 	if (hdr->magic == MH_MAGIC_64) {
597 		switch (hdr->filetype) {
598 		case MH_FILESET:
599 			return file_kind::FILESET;
600 		default:
601 			return file_kind::MACHO;
602 		}
603 	}
604 
605 	if (hdr->magic == FAT_CIGAM) {
606 		return file_kind::FAT_MACHO;
607 	}
608 
609 	if (memcmp("IM4P", contents.data() + 8, 4) == 0) {
610 		return file_kind::IMG4;
611 	}
612 
613 	return file_kind::UNKNOWN;
614 }
615 
616 static int
call_cmd_silent(const char * const * args)617 call_cmd_silent(const char *const *args)
618 {
619 	posix_spawn_file_actions_t facts;
620 	extern char **environ;
621 	pid_t pid;
622 	int rc;
623 
624 	posix_spawn_file_actions_init(&facts);
625 	posix_spawn_file_actions_addopen(&facts,
626 	    STDIN_FILENO, "/dev/null", O_RDONLY, 0777);
627 	posix_spawn_file_actions_addopen(&facts,
628 	    STDOUT_FILENO, "/dev/null", O_WRONLY, 0777);
629 	posix_spawn_file_actions_addopen(&facts,
630 	    STDERR_FILENO, "/dev/null", O_WRONLY, 0777);
631 	rc = posix_spawnp(&pid, args[0], &facts, nullptr,
632 	    (char *const *)args, environ);
633 	posix_spawn_file_actions_destroy(&facts);
634 
635 	if (rc != 0) {
636 		return 1;
637 	}
638 
639 	waitpid(pid, &rc, 0);
640 	if (!WIFEXITED(rc) || WEXITSTATUS(rc)) {
641 		return 1;
642 	}
643 
644 	return 0;
645 }
646 
647 static int
do_file(const std::filesystem::path & path,std::vector<uint8_t> & contents)648 do_file(const std::filesystem::path &path, std::vector<uint8_t> &contents)
649 {
650 	int status = 0;
651 
652 	switch (recognize_file(contents)) {
653 	case file_kind::MACHO:
654 		return do_simple_macho(path.filename().string(), contents);
655 	case file_kind::FAT_MACHO:
656 		return do_fat_macho(path.filename().string(), contents);
657 	case file_kind::FILESET:
658 		return do_fileset(contents);
659 	case file_kind::IMG4:
660 		break;
661 	case file_kind::UNKNOWN:
662 		std::cerr << "Unsupported file type\n";
663 		return 1;
664 	}
665 
666 	char tmp_tpl[] = "/tmp/kt-dump.XXXXXX";
667 	char *tmp_dir = mkdtemp(tmp_tpl);
668 
669 	if (tmp_dir == NULL) {
670 		std::cerr << "Unable to make temporary directory to unpack img4\n";
671 		return 1;
672 	}
673 
674 	std::filesystem::path compressed_kc{tmp_dir};
675 	std::filesystem::path uncompressed_kc{tmp_dir};
676 
677 	compressed_kc /= "compressed.kc";
678 	uncompressed_kc /= "uncompressed.kc";
679 
680 	static const char *const img4args[] = {
681 		"img4utility",
682 		"--copyBinary",
683 		"--input",
684 		path.c_str(),
685 		"--output",
686 		compressed_kc.c_str(),
687 		NULL,
688 	};
689 
690 	static const char *const ct_args[] = {
691 		"compression_tool",
692 		"-decode",
693 		"-v",
694 		"-v",
695 		"-v",
696 		"-i",
697 		compressed_kc.c_str(),
698 		"-o",
699 		uncompressed_kc.c_str(),
700 		NULL,
701 	};
702 
703 	if (call_cmd_silent(img4args)) {
704 		std::cerr << "Unable to unpack img4 image\n";
705 		status = 1;
706 	} else if (call_cmd_silent(ct_args)) {
707 		std::cerr << "Unable to decompress KC\n";
708 		status = 1;
709 	} else {
710 		read_file(uncompressed_kc, contents);
711 	}
712 
713 	removefile_state_t s = removefile_state_alloc();
714 	removefile(tmp_dir, s, REMOVEFILE_RECURSIVE);
715 	removefile_state_free(s);
716 
717 	return status ?: do_file(path, contents);
718 }
719 
720 int
main(int argc,char const * argv[])721 main(int argc, char const *argv[])
722 {
723 	if (argc != 2 && argc != 4) {
724 		std::cout << "Usage: " << argv[0]
725 		          << " [-f <simple|json|struct|stats>] <mach-o>\n";
726 		return 1;
727 	}
728 
729 	std::string path_arg;
730 
731 	/* Parse command line args */
732 	for (int i = 1; i < argc; i++) {
733 		std::string arg(argv[i]);
734 		if (arg == "-f") {
735 			if (++i == argc) {
736 				std::cerr << "Option " << arg << " requires an argument\n";
737 				return 1;
738 			}
739 			arg = argv[i];
740 			if (arg == "simple") {
741 				out_fmt = out_fmt_type::SIMPLE;
742 			} else if (arg == "json" || arg == "JSON") {
743 				out_fmt = out_fmt_type::JSON;
744 			} else if (arg == "struct") {
745 				out_fmt = out_fmt_type::STRUCT;
746 			} else if (arg == "stats") {
747 				out_fmt = out_fmt_type::STATS;
748 			} else {
749 				std::cerr << "Unknown output format: " << arg << std::endl;
750 				return 1;
751 			}
752 		} else {
753 			/* Read the file specified as a positional arg */
754 			path_arg = arg;
755 		}
756 	}
757 
758 	if (path_arg.length() == 0) {
759 		std::cerr << "no file specified\n";
760 		return 1;
761 	}
762 
763 	std::filesystem::path path(path_arg);
764 	std::vector<uint8_t> contents;
765 
766 	read_file(path, contents);
767 	return do_file(path, contents);
768 }
769