From 41bcb4a78fb5329b57c9f56671682ed9d34c5c6e Mon Sep 17 00:00:00 2001 From: Benjamin Oldenburg Date: Sat, 4 Apr 2026 16:29:28 +0200 Subject: [PATCH] arm64-win32 support : linker & -run --- arm64-link.c | 62 +++++++++++++++- tcc.h | 5 +- tccpe.c | 196 +++++++++++++++++++++++++++++++++++++++++++++++---- tccrun.c | 10 ++- 4 files changed, 256 insertions(+), 17 deletions(-) diff --git a/arm64-link.c b/arm64-link.c index cfdd95ea..275796b4 100644 --- a/arm64-link.c +++ b/arm64-link.c @@ -50,6 +50,8 @@ ST_FUNC int code_reloc (int reloc_type) case R_AARCH64_JUMP26: case R_AARCH64_CALL26: case R_AARCH64_JUMP_SLOT: + case R_AARCH64_CONDBR19: + case R_AARCH64_TSTBR14: return 1; } return -1; @@ -76,6 +78,8 @@ ST_FUNC int gotplt_entry_type (int reloc_type) case R_AARCH64_GLOB_DAT: case R_AARCH64_JUMP_SLOT: case R_AARCH64_COPY: + case R_AARCH64_CONDBR19: + case R_AARCH64_TSTBR14: return NO_GOTPLT_ENTRY; case R_AARCH64_ABS32: @@ -238,8 +242,23 @@ ST_FUNC void relocate(TCCState *s1, ElfW_Rel *rel, int type, unsigned char *ptr, return; case R_AARCH64_ADR_PREL_PG_HI21: { uint64_t off = (val >> 12) - (addr >> 12); +#ifdef TCC_TARGET_PE + /* Weak undefined symbols resolve to address 0 on PE. ADRP cannot + encode that from the default 64-bit image base, so materialize + zero directly and let the paired ADD handle any low addend. */ + if ((off + ((uint64_t)1 << 20)) >> 21) { + ElfW(Sym) *sym = &((ElfW(Sym) *)symtab_section->data)[sym_index]; + if (sym->st_shndx == SHN_UNDEF + && ELFW(ST_BIND)(sym->st_info) == STB_WEAK) { + write32le(ptr, 0xd2800000 | (read32le(ptr) & 0x1f)); + return; + } + tcc_error_noabort("R_AARCH64_ADR_PREL_PG_HI21 relocation failed"); + } +#else if ((off + ((uint64_t)1 << 20)) >> 21) tcc_error_noabort("R_AARCH64_ADR_PREL_PG_HI21 relocation failed"); +#endif write32le(ptr, ((read32le(ptr) & 0x9f00001f) | (off & 0x1ffffc) << 3 | (off & 3) << 29)); return; @@ -265,19 +284,58 @@ ST_FUNC void relocate(TCCState *s1, ElfW_Rel *rel, int type, unsigned char *ptr, write32le(ptr, ((read32le(ptr) & 0xffc003ff) | (val & 0xff0) << 6)); return; + case R_AARCH64_CONDBR19: + /* Conditional branch: 19-bit signed offset, bits 23:5 */ +#ifdef DEBUG_RELOC + printf ("reloc %d @ 0x%lx: val=0x%lx name=%s\n", type, addr, val, + (char *) symtab_section->link->data + sym->st_name); +#endif + if (((val - addr) + ((uint64_t)1 << 20)) & ~(uint64_t)0x1ffffc) + tcc_error_noabort("R_AARCH64_CONDBR19 relocation failed" + " (val=%lx, addr=%lx)", (long)val, (long)addr); + write32le(ptr, ((read32le(ptr) & 0xff00001f) | + (((val - addr) >> 2 & 0x7ffff) << 5))); + return; + case R_AARCH64_TSTBR14: + /* Test and branch: 14-bit signed offset, bits 20:5 */ +#ifdef DEBUG_RELOC + printf ("reloc %d @ 0x%lx: val=0x%lx name=%s\n", type, addr, val, + (char *) symtab_section->link->data + sym->st_name); +#endif + if (((val - addr) + ((uint64_t)1 << 15)) & ~(uint64_t)0xfffc) + tcc_error_noabort("R_AARCH64_TSTBR14 relocation failed" + " (val=%lx, addr=%lx)", (long)val, (long)addr); + write32le(ptr, ((read32le(ptr) & 0xfff8001f) | + (((val - addr) >> 2 & 0x3fff) << 5))); + return; case R_AARCH64_JUMP26: case R_AARCH64_CALL26: + { + const char *name; #ifdef DEBUG_RELOC printf ("reloc %d @ 0x%lx: val=0x%lx name=%s\n", type, addr, val, (char *) symtab_section->link->data + sym->st_name); #endif - if (((val - addr) + ((uint64_t)1 << 27)) & ~(uint64_t)0xffffffc) + if (((val - addr) + ((uint64_t)1 << 27)) & ~(uint64_t)0xffffffc) { +#ifdef TCC_TARGET_PE + ElfW(Sym) *sym = &((ElfW(Sym) *)symtab_section->data)[sym_index]; + if (sym->st_shndx == SHN_UNDEF + && ELFW(ST_BIND)(sym->st_info) == STB_WEAK) { + write32le(ptr, ARM64_NOP); /* nop */ + return; + } +#endif + name = (char *)symtab_section->link->data + + ((ElfW(Sym) *)symtab_section->data)[sym_index].st_name; tcc_error_noabort("R_AARCH64_(JUMP|CALL)26 relocation failed" - " (val=%lx, addr=%lx)", (long)val, (long)addr); + " for '%s' (val=%lx, addr=%lx)", + name, (long)val, (long)addr); + } write32le(ptr, (0x14000000 | (uint32_t)(type == R_AARCH64_CALL26) << 31 | ((val - addr) >> 2 & 0x3ffffff))); return; + } case R_AARCH64_ADR_GOT_PAGE: { uint64_t off = (((s1->got->sh_addr + diff --git a/tcc.h b/tcc.h index 226bfa9b..04befa20 100644 --- a/tcc.h +++ b/tcc.h @@ -949,9 +949,10 @@ struct TCCState { unsigned pe_file_align; unsigned pe_stack_size; addr_t pe_imagebase; -# ifdef TCC_TARGET_X86_64 +# if defined(TCC_TARGET_X86_64) || defined(TCC_TARGET_ARM64) Section *uw_pdata; int uw_sym; + int uw_xsym; unsigned uw_offs; # endif #endif @@ -1762,7 +1763,7 @@ ST_FUNC int pe_putimport(TCCState *s1, int dllindex, const char *name, addr_t va ST_FUNC int pe_setsubsy(TCCState *s1, const char *arg); #if defined TCC_TARGET_I386 || defined TCC_TARGET_X86_64 #endif -#ifdef TCC_TARGET_X86_64 +#if defined(TCC_TARGET_X86_64) || defined(TCC_TARGET_ARM64) ST_FUNC void pe_add_unwind_data(unsigned start, unsigned end, unsigned stack); #endif PUB_FUNC int tcc_get_dllexports(const char *filename, char **pp); diff --git a/tccpe.c b/tccpe.c index 1272cbed..11817e33 100644 --- a/tccpe.c +++ b/tccpe.c @@ -50,6 +50,16 @@ # define IMAGE_FILE_MACHINE 0x01C0 # define RSRC_RELTYPE 7 /* ??? (not tested) */ +#elif defined TCC_TARGET_ARM64 +# define ADDR3264 ULONGLONG +# define PE_IMAGE_REL IMAGE_REL_BASED_DIR64 +# define REL_TYPE_DIRECT R_AARCH64_ABS64 +# define R_XXX_THUNKFIX R_AARCH64_ABS64 +# define R_XXX_RELATIVE R_AARCH64_RELATIVE +# define R_XXX_FUNCCALL R_AARCH64_CALL26 +# define IMAGE_FILE_MACHINE 0xAA64 +# define RSRC_RELTYPE 3 + #elif defined TCC_TARGET_I386 # define ADDR3264 DWORD # define PE_IMAGE_REL IMAGE_REL_BASED_HIGHLOW @@ -126,7 +136,7 @@ typedef struct _IMAGE_OPTIONAL_HEADER { DWORD SizeOfUninitializedData; DWORD AddressOfEntryPoint; DWORD BaseOfCode; -#ifndef TCC_TARGET_X86_64 +#if !defined(TCC_TARGET_X86_64) && !defined(TCC_TARGET_ARM64) DWORD BaseOfData; #endif /* NT additional fields. */ @@ -225,6 +235,19 @@ typedef struct _IMAGE_BASE_RELOCATION { #define IMAGE_SIZEOF_BASE_RELOCATION 8 +#ifndef IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA +#define IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA 0x0020 +#endif +#ifndef IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE +#define IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE 0x0040 +#endif +#ifndef IMAGE_DLLCHARACTERISTICS_NX_COMPAT +#define IMAGE_DLLCHARACTERISTICS_NX_COMPAT 0x0100 +#endif +#ifndef IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE +#define IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE 0x8000 +#endif + #define IMAGE_REL_BASED_ABSOLUTE 0 #define IMAGE_REL_BASED_HIGH 1 #define IMAGE_REL_BASED_LOW 2 @@ -250,6 +273,9 @@ typedef struct _IMAGE_BASE_RELOCATION { #endif /* ndef IMAGE_NT_SIGNATURE */ /* ----------------------------------------------------------- */ +#ifndef IMAGE_FILE_MACHINE_ARM64 +#define IMAGE_FILE_MACHINE_ARM64 0xAA64 +#endif #ifndef IMAGE_REL_BASED_DIR64 # define IMAGE_REL_BASED_DIR64 10 #endif @@ -261,7 +287,7 @@ struct pe_header BYTE dosstub[0x40]; DWORD nt_sig; IMAGE_FILE_HEADER filehdr; -#ifdef TCC_TARGET_X86_64 +#if defined(TCC_TARGET_X86_64) || defined(TCC_TARGET_ARM64) IMAGE_OPTIONAL_HEADER64 opthdr; #else #ifdef _WIN64 @@ -605,11 +631,15 @@ static int pe_write(struct pe_info *pe) 0x00E0, /*WORD SizeOfOptionalHeader; */ 0x010F, /*WORD Characteristics; */ #define CHARACTERISTICS_DLL 0x230F +#elif defined(TCC_TARGET_ARM64) + 0x00F0, /*WORD SizeOfOptionalHeader; */ + 0x0022 /*WORD Characteristics; */ +#define CHARACTERISTICS_DLL 0x2022 #endif },{ /* IMAGE_OPTIONAL_HEADER opthdr */ /* Standard fields. */ -#ifdef TCC_TARGET_X86_64 +#if defined(TCC_TARGET_X86_64) || defined(TCC_TARGET_ARM64) 0x020B, /*WORD Magic; */ #else 0x010B, /*WORD Magic; */ @@ -621,29 +651,48 @@ static int pe_write(struct pe_info *pe) 0x00000000, /*DWORD SizeOfUninitializedData; */ 0x00000000, /*DWORD AddressOfEntryPoint; */ 0x00000000, /*DWORD BaseOfCode; */ -#ifndef TCC_TARGET_X86_64 +#if !defined(TCC_TARGET_X86_64) && !defined(TCC_TARGET_ARM64) 0x00000000, /*DWORD BaseOfData; */ #endif /* NT additional fields. */ #if defined(TCC_TARGET_ARM) 0x00100000, /*DWORD ImageBase; */ +#elif defined(TCC_TARGET_ARM64) + 0x140000000ULL, /*ULONGLONG ImageBase; */ #else 0x00400000, /*DWORD ImageBase; */ #endif 0x00001000, /*DWORD SectionAlignment; */ 0x00000200, /*DWORD FileAlignment; */ +#if defined(TCC_TARGET_ARM64) + 0x0006, /*WORD MajorOperatingSystemVersion; */ + 0x0002, /*WORD MinorOperatingSystemVersion; */ +#else 0x0004, /*WORD MajorOperatingSystemVersion; */ 0x0000, /*WORD MinorOperatingSystemVersion; */ +#endif 0x0000, /*WORD MajorImageVersion; */ 0x0000, /*WORD MinorImageVersion; */ +#if defined(TCC_TARGET_ARM64) + 0x0006, /*WORD MajorSubsystemVersion; */ + 0x0002, /*WORD MinorSubsystemVersion; */ +#else 0x0004, /*WORD MajorSubsystemVersion; */ 0x0000, /*WORD MinorSubsystemVersion; */ +#endif 0x00000000, /*DWORD Win32VersionValue; */ 0x00000000, /*DWORD SizeOfImage; */ 0x00000200, /*DWORD SizeOfHeaders; */ 0x00000000, /*DWORD CheckSum; */ 0x0002, /*WORD Subsystem; */ +#if defined(TCC_TARGET_ARM64) + IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA | + IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE | + IMAGE_DLLCHARACTERISTICS_NX_COMPAT | + IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE, +#else 0x0000, /*WORD DllCharacteristics; */ +#endif 0x00100000, /*DWORD SizeOfStackReserve; */ 0x00001000, /*DWORD SizeOfStackCommit; */ 0x00100000, /*DWORD SizeOfHeapReserve; */ @@ -702,7 +751,7 @@ static int pe_write(struct pe_info *pe) break; case sec_data: -#ifndef TCC_TARGET_X86_64 +#if !defined(TCC_TARGET_X86_64) && !defined(TCC_TARGET_ARM64) if (!pe_header.opthdr.BaseOfData) pe_header.opthdr.BaseOfData = addr; #endif @@ -1191,7 +1240,11 @@ static int pe_assign_addresses (struct pe_info *pe) Section *s; TCCState *s1 = pe->s1; - if (PE_DLL == pe->type) + if (PE_DLL == pe->type +#ifdef TCC_TARGET_ARM64 + || PE_EXE == pe->type || PE_GUI == pe->type +#endif + ) pe->reloc = new_section(s1, ".reloc", SHT_PROGBITS, 0); //pe->thunk = new_section(s1, ".iedat", SHT_PROGBITS, SHF_ALLOC); @@ -1380,6 +1433,18 @@ static int pe_check_symbols(struct pe_info *pe) write32le(p + 4, 0xE59CF000); // arm code ldr pc, [ip] put_elf_reloc(symtab_section, text_section, offset + 8, R_XXX_THUNKFIX, is->iat_index); // offset to IAT position +#elif defined(TCC_TARGET_ARM64) + p = section_ptr_add(text_section, 24); + /* ldr x16, [pc, #16] */ + write32le(p + 0, 0x58000090); + /* ldr x16, [x16] */ + write32le(p + 4, 0xf9400210); + /* br x16 */ + write32le(p + 8, 0xd61f0200); + /* nop for 8-byte literal alignment */ + write32le(p + 12, 0xd503201f); + put_elf_reloc(symtab_section, text_section, + offset + 16, R_XXX_THUNKFIX, is->iat_index); #else p = section_ptr_add(text_section, 8); write16le(p, 0x25FF); @@ -1611,7 +1676,7 @@ static int get_dllexports(int fd, char **pp) if (IMAGE_DIRECTORY_ENTRY_EXPORT >= oh.NumberOfRvaAndSizes) goto the_end_0; addr = oh.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; - } else if (ih.Machine == 0x8664) { + } else if (ih.Machine == 0x8664 || ih.Machine == IMAGE_FILE_MACHINE_ARM64) { IMAGE_OPTIONAL_HEADER64 oh; sec_hdroffset = opt_hdroffset + sizeof oh; if (!read_mem(fd, opt_hdroffset, &oh, sizeof oh)) @@ -1834,7 +1899,7 @@ PUB_FUNC int tcc_get_dllexports(const char *filename, char **pp) /* ------------------------------------------------------------- */ #ifdef TCC_TARGET_X86_64 -static unsigned pe_add_uwwind_info(TCCState *s1) +static unsigned pe_add_unwind_info(TCCState *s1) { if (NULL == s1->uw_pdata) { s1->uw_pdata = find_section(s1, ".pdata"); @@ -1879,7 +1944,7 @@ ST_FUNC void pe_add_unwind_data(unsigned start, unsigned end, unsigned stack) DWORD UnwindData; } *p; - d = pe_add_uwwind_info(s1); + d = pe_add_unwind_info(s1); pd = s1->uw_pdata; o = pd->data_offset; p = section_ptr_add(pd, sizeof *p); @@ -1893,9 +1958,110 @@ ST_FUNC void pe_add_unwind_data(unsigned start, unsigned end, unsigned stack) for (n = o + sizeof *p; o < n; o += sizeof p->BeginAddress) put_elf_reloc(symtab_section, pd, o, R_XXX_RELATIVE, s1->uw_sym); } +#elif defined(TCC_TARGET_ARM64) +/* ARM64 unwind codes: + save_fplr_x: 10iiiiii - stp x29,lr,[sp,#-(i+1)*8]! + set_fp: 11100001 - mov x29,sp + alloc_s: 000iiiii - sub sp,sp,#i*16 (up to 496 bytes) + alloc_m: 11000iii xxxxxxxx - sub sp,sp,#X*16 (up to 32KB) + end: 11100100 - end of unwind codes +*/ +static Section *pe_add_unwind_info(TCCState *s1) +{ + Section *s; + + if (NULL == s1->uw_pdata) { + s1->uw_pdata = find_section(s1, ".pdata"); + s1->uw_pdata->sh_addralign = 4; + } + s = find_section(s1, ".xdata"); + if (NULL == s) { + s = new_section(s1, ".xdata", SHT_PROGBITS, SHF_ALLOC); + s->sh_addralign = 4; + } + if (0 == s1->uw_sym) + s1->uw_sym = put_elf_sym(symtab_section, 0, 0, 0, 0, + text_section->sh_num, ".uw_text_base"); + if (0 == s1->uw_xsym) + s1->uw_xsym = put_elf_sym(symtab_section, 0, 0, 0, 0, + s->sh_num, ".uw_base"); + return s; +} + +ST_FUNC void pe_add_unwind_data(unsigned start, unsigned end, unsigned stack) +{ + TCCState *s1 = tcc_state; + Section *pd, *xd; + unsigned o, n, d, code_bytes, func_len, stack_slots; + unsigned char *q; + uint32_t header; + struct { + DWORD BeginAddress; + DWORD EndAddress; + DWORD UnwindData; + } *p; + + xd = pe_add_unwind_info(s1); + pd = s1->uw_pdata; + + stack = (stack + 15) & ~15; + stack_slots = stack >> 4; + func_len = (end - start) >> 2; + code_bytes = 0; + if (stack_slots) { + if (stack_slots <= 31) { + code_bytes += 1; + } else if (stack_slots <= 0x7ff) { + code_bytes += 2; + } else { + code_bytes += 4; + } + } + code_bytes += 3; /* set_fp, save_fplr_x, end */ + code_bytes = (code_bytes + 3) & ~3; + + section_ptr_add(xd, -xd->data_offset & 3); + d = xd->data_offset; + q = section_ptr_add(xd, 4 + code_bytes); + + /* Full ARM64 xdata header: E=1 with one epilog and no exception handler. */ + header = (func_len & 0x3ffff) | (1u << 21) | ((code_bytes >> 2) << 27); + write32le(q, header); + q += 4; + + if (stack_slots) { + if (stack_slots <= 31) { + *q++ = stack_slots; /* alloc_s */ + } else if (stack_slots <= 0x7ff) { + *q++ = 0xC0 | (stack_slots >> 8); /* alloc_m */ + *q++ = stack_slots & 0xff; + } else { + *q++ = 0xE0; /* alloc_l */ + *q++ = (stack_slots >> 16) & 0xff; + *q++ = (stack_slots >> 8) & 0xff; + *q++ = stack_slots & 0xff; + } + } + *q++ = 0xE1; /* set_fp */ + *q++ = 0x9B; /* save_fplr_x: stp x29,lr,[sp,#-224]! */ + *q++ = 0xE4; /* end */ + while ((unsigned)(q - (xd->data + d + 4)) < code_bytes) + *q++ = 0xE3; /* nop padding */ + + o = pd->data_offset; + p = section_ptr_add(pd, sizeof *p); + + p->BeginAddress = start; + p->EndAddress = end; + p->UnwindData = d; + + for (n = o + 2 * sizeof p->BeginAddress; o < n; o += sizeof p->BeginAddress) + put_elf_reloc(symtab_section, pd, o, R_XXX_RELATIVE, s1->uw_sym); + put_elf_reloc(symtab_section, pd, n, R_XXX_RELATIVE, s1->uw_xsym); +} #endif /* ------------------------------------------------------------- */ -#ifdef TCC_TARGET_X86_64 +#if defined(TCC_TARGET_X86_64) || defined(TCC_TARGET_ARM64) #define PE_STDSYM(n,s) n #else #define PE_STDSYM(n,s) "_" n s @@ -1991,7 +2157,7 @@ static void pe_add_runtime(TCCState *s1, struct pe_info *pe) ST_FUNC int pe_setsubsy(TCCState *s1, const char *arg) { static const struct subsy { const char* p; int v; } x[] = { -#if defined(TCC_TARGET_I386) || defined(TCC_TARGET_X86_64) +#if defined(TCC_TARGET_I386) || defined(TCC_TARGET_X86_64) || defined(TCC_TARGET_ARM64) { "native", 1 }, { "gui", 2 }, { "windows", 2 }, @@ -2020,10 +2186,16 @@ static void pe_set_options(TCCState * s1, struct pe_info *pe) { if (PE_DLL == pe->type) { /* XXX: check if is correct for arm-pe target */ +#if defined(TCC_TARGET_ARM64) + pe->imagebase = 0x180000000ULL; +#else pe->imagebase = 0x10000000; +#endif } else { #if defined(TCC_TARGET_ARM) pe->imagebase = 0x00010000; +#elif defined(TCC_TARGET_ARM64) + pe->imagebase = 0x140000000ULL; #else pe->imagebase = 0x00400000; #endif @@ -2098,7 +2270,7 @@ ST_FUNC int pe_output_file(TCCState *s1, const char *filename) pe.thunk = data_section; pe_build_imports(&pe); s1->run_main = pe.start_symbol; -#ifdef TCC_TARGET_X86_64 +#if defined(TCC_TARGET_X86_64) || defined(TCC_TARGET_ARM64) s1->uw_pdata = find_section(s1, ".pdata"); #endif #endif diff --git a/tccrun.c b/tccrun.c index a01faee7..efec3228 100644 --- a/tccrun.c +++ b/tccrun.c @@ -1199,7 +1199,11 @@ static int rt_error(rt_frame *f, const char *fmt, ...) /* translate from ucontext_t* to internal rt_context * */ static void rt_getcontext(ucontext_t *uc, rt_frame *rc) { -#if defined _WIN64 +#if defined _WIN64 && defined __aarch64__ + rc->ip = uc->Pc; /* Program Counter */ + rc->fp = uc->Fp; /* Frame Pointer (X29) */ + rc->sp = uc->Sp; /* Stack Pointer (X30 is LR, but SP is separate) */ +#elif defined _WIN64 rc->ip = uc->Rip; rc->fp = uc->Rbp; rc->sp = uc->Rsp; @@ -1398,7 +1402,11 @@ static long __stdcall cpu_exception_handler(EXCEPTION_POINTERS *ex_info) /* Generate a stack backtrace when a CPU exception occurs. */ static void set_exception_handler(void) { +#ifdef _WIN64 + AddVectoredExceptionHandler(1, cpu_exception_handler); +#else SetUnhandledExceptionFilter(cpu_exception_handler); +#endif } #endif