Browse Source
This tool will be used for post-processing the linked vdso image, turning it into something that is easy to include into elfload.c. Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Signed-off-by: Richard Henderson <richard.henderson@linaro.org>pull/256/head
3 changed files with 535 additions and 1 deletions
@ -0,0 +1,307 @@ |
|||
/* |
|||
* Post-process a vdso elf image for inclusion into qemu. |
|||
* Elf size specialization. |
|||
* |
|||
* Copyright 2023 Linaro, Ltd. |
|||
* |
|||
* SPDX-License-Identifier: GPL-2.0-or-later |
|||
*/ |
|||
|
|||
static void elfN(bswap_ehdr)(ElfN(Ehdr) *ehdr) |
|||
{ |
|||
bswaps(&ehdr->e_type); /* Object file type */ |
|||
bswaps(&ehdr->e_machine); /* Architecture */ |
|||
bswaps(&ehdr->e_version); /* Object file version */ |
|||
bswaps(&ehdr->e_entry); /* Entry point virtual address */ |
|||
bswaps(&ehdr->e_phoff); /* Program header table file offset */ |
|||
bswaps(&ehdr->e_shoff); /* Section header table file offset */ |
|||
bswaps(&ehdr->e_flags); /* Processor-specific flags */ |
|||
bswaps(&ehdr->e_ehsize); /* ELF header size in bytes */ |
|||
bswaps(&ehdr->e_phentsize); /* Program header table entry size */ |
|||
bswaps(&ehdr->e_phnum); /* Program header table entry count */ |
|||
bswaps(&ehdr->e_shentsize); /* Section header table entry size */ |
|||
bswaps(&ehdr->e_shnum); /* Section header table entry count */ |
|||
bswaps(&ehdr->e_shstrndx); /* Section header string table index */ |
|||
} |
|||
|
|||
static void elfN(bswap_phdr)(ElfN(Phdr) *phdr) |
|||
{ |
|||
bswaps(&phdr->p_type); /* Segment type */ |
|||
bswaps(&phdr->p_flags); /* Segment flags */ |
|||
bswaps(&phdr->p_offset); /* Segment file offset */ |
|||
bswaps(&phdr->p_vaddr); /* Segment virtual address */ |
|||
bswaps(&phdr->p_paddr); /* Segment physical address */ |
|||
bswaps(&phdr->p_filesz); /* Segment size in file */ |
|||
bswaps(&phdr->p_memsz); /* Segment size in memory */ |
|||
bswaps(&phdr->p_align); /* Segment alignment */ |
|||
} |
|||
|
|||
static void elfN(bswap_shdr)(ElfN(Shdr) *shdr) |
|||
{ |
|||
bswaps(&shdr->sh_name); |
|||
bswaps(&shdr->sh_type); |
|||
bswaps(&shdr->sh_flags); |
|||
bswaps(&shdr->sh_addr); |
|||
bswaps(&shdr->sh_offset); |
|||
bswaps(&shdr->sh_size); |
|||
bswaps(&shdr->sh_link); |
|||
bswaps(&shdr->sh_info); |
|||
bswaps(&shdr->sh_addralign); |
|||
bswaps(&shdr->sh_entsize); |
|||
} |
|||
|
|||
static void elfN(bswap_sym)(ElfN(Sym) *sym) |
|||
{ |
|||
bswaps(&sym->st_name); |
|||
bswaps(&sym->st_value); |
|||
bswaps(&sym->st_size); |
|||
bswaps(&sym->st_shndx); |
|||
} |
|||
|
|||
static void elfN(bswap_dyn)(ElfN(Dyn) *dyn) |
|||
{ |
|||
bswaps(&dyn->d_tag); /* Dynamic type tag */ |
|||
bswaps(&dyn->d_un.d_ptr); /* Dynamic ptr or val, in union */ |
|||
} |
|||
|
|||
static void elfN(search_symtab)(ElfN(Shdr) *shdr, unsigned sym_idx, |
|||
void *buf, bool need_bswap) |
|||
{ |
|||
unsigned str_idx = shdr[sym_idx].sh_link; |
|||
ElfN(Sym) *sym = buf + shdr[sym_idx].sh_offset; |
|||
unsigned sym_n = shdr[sym_idx].sh_size / sizeof(*sym); |
|||
const char *str = buf + shdr[str_idx].sh_offset; |
|||
|
|||
for (unsigned i = 0; i < sym_n; ++i) { |
|||
const char *name; |
|||
|
|||
if (need_bswap) { |
|||
elfN(bswap_sym)(sym + i); |
|||
} |
|||
name = str + sym[i].st_name; |
|||
|
|||
if (sigreturn_sym && strcmp(sigreturn_sym, name) == 0) { |
|||
sigreturn_addr = sym[i].st_value; |
|||
} |
|||
if (rt_sigreturn_sym && strcmp(rt_sigreturn_sym, name) == 0) { |
|||
rt_sigreturn_addr = sym[i].st_value; |
|||
} |
|||
} |
|||
} |
|||
|
|||
static void elfN(process)(FILE *outf, void *buf, bool need_bswap) |
|||
{ |
|||
ElfN(Ehdr) *ehdr = buf; |
|||
ElfN(Phdr) *phdr; |
|||
ElfN(Shdr) *shdr; |
|||
unsigned phnum, shnum; |
|||
unsigned dynamic_ofs = 0; |
|||
unsigned dynamic_addr = 0; |
|||
unsigned symtab_idx = 0; |
|||
unsigned dynsym_idx = 0; |
|||
unsigned first_segsz = 0; |
|||
int errors = 0; |
|||
|
|||
if (need_bswap) { |
|||
elfN(bswap_ehdr)(ehdr); |
|||
} |
|||
|
|||
phnum = ehdr->e_phnum; |
|||
phdr = buf + ehdr->e_phoff; |
|||
if (need_bswap) { |
|||
for (unsigned i = 0; i < phnum; ++i) { |
|||
elfN(bswap_phdr)(phdr + i); |
|||
} |
|||
} |
|||
|
|||
shnum = ehdr->e_shnum; |
|||
shdr = buf + ehdr->e_shoff; |
|||
if (need_bswap) { |
|||
for (unsigned i = 0; i < shnum; ++i) { |
|||
elfN(bswap_shdr)(shdr + i); |
|||
} |
|||
} |
|||
for (unsigned i = 0; i < shnum; ++i) { |
|||
switch (shdr[i].sh_type) { |
|||
case SHT_SYMTAB: |
|||
symtab_idx = i; |
|||
break; |
|||
case SHT_DYNSYM: |
|||
dynsym_idx = i; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
/* |
|||
* Validate the VDSO is created as we expect: that PT_PHDR, |
|||
* PT_DYNAMIC, and PT_NOTE located in a writable data segment. |
|||
* PHDR and DYNAMIC require relocation, and NOTE will get the |
|||
* linux version number. |
|||
*/ |
|||
for (unsigned i = 0; i < phnum; ++i) { |
|||
if (phdr[i].p_type != PT_LOAD) { |
|||
continue; |
|||
} |
|||
if (first_segsz != 0) { |
|||
fprintf(stderr, "Multiple LOAD segments\n"); |
|||
errors++; |
|||
} |
|||
if (phdr[i].p_offset != 0) { |
|||
fprintf(stderr, "LOAD segment does not cover EHDR\n"); |
|||
errors++; |
|||
} |
|||
if (phdr[i].p_vaddr != 0) { |
|||
fprintf(stderr, "LOAD segment not loaded at address 0\n"); |
|||
errors++; |
|||
} |
|||
first_segsz = phdr[i].p_filesz; |
|||
if (first_segsz < ehdr->e_phoff + phnum * sizeof(*phdr)) { |
|||
fprintf(stderr, "LOAD segment does not cover PHDRs\n"); |
|||
errors++; |
|||
} |
|||
if ((phdr[i].p_flags & (PF_R | PF_W)) != (PF_R | PF_W)) { |
|||
fprintf(stderr, "LOAD segment is not read-write\n"); |
|||
errors++; |
|||
} |
|||
} |
|||
for (unsigned i = 0; i < phnum; ++i) { |
|||
const char *which; |
|||
|
|||
switch (phdr[i].p_type) { |
|||
case PT_PHDR: |
|||
which = "PT_PHDR"; |
|||
break; |
|||
case PT_NOTE: |
|||
which = "PT_NOTE"; |
|||
break; |
|||
case PT_DYNAMIC: |
|||
dynamic_ofs = phdr[i].p_offset; |
|||
dynamic_addr = phdr[i].p_vaddr; |
|||
which = "PT_DYNAMIC"; |
|||
break; |
|||
default: |
|||
continue; |
|||
} |
|||
if (first_segsz < phdr[i].p_vaddr + phdr[i].p_filesz) { |
|||
fprintf(stderr, "LOAD segment does not cover %s\n", which); |
|||
errors++; |
|||
} |
|||
} |
|||
if (errors) { |
|||
exit(EXIT_FAILURE); |
|||
} |
|||
|
|||
/* Relocate the program headers. */ |
|||
for (unsigned i = 0; i < phnum; ++i) { |
|||
output_reloc(outf, buf, &phdr[i].p_vaddr); |
|||
output_reloc(outf, buf, &phdr[i].p_paddr); |
|||
} |
|||
|
|||
/* Relocate the DYNAMIC entries. */ |
|||
if (dynamic_addr) { |
|||
ElfN(Dyn) *dyn = buf + dynamic_ofs; |
|||
__typeof(dyn->d_tag) tag; |
|||
|
|||
do { |
|||
|
|||
if (need_bswap) { |
|||
elfN(bswap_dyn)(dyn); |
|||
} |
|||
tag = dyn->d_tag; |
|||
|
|||
switch (tag) { |
|||
case DT_HASH: |
|||
case DT_SYMTAB: |
|||
case DT_STRTAB: |
|||
case DT_VERDEF: |
|||
case DT_VERSYM: |
|||
case DT_PLTGOT: |
|||
case DT_ADDRRNGLO ... DT_ADDRRNGHI: |
|||
/* These entries store an address in the entry. */ |
|||
output_reloc(outf, buf, &dyn->d_un.d_val); |
|||
break; |
|||
|
|||
case DT_NULL: |
|||
case DT_STRSZ: |
|||
case DT_SONAME: |
|||
case DT_DEBUG: |
|||
case DT_FLAGS: |
|||
case DT_FLAGS_1: |
|||
case DT_SYMBOLIC: |
|||
case DT_BIND_NOW: |
|||
case DT_VERDEFNUM: |
|||
case DT_VALRNGLO ... DT_VALRNGHI: |
|||
/* These entries store an integer in the entry. */ |
|||
break; |
|||
|
|||
case DT_SYMENT: |
|||
if (dyn->d_un.d_val != sizeof(ElfN(Sym))) { |
|||
fprintf(stderr, "VDSO has incorrect dynamic symbol size\n"); |
|||
errors++; |
|||
} |
|||
break; |
|||
|
|||
case DT_REL: |
|||
case DT_RELSZ: |
|||
case DT_RELA: |
|||
case DT_RELASZ: |
|||
/* |
|||
* These entries indicate that the VDSO was built incorrectly. |
|||
* It should not have any real relocations. |
|||
* ??? The RISC-V toolchain will emit these even when there |
|||
* are no relocations. Validate zeros. |
|||
*/ |
|||
if (dyn->d_un.d_val != 0) { |
|||
fprintf(stderr, "VDSO has dynamic relocations\n"); |
|||
errors++; |
|||
} |
|||
break; |
|||
case DT_RELENT: |
|||
case DT_RELAENT: |
|||
case DT_TEXTREL: |
|||
/* These entries store an integer in the entry. */ |
|||
/* Should not be required; see above. */ |
|||
break; |
|||
|
|||
case DT_NEEDED: |
|||
case DT_VERNEED: |
|||
case DT_PLTREL: |
|||
case DT_JMPREL: |
|||
case DT_RPATH: |
|||
case DT_RUNPATH: |
|||
fprintf(stderr, "VDSO has external dependencies\n"); |
|||
errors++; |
|||
break; |
|||
|
|||
default: |
|||
/* This is probably something target specific. */ |
|||
fprintf(stderr, "VDSO has unknown DYNAMIC entry (%lx)\n", |
|||
(unsigned long)tag); |
|||
errors++; |
|||
break; |
|||
} |
|||
dyn++; |
|||
} while (tag != DT_NULL); |
|||
if (errors) { |
|||
exit(EXIT_FAILURE); |
|||
} |
|||
} |
|||
|
|||
/* Relocate the dynamic symbol table. */ |
|||
if (dynsym_idx) { |
|||
ElfN(Sym) *sym = buf + shdr[dynsym_idx].sh_offset; |
|||
unsigned sym_n = shdr[dynsym_idx].sh_size / sizeof(*sym); |
|||
|
|||
for (unsigned i = 0; i < sym_n; ++i) { |
|||
output_reloc(outf, buf, &sym[i].st_value); |
|||
} |
|||
} |
|||
|
|||
/* Search both dynsym and symtab for the signal return symbols. */ |
|||
if (dynsym_idx) { |
|||
elfN(search_symtab)(shdr, dynsym_idx, buf, need_bswap); |
|||
} |
|||
if (symtab_idx) { |
|||
elfN(search_symtab)(shdr, symtab_idx, buf, need_bswap); |
|||
} |
|||
} |
|||
@ -0,0 +1,223 @@ |
|||
/*
|
|||
* 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; |
|||
|
|||
#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, *outf; |
|||
long total_len; |
|||
const char *prefix = "vdso"; |
|||
const char *inf_name; |
|||
const char *outf_name = NULL; |
|||
unsigned char *buf; |
|||
bool need_bswap; |
|||
|
|||
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. |
|||
*/ |
|||
fseek(inf, 0, SEEK_END); |
|||
total_len = ftell(inf); |
|||
fseek(inf, 0, SEEK_SET); |
|||
|
|||
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; |
|||
} |
|||
fclose(inf); |
|||
|
|||
/*
|
|||
* Write out the vdso image now, before we make local changes. |
|||
*/ |
|||
|
|||
fprintf(outf, |
|||
"/* Automatically generated from linux-user/gen-vdso.c. */\n" |
|||
"\n" |
|||
"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"); |
|||
|
|||
/*
|
|||
* 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, "static const unsigned %s_relocs[] = {\n", prefix); |
|||
|
|||
switch (buf[EI_CLASS]) { |
|||
case ELFCLASS32: |
|||
elf32_process(outf, buf, need_bswap); |
|||
break; |
|||
case ELFCLASS64: |
|||
elf64_process(outf, buf, 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. */ |
|||
|
|||
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, "};\n"); |
|||
|
|||
/*
|
|||
* Everything should have gone well. |
|||
*/ |
|||
if (fclose(outf)) { |
|||
goto perror_outf; |
|||
} |
|||
return EXIT_SUCCESS; |
|||
|
|||
perror_inf: |
|||
perror(inf_name); |
|||
return EXIT_FAILURE; |
|||
|
|||
perror_outf: |
|||
perror(outf_name); |
|||
return EXIT_FAILURE; |
|||
} |
|||
Loading…
Reference in new issue