mirror of
https://github.com/zeldaret/oot.git
synced 2025-02-05 03:34:20 +00:00
c028db03b4
* git subrepo clone git@github.com:Thar0/com-plugin.git tools/com-plugin subrepo: subdir: "tools/com-plugin" merged: "e8543312d" upstream: origin: "git@github.com:Thar0/com-plugin.git" branch: "main" commit: "e8543312d" git-subrepo: version: "0.4.6" origin: "https://github.com/ingydotnet/git-subrepo" commit: "110b9eb" * ique-cn OK * Review suggestions * Most suggestions * git subrepo pull tools/com-plugin subrepo: subdir: "tools/com-plugin" merged: "81595ed1c" upstream: origin: "git@github.com:Thar0/com-plugin.git" branch: "main" commit: "81595ed1c" git-subrepo: version: "0.4.6" origin: "https://github.com/ingydotnet/git-subrepo" commit: "110b9eb" * Fix other versions
831 lines
29 KiB
C
831 lines
29 KiB
C
/**
|
|
* 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;
|
|
}
|