linux-user: Add gen-vdso tool

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>
This commit is contained in:
Richard Henderson 2021-06-17 14:30:09 -07:00
parent c40f621a19
commit 2fa536d107
3 changed files with 535 additions and 1 deletions

View File

@ -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);
}
}

223
linux-user/gen-vdso.c Normal file
View File

@ -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;
}

View File

@ -28,9 +28,13 @@ linux_user_ss.add(when: 'TARGET_HAS_BFLT', if_true: files('flatload.c'))
linux_user_ss.add(when: 'TARGET_I386', if_true: files('vm86.c')) linux_user_ss.add(when: 'TARGET_I386', if_true: files('vm86.c'))
linux_user_ss.add(when: 'CONFIG_ARM_COMPATIBLE_SEMIHOSTING', if_true: files('semihost.c')) linux_user_ss.add(when: 'CONFIG_ARM_COMPATIBLE_SEMIHOSTING', if_true: files('semihost.c'))
syscall_nr_generators = {} syscall_nr_generators = {}
gen_vdso_exe = executable('gen-vdso', 'gen-vdso.c',
native: true, build_by_default: false)
gen_vdso = generator(gen_vdso_exe, output: '@BASENAME@.c.inc',
arguments: ['-o', '@OUTPUT@', '@EXTRA_ARGS@', '@INPUT@'])
subdir('alpha') subdir('alpha')
subdir('arm') subdir('arm')
subdir('hppa') subdir('hppa')