| 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 | // 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(s: fn) == 0) |
| 201 | return {}; |
| 202 | |
| 203 | FILE *f = fopen(filename: fn, modes: "r" ); |
| 204 | if (f == nullptr) { |
| 205 | fprintf(stderr, format: "Unable to open binary '%s' to get uuid\n" , fn); |
| 206 | exit(status: 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(ptr: magic, size: 1, n: 4, stream: f) != 4) { |
| 215 | fprintf(stderr, format: "Failed to read magic number from input file %s\n" , fn); |
| 216 | exit(status: 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(s1: magic, s2: magic_32_be, n: 4) == 0 || |
| 224 | memcmp(s1: magic, s2: magic_64_be, n: 4) == 0) { |
| 225 | fprintf(stderr, format: "big endian corefiles not supported\n" ); |
| 226 | exit(status: 1); |
| 227 | } |
| 228 | |
| 229 | ::fseeko(stream: f, off: 0, SEEK_SET); |
| 230 | if (memcmp(s1: magic, s2: magic_32_le, n: 4) == 0) { |
| 231 | struct mh; |
| 232 | if (::fread(ptr: &mh, size: 1, n: sizeof(mh), stream: f) != sizeof(mh)) { |
| 233 | fprintf(stderr, format: "error reading mach header from input file\n" ); |
| 234 | exit(status: 1); |
| 235 | } |
| 236 | if (mh.cputype != CPU_TYPE_X86_64 && mh.cputype != CPU_TYPE_ARM64) { |
| 237 | fprintf(stderr, |
| 238 | format: "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(status: 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 mh; |
| 250 | if (::fread(ptr: &mh, size: 1, n: sizeof(mh), stream: f) != sizeof(mh)) { |
| 251 | fprintf(stderr, format: "error reading mach header from input file\n" ); |
| 252 | exit(status: 1); |
| 253 | } |
| 254 | if (mh.cputype != CPU_TYPE_X86_64 && mh.cputype != CPU_TYPE_ARM64) { |
| 255 | fprintf(stderr, |
| 256 | format: "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(status: 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(stream: f, off: file_offset, SEEK_SET); |
| 274 | uint32_t cmd; |
| 275 | uint32_t cmdsize; |
| 276 | ::fread(ptr: &cmd, size: sizeof(uint32_t), n: 1, stream: f); |
| 277 | ::fread(ptr: &cmdsize, size: sizeof(uint32_t), n: 1, stream: f); |
| 278 | if (cmd == LC_UUID) { |
| 279 | struct uuid_command uuidcmd; |
| 280 | ::fseeko(stream: f, off: file_offset, SEEK_SET); |
| 281 | if (::fread(ptr: &uuidcmd, size: 1, n: sizeof(uuidcmd), stream: f) != sizeof(uuidcmd)) { |
| 282 | fprintf(stderr, format: "Unable to read LC_UUID load command.\n" ); |
| 283 | exit(status: 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 | format: "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 | format: " <address> is base 16, 0xffffffffffffffff means unknown\n" ); |
| 303 | fprintf(stderr, |
| 304 | format: " <slide> is base 16, 0xffffffffffffffff means unknown\n" ); |
| 305 | fprintf( |
| 306 | stderr, |
| 307 | format: "Create a Mach-O corefile with an either LC_NOTE 'kern ver str' or \n" ); |
| 308 | fprintf(stderr, format: "an LC_NOTE 'main bin spec' load command without an " |
| 309 | "address specified, depending on\n" ); |
| 310 | fprintf(stderr, format: "whether the 1st arg is version-string or main-bin-spec\n" ); |
| 311 | fprintf(stderr, format: "\nan LC_NOTE 'kern ver str' with no binary provided " |
| 312 | "(empty string filename) to get a UUID\n" ); |
| 313 | fprintf(stderr, format: "means an empty 'kern ver str' will be written, an invalid " |
| 314 | "LC_NOTE that lldb should handle.\n" ); |
| 315 | exit(status: 1); |
| 316 | } |
| 317 | if (strcmp(s1: argv[1], s2: "version-string" ) != 0 && |
| 318 | strcmp(s1: argv[1], s2: "main-bin-spec" ) != 0) { |
| 319 | fprintf(stderr, format: "arg1 was not version-string or main-bin-spec\n" ); |
| 320 | exit(status: 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(nptr: argv[4], NULL, base: 16); |
| 335 | if (errno != 0) { |
| 336 | fprintf(stderr, format: "Unable to parse address %s as base 16" , argv[4]); |
| 337 | exit(status: 1); |
| 338 | } |
| 339 | |
| 340 | errno = 0; |
| 341 | uint64_t slide = strtoull(nptr: argv[5], NULL, base: 16); |
| 342 | if (errno != 0) { |
| 343 | fprintf(stderr, format: "Unable to parse slide %s as base 16" , argv[4]); |
| 344 | exit(status: 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(s1: argv[1], s2: "version-string" ) == 0) |
| 351 | add_lc_note_kern_ver_str_load_command(loadcmds&: load_commands, payload, payload_file_offset: 0, uuid, |
| 352 | address); |
| 353 | else |
| 354 | add_lc_note_main_bin_spec_load_command(loadcmds&: load_commands, payload, payload_file_offset: 0, uuidstr: uuid, |
| 355 | address, slide); |
| 356 | add_lc_segment(loadcmds&: load_commands, payload, payload_file_offset: 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(s1: argv[1], s2: "version-string" ) == 0) |
| 373 | add_lc_note_kern_ver_str_load_command( |
| 374 | loadcmds&: load_commands, payload, payload_file_offset: header_and_load_cmd_room, uuid, address); |
| 375 | else |
| 376 | add_lc_note_main_bin_spec_load_command( |
| 377 | loadcmds&: load_commands, payload, payload_file_offset: header_and_load_cmd_room, uuidstr: uuid, address, slide); |
| 378 | |
| 379 | add_lc_segment(loadcmds&: load_commands, payload, payload_file_offset: 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(filename: argv[2], modes: "w" ); |
| 393 | |
| 394 | if (f == nullptr) { |
| 395 | fprintf(stderr, format: "Unable to open file %s for writing\n" , argv[2]); |
| 396 | exit(status: 1); |
| 397 | } |
| 398 | |
| 399 | fwrite(&mh, sizeof(struct mach_header_64), 1, f); |
| 400 | |
| 401 | for (const auto &lc : load_commands) |
| 402 | fwrite(ptr: lc.data(), size: lc.size(), n: 1, s: f); |
| 403 | |
| 404 | fseek(stream: f, off: header_and_load_cmd_room, SEEK_SET); |
| 405 | |
| 406 | fwrite(ptr: payload.data(), size: payload.size(), n: 1, s: f); |
| 407 | |
| 408 | fclose(stream: f); |
| 409 | } |
| 410 | |