| 1 | /* |
| 2 | * Post-process a vdso elf image for inclusion into qemu. |
| 3 | * Elf size specialization. |
| 4 | * |
| 5 | * Copyright 2023 Linaro, Ltd. |
| 6 | * |
| 7 | * SPDX-License-Identifier: GPL-2.0-or-later |
| 8 | */ |
| 9 | |
| 10 | static void elfN(bswap_ehdr)(ElfN(Ehdr) *ehdr) |
| 11 | { |
| 12 | bswaps(&ehdr->e_type); /* Object file type */ |
| 13 | bswaps(&ehdr->e_machine); /* Architecture */ |
| 14 | bswaps(&ehdr->e_version); /* Object file version */ |
| 15 | bswaps(&ehdr->e_entry); /* Entry point virtual address */ |
| 16 | bswaps(&ehdr->e_phoff); /* Program header table file offset */ |
| 17 | bswaps(&ehdr->e_shoff); /* Section header table file offset */ |
| 18 | bswaps(&ehdr->e_flags); /* Processor-specific flags */ |
| 19 | bswaps(&ehdr->e_ehsize); /* ELF header size in bytes */ |
| 20 | bswaps(&ehdr->e_phentsize); /* Program header table entry size */ |
| 21 | bswaps(&ehdr->e_phnum); /* Program header table entry count */ |
| 22 | bswaps(&ehdr->e_shentsize); /* Section header table entry size */ |
| 23 | bswaps(&ehdr->e_shnum); /* Section header table entry count */ |
| 24 | bswaps(&ehdr->e_shstrndx); /* Section header string table index */ |
| 25 | } |
| 26 | |
| 27 | static void elfN(bswap_phdr)(ElfN(Phdr) *phdr) |
| 28 | { |
| 29 | bswaps(&phdr->p_type); /* Segment type */ |
| 30 | bswaps(&phdr->p_flags); /* Segment flags */ |
| 31 | bswaps(&phdr->p_offset); /* Segment file offset */ |
| 32 | bswaps(&phdr->p_vaddr); /* Segment virtual address */ |
| 33 | bswaps(&phdr->p_paddr); /* Segment physical address */ |
| 34 | bswaps(&phdr->p_filesz); /* Segment size in file */ |
| 35 | bswaps(&phdr->p_memsz); /* Segment size in memory */ |
| 36 | bswaps(&phdr->p_align); /* Segment alignment */ |
| 37 | } |
| 38 | |
| 39 | static void elfN(bswap_shdr)(ElfN(Shdr) *shdr) |
| 40 | { |
| 41 | bswaps(&shdr->sh_name); |
| 42 | bswaps(&shdr->sh_type); |
| 43 | bswaps(&shdr->sh_flags); |
| 44 | bswaps(&shdr->sh_addr); |
| 45 | bswaps(&shdr->sh_offset); |
| 46 | bswaps(&shdr->sh_size); |
| 47 | bswaps(&shdr->sh_link); |
| 48 | bswaps(&shdr->sh_info); |
| 49 | bswaps(&shdr->sh_addralign); |
| 50 | bswaps(&shdr->sh_entsize); |
| 51 | } |
| 52 | |
| 53 | static void elfN(bswap_sym)(ElfN(Sym) *sym) |
| 54 | { |
| 55 | bswaps(&sym->st_name); |
| 56 | bswaps(&sym->st_value); |
| 57 | bswaps(&sym->st_size); |
| 58 | bswaps(&sym->st_shndx); |
| 59 | } |
| 60 | |
| 61 | static void elfN(bswap_dyn)(ElfN(Dyn) *dyn) |
| 62 | { |
| 63 | bswaps(&dyn->d_tag); /* Dynamic type tag */ |
| 64 | bswaps(&dyn->d_un.d_ptr); /* Dynamic ptr or val, in union */ |
| 65 | } |
| 66 | |
| 67 | static void elfN(search_symtab)(ElfN(Shdr) *shdr, unsigned sym_idx, |
| 68 | void *buf, bool need_bswap) |
| 69 | { |
| 70 | unsigned str_idx = shdr[sym_idx].sh_link; |
| 71 | ElfN(Sym) *target_sym = buf + shdr[sym_idx].sh_offset; |
| 72 | unsigned sym_n = shdr[sym_idx].sh_size / sizeof(*target_sym); |
| 73 | const char *str = buf + shdr[str_idx].sh_offset; |
| 74 | |
| 75 | for (unsigned i = 0; i < sym_n; ++i) { |
| 76 | const char *name; |
| 77 | ElfN(Sym) sym; |
| 78 | |
| 79 | memcpy(&sym, &target_sym[i], sizeof(sym)); |
| 80 | if (need_bswap) { |
| 81 | elfN(bswap_sym)(&sym); |
| 82 | } |
| 83 | name = str + sym.st_name; |
| 84 | |
| 85 | if (sigreturn_sym && strcmp(sigreturn_sym, name) == 0) { |
| 86 | sigreturn_addr = sym.st_value; |
| 87 | } else if (rt_sigreturn_sym && strcmp(rt_sigreturn_sym, name) == 0) { |
| 88 | rt_sigreturn_addr = sym.st_value; |
| 89 | } else if (strcmp("sigreturn_region_start", name) == 0) { |
| 90 | sigreturn_region_start_addr = sym.st_value; |
| 91 | } else if (strcmp("sigreturn_region_end", name) == 0) { |
| 92 | sigreturn_region_end_addr = sym.st_value; |
| 93 | } |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | static void elfN(bswap_ps_hdrs)(ElfN(Ehdr) *ehdr) |
| 98 | { |
| 99 | ElfN(Phdr) *phdr = (void *)ehdr + ehdr->e_phoff; |
| 100 | ElfN(Shdr) *shdr = (void *)ehdr + ehdr->e_shoff; |
| 101 | ElfN(Half) i; |
| 102 | |
| 103 | for (i = 0; i < ehdr->e_phnum; ++i) { |
| 104 | elfN(bswap_phdr)(&phdr[i]); |
| 105 | } |
| 106 | |
| 107 | for (i = 0; i < ehdr->e_shnum; ++i) { |
| 108 | elfN(bswap_shdr)(&shdr[i]); |
| 109 | } |
| 110 | } |
| 111 | |
| 112 | static void elfN(process)(FILE *outf, void *buf, long len, bool need_bswap) |
| 113 | { |
| 114 | ElfN(Ehdr) *ehdr = buf; |
| 115 | ElfN(Phdr) *phdr; |
| 116 | ElfN(Shdr) *shdr; |
| 117 | unsigned phnum, shnum; |
| 118 | unsigned dynamic_ofs = 0; |
| 119 | unsigned dynamic_addr = 0; |
| 120 | unsigned symtab_idx = 0; |
| 121 | unsigned dynsym_idx = 0; |
| 122 | unsigned first_segsz = 0; |
| 123 | int errors = 0; |
| 124 | |
| 125 | if (need_bswap) { |
| 126 | elfN(bswap_ehdr)(buf); |
| 127 | elfN(bswap_ps_hdrs)(buf); |
| 128 | } |
| 129 | |
| 130 | phnum = ehdr->e_phnum; |
| 131 | phdr = buf + ehdr->e_phoff; |
| 132 | shnum = ehdr->e_shnum; |
| 133 | shdr = buf + ehdr->e_shoff; |
| 134 | for (unsigned i = 0; i < shnum; ++i) { |
| 135 | switch (shdr[i].sh_type) { |
| 136 | case SHT_SYMTAB: |
| 137 | symtab_idx = i; |
| 138 | break; |
| 139 | case SHT_DYNSYM: |
| 140 | dynsym_idx = i; |
| 141 | break; |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | /* |
| 146 | * Validate the VDSO is created as we expect: that PT_PHDR, |
| 147 | * PT_DYNAMIC, and PT_NOTE located in a writable data segment. |
| 148 | * PHDR and DYNAMIC require relocation, and NOTE will get the |
| 149 | * linux version number. |
| 150 | */ |
| 151 | for (unsigned i = 0; i < phnum; ++i) { |
| 152 | if (phdr[i].p_type != PT_LOAD) { |
| 153 | continue; |
| 154 | } |
| 155 | if (first_segsz != 0) { |
| 156 | fprintf(stderr, "Multiple LOAD segments\n"); |
| 157 | errors++; |
| 158 | } |
| 159 | if (phdr[i].p_offset != 0) { |
| 160 | fprintf(stderr, "LOAD segment does not cover EHDR\n"); |
| 161 | errors++; |
| 162 | } |
| 163 | if (phdr[i].p_vaddr != 0) { |
| 164 | fprintf(stderr, "LOAD segment not loaded at address 0\n"); |
| 165 | errors++; |
| 166 | } |
| 167 | /* |
| 168 | * Extend the program header to cover the entire VDSO, so that |
| 169 | * load_elf_vdso() loads everything, including section headers. |
| 170 | * |
| 171 | * Require that there is no .bss, since it would break this |
| 172 | * approach. |
| 173 | */ |
| 174 | if (phdr[i].p_filesz != phdr[i].p_memsz) { |
| 175 | fprintf(stderr, "LOAD segment's filesz and memsz differ\n"); |
| 176 | errors++; |
| 177 | } |
| 178 | if (phdr[i].p_filesz > len) { |
| 179 | fprintf(stderr, "LOAD segment is larger than the whole VDSO\n"); |
| 180 | errors++; |
| 181 | } |
| 182 | phdr[i].p_filesz = len; |
| 183 | phdr[i].p_memsz = len; |
| 184 | first_segsz = len; |
| 185 | if (first_segsz < ehdr->e_phoff + phnum * sizeof(*phdr)) { |
| 186 | fprintf(stderr, "LOAD segment does not cover PHDRs\n"); |
| 187 | errors++; |
| 188 | } |
| 189 | if ((phdr[i].p_flags & (PF_R | PF_W)) != (PF_R | PF_W)) { |
| 190 | fprintf(stderr, "LOAD segment is not read-write\n"); |
| 191 | errors++; |
| 192 | } |
| 193 | } |
| 194 | for (unsigned i = 0; i < phnum; ++i) { |
| 195 | const char *which; |
| 196 | |
| 197 | switch (phdr[i].p_type) { |
| 198 | case PT_PHDR: |
| 199 | which = "PT_PHDR"; |
| 200 | break; |
| 201 | case PT_NOTE: |
| 202 | which = "PT_NOTE"; |
| 203 | break; |
| 204 | case PT_DYNAMIC: |
| 205 | dynamic_ofs = phdr[i].p_offset; |
| 206 | dynamic_addr = phdr[i].p_vaddr; |
| 207 | which = "PT_DYNAMIC"; |
| 208 | break; |
| 209 | default: |
| 210 | continue; |
| 211 | } |
| 212 | if (first_segsz < phdr[i].p_vaddr + phdr[i].p_filesz) { |
| 213 | fprintf(stderr, "LOAD segment does not cover %s\n", which); |
| 214 | errors++; |
| 215 | } |
| 216 | } |
| 217 | if (errors) { |
| 218 | exit(EXIT_FAILURE); |
| 219 | } |
| 220 | |
| 221 | /* Relocate the program headers. */ |
| 222 | for (unsigned i = 0; i < phnum; ++i) { |
| 223 | output_reloc(outf, buf, &phdr[i].p_vaddr); |
| 224 | output_reloc(outf, buf, &phdr[i].p_paddr); |
| 225 | } |
| 226 | |
| 227 | /* Relocate the section headers. */ |
| 228 | for (unsigned i = 0; i < shnum; ++i) { |
| 229 | output_reloc(outf, buf, &shdr[i].sh_addr); |
| 230 | } |
| 231 | |
| 232 | /* Relocate the DYNAMIC entries. */ |
| 233 | if (dynamic_addr) { |
| 234 | ElfN(Dyn) *target_dyn = buf + dynamic_ofs; |
| 235 | __typeof(((ElfN(Dyn) *)target_dyn)->d_tag) tag; |
| 236 | |
| 237 | do { |
| 238 | ElfN(Dyn) dyn; |
| 239 | |
| 240 | memcpy(&dyn, target_dyn, sizeof(dyn)); |
| 241 | if (need_bswap) { |
| 242 | elfN(bswap_dyn)(&dyn); |
| 243 | } |
| 244 | tag = dyn.d_tag; |
| 245 | |
| 246 | switch (tag) { |
| 247 | case DT_HASH: |
| 248 | case DT_SYMTAB: |
| 249 | case DT_STRTAB: |
| 250 | case DT_VERDEF: |
| 251 | case DT_VERSYM: |
| 252 | case DT_PLTGOT: |
| 253 | case DT_ADDRRNGLO ... DT_ADDRRNGHI: |
| 254 | /* These entries store an address in the entry. */ |
| 255 | output_reloc(outf, buf, &target_dyn->d_un.d_val); |
| 256 | break; |
| 257 | |
| 258 | case DT_NULL: |
| 259 | case DT_STRSZ: |
| 260 | case DT_SONAME: |
| 261 | case DT_DEBUG: |
| 262 | case DT_FLAGS: |
| 263 | case DT_FLAGS_1: |
| 264 | case DT_SYMBOLIC: |
| 265 | case DT_BIND_NOW: |
| 266 | case DT_VERDEFNUM: |
| 267 | case DT_VALRNGLO ... DT_VALRNGHI: |
| 268 | /* These entries store an integer in the entry. */ |
| 269 | break; |
| 270 | |
| 271 | case DT_SYMENT: |
| 272 | if (dyn.d_un.d_val != sizeof(ElfN(Sym))) { |
| 273 | fprintf(stderr, "VDSO has incorrect dynamic symbol size\n"); |
| 274 | errors++; |
| 275 | } |
| 276 | break; |
| 277 | |
| 278 | case DT_REL: |
| 279 | case DT_RELSZ: |
| 280 | case DT_RELA: |
| 281 | case DT_RELASZ: |
| 282 | /* |
| 283 | * These entries indicate that the VDSO was built incorrectly. |
| 284 | * It should not have any real relocations. |
| 285 | * ??? The RISC-V toolchain will emit these even when there |
| 286 | * are no relocations. Validate zeros. |
| 287 | */ |
| 288 | if (dyn.d_un.d_val != 0) { |
| 289 | fprintf(stderr, "VDSO has dynamic relocations\n"); |
| 290 | errors++; |
| 291 | } |
| 292 | break; |
| 293 | case DT_RELENT: |
| 294 | case DT_RELAENT: |
| 295 | case DT_TEXTREL: |
| 296 | /* These entries store an integer in the entry. */ |
| 297 | /* Should not be required; see above. */ |
| 298 | break; |
| 299 | |
| 300 | case DT_NEEDED: |
| 301 | case DT_VERNEED: |
| 302 | case DT_PLTREL: |
| 303 | case DT_JMPREL: |
| 304 | case DT_RPATH: |
| 305 | case DT_RUNPATH: |
| 306 | fprintf(stderr, "VDSO has external dependencies\n"); |
| 307 | errors++; |
| 308 | break; |
| 309 | |
| 310 | case PT_LOPROC + 3: |
| 311 | if (ehdr->e_machine == EM_PPC64) { |
| 312 | break; /* DT_PPC64_OPT: integer bitmask */ |
| 313 | } |
| 314 | goto do_default; |
| 315 | |
| 316 | default: |
| 317 | do_default: |
| 318 | /* This is probably something target specific. */ |
| 319 | fprintf(stderr, "VDSO has unknown DYNAMIC entry (%lx)\n", |
| 320 | (unsigned long)tag); |
| 321 | errors++; |
| 322 | break; |
| 323 | } |
| 324 | target_dyn++; |
| 325 | } while (tag != DT_NULL); |
| 326 | if (errors) { |
| 327 | exit(EXIT_FAILURE); |
| 328 | } |
| 329 | } |
| 330 | |
| 331 | /* Relocate the dynamic symbol table. */ |
| 332 | if (dynsym_idx) { |
| 333 | ElfN(Sym) *target_sym = buf + shdr[dynsym_idx].sh_offset; |
| 334 | unsigned sym_n = shdr[dynsym_idx].sh_size / sizeof(*target_sym); |
| 335 | |
| 336 | for (unsigned i = 0; i < sym_n; ++i) { |
| 337 | output_reloc(outf, buf, &target_sym[i].st_value); |
| 338 | } |
| 339 | } |
| 340 | |
| 341 | /* Search both dynsym and symtab for the signal return symbols. */ |
| 342 | if (dynsym_idx) { |
| 343 | elfN(search_symtab)(shdr, dynsym_idx, buf, need_bswap); |
| 344 | } |
| 345 | if (symtab_idx) { |
| 346 | elfN(search_symtab)(shdr, symtab_idx, buf, need_bswap); |
| 347 | } |
| 348 | |
| 349 | if (need_bswap) { |
| 350 | elfN(bswap_ps_hdrs)(buf); |
| 351 | elfN(bswap_ehdr)(buf); |
| 352 | } |
| 353 | } |