| /* | 
 |  * 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; | 
 |  | 
 |             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; | 
 |             } | 
 |             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); | 
 |     } | 
 | } |