| /* | |
| * Post-process a vdso elf image for inclusion into qemu. | |
| * | |
| * Copyright 2023 Linaro, Ltd. | |
| * | |
| * SPDX-License-Identifier: GPL-2.0-or-later | |
| */ | |
| #include <stdlib.h> | |
| #include <stdbool.h> | |
| #include <stdint.h> | |
| #include <stdio.h> | |
| #include <string.h> | |
| #include <errno.h> | |
| #include <endian.h> | |
| #include <unistd.h> | |
| #include "elf.h" | |
| #define bswap_(p) _Generic(*(p), \ | |
| uint16_t: __builtin_bswap16, \ | |
| uint32_t: __builtin_bswap32, \ | |
| uint64_t: __builtin_bswap64, \ | |
| int16_t: __builtin_bswap16, \ | |
| int32_t: __builtin_bswap32, \ | |
| int64_t: __builtin_bswap64) | |
| #define bswaps(p) (*(p) = bswap_(p)(*(p))) | |
| static void output_reloc(FILE *outf, void *buf, void *loc) | |
| { | |
| fprintf(outf, " 0x%08tx,\n", loc - buf); | |
| } | |
| static const char *sigreturn_sym; | |
| static const char *rt_sigreturn_sym; | |
| static unsigned sigreturn_addr; | |
| static unsigned rt_sigreturn_addr; | |
| static unsigned sigreturn_region_start_addr; | |
| static unsigned sigreturn_region_end_addr; | |
| #define N 32 | |
| #define elfN(x) elf32_##x | |
| #define ElfN(x) Elf32_##x | |
| #include "gen-vdso-elfn.c.inc" | |
| #undef N | |
| #undef elfN | |
| #undef ElfN | |
| #define N 64 | |
| #define elfN(x) elf64_##x | |
| #define ElfN(x) Elf64_##x | |
| #include "gen-vdso-elfn.c.inc" | |
| #undef N | |
| #undef elfN | |
| #undef ElfN | |
| int main(int argc, char **argv) | |
| { | |
| FILE *inf = NULL, *outf = NULL; | |
| long total_len; | |
| const char *prefix = "vdso"; | |
| const char *inf_name; | |
| const char *outf_name = NULL; | |
| unsigned char *buf = NULL; | |
| bool need_bswap; | |
| int ret = EXIT_FAILURE; | |
| while (1) { | |
| int opt = getopt(argc, argv, "o:p:r:s:"); | |
| if (opt < 0) { | |
| break; | |
| } | |
| switch (opt) { | |
| case 'o': | |
| outf_name = optarg; | |
| break; | |
| case 'p': | |
| prefix = optarg; | |
| break; | |
| case 'r': | |
| rt_sigreturn_sym = optarg; | |
| break; | |
| case 's': | |
| sigreturn_sym = optarg; | |
| break; | |
| default: | |
| usage: | |
| fprintf(stderr, "usage: [-p prefix] [-r rt-sigreturn-name] " | |
| "[-s sigreturn-name] -o output-file input-file\n"); | |
| return EXIT_FAILURE; | |
| } | |
| } | |
| if (optind >= argc || outf_name == NULL) { | |
| goto usage; | |
| } | |
| inf_name = argv[optind]; | |
| /* | |
| * Open the input and output files. | |
| */ | |
| inf = fopen(inf_name, "rb"); | |
| if (inf == NULL) { | |
| goto perror_inf; | |
| } | |
| outf = fopen(outf_name, "w"); | |
| if (outf == NULL) { | |
| goto perror_outf; | |
| } | |
| /* | |
| * Read the input file into a buffer. | |
| * We expect the vdso to be small, on the order of one page, | |
| * therefore we do not expect a partial read. | |
| */ | |
| if (fseek(inf, 0, SEEK_END) < 0) { | |
| goto perror_inf; | |
| } | |
| total_len = ftell(inf); | |
| if (total_len < 0) { | |
| goto perror_inf; | |
| } | |
| if (fseek(inf, 0, SEEK_SET) < 0) { | |
| goto perror_inf; | |
| } | |
| if (total_len < EI_NIDENT) { | |
| fprintf(stderr, "%s: file too small (truncated?)\n", inf_name); | |
| return EXIT_FAILURE; | |
| } | |
| buf = malloc(total_len); | |
| if (buf == NULL) { | |
| goto perror_inf; | |
| } | |
| errno = 0; | |
| if (fread(buf, 1, total_len, inf) != total_len) { | |
| if (errno) { | |
| goto perror_inf; | |
| } | |
| fprintf(stderr, "%s: incomplete read\n", inf_name); | |
| return EXIT_FAILURE; | |
| } | |
| /* | |
| * Identify which elf flavor we're processing. | |
| * The first 16 bytes of the file are e_ident. | |
| */ | |
| if (buf[EI_MAG0] != ELFMAG0 || buf[EI_MAG1] != ELFMAG1 || | |
| buf[EI_MAG2] != ELFMAG2 || buf[EI_MAG3] != ELFMAG3) { | |
| fprintf(stderr, "%s: not an elf file\n", inf_name); | |
| return EXIT_FAILURE; | |
| } | |
| switch (buf[EI_DATA]) { | |
| case ELFDATA2LSB: | |
| need_bswap = BYTE_ORDER != LITTLE_ENDIAN; | |
| break; | |
| case ELFDATA2MSB: | |
| need_bswap = BYTE_ORDER != BIG_ENDIAN; | |
| break; | |
| default: | |
| fprintf(stderr, "%s: invalid elf EI_DATA (%u)\n", | |
| inf_name, buf[EI_DATA]); | |
| return EXIT_FAILURE; | |
| } | |
| /* | |
| * We need to relocate the VDSO image. The one built into the kernel | |
| * is built for a fixed address. The one we built for QEMU is not, | |
| * since that requires close control of the guest address space. | |
| * | |
| * Output relocation addresses as we go. | |
| */ | |
| fprintf(outf, | |
| "/* Automatically generated by linux-user/gen-vdso.c. */\n" | |
| "\n" | |
| "static const unsigned %s_relocs[] = {\n", prefix); | |
| switch (buf[EI_CLASS]) { | |
| case ELFCLASS32: | |
| elf32_process(outf, buf, total_len, need_bswap); | |
| break; | |
| case ELFCLASS64: | |
| elf64_process(outf, buf, total_len, need_bswap); | |
| break; | |
| default: | |
| fprintf(stderr, "%s: invalid elf EI_CLASS (%u)\n", | |
| inf_name, buf[EI_CLASS]); | |
| return EXIT_FAILURE; | |
| } | |
| fprintf(outf, "};\n\n"); /* end vdso_relocs. */ | |
| /* | |
| * Write out the vdso image now, after we made local changes. | |
| */ | |
| fprintf(outf, | |
| "static const uint8_t %s_image[] = {", | |
| prefix); | |
| for (long i = 0; i < total_len; ++i) { | |
| if (i % 12 == 0) { | |
| fputs("\n ", outf); | |
| } | |
| fprintf(outf, " 0x%02x,", buf[i]); | |
| } | |
| fprintf(outf, "\n};\n\n"); | |
| fprintf(outf, "static const VdsoImageInfo %s_image_info = {\n", prefix); | |
| fprintf(outf, " .image = %s_image,\n", prefix); | |
| fprintf(outf, " .relocs = %s_relocs,\n", prefix); | |
| fprintf(outf, " .image_size = sizeof(%s_image),\n", prefix); | |
| fprintf(outf, " .reloc_count = ARRAY_SIZE(%s_relocs),\n", prefix); | |
| fprintf(outf, " .sigreturn_ofs = 0x%x,\n", sigreturn_addr); | |
| fprintf(outf, " .rt_sigreturn_ofs = 0x%x,\n", rt_sigreturn_addr); | |
| fprintf(outf, " .sigreturn_region_start_ofs = 0x%x,\n", | |
| sigreturn_region_start_addr); | |
| fprintf(outf, " .sigreturn_region_end_ofs = 0x%x,\n", | |
| sigreturn_region_end_addr); | |
| fprintf(outf, "};\n"); | |
| ret = EXIT_SUCCESS; | |
| cleanup: | |
| free(buf); | |
| if (outf && fclose(outf) != 0) { | |
| ret = EXIT_FAILURE; | |
| } | |
| if (inf && fclose(inf) != 0) { | |
| ret = EXIT_FAILURE; | |
| } | |
| return ret; | |
| perror_inf: | |
| perror(inf_name); | |
| goto cleanup; | |
| perror_outf: | |
| perror(outf_name); | |
| goto cleanup; | |
| } |