xref: /llvm-project/lldb/test/API/macosx/lc-note/firmware-corefile/create-empty-corefile.cpp (revision 527595f92789f9701a4b91ab32b792034352f78d)
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(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(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(buf, "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(loadcmd_data, 40);      // note_command.cmdsize
105   char lc_note_name[16];
106   memset(lc_note_name, 0, 16);
107   strcpy(lc_note_name, "kern ver str");
108 
109   // lc_note.data_owner
110   for (int i = 0; i < 16; i++)
111     loadcmd_data.push_back(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(loadcmd_data, current_payload_offset); // note_command.offset
118   add_uint64(loadcmd_data, 4 + ident.size() + 1);   // note_command.size
119 
120   loadcmds.push_back(loadcmd_data);
121 
122   add_uint32(payload, 1); // kerneL_version_string.version
123   for (int i = 0; i < ident.size() + 1; i++) {
124     payload.push_back(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(loadcmd_data, 40);      // note_command.cmdsize
136   char lc_note_name[16];
137   memset(lc_note_name, 0, 16);
138   strcpy(lc_note_name, "main bin spec");
139 
140   // lc_note.data_owner
141   for (int i = 0; i < 16; i++)
142     loadcmd_data.push_back(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(loadcmd_data, current_payload_offset); // note_command.offset
149   add_uint64(loadcmd_data,
150              sizeof(struct main_bin_spec_payload)); // note_command.size
151 
152   loadcmds.push_back(loadcmd_data);
153 
154   // Now write the "main bin spec" payload.
155   add_uint32(payload, 2);          // version
156   add_uint32(payload, 3);          // type == 3 [ firmware, standalone, etc ]
157   add_uint64(payload, address);    // load address
158   add_uint64(payload, slide);      // slide
159   uuid_t uuid;
160   uuid_parse(uuidstr.c_str(), uuid);
161   for (int i = 0; i < sizeof(uuid_t); i++)
162     payload.push_back(uuid[i]);
163   add_uint32(payload, 0); // log2_pagesize unspecified
164   add_uint32(payload, 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(loadcmd_data);
188 }
189 
190 std::string get_uuid_from_binary(const char *fn, cpu_type_t &cputype,
191                                  cpu_subtype_t &cpusubtype) {
192 // We may be given a file, set reasonable values.
193 #if defined(__x86_64__)
194   cputype = CPU_TYPE_X86;
195   cpusubtype = CPU_SUBTYPE_X86_ALL;
196 #else
197   cputype = CPU_TYPE_ARM64;
198   cpusubtype = CPU_SUBTYPE_ARM64_ALL;
199 #endif
200   if (strlen(fn) == 0)
201     return {};
202 
203   FILE *f = fopen(fn, "r");
204   if (f == nullptr) {
205     fprintf(stderr, "Unable to open binary '%s' to get uuid\n", fn);
206     exit(1);
207   }
208   uint32_t num_of_load_cmds = 0;
209   uint32_t size_of_load_cmds = 0;
210   std::string uuid;
211   off_t file_offset = 0;
212 
213   uint8_t magic[4];
214   if (::fread(magic, 1, 4, f) != 4) {
215     fprintf(stderr, "Failed to read magic number from input file %s\n", fn);
216     exit(1);
217   }
218   uint8_t magic_32_be[] = {0xfe, 0xed, 0xfa, 0xce};
219   uint8_t magic_32_le[] = {0xce, 0xfa, 0xed, 0xfe};
220   uint8_t magic_64_be[] = {0xfe, 0xed, 0xfa, 0xcf};
221   uint8_t magic_64_le[] = {0xcf, 0xfa, 0xed, 0xfe};
222 
223   if (memcmp(magic, magic_32_be, 4) == 0 ||
224       memcmp(magic, magic_64_be, 4) == 0) {
225     fprintf(stderr, "big endian corefiles not supported\n");
226     exit(1);
227   }
228 
229   ::fseeko(f, 0, SEEK_SET);
230   if (memcmp(magic, magic_32_le, 4) == 0) {
231     struct mach_header mh;
232     if (::fread(&mh, 1, sizeof(mh), f) != sizeof(mh)) {
233       fprintf(stderr, "error reading mach header from input file\n");
234       exit(1);
235     }
236     if (mh.cputype != CPU_TYPE_X86_64 && mh.cputype != CPU_TYPE_ARM64) {
237       fprintf(stderr,
238               "This tool creates an x86_64/arm64 corefile but "
239               "the supplied binary '%s' is cputype 0x%x\n",
240               fn, (uint32_t)mh.cputype);
241       exit(1);
242     }
243     num_of_load_cmds = mh.ncmds;
244     size_of_load_cmds = mh.sizeofcmds;
245     file_offset += sizeof(struct mach_header);
246     cputype = mh.cputype;
247     cpusubtype = mh.cpusubtype;
248   } else {
249     struct mach_header_64 mh;
250     if (::fread(&mh, 1, sizeof(mh), f) != sizeof(mh)) {
251       fprintf(stderr, "error reading mach header from input file\n");
252       exit(1);
253     }
254     if (mh.cputype != CPU_TYPE_X86_64 && mh.cputype != CPU_TYPE_ARM64) {
255       fprintf(stderr,
256               "This tool creates an x86_64/arm64 corefile but "
257               "the supplied binary '%s' is cputype 0x%x\n",
258               fn, (uint32_t)mh.cputype);
259       exit(1);
260     }
261     num_of_load_cmds = mh.ncmds;
262     size_of_load_cmds = mh.sizeofcmds;
263     file_offset += sizeof(struct mach_header_64);
264     cputype = mh.cputype;
265     cpusubtype = mh.cpusubtype;
266   }
267 
268   off_t load_cmds_offset = file_offset;
269 
270   for (int i = 0; i < num_of_load_cmds &&
271                   (file_offset - load_cmds_offset) < size_of_load_cmds;
272        i++) {
273     ::fseeko(f, file_offset, SEEK_SET);
274     uint32_t cmd;
275     uint32_t cmdsize;
276     ::fread(&cmd, sizeof(uint32_t), 1, f);
277     ::fread(&cmdsize, sizeof(uint32_t), 1, f);
278     if (cmd == LC_UUID) {
279       struct uuid_command uuidcmd;
280       ::fseeko(f, file_offset, SEEK_SET);
281       if (::fread(&uuidcmd, 1, sizeof(uuidcmd), f) != sizeof(uuidcmd)) {
282         fprintf(stderr, "Unable to read LC_UUID load command.\n");
283         exit(1);
284       }
285       uuid_string_t uuidstr;
286       uuid_unparse(uuidcmd.uuid, uuidstr);
287       uuid = uuidstr;
288       break;
289     }
290     file_offset += cmdsize;
291   }
292   return uuid;
293 }
294 
295 int main(int argc, char **argv) {
296   if (argc != 6) {
297     fprintf(
298         stderr,
299         "usage: create-empty-corefile version-string|main-bin-spec "
300         "<output-core-name> <binary-to-copy-uuid-from> <address> <slide>\n");
301     fprintf(stderr,
302             "     <address> is base 16, 0xffffffffffffffff means unknown\n");
303     fprintf(stderr,
304             "     <slide> is base 16, 0xffffffffffffffff means unknown\n");
305     fprintf(
306         stderr,
307         "Create a Mach-O corefile with an either LC_NOTE 'kern ver str' or \n");
308     fprintf(stderr, "an LC_NOTE 'main bin spec' load command without an "
309                     "address specified, depending on\n");
310     fprintf(stderr, "whether the 1st arg is version-string or main-bin-spec\n");
311     fprintf(stderr, "\nan LC_NOTE 'kern ver str' with no binary provided "
312                     "(empty string filename) to get a UUID\n");
313     fprintf(stderr, "means an empty 'kern ver str' will be written, an invalid "
314                     "LC_NOTE that lldb should handle.\n");
315     exit(1);
316   }
317   if (strcmp(argv[1], "version-string") != 0 &&
318       strcmp(argv[1], "main-bin-spec") != 0) {
319     fprintf(stderr, "arg1 was not version-string or main-bin-spec\n");
320     exit(1);
321   }
322 
323   cpu_type_t cputype;
324   cpu_subtype_t cpusubtype;
325   std::string uuid = get_uuid_from_binary(argv[3], cputype, cpusubtype);
326 
327   // An array of load commands (in the form of byte arrays)
328   std::vector<std::vector<uint8_t>> load_commands;
329 
330   // An array of corefile contents (page data, lc_note data, etc)
331   std::vector<uint8_t> payload;
332 
333   errno = 0;
334   uint64_t address = strtoull(argv[4], NULL, 16);
335   if (errno != 0) {
336     fprintf(stderr, "Unable to parse address %s as base 16", argv[4]);
337     exit(1);
338   }
339 
340   errno = 0;
341   uint64_t slide = strtoull(argv[5], NULL, 16);
342   if (errno != 0) {
343     fprintf(stderr, "Unable to parse slide %s as base 16", argv[4]);
344     exit(1);
345   }
346 
347   // First add all the load commands / payload so we can figure out how large
348   // the load commands will actually be.
349   load_commands.push_back(lc_thread_load_command(cputype));
350   if (strcmp(argv[1], "version-string") == 0)
351     add_lc_note_kern_ver_str_load_command(load_commands, payload, 0, uuid,
352                                           address);
353   else
354     add_lc_note_main_bin_spec_load_command(load_commands, payload, 0, uuid,
355                                            address, slide);
356   add_lc_segment(load_commands, payload, 0);
357 
358   int size_of_load_commands = 0;
359   for (const auto &lc : load_commands)
360     size_of_load_commands += lc.size();
361 
362   int header_and_load_cmd_room =
363       sizeof(struct mach_header_64) + size_of_load_commands;
364 
365   // Erase the load commands / payload now that we know how much space is
366   // needed, redo it.
367   load_commands.clear();
368   payload.clear();
369 
370   load_commands.push_back(lc_thread_load_command(cputype));
371 
372   if (strcmp(argv[1], "version-string") == 0)
373     add_lc_note_kern_ver_str_load_command(
374         load_commands, payload, header_and_load_cmd_room, uuid, address);
375   else
376     add_lc_note_main_bin_spec_load_command(
377         load_commands, payload, header_and_load_cmd_room, uuid, address, slide);
378 
379   add_lc_segment(load_commands, payload, header_and_load_cmd_room);
380 
381   struct mach_header_64 mh;
382   mh.magic = MH_MAGIC_64;
383   mh.cputype = cputype;
384 
385   mh.cpusubtype = cpusubtype;
386   mh.filetype = MH_CORE;
387   mh.ncmds = load_commands.size();
388   mh.sizeofcmds = size_of_load_commands;
389   mh.flags = 0;
390   mh.reserved = 0;
391 
392   FILE *f = fopen(argv[2], "w");
393 
394   if (f == nullptr) {
395     fprintf(stderr, "Unable to open file %s for writing\n", argv[2]);
396     exit(1);
397   }
398 
399   fwrite(&mh, sizeof(struct mach_header_64), 1, f);
400 
401   for (const auto &lc : load_commands)
402     fwrite(lc.data(), lc.size(), 1, f);
403 
404   fseek(f, header_and_load_cmd_room, SEEK_SET);
405 
406   fwrite(payload.data(), payload.size(), 1, f);
407 
408   fclose(f);
409 }
410