From dd53f3114755394aa831b441d061248a9b757ebe Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Wed, 9 Aug 2023 18:36:38 -0700 Subject: [PATCH] Introduce post-linker that converts ELF to PE If you build a static ELF executable in `ld -q` mode (which leaves rela sections inside the binary) then you can run it through the elf2pe.com program afterwards, which will turn it into a PE executable. We have a new trick for defining WIN32 DLL imports in C without any assembly code. This also achieves the optimally tiny and perfect PE binary structure. We need this because it isn't possible to have a GNU ld linker script generate a PE file where the virtual pointer and the file pointer can drift apart. This post-linker can do that. One cool benefit is we can now use a smaller 512-byte alignment in the file, and an even bigger 64kb alignment for the segment virtual addresses, and the executable ends up being smaller. Another program introduced by this change is pecheck.com which can do extensive linting of PE static executables to help explain why Windows won't load it. --- ape/ape.lds | 59 +- ape/idata.internal.h | 6 +- build/definitions.mk | 2 +- examples/hello2.c | 2 +- libc/elf/struct/rela.h | 16 +- libc/elf/struct/shdr.h | 2 +- libc/elf/struct/sym.h | 1 + libc/nt/struct/imagefileheader.internal.h | 2 +- libc/nt/struct/imageoptionalheader.internal.h | 14 +- tool/build/elf2pe.c | 1073 +++++++++++++++++ tool/build/pecheck.c | 215 ++++ tool/decode/pe2.c | 54 +- tool/emacs/c.lang | 1 + tool/emacs/cosmo-c-keywords.el | 3 +- tool/hello/hello.c | 435 ++++++- tool/hello/hello.mk | 47 +- tool/hello/start.S | 27 + tool/hello/systemcall.S | 57 + 18 files changed, 1914 insertions(+), 102 deletions(-) create mode 100644 tool/build/elf2pe.c create mode 100644 tool/build/pecheck.c create mode 100644 tool/hello/start.S create mode 100644 tool/hello/systemcall.S diff --git a/ape/ape.lds b/ape/ape.lds index 095ddf17c..0fa664d93 100644 --- a/ape/ape.lds +++ b/ape/ape.lds @@ -261,7 +261,7 @@ SECTIONS { . = ALIGN(CODE_GRANULE); KEEP(*(.ape.pad.head)) - . = ALIGN(SupportsWindows() || SupportsMetal() ? CONSTANT(COMMONPAGESIZE) : 16); + . = ALIGN(SupportsWindows() || SupportsMetal() ? CONSTANT(MAXPAGESIZE) : 16); _ehead = .; } :Head @@ -312,14 +312,14 @@ SECTIONS { /* Privileged code invulnerable to magic */ KEEP(*(.ape.pad.privileged)); - . = ALIGN(__privileged_end > __privileged_start ? CONSTANT(COMMONPAGESIZE) : 1); + . = ALIGN(__privileged_end > __privileged_start ? CONSTANT(MAXPAGESIZE) : 1); /*END: morphable code */ __privileged_start = .; *(.privileged) __privileged_end = .; KEEP(*(.ape.pad.text)) - . = ALIGN(CONSTANT(COMMONPAGESIZE)); + . = ALIGN(CONSTANT(MAXPAGESIZE)); /*END: Read Only Data (only needed for initialization) */ } :Cod @@ -349,28 +349,12 @@ SECTIONS { KEEP(*(SORT_BY_NAME(.idata.ro.*))) #endif - . = ALIGN(__SIZEOF_POINTER__); - __init_array_start = .; - KEEP(*(SORT_BY_INIT_PRIORITY(.init_array.*) - SORT_BY_INIT_PRIORITY(.ctors.*))) - KEEP(*(.ctors)) - KEEP(*(.init_array)) - KEEP(*(.preinit_array)) - __init_array_end = .; - - . = ALIGN(__SIZEOF_POINTER__); - __fini_array_start = .; - KEEP(*(SORT_BY_INIT_PRIORITY(.fini_array.*) - SORT_BY_INIT_PRIORITY(.dtors.*))) - KEEP(*(.fini_array)) - KEEP(*(.dtors)) - __fini_array_end = .; - /* Encoded Data Structures w/ Linear Initialization Order */ KEEP(*(.initroprologue)) KEEP(*(SORT_BY_NAME(.initro.*))) KEEP(*(.initroepilogue)) KEEP(*(SORT_BY_NAME(.sort.rodata.*))) + . = ALIGN(CONSTANT(MAXPAGESIZE)); /*END: read-only data that's only needed for initialization */ @@ -384,13 +368,13 @@ SECTIONS { *(SORT_BY_ALIGNMENT(.tdata.*)) _tdata_end = .; KEEP(*(.ape.pad.rodata)) - . = ALIGN(CONSTANT(COMMONPAGESIZE)); + . = ALIGN(CONSTANT(MAXPAGESIZE)); _etext = .; PROVIDE(etext = .); } :Tls :Rom /*END: Read Only Data */ - . = DATA_SEGMENT_ALIGN(CONSTANT(COMMONPAGESIZE), CONSTANT(COMMONPAGESIZE)); + . = DATA_SEGMENT_ALIGN(CONSTANT(MAXPAGESIZE), CONSTANT(MAXPAGESIZE)); . = DATA_SEGMENT_RELRO_END(0, .); /* this only tells the linker about the layout of uninitialized */ @@ -425,6 +409,23 @@ SECTIONS { *(.got.plt) KEEP(*(.gotpltepilogue)) + . = ALIGN(__SIZEOF_POINTER__); + __init_array_start = .; + KEEP(*(SORT_BY_INIT_PRIORITY(.init_array.*) + SORT_BY_INIT_PRIORITY(.ctors.*))) + KEEP(*(.ctors)) + KEEP(*(.init_array)) + KEEP(*(.preinit_array)) + __init_array_end = .; + + . = ALIGN(__SIZEOF_POINTER__); + __fini_array_start = .; + KEEP(*(SORT_BY_INIT_PRIORITY(.fini_array.*) + SORT_BY_INIT_PRIORITY(.dtors.*))) + KEEP(*(.fini_array)) + KEEP(*(.dtors)) + __fini_array_end = .; + /*BEGIN: Post-Initialization Read-Only */ . = ALIGN(__SIZEOF_POINTER__); KEEP(*(SORT_BY_NAME(.piro.relo.sort.*))) @@ -432,7 +433,7 @@ SECTIONS { KEEP(*(SORT_BY_NAME(.piro.data.sort.*))) KEEP(*(.piro.pad.data)) KEEP(*(.dataepilogue)) - . = ALIGN(512); + . = ALIGN(CONSTANT(MAXPAGESIZE)); /*END: NT FORK COPYING */ _edata = .; PROVIDE(edata = .); @@ -462,7 +463,7 @@ SECTIONS { KEEP(*(.bssepilogue)) - . = ALIGN(CONSTANT(COMMONPAGESIZE)); + . = ALIGN(CONSTANT(MAXPAGESIZE)); /*END: NT FORK COPYING */ _end = .; @@ -557,7 +558,7 @@ ape_cod_vaddr = ADDR(.head); ape_cod_paddr = LOADADDR(.head); ape_cod_filesz = SIZEOF(.head) + SIZEOF(.text); ape_cod_memsz = ape_cod_filesz; -ape_cod_align = CONSTANT(COMMONPAGESIZE); +ape_cod_align = CONSTANT(MAXPAGESIZE); ape_cod_rva = RVA(ape_cod_vaddr); ape_rom_offset = ape_cod_offset + ape_cod_filesz; @@ -565,7 +566,7 @@ ape_rom_vaddr = ADDR(.rodata); ape_rom_paddr = LOADADDR(.rodata); ape_rom_filesz = SIZEOF(.rodata) + SIZEOF(.tdata); ape_rom_memsz = ape_rom_filesz; -ape_rom_align = CONSTANT(COMMONPAGESIZE); +ape_rom_align = CONSTANT(MAXPAGESIZE); ape_rom_rva = RVA(ape_rom_vaddr); ape_ram_offset = ape_rom_offset + ape_rom_filesz; @@ -573,7 +574,7 @@ ape_ram_vaddr = ADDR(.data); ape_ram_paddr = LOADADDR(.data); ape_ram_filesz = SIZEOF(.data); ape_ram_memsz = SIZEOF(.data) + SIZEOF(.bss); -ape_ram_align = CONSTANT(COMMONPAGESIZE); +ape_ram_align = CONSTANT(MAXPAGESIZE); ape_ram_rva = RVA(ape_ram_vaddr); ape_stack_pf = DEFINED(ape_stack_pf) ? ape_stack_pf : PF_R | PF_W; @@ -595,7 +596,7 @@ ape_text_paddr = LOADADDR(.text); ape_text_vaddr = ADDR(.text); ape_text_filesz = SIZEOF(.text) + SIZEOF(.rodata) + SIZEOF(.tdata); ape_text_memsz = ape_text_filesz; -ape_text_align = CONSTANT(COMMONPAGESIZE); +ape_text_align = CONSTANT(MAXPAGESIZE); ape_text_rva = RVA(ape_text_vaddr); /* we roundup here because xnu wants the file load segments page-aligned */ @@ -604,7 +605,7 @@ ape_text_rva = RVA(ape_text_vaddr); SHSTUB2(ape_loader_dd_skip, DEFINED(ape_loader) ? RVA(ape_loader) / 64 : 0); SHSTUB2(ape_loader_dd_count, DEFINED(ape_loader_end) - ? ROUNDUP(ape_loader_end - ape_loader, CONSTANT(COMMONPAGESIZE)) / 64 + ? ROUNDUP(ape_loader_end - ape_loader, CONSTANT(MAXPAGESIZE)) / 64 : 0); #if defined(APE_IS_SHELL_SCRIPT) && !IsTiny() diff --git a/ape/idata.internal.h b/ape/idata.internal.h index 918e39bc3..80c0ef892 100644 --- a/ape/idata.internal.h +++ b/ape/idata.internal.h @@ -81,7 +81,7 @@ .size ".Lidata.idt.\name",.-".Lidata.idt.\name" .previous .section ".idata.ro.ilt.\name\().1","aG",@progbits,"\name",comdat - .align __SIZEOF_POINTER__ + .balign __SIZEOF_POINTER__ .type "idata.ilt.\name",@object "idata.ilt.\name": .previous/* @@ -92,12 +92,12 @@ .quad 0 .previous .section ".idata.ro.hnt.\name\().1","aG",@progbits,"\name",comdat - .align __SIZEOF_POINTER__ + .balign __SIZEOF_POINTER__ .type "idata.hnt.\name",@object .equ "idata.hnt.\name",. .previous .section ".piro.data.sort.iat.2.\name\().1","awG",@progbits,"\name",comdat - .align __SIZEOF_POINTER__ + .balign __SIZEOF_POINTER__ .type "idata.iat.\name",@object "idata.iat.\name": .previous/* diff --git a/build/definitions.mk b/build/definitions.mk index 2e47ab5fd..aca2e1880 100644 --- a/build/definitions.mk +++ b/build/definitions.mk @@ -252,7 +252,7 @@ DEFAULT_LDFLAGS += \ -znorelro else DEFAULT_LDFLAGS += \ - -zmax-page-size=0x10000 \ + -zmax-page-size=0x1000 \ -zcommon-page-size=0x1000 endif diff --git a/examples/hello2.c b/examples/hello2.c index e29718369..c46a5794b 100644 --- a/examples/hello2.c +++ b/examples/hello2.c @@ -11,6 +11,6 @@ #include "libc/str/str.h" int main() { - write(1, "hello world\n", 12); + // write(1, "hello world\n", 12); return 0; } diff --git a/libc/elf/struct/rela.h b/libc/elf/struct/rela.h index 2d568c4da..6d8f0e2fa 100644 --- a/libc/elf/struct/rela.h +++ b/libc/elf/struct/rela.h @@ -34,12 +34,20 @@ typedef struct Elf64_Rela { * Elf64_Word sym = ELF64_R_SYM(r_info); * Elf64_Word type = ELF64_R_TYPE(r_info); * - * Where `sym` is a symbol index, and `type` might be: + * Where `sym` is a symbol index, and `type` will likely be: * - * - `R_X86_64_64` * - `R_X86_64_PC32` - * - `R_X86_64_GOTPCRELX` - * - `R_AARCH64_ABS64` + * - `R_X86_64_PLT32` + * - `R_X86_64_32` + * - `R_X86_64_64` + * - `R_X86_64_32S` + * - `R_X86_64_8` + * - `R_X86_64_16` + * - `R_X86_64_DTPOFF32` + * - `R_X86_64_GOTPCREL` + * - `R_X86_64_PC16` + * - `R_X86_64_REX_GOTPCRELX` + * - `R_X86_64_TPOFF32` * */ Elf64_Xword r_info; diff --git a/libc/elf/struct/shdr.h b/libc/elf/struct/shdr.h index 2edff9f35..23de1b9b6 100644 --- a/libc/elf/struct/shdr.h +++ b/libc/elf/struct/shdr.h @@ -13,7 +13,7 @@ typedef struct Elf64_Shdr { Elf64_Word sh_type; /* SHT_{PROGBITS,NOBITS,STRTAB,SYMTAB,RELA,...} */ - Elf64_Xword sh_flags; /* SHF_{WRITE,ALLOC,EXECINSTR,MERGE,STRINGS,...} */ + Elf64_Xword sh_flags; /* SHF_{WRITE,ALLOC,EXECINSTR,TLS,MERGE,STRINGS,,...} */ Elf64_Addr sh_addr; diff --git a/libc/elf/struct/sym.h b/libc/elf/struct/sym.h index 8137759e4..1392a43de 100644 --- a/libc/elf/struct/sym.h +++ b/libc/elf/struct/sym.h @@ -39,6 +39,7 @@ typedef struct Elf64_Sym { * - `STT_SECTION` * - `STT_FILE` * - `STT_COMMON` + * - `STT_TLS` */ uint8_t st_info; diff --git a/libc/nt/struct/imagefileheader.internal.h b/libc/nt/struct/imagefileheader.internal.h index 0bfff84fc..e45267d10 100644 --- a/libc/nt/struct/imagefileheader.internal.h +++ b/libc/nt/struct/imagefileheader.internal.h @@ -22,7 +22,7 @@ struct NtImageFileHeader { uint32_t NumberOfSymbols; /* - * [File Size] The size of the optional header, which is required for + * [file size] The size of the optional header, which is required for * executable files but not for object files. This value should be * zero for an object file. For a description of the header format, * see Optional Header (Image Only). diff --git a/libc/nt/struct/imageoptionalheader.internal.h b/libc/nt/struct/imageoptionalheader.internal.h index 421a6ce92..b6c5a5398 100644 --- a/libc/nt/struct/imageoptionalheader.internal.h +++ b/libc/nt/struct/imageoptionalheader.internal.h @@ -41,7 +41,7 @@ struct NtImageOptionalHeader { uint32_t SizeOfUninitializedData; /* - * [Relative Virtual Address] The address of the entry point relative + * [relative virtual address] The address of the entry point relative * to the image base when the executable file is loaded into memory. * For program images, this is the starting address. For device * drivers, this is the address of the initialization function. An @@ -51,14 +51,14 @@ struct NtImageOptionalHeader { uint32_t AddressOfEntryPoint; /* - * [Relative Virtual Address] The address that is relative to the - * image base of the beginning-of-code section when it is loaded into - * memory. + * [relative virtual address; Informative] The address that is + * relative to the image base of the beginning-of-code section when it + * is loaded into memory. */ uint32_t BaseOfCode; /* - * [Virtual Address] The preferred address of the first byte + * [virtual address] The preferred address of the first byte * of image when loaded into memory; must be a multiple of 64 K. The * default for DLLs is 0x10000000. The default for Windows CE EXEs is * 0x00010000. The default for Windows NT, Windows 2000, Windows XP, @@ -91,14 +91,14 @@ struct NtImageOptionalHeader { uint32_t Win32VersionValue; /* - * [Virtual Size] The size (in bytes) of the image, including all + * [virtual size] The size (in bytes) of the image, including all * headers, as the image is loaded in memory. It must be a multiple of * SectionAlignment. */ uint32_t SizeOfImage; /* - * [File Size] The combined size of an MS-DOS stub, PE header, and + * [file size] The combined size of an MS-DOS stub, PE header, and * section headers rounded up to a multiple of FileAlignment. */ uint32_t SizeOfHeaders; diff --git a/tool/build/elf2pe.c b/tool/build/elf2pe.c new file mode 100644 index 000000000..416b05e0a --- /dev/null +++ b/tool/build/elf2pe.c @@ -0,0 +1,1073 @@ +/*-*- 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 2023 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" +#include "libc/calls/calls.h" +#include "libc/elf/def.h" +#include "libc/elf/elf.h" +#include "libc/elf/scalar.h" +#include "libc/elf/struct/rela.h" +#include "libc/elf/struct/shdr.h" +#include "libc/fmt/itoa.h" +#include "libc/intrin/bits.h" +#include "libc/intrin/describeflags.internal.h" +#include "libc/intrin/dll.h" +#include "libc/intrin/kprintf.h" +#include "libc/limits.h" +#include "libc/macros.internal.h" +#include "libc/mem/mem.h" +#include "libc/nt/pedef.internal.h" +#include "libc/nt/struct/imagedatadirectory.internal.h" +#include "libc/nt/struct/imagedosheader.internal.h" +#include "libc/nt/struct/imagefileheader.internal.h" +#include "libc/nt/struct/imageimportdescriptor.internal.h" +#include "libc/nt/struct/imageoptionalheader.internal.h" +#include "libc/nt/struct/imagesectionheader.internal.h" +#include "libc/runtime/runtime.h" +#include "libc/stdckdint.h" +#include "libc/stdio/stdio.h" +#include "libc/str/str.h" +#include "libc/sysv/consts/map.h" +#include "libc/sysv/consts/o.h" +#include "libc/sysv/consts/prot.h" +#include "third_party/getopt/getopt.internal.h" + +// see tool/hello/hello.c for an example program this can link +// make -j8 m=tiny o/tiny/tool/hello/hello.com + +#define VERSION \ + "elf2pe v0.1\n" \ + "copyright 2023 justine tunney\n" \ + "https://github.com/jart/cosmopolitan\n" + +#define MANUAL \ + " -o OUTPUT INPUT\n" \ + "\n" \ + "DESCRIPTION\n" \ + "\n" \ + " Converts ELF executables to PE\n" \ + "\n" \ + "FLAGS\n" \ + "\n" \ + " -h show usage\n" \ + " -o OUTPUT set output path\n" \ + " -D PATH embed dos/bios stub\n" \ + "\n" + +#define MAX_ALIGN 65536 + +#define ALIGN_VIRT(p, a) ROUNDUP(p, (long)(a)) +#define ALIGN_FILE(p, a) (char *)ROUNDUP((uintptr_t)(p), (long)(a)) + +#define FUNC_CONTAINER(e) DLL_CONTAINER(struct Func, elem, e) +#define LIBRARY_CONTAINER(e) DLL_CONTAINER(struct Library, elem, e) +#define SECTION_CONTAINER(e) DLL_CONTAINER(struct Section, elem, e) +#define SEGMENT_CONTAINER(e) DLL_CONTAINER(struct Segment, elem, e) + +struct ImagePointer { + char *fp; + int64_t vp; +}; + +struct Func { + const char *name; + struct Dll elem; + uint64_t *ilt; + Elf64_Sym *symbol; +}; + +struct Library { + const char *name; + struct Dll *funcs; + struct Dll elem; + struct NtImageImportDescriptor *idt; + uint64_t *ilt; + size_t iltbytes; +}; + +struct Section { + int prot; + int index; + char *name; + Elf64_Shdr *shdr; + Elf64_Rela *relas; + Elf64_Xword relacount; + struct Dll elem; +}; + +struct Segment { + int prot; + char *ptr_new; + bool hasnobits; + bool hasprogbits; + Elf64_Xword align; + Elf64_Off offset_min; + Elf64_Off offset_max; + Elf64_Addr vaddr_min; + Elf64_Addr vaddr_max; + Elf64_Addr vaddr_new_min; + Elf64_Addr vaddr_new_max; + struct Dll *sections; + struct Dll elem; + struct NtImageSectionHeader *pesection; +}; + +struct Elf { + union { + char *map; + Elf64_Ehdr *ehdr; + }; + size_t size; + const char *path; + char *strtab; + char *secstrs; + Elf64_Sym *symtab; + Elf64_Shdr *symhdr; + Elf64_Xword align; + Elf64_Xword symcount; + struct Dll *imports; + struct Dll *segments; + Elf64_Half text_count; + Elf64_Half rdata_count; + Elf64_Half data_count; + Elf64_Half bss_count; +}; + +static const char *prog; +static const char *outpath; +static const char *stubpath; + +static wontreturn void Die(const char *thing, const char *reason) { + tinyprint(2, thing, ": ", reason, "\n", NULL); + exit(1); +} + +static wontreturn void DieSys(const char *thing) { + perror(thing); + exit(1); +} + +static wontreturn void ShowUsage(int rc, int fd) { + tinyprint(fd, VERSION, "\nUSAGE\n\n ", prog, MANUAL, NULL); + exit(rc); +} + +static wontreturn void DieOom(void) { + Die("makepe", "out of memory"); +} + +static void *Calloc(size_t n) { + void *p; + if (!(p = calloc(1, n))) DieOom(); + return p; +} + +static void *Realloc(void *p, size_t n) { + if (!(p = realloc(p, n))) DieOom(); + return p; +} + +static struct Func *NewFunc(void) { + struct Func *s; + s = Calloc(sizeof(struct Func)); + dll_init(&s->elem); + return s; +} + +static struct Library *NewLibrary(void) { + struct Library *s; + s = Calloc(sizeof(struct Library)); + dll_init(&s->elem); + return s; +} + +static struct Section *NewSection(void) { + struct Section *s; + s = Calloc(sizeof(struct Section)); + dll_init(&s->elem); + return s; +} + +static struct Segment *NewSegment(void) { + struct Segment *s; + s = Calloc(sizeof(struct Segment)); + dll_init(&s->elem); + return s; +} + +static Elf64_Addr RelocateVaddrWithinSegment(struct Elf *elf, + Elf64_Addr vaddr_old, + struct Segment *segment) { + unassert(segment->vaddr_min <= vaddr_old && vaddr_old < segment->vaddr_max); + Elf64_Addr vaddr_new = + vaddr_old + (segment->vaddr_new_min - segment->vaddr_min); + unassert(segment->vaddr_new_min <= vaddr_new && + vaddr_new < segment->vaddr_new_max); + return vaddr_new; +} + +static Elf64_Addr RelocateVaddr(struct Elf *elf, Elf64_Addr vaddr) { + for (struct Dll *e = dll_first(elf->segments); e; + e = dll_next(elf->segments, e)) { + struct Segment *segment = SEGMENT_CONTAINER(e); + if (segment->vaddr_min <= vaddr && vaddr < segment->vaddr_max) { + return RelocateVaddrWithinSegment(elf, vaddr, segment); + } + } + return -1; +} + +static Elf64_Phdr *GetTlsPhdr(struct Elf *elf) { + Elf64_Phdr *phdr; + for (int i = 0; i < elf->ehdr->e_phnum; ++i) { + if ((phdr = GetElfProgramHeaderAddress(elf->ehdr, elf->size, i)) && + phdr->p_type == PT_TLS) { + return phdr; + } + } + Die(elf->path, "ELF has TLS relocations but no PT_TLS program header"); +} + +static Elf64_Addr RelocateTlsVaddr(struct Elf *elf, Elf64_Addr vaddr) { + Elf64_Addr res; + if ((res = RelocateVaddr(elf, vaddr)) != -1) { + return res; + } else { + Die(elf->path, "ELF PT_TLS program header doesn't overlap with any of " + "the loaded segments we're copying"); + } +} + +static Elf64_Addr GetTpAddr(struct Elf *elf) { + unassert(elf->ehdr->e_machine == EM_NEXGEN32E || + elf->ehdr->e_machine == EM_S390); + Elf64_Phdr *p = GetTlsPhdr(elf); + return RelocateTlsVaddr( + elf, (p->p_vaddr + p->p_memsz + (p->p_align - 1)) & -p->p_align); +} + +static Elf64_Addr GetDtpAddr(struct Elf *elf) { + unassert(elf->ehdr->e_machine != EM_PPC64 && + elf->ehdr->e_machine != EM_RISCV); + Elf64_Phdr *p = GetTlsPhdr(elf); + return RelocateTlsVaddr(elf, p->p_vaddr); +} + +static void RelocateRela(struct Elf *elf, struct Segment *segment, + struct Section *section, Elf64_Rela *rela) { + Elf64_Addr place_vaddr = + RelocateVaddrWithinSegment(elf, rela->r_offset, segment); + Elf64_Addr symbol_vaddr = elf->symtab[ELF64_R_SYM(rela->r_info)].st_value; + char *place_ptr = segment->ptr_new + (place_vaddr - segment->vaddr_new_min); + Elf64_Sxword addend = rela->r_addend; + switch (ELF64_R_TYPE(rela->r_info)) { + case R_X86_64_NONE: // do nothing + case R_X86_64_COPY: // do nothing + case R_X86_64_SIZE32: // isn't impacted + case R_X86_64_SIZE64: // isn't impacted + break; + case R_X86_64_64: { // S + A + uint64_t abs64; + if (ckd_add(&abs64, symbol_vaddr, rela->r_addend)) { + fprintf(stderr, + "%s: ELF R_X86_64_64 relocation %lx + %ld overflowed " + "at %lx in section %s\n", + elf->path, symbol_vaddr, rela->r_addend, place_vaddr, + section->name); + exit(1); + } + memcpy(place_ptr, &abs64, sizeof(abs64)); + break; + } + case R_X86_64_32: { // S + A + uint32_t abs32; + if (ckd_add(&abs32, symbol_vaddr, rela->r_addend)) { + fprintf(stderr, + "%s: ELF R_X86_64_32 relocation %lx + %ld overflowed " + "at %lx in section %s\n", + elf->path, symbol_vaddr, rela->r_addend, place_vaddr, + section->name); + exit(1); + } + memcpy(place_ptr, &abs32, sizeof(abs32)); + break; + } + case R_X86_64_32S: { // S + A + int32_t abs32s; + if (ckd_add(&abs32s, symbol_vaddr, rela->r_addend)) { + fprintf(stderr, + "%s: ELF R_X86_64_32S relocation %lx + %ld overflowed " + "at %lx in section %s\n", + elf->path, symbol_vaddr, rela->r_addend, place_vaddr, + section->name); + exit(1); + } + memcpy(place_ptr, &abs32s, sizeof(abs32s)); + break; + } + case R_X86_64_16: { // S + A + uint16_t abs16; + if (ckd_add(&abs16, symbol_vaddr, rela->r_addend)) { + fprintf(stderr, + "%s: ELF R_X86_64_16 relocation %lx + %ld overflowed " + "at %lx in section %s\n", + elf->path, symbol_vaddr, rela->r_addend, place_vaddr, + section->name); + exit(1); + } + memcpy(place_ptr, &abs16, sizeof(abs16)); + break; + } + case R_X86_64_8: { // S + A + uint8_t abs8; + if (ckd_add(&abs8, symbol_vaddr, rela->r_addend)) { + fprintf(stderr, + "%s: ELF R_X86_64_8 relocation %lx + %ld overflowed " + "at %lx in section %s\n", + elf->path, symbol_vaddr, rela->r_addend, place_vaddr, + section->name); + exit(1); + } + memcpy(place_ptr, &abs8, sizeof(abs8)); + break; + } + case R_X86_64_PC64: { // S + A - P + int64_t pc64; + Elf64_Sxword tmp; + if (ckd_add(&tmp, symbol_vaddr, rela->r_addend) || + ckd_sub(&pc64, tmp, place_vaddr)) { + fprintf(stderr, + "%s: ELF R_X86_64_PC64 relocation %lx + %ld - %lx overflowed " + "at %lx in section %s\n", + elf->path, symbol_vaddr, rela->r_addend, place_vaddr, + place_vaddr, section->name); + exit(1); + } + memcpy(place_ptr, &pc64, sizeof(pc64)); + break; + } + case R_X86_64_PC32: // S + A - P + case R_X86_64_PLT32: { + int32_t pc32; + Elf64_Sxword tmp; + if (ckd_add(&tmp, symbol_vaddr, rela->r_addend) || + ckd_sub(&pc32, tmp, place_vaddr)) { + fprintf(stderr, + "%s: ELF R_X86_64_PC32 relocation %lx + %ld - %lx overflowed " + "at %lx in section %s\n", + elf->path, symbol_vaddr, rela->r_addend, place_vaddr, + place_vaddr, section->name); + exit(1); + } + memcpy(place_ptr, &pc32, sizeof(pc32)); + break; + } + case R_X86_64_PC16: { // S + A - P + int16_t pc16; + Elf64_Sxword tmp; + if (ckd_add(&tmp, symbol_vaddr, rela->r_addend) || + ckd_sub(&pc16, tmp, place_vaddr)) { + fprintf(stderr, + "%s: ELF R_X86_64_PC16 relocation %lx + %ld - %lx overflowed " + "at %lx in section %s\n", + elf->path, symbol_vaddr, rela->r_addend, place_vaddr, + place_vaddr, section->name); + exit(1); + } + memcpy(place_ptr, &pc16, sizeof(pc16)); + break; + } + case R_X86_64_PC8: { // S + A - P + int8_t pc8; + Elf64_Sxword tmp; + if (ckd_add(&tmp, symbol_vaddr, rela->r_addend) || + ckd_sub(&pc8, tmp, place_vaddr)) { + fprintf(stderr, + "%s: ELF R_X86_64_PC8 relocation %lx + %ld - %lx overflowed " + "at %lx in section %s\n", + elf->path, symbol_vaddr, rela->r_addend, place_vaddr, + place_vaddr, section->name); + exit(1); + } + memcpy(place_ptr, &pc8, sizeof(pc8)); + break; + } + case R_X86_64_DTPOFF32: { // S + A - T + int32_t pc32; + Elf64_Addr dtp; + Elf64_Sxword tmp; + dtp = GetDtpAddr(elf); + if (ckd_add(&tmp, symbol_vaddr, rela->r_addend) || + ckd_sub(&pc32, tmp, dtp)) { + fprintf( + stderr, + "%s: ELF R_X86_64_DTPOFF32 relocation %lx + %ld - %lx overflowed " + "at %lx in section %s\n", + elf->path, symbol_vaddr, rela->r_addend, dtp, place_vaddr, + section->name); + exit(1); + } + memcpy(place_ptr, &pc32, sizeof(pc32)); + break; + } + case R_X86_64_DTPOFF64: { // S + A - T + int64_t pc64; + Elf64_Addr dtp; + Elf64_Sxword tmp; + dtp = GetDtpAddr(elf); + if (ckd_add(&tmp, symbol_vaddr, rela->r_addend) || + ckd_sub(&pc64, tmp, dtp)) { + fprintf( + stderr, + "%s: ELF R_X86_64_DTPOFF64 relocation %lx + %ld - %lx overflowed " + "at %lx in section %s\n", + elf->path, symbol_vaddr, rela->r_addend, dtp, place_vaddr, + section->name); + exit(1); + } + memcpy(place_ptr, &pc64, sizeof(pc64)); + break; + } + case R_X86_64_TPOFF32: { // S + A - T + int32_t pc32; + Elf64_Addr tp; + Elf64_Sxword tmp; + tp = GetTpAddr(elf); + if (ckd_add(&tmp, symbol_vaddr, rela->r_addend) || + ckd_sub(&pc32, tmp, tp)) { + fprintf( + stderr, + "%s: ELF R_X86_64_TPOFF32 relocation %lx + %ld - %lx overflowed " + "at %lx in section %s\n", + elf->path, symbol_vaddr, rela->r_addend, tp, place_vaddr, + section->name); + exit(1); + } + memcpy(place_ptr, &pc32, sizeof(pc32)); + break; + } + case R_X86_64_TPOFF64: { // S + A - T + int64_t pc64; + Elf64_Addr tp; + Elf64_Sxword tmp; + tp = GetTpAddr(elf); + if (ckd_add(&tmp, symbol_vaddr, rela->r_addend) || + ckd_sub(&pc64, tmp, tp)) { + fprintf( + stderr, + "%s: ELF R_X86_64_TPOFF64 relocation %lx + %ld - %lx overflowed " + "at %lx in section %s\n", + elf->path, symbol_vaddr, rela->r_addend, tp, place_vaddr, + section->name); + exit(1); + } + memcpy(place_ptr, &pc64, sizeof(pc64)); + break; + } + default: + fprintf(stderr, "%s: don't understand ELF relocation type %d\n", + elf->path, ELF64_R_TYPE(rela->r_info)); + } +} + +static void RelocateSection(struct Elf *elf, struct Segment *segment, + struct Section *section) { + for (Elf64_Xword i = 0; i < section->relacount; ++i) { + RelocateRela(elf, segment, section, section->relas + i); + } +} + +static void RelocateSegment(struct Elf *elf, struct Segment *segment) { + unassert(segment->ptr_new); + unassert(segment->hasprogbits); + for (struct Dll *e = dll_first(segment->sections); e; + e = dll_next(segment->sections, e)) { + struct Section *section = SECTION_CONTAINER(e); + RelocateSection(elf, segment, section); + } +} + +static void RelocateElf(struct Elf *elf) { + for (Elf64_Xword i = 0; i < elf->symcount; ++i) { + if (elf->symtab[i].st_shndx != SHN_ABS && + elf->symtab[i].st_shndx != SHN_UNDEF && + ELF64_ST_TYPE(elf->symtab[i].st_info) != STT_TLS) { + elf->symtab[i].st_value = RelocateVaddr(elf, elf->symtab[i].st_value); + } + } + for (struct Dll *e = dll_first(elf->segments); e; + e = dll_next(elf->segments, e)) { + struct Segment *segment = SEGMENT_CONTAINER(e); + if (segment->hasprogbits) { + RelocateSegment(elf, segment); + } + } +} + +static struct Elf64_Sym *FindGlobal(struct Elf *elf, const char *name) { + Elf64_Xword i; + for (i = elf->symhdr->sh_info; i < elf->symcount; ++i) { + if (elf->symtab[i].st_name && + !strcmp(elf->strtab + elf->symtab[i].st_name, name)) { + return elf->symtab + i; + } + } + return 0; +} + +static int GetRelaSectionIndex(struct Elf *elf, int i) { + int j; + Elf64_Shdr *shdr; + for (j = 0; j < elf->ehdr->e_shnum; ++j) { + if ((shdr = GetElfSectionHeaderAddress(elf->ehdr, elf->size, j)) && + shdr->sh_type == SHT_RELA && shdr->sh_info == i) { + return j; + } + } + return -1; +} + +static struct Section *LoadSection(struct Elf *elf, int index, + Elf64_Shdr *shdr) { + int rela_index; + Elf64_Shdr *rela_shdr; + struct Section *section; + section = NewSection(); + section->index = index; + section->shdr = shdr; + section->prot = PROT_READ; + section->name = elf->secstrs + shdr->sh_name; + if (shdr->sh_flags & SHF_WRITE) { + section->prot |= PROT_WRITE; + } + if (shdr->sh_flags & SHF_EXECINSTR) { + section->prot |= PROT_EXEC; + } + if ((rela_index = GetRelaSectionIndex(elf, index)) != -1 && + (rela_shdr = + GetElfSectionHeaderAddress(elf->ehdr, elf->size, rela_index)) && + (section->relas = + GetElfSectionAddress(elf->ehdr, elf->size, rela_shdr))) { + section->relacount = rela_shdr->sh_size / sizeof(Elf64_Rela); + } + return section; +} + +static void LoadSectionsIntoSegments(struct Elf *elf) { + int i; + Elf64_Shdr *shdr; + struct Segment *segment = 0; + for (i = 0; i < elf->ehdr->e_shnum; ++i) { + if ((shdr = GetElfSectionHeaderAddress(elf->ehdr, elf->size, i)) && + (shdr->sh_type == SHT_PROGBITS || shdr->sh_type == SHT_NOBITS) && + !((shdr->sh_flags & SHF_TLS) && shdr->sh_type == SHT_NOBITS) && + (shdr->sh_flags & SHF_ALLOC)) { + struct Section *section; + section = LoadSection(elf, i, shdr); + if (segment && (segment->prot != section->prot || + (segment->hasnobits && shdr->sh_type == SHT_PROGBITS))) { + dll_make_last(&elf->segments, &segment->elem); + segment = 0; + } + if (!segment) { + segment = NewSegment(); + segment->prot = section->prot; + segment->vaddr_min = section->shdr->sh_addr; + if (shdr->sh_type == SHT_PROGBITS) + segment->offset_min = section->shdr->sh_offset; + } + segment->hasnobits |= shdr->sh_type == SHT_NOBITS; + segment->hasprogbits |= shdr->sh_type == SHT_PROGBITS; + segment->vaddr_max = section->shdr->sh_addr + section->shdr->sh_size; + if (shdr->sh_type == SHT_PROGBITS) + segment->offset_max = section->shdr->sh_offset + section->shdr->sh_size; + segment->align = MAX(segment->align, section->shdr->sh_addralign); + elf->align = MAX(elf->align, segment->align); + dll_make_last(&segment->sections, §ion->elem); + } + } + if (segment) { + dll_make_last(&elf->segments, &segment->elem); + } +} + +static bool ParseDllImportSymbol(const char *symbol_name, + const char **out_dll_name, + const char **out_func_name) { + size_t n; + char *dll_name; + const char *dolla; + if (!_startswith(symbol_name, "dll$")) return false; + symbol_name += 4; + dolla = strchr(symbol_name, '$'); + if (!dolla) return false; + n = dolla - symbol_name; + dll_name = memcpy(Calloc(n + 1), symbol_name, n); + *out_dll_name = dll_name; + *out_func_name = dolla + 1; + return true; +} + +static struct Library *FindImport(struct Elf *elf, const char *name) { + struct Dll *e; + for (e = dll_first(elf->imports); e; e = dll_next(elf->imports, e)) { + struct Library *library = LIBRARY_CONTAINER(e); + if (!strcmp(name, library->name)) { + return library; + } + } + return 0; +} + +static void LoadDllImports(struct Elf *elf) { + Elf64_Xword i; + struct Func *func; + const char *dll_name; + const char *func_name; + struct Library *library; + for (i = 0; i < elf->symcount; ++i) { + Elf64_Sym *symbol = elf->symtab + i; + if (symbol->st_name && ParseDllImportSymbol(elf->strtab + symbol->st_name, + &dll_name, &func_name)) { + if (symbol->st_value || symbol->st_shndx != SHN_UNDEF) + Die(elf->path, "ELF executable declared a dll: import symbol without " + "annotating it weak extern"); + if (!(library = FindImport(elf, dll_name))) { + library = NewLibrary(); + library->name = dll_name; + dll_make_last(&elf->imports, &library->elem); + } + func = NewFunc(); + func->name = func_name; + func->symbol = symbol; + dll_make_last(&library->funcs, &func->elem); + } + } +} + +static struct Elf *OpenElf(const char *path) { + int fd; + struct Elf *elf; + elf = Calloc(sizeof(*elf)); + elf->path = path; + if ((fd = open(path, O_RDONLY)) == -1) DieSys(path); + if ((elf->size = lseek(fd, 0, SEEK_END)) == -1) DieSys(path); + elf->map = mmap(0, elf->size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); + if (elf->map == MAP_FAILED) DieSys(path); + if (!IsElf64Binary(elf->ehdr, elf->size)) Die(path, "not an elf64 binary"); + elf->symhdr = + GetElfSymbolTable(elf->ehdr, elf->size, SHT_SYMTAB, &elf->symcount); + elf->symtab = GetElfSectionAddress(elf->ehdr, elf->size, elf->symhdr); + if (!elf->symtab) Die(path, "elf doesn't have symbol table"); + elf->strtab = GetElfStringTable(elf->ehdr, elf->size, ".strtab"); + if (!elf->strtab) Die(path, "elf doesn't have string table"); + elf->secstrs = GetElfSectionNameStringTable(elf->ehdr, elf->size); + if (!elf->strtab) Die(path, "elf doesn't have section string table"); + LoadSectionsIntoSegments(elf); + LoadDllImports(elf); + close(fd); + return elf; +} + +static void PrintElf(struct Elf *elf) { + struct Dll *e, *g; + printf("\n"); + printf("%s\n", elf->path); + printf("sections\n"); + for (e = dll_first(elf->segments); e; e = dll_next(elf->segments, e)) { + struct Segment *segment = SEGMENT_CONTAINER(e); + for (g = dll_first(segment->sections); g; + g = dll_next(segment->sections, g)) { + struct Section *section = SECTION_CONTAINER(g); + printf("\t%s\n", section->name); + } + printf("\t\talign = %ld\n", segment->align); + printf("\t\tvaddr_old = %lx\n", segment->vaddr_min); + printf("\t\tvaddr_new = %lx\n", segment->vaddr_new_min); + printf("\t\tprot = %s\n", DescribeProtFlags(segment->prot)); + } + printf("imports\n"); + for (e = dll_first(elf->imports); e; e = dll_next(elf->imports, e)) { + struct Library *library = LIBRARY_CONTAINER(e); + printf("\t%s\n", library->name); + for (g = dll_first(library->funcs); g; g = dll_next(library->funcs, g)) { + struct Func *func = FUNC_CONTAINER(g); + printf("\t\t%s\n", func->name); + } + } +} + +static void PickPeSectionName(char *p, struct Elf *elf, + struct Segment *segment) { + int n; + switch (segment->prot) { + case PROT_EXEC: + case PROT_EXEC | PROT_READ: + case PROT_EXEC | PROT_READ | PROT_WRITE: + p = stpcpy(p, ".text"); + n = ++elf->text_count; + break; + case PROT_READ: + p = stpcpy(p, ".rdata"); + n = ++elf->rdata_count; + break; + case PROT_READ | PROT_WRITE: + if (segment->hasprogbits) { + p = stpcpy(p, ".data"); + n = ++elf->data_count; + } else { + p = stpcpy(p, ".bss"); + n = ++elf->bss_count; + } + break; + default: + notpossible; + } + if (n > 1) { + FormatInt32(p, n); + } +} + +static uint32_t GetPeSectionCharacteristics(struct Segment *s) { + uint32_t x = 0; + if (s->prot & PROT_EXEC) x |= kNtPeSectionCntCode | kNtPeSectionMemExecute; + if (s->prot & PROT_READ) x |= kNtPeSectionMemRead; + if (s->prot & PROT_WRITE) x |= kNtPeSectionMemWrite; + if (s->hasnobits) x |= kNtPeSectionCntUninitializedData; + if (s->hasprogbits) x |= kNtPeSectionCntInitializedData; + return x; +} + +// converts static elf executable to portable executable +// +// the trick to generating a portable executable is to maintain the file +// pointer and virtual address pointers separately, as the image is made +// +// during this process, we're going to be inserting and removing padding +// to both the file layout and virtual address space, that weren't there +// originally in the elf image that ld linked. in order for this to work +// the executable needs to be linked in `ld -q` mode, since it'll retain +// the .rela sections we'll need later to fixup the binary. +static struct ImagePointer GeneratePe(struct Elf *elf, char *fp, int64_t vp) { + + Elf64_Sym *entry; + if (!(entry = FindGlobal(elf, "__win32_start")) && + !(entry = FindGlobal(elf, "WinMain"))) + Die(elf->path, "ELF didn't define global `__win32_start` PE entrypoint or " + "alternatively a `WinMain` function"); + + if (elf->align > MAX_ALIGN) + Die(elf->path, "ELF specified an alignment greater than 64k which isn't " + "supported by the PE format"); + + vp = ALIGN_VIRT(vp, 65536); + struct NtImageDosHeader *mzhdr; + mzhdr = (struct NtImageDosHeader *)fp; + fp += sizeof(struct NtImageDosHeader); + memcpy(mzhdr, "MZ", 2); + /* memcpy(mzhdr, "MZqFpD='\n\n", 10); */ + /* mzhdr->e_oemid = 'J' | 'T' << 8; */ + /* memcpy(mzhdr->e_res2, "' <<'@'\n", 8); */ + + // embed the ms-dos stub and/or bios bootloader + if (stubpath) { + int fd = open(stubpath, O_RDONLY); + if (fd == -1) DieSys(stubpath); + for (;;) { + ssize_t got = read(fd, fp, 512); + if (got == -1) DieSys(stubpath); + if (!got) break; + fp += got; + } + if (close(fd)) DieSys(stubpath); + } + + // begin the shell script + /* fp = stpcpy(fp, "\n@\n" */ + /* "#'\"\n"); */ + + // output portable executable magic + fp = ALIGN_FILE(fp, 8); + mzhdr->e_lfanew = fp - (char *)mzhdr; + fp = WRITE32LE(fp, 'P' | 'E' << 8); + + // output coff header + struct NtImageFileHeader *filehdr; + filehdr = (struct NtImageFileHeader *)fp; + fp += sizeof(struct NtImageFileHeader); + filehdr->Machine = kNtImageFileMachineNexgen32e; + filehdr->TimeDateStamp = 1690072024; + filehdr->Characteristics = + kNtPeFileExecutableImage | kNtImageFileLargeAddressAware | + kNtPeFileRelocsStripped | kNtPeFileLineNumsStripped | + kNtPeFileLocalSymsStripped; + + // output "optional" header + struct NtImageOptionalHeader *opthdr; + opthdr = (struct NtImageOptionalHeader *)fp; + fp += sizeof(struct NtImageOptionalHeader); + opthdr->Magic = kNtPe64bit; + opthdr->MajorLinkerVersion = 14; + opthdr->MinorLinkerVersion = 35; + opthdr->ImageBase = vp; + opthdr->FileAlignment = 512; + opthdr->SectionAlignment = MAX(4096, elf->align); + opthdr->MajorOperatingSystemVersion = 6; + opthdr->MajorSubsystemVersion = 6; + opthdr->Subsystem = kNtImageSubsystemWindowsCui; + opthdr->DllCharacteristics = kNtImageDllcharacteristicsNxCompat | + kNtImageDllcharacteristicsHighEntropyVa; + opthdr->SizeOfStackReserve = 8 * 1024 * 1024; + opthdr->SizeOfStackCommit = 64 * 1024; + + // output data directory entries + if (elf->imports) { + opthdr->NumberOfRvaAndSizes = 2; + fp += opthdr->NumberOfRvaAndSizes * sizeof(struct NtImageDataDirectory); + } + + // finish optional header + filehdr->SizeOfOptionalHeader = fp - (char *)opthdr; + + // output section headers + struct NtImageSectionHeader *sections; + sections = (struct NtImageSectionHeader *)fp; + struct NtImageSectionHeader *idata_section; + idata_section = (struct NtImageSectionHeader *)fp; + if (elf->imports) { + fp += sizeof(struct NtImageSectionHeader); + ++filehdr->NumberOfSections; + } + for (struct Dll *e = dll_first(elf->segments); e; + e = dll_next(elf->segments, e)) { + struct Segment *segment = SEGMENT_CONTAINER(e); + segment->pesection = (struct NtImageSectionHeader *)fp; + fp += sizeof(struct NtImageSectionHeader); + ++filehdr->NumberOfSections; + } + + // finish headers + fp = ALIGN_FILE(fp, opthdr->FileAlignment); + opthdr->SizeOfHeaders = fp - (char *)mzhdr; + vp += opthdr->SizeOfHeaders; + + // output .idata section + if (elf->imports) { + vp = ALIGN_VIRT(vp, opthdr->SectionAlignment); + char *fbegin = fp; + int64_t vbegin = vp; + idata_section->VirtualAddress = vp - opthdr->ImageBase; + idata_section->PointerToRawData = fbegin - (char *)mzhdr; + ////////////////////////////////////////////////////////////////////// + strcpy(idata_section->Name, ".idata"); + idata_section->Characteristics = + kNtPeSectionMemRead | kNtPeSectionCntInitializedData; + // output import descriptor for each dll + for (struct Dll *e = dll_first(elf->imports); e; + e = dll_next(elf->imports, e)) { + struct Library *library = LIBRARY_CONTAINER(e); + library->idt = (struct NtImageImportDescriptor *)fp; + fp += sizeof(struct NtImageImportDescriptor); + vp += sizeof(struct NtImageImportDescriptor); + } + fp += sizeof(struct NtImageImportDescriptor); + vp += sizeof(struct NtImageImportDescriptor); + opthdr->DataDirectory[kNtImageDirectoryEntryImport].VirtualAddress = + idata_section->VirtualAddress; + opthdr->DataDirectory[kNtImageDirectoryEntryImport].Size = vp - vbegin; + // output import lookup table for each dll + for (struct Dll *e = dll_first(elf->imports); e; + e = dll_next(elf->imports, e)) { + struct Library *library = LIBRARY_CONTAINER(e); + library->idt->ImportLookupTable = vp - opthdr->ImageBase; + library->ilt = (uint64_t *)fp; + for (struct Dll *g = dll_first(library->funcs); g; + g = dll_next(library->funcs, g)) { + struct Func *func = FUNC_CONTAINER(g); + func->ilt = (uint64_t *)fp; + fp += sizeof(uint64_t); + vp += sizeof(uint64_t); + } + fp += sizeof(uint64_t); + vp += sizeof(uint64_t); + library->iltbytes = fp - (char *)library->ilt; + } + // output the hint name table + for (struct Dll *e = dll_first(elf->imports); e; + e = dll_next(elf->imports, e)) { + struct Library *library = LIBRARY_CONTAINER(e); + for (struct Dll *g = dll_first(library->funcs); g; + g = dll_next(library->funcs, g)) { + struct Func *func = FUNC_CONTAINER(g); + *func->ilt = vp - opthdr->ImageBase; + fp += sizeof(uint16_t); // hint + vp += sizeof(uint16_t); + size_t n = strlen(func->name); + fp = mempcpy(fp, func->name, n + 1); + vp += n + 1; + fp = ALIGN_FILE(fp, 2); + vp = ALIGN_VIRT(vp, 2); + } + } + // output the dll names + for (struct Dll *e = dll_first(elf->imports); e; + e = dll_next(elf->imports, e)) { + struct Library *library = LIBRARY_CONTAINER(e); + size_t n = strlen(library->name); + library->idt->DllNameRva = vp - opthdr->ImageBase; + fp = mempcpy(fp, library->name, n + 1); + vp += n + 1; + } + ////////////////////////////////////////////////////////////////////// + idata_section->Misc.VirtualSize = vp - vbegin; + fp = ALIGN_FILE(fp, opthdr->FileAlignment); + idata_section->SizeOfRawData = fp - fbegin; + } + + // output elf segment sections + for (struct Dll *e = dll_first(elf->segments); e; + e = dll_next(elf->segments, e)) { + struct Segment *segment = SEGMENT_CONTAINER(e); + vp = ALIGN_VIRT(vp, opthdr->SectionAlignment); + char *fbegin = fp; + int64_t vbegin = vp; + segment->pesection->VirtualAddress = vp - opthdr->ImageBase; + segment->pesection->PointerToRawData = fbegin - (char *)mzhdr; + ////////////////////////////////////////////////////////////////////// + if (segment->prot == (PROT_READ | PROT_WRITE)) { + // sneak InputAddressTable into .data or .bss section + for (struct Dll *e = dll_first(elf->imports); e; + e = dll_next(elf->imports, e)) { + struct Library *library = LIBRARY_CONTAINER(e); + library->idt->ImportAddressTable = vp - opthdr->ImageBase; + fp = mempcpy(fp, library->ilt, library->iltbytes); + for (struct Dll *g = dll_first(library->funcs); g; + g = dll_next(library->funcs, g)) { + struct Func *func = FUNC_CONTAINER(g); + func->symbol->st_value = vp; + vp += 8; + } + vp += 8; + } + fp = ALIGN_FILE(fp, segment->align); + vp = ALIGN_VIRT(vp, segment->align); + } + PickPeSectionName(segment->pesection->Name, elf, segment); + segment->vaddr_new_min = vp; + if (segment->vaddr_min <= entry->st_value && + entry->st_value < segment->vaddr_max) { + opthdr->AddressOfEntryPoint = + vp + (entry->st_value - segment->vaddr_min) - opthdr->ImageBase; + } + if (segment->hasprogbits) { + segment->ptr_new = fp; + fp = mempcpy(fp, elf->map + segment->offset_min, + segment->offset_max - segment->offset_min); + } + vp += segment->vaddr_max - segment->vaddr_min; + segment->vaddr_new_max = vp; + segment->pesection->Characteristics = GetPeSectionCharacteristics(segment); + ////////////////////////////////////////////////////////////////////// + segment->pesection->Misc.VirtualSize = vp - vbegin; + fp = ALIGN_FILE(fp, opthdr->FileAlignment); + segment->pesection->SizeOfRawData = fp - fbegin; + } + + // compute relocations + RelocateElf(elf); + + // compute informative sizes + // the windows executive ignores these fields, but they can't hurt. + for (int i = 0; i < filehdr->NumberOfSections; ++i) { + if (sections[i].Characteristics & kNtPeSectionCntCode) { + opthdr->SizeOfCode += sections[i].SizeOfRawData; + if (!opthdr->BaseOfCode) { + opthdr->BaseOfCode = sections[i].VirtualAddress; + } + } + if (sections[i].Characteristics & kNtPeSectionCntInitializedData) { + opthdr->SizeOfInitializedData += sections[i].SizeOfRawData; + } + if ((sections[i].Characteristics & kNtPeSectionCntUninitializedData) && + sections[i].Misc.VirtualSize > sections[i].SizeOfRawData) { + opthdr->SizeOfUninitializedData += + sections[i].Misc.VirtualSize - sections[i].SizeOfRawData; + } + } + + // finish image + vp = ALIGN_VIRT(vp, opthdr->SectionAlignment); + opthdr->SizeOfImage = vp - opthdr->ImageBase; + return (struct ImagePointer){fp, vp}; +} + +static void GetOpts(int argc, char *argv[]) { + int opt; + char *endptr; + while ((opt = getopt(argc, argv, "ho:D:")) != -1) { + switch (opt) { + case 'o': + outpath = optarg; + break; + case 'D': + stubpath = optarg; + break; + case 'h': + ShowUsage(0, 1); + default: + ShowUsage(1, 2); + } + } + if (!outpath) { + Die(prog, "need output path"); + } + if (optind == argc) { + Die(prog, "missing input argument"); + } +} + +static void Pwrite(int fd, const void *data, size_t size, uint64_t offset) { + ssize_t rc; + const char *p, *e; + for (p = data, e = p + size; p < e; p += (size_t)rc, offset += (size_t)rc) { + if ((rc = pwrite(fd, p, e - p, offset)) == -1) { + DieSys(outpath); + } + } +} + +int main(int argc, char *argv[]) { +#ifndef NDEBUG + ShowCrashReports(); +#endif + + // get program name + prog = argv[0]; + if (!prog) prog = "elf2pe"; + + // process flags + GetOpts(argc, argv); + + // translate executable + struct Elf *elf = OpenElf(argv[optind]); + char *buf = memalign(MAX_ALIGN, INT_MAX); + struct ImagePointer ip = GeneratePe(elf, buf, 0x00400000); + if (creat(outpath, 0755) == -1) DieSys(elf->path); + Pwrite(3, buf, ip.fp - buf, 0); + if (close(3)) DieSys(elf->path); +} diff --git a/tool/build/pecheck.c b/tool/build/pecheck.c new file mode 100644 index 000000000..6665ce0d0 --- /dev/null +++ b/tool/build/pecheck.c @@ -0,0 +1,215 @@ +/*-*- 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 2023 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" +#include "libc/intrin/bits.h" +#include "libc/limits.h" +#include "libc/nt/struct/imageimportbyname.internal.h" +#include "libc/nt/struct/imageimportdescriptor.internal.h" +#include "libc/nt/struct/imagentheaders.internal.h" +#include "libc/nt/struct/imageoptionalheader.internal.h" +#include "libc/nt/struct/imagesectionheader.internal.h" +#include "libc/runtime/runtime.h" +#include "libc/stdckdint.h" +#include "libc/stdio/stdio.h" +#include "libc/sysv/consts/map.h" +#include "libc/sysv/consts/o.h" +#include "libc/sysv/consts/prot.h" + +static wontreturn void Die(const char *thing, const char *reason) { + tinyprint(2, thing, ": ", reason, "\n", NULL); + exit(1); +} + +static wontreturn void DieSys(const char *thing) { + perror(thing); + exit(1); +} + +static void CheckPe(const char *path, char *map, size_t size) { + + int pagesz = 4096; + + // sanity check mz header + if (size < 64) // + Die(path, "Image too small for MZ header"); + if (READ16LE(map) != ('M' | 'Z' << 8)) + Die(path, "Image doesn't start with MZ"); + uint32_t pe_offset; + if ((pe_offset = READ32LE(map + 60)) >= size) + Die(path, "PE header offset points past end of image"); + if (pe_offset + sizeof(struct NtImageNtHeaders) > size) + Die(path, "PE mandatory headers overlap end of image"); + struct NtImageNtHeaders *pe = (struct NtImageNtHeaders *)(map + pe_offset); + if ((pe_offset + sizeof(struct NtImageFileHeader) + 4 + + pe->FileHeader.SizeOfOptionalHeader) > size) + Die(path, "PE optional header size overlaps end of image"); + + // sanity check pe header + if (pe->Signature != ('P' | 'E' << 8)) + Die(path, "PE Signature must be 0x00004550"); + if (!(pe->FileHeader.Characteristics & kNtPeFileExecutableImage)) + Die(path, "PE Characteristics must have executable bit set"); + if (pe->FileHeader.Characteristics & kNtPeFileDll) + Die(path, "PE Characteristics can't have DLL bit set"); + if (pe->FileHeader.NumberOfSections < 1) + Die(path, "PE NumberOfSections >= 1 must be the case"); + if (pe->OptionalHeader.Magic != kNtPe64bit) + Die(path, "PE OptionalHeader Magic must be 0x020b"); + if (pe->OptionalHeader.FileAlignment < 512) + Die(path, "PE FileAlignment must be at least 512"); + if (pe->OptionalHeader.FileAlignment > 65536) + Die(path, "PE FileAlignment can't exceed 65536"); + if (pe->OptionalHeader.FileAlignment & (pe->OptionalHeader.FileAlignment - 1)) + Die(path, "PE FileAlignment must be a two power"); + if (pe->OptionalHeader.SectionAlignment & + (pe->OptionalHeader.SectionAlignment - 1)) + Die(path, "PE SectionAlignment must be a two power"); + if (pe->OptionalHeader.SectionAlignment < pe->OptionalHeader.FileAlignment) + Die(path, "PE SectionAlignment >= FileAlignment must be the case"); + if (pe->OptionalHeader.SectionAlignment < pagesz && + pe->OptionalHeader.SectionAlignment != pe->OptionalHeader.FileAlignment) + Die(path, "PE SectionAlignment must equal FileAlignment if it's less than " + "the microprocessor architecture's page size"); + if (pe->OptionalHeader.ImageBase & 65535) + Die(path, "PE ImageBase must be multiple of 65536"); + if (pe->OptionalHeader.ImageBase > INT_MAX && + !(pe->FileHeader.Characteristics & kNtImageFileLargeAddressAware)) + Die(path, "PE FileHeader.Characteristics needs " + "IMAGE_FILE_LARGE_ADDRESS_AWARE if ImageBase > INT_MAX"); + + // fixup pe header + int len; + if (ckd_mul(&len, pe->OptionalHeader.NumberOfRvaAndSizes, 8) || + ckd_add(&len, len, sizeof(struct NtImageOptionalHeader)) || + pe->FileHeader.SizeOfOptionalHeader < len) + Die(path, "PE SizeOfOptionalHeader too small"); + if (len > size || (char *)&pe->OptionalHeader + len > map + size) + Die(path, "PE OptionalHeader overflows image"); + + // perform even more pe validation + if (pe->OptionalHeader.SizeOfImage & + (pe->OptionalHeader.SectionAlignment - 1)) + Die(path, "PE SizeOfImage must be multiple of SectionAlignment"); + if (pe->OptionalHeader.SizeOfHeaders & (pe->OptionalHeader.FileAlignment - 1)) + Die(path, "PE SizeOfHeaders must be multiple of FileAlignment"); + if (pe->OptionalHeader.SizeOfHeaders > pe->OptionalHeader.AddressOfEntryPoint) + Die(path, "PE SizeOfHeaders <= AddressOfEntryPoint must be the case"); + if (pe->OptionalHeader.SizeOfHeaders >= pe->OptionalHeader.SizeOfImage) + Die(path, "PE SizeOfHeaders < SizeOfImage must be the case"); + if (pe->OptionalHeader.SizeOfStackCommit >> 32) + Die(path, "PE SizeOfStackReserve can't exceed 4GB"); + if (pe->OptionalHeader.SizeOfStackReserve >> 32) + Die(path, "PE SizeOfStackReserve can't exceed 4GB"); + if (pe->OptionalHeader.SizeOfHeapCommit >> 32) + Die(path, "PE SizeOfHeapReserve can't exceed 4GB"); + if (pe->OptionalHeader.SizeOfHeapReserve >> 32) + Die(path, "PE SizeOfHeapReserve can't exceed 4GB"); + + // check pe section headers + struct NtImageSectionHeader *sections = + (struct NtImageSectionHeader *)((char *)&pe->OptionalHeader + + pe->FileHeader.SizeOfOptionalHeader); + for (int i = 0; i < pe->FileHeader.NumberOfSections; ++i) { + if (sections[i].SizeOfRawData & (pe->OptionalHeader.FileAlignment - 1)) + Die(path, "PE SizeOfRawData should be multiple of FileAlignment"); + if (sections[i].PointerToRawData & (pe->OptionalHeader.FileAlignment - 1)) + Die(path, "PE PointerToRawData must be multiple of FileAlignment"); + if (map + sections[i].PointerToRawData >= map + size) + Die(path, "PE PointerToRawData points outside image"); + if (map + sections[i].PointerToRawData + sections[i].SizeOfRawData > + map + size) + Die(path, "PE SizeOfRawData overlaps end of image"); + if (!sections[i].VirtualAddress) + Die(path, "PE VirtualAddress shouldn't be zero"); + if (sections[i].VirtualAddress & (pe->OptionalHeader.SectionAlignment - 1)) + Die(path, "PE VirtualAddress must be multiple of SectionAlignment"); + if ((sections[i].Characteristics & + (kNtPeSectionCntCode | kNtPeSectionCntInitializedData | + kNtPeSectionCntUninitializedData)) == + kNtPeSectionCntUninitializedData) { + if (sections[i].SizeOfRawData) + Die(path, "PE SizeOfRawData should be zero for pure BSS section"); + if (sections[i].SizeOfRawData) + Die(path, "PE PointerToRawData should be zero for pure BSS section"); + } + if (!i) { + if (sections[i].VirtualAddress != + ((pe->OptionalHeader.SizeOfHeaders + + (pe->OptionalHeader.SectionAlignment - 1)) & + -pe->OptionalHeader.SectionAlignment)) + Die(path, "PE VirtualAddress of first section must be SizeOfHeaders " + "rounded up to SectionAlignment"); + } else { + if (sections[i].VirtualAddress != + sections[i - 1].VirtualAddress + + ((sections[i - 1].Misc.VirtualSize + + (pe->OptionalHeader.SectionAlignment - 1)) & + -pe->OptionalHeader.SectionAlignment)) + Die(path, "PE sections must be in ascending order and the virtual " + "memory they define must be adjacent after VirtualSize is " + "rounded up to the SectionAlignment"); + } + } + +#if 0 // broken + // validate dll imports + if (pe->OptionalHeader.NumberOfRvaAndSizes >= 2 && + pe->OptionalHeader.DataDirectory[kNtImageDirectoryEntryImport] + .VirtualAddress) { + struct NtImageImportDescriptor *idt = + (struct NtImageImportDescriptor + *)(map + + pe->OptionalHeader.DataDirectory[kNtImageDirectoryEntryImport] + .VirtualAddress); + for (int i = 0;; ++i) { + if ((char *)(idt + i + sizeof(*idt)) > map + size) + Die(path, "PE IMAGE_DIRECTORY_ENTRY_IMPORT points outside image"); + if (!idt[i].ImportLookupTable) break; + uint64_t *ilt = (uint64_t *)(map + idt[i].ImportLookupTable); + for (int j = 0;; ++j) { + if ((char *)(ilt + j + sizeof(*ilt)) > map + size) + Die(path, "PE ImportLookupTable points outside image"); + if (!ilt[j]) break; + struct NtImageImportByName *func = + (struct NtImageImportByName *)(map + ilt[j]); + } + uint64_t *iat = (uint64_t *)(map + idt[i].ImportAddressTable); + if ((char *)(iat + sizeof(*iat)) > map + size) + Die(path, "PE ImportAddressTable points outside image"); + } + } +#endif +} + +int main(int argc, char *argv[]) { + int i, fd; + void *map; + ssize_t size; + const char *path; + for (i = 1; i < argc; ++i) { + path = argv[i]; + if ((fd = open(path, O_RDONLY)) == -1) DieSys(path); + if ((size = lseek(fd, 0, SEEK_END)) == -1) DieSys(path); + map = mmap(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); + if (map == MAP_FAILED) DieSys(path); + CheckPe(path, map, size); + if (munmap(map, size)) DieSys(path); + if (close(fd)) DieSys(path); + } +} diff --git a/tool/decode/pe2.c b/tool/decode/pe2.c index ba0862596..d2e706f1b 100644 --- a/tool/decode/pe2.c +++ b/tool/decode/pe2.c @@ -27,6 +27,7 @@ #include "libc/nt/struct/imagentheaders.internal.h" #include "libc/nt/struct/imageoptionalheader.internal.h" #include "libc/nt/struct/imagesectionheader.internal.h" +#include "libc/runtime/runtime.h" #include "libc/stdio/stdio.h" #include "libc/str/str.h" #include "libc/sysv/consts/map.h" @@ -45,9 +46,32 @@ */ static const char *path; + static struct NtImageDosHeader *mz; static size_t mzsize; +static struct NtImageSectionHeader *sections; +static size_t section_count; + +static void *GetOff(uint32_t off) { + if (off < mzsize) return (char *)mz + off; + fprintf(stderr, "%s: off %#x not defined within image\n", path, off); + exit(1); +} + +static void *GetRva(uint32_t rva) { + int i; + for (i = 0; i < section_count; ++i) { + if (sections[i].VirtualAddress <= rva && + rva < sections[i].VirtualAddress + sections[i].Misc.VirtualSize) { + return (char *)mz + sections[i].PointerToRawData + + (rva - sections[i].VirtualAddress); + } + } + fprintf(stderr, "%s: rva %#x not defined by any sections\n", path, rva); + exit(1); +} + static struct XedDecodedInst *ildreal(void *addr) { static struct XedDecodedInst xedd; if (xed_instruction_length_decode( @@ -67,20 +91,13 @@ static void startfile(void) { static void *pecheckaddress(struct NtImageDosHeader *mz, size_t mzsize, void *addr, uint32_t addrsize) { -#if !(TRUSTWORTHY + PE_TRUSTWORTHY + 0) if ((intptr_t)addr < (intptr_t)mz || (intptr_t)addr + addrsize > (intptr_t)mz + mzsize) { abort(); } -#endif return addr; } -static void *PeComputeRva(struct NtImageDosHeader *mz, size_t mzsize, - uint32_t reladdr, uint32_t addrsize) { - return pecheckaddress(mz, mzsize, (void *)((intptr_t)mz + reladdr), addrsize); -} - static void showmzheader(void) { showtitle(basename(path), "dos", "mz header", "\tMZ = Mark 'Zibo' Joseph Zbikowski\n" @@ -217,10 +234,10 @@ static void ShowIlt(int64_t *ilt) { show(".quad", format(b1, "%#lx", *ilt), _gc(xasprintf("@%#lx", (intptr_t)ilt - (intptr_t)mz))); if (*ilt) { - char *hint = (char *)((intptr_t)mz + *ilt); + char *hint = GetRva(*ilt); printf("/\t.short\t%d\t\t\t# @%#lx\n", READ16LE(hint), (intptr_t)hint - (intptr_t)mz); - char *name = (char *)((intptr_t)mz + *ilt + 2); + char *name = GetRva(*ilt + 2); printf("/\t.asciz\t%`'s\n", name); printf("/\t.align\t2\n"); } @@ -240,14 +257,14 @@ static void ShowIat(char *iat, size_t size) { show(".long", format(b1, "%#x", READ32LE(p + 8)), "ForwarderChain"); show(".long", format(b1, "%#x", READ32LE(p + 12)), READ32LE(p + 12) - ? _gc(xasprintf("DllName RVA (%s)", (char *)mz + READ32LE(p + 12))) + ? _gc(xasprintf("DllName RVA (%s)", GetRva(READ32LE(p + 12)))) : "DllName RVA"); show(".long", format(b1, "%#x", READ32LE(p + 16)), "ImportAddressTable RVA"); } for (p = iat, e = iat + size; p + 20 <= e; p += 20) { if (READ32LE(p)) { - ShowIlt((void *)((intptr_t)mz + READ32LE(p))); + ShowIlt(GetRva(READ32LE(p))); } } } @@ -286,6 +303,8 @@ static void ShowSection(struct NtImageSectionHeader *s) { static void ShowSections(struct NtImageSectionHeader *s, size_t n) { size_t i; + sections = s; + section_count = n; printf("\n"); showtitle(basename(path), "windows", "sections", 0, 0); for (i = 0; i < n; ++i) { @@ -323,26 +342,25 @@ static void showpeheader(struct NtImageNtHeaders *pe) { pe->FileHeader.NumberOfSections * sizeof(struct NtImageSectionHeader)), pe->FileHeader.NumberOfSections); - ShowIat( - (void *)((intptr_t)mz + - pe->OptionalHeader.DataDirectory[kNtImageDirectoryEntryImport] - .VirtualAddress), - pe->OptionalHeader.DataDirectory[kNtImageDirectoryEntryImport].Size); + ShowIat(GetRva(pe->OptionalHeader.DataDirectory[kNtImageDirectoryEntryImport] + .VirtualAddress), + pe->OptionalHeader.DataDirectory[kNtImageDirectoryEntryImport].Size); } static void showall(void) { + startfile(); showmzheader(); showdosstub(); if (mz->e_lfanew) { - showpeheader(PeComputeRva(mz, mzsize, mz->e_lfanew, - sizeof(struct NtImageFileHeader))); + showpeheader(GetOff(mz->e_lfanew)); } } int main(int argc, char *argv[]) { int64_t fd; struct stat st[1]; + ShowCrashReports(); if (argc != 2) fprintf(stderr, "usage: %s FILE\n", argv[0]), exit(1); if ((fd = open((path = argv[1]), O_RDONLY)) == -1 || fstat(fd, st) == -1 || (mz = mmap(NULL, (mzsize = st->st_size), PROT_READ, MAP_SHARED, fd, 0)) == diff --git a/tool/emacs/c.lang b/tool/emacs/c.lang index efccfc0f4..a628f07fa 100644 --- a/tool/emacs/c.lang +++ b/tool/emacs/c.lang @@ -81,6 +81,7 @@ Keywords={ "_Imaginary_I", "__inline", "__msabi", +"__weak", "offsetof", "microarchitecture", "forcealignargpointer", diff --git a/tool/emacs/cosmo-c-keywords.el b/tool/emacs/cosmo-c-keywords.el index 708a67df6..4aa48d155 100644 --- a/tool/emacs/cosmo-c-keywords.el +++ b/tool/emacs/cosmo-c-keywords.el @@ -25,7 +25,8 @@ "_Imaginary_I")) (cosmo - '("__msabi" + '("__weak" + "__msabi" "__funline" "function" "offsetof" diff --git a/tool/hello/hello.c b/tool/hello/hello.c index d84bef4ab..83f952264 100644 --- a/tool/hello/hello.c +++ b/tool/hello/hello.c @@ -1,12 +1,433 @@ -#include "libc/calls/calls.h" -#include "libc/str/str.h" +/*-*- 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 2023 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ -// hello world with minimal build system dependencies +#define _HOSTLINUX 1 +#define _HOSTWINDOWS 4 +#define _HOSTXNU 8 +#define _HOSTOPENBSD 16 +#define _HOSTFREEBSD 32 +#define _HOSTNETBSD 64 -static ssize_t Write(int fd, const char *s) { - return write(fd, s, strlen(s)); +#ifndef SUPPORT_VECTOR +#define SUPPORT_VECTOR -1 +#endif + +#ifdef __aarch64__ +#define IsAarch64() 1 +#else +#define IsAarch64() 0 +#endif + +#define SupportsLinux() (SUPPORT_VECTOR & _HOSTLINUX) +#define SupportsXnu() (SUPPORT_VECTOR & _HOSTXNU) +#define SupportsWindows() (SUPPORT_VECTOR & _HOSTWINDOWS) +#define SupportsFreebsd() (SUPPORT_VECTOR & _HOSTFREEBSD) +#define SupportsOpenbsd() (SUPPORT_VECTOR & _HOSTOPENBSD) +#define SupportsNetbsd() (SUPPORT_VECTOR & _HOSTNETBSD) + +#define IsLinux() (SupportsLinux() && __crt.os == _HOSTLINUX) +#define IsXnu() (SupportsXnu() && __crt.os == _HOSTXNU) +#define IsWindows() (SupportsWindows() && __crt.os == _HOSTWINDOWS) +#define IsFreebsd() (SupportsFreebsd() && __crt.os == _HOSTFREEBSD) +#define IsOpenbsd() (SupportsOpenbsd() && __crt.os == _HOSTOPENBSD) +#define IsNetbsd() (SupportsNetbsd() && __crt.os == _HOSTNETBSD) + +#define O_RDONLY 0 +#define PROT_NONE 0 +#define PROT_READ 1 +#define PROT_WRITE 2 +#define PROT_EXEC 4 +#define MAP_SHARED 1 +#define MAP_PRIVATE 2 +#define MAP_FIXED 16 +#define MAP_ANONYMOUS 32 +#define MAP_EXECUTABLE 4096 +#define MAP_NORESERVE 16384 +#define ELFCLASS32 1 +#define ELFDATA2LSB 1 +#define EM_NEXGEN32E 62 +#define EM_AARCH64 183 +#define ET_EXEC 2 +#define ET_DYN 3 +#define PT_LOAD 1 +#define PT_DYNAMIC 2 +#define PT_INTERP 3 +#define EI_CLASS 4 +#define EI_DATA 5 +#define PF_X 1 +#define PF_W 2 +#define PF_R 4 +#define AT_PHDR 3 +#define AT_PHENT 4 +#define AT_PHNUM 5 +#define AT_PAGESZ 6 +#define AT_EXECFN_LINUX 31 +#define AT_EXECFN_NETBSD 2014 +#define X_OK 1 +#define XCR0_SSE 2 +#define XCR0_AVX 4 +#define PR_SET_MM 35 +#define PR_SET_MM_EXE_FILE 13 + +#define EIO 5 +#define EBADF 9 + +#define kNtInvalidHandleValue -1L +#define kNtStdInputHandle -10u +#define kNtStdOutputHandle -11u +#define kNtStdErrorHandle -12u + +#define kNtFileTypeUnknown 0x0000 +#define kNtFileTypeDisk 0x0001 +#define kNtFileTypeChar 0x0002 +#define kNtFileTypePipe 0x0003 +#define kNtFileTypeRemote 0x8000 + +#define kNtGenericRead 0x80000000u +#define kNtGenericWrite 0x40000000u + +#define kNtFileShareRead 0x00000001u +#define kNtFileShareWrite 0x00000002u +#define kNtFileShareDelete 0x00000004u + +#define kNtCreateNew 1 +#define kNtCreateAlways 2 +#define kNtOpenExisting 3 +#define kNtOpenAlways 4 +#define kNtTruncateExisting 5 + +#define kNtFileFlagOverlapped 0x40000000u +#define kNtFileAttributeNotContentIndexed 0x00002000u +#define kNtFileFlagBackupSemantics 0x02000000u +#define kNtFileFlagOpenReparsePoint 0x00200000u +#define kNtFileAttributeCompressed 0x00000800u +#define kNtFileAttributeTemporary 0x00000100u +#define kNtFileAttributeDirectory 0x00000010u + +struct NtOverlapped { + unsigned long Internal; + unsigned long InternalHigh; + union { + struct { + unsigned Offset; + unsigned OffsetHigh; + }; + void *Pointer; + }; + long hEvent; +}; + +#define __dll_import(DLL, RET, FUNC, ARGS) \ + extern RET(*__attribute__((__ms_abi__, __weak__)) FUNC) \ + ARGS __asm__("dll$" DLL ".dll$" #FUNC) + +__dll_import("kernel32", void, ExitProcess, (unsigned)); +__dll_import("kernel32", int, CloseHandle, (long)); +__dll_import("kernel32", long, GetStdHandle, (unsigned)); +__dll_import("kernel32", int, ReadFile, + (long, void *, unsigned, unsigned *, struct NtOverlapped *)); +__dll_import("kernel32", int, WriteFile, + (long, const void *, unsigned, unsigned *, struct NtOverlapped *)); + +struct WinCrt { + long fd2handle[3]; +}; + +struct Crt { + int os; + int argc; + char **argv; + char **envp; + long *auxv; + int pagesz; + int gransz; + struct WinCrt *wincrt; +} __crt; + +long SystemCall(long, long, long, long, long, long, long, int); +long CallSystem(long a, long b, long c, long d, long e, long f, long g, int x) { + if (IsXnu() && !IsAarch64()) x |= 0x2000000; + return SystemCall(a, b, c, d, e, f, g, x); } -int main(int argc, char *argv[]) { - Write(1, "hello\n"); +wontreturn void _Exit(int rc) { + int numba; + if (!IsWindows()) { + if (IsLinux()) { + if (IsAarch64()) { + numba = 94; + } else { + numba = 60; + } + } else { + numba = 1; + } + CallSystem(rc, 0, 0, 0, 0, 0, 0, numba); + } else { + ExitProcess((unsigned)rc << 8); + } + __builtin_unreachable(); +} + +static int ConvertFdToWin32Handle(int fd, long *out_handle) { + if (fd < 0 || fd > 2) return -EBADF; + *out_handle = __crt.wincrt->fd2handle[fd]; + return 0; +} + +int sys_close(int fd) { + if (!IsWindows()) { + int numba; + if (IsLinux()) { + if (IsAarch64()) { + numba = 57; + } else { + numba = 3; + } + } else { + numba = 6; + } + return CallSystem(fd, 0, 0, 0, 0, 0, 0, numba); + } else { + int rc; + long handle; + if (!(rc = ConvertFdToWin32Handle(fd, &handle))) { + CloseHandle(handle); + return 0; + } else { + return rc; + } + } +} + +ssize_t sys_write(int fd, const void *data, size_t size) { + if (!IsWindows()) { + int numba; + if (IsLinux()) { + if (IsAarch64()) { + numba = 64; + } else { + numba = 1; + } + } else { + numba = 4; + } + return CallSystem(fd, (long)data, size, 0, 0, 0, 0, numba); + } else { + ssize_t rc; + long handle; + uint32_t got; + if (!(rc = ConvertFdToWin32Handle(fd, &handle))) { + if (WriteFile(handle, data, size, &got, 0)) { + return got; + } else { + return -EIO; + } + } else { + return rc; + } + } +} + +ssize_t sys_pread(int fd, void *data, size_t size, long off) { + int numba; + if (IsLinux()) { + if (IsAarch64()) { + numba = 0x043; + } else { + numba = 0x011; + } + } else if (IsXnu()) { + numba = 0x099; + } else if (IsFreebsd()) { + numba = 0x1db; + } else if (IsOpenbsd()) { + numba = 0x0a9; + } else if (IsNetbsd()) { + numba = 0x0ad; + } else { + __builtin_unreachable(); + } + return SystemCall(fd, (long)data, size, off, off, 0, 0, numba); +} + +int sys_access(const char *path, int mode) { + if (IsLinux() && IsAarch64()) { + return SystemCall(-100, (long)path, mode, 0, 0, 0, 0, 48); + } else { + return CallSystem((long)path, mode, 0, 0, 0, 0, 0, IsLinux() ? 21 : 33); + } +} + +int sys_open(const char *path, int flags, int mode) { + if (IsLinux() && IsAarch64()) { + return SystemCall(-100, (long)path, flags, mode, 0, 0, 0, 56); + } else { + return CallSystem((long)path, flags, mode, 0, 0, 0, 0, IsLinux() ? 2 : 5); + } +} + +int sys_mprotect(void *addr, size_t size, int prot) { + int numba; + // all unix operating systems define the same values for prot + if (IsLinux()) { + if (IsAarch64()) { + numba = 226; + } else { + numba = 10; + } + } else { + numba = 74; + } + return CallSystem((long)addr, size, prot, 0, 0, 0, 0, numba); +} + +long sys_mmap(void *addr, size_t size, int prot, int flags, int fd, long off) { + long numba; + if (IsLinux()) { + if (IsAarch64()) { + numba = 222; + } else { + numba = 9; + } + } else { + // this flag isn't supported on non-Linux systems. since it's just + // hinting the kernel, it should be inconsequential to just ignore + flags &= ~MAP_NORESERVE; + // this flag is ignored by Linux and it overlaps with bsd map_anon + flags &= ~MAP_EXECUTABLE; + if (flags & MAP_ANONYMOUS) { + // all bsd-style operating systems share the same mag_anon magic + flags &= ~MAP_ANONYMOUS; + flags |= 4096; + } + if (IsFreebsd()) { + numba = 477; + } else if (IsOpenbsd()) { + numba = 49; + } else { + numba = 197; // xnu, netbsd + } + } + return CallSystem((long)addr, size, prot, flags, fd, off, off, numba); +} + +wontreturn void __unix_start(long di, long *sp, char os) { + + // detect freebsd + if (SupportsXnu() && os == _HOSTXNU) { + os = _HOSTXNU; + } else if (SupportsFreebsd() && di) { + os = _HOSTFREEBSD; + sp = (long *)di; + } + + // extract arguments + __crt.argc = *sp; + __crt.argv = (char **)(sp + 1); + __crt.envp = (char **)(sp + 1 + __crt.argc + 1); + __crt.auxv = (long *)(sp + 1 + __crt.argc + 1); + for (;;) { + if (!*__crt.auxv++) { + break; + } + } + + // detect openbsd + if (SupportsOpenbsd() && !os && !__crt.auxv[0]) { + os = _HOSTOPENBSD; + } + + // detect netbsd and find end of words + __crt.pagesz = IsAarch64() ? 16384 : 4096; + for (long *ap = __crt.auxv; ap[0]; ap += 2) { + if (ap[0] == AT_PAGESZ) { + __crt.pagesz = ap[1]; + } else if (SupportsNetbsd() && !os && ap[0] == AT_EXECFN_NETBSD) { + os = _HOSTNETBSD; + } + } + if (!os) { + os = _HOSTLINUX; + } + __crt.gransz = __crt.pagesz; + __crt.os = os; + + // call startup routines + typedef int init_f(int, char **, char **, long *); + extern init_f *__init_array_start[] __attribute__((__weak__)); + extern init_f *__init_array_end[] __attribute__((__weak__)); + for (init_f **fp = __init_array_end; fp-- > __init_array_start;) { + (*fp)(__crt.argc, __crt.argv, __crt.envp, __crt.auxv); + } + + // call program + int main(int, char **, char **); + _Exit(main(__crt.argc, __crt.argv, __crt.envp)); +} + +wontreturn void __win32_start(void) { + long sp[] = { + 0, // argc + 0, // empty argv + 0, // empty environ + 0, // empty auxv + }; + __crt.wincrt = &(struct WinCrt){ + .fd2handle = + { + GetStdHandle(kNtStdInputHandle), + GetStdHandle(kNtStdOutputHandle), + GetStdHandle(kNtStdErrorHandle), + }, + }; + __unix_start(0, sp, _HOSTWINDOWS); +} + +ssize_t print(int fd, const char *s, ...) { + int c; + unsigned n; + va_list va; + char b[512]; + va_start(va, s); + for (n = 0; s; s = va_arg(va, const char *)) { + while ((c = *s++)) { + if (n < sizeof(b)) { + b[n++] = c; + } + } + } + va_end(va); + return sys_write(fd, b, n); +} + +//////////////////////////////////////////////////////////////////////////////// + +char data[10] = "sup"; +char bss[0xf9]; +_Thread_local char tdata[10] = "hello"; +_Thread_local char tbss[0xf9]; + +_Section(".love") int main(int argc, char **argv, char **envp) { + if (argc == 666) { + bss[0] = data[0]; + tbss[0] = tdata[0]; + } + print(1, "hello world\n", NULL); } diff --git a/tool/hello/hello.mk b/tool/hello/hello.mk index 041eb41bb..b40e978f5 100644 --- a/tool/hello/hello.mk +++ b/tool/hello/hello.mk @@ -3,37 +3,26 @@ PKGS += TOOL_HELLO -TOOL_HELLO_SRCS := $(wildcard tool/hello/*.c) -TOOL_HELLO_OBJS = $(TOOL_HELLO_SRCS:%.c=o/$(MODE)/%.o) -TOOL_HELLO_COMS = $(TOOL_HELLO_SRCS:%.c=o/$(MODE)/%.com) -TOOL_HELLO_BINS = $(TOOL_HELLO_COMS) $(TOOL_HELLO_COMS:%=%.dbg) +TOOL_HELLO_FILES := $(wildcard tool/hello/*) +TOOL_HELLO_HDRS = $(filter %.h,$(TOOL_HELLO_FILES)) +TOOL_HELLO_SRCS_C = $(filter %.c,$(TOOL_HELLO_FILES)) +TOOL_HELLO_SRCS_S = $(filter %.S,$(TOOL_HELLO_FILES)) +TOOL_HELLO_SRCS = $(TOOL_HELLO_SRCS_C) $(TOOL_HELLO_SRCS_S) +TOOL_HELLO_OBJS = $(TOOL_HELLO_SRCS_C:%.c=o/$(MODE)/%.o) $(TOOL_HELLO_SRCS_S:%.S=o/$(MODE)/%.o) +TOOL_HELLO_BINS = o/$(MODE)/tool/hello/hello.com.dbg -TOOL_HELLO_DIRECTDEPS = \ - LIBC_CALLS \ - LIBC_INTRIN \ - LIBC_NEXGEN32E \ - LIBC_RUNTIME \ - LIBC_STR \ - LIBC_SYSV +o/$(MODE)/tool/hello/hello.com.dbg: \ + o/$(MODE)/tool/hello/systemcall.o \ + o/$(MODE)/tool/hello/hello.o \ + o/$(MODE)/tool/hello/start.o + @$(COMPILE) -ALINK.elf $(LINK) $(LINKARGS) $(OUTPUT_OPTION) -q -zmax-page-size=4096 -TOOL_HELLO_DEPS := \ - $(call uniq,$(foreach x,$(TOOL_HELLO_DIRECTDEPS),$($(x)))) +o/$(MODE)/tool/hello/hello.com: \ + o/$(MODE)/tool/hello/hello.com.dbg \ + o/$(MODE)/tool/build/elf2pe.com + o/$(MODE)/tool/build/elf2pe.com -o $@ $< -o/$(MODE)/tool/hello/hello.pkg: \ - $(TOOL_HELLO_OBJS) \ - $(foreach x,$(TOOL_HELLO_DIRECTDEPS),$($(x)_A).pkg) - -o/$(MODE)/tool/hello/%.com.dbg: \ - $(TOOL_HELLO_DEPS) \ - o/$(MODE)/tool/hello/%.o \ - o/$(MODE)/tool/hello/hello.pkg \ - $(CRT) \ - $(APE_NO_MODIFY_SELF) - @$(APELINK) - -$(TOOL_HELLO_OBJS): \ - $(BUILD_FILES) \ - tool/hello/hello.mk +$(TOOL_HELLO_OBJS): tool/hello/hello.mk .PHONY: o/$(MODE)/tool/hello -o/$(MODE)/tool/hello: $(TOOL_HELLO_BINS) $(TOOL_HELLO_CHECKS) +o/$(MODE)/tool/hello: $(TOOL_HELLO_BINS) diff --git a/tool/hello/start.S b/tool/hello/start.S new file mode 100644 index 000000000..c4bb088ce --- /dev/null +++ b/tool/hello/start.S @@ -0,0 +1,27 @@ +/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ +│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2023 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/macros.internal.h" + +_apple: mov $8,%dl +_start: mov %rsp,%rsi + call __unix_start + call __win32_start // prevent --gc-sections + .weak __win32_start + .endfn _start,globl + .endfn _apple,globl diff --git a/tool/hello/systemcall.S b/tool/hello/systemcall.S new file mode 100644 index 000000000..4f4829f2c --- /dev/null +++ b/tool/hello/systemcall.S @@ -0,0 +1,57 @@ +/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ +│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2023 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/macros.internal.h" + +// Invokes system call. +// +// This function has eight parameters. The first seven are for +// arguments passed along to the system call. The eight is for +// the magic number that indicates which system call is called +// +// The return value follows the Linux kernel convention, where +// errors are returned as `-errno`. BSD systems are normalized +// to follow this convention automatically. +// +// It's important to use a function call wrapper when invoking +// syscall, because BSD kernels will unpredictably clobber any +// volatile registers (unlike Linux). There's no overhead with +// the extra call since a system call takes like a microsecond +// +// @return negative errno above -4096ul on error +SystemCall: +#ifdef __aarch64__ + mov x8,x7 + mov x16,x7 + mov x9,0 + adds x9,x9,0 + svc 0 + bcs 1f + ret +1: neg x0,x0 + ret +#else + mov %rcx,%r10 + mov 16(%rsp),%eax + clc + syscall + jnc 1f + neg %rax +1: ret +#endif + .endfn SystemCall,globl