| /* |
| * 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) *target_sym = buf + shdr[sym_idx].sh_offset; |
| unsigned sym_n = shdr[sym_idx].sh_size / sizeof(*target_sym); |
| const char *str = buf + shdr[str_idx].sh_offset; |
| |
| for (unsigned i = 0; i < sym_n; ++i) { |
| const char *name; |
| ElfN(Sym) sym; |
| |
| memcpy(&sym, &target_sym[i], sizeof(sym)); |
| if (need_bswap) { |
| elfN(bswap_sym)(&sym); |
| } |
| name = str + sym.st_name; |
| |
| if (sigreturn_sym && strcmp(sigreturn_sym, name) == 0) { |
| sigreturn_addr = sym.st_value; |
| } |
| if (rt_sigreturn_sym && strcmp(rt_sigreturn_sym, name) == 0) { |
| rt_sigreturn_addr = sym.st_value; |
| } |
| } |
| } |
| |
| static void elfN(bswap_ps_hdrs)(ElfN(Ehdr) *ehdr) |
| { |
| ElfN(Phdr) *phdr = (void *)ehdr + ehdr->e_phoff; |
| ElfN(Shdr) *shdr = (void *)ehdr + ehdr->e_shoff; |
| ElfN(Half) i; |
| |
| for (i = 0; i < ehdr->e_phnum; ++i) { |
| elfN(bswap_phdr)(&phdr[i]); |
| } |
| |
| for (i = 0; i < ehdr->e_shnum; ++i) { |
| elfN(bswap_shdr)(&shdr[i]); |
| } |
| } |
| |
| static void elfN(process)(FILE *outf, void *buf, long len, 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)(buf); |
| elfN(bswap_ps_hdrs)(buf); |
| } |
| |
| phnum = ehdr->e_phnum; |
| phdr = buf + ehdr->e_phoff; |
| shnum = ehdr->e_shnum; |
| shdr = buf + ehdr->e_shoff; |
| 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++; |
| } |
| /* |
| * Extend the program header to cover the entire VDSO, so that |
| * load_elf_vdso() loads everything, including section headers. |
| * |
| * Require that there is no .bss, since it would break this |
| * approach. |
| */ |
| if (phdr[i].p_filesz != phdr[i].p_memsz) { |
| fprintf(stderr, "LOAD segment's filesz and memsz differ\n"); |
| errors++; |
| } |
| if (phdr[i].p_filesz > len) { |
| fprintf(stderr, "LOAD segment is larger than the whole VDSO\n"); |
| errors++; |
| } |
| phdr[i].p_filesz = len; |
| phdr[i].p_memsz = len; |
| first_segsz = len; |
| 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 section headers. */ |
| for (unsigned i = 0; i < shnum; ++i) { |
| output_reloc(outf, buf, &shdr[i].sh_addr); |
| } |
| |
| /* Relocate the DYNAMIC entries. */ |
| if (dynamic_addr) { |
| ElfN(Dyn) *target_dyn = buf + dynamic_ofs; |
| __typeof(((ElfN(Dyn) *)target_dyn)->d_tag) tag; |
| |
| do { |
| ElfN(Dyn) dyn; |
| |
| memcpy(&dyn, target_dyn, sizeof(dyn)); |
| 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, &target_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; |
| |
| case PT_LOPROC + 3: |
| if (ehdr->e_machine == EM_PPC64) { |
| break; /* DT_PPC64_OPT: integer bitmask */ |
| } |
| goto do_default; |
| |
| default: |
| do_default: |
| /* This is probably something target specific. */ |
| fprintf(stderr, "VDSO has unknown DYNAMIC entry (%lx)\n", |
| (unsigned long)tag); |
| errors++; |
| break; |
| } |
| target_dyn++; |
| } while (tag != DT_NULL); |
| if (errors) { |
| exit(EXIT_FAILURE); |
| } |
| } |
| |
| /* Relocate the dynamic symbol table. */ |
| if (dynsym_idx) { |
| ElfN(Sym) *target_sym = buf + shdr[dynsym_idx].sh_offset; |
| unsigned sym_n = shdr[dynsym_idx].sh_size / sizeof(*target_sym); |
| |
| for (unsigned i = 0; i < sym_n; ++i) { |
| output_reloc(outf, buf, &target_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); |
| } |
| |
| if (need_bswap) { |
| elfN(bswap_ps_hdrs)(buf); |
| elfN(bswap_ehdr)(buf); |
| } |
| } |