blob: b41f900b0c31ba7d9930ffca91c95e76e39aadee [file] [log] [blame]
Ben Vanik0635b092023-03-28 13:03:38 -07001// Copyright 2023 The IREE Authors
2//
3// Licensed under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6
7#include <stdio.h>
8
9#include "iree/base/api.h"
10#include "iree/base/internal/file_io.h"
11#include "iree/base/internal/path.h"
12#include "iree/hal/local/elf/fatelf.h"
13
14// NOTE: we don't verify ELF information in here and just pass it along. Don't
15// run this on untrusted ELFs.
16
17// NOTE: errors are handled in here just enough to get error messages - we don't
18// care about leaks on failure as the process is going to die right away.
19
20// TODO(benvanik): make this based on the archs used? It needs to be the common
21// page size across all targets used within the file.
22#define IREE_FATELF_PAGE_SIZE 4096
23
24#if defined(IREE_PLATFORM_WINDOWS)
25#include <fcntl.h>
26#include <io.h>
27#define IREE_SET_BINARY_MODE(handle) _setmode(_fileno(handle), O_BINARY)
28#else
29#define IREE_SET_BINARY_MODE(handle) ((void)0)
30#endif // IREE_PLATFORM_WINDOWS
31
32static int print_usage() {
33 fprintf(stderr, "Syntax: iree-fatelf [join|split|select|dump] files...\n");
34 fprintf(stderr, "\n");
35 fprintf(stderr, "Join multiple ELFs into a FatELF:\n");
36 fprintf(stderr, " iree-fatelf join elf_a.so elf_b.so > fatelf.sos\n");
37 fprintf(stderr, "\n");
38 fprintf(stderr, "Split a FatELF into multiple ELF files (to dir):\n");
39 fprintf(stderr, " iree-fatelf split fatelf.sos\n");
40 fprintf(stderr, "\n");
41 fprintf(stderr, "Select a FatELF matching the current arch:\n");
42 fprintf(stderr, " iree-fatelf select fatelf.sos > elf.so\n");
43 fprintf(stderr, "\n");
44 fprintf(stderr, "Dump header records:\n");
45 fprintf(stderr, " iree-fatelf dump fatelf.sos\n");
46 fprintf(stderr, "\n");
47 return 1;
48}
49
50// NOTE: this is somewhat redundant with fatelf.c but that's ok - this is a
51// developer tool and I'd rather have the implementation linked into every
52// runtime be kept as simple as possible than not repeating 100 lines of code.
53// The runtime version is also designed to gracefully accept ELF files where
54// here we only want FatELF files.
55static iree_status_t fatelf_parse(iree_const_byte_span_t file_data,
56 iree_fatelf_header_t** out_header) {
57 *out_header = NULL;
58
59 if (file_data.data_length <
60 sizeof(iree_fatelf_header_t) + sizeof(iree_fatelf_record_t)) {
61 return iree_make_status(
62 IREE_STATUS_INVALID_ARGUMENT,
63 "file does not have enough data to even hold a FatELF header");
64 }
65
66 const iree_fatelf_header_t* raw_header =
67 (const iree_fatelf_header_t*)file_data.data;
68 iree_fatelf_header_t host_header = {
69 .magic = iree_unaligned_load_le_u32(&raw_header->magic),
70 .version = iree_unaligned_load_le_u16(&raw_header->version),
71 .record_count = iree_unaligned_load_le_u8(&raw_header->record_count),
72 .reserved = iree_unaligned_load_le_u8(&raw_header->reserved),
73 };
74
75 if (host_header.magic != IREE_FATELF_MAGIC) {
76 return iree_make_status(
77 IREE_STATUS_INVALID_ARGUMENT,
78 "file magic %08X does not match expected FatELF magic %08X",
79 host_header.magic, IREE_FATELF_MAGIC);
80 }
81 if (host_header.version != IREE_FATELF_FORMAT_VERSION) {
82 return iree_make_status(
83 IREE_STATUS_UNIMPLEMENTED,
84 "FatELF has version %d but runtime only supports version %d",
85 host_header.version, IREE_FATELF_FORMAT_VERSION);
86 }
87
88 iree_host_size_t required_bytes =
89 sizeof(iree_fatelf_header_t) +
90 host_header.record_count * sizeof(iree_fatelf_record_t);
91 if (file_data.data_length < required_bytes) {
92 return iree_make_status(IREE_STATUS_INVALID_ARGUMENT,
93 "FatELF file truncated, requires at least %" PRIhsz
94 "B for headers but only have %" PRIhsz
95 "B available",
96 required_bytes, file_data.data_length);
97 }
98
99 // Allocate storage for the parsed header and records.
100 iree_fatelf_header_t* header = NULL;
101 IREE_RETURN_IF_ERROR(iree_allocator_malloc(
102 iree_allocator_system(),
103 sizeof(iree_fatelf_header_t) +
104 host_header.record_count * sizeof(iree_fatelf_record_t),
105 (void**)&header));
106 memcpy(header, &host_header, sizeof(*header));
107 for (iree_elf64_byte_t i = 0; i < host_header.record_count; ++i) {
108 const iree_fatelf_record_t* raw_record = &raw_header->records[i];
109 const iree_fatelf_record_t host_record = {
110 .machine = iree_unaligned_load_le_u16(&raw_record->machine),
111 .osabi = iree_unaligned_load_le_u8(&raw_record->osabi),
112 .osabi_version = iree_unaligned_load_le_u8(&raw_record->osabi_version),
113 .word_size = iree_unaligned_load_le_u8(&raw_record->word_size),
114 .byte_order = iree_unaligned_load_le_u8(&raw_record->byte_order),
115 .reserved0 = iree_unaligned_load_le_u8(&raw_record->reserved0),
116 .reserved1 = iree_unaligned_load_le_u8(&raw_record->reserved1),
117 .offset = iree_unaligned_load_le_u64(&raw_record->offset),
118 .size = iree_unaligned_load_le_u64(&raw_record->size),
119 };
120 memcpy(&header->records[i], &host_record, sizeof(host_record));
121 }
122
123 *out_header = header;
124 return iree_ok_status();
125}
126
127// Tries to parse basic ELF metadata from |elf_data|.
128// Very little verification done. Note that this must support both 32 and 64-bit
129// ELF files regardless of the tool host configuration.
130// The returned fields match the ELF spec and may differ from FatELF.
131static iree_status_t fatelf_parse_elf_metadata(
132 iree_const_byte_span_t elf_data, iree_elf64_half_t* out_machine,
133 iree_elf64_byte_t* out_osabi, iree_elf64_byte_t* out_osabi_version,
134 iree_elf64_byte_t* out_elf_class, iree_elf64_byte_t* out_elf_data) {
135 *out_machine = 0;
136 *out_osabi = 0;
137 *out_osabi_version = 0;
138 *out_elf_data = 0;
139 *out_elf_class = 0;
140
141 if (elf_data.data_length < sizeof(iree_elf32_ehdr_t)) {
Scott Todd60b07642023-06-15 09:41:01 -0700142 return iree_make_status(IREE_STATUS_FAILED_PRECONDITION,
143 "ELF data provided (%" PRIhsz
144 ") is smaller than ehdr (%zu)",
145 elf_data.data_length, sizeof(iree_elf32_ehdr_t));
Ben Vanik0635b092023-03-28 13:03:38 -0700146 }
147
148 // The fields we're checking are the same in both 32 and 64 classes so we just
149 // use 32 for consistency.
150 const iree_elf32_ehdr_t* ehdr = (const iree_elf32_ehdr_t*)elf_data.data;
151 static const iree_elf_byte_t elf_magic[4] = {0x7F, 'E', 'L', 'F'};
152 if (memcmp(ehdr->e_ident, elf_magic, sizeof(elf_magic)) != 0) {
153 return iree_make_status(
154 IREE_STATUS_FAILED_PRECONDITION,
155 "data provided does not contain the ELF identifier");
156 }
157
158 *out_osabi = ehdr->e_ident[IREE_ELF_EI_OSABI];
159 *out_osabi_version = ehdr->e_ident[IREE_ELF_EI_ABIVERSION];
160 *out_elf_class = ehdr->e_ident[IREE_ELF_EI_CLASS];
161 *out_elf_data = ehdr->e_ident[IREE_ELF_EI_DATA];
162
163 // Note machine is multibyte and respects the declared endianness.
164 if (ehdr->e_ident[IREE_ELF_EI_DATA] == IREE_ELF_ELFDATA2LSB) {
165 *out_machine = iree_unaligned_load_le_u16(&ehdr->e_machine);
166 } else {
167#if IREE_ENDIANNESS_BIG
168// TODO(benvanik): helpers for big<->little endian
169// *out_machine = iree_unaligned_load_be_u16(&ehdr->e_machine);
170#error "ELF parsing support only available on little-endian systems today"
171#endif // IREE_ENDIANNESS_BIG
172 }
173
174 return iree_ok_status();
175}
176
177typedef struct {
178 uint64_t offset;
179 iree_file_contents_t* contents;
180 iree_const_byte_span_t elf_data;
181} fatelf_entry_t;
182
183// Joins one or more ELF files together and writes the output to stdout.
184static iree_status_t fatelf_join(int argc, char** argv) {
185 IREE_SET_BINARY_MODE(stdout); // ensure binary output mode
186
187#if IREE_ENDIANNESS_BIG
188#error "FatELF writing support only available on little-endian systems today"
189#endif // IREE_ENDIANNESS_BIG
190
191 // Load all source files.
192 iree_elf64_byte_t entry_count = argc;
193 fatelf_entry_t* entries =
194 (fatelf_entry_t*)iree_alloca(entry_count * sizeof(fatelf_entry_t));
195 memset(entries, 0, entry_count * sizeof(*entries));
196 for (iree_elf64_byte_t i = 0; i < entry_count; ++i) {
Ben Vanik9d15f362023-08-03 10:18:58 -0700197 IREE_RETURN_IF_ERROR(
198 iree_file_read_contents(argv[i], IREE_FILE_READ_FLAG_DEFAULT,
199 iree_allocator_system(), &entries[i].contents));
Ben Vanik0635b092023-03-28 13:03:38 -0700200 entries[i].elf_data = entries[i].contents->const_buffer;
201 }
202
203 // Compute offsets of all files based on their size and padding.
204 uint64_t file_offset = iree_host_align(
205 sizeof(iree_fatelf_header_t) + entry_count * sizeof(iree_fatelf_record_t),
206 IREE_FATELF_PAGE_SIZE);
207 for (iree_elf64_byte_t i = 0; i < entry_count; ++i) {
208 entries[i].offset = file_offset;
209 file_offset += iree_host_align(
210 entries[i].contents->const_buffer.data_length, IREE_FATELF_PAGE_SIZE);
211 }
212
213 // Write header without records.
214 iree_fatelf_header_t host_header = {
215 .magic = IREE_FATELF_MAGIC,
216 .version = IREE_FATELF_FORMAT_VERSION,
217 .record_count = entry_count,
218 .reserved = 0,
219 };
220 fwrite(&host_header, 1, sizeof(host_header), stdout);
221
222 // Write all records.
223 for (iree_elf64_byte_t i = 0; i < entry_count; ++i) {
224 iree_elf64_half_t machine = 0;
225 iree_elf64_byte_t osabi = 0;
226 iree_elf64_byte_t osabi_version = 0;
227 iree_elf64_byte_t elf_class = 0;
228 iree_elf64_byte_t elf_data = 0;
229 IREE_RETURN_IF_ERROR(
230 fatelf_parse_elf_metadata(entries[i].elf_data, &machine, &osabi,
231 &osabi_version, &elf_class, &elf_data));
232 iree_fatelf_record_t host_record = {
233 .machine = machine,
234 .osabi = osabi,
235 .osabi_version = osabi_version,
236 .word_size = elf_class == IREE_ELF_ELFCLASS32
237 ? IREE_FATELF_WORD_SIZE_32
238 : IREE_FATELF_WORD_SIZE_64,
239 .byte_order = elf_data == IREE_ELF_ELFDATA2LSB
240 ? IREE_FATELF_BYTE_ORDER_LSB
241 : IREE_FATELF_BYTE_ORDER_MSB,
242 .reserved0 = 0,
243 .reserved1 = 0,
244 .offset = (iree_elf64_off_t)entries[i].offset,
245 .size = (iree_elf64_xword_t)entries[i].elf_data.data_length,
246 };
247 fwrite(&host_record, 1, sizeof(host_record), stdout);
248 }
249
250 // Write all files, padding with zeros in-between as needed.
251 uint64_t write_offset =
252 sizeof(iree_fatelf_header_t) + entry_count * sizeof(iree_fatelf_record_t);
253 for (iree_elf64_byte_t i = 0; i < entry_count; ++i) {
254 uint64_t padding = entries[i].offset - write_offset;
255 for (uint64_t i = 0; i < padding; ++i) fputc(0, stdout);
256 write_offset += padding;
257 if (write_offset != entries[i].offset) {
258 return iree_make_status(IREE_STATUS_INTERNAL,
259 "actual offset does not match expected");
260 }
261 fwrite(entries[i].elf_data.data, 1, entries[i].elf_data.data_length,
262 stdout);
263 write_offset += entries[i].elf_data.data_length;
264 }
265 fflush(stdout);
266
267 for (iree_elf64_byte_t i = 0; i < entry_count; ++i) {
268 iree_file_contents_free(entries[i].contents);
269 }
270 return iree_ok_status();
271}
272
273static const char* fatelf_machine_id_str(iree_elf64_half_t value) {
274 // TODO(benvanik): include a full table from the spec?
275 // http://formats.kaitai.io/elf/ has a good source of canonical short names.
276 // For now we just support what we have in our ELF loader.
277 switch (value) {
278 case 0x03: // EM_386 / 3
279 return "x86";
280 case 0x28: // EM_ARM / 40
281 return "arm";
282 case 0xB7: // EM_AARCH64 / 183
283 return "aarch64";
284 case 0xF3: // EM_RISCV / 243
285 return "risvc";
286 case 0x3E: // EM_X86_64 / 62
287 return "x86_64";
288 default:
289 return "unknown";
290 }
291}
292
293static const char* fatelf_osabi_id_str(iree_elf64_byte_t value) {
294 switch (value) {
295 case IREE_ELF_ELFOSABI_NONE:
296 return "none";
297 case IREE_ELF_ELFOSABI_LINUX:
298 return "linux";
299 case IREE_ELF_ELFOSABI_STANDALONE:
300 return "standalone";
301 default:
302 return "unknown";
303 }
304}
305
306static const char* fatelf_word_size_id_str(iree_elf64_byte_t value) {
307 switch (value) {
308 case IREE_FATELF_WORD_SIZE_32:
309 return "lp32";
310 case IREE_FATELF_WORD_SIZE_64:
311 return "lp64";
312 default:
313 return "lpUNK";
314 }
315}
316
317static const char* fatelf_byte_order_id_str(iree_elf64_byte_t value) {
318 switch (value) {
319 case IREE_FATELF_BYTE_ORDER_MSB:
320 return "be";
321 case IREE_FATELF_BYTE_ORDER_LSB:
322 return "le";
323 default:
324 return "xx";
325 }
326}
327
328// Splits a FatELF into multiple files, writing each beside the input file.
329static iree_status_t fatelf_split(int argc, char** argv) {
330 iree_file_contents_t* fatelf_contents = NULL;
Ben Vanik9d15f362023-08-03 10:18:58 -0700331 IREE_RETURN_IF_ERROR(
332 iree_file_read_contents(argv[0], IREE_FILE_READ_FLAG_DEFAULT,
333 iree_allocator_system(), &fatelf_contents));
Ben Vanik0635b092023-03-28 13:03:38 -0700334 iree_fatelf_header_t* header = NULL;
335 IREE_RETURN_IF_ERROR(fatelf_parse(fatelf_contents->const_buffer, &header));
336
337 iree_string_view_t dirname, basename;
338 iree_file_path_split(iree_make_cstring_view(argv[0]), &dirname, &basename);
339 iree_string_view_t stem, extension;
340 iree_file_path_split_basename(basename, &stem, &extension);
341
342 for (iree_elf64_byte_t i = 0; i < header->record_count; ++i) {
343 const iree_fatelf_record_t* record = &header->records[i];
344
345 const char* machine_str = fatelf_machine_id_str(record->machine);
346 const char* osabi_str = fatelf_osabi_id_str(record->osabi);
347 const char* word_size_str = fatelf_word_size_id_str(record->word_size);
348 const char* byte_order_str = fatelf_byte_order_id_str(record->byte_order);
349
350 char record_path[2048];
351 iree_host_size_t record_path_length =
352 snprintf(record_path, IREE_ARRAYSIZE(record_path),
353 "%.*s%s%.*s.%s_%s_%s%s.so", (int)dirname.size, dirname.data,
354 dirname.size ? "/" : "", (int)stem.size, stem.data,
355 machine_str, osabi_str, word_size_str, byte_order_str);
356 record_path_length =
357 iree_file_path_canonicalize(record_path, record_path_length);
358
359 fprintf(stdout, "Writing record[%d] to '%.*s'...\n", i,
360 (int)record_path_length, record_path);
361
362 iree_const_byte_span_t record_data = iree_make_const_byte_span(
363 fatelf_contents->const_buffer.data + record->offset, record->size);
364 IREE_RETURN_IF_ERROR(iree_file_write_contents(record_path, record_data));
365 }
366
367 fprintf(stdout, "Wrote %d records to %.*s!\n", header->record_count,
368 (int)dirname.size, dirname.data);
369 iree_allocator_free(iree_allocator_system(), header);
370 iree_file_contents_free(fatelf_contents);
371 return iree_ok_status();
372}
373
374// Selects the ELF matching the current host config from a FatELF and writes
375// it to stdout.
376static iree_status_t fatelf_select(int argc, char** argv) {
377 IREE_SET_BINARY_MODE(stdout); // ensure binary output mode
378 iree_file_contents_t* fatelf_contents = NULL;
Ben Vanik9d15f362023-08-03 10:18:58 -0700379 IREE_RETURN_IF_ERROR(
380 iree_file_read_contents(argv[0], IREE_FILE_READ_FLAG_DEFAULT,
381 iree_allocator_system(), &fatelf_contents));
Ben Vanik0635b092023-03-28 13:03:38 -0700382 iree_const_byte_span_t elf_data = iree_const_byte_span_empty();
383 IREE_RETURN_IF_ERROR(
384 iree_fatelf_select(fatelf_contents->const_buffer, &elf_data));
385 fwrite(elf_data.data, 1, elf_data.data_length, stdout);
386 iree_file_contents_free(fatelf_contents);
387 return iree_ok_status();
388}
389
390static const char* fatelf_word_size_enum_str(iree_elf64_byte_t value) {
391 switch (value) {
392 case IREE_FATELF_WORD_SIZE_32:
393 return "ELFCLASS32";
394 case IREE_FATELF_WORD_SIZE_64:
395 return "ELFCLASS64";
396 default:
397 return "<unknown>";
398 }
399}
400
401static const char* fatelf_byte_order_enum_str(iree_elf64_byte_t value) {
402 switch (value) {
403 case IREE_FATELF_BYTE_ORDER_MSB:
404 return "ELFDATA2MSB (big-endian)";
405 case IREE_FATELF_BYTE_ORDER_LSB:
406 return "ELFDATA2LSB (little-endian)";
407 default:
408 return "<unknown>";
409 }
410}
411
412// Dumps the FatELF file records.
413static iree_status_t fatelf_dump(int argc, char** argv) {
414 iree_file_contents_t* fatelf_contents = NULL;
Ben Vanik9d15f362023-08-03 10:18:58 -0700415 IREE_RETURN_IF_ERROR(
416 iree_file_read_contents(argv[0], IREE_FILE_READ_FLAG_DEFAULT,
417 iree_allocator_system(), &fatelf_contents));
Ben Vanik0635b092023-03-28 13:03:38 -0700418 iree_fatelf_header_t* header = NULL;
419 IREE_RETURN_IF_ERROR(fatelf_parse(fatelf_contents->const_buffer, &header));
420
421 fprintf(stdout, "iree_fatelf_header_t:\n");
422 fprintf(stdout, " magic: %" PRIX32 "\n", header->magic);
423 fprintf(stdout, " version: %d\n", header->version);
424 fprintf(stdout, " records: %d\n", header->record_count);
425 fprintf(stdout, " reserved: %" PRIX8 "\n", header->reserved);
426 fprintf(stdout, "\n");
427
428 for (iree_elf64_byte_t i = 0; i < header->record_count; ++i) {
429 const iree_fatelf_record_t* record = &header->records[i];
430 fprintf(stdout, "iree_fatelf_record_t[%d]:\n", i);
431 fprintf(stdout, " machine: %d / %04X = %s\n", record->machine,
432 record->machine, fatelf_machine_id_str(record->machine));
433 fprintf(stdout, " osabi: %d / %02X = %s\n", record->osabi,
434 record->osabi, fatelf_osabi_id_str(record->osabi));
435 fprintf(stdout, " version: %d / %02X\n", record->osabi_version,
436 record->osabi_version);
437 fprintf(stdout, " word_size: %d / %02X = %s\n", record->word_size,
438 record->word_size, fatelf_word_size_enum_str(record->word_size));
439 fprintf(stdout, " byte_order: %d / %02X = %s\n", record->byte_order,
440 record->byte_order, fatelf_byte_order_enum_str(record->byte_order));
441 fprintf(stdout, " reserved0: %d / %02X\n", record->reserved0,
442 record->reserved0);
443 fprintf(stdout, " reserved1: %d / %02X\n", record->reserved1,
444 record->reserved1);
445 fprintf(stdout, " offset: %" PRIu64 " / %016" PRIX64 "\n",
446 record->offset, record->offset);
447 fprintf(stdout, " size: %" PRIu64 " / %016" PRIX64 "\n", record->size,
448 record->size);
449 fprintf(stdout, "\n");
450 }
451
452 iree_allocator_free(iree_allocator_system(), header);
453 iree_file_contents_free(fatelf_contents);
454 return iree_ok_status();
455}
456
457int main(int argc, char** argv) {
458 if (argc < 2) {
459 return print_usage();
460 }
461
462 char* command = argv[1];
463 int command_argc = argc - 2;
464 char** command_argv = argv + 2;
465
466 iree_status_t status = iree_ok_status();
467 if (strcmp(command, "join") == 0) {
468 if (command_argc < 1) return print_usage();
469 status = fatelf_join(command_argc, command_argv);
470 } else if (strcmp(command, "split") == 0) {
471 if (command_argc != 1) return print_usage();
472 status = fatelf_split(command_argc, command_argv);
473 } else if (strcmp(command, "select") == 0) {
474 if (command_argc != 1) return print_usage();
475 status = fatelf_select(command_argc, command_argv);
476 } else if (strcmp(command, "dump") == 0) {
477 if (command_argc != 1) return print_usage();
478 status = fatelf_dump(command_argc, command_argv);
479 } else {
480 return print_usage();
481 }
482
483 if (!iree_status_is_ok(status)) {
484 fprintf(stderr, "iree-fatelf encountered error:\n");
485 iree_status_fprint(stderr, status);
486 iree_status_free(status);
487 return 1;
488 }
489 return 0;
490}