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