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:
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);