260 lines
11 KiB
C
260 lines
11 KiB
C
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
|
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
|
|
╞══════════════════════════════════════════════════════════════════════════════╡
|
|
│ Copyright 2020 Justine Alexandra Roberts Tunney │
|
|
│ │
|
|
│ 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; version 2 of the License. │
|
|
│ │
|
|
│ 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, write to the Free Software │
|
|
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
|
|
│ 02110-1301 USA │
|
|
╚─────────────────────────────────────────────────────────────────────────────*/
|
|
#include "libc/alg/arraylist.h"
|
|
#include "libc/assert.h"
|
|
#include "libc/bits/safemacros.h"
|
|
#include "libc/calls/calls.h"
|
|
#include "libc/log/check.h"
|
|
#include "libc/macros.h"
|
|
#include "libc/mem/mem.h"
|
|
#include "libc/runtime/gc.h"
|
|
#include "libc/runtime/memtrack.h"
|
|
#include "libc/runtime/runtime.h"
|
|
#include "libc/sysv/consts/map.h"
|
|
#include "libc/sysv/consts/mremap.h"
|
|
#include "libc/sysv/consts/o.h"
|
|
#include "libc/sysv/consts/ok.h"
|
|
#include "libc/sysv/consts/prot.h"
|
|
#include "libc/x/x.h"
|
|
#include "tool/build/lib/elfwriter.h"
|
|
#include "tool/build/lib/interner.h"
|
|
|
|
static const Elf64_Ehdr kObjHeader = {
|
|
.e_ident = {ELFMAG0, ELFMAG1, ELFMAG2, ELFMAG3, ELFCLASS64, ELFDATA2LSB, 1,
|
|
ELFOSABI_NONE},
|
|
.e_type = ET_REL,
|
|
.e_machine = EM_NEXGEN32E,
|
|
.e_version = 1,
|
|
.e_ehsize = sizeof(Elf64_Ehdr),
|
|
.e_shentsize = sizeof(Elf64_Shdr)};
|
|
|
|
static size_t AppendSection(struct ElfWriter *elf, const char *name,
|
|
int sh_type, int sh_flags) {
|
|
ssize_t section =
|
|
append(elf->shdrs,
|
|
(&(Elf64_Shdr){.sh_type = sh_type,
|
|
.sh_flags = sh_flags,
|
|
.sh_entsize = elf->entsize,
|
|
.sh_addralign = elf->addralign,
|
|
.sh_offset = sh_type != SHT_NULL ? elf->wrote : 0,
|
|
.sh_name = intern(elf->shstrtab, name)}));
|
|
CHECK_NE(-1, section);
|
|
return section;
|
|
}
|
|
|
|
static size_t FinishSection(struct ElfWriter *elf) {
|
|
size_t section = elf->shdrs->i - 1;
|
|
elf->shdrs->p[section].sh_size =
|
|
elf->wrote - elf->shdrs->p[section].sh_offset;
|
|
return section;
|
|
}
|
|
|
|
static struct ElfWriterSymRef AppendSymbol(struct ElfWriter *elf,
|
|
const char *name, int st_info,
|
|
int st_other, size_t st_value,
|
|
size_t st_size, size_t st_shndx,
|
|
enum ElfWriterSymOrder slg) {
|
|
ssize_t sym =
|
|
append(elf->syms[slg], (&(Elf64_Sym){.st_info = st_info,
|
|
.st_size = st_size,
|
|
.st_value = st_value,
|
|
.st_other = st_other,
|
|
.st_name = intern(elf->strtab, name),
|
|
.st_shndx = st_shndx}));
|
|
CHECK_NE(-1, sym);
|
|
return (struct ElfWriterSymRef){.slg = slg, .sym = sym};
|
|
}
|
|
|
|
static void MakeRelaSection(struct ElfWriter *elf, size_t section) {
|
|
size_t shdr, size;
|
|
size = (elf->relas->i - elf->relas->j) * sizeof(Elf64_Rela);
|
|
elfwriter_align(elf, alignof(Elf64_Rela), sizeof(Elf64_Rela));
|
|
shdr = elfwriter_startsection(
|
|
elf,
|
|
gc(xasprintf("%s%s", ".rela",
|
|
&elf->shstrtab->p[elf->shdrs->p[section].sh_name])),
|
|
SHT_RELA, SHF_INFO_LINK);
|
|
elf->shdrs->p[shdr].sh_info = section;
|
|
elfwriter_reserve(elf, size);
|
|
elfwriter_commit(elf, size);
|
|
FinishSection(elf);
|
|
elf->relas->j = elf->relas->i;
|
|
}
|
|
|
|
static void WriteRelaSections(struct ElfWriter *elf, size_t symtab) {
|
|
uint32_t sym;
|
|
size_t i, j, k;
|
|
Elf64_Rela *rela;
|
|
for (j = 0, i = 0; i < elf->shdrs->i; ++i) {
|
|
if (elf->shdrs->p[i].sh_type == SHT_RELA) {
|
|
elf->shdrs->p[i].sh_link = symtab;
|
|
for (rela = (Elf64_Rela *)((char *)elf->map + elf->shdrs->p[i].sh_offset);
|
|
rela <
|
|
(Elf64_Rela *)((char *)elf->map + (elf->shdrs->p[i].sh_offset +
|
|
elf->shdrs->p[i].sh_size));
|
|
rela++, j++) {
|
|
sym = elf->relas->p[j].symkey.sym;
|
|
for (k = 0; k < elf->relas->p[j].symkey.slg; ++k) {
|
|
sym += elf->syms[k]->i;
|
|
}
|
|
rela->r_offset = elf->relas->p[j].offset;
|
|
rela->r_info = ELF64_R_INFO(sym, elf->relas->p[j].type);
|
|
rela->r_addend = elf->relas->p[j].addend;
|
|
}
|
|
}
|
|
}
|
|
assert(j == elf->relas->i);
|
|
}
|
|
|
|
static size_t FlushStrtab(struct ElfWriter *elf, const char *name,
|
|
struct Interner *strtab) {
|
|
size_t size = strtab->i * sizeof(strtab->p[0]);
|
|
elfwriter_align(elf, 1, 0);
|
|
AppendSection(elf, ".strtab", SHT_STRTAB, 0);
|
|
mempcpy(elfwriter_reserve(elf, size), strtab->p, size);
|
|
elfwriter_commit(elf, size);
|
|
return FinishSection(elf);
|
|
}
|
|
|
|
static void FlushTables(struct ElfWriter *elf) {
|
|
size_t i, size, symtab;
|
|
elfwriter_align(elf, alignof(Elf64_Sym), sizeof(Elf64_Sym));
|
|
symtab = AppendSection(elf, ".symtab", SHT_SYMTAB, 0);
|
|
for (i = 0; i < ARRAYLEN(elf->syms); ++i) {
|
|
size = elf->syms[i]->i * sizeof(Elf64_Sym);
|
|
memcpy(elfwriter_reserve(elf, size), elf->syms[i]->p, size);
|
|
elfwriter_commit(elf, size);
|
|
}
|
|
FinishSection(elf);
|
|
elf->shdrs->p[symtab].sh_link = FlushStrtab(elf, ".strtab", elf->strtab);
|
|
elf->ehdr->e_shstrndx = FlushStrtab(elf, ".shstrtab", elf->shstrtab);
|
|
WriteRelaSections(elf, symtab);
|
|
size = elf->shdrs->i * sizeof(elf->shdrs->p[0]);
|
|
elfwriter_align(elf, alignof(elf->shdrs->p[0]), sizeof(elf->shdrs->p[0]));
|
|
elf->ehdr->e_shoff = elf->wrote;
|
|
elf->ehdr->e_shnum = elf->shdrs->i;
|
|
elf->shdrs->p[symtab].sh_info =
|
|
elf->syms[kElfWriterSymSection]->i + elf->syms[kElfWriterSymLocal]->i;
|
|
mempcpy(elfwriter_reserve(elf, size), elf->shdrs->p, size);
|
|
elfwriter_commit(elf, size);
|
|
}
|
|
|
|
struct ElfWriter *elfwriter_open(const char *path, int mode) {
|
|
struct ElfWriter *elf;
|
|
CHECK_NOTNULL((elf = calloc(1, sizeof(struct ElfWriter))));
|
|
CHECK_NOTNULL((elf->path = strdup(path)));
|
|
CHECK_NE(-1, asprintf(&elf->tmppath, "%s.%d", elf->path, getpid()));
|
|
CHECK_NE(-1, (elf->fd = open(elf->tmppath,
|
|
O_CREAT | O_TRUNC | O_RDWR | O_EXCL, mode)));
|
|
CHECK_NE(-1, ftruncate(elf->fd, (elf->mapsize = FRAMESIZE)));
|
|
CHECK_NE(MAP_FAILED, (elf->map = mmap((void *)(intptr_t)kFixedMappingsStart,
|
|
elf->mapsize, PROT_READ | PROT_WRITE,
|
|
MAP_SHARED | MAP_FIXED, elf->fd, 0)));
|
|
elf->ehdr = memcpy(elf->map, &kObjHeader, (elf->wrote = sizeof(kObjHeader)));
|
|
elf->strtab = newinterner();
|
|
elf->shstrtab = newinterner();
|
|
return elf;
|
|
}
|
|
|
|
void elfwriter_close(struct ElfWriter *elf) {
|
|
size_t i;
|
|
FlushTables(elf);
|
|
CHECK_NE(-1, munmap(elf->map, elf->mapsize));
|
|
CHECK_NE(-1, ftruncate(elf->fd, elf->wrote));
|
|
CHECK_NE(-1, close(elf->fd));
|
|
CHECK_NE(-1, rename(elf->tmppath, elf->path));
|
|
freeinterner(elf->shstrtab);
|
|
freeinterner(elf->strtab);
|
|
free(elf->shdrs->p);
|
|
free(elf->relas->p);
|
|
for (i = 0; i < ARRAYLEN(elf->syms); ++i) free(elf->syms[i]->p);
|
|
free(elf);
|
|
}
|
|
|
|
void elfwriter_align(struct ElfWriter *elf, size_t addralign, size_t entsize) {
|
|
elf->entsize = entsize;
|
|
elf->addralign = addralign;
|
|
elf->wrote = roundup(elf->wrote, addralign);
|
|
}
|
|
|
|
size_t elfwriter_startsection(struct ElfWriter *elf, const char *name,
|
|
int sh_type, int sh_flags) {
|
|
size_t shdr = AppendSection(elf, name, sh_type, sh_flags);
|
|
AppendSymbol(elf, "",
|
|
sh_type != SHT_NULL ? ELF64_ST_INFO(STB_LOCAL, STT_SECTION)
|
|
: ELF64_ST_INFO(STB_LOCAL, STT_NOTYPE),
|
|
STV_DEFAULT, 0, 0, shdr, kElfWriterSymSection);
|
|
return shdr;
|
|
}
|
|
|
|
void *elfwriter_reserve(struct ElfWriter *elf, size_t size) {
|
|
size_t need, greed;
|
|
need = elf->wrote + size;
|
|
greed = elf->mapsize;
|
|
if (need > greed) {
|
|
do {
|
|
greed = greed + (greed >> 1);
|
|
} while (need > greed);
|
|
greed = roundup(greed, FRAMESIZE);
|
|
CHECK_NE(-1, ftruncate(elf->fd, greed));
|
|
CHECK_NE(MAP_FAILED, mmap((char *)elf->map + elf->mapsize,
|
|
greed - elf->mapsize, PROT_READ | PROT_WRITE,
|
|
MAP_SHARED | MAP_FIXED, elf->fd, elf->mapsize));
|
|
elf->mapsize = greed;
|
|
}
|
|
return (char *)elf->map + elf->wrote;
|
|
}
|
|
|
|
void elfwriter_commit(struct ElfWriter *elf, size_t size) {
|
|
elf->wrote += size;
|
|
}
|
|
|
|
void elfwriter_finishsection(struct ElfWriter *elf) {
|
|
size_t section = FinishSection(elf);
|
|
if (elf->relas->j < elf->relas->i) MakeRelaSection(elf, section);
|
|
}
|
|
|
|
struct ElfWriterSymRef elfwriter_appendsym(struct ElfWriter *elf,
|
|
const char *name, int st_info,
|
|
int st_other, size_t st_value,
|
|
size_t st_size) {
|
|
return AppendSymbol(
|
|
elf, name, st_info, st_other, st_value, st_size, elf->shdrs->i - 1,
|
|
ELF64_ST_BIND(st_info) == STB_LOCAL ? kElfWriterSymLocal
|
|
: kElfWriterSymGlobal);
|
|
}
|
|
|
|
struct ElfWriterSymRef elfwriter_linksym(struct ElfWriter *elf,
|
|
const char *name, int st_info,
|
|
int st_other) {
|
|
return AppendSymbol(elf, name, st_info, st_other, 0, 0, 0,
|
|
kElfWriterSymGlobal);
|
|
}
|
|
|
|
void elfwriter_appendrela(struct ElfWriter *elf, uint64_t r_offset,
|
|
struct ElfWriterSymRef symkey, uint32_t type,
|
|
int64_t r_addend) {
|
|
CHECK_NE(-1,
|
|
append(elf->relas, (&(struct ElfWriterRela){.type = type,
|
|
.symkey = symkey,
|
|
.offset = r_offset,
|
|
.addend = r_addend})));
|
|
}
|