1*5c2921b0SApple OSS Distributions // clang++ -o kt-dump{,.cpp} -Wall -std=c++20
2*5c2921b0SApple OSS Distributions
3*5c2921b0SApple OSS Distributions #include <assert.h>
4*5c2921b0SApple OSS Distributions #include <cstdio>
5*5c2921b0SApple OSS Distributions #include <filesystem>
6*5c2921b0SApple OSS Distributions #include <fstream>
7*5c2921b0SApple OSS Distributions #include <iostream>
8*5c2921b0SApple OSS Distributions #include <mach-o/fat.h>
9*5c2921b0SApple OSS Distributions #include <mach-o/loader.h>
10*5c2921b0SApple OSS Distributions #include <optional>
11*5c2921b0SApple OSS Distributions #include <set>
12*5c2921b0SApple OSS Distributions #include <span>
13*5c2921b0SApple OSS Distributions #include <vector>
14*5c2921b0SApple OSS Distributions
15*5c2921b0SApple OSS Distributions /*
16*5c2921b0SApple OSS Distributions * kt-dump.cpp
17*5c2921b0SApple OSS Distributions *
18*5c2921b0SApple OSS Distributions * Tool to dump the kalloc type information from a given Mach-O binary.
19*5c2921b0SApple OSS Distributions * Usage:
20*5c2921b0SApple OSS Distributions * kt-dump [-f <simple|json|struct|stats>] <mach-o>
21*5c2921b0SApple OSS Distributions *
22*5c2921b0SApple OSS Distributions * The tool will scan the given Mach-O to find the __kalloc_type section.
23*5c2921b0SApple OSS Distributions * It will then walk that section using the kalloc_type_view definition
24*5c2921b0SApple OSS Distributions * provided below, in order to dump the type names and signatures that
25*5c2921b0SApple OSS Distributions * have been compiled into the binary.
26*5c2921b0SApple OSS Distributions *
27*5c2921b0SApple OSS Distributions * The output "format" can be specified with the -f option. The default
28*5c2921b0SApple OSS Distributions * format ("simple") will output the type name and the signature,
29*5c2921b0SApple OSS Distributions * enclosed in square brackets. The "json" format will print a JSON
30*5c2921b0SApple OSS Distributions * dictionary for each kalloc_type_view entry, including the type name,
31*5c2921b0SApple OSS Distributions * its size and the signature. The "struct" output format will use
32*5c2921b0SApple OSS Distributions * __builtin_dump_struct to dump a C-like representation of the view.
33*5c2921b0SApple OSS Distributions * Finally, if the "stats" output format is chosen, the tool will only
34*5c2921b0SApple OSS Distributions * show overall information about the __kalloc_type section.
35*5c2921b0SApple OSS Distributions *
36*5c2921b0SApple OSS Distributions * The tool supports both MH_KEXT_BUNDLE and kernel cache files. If a
37*5c2921b0SApple OSS Distributions * FAT Mach-O is provided, it must contain an arm64 slice.
38*5c2921b0SApple OSS Distributions */
39*5c2921b0SApple OSS Distributions
40*5c2921b0SApple OSS Distributions /* Read in_path into out_vec */
41*5c2921b0SApple OSS Distributions static bool read_file(std::string in_path, std::vector<uint8_t> &out_vec);
42*5c2921b0SApple OSS Distributions
43*5c2921b0SApple OSS Distributions /* Find a suitable arch span in a FAT file */
44*5c2921b0SApple OSS Distributions static std::optional<std::span<uint8_t> >
45*5c2921b0SApple OSS Distributions find_arm64_slice(const std::span<uint8_t> &contents);
46*5c2921b0SApple OSS Distributions
47*5c2921b0SApple OSS Distributions /* Note: these must be kept in sync with the defs in kalloc.h/zalloc.h */
48*5c2921b0SApple OSS Distributions struct zone_view {
49*5c2921b0SApple OSS Distributions void *zv_zone;
50*5c2921b0SApple OSS Distributions void *zv_stats;
51*5c2921b0SApple OSS Distributions const char *zv_name;
52*5c2921b0SApple OSS Distributions void *zv_next;
53*5c2921b0SApple OSS Distributions };
54*5c2921b0SApple OSS Distributions
55*5c2921b0SApple OSS Distributions struct kalloc_type_view {
56*5c2921b0SApple OSS Distributions struct zone_view kt_zv;
57*5c2921b0SApple OSS Distributions const char *kt_signature;
58*5c2921b0SApple OSS Distributions uint32_t kt_flags;
59*5c2921b0SApple OSS Distributions uint32_t kt_size;
60*5c2921b0SApple OSS Distributions void *kt_site;
61*5c2921b0SApple OSS Distributions void *unused;
62*5c2921b0SApple OSS Distributions };
63*5c2921b0SApple OSS Distributions
64*5c2921b0SApple OSS Distributions template <typename T> struct macho_section {
65*5c2921b0SApple OSS Distributions section_64 section;
66*5c2921b0SApple OSS Distributions std::span<const T> contents;
67*5c2921b0SApple OSS Distributions
macho_sectionmacho_section68*5c2921b0SApple OSS Distributions macho_section(const section_64 &sec, std::span<uint8_t> data)
69*5c2921b0SApple OSS Distributions : section(sec),
70*5c2921b0SApple OSS Distributions contents(reinterpret_cast<T *>(
71*5c2921b0SApple OSS Distributions data.subspan(sec.offset, sec.size / sizeof(T)).data()),
72*5c2921b0SApple OSS Distributions sec.size / sizeof(T))
73*5c2921b0SApple OSS Distributions {
74*5c2921b0SApple OSS Distributions }
75*5c2921b0SApple OSS Distributions };
76*5c2921b0SApple OSS Distributions
77*5c2921b0SApple OSS Distributions int
main(int argc,char const * argv[])78*5c2921b0SApple OSS Distributions main(int argc, char const *argv[])
79*5c2921b0SApple OSS Distributions {
80*5c2921b0SApple OSS Distributions if (argc != 2 && argc != 4) {
81*5c2921b0SApple OSS Distributions std::cout << "Usage: " << argv[0]
82*5c2921b0SApple OSS Distributions << " [-f <simple|json|struct|stats>] <mach-o>\n";
83*5c2921b0SApple OSS Distributions return 1;
84*5c2921b0SApple OSS Distributions }
85*5c2921b0SApple OSS Distributions
86*5c2921b0SApple OSS Distributions enum class out_fmt_type {
87*5c2921b0SApple OSS Distributions SIMPLE,
88*5c2921b0SApple OSS Distributions JSON,
89*5c2921b0SApple OSS Distributions STRUCT,
90*5c2921b0SApple OSS Distributions STATS
91*5c2921b0SApple OSS Distributions } out_fmt = out_fmt_type::SIMPLE;
92*5c2921b0SApple OSS Distributions std::string arg_str;
93*5c2921b0SApple OSS Distributions std::vector<uint8_t> file_contents;
94*5c2921b0SApple OSS Distributions uint32_t file_magic = 0;
95*5c2921b0SApple OSS Distributions std::span<uint8_t> slice_contents;
96*5c2921b0SApple OSS Distributions mach_header_64 *hdr = NULL;
97*5c2921b0SApple OSS Distributions std::optional<macho_section<kalloc_type_view> > sec_types;
98*5c2921b0SApple OSS Distributions std::optional<macho_section<char> > sec_cstring;
99*5c2921b0SApple OSS Distributions struct {
100*5c2921b0SApple OSS Distributions size_t uniq_structs_sz;
101*5c2921b0SApple OSS Distributions size_t names_sz;
102*5c2921b0SApple OSS Distributions size_t sig_sz;
103*5c2921b0SApple OSS Distributions } stats = {};
104*5c2921b0SApple OSS Distributions
105*5c2921b0SApple OSS Distributions /* Parse command line args */
106*5c2921b0SApple OSS Distributions for (int i = 1; i < argc; i++) {
107*5c2921b0SApple OSS Distributions std::string arg(argv[i]);
108*5c2921b0SApple OSS Distributions if (arg == "-f") {
109*5c2921b0SApple OSS Distributions if (++i == argc) {
110*5c2921b0SApple OSS Distributions std::cerr << "Option " << arg << " requires an argument\n";
111*5c2921b0SApple OSS Distributions return 1;
112*5c2921b0SApple OSS Distributions }
113*5c2921b0SApple OSS Distributions arg = argv[i];
114*5c2921b0SApple OSS Distributions if (arg == "simple") {
115*5c2921b0SApple OSS Distributions out_fmt = out_fmt_type::SIMPLE;
116*5c2921b0SApple OSS Distributions } else if (arg == "json" || arg == "JSON") {
117*5c2921b0SApple OSS Distributions out_fmt = out_fmt_type::JSON;
118*5c2921b0SApple OSS Distributions } else if (arg == "struct") {
119*5c2921b0SApple OSS Distributions out_fmt = out_fmt_type::STRUCT;
120*5c2921b0SApple OSS Distributions } else if (arg == "stats") {
121*5c2921b0SApple OSS Distributions out_fmt = out_fmt_type::STATS;
122*5c2921b0SApple OSS Distributions } else {
123*5c2921b0SApple OSS Distributions std::cerr << "Unknown output format: " << arg << std::endl;
124*5c2921b0SApple OSS Distributions return 1;
125*5c2921b0SApple OSS Distributions }
126*5c2921b0SApple OSS Distributions } else {
127*5c2921b0SApple OSS Distributions /* Read the file specified as a positional arg */
128*5c2921b0SApple OSS Distributions if (!read_file(arg, file_contents)) {
129*5c2921b0SApple OSS Distributions std::cerr << "Failed to read file: " << arg << std::endl;
130*5c2921b0SApple OSS Distributions return 1;
131*5c2921b0SApple OSS Distributions }
132*5c2921b0SApple OSS Distributions }
133*5c2921b0SApple OSS Distributions }
134*5c2921b0SApple OSS Distributions
135*5c2921b0SApple OSS Distributions file_magic = *reinterpret_cast<uint32_t *>(file_contents.data());
136*5c2921b0SApple OSS Distributions if (file_magic == MH_MAGIC_64) {
137*5c2921b0SApple OSS Distributions /* Single arch Mach-O file: the slice covers the whole file */
138*5c2921b0SApple OSS Distributions slice_contents = std::span(file_contents);
139*5c2921b0SApple OSS Distributions } else if (file_magic == FAT_CIGAM) {
140*5c2921b0SApple OSS Distributions /* FAT Mach-O: Retrieve the appropriate slice */
141*5c2921b0SApple OSS Distributions auto arch_span = find_arm64_slice(file_contents);
142*5c2921b0SApple OSS Distributions if (!arch_span) {
143*5c2921b0SApple OSS Distributions std::cerr << "Could not find a suitable arch\n";
144*5c2921b0SApple OSS Distributions return 1;
145*5c2921b0SApple OSS Distributions }
146*5c2921b0SApple OSS Distributions slice_contents = arch_span.value();
147*5c2921b0SApple OSS Distributions } else {
148*5c2921b0SApple OSS Distributions std::cerr << "Unsupported file magic: 0x" << std::hex << file_magic << "\n";
149*5c2921b0SApple OSS Distributions return 1;
150*5c2921b0SApple OSS Distributions }
151*5c2921b0SApple OSS Distributions assert(slice_contents.size() > sizeof(*hdr));
152*5c2921b0SApple OSS Distributions hdr = reinterpret_cast<mach_header_64 *>(slice_contents.data());
153*5c2921b0SApple OSS Distributions if (hdr->magic != MH_MAGIC_64) {
154*5c2921b0SApple OSS Distributions std::cerr << "Unsupported header magic: 0x" << std::hex << hdr->magic
155*5c2921b0SApple OSS Distributions << "\n";
156*5c2921b0SApple OSS Distributions return 1;
157*5c2921b0SApple OSS Distributions }
158*5c2921b0SApple OSS Distributions
159*5c2921b0SApple OSS Distributions for (uint32_t cmds_offset = sizeof(*hdr); cmds_offset < hdr->sizeofcmds;) {
160*5c2921b0SApple OSS Distributions load_command *cmd =
161*5c2921b0SApple OSS Distributions reinterpret_cast<load_command *>(&slice_contents[cmds_offset]);
162*5c2921b0SApple OSS Distributions cmds_offset += cmd->cmdsize;
163*5c2921b0SApple OSS Distributions /* We only need to process LC_SEGMENT_64 */
164*5c2921b0SApple OSS Distributions if (cmd->cmd != LC_SEGMENT_64) {
165*5c2921b0SApple OSS Distributions continue;
166*5c2921b0SApple OSS Distributions }
167*5c2921b0SApple OSS Distributions
168*5c2921b0SApple OSS Distributions segment_command_64 *seg_cmd = reinterpret_cast<segment_command_64 *>(cmd);
169*5c2921b0SApple OSS Distributions std::span<section_64> sections(reinterpret_cast<section_64 *>(seg_cmd + 1),
170*5c2921b0SApple OSS Distributions seg_cmd->nsects);
171*5c2921b0SApple OSS Distributions for (auto &sec : sections) {
172*5c2921b0SApple OSS Distributions std::string segname(sec.segname);
173*5c2921b0SApple OSS Distributions std::string sectname(sec.sectname);
174*5c2921b0SApple OSS Distributions if (sectname == "__kalloc_type") {
175*5c2921b0SApple OSS Distributions assert(!sec_types && "Multiple __kalloc_type sections?");
176*5c2921b0SApple OSS Distributions assert(sec.size % sizeof(kalloc_type_view) == 0 &&
177*5c2921b0SApple OSS Distributions "Check the definition of kalloc_type_view");
178*5c2921b0SApple OSS Distributions sec_types = macho_section<kalloc_type_view>(sec, slice_contents);
179*5c2921b0SApple OSS Distributions } else if (segname == "__TEXT" && sectname == "__cstring") {
180*5c2921b0SApple OSS Distributions sec_cstring = macho_section<char>(sec, slice_contents);
181*5c2921b0SApple OSS Distributions }
182*5c2921b0SApple OSS Distributions }
183*5c2921b0SApple OSS Distributions }
184*5c2921b0SApple OSS Distributions
185*5c2921b0SApple OSS Distributions if (!sec_types) {
186*5c2921b0SApple OSS Distributions std::cerr << "Could not find __kalloc_type section\n";
187*5c2921b0SApple OSS Distributions return 1;
188*5c2921b0SApple OSS Distributions }
189*5c2921b0SApple OSS Distributions if (!sec_cstring) {
190*5c2921b0SApple OSS Distributions std::cerr << "Could not find __TEXT,__cstring section\n";
191*5c2921b0SApple OSS Distributions return 1;
192*5c2921b0SApple OSS Distributions }
193*5c2921b0SApple OSS Distributions
194*5c2921b0SApple OSS Distributions std::set<std::pair<uint32_t, uint32_t> > dedup_entries;
195*5c2921b0SApple OSS Distributions std::set<uint32_t> dedup_strings;
196*5c2921b0SApple OSS Distributions
197*5c2921b0SApple OSS Distributions for (auto &ktv : sec_types->contents) {
198*5c2921b0SApple OSS Distributions uintptr_t name_p = reinterpret_cast<uintptr_t>(ktv.kt_zv.zv_name);
199*5c2921b0SApple OSS Distributions uintptr_t signature_p = reinterpret_cast<uintptr_t>(ktv.kt_signature);
200*5c2921b0SApple OSS Distributions /*
201*5c2921b0SApple OSS Distributions * Compute the offsets into the __cstring section.
202*5c2921b0SApple OSS Distributions * This works for both single kexts (MH_KEXT_BUNDLE) and kernel caches.
203*5c2921b0SApple OSS Distributions * For the former, the __cstring section addr is the offset of the section
204*5c2921b0SApple OSS Distributions * into the slice. For the latter, the __cstring section addr is the virtual
205*5c2921b0SApple OSS Distributions * address of the section, and the fields are pointers into such space.
206*5c2921b0SApple OSS Distributions */
207*5c2921b0SApple OSS Distributions uint32_t name_off = (name_p - sec_cstring->section.addr) & 0xffffffff;
208*5c2921b0SApple OSS Distributions uint32_t sig_off = (signature_p - sec_cstring->section.addr) & 0xffffffff;
209*5c2921b0SApple OSS Distributions
210*5c2921b0SApple OSS Distributions /* Only output the equal entries (same name/signature) once */
211*5c2921b0SApple OSS Distributions if (!dedup_entries.insert(std::make_tuple(name_off, sig_off)).second) {
212*5c2921b0SApple OSS Distributions continue;
213*5c2921b0SApple OSS Distributions }
214*5c2921b0SApple OSS Distributions
215*5c2921b0SApple OSS Distributions stats.uniq_structs_sz += sizeof(ktv);
216*5c2921b0SApple OSS Distributions
217*5c2921b0SApple OSS Distributions const char *name = &sec_cstring->contents[name_off];
218*5c2921b0SApple OSS Distributions const char *signature = &sec_cstring->contents[sig_off];
219*5c2921b0SApple OSS Distributions if (dedup_strings.insert(name_off).second) {
220*5c2921b0SApple OSS Distributions stats.names_sz += strlen(name) + 1;
221*5c2921b0SApple OSS Distributions }
222*5c2921b0SApple OSS Distributions if (dedup_strings.insert(sig_off).second) {
223*5c2921b0SApple OSS Distributions stats.sig_sz += strlen(signature) + 1;
224*5c2921b0SApple OSS Distributions }
225*5c2921b0SApple OSS Distributions
226*5c2921b0SApple OSS Distributions switch (out_fmt) {
227*5c2921b0SApple OSS Distributions case out_fmt_type::SIMPLE:
228*5c2921b0SApple OSS Distributions std::cout << name << " [" << signature << "]\n";
229*5c2921b0SApple OSS Distributions break;
230*5c2921b0SApple OSS Distributions case out_fmt_type::JSON:
231*5c2921b0SApple OSS Distributions std::cout << "{\"name\":\"" << name << "\","
232*5c2921b0SApple OSS Distributions << "\"signature\":\"" << signature << "\","
233*5c2921b0SApple OSS Distributions << "\"size\":" << ktv.kt_size << "}\n";
234*5c2921b0SApple OSS Distributions break;
235*5c2921b0SApple OSS Distributions case out_fmt_type::STRUCT: {
236*5c2921b0SApple OSS Distributions /* Make a copy and fill in the pointers to the cstring section */
237*5c2921b0SApple OSS Distributions kalloc_type_view printable_view = ktv;
238*5c2921b0SApple OSS Distributions printable_view.kt_zv.zv_name = name;
239*5c2921b0SApple OSS Distributions printable_view.kt_signature = signature;
240*5c2921b0SApple OSS Distributions __builtin_dump_struct(&printable_view, &printf);
241*5c2921b0SApple OSS Distributions } break;
242*5c2921b0SApple OSS Distributions case out_fmt_type::STATS:
243*5c2921b0SApple OSS Distributions break;
244*5c2921b0SApple OSS Distributions }
245*5c2921b0SApple OSS Distributions }
246*5c2921b0SApple OSS Distributions if (out_fmt == out_fmt_type::STATS) {
247*5c2921b0SApple OSS Distributions std::cout << "__kalloc_type: " << sec_types->section.size << std::endl;
248*5c2921b0SApple OSS Distributions std::cout << "uniq structs: " << stats.uniq_structs_sz << std::endl;
249*5c2921b0SApple OSS Distributions std::cout << "names strings: " << stats.names_sz << std::endl;
250*5c2921b0SApple OSS Distributions std::cout << "signatures strings: " << stats.sig_sz << std::endl;
251*5c2921b0SApple OSS Distributions }
252*5c2921b0SApple OSS Distributions
253*5c2921b0SApple OSS Distributions return 0;
254*5c2921b0SApple OSS Distributions }
255*5c2921b0SApple OSS Distributions
256*5c2921b0SApple OSS Distributions static bool
read_file(std::string in_path,std::vector<uint8_t> & out_vec)257*5c2921b0SApple OSS Distributions read_file(std::string in_path, std::vector<uint8_t> &out_vec)
258*5c2921b0SApple OSS Distributions {
259*5c2921b0SApple OSS Distributions std::filesystem::path path(in_path);
260*5c2921b0SApple OSS Distributions std::ifstream file(path, std::ifstream::binary);
261*5c2921b0SApple OSS Distributions size_t size(std::filesystem::file_size(path));
262*5c2921b0SApple OSS Distributions out_vec.resize(size);
263*5c2921b0SApple OSS Distributions file.read(reinterpret_cast<char *>(out_vec.data()), size);
264*5c2921b0SApple OSS Distributions file.close();
265*5c2921b0SApple OSS Distributions return true;
266*5c2921b0SApple OSS Distributions }
267*5c2921b0SApple OSS Distributions
268*5c2921b0SApple OSS Distributions static std::optional<std::span<uint8_t> >
find_arm64_slice(const std::span<uint8_t> & contents)269*5c2921b0SApple OSS Distributions find_arm64_slice(const std::span<uint8_t> &contents)
270*5c2921b0SApple OSS Distributions {
271*5c2921b0SApple OSS Distributions fat_header *fhdr = reinterpret_cast<fat_header *>(contents.data());
272*5c2921b0SApple OSS Distributions std::span<fat_arch> fat_archs(
273*5c2921b0SApple OSS Distributions reinterpret_cast<fat_arch *>(&contents[sizeof(fat_header)]),
274*5c2921b0SApple OSS Distributions OSSwapInt32(fhdr->nfat_arch));
275*5c2921b0SApple OSS Distributions std::optional<std::span<uint8_t> > chosen_span;
276*5c2921b0SApple OSS Distributions for (auto &arch : fat_archs) {
277*5c2921b0SApple OSS Distributions if (OSSwapInt32(arch.cputype) == CPU_TYPE_ARM64) {
278*5c2921b0SApple OSS Distributions if (OSSwapInt32(arch.cpusubtype) == CPU_SUBTYPE_ARM64E || !chosen_span) {
279*5c2921b0SApple OSS Distributions chosen_span =
280*5c2921b0SApple OSS Distributions contents.subspan(OSSwapInt32(arch.offset), OSSwapInt32(arch.size));
281*5c2921b0SApple OSS Distributions }
282*5c2921b0SApple OSS Distributions }
283*5c2921b0SApple OSS Distributions }
284*5c2921b0SApple OSS Distributions return chosen_span;
285*5c2921b0SApple OSS Distributions }
286