mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-07-02 17:28:30 +00:00
Rewrite .zip.o file linker
This change takes an entirely new approach to the incremental linking of pkzip executables. The assets created by zipobj.com are now treated like debug data. After a .com.dbg is compiled, fixupobj.com should be run, so it can apply fixups to the offsets and move the zip directory to the end of the file. Since debug data doesn't get objcopy'd, a new tool has been introduced called zipcopy.com which should be run after objcopy whenever a .com file is created. This is all automated by the `cosmocc` toolchain which is rapidly becoming the new recommended approach. This change also introduces the new C23 checked arithmetic macros.
This commit is contained in:
parent
f6407d5f7c
commit
8ff48201ca
125 changed files with 1056 additions and 928 deletions
|
@ -28,6 +28,9 @@
|
|||
#include "libc/errno.h"
|
||||
#include "libc/fmt/itoa.h"
|
||||
#include "libc/fmt/magnumstrs.internal.h"
|
||||
#include "libc/intrin/bits.h"
|
||||
#include "libc/intrin/kprintf.h"
|
||||
#include "libc/limits.h"
|
||||
#include "libc/log/log.h"
|
||||
#include "libc/macros.internal.h"
|
||||
#include "libc/mem/gc.internal.h"
|
||||
|
@ -37,22 +40,13 @@
|
|||
#include "libc/sysv/consts/msync.h"
|
||||
#include "libc/sysv/consts/o.h"
|
||||
#include "libc/sysv/consts/prot.h"
|
||||
#include "libc/zip.internal.h"
|
||||
#include "third_party/getopt/getopt.h"
|
||||
|
||||
/**
|
||||
* @fileoverview GCC Codegen Fixer-Upper.
|
||||
*/
|
||||
|
||||
#define GETOPTS "ch"
|
||||
|
||||
#define USAGE \
|
||||
"\
|
||||
Usage: fixupobj.com [-h] ARGS...\n\
|
||||
-?\n\
|
||||
-h show help\n\
|
||||
-c check-only mode\n\
|
||||
"
|
||||
|
||||
#define COSMO_TLS_REG 28
|
||||
#define MRS_TPIDR_EL0 0xd53bd040u
|
||||
#define MOV_REG(DST, SRC) (0xaa0003e0u | (SRC) << 16 | (DST))
|
||||
|
@ -69,6 +63,7 @@ static const unsigned char kFatNops[8][8] = {
|
|||
};
|
||||
|
||||
static int mode;
|
||||
static int fildes;
|
||||
static char *symstrs;
|
||||
static char *secstrs;
|
||||
static ssize_t esize;
|
||||
|
@ -89,6 +84,11 @@ nullterminated() static void Print(int fd, const char *s, ...) {
|
|||
va_end(va);
|
||||
}
|
||||
|
||||
static wontreturn void Die(const char *reason) {
|
||||
Print(2, epath, ": ", reason, "\n", NULL);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static wontreturn void SysExit(const char *func) {
|
||||
const char *errstr;
|
||||
if (!(errstr = _strerdoc(errno))) errstr = "EUNKNOWN";
|
||||
|
@ -96,31 +96,68 @@ static wontreturn void SysExit(const char *func) {
|
|||
exit(1);
|
||||
}
|
||||
|
||||
static wontreturn void PrintUsage(int fd, int exitcode) {
|
||||
Print(fd, "\n\
|
||||
NAME\n\
|
||||
\n\
|
||||
Cosmopolitan Object Fixer\n\
|
||||
\n\
|
||||
SYNOPSIS\n\
|
||||
\n\
|
||||
",
|
||||
program_invocation_name, " [FLAGS] OBJECT...\n\
|
||||
\n\
|
||||
DESCRIPTION\n\
|
||||
\n\
|
||||
This program applies fixups to ELF object files and executables that\n\
|
||||
at build time whenever they're created by the toolchain. It's needed\n\
|
||||
so that zip assets work correctly, plus this'll make code go faster.\n\
|
||||
This program is also able to spot some coding errors like privileged\n\
|
||||
functions calling unprivileged ones.\n\
|
||||
\n\
|
||||
Multiple binary files may be specified, which are modified in-place.\n\
|
||||
\n\
|
||||
FLAGS\n\
|
||||
\n\
|
||||
-h show this help\n\
|
||||
-c checks only mode\n\
|
||||
\n\
|
||||
",
|
||||
NULL);
|
||||
exit(exitcode);
|
||||
}
|
||||
|
||||
static void GetOpts(int argc, char *argv[]) {
|
||||
int opt;
|
||||
mode = O_RDWR;
|
||||
while ((opt = getopt(argc, argv, GETOPTS)) != -1) {
|
||||
while ((opt = getopt(argc, argv, "ch")) != -1) {
|
||||
switch (opt) {
|
||||
case 'c':
|
||||
mode = O_RDONLY;
|
||||
break;
|
||||
case 'h':
|
||||
case '?':
|
||||
write(1, USAGE, sizeof(USAGE) - 1);
|
||||
exit(0);
|
||||
PrintUsage(1, 0);
|
||||
default:
|
||||
write(2, USAGE, sizeof(USAGE) - 1);
|
||||
exit(64);
|
||||
PrintUsage(2, 1);
|
||||
}
|
||||
}
|
||||
if (optind == argc) {
|
||||
Print(2,
|
||||
"error: no elf object files specified\n"
|
||||
"run ",
|
||||
program_invocation_name, " -h for usage\n", NULL);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
static Elf64_Shdr *FindElfSectionByName(const char *name) {
|
||||
long i;
|
||||
Elf64_Shdr *shdr;
|
||||
const char *secname;
|
||||
for (i = 0; i < elf->e_shnum; ++i) {
|
||||
shdr = GetElfSectionHeaderAddress(elf, esize, i);
|
||||
if (!strcmp(GetElfString(elf, esize, secstrs, shdr->sh_name), name)) {
|
||||
if ((shdr = GetElfSectionHeaderAddress(elf, esize, i)) &&
|
||||
(secname = GetElfString(elf, esize, secstrs, shdr->sh_name)) &&
|
||||
!strcmp(name, secname)) {
|
||||
return shdr;
|
||||
}
|
||||
}
|
||||
|
@ -128,28 +165,31 @@ static Elf64_Shdr *FindElfSectionByName(const char *name) {
|
|||
}
|
||||
|
||||
static void CheckPrivilegedCrossReferences(void) {
|
||||
long i, x;
|
||||
long i;
|
||||
unsigned long x;
|
||||
Elf64_Shdr *shdr;
|
||||
const char *secname;
|
||||
Elf64_Rela *rela, *erela;
|
||||
if (!(shdr = FindElfSectionByName(".rela.privileged"))) return;
|
||||
rela = GetElfSectionAddress(elf, esize, shdr);
|
||||
if (!(rela = GetElfSectionAddress(elf, esize, shdr))) return;
|
||||
erela = rela + shdr->sh_size / sizeof(*rela);
|
||||
for (; rela < erela; ++rela) {
|
||||
if (!ELF64_R_TYPE(rela->r_info)) continue;
|
||||
if (!(x = ELF64_R_SYM(rela->r_info))) continue;
|
||||
if (x >= symcount) continue;
|
||||
if (syms[x].st_shndx == SHN_ABS) continue;
|
||||
if (!syms[x].st_shndx) continue;
|
||||
shdr = GetElfSectionHeaderAddress(elf, esize, syms[x].st_shndx);
|
||||
if (~shdr->sh_flags & SHF_EXECINSTR) continue; // data reference
|
||||
secname = GetElfString(elf, esize, secstrs, shdr->sh_name);
|
||||
if (strcmp(".privileged", secname)) {
|
||||
Print(2, epath,
|
||||
": code in .privileged section "
|
||||
"references symbol '",
|
||||
GetElfString(elf, esize, symstrs, syms[x].st_name),
|
||||
"' in unprivileged code section '", secname, "'\n", NULL);
|
||||
exit(1);
|
||||
if ((shdr = GetElfSectionHeaderAddress(elf, esize, syms[x].st_shndx))) {
|
||||
if (~shdr->sh_flags & SHF_EXECINSTR) continue; // data reference
|
||||
if ((secname = GetElfString(elf, esize, secstrs, shdr->sh_name)) &&
|
||||
strcmp(".privileged", secname)) {
|
||||
Print(2, epath,
|
||||
": code in .privileged section "
|
||||
"references symbol '",
|
||||
GetElfString(elf, esize, symstrs, syms[x].st_name),
|
||||
"' in unprivileged code section '", secname, "'\n", NULL);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -160,11 +200,15 @@ static void RewriteTlsCode(void) {
|
|||
Elf64_Shdr *shdr;
|
||||
uint32_t *p, *pe;
|
||||
for (i = 0; i < elf->e_shnum; ++i) {
|
||||
shdr = GetElfSectionHeaderAddress(elf, esize, i);
|
||||
if (shdr->sh_type == SHT_PROGBITS && //
|
||||
(shdr->sh_flags & SHF_ALLOC) && //
|
||||
(shdr->sh_flags & SHF_EXECINSTR) && //
|
||||
(p = GetElfSectionAddress(elf, esize, shdr))) {
|
||||
if (!(shdr = GetElfSectionHeaderAddress(elf, esize, i))) {
|
||||
Die("elf header overflow");
|
||||
}
|
||||
if (shdr->sh_type == SHT_PROGBITS && //
|
||||
(shdr->sh_flags & SHF_ALLOC) && //
|
||||
(shdr->sh_flags & SHF_EXECINSTR)) {
|
||||
if (!(p = GetElfSectionAddress(elf, esize, shdr))) {
|
||||
Die("elf header overflow");
|
||||
}
|
||||
for (pe = p + shdr->sh_size / 4; p <= pe; ++p) {
|
||||
if ((*p & -32) == MRS_TPIDR_EL0) {
|
||||
*p = MOV_REG(*p & 31, COSMO_TLS_REG);
|
||||
|
@ -191,119 +235,140 @@ static void OptimizePatchableFunctionEntries(void) {
|
|||
Elf64_Shdr *shdr;
|
||||
unsigned char *p, *pe;
|
||||
for (i = 0; i < symcount; ++i) {
|
||||
if (ELF64_ST_TYPE(syms[i].st_info) == STT_FUNC && syms[i].st_size) {
|
||||
shdr = GetElfSectionHeaderAddress(elf, esize, syms[i].st_shndx);
|
||||
p = GetElfSectionAddress(elf, esize, shdr);
|
||||
p += syms[i].st_value;
|
||||
pe = p + syms[i].st_size;
|
||||
for (; p + 1 < pe; p += n) {
|
||||
if (p[0] != 0x90) break;
|
||||
if (p[1] != 0x90) break;
|
||||
for (n = 2; p + n < pe && n < ARRAYLEN(kFatNops); ++n) {
|
||||
if (p[n] != 0x90) break;
|
||||
}
|
||||
memcpy(p, kFatNops[n], n);
|
||||
if (!syms[i].st_size) continue;
|
||||
if (ELF64_ST_TYPE(syms[i].st_info) != STT_FUNC) continue;
|
||||
if (!(shdr = GetElfSectionHeaderAddress(elf, esize, syms[i].st_shndx))) {
|
||||
Die("elf header overflow");
|
||||
}
|
||||
if (shdr->sh_type != SHT_PROGBITS) continue;
|
||||
if (!(p = GetElfSectionAddress(elf, esize, shdr))) {
|
||||
Die("elf header overflow");
|
||||
}
|
||||
p += syms[i].st_value - shdr->sh_addr;
|
||||
pe = p + syms[i].st_size;
|
||||
for (; p + 1 < pe; p += n) {
|
||||
if (p[0] != 0x90) break;
|
||||
if (p[1] != 0x90) break;
|
||||
for (n = 2; p + n < pe && n < ARRAYLEN(kFatNops); ++n) {
|
||||
if (p[n] != 0x90) break;
|
||||
}
|
||||
memcpy(p, kFatNops[n], n);
|
||||
}
|
||||
}
|
||||
#endif /* __x86_64__ */
|
||||
}
|
||||
|
||||
static void OptimizeRelocations(void) {
|
||||
Elf64_Half i;
|
||||
Elf64_Rela *rela;
|
||||
unsigned char *code, *p;
|
||||
Elf64_Shdr *shdr, *shdrcode;
|
||||
for (i = 0; i < elf->e_shnum; ++i) {
|
||||
shdr = GetElfSectionHeaderAddress(elf, esize, i);
|
||||
if (shdr->sh_type == SHT_RELA) {
|
||||
shdrcode = GetElfSectionHeaderAddress(elf, esize, shdr->sh_info);
|
||||
if (!(shdrcode->sh_flags & SHF_EXECINSTR)) continue;
|
||||
code = GetElfSectionAddress(elf, esize, shdrcode);
|
||||
for (rela = GetElfSectionAddress(elf, esize, shdr);
|
||||
((uintptr_t)rela + shdr->sh_entsize <=
|
||||
MIN((uintptr_t)elf + esize,
|
||||
(uintptr_t)elf + shdr->sh_offset + shdr->sh_size));
|
||||
++rela) {
|
||||
|
||||
/*
|
||||
* GCC isn't capable of -mnop-mcount when using -fpie.
|
||||
* Let's fix that. It saves ~14 cycles per function call.
|
||||
* Then libc/runtime/ftrace.greg.c morphs it back at runtime.
|
||||
*/
|
||||
if (ELF64_R_TYPE(rela->r_info) == R_X86_64_GOTPCRELX &&
|
||||
strcmp(GetElfString(elf, esize, symstrs,
|
||||
syms[ELF64_R_SYM(rela->r_info)].st_name),
|
||||
"mcount") == 0) {
|
||||
rela->r_info = R_X86_64_NONE;
|
||||
p = code + rela->r_offset - 2;
|
||||
p[0] = 0x66; /* nopw 0x00(%rax,%rax,1) */
|
||||
p[1] = 0x0f;
|
||||
p[2] = 0x1f;
|
||||
p[3] = 0x44;
|
||||
p[4] = 0x00;
|
||||
p[5] = 0x00;
|
||||
}
|
||||
|
||||
/*
|
||||
* Let's just try to nop mcount calls in general due to the above.
|
||||
*/
|
||||
if ((ELF64_R_TYPE(rela->r_info) == R_X86_64_PC32 ||
|
||||
ELF64_R_TYPE(rela->r_info) == R_X86_64_PLT32) &&
|
||||
strcmp(GetElfString(elf, esize, symstrs,
|
||||
syms[ELF64_R_SYM(rela->r_info)].st_name),
|
||||
"mcount") == 0) {
|
||||
rela->r_info = R_X86_64_NONE;
|
||||
p = code + rela->r_offset - 1;
|
||||
p[0] = 0x0f; /* nopl 0x00(%rax,%rax,1) */
|
||||
p[1] = 0x1f;
|
||||
p[2] = 0x44;
|
||||
p[3] = 0x00;
|
||||
p[4] = 0x00;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Converts PKZIP recs from PC-relative to RVA-relative.
|
||||
*/
|
||||
static void RelinkZipFiles(void) {
|
||||
int rela, recs;
|
||||
unsigned long cdsize, cdoffset;
|
||||
unsigned char foot[kZipCdirHdrMinSize];
|
||||
unsigned char *base, *xeof, *stop, *eocd, *cdir, *lfile, *cfile;
|
||||
base = (unsigned char *)elf;
|
||||
xeof = (unsigned char *)elf + esize;
|
||||
eocd = xeof - kZipCdirHdrMinSize;
|
||||
stop = base;
|
||||
// scan backwards for zip eocd todo record
|
||||
// that was created by libc/nexgen32e/zip.S
|
||||
for (;;) {
|
||||
if (eocd < stop) return;
|
||||
if (READ32LE(eocd) == kZipCdirHdrMagicTodo && //
|
||||
ZIP_CDIR_SIZE(eocd) && //
|
||||
!ZIP_CDIR_OFFSET(eocd) && //
|
||||
!ZIP_CDIR_RECORDS(eocd) && //
|
||||
!ZIP_CDIR_RECORDSONDISK(eocd)) {
|
||||
break;
|
||||
}
|
||||
eocd = memrchr(stop, 'P', eocd - base);
|
||||
}
|
||||
// apply fixups to zip central directory recs
|
||||
recs = 0;
|
||||
cdir = (stop = eocd) - (cdsize = ZIP_CDIR_SIZE(eocd));
|
||||
for (cfile = cdir; cfile < stop; cfile += ZIP_CFILE_HDRSIZE(cfile)) {
|
||||
if (++recs >= 65536) {
|
||||
Die("too many zip central directory records");
|
||||
}
|
||||
if (cfile < base || //
|
||||
cfile + kZipCfileHdrMinSize > xeof || //
|
||||
cfile + ZIP_CFILE_HDRSIZE(cfile) > xeof) {
|
||||
Die("zip central directory entry overflows image");
|
||||
}
|
||||
if (READ32LE(cfile) != kZipCfileHdrMagic) {
|
||||
Die("bad __zip_cdir_size or zip central directory corrupted");
|
||||
}
|
||||
if ((rela = ZIP_CFILE_OFFSET(cfile)) < 0) {
|
||||
lfile = cfile + kZipCfileOffsetOffset + rela;
|
||||
} else {
|
||||
lfile = base + rela; // earlier fixup failed partway?
|
||||
}
|
||||
if (lfile < base || //
|
||||
lfile + kZipLfileHdrMinSize > xeof || //
|
||||
lfile + ZIP_LFILE_SIZE(lfile) > xeof) {
|
||||
Die("zip local file overflows image");
|
||||
}
|
||||
if (READ32LE(lfile) != kZipLfileHdrMagic) {
|
||||
Die("zip central directory offset to local file corrupted");
|
||||
}
|
||||
if (rela < 0) {
|
||||
WRITE32LE(cfile + kZipCfileOffsetOffset, lfile - base);
|
||||
}
|
||||
}
|
||||
// append new eocd record to program image
|
||||
if (esize > INT_MAX - sizeof(foot) ||
|
||||
(cdoffset = esize) > INT_MAX - sizeof(foot)) {
|
||||
Die("the time has come to adopt zip64");
|
||||
}
|
||||
bzero(foot, sizeof(foot));
|
||||
WRITE32LE(foot, kZipCdirHdrMagic);
|
||||
WRITE32LE(foot + kZipCdirSizeOffset, cdsize);
|
||||
WRITE16LE(foot + kZipCdirRecordsOffset, recs);
|
||||
WRITE32LE(foot + kZipCdirOffsetOffset, cdoffset);
|
||||
WRITE16LE(foot + kZipCdirRecordsOnDiskOffset, recs);
|
||||
if (pwrite(fildes, cdir, cdsize, esize) != cdsize) {
|
||||
SysExit("cdir pwrite");
|
||||
}
|
||||
if (pwrite(fildes, foot, sizeof(foot), esize + cdsize) != sizeof(foot)) {
|
||||
SysExit("eocd pwrite");
|
||||
}
|
||||
eocd = foot;
|
||||
}
|
||||
|
||||
static void FixupObject(void) {
|
||||
int fd;
|
||||
if ((fd = open(epath, mode)) == -1) {
|
||||
if ((fildes = open(epath, mode)) == -1) {
|
||||
SysExit("open");
|
||||
}
|
||||
if ((esize = lseek(fd, 0, SEEK_END)) == -1) {
|
||||
if ((esize = lseek(fildes, 0, SEEK_END)) == -1) {
|
||||
SysExit("lseek");
|
||||
}
|
||||
if (esize) {
|
||||
if ((elf = mmap(0, esize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) ==
|
||||
if ((elf = mmap(0, esize, PROT_READ | PROT_WRITE, MAP_SHARED, fildes, 0)) ==
|
||||
MAP_FAILED) {
|
||||
SysExit("mmap");
|
||||
}
|
||||
if (!IsElf64Binary(elf, esize)) {
|
||||
Print(2, epath, ": not an elf64 binary\n", NULL);
|
||||
exit(1);
|
||||
Die("not an elf64 binary");
|
||||
}
|
||||
if (!(syms = GetElfSymbolTable(elf, esize, &symcount))) {
|
||||
Print(2, epath, ": missing elf symbol table\n", NULL);
|
||||
exit(1);
|
||||
Die("missing elf symbol table");
|
||||
}
|
||||
if (!(secstrs = GetElfSectionNameStringTable(elf, esize))) {
|
||||
Print(2, epath, ": missing elf section string table\n", NULL);
|
||||
exit(1);
|
||||
Die("missing elf section string table");
|
||||
}
|
||||
if (!(symstrs = GetElfStringTable(elf, esize))) {
|
||||
Print(2, epath, ": missing elf symbol string table\n", NULL);
|
||||
exit(1);
|
||||
Die("missing elf symbol string table");
|
||||
}
|
||||
CheckPrivilegedCrossReferences();
|
||||
if (mode == O_RDWR) {
|
||||
if (elf->e_machine == EM_NEXGEN32E) {
|
||||
OptimizeRelocations();
|
||||
OptimizePatchableFunctionEntries();
|
||||
}
|
||||
if (elf->e_machine == EM_AARCH64) {
|
||||
} else if (elf->e_machine == EM_AARCH64) {
|
||||
RewriteTlsCode();
|
||||
}
|
||||
if (elf->e_type != ET_REL) {
|
||||
RelinkZipFiles();
|
||||
}
|
||||
if (msync(elf, esize, MS_ASYNC | MS_INVALIDATE)) {
|
||||
SysExit("msync");
|
||||
}
|
||||
|
@ -312,7 +377,7 @@ static void FixupObject(void) {
|
|||
SysExit("munmap");
|
||||
}
|
||||
}
|
||||
if (close(fd)) {
|
||||
if (close(fildes)) {
|
||||
SysExit("close");
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue