1
0
Fork 0
mirror of https://github.com/zeldaret/oot.git synced 2025-02-05 03:34:20 +00:00
oot/tools/com-plugin/plugin.c

832 lines
29 KiB
C
Raw Permalink Normal View History

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