1 | #include <inttypes.h> |
2 | #include <mach-o/loader.h> |
3 | #include <mach/thread_status.h> |
4 | #include <stdio.h> |
5 | #include <stdlib.h> |
6 | #include <string.h> |
7 | #include <string> |
8 | #include <sys/errno.h> |
9 | #include <uuid/uuid.h> |
10 | #include <vector> |
11 | |
12 | // Create an empty corefile with a "kern ver str" LC_NOTE |
13 | // or a "main bin spec" LC_NOTE.. |
14 | // If an existing binary is given as a 3rd argument on the cmd line, |
15 | // the UUID from that binary will be encoded in the corefile. |
16 | // Otherwise a pre-set UUID will be put in the corefile that |
17 | // is created. |
18 | |
19 | struct main_bin_spec_payload { |
20 | uint32_t version; |
21 | uint32_t type; |
22 | uint64_t address; |
23 | uint64_t slide; |
24 | uuid_t uuid; |
25 | uint32_t log2_pagesize; |
26 | uint32_t platform; |
27 | }; |
28 | |
29 | union uint32_buf { |
30 | uint8_t bytebuf[4]; |
31 | uint32_t val; |
32 | }; |
33 | |
34 | union uint64_buf { |
35 | uint8_t bytebuf[8]; |
36 | uint64_t val; |
37 | }; |
38 | |
39 | void add_uint64(std::vector<uint8_t> &buf, uint64_t val) { |
40 | uint64_buf conv; |
41 | conv.val = val; |
42 | for (int i = 0; i < 8; i++) |
43 | buf.push_back(x: conv.bytebuf[i]); |
44 | } |
45 | |
46 | void add_uint32(std::vector<uint8_t> &buf, uint32_t val) { |
47 | uint32_buf conv; |
48 | conv.val = val; |
49 | for (int i = 0; i < 4; i++) |
50 | buf.push_back(x: conv.bytebuf[i]); |
51 | } |
52 | |
53 | std::vector<uint8_t> lc_thread_load_command(cpu_type_t cputype) { |
54 | std::vector<uint8_t> data; |
55 | // Emit an LC_THREAD register context appropriate for the cputype |
56 | // of the binary we're embedded. The tests in this case do not |
57 | // use the register values, so 0's are fine, lldb needs to see at |
58 | // least one LC_THREAD in the corefile. |
59 | #if defined(__x86_64__) |
60 | if (cputype == CPU_TYPE_X86_64) { |
61 | add_uint32(data, LC_THREAD); // thread_command.cmd |
62 | add_uint32(data, |
63 | 16 + (x86_THREAD_STATE64_COUNT * 4)); // thread_command.cmdsize |
64 | add_uint32(data, x86_THREAD_STATE64); // thread_command.flavor |
65 | add_uint32(data, x86_THREAD_STATE64_COUNT); // thread_command.count |
66 | for (int i = 0; i < x86_THREAD_STATE64_COUNT; i++) { |
67 | add_uint32(data, 0); // whatever, just some empty register values |
68 | } |
69 | } |
70 | #endif |
71 | #if defined(__arm64__) || defined(__aarch64__) |
72 | if (cputype == CPU_TYPE_ARM64) { |
73 | add_uint32(data, LC_THREAD); // thread_command.cmd |
74 | add_uint32(data, |
75 | 16 + (ARM_THREAD_STATE64_COUNT * 4)); // thread_command.cmdsize |
76 | add_uint32(data, ARM_THREAD_STATE64); // thread_command.flavor |
77 | add_uint32(data, ARM_THREAD_STATE64_COUNT); // thread_command.count |
78 | for (int i = 0; i < ARM_THREAD_STATE64_COUNT; i++) { |
79 | add_uint32(data, 0); // whatever, just some empty register values |
80 | } |
81 | } |
82 | #endif |
83 | return data; |
84 | } |
85 | |
86 | void add_lc_note_kern_ver_str_load_command( |
87 | std::vector<std::vector<uint8_t>> &loadcmds, std::vector<uint8_t> &payload, |
88 | int payload_file_offset, std::string uuid, uint64_t address) { |
89 | std::string ident; |
90 | if (!uuid.empty()) { |
91 | ident = "EFI UUID=" ; |
92 | ident += uuid; |
93 | if (address != 0xffffffffffffffff) { |
94 | ident += "; stext=" ; |
95 | char buf[24]; |
96 | sprintf(s: buf, format: "0x%" PRIx64, address); |
97 | ident += buf; |
98 | } |
99 | } |
100 | |
101 | std::vector<uint8_t> loadcmd_data; |
102 | |
103 | add_uint32(loadcmd_data, LC_NOTE); // note_command.cmd |
104 | add_uint32(buf&: loadcmd_data, val: 40); // note_command.cmdsize |
105 | char lc_note_name[16]; |
106 | memset(s: lc_note_name, c: 0, n: 16); |
107 | strcpy(dest: lc_note_name, src: "kern ver str" ); |
108 | |
109 | // lc_note.data_owner |
110 | for (int i = 0; i < 16; i++) |
111 | loadcmd_data.push_back(x: lc_note_name[i]); |
112 | |
113 | // we start writing the payload at payload_file_offset to leave |
114 | // room at the start for the header & the load commands. |
115 | uint64_t current_payload_offset = payload.size() + payload_file_offset; |
116 | |
117 | add_uint64(buf&: loadcmd_data, val: current_payload_offset); // note_command.offset |
118 | add_uint64(buf&: loadcmd_data, val: 4 + ident.size() + 1); // note_command.size |
119 | |
120 | loadcmds.push_back(x: loadcmd_data); |
121 | |
122 | add_uint32(buf&: payload, val: 1); // kerneL_version_string.version |
123 | for (int i = 0; i < ident.size() + 1; i++) { |
124 | payload.push_back(x: ident[i]); |
125 | } |
126 | } |
127 | |
128 | void add_lc_note_main_bin_spec_load_command( |
129 | std::vector<std::vector<uint8_t>> &loadcmds, std::vector<uint8_t> &payload, |
130 | int payload_file_offset, std::string uuidstr, uint64_t address, |
131 | uint64_t slide) { |
132 | std::vector<uint8_t> loadcmd_data; |
133 | |
134 | add_uint32(loadcmd_data, LC_NOTE); // note_command.cmd |
135 | add_uint32(buf&: loadcmd_data, val: 40); // note_command.cmdsize |
136 | char lc_note_name[16]; |
137 | memset(s: lc_note_name, c: 0, n: 16); |
138 | strcpy(dest: lc_note_name, src: "main bin spec" ); |
139 | |
140 | // lc_note.data_owner |
141 | for (int i = 0; i < 16; i++) |
142 | loadcmd_data.push_back(x: lc_note_name[i]); |
143 | |
144 | // we start writing the payload at payload_file_offset to leave |
145 | // room at the start for the header & the load commands. |
146 | uint64_t current_payload_offset = payload.size() + payload_file_offset; |
147 | |
148 | add_uint64(buf&: loadcmd_data, val: current_payload_offset); // note_command.offset |
149 | add_uint64(buf&: loadcmd_data, |
150 | val: sizeof(struct main_bin_spec_payload)); // note_command.size |
151 | |
152 | loadcmds.push_back(x: loadcmd_data); |
153 | |
154 | // Now write the "main bin spec" payload. |
155 | add_uint32(buf&: payload, val: 2); // version |
156 | add_uint32(buf&: payload, val: 3); // type == 3 [ firmware, standalone, etc ] |
157 | add_uint64(buf&: payload, val: address); // load address |
158 | add_uint64(buf&: payload, val: slide); // slide |
159 | uuid_t uuid; |
160 | uuid_parse(in: uuidstr.c_str(), uu: uuid); |
161 | for (int i = 0; i < sizeof(uuid_t); i++) |
162 | payload.push_back(x: uuid[i]); |
163 | add_uint32(buf&: payload, val: 0); // log2_pagesize unspecified |
164 | add_uint32(buf&: payload, val: 0); // platform unspecified |
165 | } |
166 | |
167 | void add_lc_segment(std::vector<std::vector<uint8_t>> &loadcmds, |
168 | std::vector<uint8_t> &payload, int payload_file_offset) { |
169 | std::vector<uint8_t> loadcmd_data; |
170 | struct segment_command_64 seg; |
171 | seg.cmd = LC_SEGMENT_64; |
172 | seg.cmdsize = sizeof(struct segment_command_64); // no sections |
173 | memset(seg.segname, 0, 16); |
174 | seg.vmaddr = 0xffffff7f96400000; |
175 | seg.vmsize = 4096; |
176 | seg.fileoff = payload.size() + payload_file_offset; |
177 | seg.filesize = 0; |
178 | seg.maxprot = 1; |
179 | seg.initprot = 1; |
180 | seg.nsects = 0; |
181 | seg.flags = 0; |
182 | |
183 | uint8_t *p = (uint8_t *)&seg; |
184 | for (int i = 0; i < sizeof(struct segment_command_64); i++) { |
185 | loadcmd_data.push_back(*(p + i)); |
186 | } |
187 | loadcmds.push_back(x: loadcmd_data); |
188 | } |
189 | |
190 | std::string get_uuid_from_binary(const char *fn, cpu_type_t &cputype, |
191 | cpu_subtype_t &cpusubtype) { |
192 | if (strlen(s: fn) == 0) |
193 | return {}; |
194 | |
195 | FILE *f = fopen(filename: fn, modes: "r" ); |
196 | if (f == nullptr) { |
197 | fprintf(stderr, format: "Unable to open binary '%s' to get uuid\n" , fn); |
198 | exit(status: 1); |
199 | } |
200 | uint32_t num_of_load_cmds = 0; |
201 | uint32_t size_of_load_cmds = 0; |
202 | std::string uuid; |
203 | off_t file_offset = 0; |
204 | |
205 | uint8_t magic[4]; |
206 | if (::fread(ptr: magic, size: 1, n: 4, stream: f) != 4) { |
207 | fprintf(stderr, format: "Failed to read magic number from input file %s\n" , fn); |
208 | exit(status: 1); |
209 | } |
210 | uint8_t magic_32_be[] = {0xfe, 0xed, 0xfa, 0xce}; |
211 | uint8_t magic_32_le[] = {0xce, 0xfa, 0xed, 0xfe}; |
212 | uint8_t magic_64_be[] = {0xfe, 0xed, 0xfa, 0xcf}; |
213 | uint8_t magic_64_le[] = {0xcf, 0xfa, 0xed, 0xfe}; |
214 | |
215 | if (memcmp(s1: magic, s2: magic_32_be, n: 4) == 0 || |
216 | memcmp(s1: magic, s2: magic_64_be, n: 4) == 0) { |
217 | fprintf(stderr, format: "big endian corefiles not supported\n" ); |
218 | exit(status: 1); |
219 | } |
220 | |
221 | ::fseeko(stream: f, off: 0, SEEK_SET); |
222 | if (memcmp(s1: magic, s2: magic_32_le, n: 4) == 0) { |
223 | struct mh; |
224 | if (::fread(ptr: &mh, size: 1, n: sizeof(mh), stream: f) != sizeof(mh)) { |
225 | fprintf(stderr, format: "error reading mach header from input file\n" ); |
226 | exit(status: 1); |
227 | } |
228 | if (mh.cputype != CPU_TYPE_X86_64 && mh.cputype != CPU_TYPE_ARM64) { |
229 | fprintf(stderr, |
230 | format: "This tool creates an x86_64/arm64 corefile but " |
231 | "the supplied binary '%s' is cputype 0x%x\n" , |
232 | fn, (uint32_t)mh.cputype); |
233 | exit(status: 1); |
234 | } |
235 | num_of_load_cmds = mh.ncmds; |
236 | size_of_load_cmds = mh.sizeofcmds; |
237 | file_offset += sizeof(struct mach_header); |
238 | cputype = mh.cputype; |
239 | cpusubtype = mh.cpusubtype; |
240 | } else { |
241 | struct mh; |
242 | if (::fread(ptr: &mh, size: 1, n: sizeof(mh), stream: f) != sizeof(mh)) { |
243 | fprintf(stderr, format: "error reading mach header from input file\n" ); |
244 | exit(status: 1); |
245 | } |
246 | if (mh.cputype != CPU_TYPE_X86_64 && mh.cputype != CPU_TYPE_ARM64) { |
247 | fprintf(stderr, |
248 | format: "This tool creates an x86_64/arm64 corefile but " |
249 | "the supplied binary '%s' is cputype 0x%x\n" , |
250 | fn, (uint32_t)mh.cputype); |
251 | exit(status: 1); |
252 | } |
253 | num_of_load_cmds = mh.ncmds; |
254 | size_of_load_cmds = mh.sizeofcmds; |
255 | file_offset += sizeof(struct mach_header_64); |
256 | cputype = mh.cputype; |
257 | cpusubtype = mh.cpusubtype; |
258 | } |
259 | |
260 | off_t load_cmds_offset = file_offset; |
261 | |
262 | for (int i = 0; i < num_of_load_cmds && |
263 | (file_offset - load_cmds_offset) < size_of_load_cmds; |
264 | i++) { |
265 | ::fseeko(stream: f, off: file_offset, SEEK_SET); |
266 | uint32_t cmd; |
267 | uint32_t cmdsize; |
268 | ::fread(ptr: &cmd, size: sizeof(uint32_t), n: 1, stream: f); |
269 | ::fread(ptr: &cmdsize, size: sizeof(uint32_t), n: 1, stream: f); |
270 | if (cmd == LC_UUID) { |
271 | struct uuid_command uuidcmd; |
272 | ::fseeko(stream: f, off: file_offset, SEEK_SET); |
273 | if (::fread(ptr: &uuidcmd, size: 1, n: sizeof(uuidcmd), stream: f) != sizeof(uuidcmd)) { |
274 | fprintf(stderr, format: "Unable to read LC_UUID load command.\n" ); |
275 | exit(status: 1); |
276 | } |
277 | uuid_string_t uuidstr; |
278 | uuid_unparse(uuidcmd.uuid, uuidstr); |
279 | uuid = uuidstr; |
280 | break; |
281 | } |
282 | file_offset += cmdsize; |
283 | } |
284 | return uuid; |
285 | } |
286 | |
287 | int main(int argc, char **argv) { |
288 | if (argc != 6) { |
289 | fprintf( |
290 | stderr, |
291 | format: "usage: create-empty-corefile version-string|main-bin-spec " |
292 | "<output-core-name> <binary-to-copy-uuid-from> <address> <slide>\n" ); |
293 | fprintf(stderr, |
294 | format: " <address> is base 16, 0xffffffffffffffff means unknown\n" ); |
295 | fprintf(stderr, |
296 | format: " <slide> is base 16, 0xffffffffffffffff means unknown\n" ); |
297 | fprintf( |
298 | stderr, |
299 | format: "Create a Mach-O corefile with an either LC_NOTE 'kern ver str' or \n" ); |
300 | fprintf(stderr, format: "an LC_NOTE 'main bin spec' load command without an " |
301 | "address specified, depending on\n" ); |
302 | fprintf(stderr, format: "whether the 1st arg is version-string or main-bin-spec\n" ); |
303 | fprintf(stderr, format: "\nan LC_NOTE 'kern ver str' with no binary provided " |
304 | "(empty string filename) to get a UUID\n" ); |
305 | fprintf(stderr, format: "means an empty 'kern ver str' will be written, an invalid " |
306 | "LC_NOTE that lldb should handle.\n" ); |
307 | exit(status: 1); |
308 | } |
309 | if (strcmp(s1: argv[1], s2: "version-string" ) != 0 && |
310 | strcmp(s1: argv[1], s2: "main-bin-spec" ) != 0) { |
311 | fprintf(stderr, format: "arg1 was not version-string or main-bin-spec\n" ); |
312 | exit(status: 1); |
313 | } |
314 | |
315 | cpu_type_t cputype; |
316 | cpu_subtype_t cpusubtype; |
317 | std::string uuid = get_uuid_from_binary(argv[3], cputype, cpusubtype); |
318 | |
319 | // An array of load commands (in the form of byte arrays) |
320 | std::vector<std::vector<uint8_t>> load_commands; |
321 | |
322 | // An array of corefile contents (page data, lc_note data, etc) |
323 | std::vector<uint8_t> payload; |
324 | |
325 | errno = 0; |
326 | uint64_t address = strtoull(nptr: argv[4], NULL, base: 16); |
327 | if (errno != 0) { |
328 | fprintf(stderr, format: "Unable to parse address %s as base 16" , argv[4]); |
329 | exit(status: 1); |
330 | } |
331 | |
332 | errno = 0; |
333 | uint64_t slide = strtoull(nptr: argv[5], NULL, base: 16); |
334 | if (errno != 0) { |
335 | fprintf(stderr, format: "Unable to parse slide %s as base 16" , argv[4]); |
336 | exit(status: 1); |
337 | } |
338 | |
339 | // First add all the load commands / payload so we can figure out how large |
340 | // the load commands will actually be. |
341 | load_commands.push_back(lc_thread_load_command(cputype)); |
342 | if (strcmp(s1: argv[1], s2: "version-string" ) == 0) |
343 | add_lc_note_kern_ver_str_load_command(loadcmds&: load_commands, payload, payload_file_offset: 0, uuid, |
344 | address); |
345 | else |
346 | add_lc_note_main_bin_spec_load_command(loadcmds&: load_commands, payload, payload_file_offset: 0, uuidstr: uuid, |
347 | address, slide); |
348 | add_lc_segment(loadcmds&: load_commands, payload, payload_file_offset: 0); |
349 | |
350 | int size_of_load_commands = 0; |
351 | for (const auto &lc : load_commands) |
352 | size_of_load_commands += lc.size(); |
353 | |
354 | int header_and_load_cmd_room = |
355 | sizeof(struct mach_header_64) + size_of_load_commands; |
356 | |
357 | // Erase the load commands / payload now that we know how much space is |
358 | // needed, redo it. |
359 | load_commands.clear(); |
360 | payload.clear(); |
361 | |
362 | load_commands.push_back(lc_thread_load_command(cputype)); |
363 | |
364 | if (strcmp(s1: argv[1], s2: "version-string" ) == 0) |
365 | add_lc_note_kern_ver_str_load_command( |
366 | loadcmds&: load_commands, payload, payload_file_offset: header_and_load_cmd_room, uuid, address); |
367 | else |
368 | add_lc_note_main_bin_spec_load_command( |
369 | loadcmds&: load_commands, payload, payload_file_offset: header_and_load_cmd_room, uuidstr: uuid, address, slide); |
370 | |
371 | add_lc_segment(loadcmds&: load_commands, payload, payload_file_offset: header_and_load_cmd_room); |
372 | |
373 | struct mach_header_64 mh; |
374 | mh.magic = MH_MAGIC_64; |
375 | mh.cputype = cputype; |
376 | |
377 | mh.cpusubtype = cpusubtype; |
378 | mh.filetype = MH_CORE; |
379 | mh.ncmds = load_commands.size(); |
380 | mh.sizeofcmds = size_of_load_commands; |
381 | mh.flags = 0; |
382 | mh.reserved = 0; |
383 | |
384 | FILE *f = fopen(filename: argv[2], modes: "w" ); |
385 | |
386 | if (f == nullptr) { |
387 | fprintf(stderr, format: "Unable to open file %s for writing\n" , argv[2]); |
388 | exit(status: 1); |
389 | } |
390 | |
391 | fwrite(&mh, sizeof(struct mach_header_64), 1, f); |
392 | |
393 | for (const auto &lc : load_commands) |
394 | fwrite(ptr: lc.data(), size: lc.size(), n: 1, s: f); |
395 | |
396 | fseek(stream: f, off: header_and_load_cmd_room, SEEK_SET); |
397 | |
398 | fwrite(ptr: payload.data(), size: payload.size(), n: 1, s: f); |
399 | |
400 | fclose(stream: f); |
401 | } |
402 | |