diff options
Diffstat (limited to 'tools/com-plugin/plugin.c')
| -rw-r--r-- | tools/com-plugin/plugin.c | 831 |
1 files changed, 831 insertions, 0 deletions
diff --git a/tools/com-plugin/plugin.c b/tools/com-plugin/plugin.c new file mode 100644 index 000000000..cd1fa0a53 --- /dev/null +++ b/tools/com-plugin/plugin.c @@ -0,0 +1,831 @@ +/** + * COMMON symbol ordering plugin for linkers supporting the External Linker Plugin API. + * + * Copyright (C) 2025 Tharo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +#include <assert.h> +#include <ctype.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> + +#include "endian.h" +#include "elf.h" +#include "plugin-api.h" + +#define ELF32_IDENT_VALID(ehdr) \ + ((ehdr)->e_ident[EI_MAG0] == ELFMAG0 && (ehdr)->e_ident[EI_MAG1] == ELFMAG1 && \ + (ehdr)->e_ident[EI_MAG2] == ELFMAG2 && (ehdr)->e_ident[EI_MAG3] == ELFMAG3) + +#define ELF32_IS_32(ehdr) ((ehdr)->e_ident[EI_CLASS] == ELFCLASS32) + +#define ELF32_IS_BE(ehdr) ((ehdr)->e_ident[EI_DATA] == ELFDATA2MSB) + +#define iswhitespace(c) (isblank(c) || ((c) == '\n')) + +#define GLUE2(a,b) a##b +#define GLUE(a,b) GLUE2(a,b) +#define ARRLEN(a) (sizeof(a) / sizeof((a)[0])) + +#define BITSET_TYPE uint64_t +#define BITSET_MASK (8 * sizeof(BITSET_TYPE) - 1) +#define BITSET_SHIFT (__builtin_ctz((uint32_t)~BITSET_MASK)) +#define BITSET_SIZE_BYTES(n) ((((n) + 8 * sizeof(BITSET_TYPE) - 1) & ~(8 * sizeof(BITSET_TYPE) - 1)) >> 3) +#define BITSET_ALLOC(size) calloc(1, BITSET_SIZE_BYTES(size)) +#define BITSET_FREE(set) free(set) +#define BITSET_GET(set, key) ((set)[(key) >> BITSET_SHIFT] & ((BITSET_TYPE)1 << ((key) & BITSET_MASK))) +#define BITSET_SET(set, key) ((set)[(key) >> BITSET_SHIFT] |= ((BITSET_TYPE)1 << ((key) & BITSET_MASK))) + +#define strequ(s1, s2const) (strncmp(s1, s2const, sizeof(s2const) - 1) == 0) + +struct bss_ofile { + char *path; + size_t num_symbols; + char **symbol_names; + Elf32_Sym *symbols; + BITSET_TYPE *symbols_found; +}; + +static struct { + // Our state + char *string_pool; + struct bss_ofile *ofiles; + size_t num_ofiles; + // Our options + char *order_path; + // LD plugin + int api_version; + int gnu_ld_version; + int linker_output; + const char *output_name; + ld_plugin_message message; + ld_plugin_add_symbols add_symbols; + ld_plugin_get_symbols get_symbols; + ld_plugin_get_symbols get_symbols_v2; + ld_plugin_add_input_file add_input_file; + ld_plugin_get_input_file get_input_file; + ld_plugin_register_cleanup register_cleanup; + ld_plugin_register_claim_file register_claim_file; + ld_plugin_register_all_symbols_read register_all_symbols_read; +} pl; + +#define ltell(fd) lseek((fd), 0, SEEK_CUR) + +static int read_s_(int fd, void *buf, size_t nbyte, int line) +{ + ssize_t result = read(fd, buf, nbyte); + if ((size_t)result != nbyte) { + pl.message(LDPL_FATAL, "[%d] Could not read %lu bytes from input file [%ld]", line, nbyte, result); + return 1; + } + return 0; +} +#define read_s(fd, buf, nbyte) \ + do { if (read_s_(fd, buf, nbyte, __LINE__)) return LDPS_ERR; } while (0) + +#define SEEKF(file, pos) \ + lseek((file)->fd, (file)->offset + (pos), SEEK_SET) + +#define WITH_NEW_FILE_POS(file, pos) \ + for (off_t GLUE(_o_,__LINE__) = ltell((file)->fd),GLUE(_b_,__LINE__)=(SEEKF(file, pos), 1); \ + GLUE(_b_,__LINE__); \ + GLUE(_b_,__LINE__)=0,lseek((file)->fd, GLUE(_o_,__LINE__), SEEK_SET)) + +static bool +elf_syms_equal(Elf32_Sym *sym1, Elf32_Sym *sym2) +{ + return (sym1->st_value == sym2->st_value) && + (sym1->st_size == sym2->st_size) && + (sym1->st_info == sym2->st_info) && + (sym1->st_other == sym2->st_other) && + (sym1->st_shndx == sym2->st_shndx); +} + +static enum ld_plugin_status +claim_file(const struct ld_plugin_input_file *file, int *claimed) +{ + *claimed = false; + + // Read the input file for COMMON symbol definitions + // The input is assumed to be a Big-Endian 32-bit ELF file + + bool error = false; + + WITH_NEW_FILE_POS(file, 0) { + Elf32_Ehdr ehdr; + read_s(file->fd, &ehdr, sizeof(ehdr)); + + if (!ELF32_IDENT_VALID(&ehdr) || !ELF32_IS_32(&ehdr) || !ELF32_IS_BE(&ehdr)) { + pl.message(LDPL_FATAL, "Input file %s is not a 32-bit Big-Endian ELF file", file->name); + return LDPS_ERR; + } + + uint32_t e_shoff = be32toh(ehdr.e_shoff); + uint16_t e_shentsize = be16toh(ehdr.e_shentsize); + uint16_t e_shnum = be16toh(ehdr.e_shnum); + + if (e_shentsize != sizeof(Elf32_Shdr)) { + pl.message(LDPL_FATAL, "e_shentsize unexpected size"); + return LDPS_ERR; + } + + Elf32_Shdr strtab; + char *strtab_data = NULL; + size_t last_strtab_ind = (size_t)-1; + + // Look for symbol tables to search for COMMON symbols in + SEEKF(file, e_shoff); + for (size_t i = 0; i < e_shnum; i++) { + Elf32_Shdr shdr; + read_s(file->fd, &shdr, sizeof(shdr)); + + uint32_t sh_type = be32toh(shdr.sh_type); + + if (sh_type != SHT_SYMTAB) + continue; + + // Found a symtab + uint32_t sh_offset = be32toh(shdr.sh_offset); + uint32_t sh_size = be32toh(shdr.sh_size); + + // Read strtab for this symtab, cache it + size_t strtab_ind = be32toh(shdr.sh_link); + if (strtab_ind != last_strtab_ind) { + if (strtab_data != NULL) + free(strtab_data); + + // Read strtab header + WITH_NEW_FILE_POS(file, e_shoff + strtab_ind * sizeof(Elf32_Shdr)) { + read_s(file->fd, &strtab, sizeof(strtab)); + } + + // Read strtab contents + uint32_t strtab_offset = be32toh(strtab.sh_offset); + uint32_t strtab_size = be32toh(strtab.sh_size); + strtab_data = malloc(strtab_size); + WITH_NEW_FILE_POS(file, strtab_offset) { + read_s(file->fd, strtab_data, strtab_size); + } + + last_strtab_ind = strtab_ind; + } + + // Read each symbol, save the COMMON symbols into a list + WITH_NEW_FILE_POS(file, sh_offset) { + size_t nsym = sh_size / sizeof(Elf32_Sym); + + for (size_t j = 0; j < nsym; j++) { + Elf32_Sym elfsym; + read_s(file->fd, &elfsym, sizeof(elfsym)); + + if (be16toh(elfsym.st_shndx) != SHN_COMMON) + continue; // Skip non-COMMON symbols + + const char *sym_name = &strtab_data[be32toh(elfsym.st_name)]; + + // Try to find this symbol in one of the specified bss output files + for (size_t f = 0; f < pl.num_ofiles; f++) { + struct bss_ofile *ofile = &pl.ofiles[f]; + + for (size_t k = 0; k < ofile->num_symbols; k++) { + if (strcmp(sym_name, ofile->symbol_names[k]) == 0) { + if (BITSET_GET(ofile->symbols_found, k)) { + // Already occupied, check equivalence + if (!elf_syms_equal(&ofile->symbols[k], &elfsym)) { + pl.message(LDPL_ERROR, "Found distinct COMMON symbols with the same name: %s", + ofile->symbol_names[k]); + error = true; + } + } else { + BITSET_SET(ofile->symbols_found, k); + ofile->symbols[k] = elfsym; + } + goto found; + } + } + } + pl.message(LDPL_WARNING, + "Found COMMON symbol %s in input file %s not mentioned in the order spec", + sym_name, file->name); + found:; + } + } + } + if (strtab_data != NULL) + free(strtab_data); + } + return (error) ? LDPS_ERR : LDPS_OK; +} + +static enum ld_plugin_status +all_symbols_read(void) +{ + for (size_t f = 0; f < pl.num_ofiles; f++) { + struct bss_ofile *ofile = &pl.ofiles[f]; + + // Make sure we got every symbol mentioned + bool error = false; + for (size_t i = 0; i < ofile->num_symbols; i++) { + if (!BITSET_GET(ofile->symbols_found, i)) { + pl.message(LDPL_ERROR, "Did not find symbol %s mentioned in the order specification in any input file", + ofile->symbol_names[i]); + error = true; + } + } + if (error) + return LDPS_ERR; + + // Create a dummy ELF file full of bss definitions generated from the COMMON defs and add it as an input file. + // TODO look into replacing all this with pl.add_symbols? Seems like it's not possible + // to add symbols at this stage though, since pl.add_symbols requires a file handle.. + // ELF layout: + // Elf32_Ehdr ehdr + // char symbol_names[sum of name lengths] + // Elf32_Sym symbols[nsyms] + // char section_names[sum of name lengths] + // Elf32_Shdr NULL + // Elf32_Shdr .bss + // Elf32_Shdr symtab + // Elf32_Shdr strtab + // Elf32_Shdr shstrtab + + FILE *elf = fopen(ofile->path, "wb"); + if (elf == NULL) { + pl.message(LDPL_FATAL, "Could not open bss output file \"%s\" for writing", ofile->path); + return LDPS_ERR; + } + + // Skip the ELF header, we'll write it at the end. + fseek(elf, sizeof(Elf32_Ehdr), SEEK_SET); + + // First write the strtab + uint32_t *string_offsets = malloc((2 + ofile->num_symbols) * sizeof(uint32_t)); + size_t strtab_offset = ftell(elf); + size_t n = 0; + string_offsets[n++] = ftell(elf) - strtab_offset; + fprintf(elf, "%c", '\0'); + string_offsets[n++] = ftell(elf) - strtab_offset; + fprintf(elf, "%s%c", ".bss", '\0'); + for (size_t i = 0; i < ofile->num_symbols; i++) { + string_offsets[n++] = ftell(elf) - strtab_offset; + fprintf(elf, "%s%c", ofile->symbol_names[i], '\0'); + } + size_t strtab_size = ftell(elf) - strtab_offset; + + // Align to 4 for symbols + fwrite("\0\0\0\0", 4 - (ftell(elf) & 3), 1, elf); + + // Record greatest alignment for the overall section alignment + size_t greatest_align = 0; + + // Now write symbols + uint32_t symtab_offset = ftell(elf); + n = 0; + + Elf32_Sym null_sym = { + .st_name = htobe32(string_offsets[n++]), + .st_value = 0, + .st_size = 0, + .st_info = ELF32_ST_INFO(STB_LOCAL, STT_NOTYPE), + .st_other = ELF32_ST_VISIBILITY(STV_DEFAULT), + .st_shndx = htobe16(SHN_UNDEF), + }; + fwrite(&null_sym, sizeof(null_sym), 1, elf); + Elf32_Sym bss_sym = { + .st_name = htobe32(string_offsets[n++]), + .st_value = 0, + .st_size = 0, + .st_info = ELF32_ST_INFO(STB_LOCAL, STT_SECTION), + .st_other = ELF32_ST_VISIBILITY(STV_DEFAULT), + .st_shndx = htobe16(1), /* .bss */ + }; + fwrite(&bss_sym, sizeof(bss_sym), 1, elf); + + size_t sym_offset = 0; + for (size_t i = 0; i < ofile->num_symbols; i++) { + Elf32_Sym *sym = &ofile->symbols[i]; + uint32_t align = be32toh(sym->st_value); + sym_offset = (sym_offset + align - 1) & ~(align - 1); + + if (align > greatest_align) + greatest_align = align; + + Elf32_Sym outsym = { + .st_name = htobe32(string_offsets[n++]), + .st_value = htobe32(sym_offset), + .st_size = sym->st_size, + .st_info = sym->st_info, + .st_other = sym->st_other, + .st_shndx = htobe16(1), /* .bss */ + }; + fwrite(&outsym, sizeof(outsym), 1, elf); + + sym_offset += be32toh(sym->st_size); + } + sym_offset = (sym_offset + greatest_align - 1) & ~(greatest_align - 1); + + size_t symtab_size = ftell(elf) - symtab_offset; + + free(string_offsets); + + const uint32_t shdr_name_offsets[5] = { 0, 1, 6, 13, 20 }; + size_t shstrtab_offset = ftell(elf); + // Write shstrtab + fwrite( + /* NULL [ 0] */ "\0" + /* .bss [ 1] */ ".bss\0" + /* symtab [ 6] */ "symtab\0" + /* strtab [13] */ "strtab\0" + /* shstrtab [20] */ "shstrtab\0" + /* [29] */ "\0\0\0", + 32, 1, elf + ); + size_t shstrtab_size = ftell(elf) - shstrtab_offset; + + // We need: { NULL, .bss, symtab, strtab, shstrtab } + Elf32_Shdr shdrs[5] = { + /* NULL */ { + .sh_name = htobe32(shdr_name_offsets[0]), + .sh_type = htobe32(SHT_NULL), + .sh_flags = htobe32(0), + .sh_addr = htobe32(0x00000000), + .sh_offset = htobe32(0x00000000), + .sh_size = htobe32(0x00000000), + .sh_link = htobe32(0), /* No Link */ + .sh_info = htobe32(0), + .sh_addralign = htobe32(0), + .sh_entsize = htobe32(0), /* Not a fixed-length array */ + }, + /* .bss */ { + .sh_name = htobe32(shdr_name_offsets[1]), + .sh_type = htobe32(SHT_NOBITS), + .sh_flags = htobe32(SHF_WRITE | SHF_ALLOC), + .sh_addr = htobe32(0x00000000), + .sh_offset = htobe32(sizeof(Elf32_Ehdr)), + .sh_size = htobe32(sym_offset), + .sh_link = htobe32(0), /* No Link */ + .sh_info = htobe32(0), + .sh_addralign = htobe32(greatest_align), + .sh_entsize = htobe32(0), /* Not a fixed-length array */ + }, + /* symtab */ { + .sh_name = htobe32(shdr_name_offsets[2]), + .sh_type = htobe32(SHT_SYMTAB), + .sh_flags = htobe32(0), + .sh_addr = htobe32(0), + .sh_offset = htobe32(symtab_offset), + .sh_size = htobe32(symtab_size), + .sh_link = htobe32(3), /* strtab */ + .sh_info = htobe32(2), + .sh_addralign = htobe32(4), + .sh_entsize = htobe32(sizeof(Elf32_Sym)), + }, + /* strtab */ { + .sh_name = htobe32(shdr_name_offsets[3]), + .sh_type = htobe32(SHT_STRTAB), + .sh_flags = htobe32(0), + .sh_addr = htobe32(0), + .sh_offset = htobe32(strtab_offset), + .sh_size = htobe32(strtab_size), + .sh_link = htobe32(0), /* No Link */ + .sh_info = htobe32(0), + .sh_addralign = htobe32(1), + .sh_entsize = htobe32(0), /* Not a fixed-length array */ + }, + /* shstrtab */ { + .sh_name = htobe32(shdr_name_offsets[4]), + .sh_type = htobe32(SHT_STRTAB), + .sh_flags = htobe32(0), + .sh_addr = htobe32(0), + .sh_offset = htobe32(shstrtab_offset), + .sh_size = htobe32(shstrtab_size), + .sh_link = htobe32(0), /* No Link */ + .sh_info = htobe32(0), + .sh_addralign = htobe32(1), + .sh_entsize = htobe32(0), /* Not a fixed-length array */ + }, + }; + size_t shdrs_offset = ftell(elf); + fwrite(shdrs, sizeof(shdrs), 1, elf); + + // TODO some of these fields should change based on the properties of the input files, or even better + // it should be based on the current output format but that might not be visible to us with the limited + // plugin api... + Elf32_Ehdr ehdr = { + .e_ident = { + [EI_MAG0] = ELFMAG0, + [EI_MAG1] = ELFMAG1, + [EI_MAG2] = ELFMAG2, + [EI_MAG3] = ELFMAG3, + [EI_CLASS] = ELFCLASS32, + [EI_DATA] = ELFDATA2MSB, + [EI_VERSION] = 1, + [EI_OSABI] = ELFOSABI_SYSV, + [EI_ABIVERSION] = 0, + }, + .e_type = htobe16(ET_REL), + .e_machine = htobe16(EM_MIPS), + .e_version = htobe32(EV_CURRENT), + .e_entry = htobe32(0x00000000), + .e_phoff = htobe32(0x00000000), + .e_shoff = htobe32(shdrs_offset), + .e_flags = htobe32(EF_MIPS_ARCH_3 | EF_MIPS_32BITMODE | EF_MIPS_NOREORDER), + .e_ehsize = htobe16(sizeof(Elf32_Ehdr)), + .e_phentsize = htobe16(0x0000), + .e_phnum = htobe16(0), + .e_shentsize = htobe16(sizeof(Elf32_Shdr)), + .e_shnum = htobe16(ARRLEN(shdrs)), + .e_shstrndx = htobe16(4), /* shstrtab */ + }; + fseek(elf, 0, SEEK_SET); + fwrite(&ehdr, sizeof(ehdr), 1, elf); + + fflush(elf); + fclose(elf); + + // Add it as an additional input file + enum ld_plugin_status ps = pl.add_input_file(ofile->path); + if (ps != LDPS_OK) { + pl.message(LDPL_ERROR, "error adding %s as an additional input file\n", ofile->path); + return ps; + } + } + return LDPS_OK; +} + +static enum ld_plugin_status +cleanup(void) +{ + free(pl.string_pool); + free(pl.order_path); + for (size_t f = 0; f < pl.num_ofiles; f++) { + struct bss_ofile *ofile = &pl.ofiles[f]; + free(ofile->symbol_names); + free(ofile->symbols); + BITSET_FREE(ofile->symbols_found); + } + free(pl.ofiles); + return LDPS_OK; +} + +static enum ld_plugin_status +parse_order_file(const char *order_file) +{ + // Read the entire bss order file and null-terminate it + FILE *bss_order = fopen(order_file, "rb"); + if (bss_order == NULL) { + pl.message(LDPL_FATAL, "Could not open bss order file %s for reading: %s", bss_order, strerror(errno)); + return LDPS_ERR; + } + + fseek(bss_order, 0, SEEK_END); + size_t fsize = ftell(bss_order); + fseek(bss_order, 0, SEEK_SET); + + pl.string_pool = malloc((fsize + 1) * sizeof(char)); + if (fread(pl.string_pool, fsize, 1, bss_order) != 1) { + pl.message(LDPL_FATAL, "Failed to read bss order file %s: %s", order_file, strerror(errno)); + free(pl.string_pool); + fclose(bss_order); + return LDPS_ERR; + } + pl.string_pool[fsize] = '\0'; + + fclose(bss_order); + + // Strip comments -> spaces, leaving newlines alone for correct error reporting + + int line = 1; + bool comment = false; + bool oneline = false; + char *s = pl.string_pool; + while (*s != '\0') { + if (*s == '\n') + line++; + + if (*s == '#' || (*s == '/' && s[1] == '/')) + comment = true, oneline = true; + else if (*s == '/' && s[1] == '*') + comment = true, oneline = false; + + if (oneline && *s == '\n') + comment = false; + + if (comment && !oneline && *s == '*' && s[1] == '/') + comment = false, s[0] = s[1] = ' '; + + if (comment && (*s != '\n')) + *s = ' '; + + s++; + } + + const char *errmsg = "?"; + +#define SYNTAX_ERR(msg) do { errmsg = msg; goto syntaxerror; } while (0) + + if (comment && !oneline) + SYNTAX_ERR("Unclosed multi-line comment"); + + // Before any detailed parsing, allocate memory based on upper bounds for the number of output files and symbols + size_t total_file_count = 0; + s = pl.string_pool; + for (size_t i = 0; i < fsize; i++, s++) { + if (*s == '{') + total_file_count++; + } + pl.num_ofiles = total_file_count; + pl.ofiles = malloc(total_file_count * sizeof(struct bss_ofile)); + + size_t cur_ofile = (size_t)-1; + size_t cur_symbol = 0; + + s = pl.string_pool; + for (size_t i = 0; i < fsize; i++, s++) { + if (*s == '{') { + if (cur_ofile != (size_t)-1) { + assert (cur_ofile < total_file_count); + pl.ofiles[cur_ofile].num_symbols = cur_symbol; + pl.ofiles[cur_ofile].symbols = malloc(cur_symbol * sizeof(Elf32_Sym)); + pl.ofiles[cur_ofile].symbol_names = malloc(cur_symbol * sizeof(char *)); + pl.ofiles[cur_ofile].symbols_found = BITSET_ALLOC(cur_symbol); + cur_symbol = 0; + } + cur_ofile++; + } else if (*s == ';') { + cur_symbol++; + } + } + assert (cur_ofile < total_file_count); + pl.ofiles[cur_ofile].num_symbols = cur_symbol; + pl.ofiles[cur_ofile].symbols = malloc(cur_symbol * sizeof(Elf32_Sym)); + pl.ofiles[cur_ofile].symbol_names = malloc(cur_symbol * sizeof(char *)); + pl.ofiles[cur_ofile].symbols_found = BITSET_ALLOC(cur_symbol); + + bool quote = false; + bool eof_ok = false; + char *start = NULL; + char *end = NULL; + void *next = &&fpath; + line = 1; + cur_ofile = (size_t)-1; + cur_symbol = 0; + + s = pl.string_pool; +whitespace: + // Skip whitespace, count newlines for syntax error messages + while (iswhitespace(*s)) { + if (*s == '\n') line++; + s++; + }; + // If we're at EOF, check if that's OK or whether it's a syntax error + if (*s == '\0') { + if (eof_ok) goto eof; + SYNTAX_ERR("Unexpected EOF"); + } + goto *next; + +fpath: + // We're looking for a file path now, up until an opening brace {. + // If the file path is unquoted, the first whitespace encountered cuts this short, but if the file path + // is quoted whitespace will be included in the path up until the closing quote. If quoted, newlines will + // cause a syntax error if used between the quotes. + eof_ok = false; + // Check quote immediately, it's only allowed at the start + if (*s == '"') s++, quote = true; + + // Consume the path + start = s; + while (*s != '{') { + if (*s == '\0') SYNTAX_ERR("Unexpected EOF"); + if (!quote && iswhitespace(*s)) break; // Unquoted whitespace cuts this short + if (quote && *s == '\n') SYNTAX_ERR("Quoted string spanning multiple lines"); + if (quote && *s == '"') break; // Ending quotes cuts this short + s++; + } + end = s; + + // Check for closing quote + if (*s == '"') { + if (!quote) SYNTAX_ERR("Found a closing quote with no opening quote"); + quote = false; + s++; + } + + // Consume any whitespace up to { + next = &&obrace; + goto whitespace; + +obrace: + // We're looking for an opening brace character {, anything else is a fail + if (*s++ != '{') SYNTAX_ERR("Expected an {"); + // NULL-terminate the file path in-place and save it + *end = '\0'; + cur_ofile++; + cur_symbol = 0; + assert(cur_ofile < total_file_count); + pl.ofiles[cur_ofile].path = start; + // Consume whitespace up to the first symbol name + next = &&sym; + goto whitespace; + +sym: + // Symbol names are similar to the file path, but we're looking to end on a semicolon ; + // If the symbol name is unquoted, the first whitespace encountered cuts this short, but if the symbol name + // is quoted whitespace will be included in the name up until the closing quote. If quoted, newlines will + // cause a syntax error if used between the quotes. + // Check quote immediately, it's only allowed at the start + if (*s == '"') quote = true, s++; + + // Consume the symbol name + start = s; + while (*s != ';') { + if (*s == '\0') SYNTAX_ERR("Unexpected EOF"); + if (quote && *s == '\n') SYNTAX_ERR("Quoted string spanning multiple lines"); + // Unlike file paths, we're not friendly to whitespace between the end of the symbol and the semicolon + // TODO we could be though? + if (!quote && iswhitespace(*s)) SYNTAX_ERR("Whitespace in unquoted symbol name"); + if (quote && *s == '"') { + // It's tempting to allow whitespace here but it would be inconsistent with the above, but if the above + // changes this should similarly allow whitespace + if (s[1] != ';') SYNTAX_ERR("Expected ;"); + break; + } + s++; + } + end = s; + + // Check for end quote + if (*s == '"') { + if (!quote) SYNTAX_ERR("Found a closing quote with no opening quote"); + quote = false; + s++; + } + // This assertion should always be true unless the above logic is wrong, it's + // not meant to be caused by a syntax error. + assert(*s++ == ';'); + + // NULL-terminate + *end = '\0'; + assert(cur_symbol < pl.ofiles[cur_ofile].num_symbols); + pl.ofiles[cur_ofile].symbol_names[cur_symbol++] = start; + + // Consume any whitespace following the semicolon + next = &&cbrace; + goto whitespace; + +cbrace: + // If the first non-whitespace character after a semicolon isn't }, assume it's another + // symbol in the list. If it is }, this is the end of the list. + if (*s != '}') goto sym; + s++; + // This might be a valid end of file (after any trailing whitespace) if this is the last + // output file in the specification. + pl.ofiles[cur_ofile].num_symbols = cur_symbol; + eof_ok = true; + next = &&fpath; + goto whitespace; + +eof: + // Reached end of file, we must have all output files and their symbols at this point. + assert(cur_ofile < pl.num_ofiles); + // Reduce the number of files to the actual number from the upper bound + pl.num_ofiles = cur_ofile + 1; + return LDPS_OK; + +syntaxerror: + pl.message(LDPL_FATAL, "%s(%d): Syntax error: %s", pl.order_path, line, errmsg); + return LDPS_ERR; +} + +static enum ld_plugin_status +parse_option(const char *opt) +{ + if (strequ(opt, "order=")) { + pl.order_path = strdup(opt + sizeof("order=") - 1); + return LDPS_OK; + } + + fprintf(stderr, "Unknown option: %s\n", opt); + return LDPS_ERR; +} + +enum ld_plugin_status +onload(struct ld_plugin_tv *tv) +{ + enum ld_plugin_status ps; + + // Initialize our state + memset(&pl, 0, sizeof(pl)); + pl.api_version = -1; + pl.gnu_ld_version = -1; + pl.linker_output = -1; + + // Parse the transfer vector + for (struct ld_plugin_tv *ptv = tv; ptv->tv_tag != LDPT_NULL; ptv++) { + switch (ptv->tv_tag) { + case LDPT_OPTION: + if (ps = parse_option(ptv->tv_u.tv_string), ps != LDPS_OK) + return ps; + break; + + case LDPT_MESSAGE: + pl.message = ptv->tv_u.tv_message; + break; + + case LDPT_API_VERSION: + pl.api_version = ptv->tv_u.tv_val; + break; + + case LDPT_GNU_LD_VERSION: + pl.gnu_ld_version = ptv->tv_u.tv_val; + break; + + case LDPT_LINKER_OUTPUT: + pl.linker_output = ptv->tv_u.tv_val; + break; + + case LDPT_OUTPUT_NAME: + pl.output_name = ptv->tv_u.tv_string; + break; + + case LDPT_REGISTER_CLAIM_FILE_HOOK: + pl.register_claim_file = ptv->tv_u.tv_register_claim_file; + break; + + case LDPT_REGISTER_ALL_SYMBOLS_READ_HOOK: + pl.register_all_symbols_read = ptv->tv_u.tv_register_all_symbols_read; + break; + + case LDPT_REGISTER_CLEANUP_HOOK: + pl.register_cleanup = ptv->tv_u.tv_register_cleanup; + break; + + case LDPT_ADD_SYMBOLS: + pl.add_symbols = ptv->tv_u.tv_add_symbols; + break; + + case LDPT_GET_INPUT_FILE: + pl.get_input_file = ptv->tv_u.tv_get_input_file; + break; + + case LDPT_GET_SYMBOLS: + pl.get_symbols = ptv->tv_u.tv_get_symbols; + break; + + case LDPT_GET_SYMBOLS_V2: + pl.get_symbols_v2 = ptv->tv_u.tv_get_symbols; + break; + + case LDPT_ADD_INPUT_FILE: + pl.add_input_file = ptv->tv_u.tv_add_input_file; + break; + + default: + break; + } + } + + // Check pl.message + if (pl.message == NULL) { + fprintf(stderr, "No message() provided to plugin"); + return LDPS_ERR; + } + + // Check args + + if (pl.order_path == NULL) { + pl.message(LDPL_ERROR, "Missing option -plugin-opt order=<order.txt>"); + return LDPS_ERR; + } + + // Read the order file + + if (ps = parse_order_file(pl.order_path), ps != LDPS_OK) + return ps; + + // Register callbacks + + pl.register_claim_file(claim_file); + pl.register_all_symbols_read(all_symbols_read); + pl.register_cleanup(cleanup); + + return LDPS_OK; +} |
