kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit 45d883cd4bfea07e86ecb7bb678d74d461d708d1
parent b4bfeaba9214d299c450544f77517aa6a19a59f3
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sun, 14 Jun 2026 13:06:12 -0700

ld: expand Rust linker compatibility

Teach kit ld more system-linker flag and sysroot behavior for Rust-driven links across Mach-O, ELF, and COFF.

Add the relocation/linker support exposed by those links, including ELF TLS relaxation/versioned imports and COFF archive/ARM64 reloc handling.

Cover the new paths with focused relocation, target parsing, ELF version, and COFF archive tests.

Diffstat:
Mdriver/cmd/ld.c | 1314++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mdriver/env/linux.c | 30++++++++++++++++++++++++++++++
Minclude/kit/target.h | 4++--
Mmk/test_unit.mk | 3++-
Mscripts/freebsd_sysroot.sh | 8++++++++
Msrc/api/target.c | 19+++++++++++++++++--
Msrc/arch/aa64/reloc.c | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/arch/riscv/reloc.c | 144++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Msrc/arch/x64/reloc.c | 37++++++++++++++++++++++++++++++++++---
Msrc/link/link.h | 5+++++
Msrc/link/link_internal.h | 1+
Msrc/link/link_resolve.c | 169++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Msrc/obj/coff/link.c | 21++++++++++++++++++++-
Msrc/obj/coff/reloc_aarch64.c | 4++++
Msrc/obj/elf/elf.h | 7+++++++
Msrc/obj/elf/link.c | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/obj/elf/link_dyn.c | 12+++++++-----
Msrc/obj/elf/read.c | 10++++++++--
Msrc/obj/elf/reloc_aarch64.c | 24++++++++++++++++++++++++
Msrc/obj/elf/reloc_riscv64.c | 4++++
Msrc/obj/format.h | 4++++
Msrc/obj/macho/read.c | 1+
Msrc/obj/macho/reloc_aarch64.c | 8++++++++
Msrc/obj/obj.c | 18++++++++++++------
Msrc/obj/obj.h | 46++++++++++++++++++++++++++++++++++++----------
Msrc/obj/obj_secnames.c | 5+++++
Msrc/obj/registry.c | 4++++
Msrc/obj/reloc.c | 12++++++++++++
Msrc/obj/reloc_apply.c | 11++++++-----
Msrc/os/windows/hosted.c | 3++-
Mtest/api/target_test.c | 32++++++++++++++++++++++++++++++++
Mtest/libc/glibc/Containerfile | 8+++++++-
Mtest/libc/glibc/Containerfile.rv64 | 8+++++++-
Mtest/libc/glibc/Containerfile.x64 | 8+++++++-
Atest/link/coff_archive_fixpoint_test.c | 192+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/link/elf_version_import_test.c | 135+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/link/reloc_apply_test.c | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/link/reloc_desc_test.c | 17+++++++++++++++--
38 files changed, 2384 insertions(+), 129 deletions(-)

diff --git a/driver/cmd/ld.c b/driver/cmd/ld.c @@ -21,7 +21,7 @@ * -e symbol entry symbol * -T script.ld linker script (parsed, not raw) * --support-dir DIR kit support root for compiler rt - * --sysroot DIR hosted C runtime/sysroot root + * --sysroot DIR / -syslibroot DIR hosted C runtime/sysroot root * -L dir library search path (-l targets) * -l c enable hosted CRT/libc expansion * -l name resolves via -L (.so preferred unless @@ -38,13 +38,18 @@ * --disable-new-dtags rpath entries emit as DT_RPATH * -E / --export-dynamic promote all defined globals to the * dynamic symbol table + * --eh-frame-hdr accepted for GNU ld compatibility + * --fix-cortex-a53-843419 accepted for GNU ld compatibility * --whole-archive / --no-whole-archive * positional state for following .a * --gc-sections / --no-gc-sections drop unreferenced sections * -Bstatic / -Bdynamic positional link-mode for following .a * --as-needed / --no-as-needed positional link-mode for following .a * --start-group / --end-group cyclic-resolution group of archives + * -z OPT / -zOPT GNU linker option compatibility * --build-id={none|sha256|uuid|0xHEX} + * -m i386pep PE/COFF emulation selector (x86_64) + * --dynamicbase / --nxcompat accepted for PE GNU ld compatibility */ #define LD_TOOL "ld" @@ -83,8 +88,11 @@ typedef struct LdOptions { const char* script_path; /* -T */ int text_base_set; /* -Ttext seen */ uint64_t text_base; /* -Ttext ADDR: static ET_EXEC image base */ - int no_default_libs; /* -nostdlib / --no-default-libs */ + int no_default_libs; /* -nostdlib / -nodefaultlibs / --no-default-libs */ + int nostdlib; /* -nostdlib: no CRT startup or default libraries */ + int nostartfiles; /* -nostartfiles: caller supplies CRT startup */ int pic_explicit; /* -static / -pie / -no-pie / -shared seen */ + int target_explicit; /* -target / --target seen */ const char* support_dir; /* --support-dir */ const char* sysroot; /* --sysroot / KIT_SYSROOT */ uint16_t pe_subsystem; /* KitPeSubsystem */ @@ -110,6 +118,7 @@ typedef struct LdOptions { const char** lib_dirs; /* -L */ uint32_t nlib_dirs; + int lib_dirs_precollected; /* first pass collected argv -L / --library-path */ char** owned_paths; /* sysroot-expanded argv/search paths */ size_t* owned_path_sizes; uint32_t nowned_paths; @@ -128,7 +137,10 @@ typedef struct LdOptions { int strip_debug; /* -S / --strip-debug */ int allow_undefined; /* shared output undefined-symbol policy */ int static_link; /* -static: hosted libc should pick static profile */ - int wants_hosted_libc; /* -lc: expand crt + libc through hosted resolver */ + int wants_hosted_libc; /* libc request: expand crt + libc via hosted resolver */ + int windows_ucrt_target; /* *-windows-gnullvm: MinGW UCRT hosted profile */ + int explicit_crt_start; /* caller supplied crt1/Scrt1/rcrt1-style start */ + int has_compiler_runtime; /* caller supplied compiler builtins/runtime */ DriverHostedPlan hosted; /* --build-id state */ @@ -232,6 +244,7 @@ void driver_help_ld(void) { " --no-gc-sections Disable section GC (default)\n" " -E, --export-dynamic Promote defined globals into dynsym\n" " (no-op for -shared; recorded for exe)\n" + " --eh-frame-hdr Accepted for GNU ld compatibility\n" " -S, --strip-debug Omit debug info from linked output\n" " --no-undefined Reject unresolved symbols in -shared " "output\n" @@ -293,12 +306,53 @@ static void ld_insert_order(LdOptions* o, uint32_t pos, uint8_t kind, o->norder++; } +static int ld_str_prefix(const char* s, const char* prefix) { + size_t n = driver_strlen(prefix); + return driver_strneq(s, prefix, n); +} + +static const char* ld_basename(const char* path) { + const char* slash = strrchr(path, '/'); + return slash ? slash + 1 : path; +} + +static int ld_is_compiler_runtime_archive(const char* path) { + const char* base = ld_basename(path); + if ((ld_str_prefix(base, "libcompiler_builtins") && + (driver_has_suffix(base, ".rlib") || driver_has_suffix(base, ".a"))) || + (ld_str_prefix(base, "libclang_rt.builtins") && + driver_has_suffix(base, ".a")) || + driver_streq(base, "libgcc.a") || driver_streq(base, "libgcc_eh.a")) { + return 1; + } + return 0; +} + +static int ld_is_compiler_runtime_libname(const char* name) { + return driver_streq(name, "gcc") || driver_streq(name, "gcc_eh") || + driver_streq(name, "compiler_builtins") || + ld_str_prefix(name, "clang_rt.builtins"); +} + +static int ld_is_crt_start_object(const char* path) { + const char* base = ld_basename(path); + return driver_streq(base, "crt1.o") || driver_streq(base, "Scrt1.o") || + driver_streq(base, "rcrt1.o") || driver_streq(base, "gcrt1.o") || + driver_streq(base, "crt0.o"); +} + +static void ld_note_object_path(LdOptions* o, const char* path) { + if (ld_is_crt_start_object(path)) o->explicit_crt_start = 1; +} + static void ld_push_object(LdOptions* o, const char* path) { + ld_note_object_path(o, path); o->object_files[o->nobject_files++] = path; ld_push_order(o, KIT_LINK_INPUT_OBJ_BYTES, o->nobject_files - 1u); } static void ld_insert_object(LdOptions* o, const char* path, uint32_t pos) { + ld_note_object_path(o, path); o->object_files[o->nobject_files++] = path; ld_insert_order(o, pos, KIT_LINK_INPUT_OBJ_BYTES, o->nobject_files - 1u); } @@ -306,6 +360,7 @@ static void ld_insert_object(LdOptions* o, const char* path, uint32_t pos) { static void ld_push_archive(LdOptions* o, const char* path, int owned, size_t owned_size) { LdArchive* a = &o->archives[o->narchives++]; + if (ld_is_compiler_runtime_archive(path)) o->has_compiler_runtime = 1; a->path = path; a->owned = owned; a->owned_size = owned_size; @@ -337,6 +392,59 @@ static void ld_push_dso(LdOptions* o, const char* path, int owned, ld_push_order(o, KIT_LINK_INPUT_DSO, o->ndsos - 1u); } +static int ld_order_capacity(LdOptions* o, uint32_t nmore) { + if (o->norder + nmore <= o->argv_bound) return 0; + driver_errf(LD_TOOL, "too many linker inputs"); + return 1; +} + +static int ld_archive_capacity(LdOptions* o) { + if (o->narchives < o->argv_bound) return 0; + driver_errf(LD_TOOL, "too many archive inputs"); + return 1; +} + +static int ld_dso_capacity(LdOptions* o) { + if (o->ndsos < o->argv_bound) return 0; + driver_errf(LD_TOOL, "too many shared-object inputs"); + return 1; +} + +static int ld_object_capacity(LdOptions* o) { + if (o->nobject_files < o->argv_bound) return 0; + driver_errf(LD_TOOL, "too many object inputs"); + return 1; +} + +static uint32_t ld_add_archive_slot(LdOptions* o, const char* path, int owned, + size_t owned_size, uint8_t whole_archive, + uint8_t link_mode, uint8_t group_id) { + LdArchive* a = &o->archives[o->narchives++]; + if (ld_is_compiler_runtime_archive(path)) o->has_compiler_runtime = 1; + a->path = path; + a->owned = owned; + a->owned_size = owned_size; + a->whole_archive = whole_archive; + a->link_mode = link_mode; + a->group_id = group_id; + return o->narchives - 1u; +} + +static uint32_t ld_add_dso_slot(LdOptions* o, const char* path, int owned, + size_t owned_size) { + LdDso* d = &o->dsos[o->ndsos++]; + d->path = path; + d->owned = owned; + d->owned_size = owned_size; + return o->ndsos - 1u; +} + +static uint32_t ld_add_object_slot(LdOptions* o, const char* path) { + ld_note_object_path(o, path); + o->object_files[o->nobject_files++] = path; + return o->nobject_files - 1u; +} + /* Filename ends in `.so` (with no further extension) or in `.so.N` * for some run of digits and dots. */ static int driver_is_so_filename(const char* path) { @@ -409,6 +517,15 @@ static int ld_own_path(LdOptions* o, char* path, size_t size, return 0; } +static int ld_add_lib_dir(LdOptions* o, const char* dir) { + if (o->nlib_dirs >= o->argv_bound) { + driver_errf(LD_TOOL, "too many library search paths"); + return 1; + } + o->lib_dirs[o->nlib_dirs++] = dir; + return 0; +} + static int ld_add_sysroot_libdir(LdOptions* o) { char* path; size_t size; @@ -416,7 +533,15 @@ static int ld_add_sysroot_libdir(LdOptions* o) { if (!o->sysroot || !o->sysroot[0]) return 0; path = ld_join2(o->env, o->sysroot, "lib", &size); if (ld_own_path(o, path, size, &owned) != 0) return 1; - o->lib_dirs[o->nlib_dirs++] = owned; + if (ld_add_lib_dir(o, owned) != 0) return 1; + path = ld_join2(o->env, o->sysroot, "usr/lib", &size); + if (ld_own_path(o, path, size, &owned) != 0) return 1; + if (ld_add_lib_dir(o, owned) != 0) return 1; + if (o->target.os == KIT_OS_WINDOWS) { + path = ld_join2(o->env, o->sysroot, "lib/windows", &size); + if (ld_own_path(o, path, size, &owned) != 0) return 1; + if (ld_add_lib_dir(o, owned) != 0) return 1; + } return 0; } @@ -436,13 +561,885 @@ static int ld_sysroot_rewrite_path(LdOptions* o, const char* path, } static int ld_note_library_request(LdOptions* o, const char* name) { - if (driver_streq(name, "c") && !o->no_default_libs) { + if (ld_is_compiler_runtime_libname(name)) o->has_compiler_runtime = 1; + if (driver_streq(name, "c") && !o->nostdlib && !o->nostartfiles && + !o->explicit_crt_start) { + o->wants_hosted_libc = 1; + return 1; + } + if (o->windows_ucrt_target && + (driver_streq(name, "msvcrt") || driver_streq(name, "ucrt")) && + !o->nostdlib && !o->nostartfiles && !o->explicit_crt_start) { o->wants_hosted_libc = 1; return 1; } return 0; } +static const char* ld_windows_runtime_alias(LdOptions* o, const char* name) { + if (o->target.os != KIT_OS_WINDOWS) return NULL; + if (driver_streq(name, "gcc_eh")) return "unwind"; + if (!driver_streq(name, "gcc")) return NULL; + switch (o->target.arch) { + case KIT_ARCH_X86_64: + return "clang_rt.builtins-x86_64"; + case KIT_ARCH_ARM_64: + return "clang_rt.builtins-aarch64"; + default: + return NULL; + } +} + +static LibResolveKind ld_kind_for_exact_lib(const char* leaf) { + if (driver_has_suffix(leaf, ".tbd")) return LIB_RESOLVE_KIND_TBD; + if (driver_has_suffix(leaf, ".so") || driver_has_suffix(leaf, ".dylib")) + return LIB_RESOLVE_KIND_SHARED; + return LIB_RESOLVE_KIND_ARCHIVE; +} + +static int ld_resolve_exact_library(LdOptions* o, const char* leaf, + char** out_path, size_t* out_size, + LibResolveKind* out_kind) { + uint32_t i; + if (!leaf || !leaf[0]) return 1; + for (i = 0; i < o->nlib_dirs; ++i) { + size_t size = 0; + char* cand = driver_path_join(o->env, o->lib_dirs[i], leaf, &size); + if (!cand) return 1; + if (driver_path_exists(cand)) { + *out_path = cand; + *out_size = size; + if (out_kind) *out_kind = ld_kind_for_exact_lib(leaf); + return 0; + } + driver_free(o->env, cand, size); + } + if (driver_path_exists(leaf)) { + size_t n = driver_strlen(leaf); + char* cand = driver_alloc(o->env, n + 1u); + if (!cand) return 1; + driver_memcpy(cand, leaf, n + 1u); + *out_path = cand; + *out_size = n + 1u; + if (out_kind) *out_kind = ld_kind_for_exact_lib(leaf); + return 0; + } + return 1; +} + +static int ld_resolve_library(LdOptions* o, const char* name, + LibResolveMode mode, LibResolveOS resolve_os, + char** out_path, size_t* out_size, + LibResolveKind* out_kind) { + const char* alias; + if (name && name[0] == ':') + return ld_resolve_exact_library(o, name + 1, out_path, out_size, out_kind); + if (driver_lib_resolve_for_os(o->env, name, mode, resolve_os, o->lib_dirs, + o->nlib_dirs, out_path, out_size, + out_kind) == 0) + return 0; + if (resolve_os != LIB_RESOLVE_OS_WINDOWS) return 1; + alias = ld_windows_runtime_alias(o, name); + if (!alias) return 1; + return driver_lib_resolve_for_os(o->env, alias, mode, resolve_os, o->lib_dirs, + o->nlib_dirs, out_path, out_size, out_kind); +} + +static int ld_tok_eq(const char* tok, size_t n, const char* lit) { + size_t ln = driver_strlen(lit); + return n == ln && driver_strneq(tok, lit, ln); +} + +static int ld_tok_prefix(const char* tok, size_t n, const char* lit) { + size_t ln = driver_strlen(lit); + return n >= ln && driver_strneq(tok, lit, ln); +} + +static int ld_span_to_owned_cstr(LdOptions* o, const char* s, size_t n, + const char** out) { + size_t bytes = n + 1u; + char* buf; + if (bytes == 0) { + driver_errf(LD_TOOL, "out of memory"); + return 1; + } + buf = driver_alloc(o->env, bytes); + if (!buf) { + driver_errf(LD_TOOL, "out of memory"); + return 1; + } + if (n) driver_memcpy(buf, s, n); + buf[n] = '\0'; + return ld_own_path(o, buf, bytes, out); +} + +static int ld_set_arch(LdOptions* o, const char* arch) { + KitArchKind k; + uint8_t ptr_size; + if (!arch) { + driver_errf(LD_TOOL, "-arch requires an argument"); + return 1; + } + if (driver_streq(arch, "arm64e")) arch = "arm64"; + if (driver_arch_from_name(arch, &k, &ptr_size) != 0) { + driver_errf(LD_TOOL, "unsupported -arch value: %.*s", + KIT_SLICE_ARG(kit_slice_cstr(arch))); + return 1; + } + o->target.arch = k; + o->target.ptr_size = ptr_size; + o->target.ptr_align = ptr_size; + return 0; +} + +static void ld_set_platform(LdOptions* o, const char* platform) { + if (!platform) return; + if (driver_streq(platform, "macos")) { + o->target.os = KIT_OS_MACOS; + o->target.obj = KIT_OBJ_MACHO; + if (!o->pic_explicit) + o->target.pic = driver_default_pic(o->target.obj, o->target.os); + } else if (driver_streq(platform, "linux")) { + o->target.os = KIT_OS_LINUX; + o->target.obj = KIT_OBJ_ELF; + if (!o->pic_explicit) + o->target.pic = driver_default_pic(o->target.obj, o->target.os); + } +} + +static int ld_set_target_triple(LdOptions* o, const char* triple) { + KitTargetSpec t; + uint8_t pic; + if (!triple) { + driver_errf(LD_TOOL, "-target requires an argument"); + return 1; + } + if (driver_target_from_triple(triple, &t) != 0) { + driver_errf(LD_TOOL, "unrecognized target triple: %.*s", + KIT_SLICE_ARG(kit_slice_cstr(triple))); + return 1; + } + o->windows_ucrt_target = strstr(triple, "windows-gnullvm") != NULL; + pic = o->target.pic; + o->target = t; + if (o->pic_explicit) + o->target.pic = pic; + else + o->target.pic = driver_default_pic(o->target.obj, o->target.os); + o->target_explicit = 1; + return 0; +} + +static int ld_set_gnu_emulation(LdOptions* o, const char* emu) { + uint8_t pic; + KitTargetSpec t; + if (!emu) { + driver_errf(LD_TOOL, "-m requires an argument"); + return 1; + } + if (driver_streq(emu, "i386pep")) { + memset(&t, 0, sizeof t); + t.arch = KIT_ARCH_X86_64; + t.os = KIT_OS_WINDOWS; + t.obj = KIT_OBJ_COFF; + t.ptr_size = 8; + t.ptr_align = 8; + } else if (driver_streq(emu, "arm64pe")) { + memset(&t, 0, sizeof t); + t.arch = KIT_ARCH_ARM_64; + t.os = KIT_OS_WINDOWS; + t.obj = KIT_OBJ_COFF; + t.ptr_size = 8; + t.ptr_align = 8; + } else { + driver_errf(LD_TOOL, "unsupported GNU ld emulation: %.*s", + KIT_SLICE_ARG(kit_slice_cstr(emu))); + return 1; + } + if (o->target_explicit) return 0; + pic = o->target.pic; + o->target = t; + if (o->pic_explicit) + o->target.pic = pic; + else + o->target.pic = driver_default_pic(o->target.obj, o->target.os); + return 0; +} + +static int ld_is_pe_gnu_noop(const char* tok, size_t n) { + return ld_tok_eq(tok, n, "--dynamicbase") || + ld_tok_eq(tok, n, "--disable-auto-image-base") || + ld_tok_eq(tok, n, "--enable-auto-image-base") || + ld_tok_eq(tok, n, "--high-entropy-va") || + ld_tok_eq(tok, n, "--nxcompat"); +} + +static int ld_is_cc_driver_noop(const char* tok, size_t n) { + return ld_tok_eq(tok, n, "-m32") || ld_tok_eq(tok, n, "-m64") || + ld_tok_eq(tok, n, "-fno-use-linker-plugin") || + ld_tok_eq(tok, n, "-nolibc") || + ld_tok_prefix(tok, n, "--unwindlib="); +} + +static int ld_apply_z_option(LdOptions* o, const char* z, size_t n) { + if (ld_tok_eq(z, n, "defs")) { + o->allow_undefined = 0; + return 0; + } + if (ld_tok_eq(z, n, "nodefs")) { + o->allow_undefined = 1; + return 0; + } + if (ld_tok_eq(z, n, "noexecstack") || ld_tok_eq(z, n, "relro") || + ld_tok_eq(z, n, "now") || ld_tok_eq(z, n, "text")) { + return 0; + } + driver_errf(LD_TOOL, "unsupported -z option: %.*s", (int)n, z); + return 1; +} + +static int ld_parse_wl(LdOptions* o, const char* arg) { + const char* p = arg; + int expect_rpath = 0; + int expect_soname = 0; + int expect_interp = 0; + int expect_z = 0; + int expect_emulation = 0; + while (*p) { + const char* tok = p; + size_t n = 0; + while (p[n] && p[n] != ',') ++n; + p = tok + n + (tok[n] == ',' ? 1u : 0u); + + if (expect_rpath || expect_soname || expect_interp || expect_z || + expect_emulation) { + if (expect_rpath) { + const char* owned; + if (ld_span_to_owned_cstr(o, tok, n, &owned) != 0) return 1; + o->rpaths[o->nrpaths++] = owned; + } else if (expect_soname) { + const char* owned; + if (ld_span_to_owned_cstr(o, tok, n, &owned) != 0) return 1; + o->soname = owned; + } else if (expect_interp) { + const char* owned; + if (ld_span_to_owned_cstr(o, tok, n, &owned) != 0) return 1; + o->interp_path = owned; + } else if (expect_z) { + if (ld_apply_z_option(o, tok, n) != 0) return 1; + } else if (expect_emulation) { + const char* owned; + if (ld_span_to_owned_cstr(o, tok, n, &owned) != 0) return 1; + if (ld_set_gnu_emulation(o, owned) != 0) return 1; + } + expect_rpath = expect_soname = expect_interp = expect_z = + expect_emulation = 0; + continue; + } + + if (ld_tok_eq(tok, n, "-dead_strip") || + ld_tok_eq(tok, n, "--gc-sections")) { + o->gc_sections = 1; + continue; + } + if (ld_tok_eq(tok, n, "--no-gc-sections")) { + o->gc_sections = 0; + continue; + } + if (ld_tok_eq(tok, n, "-Bstatic")) { + o->cur_link_mode = KIT_LM_STATIC; + continue; + } + if (ld_tok_eq(tok, n, "-Bdynamic")) { + o->cur_link_mode = KIT_LM_DYNAMIC; + continue; + } + if (ld_tok_eq(tok, n, "--as-needed")) { + o->cur_link_mode = KIT_LM_AS_NEEDED; + continue; + } + if (ld_tok_eq(tok, n, "--no-as-needed")) { + o->cur_link_mode = KIT_LM_DYNAMIC; + continue; + } + if (ld_tok_eq(tok, n, "--whole-archive")) { + o->cur_whole_archive = 1; + continue; + } + if (ld_tok_eq(tok, n, "--no-whole-archive")) { + o->cur_whole_archive = 0; + continue; + } + if (ld_tok_eq(tok, n, "-z")) { + expect_z = 1; + continue; + } + if (n > 2 && tok[0] == '-' && tok[1] == 'z') { + if (ld_apply_z_option(o, tok + 2, n - 2) != 0) return 1; + continue; + } + if (ld_tok_eq(tok, n, "--eh-frame-hdr") || + ld_tok_eq(tok, n, "--fix-cortex-a53-843419")) + continue; + if (ld_is_cc_driver_noop(tok, n)) continue; + if (ld_is_pe_gnu_noop(tok, n)) continue; + if (ld_tok_eq(tok, n, "-m")) { + expect_emulation = 1; + continue; + } + if (ld_tok_eq(tok, n, "-nostdlib")) { + o->no_default_libs = 1; + o->nostdlib = 1; + o->nostartfiles = 1; + continue; + } + if (ld_tok_eq(tok, n, "-nostartfiles")) { + o->nostartfiles = 1; + continue; + } + if (ld_tok_eq(tok, n, "-nodefaultlibs") || + ld_tok_eq(tok, n, "--no-default-libs")) { + o->no_default_libs = 1; + continue; + } + if (ld_tok_eq(tok, n, "-S") || ld_tok_eq(tok, n, "--strip-debug")) { + o->strip_debug = 1; + continue; + } + if (ld_tok_eq(tok, n, "-E") || ld_tok_eq(tok, n, "--export-dynamic")) { + o->export_dynamic = 1; + continue; + } + if (ld_tok_eq(tok, n, "--enable-new-dtags")) { + o->new_dtags = 1; + continue; + } + if (ld_tok_eq(tok, n, "--disable-new-dtags")) { + o->new_dtags = 0; + continue; + } + if (ld_tok_eq(tok, n, "-rpath")) { + expect_rpath = 1; + continue; + } + if (ld_tok_prefix(tok, n, "-rpath=")) { + const char* owned; + if (ld_span_to_owned_cstr(o, tok + 7, n - 7u, &owned) != 0) return 1; + o->rpaths[o->nrpaths++] = owned; + continue; + } + if (ld_tok_eq(tok, n, "-soname")) { + expect_soname = 1; + continue; + } + if (ld_tok_prefix(tok, n, "-soname=")) { + const char* owned; + if (ld_span_to_owned_cstr(o, tok + 8, n - 8u, &owned) != 0) return 1; + o->soname = owned; + continue; + } + if (ld_tok_eq(tok, n, "-dynamic-linker") || + ld_tok_eq(tok, n, "--dynamic-linker")) { + expect_interp = 1; + continue; + } + if (ld_tok_prefix(tok, n, "-dynamic-linker=")) { + const char* owned; + if (ld_span_to_owned_cstr(o, tok + 16, n - 16u, &owned) != 0) return 1; + o->interp_path = owned; + continue; + } + + driver_errf(LD_TOOL, "unsupported -Wl, token: %.*s", (int)n, tok); + return 1; + } + if (expect_rpath || expect_soname || expect_interp || expect_z || + expect_emulation) { + driver_errf(LD_TOOL, "-Wl option requires another comma argument"); + return 1; + } + return 0; +} + +/* ---------- GNU input-script expansion (.so text scripts) ---------- */ + +typedef struct LdInputScript { + LdOptions* o; + const char* script_path; + const char* script_dir; + const char* p; + const char* end; + uint32_t order_pos; + uint32_t insert_pos; + uint8_t saw_input_directive; + uint8_t added; +} LdInputScript; + +static int ld_is_space_char(char c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || + c == '\v'; +} + +static int ld_is_ident_char(char c) { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || c == '_'; +} + +static char ld_ascii_upper(char c) { + return (c >= 'a' && c <= 'z') ? (char)(c - 'a' + 'A') : c; +} + +static int ld_span_eq_ci(const char* s, size_t n, const char* lit) { + size_t ln = driver_strlen(lit); + size_t i; + if (n != ln) return 0; + for (i = 0; i < n; ++i) + if (ld_ascii_upper(s[i]) != ld_ascii_upper(lit[i])) return 0; + return 1; +} + +static void ld_script_skip_ws(LdInputScript* s) { + for (;;) { + while (s->p < s->end && ld_is_space_char(*s->p)) ++s->p; + if (s->p + 1 < s->end && s->p[0] == '/' && s->p[1] == '*') { + s->p += 2; + while (s->p + 1 < s->end && !(s->p[0] == '*' && s->p[1] == '/')) ++s->p; + if (s->p + 1 < s->end) s->p += 2; + continue; + } + if (s->p < s->end && *s->p == '#') { + while (s->p < s->end && *s->p != '\n') ++s->p; + continue; + } + if (s->p < s->end && (*s->p == ';' || *s->p == ',')) { + ++s->p; + continue; + } + break; + } +} + +static int ld_script_try_kw(LdInputScript* s, const char* kw) { + const char* p; + size_t n = driver_strlen(kw); + ld_script_skip_ws(s); + if ((size_t)(s->end - s->p) < n) return 0; + if (!ld_span_eq_ci(s->p, n, kw)) return 0; + p = s->p + n; + if (p < s->end && ld_is_ident_char(*p)) return 0; + s->p = p; + return 1; +} + +static void ld_script_skip_balanced(LdInputScript* s) { + int depth = 0; + if (s->p >= s->end || *s->p != '(') return; + while (s->p < s->end) { + char c = *s->p++; + if (c == '"' || c == '\'') { + char q = c; + while (s->p < s->end) { + c = *s->p++; + if (c == '\\' && s->p < s->end) { + ++s->p; + continue; + } + if (c == q) break; + } + continue; + } + if (c == '(') { + ++depth; + } else if (c == ')') { + --depth; + if (depth == 0) return; + } + } +} + +static int ld_script_next_token(LdInputScript* s, const char** out, + size_t* out_len) { + const char* start; + char quote; + ld_script_skip_ws(s); + if (s->p >= s->end || *s->p == ')') return 0; + if (*s->p == '(') return 0; + quote = *s->p; + if (quote == '"' || quote == '\'') { + ++s->p; + start = s->p; + while (s->p < s->end && *s->p != quote) { + if (*s->p == '\\' && s->p + 1 < s->end) + s->p += 2; + else + ++s->p; + } + *out = start; + *out_len = (size_t)(s->p - start); + if (s->p < s->end) ++s->p; + return 1; + } + start = s->p; + while (s->p < s->end && !ld_is_space_char(*s->p) && *s->p != '(' && + *s->p != ')' && *s->p != ';' && *s->p != ',') + ++s->p; + if (s->p == start) { + ++s->p; + return 0; + } + *out = start; + *out_len = (size_t)(s->p - start); + return 1; +} + +static int ld_path_has_slash(const char* s) { + while (*s) { + if (*s == '/' || *s == '\\') return 1; + ++s; + } + return 0; +} + +static int ld_script_dirname(LdOptions* o, const char* path, const char** out) { + const char* slash = strrchr(path, '/'); + size_t n; + if (!slash) { + *out = ""; + return 0; + } + n = (size_t)(slash - path); + if (n == 0) n = 1; /* root directory */ + return ld_span_to_owned_cstr(o, path, n, out); +} + +static int ld_script_resolve_file(LdInputScript* s, const char* name, + const char** out_path, int* out_owned, + size_t* out_size) { + LdOptions* o = s->o; + const char* rewritten; + uint32_t i; + *out_path = NULL; + *out_owned = 0; + *out_size = 0; + + if (name[0] == '/' || name[0] == '=' || ld_path_has_slash(name)) { + if (ld_sysroot_rewrite_path(o, name, &rewritten) != 0) return 1; + if (!driver_path_exists(rewritten)) { + driver_errf(LD_TOOL, "%.*s: linker script input not found: %.*s", + KIT_SLICE_ARG(kit_slice_cstr(s->script_path)), + KIT_SLICE_ARG(kit_slice_cstr(rewritten))); + return 1; + } + *out_path = rewritten; + return 0; + } + + if (s->script_dir && s->script_dir[0]) { + size_t size = 0; + char* cand = driver_path_join(o->env, s->script_dir, name, &size); + if (!cand) { + driver_errf(LD_TOOL, "out of memory"); + return 1; + } + if (driver_path_exists(cand)) { + *out_path = cand; + *out_owned = 1; + *out_size = size; + return 0; + } + driver_free(o->env, cand, size); + } + + for (i = 0; i < o->nlib_dirs; ++i) { + size_t size = 0; + char* cand = driver_path_join(o->env, o->lib_dirs[i], name, &size); + if (!cand) { + driver_errf(LD_TOOL, "out of memory"); + return 1; + } + if (driver_path_exists(cand)) { + *out_path = cand; + *out_owned = 1; + *out_size = size; + return 0; + } + driver_free(o->env, cand, size); + } + + if (driver_path_exists(name)) { + *out_path = name; + return 0; + } + + driver_errf(LD_TOOL, "%.*s: linker script input not found: %.*s", + KIT_SLICE_ARG(kit_slice_cstr(s->script_path)), + KIT_SLICE_ARG(kit_slice_cstr(name))); + return 1; +} + +static int ld_script_add_order(LdInputScript* s, uint8_t kind, uint32_t index) { + if (!s->added) { + s->o->order[s->order_pos].kind = kind; + s->o->order[s->order_pos].index = index; + s->insert_pos = s->order_pos + 1u; + s->added = 1; + return 0; + } + if (ld_order_capacity(s->o, 1) != 0) return 1; + ld_insert_order(s->o, s->insert_pos, kind, index); + s->insert_pos++; + return 0; +} + +static int ld_script_add_path(LdInputScript* s, const char* path, int owned, + size_t owned_size, int as_needed, + uint8_t group_id) { + LdOptions* o = s->o; + if (driver_has_suffix(path, ".a") || driver_has_suffix(path, ".rlib")) { + uint32_t idx; + if (ld_archive_capacity(o) != 0) goto fail; + idx = ld_add_archive_slot(o, path, owned, owned_size, o->cur_whole_archive, + as_needed ? KIT_LM_AS_NEEDED : o->cur_link_mode, + group_id); + return ld_script_add_order(s, KIT_LINK_INPUT_ARCHIVE, idx); + } + if (driver_is_so_filename(path) || driver_has_suffix(path, ".dylib") || + driver_has_suffix(path, ".tbd")) { + uint32_t idx; + if (as_needed) goto skip; + if (ld_dso_capacity(o) != 0) goto fail; + idx = ld_add_dso_slot(o, path, owned, owned_size); + return ld_script_add_order(s, KIT_LINK_INPUT_DSO, idx); + } + { + uint32_t idx; + const char* keep = path; + if (owned) { + if (ld_own_path(o, (char*)path, owned_size, &keep) != 0) return 1; + owned = 0; + owned_size = 0; + } + if (ld_object_capacity(o) != 0) return 1; + idx = ld_add_object_slot(o, keep); + return ld_script_add_order(s, KIT_LINK_INPUT_OBJ_BYTES, idx); + } + +skip: + if (owned) driver_free(o->env, (void*)path, owned_size); + return 0; +fail: + if (owned) driver_free(o->env, (void*)path, owned_size); + return 1; +} + +static int ld_script_add_token(LdInputScript* s, const char* tok, size_t n, + int as_needed, uint8_t group_id) { + LdOptions* o = s->o; + const char* owned_tok = NULL; + const char* path = NULL; + int owned_path = 0; + size_t path_size = 0; + + if (n == 0) return 0; + + if (n > 2 && tok[0] == '-' && tok[1] == 'l' && tok[2] != ':') { + char* resolved = NULL; + size_t resolved_size = 0; + LibResolveKind kind; + LibResolveMode mode = (o->cur_link_mode == KIT_LM_STATIC) + ? LIB_RESOLVE_STATIC_ONLY + : LIB_RESOLVE_DYNAMIC_PREFER; + LibResolveOS resolve_os = (o->target.os == KIT_OS_WINDOWS) + ? LIB_RESOLVE_OS_WINDOWS + : LIB_RESOLVE_OS_POSIX; + if (ld_span_to_owned_cstr(o, tok + 2, n - 2u, &owned_tok) != 0) return 1; + if (ld_resolve_library(o, owned_tok, mode, resolve_os, &resolved, + &resolved_size, &kind) != 0) { + driver_errf(LD_TOOL, "%.*s: cannot resolve linker script input -l%.*s", + KIT_SLICE_ARG(kit_slice_cstr(s->script_path)), + KIT_SLICE_ARG(kit_slice_cstr(owned_tok))); + return 1; + } + if ((kind == LIB_RESOLVE_KIND_SHARED || kind == LIB_RESOLVE_KIND_TBD) && + as_needed) { + driver_free(o->env, resolved, resolved_size); + return 0; + } + return ld_script_add_path(s, resolved, 1, resolved_size, as_needed, + group_id); + } + + if (n > 3 && tok[0] == '-' && tok[1] == 'l' && tok[2] == ':') { + tok += 3; + n -= 3; + } else if (tok[0] == '-') { + driver_errf(LD_TOOL, "%.*s: unsupported linker script token: %.*s", + KIT_SLICE_ARG(kit_slice_cstr(s->script_path)), (int)n, tok); + return 1; + } + + if (ld_span_to_owned_cstr(o, tok, n, &owned_tok) != 0) return 1; + if (ld_script_resolve_file(s, owned_tok, &path, &owned_path, &path_size) != 0) + return 1; + return ld_script_add_path(s, path, owned_path, path_size, as_needed, + group_id); +} + +static int ld_script_parse_list(LdInputScript* s, int as_needed, + uint8_t group_id, int group_directive); + +static int ld_script_parse_nested(LdInputScript* s, int as_needed, + uint8_t group_id) { + if (ld_script_try_kw(s, "AS_NEEDED")) + return ld_script_parse_list(s, 1, group_id, 0) == 0 ? 1 : -1; + if (ld_script_try_kw(s, "INPUT")) + return ld_script_parse_list(s, as_needed, group_id, 0) == 0 ? 1 : -1; + if (ld_script_try_kw(s, "GROUP")) + return ld_script_parse_list(s, as_needed, group_id, 1) == 0 ? 1 : -1; + return 0; +} + +static int ld_script_parse_list(LdInputScript* s, int as_needed, + uint8_t group_id, int group_directive) { + LdOptions* o = s->o; + ld_script_skip_ws(s); + if (s->p >= s->end || *s->p != '(') { + driver_errf(LD_TOOL, "%.*s: expected '(' in linker script input list", + KIT_SLICE_ARG(kit_slice_cstr(s->script_path))); + return 1; + } + ++s->p; + s->saw_input_directive = 1; + if (group_directive && group_id == 0) { + if (o->next_group_id == UINT8_MAX) { + driver_errf(LD_TOOL, "too many GROUP directives in linker scripts"); + return 1; + } + group_id = ++o->next_group_id; + } + for (;;) { + const char* tok; + size_t n; + ld_script_skip_ws(s); + if (s->p >= s->end) { + driver_errf(LD_TOOL, "%.*s: unterminated linker script input list", + KIT_SLICE_ARG(kit_slice_cstr(s->script_path))); + return 1; + } + if (*s->p == ')') { + ++s->p; + return 0; + } + { + int nested = ld_script_parse_nested(s, as_needed, group_id); + if (nested < 0) return 1; + if (nested > 0) continue; + } + ld_script_skip_ws(s); + if (s->p < s->end && *s->p == ')') continue; + if (!ld_script_next_token(s, &tok, &n)) { + driver_errf(LD_TOOL, "%.*s: malformed linker script input", + KIT_SLICE_ARG(kit_slice_cstr(s->script_path))); + return 1; + } + if (ld_script_add_token(s, tok, n, as_needed, group_id) != 0) return 1; + } +} + +static int ld_script_parse_input(LdInputScript* s) { + while (s->p < s->end) { + const char* tok; + size_t n; + ld_script_skip_ws(s); + if (s->p >= s->end) break; + if (ld_script_try_kw(s, "INPUT")) { + if (ld_script_parse_list(s, 0, 0, 0) != 0) return 1; + continue; + } + if (ld_script_try_kw(s, "GROUP")) { + if (ld_script_parse_list(s, 0, 0, 1) != 0) return 1; + continue; + } + if (ld_script_try_kw(s, "AS_NEEDED")) { + if (ld_script_parse_list(s, 1, 0, 0) != 0) return 1; + continue; + } + if (!ld_script_next_token(s, &tok, &n)) { + if (s->p < s->end && *s->p == '(') + ld_script_skip_balanced(s); + else if (s->p < s->end) + ++s->p; + continue; + } + ld_script_skip_ws(s); + if (s->p < s->end && *s->p == '(') ld_script_skip_balanced(s); + } + if (s->saw_input_directive && !s->added) { + driver_errf(LD_TOOL, "%.*s: linker script did not add any inputs", + KIT_SLICE_ARG(kit_slice_cstr(s->script_path))); + return 1; + } + return 0; +} + +static int ld_try_expand_dso_script(LdOptions* o, uint32_t order_pos, + int* changed) { + const KitFileIO* io = &o->env->file_io; + const KitLinkInputOrder* ord = &o->order[order_pos]; + const char* path; + KitFileData fd; + LdInputScript s; + const char* dir = NULL; + int rc = 0; + if (ord->kind != KIT_LINK_INPUT_DSO) return 0; + if (ord->index >= o->ndsos) return 0; + path = o->dsos[ord->index].path; + if (!path || !io->read_all) return 0; + memset(&fd, 0, sizeof fd); + if (io->read_all(io->user, path, &fd) != KIT_OK) { + driver_errf(LD_TOOL, "failed to read: %.*s", + KIT_SLICE_ARG(kit_slice_cstr(path))); + return 1; + } + if (kit_detect_fmt(fd.data, fd.size) != KIT_BIN_UNKNOWN) { + if (io->release) io->release(io->user, &fd); + return 0; + } + if (ld_script_dirname(o, path, &dir) != 0) { + if (io->release) io->release(io->user, &fd); + return 1; + } + memset(&s, 0, sizeof s); + s.o = o; + s.script_path = path; + s.script_dir = dir; + s.p = (const char*)fd.data; + s.end = s.p + fd.size; + s.order_pos = order_pos; + s.insert_pos = order_pos + 1u; + if (ld_script_parse_input(&s) != 0) { + rc = 1; + } else if (s.added) { + *changed = 1; + } + if (io->release) io->release(io->user, &fd); + return rc; +} + +static int ld_expand_dso_scripts(LdOptions* o) { + uint32_t pass; + for (pass = 0; pass < 8u; ++pass) { + uint32_t i; + int changed = 0; + for (i = 0; i < o->norder; ++i) { + if (ld_try_expand_dso_script(o, i, &changed) != 0) return 1; + } + if (!changed) return 0; + } + driver_errf(LD_TOOL, "too many nested linker-script inputs"); + return 1; +} + /* ---------- --build-id parsing ---------- */ /* Parse `--build-id=...` argument into options. Accepts "none", "sha256", @@ -605,7 +1602,7 @@ static int ld_try_ms_flag(LdOptions* o, const char* a) { return 1; } if ((val = ms_flag_value(a, "LIBPATH")) != NULL) { - o->lib_dirs[o->nlib_dirs++] = val; + if (ld_add_lib_dir(o, val) != 0) return -1; return 1; } if ((val = ms_flag_value(a, "DEFAULTLIB")) != NULL) { @@ -618,9 +1615,8 @@ static int ld_try_ms_flag(LdOptions* o, const char* a) { LibResolveMode mode = (o->cur_link_mode == KIT_LM_STATIC) ? LIB_RESOLVE_STATIC_ONLY : LIB_RESOLVE_DYNAMIC_PREFER; - if (driver_lib_resolve_for_os(o->env, val, mode, LIB_RESOLVE_OS_WINDOWS, - o->lib_dirs, o->nlib_dirs, &resolved, - &resolved_size, &kind) != 0) { + if (ld_resolve_library(o, val, mode, LIB_RESOLVE_OS_WINDOWS, &resolved, + &resolved_size, &kind) != 0) { driver_errf(LD_TOOL, "/DEFAULTLIB: cannot find %.*s", KIT_SLICE_ARG(kit_slice_cstr(val))); return -1; @@ -670,7 +1666,68 @@ static int ld_parse(int argc, char** argv, LdOptions* o) { o->ms_link_driver = 1; continue; } - if (driver_streq(argv[i], "--sysroot")) { + if (driver_streq(argv[i], "-nostdlib")) { + o->no_default_libs = 1; + o->nostdlib = 1; + o->nostartfiles = 1; + continue; + } + if (driver_streq(argv[i], "-nostartfiles")) { + o->nostartfiles = 1; + continue; + } + if (driver_streq(argv[i], "--no-default-libs") || + driver_streq(argv[i], "-nodefaultlibs")) { + o->no_default_libs = 1; + continue; + } + if (driver_strneq(argv[i], "-Wl,", 4)) { + const char* wl = argv[i] + 4; + if (strstr(wl, "-nostdlib")) { + o->no_default_libs = 1; + o->nostdlib = 1; + o->nostartfiles = 1; + continue; + } + if (strstr(wl, "-nostartfiles")) { + o->nostartfiles = 1; + continue; + } + if (strstr(wl, "--no-default-libs") || + strstr(wl, "-nodefaultlibs")) { + o->no_default_libs = 1; + continue; + } + continue; + } + if (driver_streq(argv[i], "-target") || + driver_streq(argv[i], "--target")) { + if (i + 1 < argc) { + if (ld_set_target_triple(o, argv[i + 1]) != 0) return 1; + i++; + } + continue; + } + if (driver_strneq(argv[i], "--target=", 9)) { + if (ld_set_target_triple(o, argv[i] + 9) != 0) return 1; + continue; + } + if (ld_is_cc_driver_noop(argv[i], driver_strlen(argv[i]))) { + continue; + } + if (driver_streq(argv[i], "-m")) { + if (i + 1 < argc) { + if (ld_set_gnu_emulation(o, argv[i + 1]) != 0) return 1; + i++; + } + continue; + } + if (driver_strneq(argv[i], "-m", 2) && argv[i][2] != '\0') { + if (ld_set_gnu_emulation(o, argv[i] + 2) != 0) return 1; + continue; + } + if (driver_streq(argv[i], "--sysroot") || + driver_streq(argv[i], "-syslibroot")) { if (i + 1 < argc) { o->sysroot = argv[i + 1]; i++; @@ -681,6 +1738,10 @@ static int ld_parse(int argc, char** argv, LdOptions* o) { o->sysroot = argv[i] + 10; continue; } + if (argv[i][0] != '-' && ld_is_crt_start_object(argv[i])) { + o->explicit_crt_start = 1; + continue; + } /* Detect -lc / -l c so hosted lib dirs are available for later -l * flags regardless of where -lc appears on the command line. */ if (driver_streq(argv[i], "-lc")) { @@ -698,10 +1759,46 @@ static int ld_parse(int argc, char** argv, LdOptions* o) { const char* env_sysroot = driver_getenv("KIT_SYSROOT"); if (env_sysroot && env_sysroot[0]) o->sysroot = env_sysroot; } + /* GNU ld treats -L / --library-path as global library-search state, not + * as a positional option that only affects later -l flags. Pre-collect + * those directories now so Rust-style invocations can put self-contained + * runtime libdirs after an earlier -l. */ + for (i = 1; i < argc; ++i) { + const char* a = argv[i]; + const char* val; + if (driver_strneq(a, "-L", 2)) { + const char* dir = a[2] ? a + 2 : (++i < argc ? argv[i] : NULL); + const char* rewritten; + if (!dir) { + driver_errf(LD_TOOL, "-L requires an argument"); + return 1; + } + if (ld_sysroot_rewrite_path(o, dir, &rewritten) != 0) return 1; + if (ld_add_lib_dir(o, rewritten) != 0) return 1; + continue; + } + if ((val = arg_eq_value(a, "--library-path")) != NULL) { + const char* rewritten; + if (ld_sysroot_rewrite_path(o, val, &rewritten) != 0) return 1; + if (ld_add_lib_dir(o, rewritten) != 0) return 1; + continue; + } + if (driver_streq(a, "--library-path")) { + const char* rewritten; + if (++i >= argc) { + driver_errf(LD_TOOL, "--library-path requires an argument"); + return 1; + } + if (ld_sysroot_rewrite_path(o, argv[i], &rewritten) != 0) return 1; + if (ld_add_lib_dir(o, rewritten) != 0) return 1; + continue; + } + } + o->lib_dirs_precollected = 1; if (ld_add_sysroot_libdir(o) != 0) return 1; /* Pre-populate the hosted lib dirs so user -l flags are resolved * against the correct sysroot even when they appear before -lc. */ - if (has_lc) { + if (has_lc && !o->explicit_crt_start) { DriverHostedRequest req; DriverHostedDirs dirs; uint32_t j; @@ -718,7 +1815,10 @@ static int ld_parse(int argc, char** argv, LdOptions* o) { if (ld_own_path(o, dirs.libdirs[j], dirs.libdir_sizes[j], &p) == 0) { dirs.libdirs[j] = NULL; dirs.libdir_sizes[j] = 0; - o->lib_dirs[o->nlib_dirs++] = p; + if (ld_add_lib_dir(o, p) != 0) { + driver_hosted_dirs_fini(&dirs); + return 1; + } } } driver_hosted_dirs_fini(&dirs); @@ -739,6 +1839,11 @@ static int ld_parse(int argc, char** argv, LdOptions* o) { if (ms_rc < 0) return 1; if (ms_rc > 0) continue; + if (driver_strneq(a, "-Wl,", 4)) { + if (ld_parse_wl(o, a + 4) != 0) return 1; + continue; + } + if (driver_streq(a, "-o")) { if (++i >= argc) { driver_errf(LD_TOOL, "-o requires an argument"); @@ -806,6 +1911,73 @@ static int ld_parse(int argc, char** argv, LdOptions* o) { if (ld_parse_pe_subsystem(o, argv[i]) != 0) return 1; continue; } + if (driver_streq(a, "-arch")) { + if (++i >= argc) { + driver_errf(LD_TOOL, "-arch requires an argument"); + return 1; + } + if (ld_set_arch(o, argv[i]) != 0) return 1; + continue; + } + if (driver_streq(a, "-platform_version")) { + if (i + 3 >= argc) { + driver_errf(LD_TOOL, "-platform_version requires three arguments"); + return 1; + } + ld_set_platform(o, argv[i + 1]); + i += 3; + continue; + } + if (driver_streq(a, "-macosx_version_min")) { + if (++i >= argc) { + driver_errf(LD_TOOL, "-macosx_version_min requires an argument"); + return 1; + } + o->target.os = KIT_OS_MACOS; + o->target.obj = KIT_OBJ_MACHO; + if (!o->pic_explicit) + o->target.pic = driver_default_pic(o->target.obj, o->target.os); + continue; + } + if (driver_streq(a, "-target") || driver_streq(a, "--target")) { + if (++i >= argc) { + driver_errf(LD_TOOL, "%s requires an argument", a); + return 1; + } + if (ld_set_target_triple(o, argv[i]) != 0) return 1; + continue; + } + if (driver_strneq(a, "--target=", 9)) { + if (ld_set_target_triple(o, a + 9) != 0) return 1; + continue; + } + if (driver_streq(a, "-flavor")) { + if (++i >= argc) { + driver_errf(LD_TOOL, "-flavor requires an argument"); + return 1; + } + if (!driver_streq(argv[i], "gnu")) { + driver_errf(LD_TOOL, "unsupported linker flavor: %s", argv[i]); + return 1; + } + continue; + } + if (ld_is_cc_driver_noop(a, driver_strlen(a))) { + continue; + } + if (driver_streq(a, "-m")) { + if (++i >= argc) { + driver_errf(LD_TOOL, "-m requires an argument"); + return 1; + } + if (ld_set_gnu_emulation(o, argv[i]) != 0) return 1; + continue; + } + if (driver_strneq(a, "-m", 2) && a[2] != '\0') { + if (ld_set_gnu_emulation(o, a + 2) != 0) return 1; + continue; + } + if (driver_streq(a, "-T")) { if (++i >= argc) { driver_errf(LD_TOOL, "-T requires an argument"); @@ -857,7 +2029,17 @@ static int ld_parse(int argc, char** argv, LdOptions* o) { * compiler runtime. A freestanding image (e.g. riscv32-none-elf) supplies * its own libkit_rt.a on the command line, and a pure-linker invocation * should not require a per-target runtime archive to exist. */ - if (driver_streq(a, "-nostdlib") || driver_streq(a, "--no-default-libs") || + if (driver_streq(a, "-nostdlib")) { + o->no_default_libs = 1; + o->nostdlib = 1; + o->nostartfiles = 1; + continue; + } + if (driver_streq(a, "-nostartfiles")) { + o->nostartfiles = 1; + continue; + } + if (driver_streq(a, "--no-default-libs") || driver_streq(a, "-nodefaultlibs")) { o->no_default_libs = 1; continue; @@ -878,9 +2060,9 @@ static int ld_parse(int argc, char** argv, LdOptions* o) { * global search-prefix that must be known before any -l / "=" rewrite), so * o->sysroot is already set. Here we only consume the tokens so they are * not misread as input files; do not re-apply the value. */ - if (driver_streq(a, "--sysroot")) { + if (driver_streq(a, "--sysroot") || driver_streq(a, "-syslibroot")) { if (++i >= argc) { - driver_errf(LD_TOOL, "--sysroot requires an argument"); + driver_errf(LD_TOOL, "%s requires an argument", a); return 1; } continue; @@ -896,14 +2078,18 @@ static int ld_parse(int argc, char** argv, LdOptions* o) { driver_errf(LD_TOOL, "-L requires an argument"); return 1; } - if (ld_sysroot_rewrite_path(o, dir, &rewritten) != 0) return 1; - o->lib_dirs[o->nlib_dirs++] = rewritten; + if (!o->lib_dirs_precollected) { + if (ld_sysroot_rewrite_path(o, dir, &rewritten) != 0) return 1; + if (ld_add_lib_dir(o, rewritten) != 0) return 1; + } continue; } if ((val = arg_eq_value(a, "--library-path")) != NULL) { const char* rewritten; - if (ld_sysroot_rewrite_path(o, val, &rewritten) != 0) return 1; - o->lib_dirs[o->nlib_dirs++] = rewritten; + if (!o->lib_dirs_precollected) { + if (ld_sysroot_rewrite_path(o, val, &rewritten) != 0) return 1; + if (ld_add_lib_dir(o, rewritten) != 0) return 1; + } continue; } if (driver_streq(a, "--library-path")) { @@ -912,8 +2098,10 @@ static int ld_parse(int argc, char** argv, LdOptions* o) { driver_errf(LD_TOOL, "--library-path requires an argument"); return 1; } - if (ld_sysroot_rewrite_path(o, argv[i], &rewritten) != 0) return 1; - o->lib_dirs[o->nlib_dirs++] = rewritten; + if (!o->lib_dirs_precollected) { + if (ld_sysroot_rewrite_path(o, argv[i], &rewritten) != 0) return 1; + if (ld_add_lib_dir(o, rewritten) != 0) return 1; + } continue; } @@ -935,9 +2123,8 @@ static int ld_parse(int argc, char** argv, LdOptions* o) { : LIB_RESOLVE_DYNAMIC_PREFER; resolve_os = (o->target.os == KIT_OS_WINDOWS) ? LIB_RESOLVE_OS_WINDOWS : LIB_RESOLVE_OS_POSIX; - if (driver_lib_resolve_for_os(o->env, name, mode, resolve_os, o->lib_dirs, - o->nlib_dirs, &resolved, &resolved_size, - &kind) != 0) { + if (ld_resolve_library(o, name, mode, resolve_os, &resolved, + &resolved_size, &kind) != 0) { driver_errf(LD_TOOL, "cannot find -l%.*s", KIT_SLICE_ARG(kit_slice_cstr(name))); return 1; @@ -960,9 +2147,8 @@ static int ld_parse(int argc, char** argv, LdOptions* o) { ? LIB_RESOLVE_OS_WINDOWS : LIB_RESOLVE_OS_POSIX; if (ld_note_library_request(o, val)) continue; - if (driver_lib_resolve_for_os(o->env, val, mode, resolve_os, o->lib_dirs, - o->nlib_dirs, &resolved, &resolved_size, - &kind) != 0) { + if (ld_resolve_library(o, val, mode, resolve_os, &resolved, + &resolved_size, &kind) != 0) { driver_errf(LD_TOOL, "cannot find -l%.*s", KIT_SLICE_ARG(kit_slice_cstr(val))); return 1; @@ -989,9 +2175,8 @@ static int ld_parse(int argc, char** argv, LdOptions* o) { : LIB_RESOLVE_DYNAMIC_PREFER; resolve_os = (o->target.os == KIT_OS_WINDOWS) ? LIB_RESOLVE_OS_WINDOWS : LIB_RESOLVE_OS_POSIX; - if (driver_lib_resolve_for_os(o->env, argv[i], mode, resolve_os, - o->lib_dirs, o->nlib_dirs, &resolved, - &resolved_size, &kind) != 0) { + if (ld_resolve_library(o, argv[i], mode, resolve_os, &resolved, + &resolved_size, &kind) != 0) { driver_errf(LD_TOOL, "cannot find -l%.*s", KIT_SLICE_ARG(kit_slice_cstr(argv[i]))); return 1; @@ -1011,6 +2196,14 @@ static int ld_parse(int argc, char** argv, LdOptions* o) { o->pic_explicit = 1; continue; } + if (driver_streq(a, "-static-pie")) { + o->target.pic = KIT_PIC_PIE; + o->pie = 1; + o->static_link = 1; + o->cur_link_mode = KIT_LM_STATIC; + o->pic_explicit = 1; + continue; + } if (driver_streq(a, "-pie")) { o->target.pic = KIT_PIC_PIE; o->pie = 1; @@ -1026,6 +2219,10 @@ static int ld_parse(int argc, char** argv, LdOptions* o) { o->interp_path = argv[i]; continue; } + if (driver_streq(a, "--no-dynamic-linker")) { + o->interp_path = ""; + continue; + } if ((val = arg_eq_value(a, "--dynamic-linker")) != NULL) { o->interp_path = val; continue; @@ -1097,6 +2294,10 @@ static int ld_parse(int argc, char** argv, LdOptions* o) { o->gc_sections = 1; continue; } + if (driver_streq(a, "-dead_strip")) { + o->gc_sections = 1; + continue; + } if (driver_streq(a, "--no-gc-sections")) { o->gc_sections = 0; continue; @@ -1109,6 +2310,20 @@ static int ld_parse(int argc, char** argv, LdOptions* o) { o->export_dynamic = 1; continue; } + if (driver_streq(a, "--eh-frame-hdr")) { + /* Rust/GNU drivers request this for unwind-table acceleration. Kit's + * ELF writer does not currently synthesize PT_GNU_EH_FRAME, so accept it + * as advisory until that feature is implemented. */ + continue; + } + if (driver_streq(a, "--fix-cortex-a53-843419")) { + /* AArch64 GNU/LLD compatibility. Kit does not currently implement this + * erratum-rewrite pass, so accept the request as advisory. */ + continue; + } + if (ld_is_pe_gnu_noop(a, driver_strlen(a))) { + continue; + } if (driver_streq(a, "--no-undefined")) { o->allow_undefined = 0; continue; @@ -1122,13 +2337,12 @@ static int ld_parse(int argc, char** argv, LdOptions* o) { driver_errf(LD_TOOL, "-z requires an argument"); return 1; } - if (driver_streq(argv[i], "defs")) { - o->allow_undefined = 0; - continue; - } - driver_errf(LD_TOOL, "unsupported -z option: %.*s", - KIT_SLICE_ARG(kit_slice_cstr(argv[i]))); - return 1; + if (ld_apply_z_option(o, argv[i], driver_strlen(argv[i])) != 0) return 1; + continue; + } + if (a[0] == '-' && a[1] == 'z' && a[2] != '\0') { + if (ld_apply_z_option(o, a + 2, driver_strlen(a + 2)) != 0) return 1; + continue; } if (driver_streq(a, "--whole-archive")) { @@ -1196,7 +2410,7 @@ static int ld_parse(int argc, char** argv, LdOptions* o) { { const char* path; if (ld_sysroot_rewrite_path(o, a, &path) != 0) return 1; - if (driver_has_suffix(path, ".a")) { + if (driver_has_suffix(path, ".a") || driver_has_suffix(path, ".rlib")) { ld_push_archive(o, path, 0, 0); } else if (driver_is_so_filename(path)) { ld_push_dso(o, path, 0, 0); @@ -1219,10 +2433,10 @@ static int ld_parse(int argc, char** argv, LdOptions* o) { ld_usage(); return 1; } - if (o->wants_hosted_libc && o->no_default_libs) { + if (o->wants_hosted_libc && (o->nostdlib || o->nostartfiles)) { driver_errf(LD_TOOL, "-lc hosted expansion is disabled by -nostdlib/" - "--no-default-libs"); + "-nostartfiles"); return 1; } if (o->shared) { @@ -1390,7 +2604,7 @@ static int ld_apply_hosted_before_after(LdOptions* o) { if (driver_hosted_resolve(&req, &o->hosted) != 0) return 1; /* Add hosted lib search dirs so user -l flags resolve against the sysroot. */ for (i = 0; i < o->hosted.nlib_search_dirs; ++i) - o->lib_dirs[o->nlib_dirs++] = o->hosted.lib_search_dirs[i]; + if (ld_add_lib_dir(o, o->hosted.lib_search_dirs[i]) != 0) return 1; for (i = 0; i < o->hosted.nbefore; ++i) { if (ld_append_hosted_input(o, &o->hosted.before[i], insert_pos, 1) != 0) return 1; @@ -1529,14 +2743,16 @@ static int ld_run_link(LdOptions* o) { if (!o->pic_explicit) o->pie = 1; } - /* Auto-link kit's compiler runtime for any target that has a variant — - * including the freestanding riscv32-none-elf / riscv64-none-elf targets, - * whose runtime (and, for rv32, the float-ABI it was detected with) is - * resolved like every other target's. A target with no variant is implicitly - * -nostdlib and supplies its own libkit_rt.a on the command line, so skip - * resolution rather than erroring. -nostdlib forces the skip for any target. + /* Auto-link kit's compiler runtime for any target that has a variant unless + * the caller has already supplied compiler builtins (Rust's + * libcompiler_builtins*.rlib, libgcc.a, clang_rt.builtins, ...). This keeps + * `kit ld object.o` useful while letting `kit ld` behave like a system + * linker under external compiler drivers. A target with no variant is + * implicitly -nostdlib and supplies its own libkit_rt.a on the command line, + * so skip resolution rather than erroring. -nostdlib forces the skip for any + * target. */ - if (!o->relocatable && !o->no_default_libs && + if (!o->relocatable && !o->no_default_libs && !o->has_compiler_runtime && driver_runtime_has_variant(o->target)) { if (driver_runtime_resolve(o->env, o->support_dir, o->driver_path, &runtime) != 0) { @@ -1552,6 +2768,8 @@ static int ld_run_link(LdOptions* o) { if (ld_apply_hosted_final(o) != 0) goto out; + if (ld_expand_dso_scripts(o) != 0) goto out; + for (i = initial_nobject_files; i < o->nobject_files; ++i) { const char* path = o->object_files[i]; if (load_file(io, path, &obj_lf[i]) != 0) { diff --git a/driver/env/linux.c b/driver/env/linux.c @@ -185,6 +185,35 @@ static const char* linux_multiarch_triple(KitArchKind arch) { } } +static int linux_add_gcc_libdirs(DriverEnv* env, DriverHostedDirs* out, + const char* triple) { + static const char* versions[] = {"15", "14", "13", "12", "11", + "10", "9", "8", "7"}; + char* gcc_triple_dir; + size_t gcc_triple_dir_size; + size_t i; + if (!triple) return 0; + gcc_triple_dir = + driver_path_join(env, "/usr/lib/gcc", triple, &gcc_triple_dir_size); + if (!gcc_triple_dir) return 1; + for (i = 0; i < sizeof versions / sizeof versions[0]; ++i) { + size_t dir_size; + char* dir = driver_path_join(env, gcc_triple_dir, versions[i], &dir_size); + if (!dir) { + driver_free(env, gcc_triple_dir, gcc_triple_dir_size); + return 1; + } + if (driver_path_exists(dir) && driver_hosted_dirs_add_lib(out, dir) != 0) { + driver_free(env, dir, dir_size); + driver_free(env, gcc_triple_dir, gcc_triple_dir_size); + return 1; + } + driver_free(env, dir, dir_size); + } + driver_free(env, gcc_triple_dir, gcc_triple_dir_size); + return 0; +} + /* A live Linux root is not sysroot-shaped: glibc keeps crt + libc_nonshared.a * in /usr/lib/<triple> and libc.so.6 in /lib/<triple>; musl/Alpine is flat in * /usr/lib + /lib. We hand the resolver an ordered library search list that @@ -208,6 +237,7 @@ int driver_default_hosted_dirs(DriverEnv* env, KitTargetSpec target, if (triple) { if (driver_hosted_dirs_add_lib_join(out, "/usr/lib", triple) != 0) return 1; if (driver_hosted_dirs_add_lib_join(out, "/lib", triple) != 0) return 1; + if (linux_add_gcc_libdirs(env, out, triple) != 0) return 1; } if (driver_hosted_dirs_add_lib(out, "/usr/lib") != 0) return 1; if (driver_hosted_dirs_add_lib(out, "/lib") != 0) return 1; diff --git a/include/kit/target.h b/include/kit/target.h @@ -27,8 +27,8 @@ * i386 / i486 / i586 / i686 -> KIT_ARCH_X86_32 (4) * aarch64 / arm64 / aa64 -> KIT_ARCH_ARM_64 (8) * arm / armv7 -> KIT_ARCH_ARM_32 (4) - * riscv64 / rv64 -> KIT_ARCH_RV64 (8) - * riscv32 / rv32 -> KIT_ARCH_RV32 (4) + * riscv64 / rv64 / riscv64<isa> -> KIT_ARCH_RV64 (8) + * riscv32 / rv32 / riscv32<isa> -> KIT_ARCH_RV32 (4) * wasm32 -> KIT_ARCH_WASM (4) * wasm64 -> KIT_ARCH_WASM (8) * The single authority for arch-name spellings; kit_target_from_triple() uses diff --git a/mk/test_unit.mk b/mk/test_unit.mk @@ -56,7 +56,7 @@ UNIT_TESTS_INTERNAL := \ aa64_isa_test rv64_decode_test rv32_decode_test aa64_sweep_gen \ reloc_uleb128_unit reloc_desc_test reloc_apply_test emu_rv64_unit_test \ interp_smoke_test jit_tls_relax_test coff_weak_alias_test \ - elf_version_import_test \ + coff_archive_fixpoint_test elf_version_import_test \ rv64_interp_smoke_test abi_classify_test ir_recorder_test \ native_direct_target_test x64_dbg_test cg_ir_lower_test tiny_inline_test dwarf_test_SRC := test/dwarf/dwarf_test.c @@ -71,6 +71,7 @@ reloc_desc_test_SRC := test/link/reloc_desc_test.c reloc_apply_test_SRC := test/link/reloc_apply_test.c jit_tls_relax_test_SRC := test/link/jit_tls_relax_test.c coff_weak_alias_test_SRC := test/link/coff_weak_alias_test.c +coff_archive_fixpoint_test_SRC := test/link/coff_archive_fixpoint_test.c elf_version_import_test_SRC := test/link/elf_version_import_test.c emu_rv64_unit_test_SRC := test/emu/rv64_vm_unit_test.c interp_smoke_test_SRC := test/interp/interp_smoke_test.c diff --git a/scripts/freebsd_sysroot.sh b/scripts/freebsd_sysroot.sh @@ -65,8 +65,16 @@ SYSROOT_MEMBERS=( ./usr/lib/libcompiler_rt.a ./usr/lib/libgcc.a ./usr/lib/libgcc_eh.a ./usr/lib/libgcc_s.so ./usr/lib/libpthread.a ./usr/lib/libthr.a + ./usr/lib/libexecinfo.a ./usr/lib/libexecinfo.so ./usr/lib/libexecinfo.so.1 + ./usr/lib/libkvm.a ./usr/lib/libkvm.so + ./usr/lib/libmemstat.a ./usr/lib/libmemstat.so ./usr/lib/libmemstat.so.3 + ./usr/lib/libprocstat.a ./usr/lib/libprocstat.so ./usr/lib/libprocstat.so.1 + ./usr/lib/libdevstat.a ./usr/lib/libdevstat.so + ./usr/lib/libutil.a ./usr/lib/libutil.so + ./usr/lib/librt.a ./usr/lib/librt.so ./lib/libc.so.7 ./lib/libsys.so.7 ./lib/libgcc_s.so.1 ./lib/libpthread.so.3 ./lib/libthr.so.3 + ./lib/libkvm.so.7 ./lib/libdevstat.so.7 ./lib/libutil.so.10 ./lib/librt.so.1 ) fetch_txz() { diff --git a/src/api/target.c b/src/api/target.c @@ -27,6 +27,17 @@ static bool triple_tok_prefix(const char* s, size_t n, const char* lit) { return n >= l && memcmp(s, lit, l) == 0; } +static bool riscv_arch_tok(const char* s, size_t n, const char* base) { + size_t l = strlen(base); + size_t i; + if (n < l || memcmp(s, base, l) != 0) return false; + for (i = l; i < n; ++i) { + if ((s[i] < 'a' || s[i] > 'z') && (s[i] < '0' || s[i] > '9')) + return false; + } + return true; +} + /* Recognize an architecture token, the single authority for the arch-name * spellings kit accepts. Writes arch + natural pointer size on a hit. Returns * true on success, false for an unrecognized token. Shared by the triple parser @@ -50,10 +61,14 @@ static bool arch_from_tok(const char* s, size_t n, KitArchKind* arch_out, } else if (triple_tok_eq(s, n, "arm") || triple_tok_eq(s, n, "armv7")) { arch = KIT_ARCH_ARM_32; ptr_size = 4; - } else if (triple_tok_eq(s, n, "riscv64") || triple_tok_eq(s, n, "rv64")) { + } else if (triple_tok_eq(s, n, "riscv64") || + triple_tok_eq(s, n, "rv64") || + riscv_arch_tok(s, n, "riscv64")) { arch = KIT_ARCH_RV64; ptr_size = 8; - } else if (triple_tok_eq(s, n, "riscv32") || triple_tok_eq(s, n, "rv32")) { + } else if (triple_tok_eq(s, n, "riscv32") || + triple_tok_eq(s, n, "rv32") || + riscv_arch_tok(s, n, "riscv32")) { arch = KIT_ARCH_RV32; ptr_size = 4; } else if (triple_tok_eq(s, n, "wasm32")) { diff --git a/src/arch/aa64/reloc.c b/src/arch/aa64/reloc.c @@ -34,6 +34,11 @@ static const RelocDescRow aa64_rows[] = { {R_AARCH64_LDST128_ABS_LO12_NC, {4, RELOC_DIRECT_PAGE}}, {R_AARCH64_ADR_GOT_PAGE, {4, RELOC_USES_GOT}}, {R_AARCH64_LD64_GOT_LO12_NC, {4, RELOC_USES_GOT}}, + {R_AARCH64_POINTER_TO_GOT, {4, RELOC_USES_GOT}}, + {R_AARCH64_TLSDESC_ADR_PAGE21, {4, 0}}, + {R_AARCH64_TLSDESC_LD64_LO12, {4, 0}}, + {R_AARCH64_TLSDESC_ADD_LO12, {4, 0}}, + {R_AARCH64_TLSDESC_CALL, {4, 0}}, {R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21, {4, RELOC_IS_TLS_GOT}}, {R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC, {4, RELOC_IS_TLS_GOT}}, {R_AARCH64_TLSLE_ADD_TPREL_HI12, {4, RELOC_IS_TLS_LE}}, @@ -45,6 +50,7 @@ static const RelocDescRow aa64_rows[] = { * classifies them as Local-Exec accesses via RELOC_IS_TLS_LE. */ {R_COFF_AARCH64_SECREL_LOW12A, {4, RELOC_IS_TLS_LE}}, {R_COFF_AARCH64_SECREL_HIGH12A, {4, RELOC_IS_TLS_LE}}, + {R_COFF_AARCH64_SECREL_LOW12L, {4, RELOC_IS_TLS_LE}}, }; const RelocDesc* aa64_reloc_desc(RelocKind k) { @@ -52,6 +58,14 @@ const RelocDesc* aa64_reloc_desc(RelocKind k) { (u32)(sizeof aa64_rows / sizeof aa64_rows[0]), k); } +static u32 aa64_movz64(u32 rd, u16 imm, u32 hw) { + return 0xd2800000u | ((hw & 3u) << 21) | ((u32)imm << 5) | (rd & 31u); +} + +static u32 aa64_movk64(u32 rd, u16 imm, u32 hw) { + return 0xf2800000u | ((hw & 3u) << 21) | ((u32)imm << 5) | (rd & 31u); +} + /* AArch64 instruction-immediate byte encoders (WS-C). Moved verbatim from the * format-neutral byte-patcher; reached via LinkArchDesc.reloc_apply_insn for * the instruction-embedded kinds. Encoding references: ARM ARMv8-A "ELF for @@ -59,6 +73,41 @@ const RelocDesc* aa64_reloc_desc(RelocKind k) { int aa64_reloc_apply_insn(Compiler* c, RelocKind k, u8* P_bytes, u64 S, i64 A, u64 P) { switch (k) { + case R_AARCH64_TLSDESC_ADR_PAGE21: + case R_AARCH64_TLSDESC_LD64_LO12: + case R_AARCH64_TLSDESC_ADD_LO12: + case R_AARCH64_TLSDESC_CALL: { + /* Static ELF local relaxation: replace the standard TLSDESC + * ADRP/LDR/ADD/BLR sequence with a 4-insn materialization of the + * local-exec TP-relative offset in x0, matching the TLSDESC resolver's + * return register. Dynamic TLSDESC descriptors are rejected earlier by + * the ELF linker unless the target is defined TLS in this image. */ + u64 v = (u64)((i64)S + A); + u16 imm = 0; + if (k == R_AARCH64_TLSDESC_ADR_PAGE21) { + imm = (u16)(v & 0xffffu); + wr_u32_le(P_bytes, aa64_movz64(0, imm, 0)); + } else if (k == R_AARCH64_TLSDESC_LD64_LO12) { + imm = (u16)((v >> 16) & 0xffffu); + wr_u32_le(P_bytes, aa64_movk64(0, imm, 1)); + } else if (k == R_AARCH64_TLSDESC_ADD_LO12) { + imm = (u16)((v >> 32) & 0xffffu); + wr_u32_le(P_bytes, aa64_movk64(0, imm, 2)); + } else { + imm = (u16)((v >> 48) & 0xffffu); + wr_u32_le(P_bytes, aa64_movk64(0, imm, 3)); + } + (void)P; + return 1; + } + case R_AARCH64_POINTER_TO_GOT: { + i64 disp = (i64)S + A - (i64)P; + if (disp < -(i64)(1ll << 31) || disp > (i64)0x7fffffff) + compiler_panic(c, SRCLOC_NONE, + "link: POINTER_TO_GOT out of range (need +/-2GiB)"); + wr_u32_le(P_bytes, (u32)(u64)disp); + return 1; + } case R_AARCH64_CONDBR19: case R_AARCH64_LD_PREL_LO19: { /* B.cond / CB(N)Z / LDR (literal) — imm19 in 4-byte units, diff --git a/src/arch/riscv/reloc.c b/src/arch/riscv/reloc.c @@ -34,6 +34,7 @@ static const RelocDescRow rv_rows[] = { {R_RV_PCREL_LO12_S, {4, 0}}, {R_RV_GOT_HI20, {4, RELOC_USES_GOT | RELOC_IS_PCREL_ANCHOR}}, {R_RV_TLS_GOT_HI20, {4, RELOC_IS_TLS_GOT}}, + {R_RV_TLS_GD_HI20, {4, RELOC_IS_TLS_LE}}, {R_RV_TPREL_HI20, {4, RELOC_IS_TLS_LE}}, {R_RV_TPREL_LO12_I, {4, RELOC_IS_TLS_LE}}, {R_RV_TPREL_LO12_S, {4, RELOC_IS_TLS_LE}}, @@ -44,18 +45,6 @@ static const RelocDescRow rv_rows[] = { {R_RV_RELAX, {4, RELOC_MARKER}}, {R_RV_TPREL_ADD, {4, RELOC_MARKER}}, {R_RV_ALIGN, {4, RELOC_MARKER}}, - {R_ADD8, {1, 0}}, - {R_SUB8, {1, 0}}, - {R_SUB6, {1, 0}}, - {R_SET6, {1, 0}}, - {R_ADD16, {2, 0}}, - {R_SUB16, {2, 0}}, - {R_ADD32, {4, 0}}, - {R_SUB32, {4, 0}}, - {R_ADD64, {8, 0}}, - {R_SUB64, {8, 0}}, - {R_SET_ULEB128, {1, RELOC_WIDTH_DYN}}, - {R_SUB_ULEB128, {1, RELOC_WIDTH_DYN}}, }; const RelocDesc* rv_reloc_desc(RelocKind k) { @@ -63,6 +52,103 @@ const RelocDesc* rv_reloc_desc(RelocKind k) { k); } +#define RV_X_RA 1u +#define RV_X_TP 4u +#define RV_X_A0 10u + +typedef struct RvTlsgdSeq { + u32 tmp_reg; + u32 call_off; +} RvTlsgdSeq; + +static u32 rv_rd(u32 instr) { return (instr >> 7) & 0x1fu; } + +static u32 rv_rs1(u32 instr) { return (instr >> 15) & 0x1fu; } + +static u32 rv_c_rd(u16 instr) { return ((u32)instr >> 7) & 0x1fu; } + +static u32 rv_c_rs2(u16 instr) { return ((u32)instr >> 2) & 0x1fu; } + +static int rv_is_32b_at(const u8* p) { return (rd_u16_le(p) & 0x3u) == 0x3u; } + +static int rv_is_addi_same_rd_rs1(u32 instr, u32 reg) { + return (instr & 0x707fu) == 0x13u && rv_rd(instr) == reg && + rv_rs1(instr) == reg; +} + +static int rv_is_addi_mv(u32 instr, u32* rd, u32* rs) { + if ((instr & 0xfff0707fu) != 0x13u) return 0; + *rd = rv_rd(instr); + *rs = rv_rs1(instr); + return *rd != 0 && *rs != 0; +} + +static int rv_is_c_mv(u16 instr, u32* rd, u32* rs) { + if ((instr & 0xf003u) != 0x8002u) return 0; + *rd = rv_c_rd(instr); + *rs = rv_c_rs2(instr); + return *rd != 0 && *rs != 0; +} + +static int rv_tlsgd_call_pair_at(const u8* p) { + return rd_u32_le(p) == (0x00000017u | (RV_X_RA << 7)) && + rd_u32_le(p + 4) == 0x000080e7u; +} + +static int rv_find_tlsgd_seq(const u8* p, RvTlsgdSeq* seq) { + enum { RV_TLSGD_SCAN_LIMIT = 24u }; + u32 auipc = rd_u32_le(p); + u32 addi = rd_u32_le(p + 4); + u32 tmp; + u32 off; + int a0_ready; + + if ((auipc & 0x7fu) != 0x17u) return 0; + tmp = rv_rd(auipc); + if (tmp == 0) return 0; + if (!rv_is_addi_same_rd_rs1(addi, tmp)) return 0; + + a0_ready = tmp == RV_X_A0; + for (off = 8u; off <= RV_TLSGD_SCAN_LIMIT;) { + if (rv_is_32b_at(p + off) && a0_ready && rv_tlsgd_call_pair_at(p + off)) { + seq->tmp_reg = tmp; + seq->call_off = off; + return 1; + } + + if (rv_is_32b_at(p + off)) { + u32 rd = 0, rs = 0; + if (!rv_is_addi_mv(rd_u32_le(p + off), &rd, &rs)) return 0; + if (rd == RV_X_A0 && rs == tmp) { + a0_ready = 1; + } else if (rd == tmp || rd == RV_X_A0 || a0_ready) { + return 0; + } + off += 4u; + continue; + } else { + u32 rd = 0, rs = 0; + if (!rv_is_c_mv(rd_u16_le(p + off), &rd, &rs)) return 0; + if (rd == RV_X_A0 && rs == tmp) { + a0_ready = 1; + } else if (rd == tmp || rd == RV_X_A0 || a0_ready) { + return 0; + } + off += 2u; + continue; + } + } + return 0; +} + +static u32 rv_encode_lui(u32 rd, u32 hi20) { + return 0x00000037u | (rd << 7) | (hi20 << 12); +} + +static u32 rv_encode_addi(u32 rd, u32 rs1, u32 lo12) { + return 0x00000013u | (rd << 7) | (rs1 << 15) | (lo12 << 20); +} + /* RISC-V instruction-immediate byte encoders (WS-C), shared by rv64 and rv32. * Moved verbatim from the format-neutral byte-patcher; reached via * LinkArchDesc.reloc_apply_insn. Encoding references: "RISC-V ELF psABI" §3 @@ -102,6 +188,40 @@ int rv_reloc_apply_insn(Compiler* c, RelocKind k, u8* P_bytes, u64 S, i64 A, wr_u32_le(P_bytes, instr); return 1; } + case R_RV_TLS_GD_HI20: { + /* Relax a general-dynamic block: + * auipc t, %tls_gd_pcrel_hi(sym) + * addi t, t, %pcrel_lo(.Lpcrel_hi) + * ... + * mv a0, t (if t is not already a0) + * call __tls_get_addr + * + * to local-exec address materialization: + * lui t, %tprel_hi(sym) + * addi t, t, %tprel_lo(sym) + * ... + * mv a0, t + * add a0, a0, tp + * nop + * + * Rust/LLVM sometimes uses a non-a0 temporary and keeps live values with + * compressed moves between the descriptor setup and the call. Accept + * only that narrow move-only shape; other dataflow stays a hard error. + */ + RvTlsgdSeq seq; + i64 v = (i64)S + A; + u32 hi20 = (u32)(((u64)(v + 0x800)) >> 12) & 0xfffffu; + u32 lo12 = (u32)((u64)v & 0xfffu); + if (!rv_find_tlsgd_seq(P_bytes, &seq)) { + compiler_panic(c, SRCLOC_NONE, + "link: unexpected RISC-V TLS_GD access sequence"); + } + wr_u32_le(P_bytes, rv_encode_lui(seq.tmp_reg, hi20)); + wr_u32_le(P_bytes + 4, rv_encode_addi(seq.tmp_reg, seq.tmp_reg, lo12)); + wr_u32_le(P_bytes + seq.call_off, 0x00450533u); /* add a0,a0,tp */ + wr_u32_le(P_bytes + seq.call_off + 4u, 0x00000013u); /* nop */ + return 1; + } case R_RV_LO12_I: case R_RV_TPREL_LO12_I: { /* I-type imm[11:0] in instruction bits [31:20]. Low 12 bits of diff --git a/src/arch/x64/reloc.c b/src/arch/x64/reloc.c @@ -9,9 +9,10 @@ * gets a slice row that overrides the neutral table's flag-free entry while * keeping the same 4-byte width. * - * The general-/local-dynamic TLS kinds (TLSGD/TLSLD/DTP*), GOTOFF64, and - * COPY are never applied through the static reloc record path and carry no - * descriptor. */ + * The general-dynamic TLS kinds (TLSGD/DTPMOD/DTP64), GOTOFF64, and COPY are + * never applied through the static reloc record path and carry no descriptor. + * Local-dynamic TLS (TLSLD + DTPOFF32) is relaxed to local-exec by the ELF + * static linker and therefore does have apply rows here. */ #include "obj/reloc.h" @@ -28,6 +29,8 @@ static const RelocDescRow x64_rows[] = { {R_X64_GOTPC32, {4, 0}}, {R_X64_GOTTPOFF, {4, RELOC_IS_TLS_GOT}}, {R_X64_TPOFF32, {4, RELOC_IS_TLS_LE}}, + {R_X64_DTPOFF32, {4, RELOC_IS_TLS_LE}}, + {R_X64_TLSLD, {4, 0}}, {R_X64_TLV, {4, RELOC_IS_TLVP}}, {R_X64_GLOB_DAT, {8, 0}}, {R_X64_JUMP_SLOT, {8, 0}}, @@ -54,6 +57,34 @@ int x64_reloc_apply_insn(Compiler* c, RelocKind k, u8* P_bytes, u64 S, i64 A, P_bytes[0] = (u8)((u64)v & 0xffu); return 1; } + case R_X64_TLSLD: { + u8* insn = P_bytes - 3; + if (insn[0] != 0x48u || insn[1] != 0x8du || insn[2] != 0x3du || + insn[7] != 0xe8u) { + compiler_panic(c, SRCLOC_NONE, + "link: unexpected x64 TLSLD access sequence"); + } + /* Relax: + * leaq sym@TLSLD(%rip), %rdi; call __tls_get_addr + * to: + * movq %fs:0, %rax; nop; nop; nop + * + * The paired DTPOFF32 access then applies as a TPOFF32 displacement + * against %rax. */ + insn[0] = 0x64u; + insn[1] = 0x48u; + insn[2] = 0x8bu; + insn[3] = 0x04u; + insn[4] = 0x25u; + insn[5] = 0x00u; + insn[6] = 0x00u; + insn[7] = 0x00u; + insn[8] = 0x00u; + insn[9] = 0x90u; + insn[10] = 0x90u; + insn[11] = 0x90u; + return 1; + } default: return 0; } diff --git a/src/link/link.h b/src/link/link.h @@ -91,6 +91,11 @@ typedef struct LinkSymbol { * stable across the dyn-link work. */ u8 imported; LinkInputId dso_input_id; + /* ELF-only: explicit version required by a name@VERSION undefined reference. + * Dynamic symbol emission still writes the base name; this field drives the + * matching .gnu.version_r entry. 0 means use the providing DSO's default + * version, if any. */ + Sym elf_version; u8 needs_plt; u8 needs_got; u8 needs_copy; diff --git a/src/link/link_internal.h b/src/link/link_internal.h @@ -329,6 +329,7 @@ void link_gc_compute(struct Linker*, LinkImage*, GcLive*); void link_gc_live_alloc(GcLive* g, struct Linker* l, Heap* h); void link_gc_live_free(GcLive* g, Heap* h); void link_gc_drop_dead_globals(struct Linker*, LinkImage*, const GcLive*); +LinkImage* link_image_alloc(Compiler*); /* ---- Public entries (link_layout.c) --------------------------------------- */ diff --git a/src/link/link_resolve.c b/src/link/link_resolve.c @@ -372,6 +372,80 @@ static LinkInputId find_dso_export(Linker* l, Sym name) { return LINK_INPUT_NONE; } +static int elf_split_versioned_undef(Compiler* c, Sym full, Sym* base_out, + Sym* version_out) { + Slice nm; + u32 i; + if (!c || full == 0 || !base_out || !version_out) return 0; + nm = pool_slice(c->global, full); + if (!nm.s || nm.len < 3u) return 0; + for (i = 1u; i + 1u < nm.len; ++i) { + u32 j; + if (nm.s[i] != '@') continue; + if (nm.s[i + 1u] == '@') return 0; + for (j = i + 1u; j < nm.len; ++j) + if (nm.s[j] == '@') return 0; + *base_out = + pool_intern_slice(c->global, (Slice){.s = nm.s, .len = i}); + *version_out = pool_intern_slice( + c->global, (Slice){.s = nm.s + i + 1u, .len = nm.len - i - 1u}); + return *base_out != 0 && *version_out != 0; + } + return 0; +} + +static const ObjImageSym* dso_dynsym_version(LinkInput* in, Sym name, + Sym version) { + const ObjImage* im; + u32 i, n; + if (!in || !in->obj || name == 0 || version == 0) return NULL; + im = obj_image(in->obj); + n = obj_image_ndynsyms(im); + for (i = 0; i < n; ++i) { + const ObjImageSym* s = obj_image_dynsym(im, i); + if (!s || s->name != name) continue; + if (s->section == OBJ_SEC_NONE && s->kind == SK_UNDEF) continue; + if (s->bind == SB_LOCAL) continue; + if (s->version == version) return s; + } + return NULL; +} + +static LinkInputId find_dso_export_version(Linker* l, Sym name, Sym version, + const ObjImageSym** sym_out) { + u32 ii; + if (sym_out) *sym_out = NULL; + if (name == 0 || version == 0) return LINK_INPUT_NONE; + for (ii = 0; ii < LinkInputs_count(&l->inputs); ++ii) { + LinkInput* in = LinkInputs_at(&l->inputs, ii); + const ObjImageSym* s; + if (in->kind != LINK_INPUT_DSO_BYTES) continue; + s = dso_dynsym_version(in, name, version); + if (!s) continue; + if (sym_out) *sym_out = s; + return in->id; + } + return LINK_INPUT_NONE; +} + +static int resolve_elf_versioned_dso_undef(Linker* l, LinkSymbol* s) { + Sym base = 0; + Sym version = 0; + const ObjImageSym* ds = NULL; + LinkInputId dso; + if (!l || !s || s->name == 0) return 0; + if (l->c->target.obj != KIT_OBJ_ELF) return 0; + if (!elf_split_versioned_undef(l->c, s->name, &base, &version)) return 0; + dso = find_dso_export_version(l, base, version, &ds); + if (dso == LINK_INPUT_NONE) return 0; + s->name = base; + s->kind = ds ? (u8)ds->kind : s->kind; + s->imported = 1; + s->dso_input_id = dso; + s->elf_version = version; + return 1; +} + /* Resolve undefined symbol `s` to the symbol named `alias` (a defined image * global or a DSO export), copying the target's binding into `s`. Returns 1 on * success. Shared by the recorded-alias path and the underscore heuristic. */ @@ -390,6 +464,7 @@ static int resolve_to_alias(Linker* l, LinkImage* img, LinkSymbol* s, s->defined = def->defined; s->imported = def->imported; s->dso_input_id = def->dso_input_id; + s->elf_version = def->elf_version; if (!s->defined && !s->imported) { s->kind = SK_ABS; s->vaddr = 0; @@ -403,11 +478,35 @@ static int resolve_to_alias(Linker* l, LinkImage* img, LinkSymbol* s, s->name = alias; s->imported = 1; s->dso_input_id = dso; + s->elf_version = 0; return 1; } return 0; } +static int resolve_elf_loader_owned_undef(Linker* l, LinkSymbol* s) { + Slice nm; + if (!l || !s || !s->name) return 0; + if (!l->emit_pie || l->c->target.obj != KIT_OBJ_ELF) return 0; + if (l->c->target.arch != KIT_ARCH_X86_64 && + l->c->target.arch != KIT_ARCH_RV64 && l->c->target.arch != KIT_ARCH_RV32) + return 0; + nm = pool_slice(l->c->global, s->name); + if (!slice_eq_cstr(nm, "__tls_get_addr")) return 0; + /* Some glibc dynamic TLS helpers are provided by the ELF interpreter itself + * (ld-linux), not by a regular DT_NEEDED DSO. GNU ld permits this + * unresolved-looking reference in dynamic executables; model it as a + * function import with no provider DSO so the PLT/dynsym machinery can emit + * any needed JUMP_SLOT without adding the interpreter as DT_NEEDED. RISC-V + * TLS-GD references that relax fully to local-exec will skip their call + * relocations later, leaving this dynsym entry harmlessly unused. */ + s->kind = SK_FUNC; + s->imported = 1; + s->dso_input_id = LINK_INPUT_NONE; + s->elf_version = 0; + return 1; +} + void link_resolve_undefs(Linker* l, LinkImage* img) { u32 i; @@ -451,12 +550,17 @@ void link_resolve_undefs(Linker* l, LinkImage* img) { } } if (s->name != 0) { + if (resolve_elf_versioned_dso_undef(l, s)) { + continue; + } LinkInputId dso = find_dso_export(l, s->name); if (dso != LINK_INPUT_NONE) { s->imported = 1; s->dso_input_id = dso; + s->elf_version = 0; continue; } + if (resolve_elf_loader_owned_undef(l, s)) continue; } if (l->resolver && s->name != 0) { Slice nm_s = pool_slice(l->c->global, s->name); @@ -1060,6 +1164,31 @@ static int member_satisfies(LinkArchiveMember* mem, const SymHash* defined, return hit; } +static int scan_archive_once(Linker* l, LinkArchive* ar, u32 max_order, + Sym want_ifunc_init, int weak_undef_pulls) { + SymHash defined, undefs; + int changed = 0; + u32 m; + symhash_init(&defined, l->heap); + symhash_init(&undefs, l->heap); + scan_presence_before(l, max_order, &defined, &undefs); + if (want_ifunc_init != 0 && + symhash_get(&defined, want_ifunc_init) == LINK_SYM_NONE) + symhash_set(&undefs, want_ifunc_init, 1u); + + for (m = 0; m < ar->nmembers; ++m) { + LinkArchiveMember* mem = &ar->members[m]; + if (mem->included) continue; + if (!mem->obj) continue; /* long-form skip (head/trailer) */ + if (!member_satisfies(mem, &defined, &undefs, weak_undef_pulls)) continue; + include_archive_member(l, ar, mem); + changed = 1; + } + symhash_fini(&defined); + symhash_fini(&undefs); + return changed; +} + /* Synthesize an ObjBuilder providing the mingw CRT ctor/dtor list * boundary symbols (`__CTOR_LIST__`, `__CTOR_END__`, `__DTOR_LIST__`, * `__DTOR_END__`) backed by a 16-byte zero blob. mingw's gccmain.o @@ -1132,7 +1261,9 @@ void link_synth_coff_ctor_dtor_list(Linker* l) { void link_ingest_archives(Linker* l) { u32 a, m; + int weak_undef_pulls; if (LinkArchives_count(&l->archives) == 0) return; + weak_undef_pulls = obj_format_weak_undef_pulls_archive_member(l->c); for (a = 0; a < LinkArchives_count(&l->archives); ++a) { LinkArchive* ar = LinkArchives_at(&l->archives, a); @@ -1145,6 +1276,19 @@ void link_ingest_archives(Linker* l) { } } + if (obj_format_global_archive_fixpoint(l->c)) { + for (;;) { + int changed = 0; + for (a = 0; a < LinkArchives_count(&l->archives); ++a) { + LinkArchive* ar = LinkArchives_at(&l->archives, a); + if (ar->whole_archive) continue; + changed |= scan_archive_once(l, ar, ~0u, 0, weak_undef_pulls); + } + if (!changed) break; + } + return; + } + for (a = 0; a < LinkArchives_count(&l->archives); ++a) { LinkArchive* ar = LinkArchives_at(&l->archives, a); Sym want_ifunc_init = 0; @@ -1153,28 +1297,9 @@ void link_ingest_archives(Linker* l) { want_ifunc_init = pool_intern_slice(l->c->global, SLICE_LIT("__kit_ifunc_init")); for (;;) { - SymHash defined, undefs; - int changed = 0; - symhash_init(&defined, l->heap); - symhash_init(&undefs, l->heap); - scan_presence_before(l, ar->order, &defined, &undefs); - if (want_ifunc_init != 0 && - symhash_get(&defined, want_ifunc_init) == LINK_SYM_NONE) - symhash_set(&undefs, want_ifunc_init, 1u); - - int weak_undef_pulls = obj_format_weak_undef_pulls_archive_member(l->c); - for (m = 0; m < ar->nmembers; ++m) { - LinkArchiveMember* mem = &ar->members[m]; - if (mem->included) continue; - if (!mem->obj) continue; /* long-form skip (head/trailer) */ - if (!member_satisfies(mem, &defined, &undefs, weak_undef_pulls)) - continue; - include_archive_member(l, ar, mem); - changed = 1; - } - symhash_fini(&defined); - symhash_fini(&undefs); - if (!changed) break; + if (!scan_archive_once(l, ar, ar->order, want_ifunc_init, + weak_undef_pulls)) + break; } } } diff --git a/src/obj/coff/link.c b/src/obj/coff/link.c @@ -1333,7 +1333,8 @@ static void coff_apply_all_relocs(LinkImage* img, * before delegating common kinds. */ if (r->kind == R_COFF_SECREL || r->kind == R_COFF_SECTION || r->kind == R_COFF_AARCH64_SECREL_LOW12A || - r->kind == R_COFF_AARCH64_SECREL_HIGH12A) { + r->kind == R_COFF_AARCH64_SECREL_HIGH12A || + r->kind == R_COFF_AARCH64_SECREL_LOW12L) { if (!tgt->defined || tgt->kind == SK_ABS) { compiler_panic(c, SRCLOC_NONE, "link_emit_coff: COFF SECREL/SECTION requires a " @@ -1348,6 +1349,24 @@ static void coff_apply_all_relocs(LinkImage* img, } else if (r->kind == R_COFF_SECTION) { /* PE section indices are 1-based; buckets are 0-based, so add 1. */ wr_u16_le(P_bytes, (u16)((tb + 1u) & 0xffffu)); + } else if (r->kind == R_COFF_AARCH64_SECREL_LOW12L) { + /* AArch64 SECREL_LOW12L: patch a load/store unsigned-immediate + * imm12. The encoded immediate is scaled by the access width, which + * is recoverable from the instruction's size/opc bits. */ + u64 v = sym_off_in_bucket + (u64)r->addend; + u64 lo12 = v & 0xfffu; + u32 instr = rd_u32_le(P_bytes); + u32 sz = (instr >> 30) & 0x3u; + u32 shift = (((instr >> 26) & 0x1u) && ((instr >> 23) & 0x1u)) ? 4u : sz; + u32 align_mask = (1u << shift) - 1u; + if (lo12 & align_mask) + compiler_panic(c, SRCLOC_NONE, + "link_emit_coff: ARM64 SECREL_LOW12L misaligned " + "offset 0x%llx for scale %u", + (unsigned long long)lo12, (unsigned)shift); + instr = (instr & ~(0xfffu << 10)) | + ((u32)((lo12 >> shift) & 0xfffu) << 10); + wr_u32_le(P_bytes, instr); } else { /* AArch64 SECREL_{LOW,HIGH}12A: patch the imm12 field of an * existing ADD-imm12 instruction. LOW12A = bits [11:0] of the diff --git a/src/obj/coff/reloc_aarch64.c b/src/obj/coff/reloc_aarch64.c @@ -52,6 +52,8 @@ u32 coff_aarch64_reloc_to(u32 kind /* RelocKind */) { return IMAGE_REL_ARM64_SECREL_LOW12A; case R_COFF_AARCH64_SECREL_HIGH12A: return IMAGE_REL_ARM64_SECREL_HIGH12A; + case R_COFF_AARCH64_SECREL_LOW12L: + return IMAGE_REL_ARM64_SECREL_LOW12L; default: return IMAGE_REL_ARM64_ABSOLUTE; } @@ -91,6 +93,8 @@ u32 coff_aarch64_reloc_from(u32 wire_type) { return R_COFF_AARCH64_SECREL_LOW12A; case IMAGE_REL_ARM64_SECREL_HIGH12A: return R_COFF_AARCH64_SECREL_HIGH12A; + case IMAGE_REL_ARM64_SECREL_LOW12L: + return R_COFF_AARCH64_SECREL_LOW12L; default: return (u32)-1; /* sentinel */ } diff --git a/src/obj/elf/elf.h b/src/obj/elf/elf.h @@ -319,6 +319,13 @@ static inline u8 elf_st_other(u8 vis /* SymVis */) { #define ELF_R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21 541 #define ELF_R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC 542 +/* TLS descriptor sequence. Static ET_EXEC links can relax a defined local TLS + * target to local-exec; dynamic TLSDESC descriptors are not emitted yet. */ +#define ELF_R_AARCH64_TLSDESC_ADR_PAGE21 562 +#define ELF_R_AARCH64_TLSDESC_LD64_LO12 563 +#define ELF_R_AARCH64_TLSDESC_ADD_LO12 564 +#define ELF_R_AARCH64_TLSDESC_CALL 569 + /* AArch64 dynamic-only reloc types: generated by the linker into * .rela.dyn / .rela.plt and processed by the runtime loader. */ #define ELF_R_AARCH64_COPY 1024 diff --git a/src/obj/elf/link.c b/src/obj/elf/link.c @@ -352,8 +352,12 @@ static void shift_image_addresses(LinkImage* img, u64 delta) { static int reloc_is_tlsle(RelocKind k, int tls_variant_ii) { if (k == R_TPOFF64 && !tls_variant_ii) return 1; return k == R_AARCH64_TLSLE_ADD_TPREL_HI12 || - k == R_AARCH64_TLSLE_ADD_TPREL_LO12_NC || k == R_RV_TPREL_HI20 || - k == R_RV_TPREL_LO12_I || k == R_RV_TPREL_LO12_S; + k == R_AARCH64_TLSLE_ADD_TPREL_LO12_NC || + k == R_AARCH64_TLSDESC_ADR_PAGE21 || + k == R_AARCH64_TLSDESC_LD64_LO12 || k == R_AARCH64_TLSDESC_ADD_LO12 || + k == R_AARCH64_TLSDESC_CALL || k == R_RV_TPREL_HI20 || + k == R_RV_TPREL_LO12_I || k == R_RV_TPREL_LO12_S || + k == R_RV_TLS_GD_HI20; } /* Variant-I TP bias: distance from the TLS image start to where `tp` points. @@ -387,8 +391,67 @@ static u64 tls_tcb_bias(Compiler* c) { * encode that signed offset directly at the reloc site (no TCB bias — * variant II's TCB sits *after* the image, so TPOFF is negative). */ static int reloc_is_x64_tlsle(RelocKind k, int tls_variant_ii) { - if (k == R_TPOFF64 && tls_variant_ii) return 1; - return k == R_X64_TPOFF32; + if (!tls_variant_ii) return 0; + return k == R_TPOFF64 || k == R_X64_TPOFF32 || k == R_X64_DTPOFF32; +} + +static int is_x64_tlsld_relaxed_get_addr_call(const LinkImage* img, + const LinkRelocApply* r, + const LinkSymbol* tgt, + const u8* P_bytes) { + Slice nm; + if (r->kind != R_X64_PLT32 && r->kind != R_PLT32) return 0; + if (!tgt->name) return 0; + if (r->offset < 8u) return 0; + nm = pool_slice(img->c->global, tgt->name); + if (!slice_eq_cstr(nm, "__tls_get_addr")) return 0; + return P_bytes[-8] == 0x64u && P_bytes[-7] == 0x48u && + P_bytes[-6] == 0x8bu && P_bytes[-5] == 0x04u && + P_bytes[-4] == 0x25u && P_bytes[-3] == 0x00u && + P_bytes[-2] == 0x00u && P_bytes[-1] == 0x00u && + P_bytes[0] == 0x00u && P_bytes[1] == 0x90u && + P_bytes[2] == 0x90u && P_bytes[3] == 0x90u; +} + +static int rv_tlsgd_hi_at(const LinkImage* img, u64 write_vaddr) { + u32 i; + for (i = 0; i < LinkRelocs_count(&img->relocs); ++i) { + const LinkRelocApply* hi = LinkRelocs_at(&img->relocs, i); + if (hi->kind == R_RV_TLS_GD_HI20 && hi->write_vaddr == write_vaddr) + return 1; + } + return 0; +} + +static int rv_lo12_pairs_tlsgd(const LinkImage* img, const LinkRelocApply* r, + const LinkSymbol* tgt) { + if (r->kind != R_RV_PCREL_LO12_I) return 0; + return rv_tlsgd_hi_at(img, tgt->vaddr); +} + +static int is_rv_tlsgd_get_addr_call(const LinkImage* img, + const LinkRelocApply* r, + const LinkSymbol* tgt, const u8* P_bytes) { + Slice nm; + u32 i; + (void)P_bytes; + if (r->kind != R_PLT32 && r->kind != R_RV_CALL) return 0; + if (!tgt->name) return 0; + if (r->offset < 8u) return 0; + nm = pool_slice(img->c->global, tgt->name); + if (!slice_eq_cstr(nm, "__tls_get_addr")) return 0; + for (i = 0; i < LinkRelocs_count(&img->relocs); ++i) { + const LinkRelocApply* hi = LinkRelocs_at(&img->relocs, i); + u64 span; + if (hi->kind != R_RV_TLS_GD_HI20) continue; + if (hi->input_id != r->input_id || hi->section_id != r->section_id) + continue; + if (hi->link_section_id != r->link_section_id) continue; + if (hi->write_vaddr >= r->write_vaddr) continue; + span = r->write_vaddr - hi->write_vaddr; + if (span >= 8u && span <= 24u) return 1; + } + return 0; } static int reloc_is_abs(RelocKind k) { return k == R_ABS32 || k == R_ABS64; } @@ -531,6 +594,7 @@ static void apply_all_relocs(LinkImage* img, u64 img_base) { P = r->write_vaddr + img_base; P_bytes = img->segment_bytes[seg->id - 1] + (size_t)(r->write_file_offset - seg->file_offset); + if (rv_lo12_pairs_tlsgd(img, r, tgt)) continue; { i64 disp = rv_pcrel_lo12_disp(img, tgt->vaddr + img_base, img_base); RelocKind alias = @@ -546,6 +610,8 @@ static void apply_all_relocs(LinkImage* img, u64 img_base) { P_bytes = img->segment_bytes[seg->id - 1] + (size_t)(r->write_file_offset - seg->file_offset); + if (is_rv_tlsgd_get_addr_call(img, r, tgt, P_bytes)) continue; + /* Imported target: redirect / rewrite per reloc kind (Phase 5). * * - CALL26 / JUMP26: target the import's PLT entry. The PLT stub @@ -583,6 +649,7 @@ static void apply_all_relocs(LinkImage* img, u64 img_base) { u32 dynidx = (img->dyn && canon_id < img->dyn->sym_dynidx_size) ? img->dyn->sym_dynidx[canon_id] : 0u; + if (is_x64_tlsld_relaxed_get_addr_call(img, r, tgt, P_bytes)) continue; if (reloc_is_branch26(r->kind)) { u64 plt_v = (img->dyn && canon_id < img->dyn->sym_dynidx_size) ? img->dyn->sym_plt_vaddr[canon_id] diff --git a/src/obj/elf/link_dyn.c b/src/obj/elf/link_dyn.c @@ -404,7 +404,8 @@ static Sym dso_default_version(LinkInput* in, Sym name) { n = obj_image_ndynsyms(im); for (i = 0; i < n; ++i) { const ObjImageSym* s = obj_image_dynsym(im, i); - if (s->name == name && s->version != 0) return s->version; + if (s->name == name && s->version != 0 && !s->version_hidden) + return s->version; } return 0; } @@ -426,9 +427,10 @@ typedef struct VerBuild { u32 capreq; } VerBuild; -/* Resolve one imported symbol's version requirement: look up its providing - * DSO's default version for the name, intern a (soname, version) requirement - * (assigning the next index), and stamp the symbol's versym slot. */ +/* Resolve one imported symbol's version requirement: explicit name@VERSION + * imports keep that requested version on LinkSymbol; plain imports use the + * providing DSO's default version for the name. The chosen (soname, version) + * pair is interned as a requirement and stamped into the symbol's versym slot. */ static void ver_process_import(VerBuild* vb, LinkSymId lsid) { LinkSymbol* s = LinkSyms_at(&vb->img->syms, lsid - 1); u32 di = vb->dyn->sym_dynidx[lsid]; @@ -440,7 +442,7 @@ static void ver_process_import(VerBuild* vb, LinkSymId lsid) { if (s->dso_input_id - 1u >= LinkInputs_count(&vb->l->inputs)) return; in = LinkInputs_at(&vb->l->inputs, s->dso_input_id - 1u); if (in->soname == 0) return; - ver = dso_default_version(in, s->name); + ver = s->elf_version ? s->elf_version : dso_default_version(in, s->name); if (ver == 0) return; for (r = 0; r < vb->nreq; ++r) if (vb->reqs[r].soname == in->soname && vb->reqs[r].version == ver) { diff --git a/src/obj/elf/read.c b/src/obj/elf/read.c @@ -537,11 +537,14 @@ static void read_elf_image(Compiler* c, ObjBuilder* ob, const u8* data, ds.value = st_value; ds.size = st_size; ds.version = 0; + ds.version_hidden = 0; if (versym && verdef_tbl && i < nversym && st_shndx != SHN_UNDEF) { u16 v = rd_u16_le(versym + (u64)i * 2u); u32 ndx = (u32)(v & VERSYM_VERSION); - if (!(v & VERSYM_HIDDEN) && ndx >= 2u && ndx <= verdef_max) + if (ndx >= 2u && ndx <= verdef_max) { ds.version = verdef_tbl[ndx]; + ds.version_hidden = (u8)((v & VERSYM_HIDDEN) != 0); + } } obj_image_add_dynsym(im, &ds); } @@ -1198,11 +1201,14 @@ ObjBuilder* read_elf_dso(Compiler* c, const char* name, const u8* data, ds.value = 0; ds.size = 0; ds.version = 0; + ds.version_hidden = 0; if (i < nversym) { u16 v = rd_u16_le(versym + (u64)i * 2u); u32 ndx = (u32)(v & VERSYM_VERSION); - if (!(v & VERSYM_HIDDEN) && ndx >= 2u && ndx <= verdef_max) + if (ndx >= 2u && ndx <= verdef_max) { ds.version = verdef_tbl[ndx]; + ds.version_hidden = (u8)((v & VERSYM_HIDDEN) != 0); + } } obj_image_add_dynsym(im, &ds); } diff --git a/src/obj/elf/reloc_aarch64.c b/src/obj/elf/reloc_aarch64.c @@ -67,6 +67,14 @@ u32 elf_aarch64_reloc_to(u32 kind /* RelocKind */) { return ELF_R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21; case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC: return ELF_R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC; + case R_AARCH64_TLSDESC_ADR_PAGE21: + return ELF_R_AARCH64_TLSDESC_ADR_PAGE21; + case R_AARCH64_TLSDESC_LD64_LO12: + return ELF_R_AARCH64_TLSDESC_LD64_LO12; + case R_AARCH64_TLSDESC_ADD_LO12: + return ELF_R_AARCH64_TLSDESC_ADD_LO12; + case R_AARCH64_TLSDESC_CALL: + return ELF_R_AARCH64_TLSDESC_CALL; case R_AARCH64_TLSLE_ADD_TPREL_HI12: return ELF_R_AARCH64_TLSLE_ADD_TPREL_HI12; case R_AARCH64_TLSLE_ADD_TPREL_LO12: @@ -154,6 +162,14 @@ u32 elf_aarch64_reloc_from(u32 elf_type) { return R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21; case ELF_R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC: return R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC; + case ELF_R_AARCH64_TLSDESC_ADR_PAGE21: + return R_AARCH64_TLSDESC_ADR_PAGE21; + case ELF_R_AARCH64_TLSDESC_LD64_LO12: + return R_AARCH64_TLSDESC_LD64_LO12; + case ELF_R_AARCH64_TLSDESC_ADD_LO12: + return R_AARCH64_TLSDESC_ADD_LO12; + case ELF_R_AARCH64_TLSDESC_CALL: + return R_AARCH64_TLSDESC_CALL; case ELF_R_AARCH64_TLSLE_ADD_TPREL_HI12: return R_AARCH64_TLSLE_ADD_TPREL_HI12; case ELF_R_AARCH64_TLSLE_ADD_TPREL_LO12: @@ -241,6 +257,14 @@ const char* elf_aarch64_reloc_name(u32 elf_type) { return "R_AARCH64_ADR_GOT_PAGE"; case ELF_R_AARCH64_LD64_GOT_LO12_NC: return "R_AARCH64_LD64_GOT_LO12_NC"; + case ELF_R_AARCH64_TLSDESC_ADR_PAGE21: + return "R_AARCH64_TLSDESC_ADR_PAGE21"; + case ELF_R_AARCH64_TLSDESC_LD64_LO12: + return "R_AARCH64_TLSDESC_LD64_LO12"; + case ELF_R_AARCH64_TLSDESC_ADD_LO12: + return "R_AARCH64_TLSDESC_ADD_LO12"; + case ELF_R_AARCH64_TLSDESC_CALL: + return "R_AARCH64_TLSDESC_CALL"; case ELF_R_AARCH64_GLOB_DAT: return "R_AARCH64_GLOB_DAT"; case ELF_R_AARCH64_JUMP_SLOT: diff --git a/src/obj/elf/reloc_riscv64.c b/src/obj/elf/reloc_riscv64.c @@ -45,6 +45,8 @@ u32 elf_riscv64_reloc_to(u32 kind /* RelocKind */) { return ELF_R_RISCV_GOT_HI20; case R_RV_TLS_GOT_HI20: return ELF_R_RISCV_TLS_GOT_HI20; + case R_RV_TLS_GD_HI20: + return ELF_R_RISCV_TLS_GD_HI20; case R_RV_TPREL_HI20: return ELF_R_RISCV_TPREL_HI20; case R_RV_TPREL_LO12_I: @@ -128,6 +130,8 @@ u32 elf_riscv64_reloc_from(u32 elf_type) { return R_RV_GOT_HI20; case ELF_R_RISCV_TLS_GOT_HI20: return R_RV_TLS_GOT_HI20; + case ELF_R_RISCV_TLS_GD_HI20: + return R_RV_TLS_GD_HI20; case ELF_R_RISCV_TPREL_HI20: return R_RV_TPREL_HI20; case ELF_R_RISCV_TPREL_LO12_I: diff --git a/src/obj/format.h b/src/obj/format.h @@ -230,6 +230,10 @@ typedef struct ObjFormatImpl { * (binutils/PE COMDAT semantics); ELF/Mach-O only pull for strong * undefs. COFF=1, else 0. */ u8 weak_undef_pulls_archive_member; + /* Archive libraries are searched as a global fixed point instead of a + * strictly positional POSIX scan. COFF linkers use library-set semantics + * close to this; ELF/Mach-O keep command-line archive order. */ + u8 global_archive_fixpoint; /* Can represent the ELF/Mach-O TLS-access symbol features the CG layer * mints (local-exec/initial-exec/local-dynamic/general-dynamic): ELF=1, * Mach-O=1; COFF (Windows TEB model) and Wasm have no such representation, diff --git a/src/obj/macho/read.c b/src/obj/macho/read.c @@ -295,6 +295,7 @@ static void read_macho_image(Compiler* c, ObjBuilder* ob, const u8* data, u8 type_field = (u8)(n_type & N_TYPE); ObjImageSym ds; + memset(&ds, 0, sizeof ds); ds.version = 0; /* Mach-O has no ELF-style symbol versioning */ ds.name = pool_intern_slice(c->global, (Slice){.s = nm, .len = nlen}); ds.bind = (n_desc & (N_WEAK_DEF | N_WEAK_REF)) ? SB_WEAK : SB_GLOBAL; diff --git a/src/obj/macho/reloc_aarch64.c b/src/obj/macho/reloc_aarch64.c @@ -44,6 +44,8 @@ u32 macho_aarch64_reloc_to(u32 kind /* RelocKind */) { return ARM64_RELOC_GOT_LOAD_PAGE21; case R_AARCH64_LD64_GOT_LO12_NC: return ARM64_RELOC_GOT_LOAD_PAGEOFF12; + case R_AARCH64_POINTER_TO_GOT: + return ARM64_RELOC_POINTER_TO_GOT; case R_AARCH64_TLVP_LOAD_PAGE21: return ARM64_RELOC_TLVP_LOAD_PAGE21; case R_AARCH64_TLVP_LOAD_PAGEOFF12: @@ -64,6 +66,7 @@ u32 macho_aarch64_reloc_pcrel(u32 kind /* RelocKind */) { case R_AARCH64_ADR_PREL_PG_HI21: case R_AARCH64_ADR_PREL_PG_HI21_NC: case R_AARCH64_ADR_GOT_PAGE: + case R_AARCH64_POINTER_TO_GOT: case R_AARCH64_TLVP_LOAD_PAGE21: return 1; default: @@ -161,6 +164,11 @@ int macho_aarch64_reloc_decode(const MachoRelocEntry* in, case ARM64_RELOC_GOT_LOAD_PAGEOFF12: out->kind = R_AARCH64_LD64_GOT_LO12_NC; return 1; + case ARM64_RELOC_POINTER_TO_GOT: + out->kind = R_AARCH64_POINTER_TO_GOT; + out->inplace_addend = 1; + out->inplace_signed = 1; + return 1; case ARM64_RELOC_TLVP_LOAD_PAGE21: out->kind = R_AARCH64_TLVP_LOAD_PAGE21; return 1; diff --git a/src/obj/obj.c b/src/obj/obj.c @@ -1172,10 +1172,9 @@ static void obj_reloc_index_ensure(ObjBuilder* ob) { if (ob->reloc_index) ob->heap->free(ob->heap, ob->reloc_index, sizeof(u32) * ob->reloc_index_len); - ob->reloc_index = - nlive ? (u32*)ob->heap->alloc(ob->heap, sizeof(u32) * nlive, - _Alignof(u32)) - : NULL; + ob->reloc_index = nlive ? (u32*)ob->heap->alloc( + ob->heap, sizeof(u32) * nlive, _Alignof(u32)) + : NULL; ob->reloc_index_len = nlive; } @@ -1404,6 +1403,12 @@ const char* reloc_kind_name(RelocKind k) { _CASE(R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21); _CASE(R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC); _CASE(R_COFF_ADDR32NB); + _CASE(R_AARCH64_POINTER_TO_GOT); + _CASE(R_AARCH64_TLSDESC_ADR_PAGE21); + _CASE(R_AARCH64_TLSDESC_LD64_LO12); + _CASE(R_AARCH64_TLSDESC_ADD_LO12); + _CASE(R_AARCH64_TLSDESC_CALL); + _CASE(R_RV_TLS_GD_HI20); _CASE(R_AARCH64_GLOB_DAT); _CASE(R_AARCH64_JUMP_SLOT); _CASE(R_AARCH64_RELATIVE); @@ -1468,6 +1473,7 @@ const char* reloc_kind_name(RelocKind k) { _CASE(R_COFF_SECTION); _CASE(R_COFF_AARCH64_SECREL_LOW12A); _CASE(R_COFF_AARCH64_SECREL_HIGH12A); + _CASE(R_COFF_AARCH64_SECREL_LOW12L); #undef _CASE } return "UNKNOWN"; @@ -1515,8 +1521,8 @@ void obj_strtab_put_raw(ObjStrtab* t, const void* bytes, u32 n) { static void obj_strtab_index_grow(ObjStrtab* t) { u32 ncap = t->scap ? t->scap * 2u : 256u; u32 mask = ncap - 1u; - ObjStrtabEnt* ns = - (ObjStrtabEnt*)t->heap->alloc(t->heap, sizeof(*ns) * ncap, _Alignof(ObjStrtabEnt)); + ObjStrtabEnt* ns = (ObjStrtabEnt*)t->heap->alloc(t->heap, sizeof(*ns) * ncap, + _Alignof(ObjStrtabEnt)); memset(ns, 0, sizeof(*ns) * ncap); for (u32 i = 0; i < t->scap; ++i) { u32 j; diff --git a/src/obj/obj.h b/src/obj/obj.h @@ -288,6 +288,26 @@ typedef enum RelocKind { /* COFF ADDR32NB: 32-bit image-relative RVA (S + A - ImageBase), used by * PE exception tables and other image metadata. */ R_COFF_ADDR32NB, + /* AArch64 Mach-O POINTER_TO_GOT: a 32-bit PC-relative data relocation that + * materializes the address of a GOT entry. Used by Apple toolchains in + * unwind metadata such as __eh_frame. */ + R_AARCH64_POINTER_TO_GOT, + /* AArch64 ELF TLSDESC static relocations. The ELF executable linker + * currently accepts these only for defined local TLS and relaxes the + * four-instruction descriptor call sequence to materialize a TP-relative + * local-exec offset. Dynamic TLSDESC descriptors are intentionally not + * represented yet. */ + R_AARCH64_TLSDESC_ADR_PAGE21, + R_AARCH64_TLSDESC_LD64_LO12, + R_AARCH64_TLSDESC_ADD_LO12, + R_AARCH64_TLSDESC_CALL, + /* RISC-V TLS General-Dynamic / Local-Dynamic access. Executable links + * currently relax it to local-exec when the TLS symbol is defined in the + * image. Appended at the enum tail to keep existing public values stable. */ + R_RV_TLS_GD_HI20, + /* AArch64 Windows SECREL low-12 load/store form. Appended at the enum tail + * to keep existing public relocation values stable. */ + R_COFF_AARCH64_SECREL_LOW12L, } RelocKind; typedef struct Section { @@ -871,6 +891,11 @@ int obj_format_supports_symbol_feature(const Compiler*, int symfeat); * ELF / Mach-O no (they pull only for strong undefs). */ int obj_format_weak_undef_pulls_archive_member(const Compiler*); +/* True when the active object format resolves archive libraries as a global + * fixed point rather than by strictly positional POSIX order. COFF yes, + * ELF / Mach-O / Wasm no. */ +int obj_format_global_archive_fixpoint(const Compiler*); + /* True when the active object format recovers weak-external / undefined * references via the mingw single-underscore alias convention (e.g. * `__set_app_type` <-> `_set_app_type`) during link symbol resolution. @@ -966,14 +991,15 @@ typedef struct ObjImageSym { ObjSecId section; /* OBJ_SEC_NONE for undefined imports */ u64 value; u64 size; - /* ELF symbol-version name (interned), set only for a DSO export that is the - * *default* (non-hidden) version of `name` — e.g. libc.so.7's - * fstat@@FBSD_1.5. 0 when the input carries no versioning, or this entry is a - * hidden compatibility alias (fstat@FBSD_1.0). The linker uses it to emit a - * matching .gnu.version_r requirement so the runtime binds the right version - * (mandatory on FreeBSD, where the INO64 transition gave `fstat`/`stat` two - * incompatible struct-stat layouts behind FBSD_1.0 vs FBSD_1.5). */ + /* ELF symbol-version name (interned) for a DSO export. 0 when the input + * carries no versioning for this entry. `version_hidden` distinguishes + * non-default compatibility aliases such as fstat@FBSD_1.0 from default + * exports such as fstat@@FBSD_1.5. The linker uses this both to emit the + * default requirement for plain imports and to satisfy explicit + * name@VERSION imports without losing the requested version. */ Sym version; + u8 version_hidden; + u8 pad[3]; } ObjImageSym; /* Dynamic relocation (.rela.dyn / .rela.plt, dyld binds, PE base relocs). @@ -1135,9 +1161,9 @@ ObjBuilder* read_tbd(Compiler*, const char* name, const u8* data, size_t len, * retains no external pointers and needs no per-add flatten). This replaced a * per-add buf_flatten + linear substring scan that was O(n^2) in the symbol * count -- 31% of all instructions when compiling sqlite to ELF. The dedup is - * exact-match (not the old suffix/tail-merge), so the table is marginally larger - * than binutils' where a name is a suffix of another, but still minimal and - * valid. */ + * exact-match (not the old suffix/tail-merge), so the table is marginally + * larger than binutils' where a name is a suffix of another, but still minimal + * and valid. */ typedef struct ObjStrtabEnt { u32 hash; u32 off; diff --git a/src/obj/obj_secnames.c b/src/obj/obj_secnames.c @@ -291,6 +291,11 @@ int obj_format_weak_undef_pulls_archive_member(const Compiler* c) { return fmt && fmt->weak_undef_pulls_archive_member; } +int obj_format_global_archive_fixpoint(const Compiler* c) { + const ObjFormatImpl* fmt = c ? obj_format_lookup(c->target.obj) : NULL; + return fmt && fmt->global_archive_fixpoint; +} + int obj_format_weak_extern_underscore_alias(const Compiler* c) { const ObjFormatImpl* fmt = c ? obj_format_lookup(c->target.obj) : NULL; return fmt && fmt->weak_extern_underscore_alias; diff --git a/src/obj/registry.c b/src/obj/registry.c @@ -555,6 +555,7 @@ static const ObjFormatImpl obj_format_impl_wasm = { .carries_file_only_debug = 0, .builds_own_static_got = 0, .weak_undef_pulls_archive_member = 0, + .global_archive_fixpoint = 0, .tls_symbol_features = 0, .alias_via_thunk = 0, .weak_undef_attr = "weak", @@ -585,6 +586,7 @@ static const ObjFormatImpl obj_format_impl_elf = { .carries_file_only_debug = 1, .builds_own_static_got = 0, .weak_undef_pulls_archive_member = 0, + .global_archive_fixpoint = 0, .tls_symbol_features = 1, .alias_via_thunk = 0, .weak_undef_attr = "weak", @@ -615,6 +617,7 @@ static const ObjFormatImpl obj_format_impl_macho = { .carries_file_only_debug = 1, .builds_own_static_got = 1, .weak_undef_pulls_archive_member = 0, + .global_archive_fixpoint = 0, .tls_symbol_features = 1, .alias_via_thunk = 1, .weak_undef_attr = "weak_import", @@ -647,6 +650,7 @@ static const ObjFormatImpl obj_format_impl_coff = { .carries_file_only_debug = 0, .builds_own_static_got = 0, .weak_undef_pulls_archive_member = 1, + .global_archive_fixpoint = 1, .tls_symbol_features = 0, .alias_via_thunk = 0, .weak_undef_attr = "weak", diff --git a/src/obj/reloc.c b/src/obj/reloc.c @@ -41,6 +41,18 @@ static const RelocDescRow neutral_rows[] = { {R_PC64, {8, 0}}, {R_GOT32, {4, 0}}, {R_PLT32, {4, 0}}, + {R_ADD8, {1, 0}}, + {R_SUB8, {1, 0}}, + {R_SUB6, {1, 0}}, + {R_SET6, {1, 0}}, + {R_ADD16, {2, 0}}, + {R_SUB16, {2, 0}}, + {R_ADD32, {4, 0}}, + {R_SUB32, {4, 0}}, + {R_ADD64, {8, 0}}, + {R_SUB64, {8, 0}}, + {R_SET_ULEB128, {1, RELOC_WIDTH_DYN}}, + {R_SUB_ULEB128, {1, RELOC_WIDTH_DYN}}, {R_COFF_SECREL, {4, RELOC_IS_SECREL}}, {R_COFF_SECTION, {2, 0}}, {R_COFF_ADDR32NB, {4, 0}}, diff --git a/src/obj/reloc_apply.c b/src/obj/reloc_apply.c @@ -88,11 +88,12 @@ int reloc_apply_neutral(Compiler* c, RelocKind k, u8* P_bytes, u64 S, i64 A, switch (k) { case R_ABS32: case R_X64_32S: - case R_X64_TPOFF32: { - /* All three write a 32-bit value at the site. ABS32 / _32S - * take an absolute (unsigned / sign-extended) symbol address; - * TPOFF32 takes the (caller-precomputed) TP-relative offset. - * At the byte level the encoding is identical. */ + case R_X64_TPOFF32: + case R_X64_DTPOFF32: { + /* All write a 32-bit value at the site. ABS32 / _32S take an + * absolute (unsigned / sign-extended) symbol address; TPOFF32 / + * DTPOFF32 take the caller-precomputed TP-relative offset. At the + * byte level the encoding is identical. */ u64 v = S + (u64)A; wr_u32_le(P_bytes, (u32)(v & 0xffffffffu)); return 1; diff --git a/src/os/windows/hosted.c b/src/os/windows/hosted.c @@ -80,7 +80,8 @@ static int hosted_sysroot_layout_windows(KitOsHostedDirs* dirs, const char* sysroot) { (void)target; if (kit_os_hosted_dirs_add_inc_join(dirs, sysroot, "include") != 0 || - kit_os_hosted_dirs_add_lib_join(dirs, sysroot, "lib") != 0) + kit_os_hosted_dirs_add_lib_join(dirs, sysroot, "lib") != 0 || + kit_os_hosted_dirs_add_lib_join(dirs, sysroot, "lib/windows") != 0) return 1; return 0; } diff --git a/test/api/target_test.c b/test/api/target_test.c @@ -1,6 +1,7 @@ /* target_test - public <kit/core.h> target creation and feature API. */ #include <kit/core.h> +#include <kit/target.h> #include <string.h> #include "lib/kit_unit.h" @@ -34,6 +35,36 @@ static int has(KitTarget* t, const char* name) { return kit_target_has_feature(t, kit_slice_cstr(name)); } +static void check_target_triple_parse(void) { + KitTargetSpec t; + KitArchKind arch = KIT_ARCH_X86_32; + uint8_t ptr_size = 0; + + EXPECT(kit_arch_from_name("riscv64gc", &arch, &ptr_size), + "riscv64gc arch spelling accepted"); + EXPECT(arch == KIT_ARCH_RV64 && ptr_size == 8, + "riscv64gc maps to rv64"); + + EXPECT(kit_arch_from_name("riscv32imac", &arch, &ptr_size), + "riscv32imac arch spelling accepted"); + EXPECT(arch == KIT_ARCH_RV32 && ptr_size == 4, + "riscv32imac maps to rv32"); + + memset(&t, 0, sizeof t); + EXPECT(kit_target_from_triple("riscv64gc-unknown-linux-gnu", &t), + "Rust riscv64gc Linux triple parses"); + EXPECT(t.arch == KIT_ARCH_RV64 && t.os == KIT_OS_LINUX && + t.obj == KIT_OBJ_ELF, + "Rust riscv64gc Linux triple maps to ELF Linux rv64"); + + memset(&t, 0, sizeof t); + EXPECT(kit_target_from_triple("riscv32imac-unknown-none-elf", &t), + "Rust riscv32imac none triple parses"); + EXPECT(t.arch == KIT_ARCH_RV32 && t.os == KIT_OS_FREESTANDING && + t.obj == KIT_OBJ_ELF, + "Rust riscv32imac none triple maps to freestanding rv32"); +} + static void check_x64_defaults_and_isa(void) { KitTargetSpec spec = target_spec(KIT_ARCH_X86_64, KIT_OS_LINUX, KIT_OBJ_ELF); KitTargetFeature disable_avx2[] = { @@ -120,6 +151,7 @@ static void check_errors(void) { int main(void) { kit_unit_init(&g_u); + check_target_triple_parse(); check_x64_defaults_and_isa(); check_rv64_isa_and_overrides(); check_wasm_features(); diff --git a/test/libc/glibc/Containerfile b/test/libc/glibc/Containerfile @@ -57,9 +57,15 @@ RUN set -eux; \ mkdir -p /sysroot/usr/include /sysroot/usr/lib /sysroot/lib; \ cp -r /usr/include/. /sysroot/usr/include/; \ cd /usr/lib/aarch64-linux-gnu; \ - cp Scrt1.o crti.o crtn.o libc_nonshared.a /sysroot/usr/lib/; \ + cp Scrt1.o crti.o crtn.o libc_nonshared.a \ + libdl.a libpthread.a libpthread_nonshared.a librt.a libutil.a \ + /sysroot/usr/lib/; \ cp -L /lib/aarch64-linux-gnu/libc.so.6 /sysroot/usr/lib/libc.so.6; \ cp -L /lib/aarch64-linux-gnu/libm.so.6 /sysroot/usr/lib/libm.so.6; \ + cp -L /lib/aarch64-linux-gnu/libdl.so.2 /sysroot/usr/lib/libdl.so.2; \ + cp -L /lib/aarch64-linux-gnu/libpthread.so.0 /sysroot/usr/lib/libpthread.so.0; \ + cp -L /lib/aarch64-linux-gnu/librt.so.1 /sysroot/usr/lib/librt.so.1; \ + cp -L /lib/aarch64-linux-gnu/libutil.so.1 /sysroot/usr/lib/libutil.so.1; \ cp -L /lib/ld-linux-aarch64.so.1 /sysroot/lib/ld-linux-aarch64.so.1; \ printf 'GROUP ( libc.so.6 libc_nonshared.a )\n' > /sysroot/usr/lib/libc.so; \ printf 'GROUP ( libm.so.6 )\n' > /sysroot/usr/lib/libm.so; \ diff --git a/test/libc/glibc/Containerfile.rv64 b/test/libc/glibc/Containerfile.rv64 @@ -37,9 +37,15 @@ RUN set -eux; \ mkdir -p /sysroot/usr/include /sysroot/usr/lib /sysroot/lib; \ cp -rL /usr/include/. /sysroot/usr/include/; \ cd /usr/lib/riscv64-linux-gnu; \ - cp Scrt1.o crti.o crtn.o libc_nonshared.a /sysroot/usr/lib/; \ + cp Scrt1.o crti.o crtn.o libc_nonshared.a \ + libdl.a libpthread.a libpthread_nonshared.a librt.a libutil.a \ + /sysroot/usr/lib/; \ cp -L /lib/riscv64-linux-gnu/libc.so.6 /sysroot/usr/lib/libc.so.6; \ cp -L /lib/riscv64-linux-gnu/libm.so.6 /sysroot/usr/lib/libm.so.6; \ + cp -L /lib/riscv64-linux-gnu/libdl.so.2 /sysroot/usr/lib/libdl.so.2; \ + cp -L /lib/riscv64-linux-gnu/libpthread.so.0 /sysroot/usr/lib/libpthread.so.0; \ + cp -L /lib/riscv64-linux-gnu/librt.so.1 /sysroot/usr/lib/librt.so.1; \ + cp -L /lib/riscv64-linux-gnu/libutil.so.1 /sysroot/usr/lib/libutil.so.1; \ cp -L /lib/ld-linux-riscv64-lp64d.so.1 /sysroot/lib/ld-linux-riscv64-lp64d.so.1; \ printf 'GROUP ( libc.so.6 libc_nonshared.a )\n' > /sysroot/usr/lib/libc.so; \ printf 'GROUP ( libm.so.6 )\n' > /sysroot/usr/lib/libm.so; \ diff --git a/test/libc/glibc/Containerfile.x64 b/test/libc/glibc/Containerfile.x64 @@ -30,9 +30,15 @@ RUN set -eux; \ mkdir -p /sysroot/usr/include /sysroot/usr/lib /sysroot/lib64; \ cp -r /usr/include/. /sysroot/usr/include/; \ cd /usr/lib/x86_64-linux-gnu; \ - cp Scrt1.o crti.o crtn.o libc_nonshared.a /sysroot/usr/lib/; \ + cp Scrt1.o crti.o crtn.o libc_nonshared.a \ + libdl.a libpthread.a libpthread_nonshared.a librt.a libutil.a \ + /sysroot/usr/lib/; \ cp -L /lib/x86_64-linux-gnu/libc.so.6 /sysroot/usr/lib/libc.so.6; \ cp -L /lib/x86_64-linux-gnu/libm.so.6 /sysroot/usr/lib/libm.so.6; \ + cp -L /lib/x86_64-linux-gnu/libdl.so.2 /sysroot/usr/lib/libdl.so.2; \ + cp -L /lib/x86_64-linux-gnu/libpthread.so.0 /sysroot/usr/lib/libpthread.so.0; \ + cp -L /lib/x86_64-linux-gnu/librt.so.1 /sysroot/usr/lib/librt.so.1; \ + cp -L /lib/x86_64-linux-gnu/libutil.so.1 /sysroot/usr/lib/libutil.so.1; \ cp -L /lib64/ld-linux-x86-64.so.2 /sysroot/lib64/ld-linux-x86-64.so.2; \ printf 'GROUP ( libc.so.6 libc_nonshared.a )\n' > /sysroot/usr/lib/libc.so; \ printf 'GROUP ( libm.so.6 )\n' > /sysroot/usr/lib/libm.so; \ diff --git a/test/link/coff_archive_fixpoint_test.c b/test/link/coff_archive_fixpoint_test.c @@ -0,0 +1,192 @@ +/* COFF archive global fixed-point regression. + * + * Rust/MinGW links can create demand for an earlier library from a later + * library. ELF/Mach-O preserve positional archive semantics, but COFF linkers + * search the library set as a fixed point. This test builds: + * + * input.o -> undef late + * libearly.a -> defines early + * liblate.a -> defines late, undef early + * + * Positional scanning leaves `early` unpulled; COFF global fixed point pulls + * liblate.a first, then revisits libearly.a. */ + +#include <kit/archive.h> +#include <kit/core.h> +#include <string.h> + +#include "core/core.h" +#include "core/pool.h" +#include "lib/kit_unit.h" +#include "link/link.h" +#include "link/link_internal.h" +#include "obj/obj.h" + +static KitUnit g_u; +#define EXPECT(cond, ...) CU_EXPECT(&g_u, cond, __VA_ARGS__) + +static Sym intern(Compiler* c, const char* s) { + return pool_intern_slice(c->global, (Slice){.s = s, .len = (u32)strlen(s)}); +} + +static ObjBuilder* make_obj(Compiler* c, Sym def, Sym undef) { + ObjBuilder* ob = obj_new(c); + ObjSecId data; + ObjSymId usym; + static const u8 byte = 1; + if (!ob) return NULL; + if (def != 0) { + data = obj_section(ob, intern(c, ".data"), SEC_DATA, SF_ALLOC | SF_WRITE, + 1); + obj_write(ob, data, &byte, sizeof byte); + obj_symbol(ob, def, SB_GLOBAL, SK_OBJ, data, 0, sizeof byte); + } + if (undef != 0) { + usym = obj_symbol_ex(ob, undef, SB_GLOBAL, SV_DEFAULT, SK_UNDEF, + OBJ_SEC_NONE, 0, 0, 0); + obj_sym_mark_referenced(ob, usym); + } + obj_finalize(ob); + return ob; +} + +static u8* copy_writer_bytes(KitWriter* w, size_t* len_out) { + size_t n = 0; + const u8* src = kit_writer_mem_bytes(w, &n); + u8* dst = (u8*)g_u.heap.alloc(&g_u.heap, n ? n : 1u, _Alignof(u8)); + if (dst && n) memcpy(dst, src, n); + if (len_out) *len_out = n; + return dst; +} + +static u8* emit_member(Compiler* c, Sym def, Sym undef, size_t* len_out) { + ObjBuilder* ob = make_obj(c, def, undef); + KitWriter* w = NULL; + u8* bytes = NULL; + if (!ob) return NULL; + if (kit_writer_mem(&g_u.heap, &w) == KIT_OK && w) { + emit_coff(c, ob, w); + bytes = copy_writer_bytes(w, len_out); + kit_writer_close(w); + } + obj_free(ob); + return bytes; +} + +static u8* make_archive_bytes(const char* member_name, const u8* member, + size_t member_len, size_t* len_out) { + KitArInput in; + KitArWriteOptions opts; + KitWriter* w = NULL; + u8* bytes = NULL; + memset(&in, 0, sizeof in); + memset(&opts, 0, sizeof opts); + in.name = kit_slice_cstr(member_name); + in.bytes.data = member; + in.bytes.len = member_len; + if (kit_writer_mem(&g_u.heap, &w) != KIT_OK || !w) return NULL; + if (kit_ar_write(w, &in, 1, &opts) == KIT_OK) + bytes = copy_writer_bytes(w, len_out); + kit_writer_close(w); + return bytes; +} + +static int input_has_defined(Linker* l, Sym name) { + u32 i; + for (i = 0; i < LinkInputs_count(&l->inputs); ++i) { + LinkInput* in = LinkInputs_at(&l->inputs, i); + ObjSymIter* it; + ObjSymEntry e; + if (!in->obj) continue; + it = obj_symiter_new(in->obj); + while (obj_symiter_next(it, &e)) { + const ObjSym* s = e.sym; + if (s->name == name && s->kind != SK_UNDEF && + (s->bind == SB_GLOBAL || s->bind == SB_WEAK)) { + obj_symiter_free(it); + return 1; + } + } + obj_symiter_free(it); + } + return 0; +} + +int main(void) { + KitCompiler* kc = NULL; + Compiler* c; + Linker* l = NULL; + ObjBuilder* seed = NULL; + Sym early, late; + u8 *early_member = NULL, *late_member = NULL; + u8 *early_archive = NULL, *late_archive = NULL; + size_t early_member_len = 0, late_member_len = 0; + size_t early_archive_len = 0, late_archive_len = 0; + int status; + + kit_unit_init(&g_u); + { + KitTargetSpec t = + kit_unit_target(KIT_ARCH_X86_64, KIT_OS_WINDOWS, KIT_OBJ_COFF); + if (kit_unit_compiler_new(&g_u, t, &kc) != KIT_OK || !kc) { + fprintf(stderr, "compiler_new failed\n"); + return 2; + } + } + c = (Compiler*)kc; + early = intern(c, "early"); + late = intern(c, "late"); + + seed = make_obj(c, 0, late); + early_member = emit_member(c, early, 0, &early_member_len); + late_member = emit_member(c, late, early, &late_member_len); + EXPECT(seed != NULL, "seed object allocation failed"); + EXPECT(early_member != NULL && early_member_len > 0, + "early member emit failed"); + EXPECT(late_member != NULL && late_member_len > 0, "late member emit failed"); + + if (early_member && late_member) { + early_archive = make_archive_bytes("early.o", early_member, + early_member_len, &early_archive_len); + late_archive = + make_archive_bytes("late.o", late_member, late_member_len, + &late_archive_len); + } + EXPECT(early_archive != NULL && early_archive_len > 0, + "early archive build failed"); + EXPECT(late_archive != NULL && late_archive_len > 0, + "late archive build failed"); + + l = link_new(c); + EXPECT(l != NULL, "link_new failed"); + if (l && seed && early_archive && late_archive) { + link_add_obj(l, seed); + link_add_archive_bytes(l, "libearly.a", early_archive, early_archive_len, + 0, 0, 0); + link_add_archive_bytes(l, "liblate.a", late_archive, late_archive_len, 0, + 0, 0); + link_ingest_archives(l); + EXPECT(input_has_defined(l, late), "late archive member was not pulled"); + EXPECT(input_has_defined(l, early), + "COFF global archive fixed point did not revisit libearly.a"); + } + + if (l) link_free(l); + if (seed) obj_free(seed); + if (early_member) + g_u.heap.free(&g_u.heap, early_member, early_member_len ? early_member_len + : 1u); + if (late_member) + g_u.heap.free(&g_u.heap, late_member, late_member_len ? late_member_len + : 1u); + if (early_archive) + g_u.heap.free(&g_u.heap, early_archive, + early_archive_len ? early_archive_len : 1u); + if (late_archive) + g_u.heap.free(&g_u.heap, late_archive, + late_archive_len ? late_archive_len : 1u); + + kit_unit_summary(&g_u, "coff_archive_fixpoint_test"); + status = kit_unit_status(&g_u); + return status; +} diff --git a/test/link/elf_version_import_test.c b/test/link/elf_version_import_test.c @@ -0,0 +1,135 @@ +/* ELF explicit-version import resolution. + * + * FreeBSD Rust std carries undefined references such as fstat@FBSD_1.0 while + * libc.so.7 exports both fstat@FBSD_1.0 and fstat@@FBSD_1.5. The linker must + * resolve the undefined against the exact DSO version, rewrite the runtime + * symbol name to the base name, and keep the requested version for + * .gnu.version_r emission. + * + * This unit stays at resolver depth: it builds one object with an undefined + * fstat@FBSD_1.0 and one synthetic DSO input whose dynsym has base-name fstat + * tagged as hidden FBSD_1.0, then runs link_resolve_symbols/undefs directly. */ + +#include <kit/core.h> +#include <string.h> + +#include "core/core.h" +#include "core/heap.h" +#include "core/pool.h" +#include "lib/kit_unit.h" +#include "link/link.h" +#include "link/link_internal.h" +#include "obj/obj.h" + +static KitUnit g_u; +#define EXPECT(cond, ...) CU_EXPECT(&g_u, cond, __VA_ARGS__) + +static Sym intern(Compiler* c, const char* s) { + return pool_intern_slice(c->global, (Slice){.s = s, .len = (u32)strlen(s)}); +} + +static ObjBuilder* make_ref(Compiler* c, Sym name) { + ObjBuilder* ob = obj_new(c); + ObjSymId id; + if (!ob) return NULL; + id = obj_symbol_ex(ob, name, SB_GLOBAL, SV_DEFAULT, SK_UNDEF, OBJ_SEC_NONE, 0, + 0, 0); + obj_sym_mark_referenced(ob, id); + obj_finalize(ob); + return ob; +} + +static ObjBuilder* make_dso(Compiler* c, Sym soname, Sym name, Sym version) { + ObjBuilder* ob = obj_new(c); + ObjImage* im; + ObjImageSym ds; + if (!ob) return NULL; + im = obj_image_ensure(ob, OBJ_KIND_DYN); + obj_image_set_soname(im, soname); + memset(&ds, 0, sizeof(ds)); + ds.name = name; + ds.bind = SB_GLOBAL; + ds.kind = SK_FUNC; + ds.section = OBJ_SEC_NONE; + ds.version = version; + ds.version_hidden = 1; + obj_image_add_dynsym(im, &ds); + obj_finalize(ob); + return ob; +} + +static LinkImage* make_resolve_image(Compiler* c, Linker* l) { + LinkImage* img = link_image_alloc(c); + Heap* h = img->heap; + img->linker = l; + img->ninput_maps = LinkInputs_count(&l->inputs); + if (img->ninput_maps) { + img->input_maps = (InputMap*)h->alloc( + h, sizeof(*img->input_maps) * img->ninput_maps, _Alignof(InputMap)); + EXPECT(img->input_maps != NULL, "input map allocation failed"); + if (img->input_maps) + memset(img->input_maps, 0, sizeof(*img->input_maps) * img->ninput_maps); + } + return img; +} + +int main(void) { + KitCompiler* kc = NULL; + Linker* l = NULL; + LinkImage* img = NULL; + ObjBuilder* ref = NULL; + ObjBuilder* dso = NULL; + LinkInputId dso_id; + Sym base, full, version, soname; + int found = 0; + + kit_unit_init(&g_u); + { + KitTargetSpec t = + kit_unit_target(KIT_ARCH_X86_64, KIT_OS_FREEBSD, KIT_OBJ_ELF); + if (kit_unit_compiler_new(&g_u, t, &kc) != KIT_OK || !kc) { + fprintf(stderr, "compiler_new failed\n"); + return 2; + } + } + Compiler* c = kc; + + base = intern(c, "fstat"); + full = intern(c, "fstat@FBSD_1.0"); + version = intern(c, "FBSD_1.0"); + soname = intern(c, "libc.so.7"); + ref = make_ref(c, full); + dso = make_dso(c, soname, base, version); + EXPECT(ref != NULL, "reference object allocation failed"); + EXPECT(dso != NULL, "DSO object allocation failed"); + + l = link_new(c); + EXPECT(l != NULL, "link_new failed"); + if (l && ref && dso) { + link_add_obj(l, ref); + dso_id = link_add_obj(l, dso); + LinkInput* din = LinkInputs_at(&l->inputs, dso_id - 1u); + din->kind = LINK_INPUT_DSO_BYTES; + din->soname = soname; + + img = make_resolve_image(c, l); + link_resolve_symbols(l, img); + link_resolve_undefs(l, img); + + for (u32 i = 0; i < LinkSyms_count(&img->syms); ++i) { + LinkSymbol* s = LinkSyms_at(&img->syms, i); + if (!s->imported || s->name != base) continue; + found = 1; + EXPECT(s->dso_input_id == dso_id, "versioned import bound wrong DSO"); + EXPECT(s->elf_version == version, "explicit ELF version was not kept"); + EXPECT(s->kind == SK_FUNC, "DSO export kind was not copied"); + } + EXPECT(found, "fstat@FBSD_1.0 did not resolve as imported fstat"); + } + + if (img) link_image_free(img); + if (l) link_free(l); + if (ref) obj_free(ref); + kit_unit_summary(&g_u, "elf_version_import_test"); + return kit_unit_status(&g_u); +} diff --git a/test/link/reloc_apply_test.c b/test/link/reloc_apply_test.c @@ -67,6 +67,16 @@ static const ApplyCase kCases[] = { 0x90000000u, 4, 0x100000, 0, 0x1000, 0xf00007e0u}, {"aa64 ADR_GOT_PAGE", KIT_ARCH_ARM_64, R_AARCH64_ADR_GOT_PAGE, 0x90000000u, 4, 0x100000, 0, 0x1000, 0xf00007e0u}, + {"aa64 POINTER_TO_GOT", KIT_ARCH_ARM_64, R_AARCH64_POINTER_TO_GOT, 0, 4, + 0x2000, 4, 0x1000, 0x1004u}, + {"aa64 TLSDESC_ADR_PAGE21", KIT_ARCH_ARM_64, R_AARCH64_TLSDESC_ADR_PAGE21, + 0x90000000u, 4, 0x123456789abcdef0ull, 0, 0, 0xd29bde00u}, + {"aa64 TLSDESC_LD64_LO12", KIT_ARCH_ARM_64, R_AARCH64_TLSDESC_LD64_LO12, + 0xf9400004u, 4, 0x123456789abcdef0ull, 0, 0, 0xf2b35780u}, + {"aa64 TLSDESC_ADD_LO12", KIT_ARCH_ARM_64, R_AARCH64_TLSDESC_ADD_LO12, + 0x91000000u, 4, 0x123456789abcdef0ull, 0, 0, 0xf2cacf00u}, + {"aa64 TLSDESC_CALL", KIT_ARCH_ARM_64, R_AARCH64_TLSDESC_CALL, 0xd63f0080u, + 4, 0x123456789abcdef0ull, 0, 0, 0xf2e24680u}, {"aa64 TLSIE_ADR_GOTTPREL_PAGE21", KIT_ARCH_ARM_64, R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21, 0x90000000u, 4, 0x100000, 0, 0x1000, 0xf00007e0u}, @@ -112,6 +122,8 @@ static const ApplyCase kCases[] = { {"x64 PC32", KIT_ARCH_X86_64, R_PC32, 0x0u, 4, 0x2000, -4, 0x1000, 0xffcu}, {"x64 TPOFF32", KIT_ARCH_X86_64, R_X64_TPOFF32, 0x0u, 4, 0x12345678u, 0, 0, 0x12345678u}, + {"x64 DTPOFF32", KIT_ARCH_X86_64, R_X64_DTPOFF32, 0x0u, 4, 0x87654321u, 0, + 0, 0x87654321u}, {"x64 GLOB_DAT", KIT_ARCH_X86_64, R_X64_GLOB_DAT, 0x0u, 8, 0xcafebabeu, 0, 0, 0xcafebabeu}, @@ -176,6 +188,51 @@ static KitCompiler* compiler_for(KitArchKind arch) { return *slot; } +static void check_x64_tlsld_relax(void) { + u8 got[12] = {0x48u, 0x8du, 0x3du, 0x00u, 0x00u, 0x00u, + 0x00u, 0xe8u, 0x00u, 0x00u, 0x00u, 0x00u}; + static const u8 want[12] = {0x64u, 0x48u, 0x8bu, 0x04u, 0x25u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x90u, 0x90u, 0x90u}; + link_reloc_apply(compiler_for(KIT_ARCH_X86_64), R_X64_TLSLD, got + 3, 0, -4, + 0); + EXPECT(memcmp(got, want, sizeof got) == 0, "x64 TLSLD relaxation mismatch"); +} + +static void check_rv_tlsgd_relax(void) { + u8 got[16] = {0x17u, 0x05u, 0x00u, 0x00u, 0x13u, 0x05u, 0x05u, 0x00u, + 0x97u, 0x00u, 0x00u, 0x00u, 0xe7u, 0x80u, 0x00u, 0x00u}; + static const u8 want[16] = { + 0x37u, 0x25u, 0x01u, 0x00u, 0x13u, 0x05u, 0x55u, 0x34u, + 0x33u, 0x05u, 0x45u, 0x00u, 0x13u, 0x00u, 0x00u, 0x00u}; + link_reloc_apply(compiler_for(KIT_ARCH_RV64), R_RV_TLS_GD_HI20, got, + 0x12345, 0, 0); + EXPECT(memcmp(got, want, sizeof got) == 0, + "RISC-V TLS_GD relaxation mismatch"); +} + +static void check_rv_tlsgd_relax_compressed_gap(void) { + u8 got[20] = { + 0x97u, 0x05u, 0x00u, 0x00u, /* auipc a1, %tlsgd_hi */ + 0x93u, 0x85u, 0x05u, 0x00u, /* mv a1, a1 */ + 0x2au, 0x89u, /* c.mv s2, a0 */ + 0x2eu, 0x85u, /* c.mv a0, a1 */ + 0x97u, 0x00u, 0x00u, 0x00u, /* auipc ra, __tls_get_addr */ + 0xe7u, 0x80u, 0x00u, 0x00u /* jalr ra */ + }; + static const u8 want[20] = { + 0xb7u, 0x25u, 0x01u, 0x00u, /* lui a1, %tprel_hi */ + 0x93u, 0x85u, 0x55u, 0x34u, /* addi a1, a1, %tprel_lo */ + 0x2au, 0x89u, /* c.mv s2, a0 */ + 0x2eu, 0x85u, /* c.mv a0, a1 */ + 0x33u, 0x05u, 0x45u, 0x00u, /* add a0, a0, tp */ + 0x13u, 0x00u, 0x00u, 0x00u /* nop */ + }; + link_reloc_apply(compiler_for(KIT_ARCH_RV64), R_RV_TLS_GD_HI20, got, + 0x12345, 0, 0); + EXPECT(memcmp(got, want, sizeof got) == 0, + "RISC-V TLS_GD compressed-gap relaxation mismatch"); +} + int main(void) { size_t i; int capture = getenv("KIT_RELOC_APPLY_CAPTURE") != NULL; @@ -201,6 +258,10 @@ int main(void) { (unsigned long long)got, (unsigned long long)tc->want); } + check_x64_tlsld_relax(); + check_rv_tlsgd_relax(); + check_rv_tlsgd_relax_compressed_gap(); + if (capture) return 0; kit_unit_summary(&g_u, "reloc_apply_test"); return kit_unit_status(&g_u); diff --git a/test/link/reloc_desc_test.c b/test/link/reloc_desc_test.c @@ -47,6 +47,11 @@ static u8 oracle_width(RelocKind k) { case R_X64_PLT32: case R_X64_32S: case R_X64_TPOFF32: + case R_X64_DTPOFF32: + /* R_X64_TLSLD: post-freeze addition for ELF local-dynamic TLS relaxation. + * The relocation points at the disp32 in `leaq x@TLSLD(%rip), %rdi`; the + * x64 apply hook owns the surrounding 12-byte block rewrite. */ + case R_X64_TLSLD: case R_X64_GOTPCREL: case R_X64_GOTPCRELX: case R_X64_REX_GOTPCRELX: @@ -86,6 +91,11 @@ static u8 oracle_width(RelocKind k) { case R_AARCH64_LDST128_ABS_LO12_NC: case R_AARCH64_ADR_GOT_PAGE: case R_AARCH64_LD64_GOT_LO12_NC: + case R_AARCH64_POINTER_TO_GOT: + case R_AARCH64_TLSDESC_ADR_PAGE21: + case R_AARCH64_TLSDESC_LD64_LO12: + case R_AARCH64_TLSDESC_ADD_LO12: + case R_AARCH64_TLSDESC_CALL: case R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21: case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC: case R_AARCH64_TLSLE_ADD_TPREL_HI12: @@ -103,6 +113,7 @@ static u8 oracle_width(RelocKind k) { case R_RV_PCREL_LO12_S: case R_RV_GOT_HI20: case R_RV_TLS_GOT_HI20: + case R_RV_TLS_GD_HI20: case R_RV_TPREL_HI20: case R_RV_TPREL_LO12_I: case R_RV_TPREL_LO12_S: @@ -146,6 +157,7 @@ static u8 oracle_width(RelocKind k) { return 2; case R_COFF_AARCH64_SECREL_LOW12A: case R_COFF_AARCH64_SECREL_HIGH12A: + case R_COFF_AARCH64_SECREL_LOW12L: return 4; default: return 0; @@ -162,7 +174,8 @@ static u8 oracle_width(RelocKind k) { /* RELOC_USES_GOT set (direct GOT load): the Mach-O is_got_load hook for * aa64/x64, and the GOT-allocating subset of reloc_uses_got for rv. */ static int oracle_aa64_got_use(RelocKind k) { - return k == R_AARCH64_ADR_GOT_PAGE || k == R_AARCH64_LD64_GOT_LO12_NC; + return k == R_AARCH64_ADR_GOT_PAGE || k == R_AARCH64_LD64_GOT_LO12_NC || + k == R_AARCH64_POINTER_TO_GOT; } static int oracle_x64_got_use(RelocKind k) { return k == R_X64_GOTPCREL || k == R_X64_GOTPCRELX || @@ -239,7 +252,7 @@ static const ArchOracle kArchOracles[] = { }; /* Last RelocKind enum value; the enum is contiguous from R_NONE = 0. */ -#define RELOC_KIND_LAST R_COFF_ADDR32NB +#define RELOC_KIND_LAST R_COFF_AARCH64_SECREL_LOW12L static KitCompiler* new_compiler(KitArchKind arch) { KitTargetSpec t = kit_unit_target(arch, KIT_OS_LINUX, KIT_OBJ_ELF);