commit e073a97068ef9d63659cb52cb182be91f3b46c51
parent 73252b15fee3804dec7b25c929f612252cf0a678
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Wed, 17 Jun 2026 08:45:18 -0700
Enable all-kit ELF shared library creation
Diffstat:
14 files changed, 257 insertions(+), 175 deletions(-)
diff --git a/driver/cmd/build.c b/driver/cmd/build.c
@@ -2277,12 +2277,6 @@ static int build_validate(BuildOptions* o) {
}
if (o->kind == BUILD_OUT_LIB) {
- if (o->dynamic) {
- driver_errf(o->tool,
- "creating dynamic/shared libraries is not yet supported; "
- "build-lib produces a static .a");
- return 1;
- }
if (o->emit != BUILD_EMIT_OBJ || o->syntax_only) {
driver_errf(o->tool, "--emit/-S/-fsyntax-only are build-obj options");
return 1;
@@ -2416,7 +2410,11 @@ static int build_main(int argc, char** argv, int kind, const char* tool,
if (build_apply_env(&o) != 0) goto done;
o.shared = (o.kind == BUILD_OUT_LIB && o.dynamic);
- if (o.shared && o.target.pic == KIT_PIC_NONE) o.target.pic = KIT_PIC_PIC;
+ if (o.shared && !o.pic_explicit) o.target.pic = KIT_PIC_PIC;
+ if (o.shared && o.target.pic == KIT_PIC_NONE) {
+ driver_errf(tool, "-shared requires PIC input; remove -fno-pic/-static");
+ goto done;
+ }
if (build_validate(&o) != 0) goto done;
@@ -2464,7 +2462,7 @@ static int build_main(int argc, char** argv, int kind, const char* tool,
rc = 1;
goto done;
}
- if (!o.no_stdlib && !o.no_defaultlibs) {
+ if (!o.shared && !o.no_stdlib && !o.no_defaultlibs) {
DriverRuntimeArchive rt = {0};
if (driver_runtime_prepare_archive(&env, tool, &runtime, o.target,
o.epoch, &rt) != 0) {
diff --git a/driver/cmd/cc.c b/driver/cmd/cc.c
@@ -148,6 +148,7 @@ typedef struct CcOptions {
int shared; /* -shared */
int static_link;
int pie;
+ int pic_explicit;
int no_stdlib;
int no_defaultlibs;
int no_startfiles;
@@ -883,31 +884,37 @@ static int cc_parse(int argc, char** argv, CcOptions* o) {
if (driver_streq(a, "-fPIC") || driver_streq(a, "-fpic")) {
o->target.pic = KIT_PIC_PIC;
+ o->pic_explicit = 1;
continue;
}
if (driver_streq(a, "-fPIE") || driver_streq(a, "-fpie")) {
o->target.pic = KIT_PIC_PIE;
+ o->pic_explicit = 1;
continue;
}
if (driver_streq(a, "-fno-PIC") || driver_streq(a, "-fno-pic") ||
driver_streq(a, "-fno-PIE") || driver_streq(a, "-fno-pie")) {
o->target.pic = KIT_PIC_NONE;
+ o->pic_explicit = 1;
continue;
}
if (driver_streq(a, "-static")) {
o->target.pic = KIT_PIC_NONE;
o->static_link = 1;
+ o->pic_explicit = 1;
o->inputs.cur_link_mode = KIT_LM_STATIC;
continue;
}
if (driver_streq(a, "-pie")) {
o->target.pic = KIT_PIC_PIE;
o->pie = 1;
+ o->pic_explicit = 1;
continue;
}
if (driver_streq(a, "-no-pie")) {
o->target.pic = KIT_PIC_NONE;
o->pie = 0;
+ o->pic_explicit = 1;
continue;
}
if (driver_streq(a, "-rdynamic") || driver_streq(a, "-export-dynamic")) {
@@ -968,7 +975,6 @@ static int cc_parse(int argc, char** argv, CcOptions* o) {
if (driver_streq(a, "-shared")) {
o->shared = 1;
- if (o->target.pic == KIT_PIC_NONE) o->target.pic = KIT_PIC_PIC;
continue;
}
if (driver_streq(a, "-mwindows")) {
@@ -1258,6 +1264,7 @@ static int cc_parse(int argc, char** argv, CcOptions* o) {
if (driver_link_inputs_append_sysroot_framework_dirs(&o->inputs, &o->sysroot,
o->target) != 0)
return 1;
+ if (o->shared && !o->pic_explicit) o->target.pic = KIT_PIC_PIC;
{
uint32_t total_sources = o->nsource_files + o->nsource_memory;
@@ -1290,9 +1297,8 @@ static int cc_parse(int argc, char** argv, CcOptions* o) {
"(shared-library LTO output is not exercised)");
return 1;
}
- if (o->shared) {
- driver_errf(CC_TOOL,
- "creating dynamic/shared libraries is not yet supported");
+ if (o->shared && o->target.pic == KIT_PIC_NONE) {
+ driver_errf(CC_TOOL, "-shared requires PIC input; remove -fno-pic/-static");
return 1;
}
if (o->emit_ir && o->opt_level < 1) {
@@ -2599,7 +2605,7 @@ static int driver_cc_main(int argc, char** argv, int force_check) {
return 1;
}
- if (link_action && !co.no_stdlib && !co.no_defaultlibs) {
+ if (link_action && !co.shared && !co.no_stdlib && !co.no_defaultlibs) {
DriverRuntimeArchive rt_archive = {0};
if (!runtime_resolved) {
driver_errf(CC_TOOL, "support dir not found");
diff --git a/driver/cmd/ld.c b/driver/cmd/ld.c
@@ -2671,11 +2671,6 @@ static int ld_parse(int argc, char** argv, LdOptions* o) {
"-nostartfiles");
return 1;
}
- if (o->shared) {
- driver_errf(LD_TOOL,
- "creating dynamic/shared libraries is not yet supported");
- return 1;
- }
if (o->relocatable) {
if (o->shared) {
driver_errf(LD_TOOL, "-r and -shared are incompatible");
diff --git a/src/api/link.c b/src/api/link.c
@@ -188,13 +188,9 @@ KitStatus kit_link_session_new(KitCompiler* c,
case KIT_LINK_OUTPUT_SHARED:
link_set_gc_sections(l, opts->gc_sections);
link_set_strip_debug(l, opts->strip_debug);
- link_set_allow_undefined(l, opts->allow_undefined);
- link_set_pie(l, 1);
- if (opts->soname.s && opts->soname.len)
- link_warn_ignored_opt(s->c, "-soname");
- if (opts->rpaths && opts->nrpaths) link_warn_ignored_opt(s->c, "-rpath");
- if (opts->runpaths && opts->nrunpaths)
- link_warn_ignored_opt(s->c, "-rpath (DT_RUNPATH)");
+ link_set_allow_undefined(l, 0);
+ link_set_shared(l, 1);
+ link_set_pie(l, 0);
if (opts->exports && opts->nexports)
link_warn_ignored_opt(s->c, "--export-symbol");
break;
@@ -226,6 +222,11 @@ KitStatus kit_link_session_new(KitCompiler* c,
l->ndefsyms = opts->ndefsyms;
l->section_starts = opts->section_starts;
l->nsection_starts = opts->nsection_starts;
+ l->soname = opts->soname;
+ l->rpaths = opts->rpaths;
+ l->nrpaths = opts->nrpaths;
+ l->runpaths = opts->runpaths;
+ l->nrunpaths = opts->nrunpaths;
l->orphan_handling = opts->orphan_handling;
l->fatal_warnings = opts->fatal_warnings ? 1 : 0;
l->freestanding_strict = opts->freestanding_strict ? 1 : 0;
diff --git a/src/link/link.c b/src/link/link.c
@@ -368,6 +368,11 @@ void link_set_allow_undefined(Linker* l, int enable) {
l->allow_undefined = enable ? 1 : 0;
}
+void link_set_shared(Linker* l, int enable) {
+ if (!l) return;
+ l->emit_shared = enable ? 1 : 0;
+}
+
void link_set_emit_static_exe(Linker* l, int enable) {
if (!l) return;
l->emit_static_exe = enable ? 1 : 0;
diff --git a/src/link/link.h b/src/link/link.h
@@ -99,7 +99,8 @@ typedef struct LinkSymbol {
u8 needs_plt;
u8 needs_got;
u8 needs_copy;
- u8 pad[5];
+ u8 vis;
+ u8 pad[4];
} LinkSymbol;
typedef struct LinkSegment {
@@ -205,6 +206,7 @@ void link_set_extern_resolver(Linker*, LinkExternResolver, void* user);
void link_set_gc_sections(Linker*, int enable);
void link_set_strip_debug(Linker*, int enable);
void link_set_allow_undefined(Linker*, int enable);
+void link_set_shared(Linker*, int enable);
/* Mark this link as targeting a static ET_EXEC ELF binary (vs. the
* in-process JIT). Setter is called by kit_link_exe; the JIT path
diff --git a/src/link/link_internal.h b/src/link/link_internal.h
@@ -215,6 +215,9 @@ struct Linker {
* `__tlv_bootstrap` as a plain non-weak undef) would panic at
* link_resolve_undefs. */
int jit_mode;
+ /* Shared-library / DSO output. Shares dynamic ELF layout with PIE
+ * executables, but has no required entry point and no PT_INTERP. */
+ int emit_shared;
/* PIE / ET_DYN output. Set by kit_link_exe when opts->pie or any
* DSO input is present. Triggers layout_dyn (Phase 4) and the
* dynamic ELF emit path (Phase 6). */
@@ -230,6 +233,11 @@ struct Linker {
u32 ndefsyms;
const KitLinkSectionStart* section_starts; /* --section-start / -Tdata/-Tbss */
u32 nsection_starts;
+ KitSlice soname;
+ const KitSlice* rpaths;
+ u32 nrpaths;
+ const KitSlice* runpaths;
+ u32 nrunpaths;
u8 orphan_handling; /* KitLinkOrphanHandling */
int fatal_warnings; /* --fatal-warnings */
int freestanding_strict; /* strict freestanding link validation */
@@ -487,6 +495,8 @@ struct LinkImage {
LinkDynState* dyn;
/* Mirror of Linker.emit_pie at link_resolve time; consulted by emit. */
int pie;
+ /* Mirror of Linker.emit_shared at link_resolve time; consulted by emit. */
+ int shared;
/* Set when layout was driven by Linker.script. The emitter then keeps
* segment vaddrs at their script-assigned absolute values, drops the
* self-describing headers PT_LOAD / build-id PT_NOTE, and only shifts
diff --git a/src/link/link_layout.c b/src/link/link_layout.c
@@ -2224,6 +2224,7 @@ LinkImage* link_resolve(Linker* l) {
img->linker = l;
img->text_base_set = l->text_base_set;
img->text_base = l->text_base;
+ img->shared = l->emit_shared;
img->ninput_maps = LinkInputs_count(&l->inputs);
metrics_count(l->c, "link.inputs", img->ninput_maps);
@@ -2288,7 +2289,8 @@ LinkImage* link_resolve(Linker* l) {
* .dynamic vaddr; for static, the weak undef from crt/libc already
* resolved to SK_ABS 0, and defining it here as a rebased SK_OBJ symbol
* would wrongly make `&_DYNAMIC` non-zero. */
- if (l->emit_pie) link_emit_boundary_sym(l, img, "_DYNAMIC", 0);
+ if (l->emit_pie || l->emit_shared)
+ link_emit_boundary_sym(l, img, "_DYNAMIC", 0);
link_emit_boundary_sym(l, img, "_GLOBAL_OFFSET_TABLE_", 0);
/* PE/COFF: mingw CRT references `__ImageBase` for ASLR-relative
* addressing and base-relocation bookkeeping. The PE emitter
diff --git a/src/link/link_reloc_layout.c b/src/link/link_reloc_layout.c
@@ -894,6 +894,10 @@ void link_layout_iplt(Linker* l, LinkImage* img) {
void link_resolve_entry(Linker* l, LinkImage* img) {
LinkSymId id;
LinkSymbol* s;
+ if (l->emit_shared) {
+ img->entry_sym = LINK_SYM_NONE;
+ return;
+ }
if (l->entry_name == 0) return;
id = symhash_get(&img->globals, l->entry_name);
if (id == LINK_SYM_NONE) {
diff --git a/src/link/link_resolve.c b/src/link/link_resolve.c
@@ -274,6 +274,7 @@ void link_resolve_symbols(Linker* l, LinkImage* img) {
rec.common_align = (s->kind == SK_COMMON) ? (u32)s->common_align : 0u;
rec.bind = (u8)s->bind;
rec.kind = (u8)s->kind;
+ rec.vis = (u8)s->vis;
rec.defined = (u8)is_def;
rec.vaddr = 0;
@@ -562,6 +563,7 @@ void link_resolve_undefs(Linker* l, LinkImage* img) {
s->vaddr = def->vaddr;
s->kind = def->kind;
s->bind = def->bind;
+ s->vis = def->vis;
s->defined = 1;
continue;
}
@@ -674,6 +676,12 @@ void link_resolve_undefs(Linker* l, LinkImage* img) {
s->defined = 1;
continue;
}
+ if (l->emit_shared && s->name != 0 && s->bind != SB_LOCAL) {
+ s->imported = 1;
+ s->dso_input_id = LINK_INPUT_NONE;
+ s->elf_version = 0;
+ continue;
+ }
/* --defsym NAME=EXPR: a defsym whose NAME matches this undef satisfies the
* reference. Defsyms are applied AFTER undef resolution (link_apply_defsyms
* runs in link_resolve, once the alias target's vaddr is settled), so we
diff --git a/src/obj/elf/link.c b/src/obj/elf/link.c
@@ -190,6 +190,24 @@ static u32 perms_to_pflags(u32 secflags) {
return f;
}
+static u32 dynstr_find_sym(Compiler* c, const LinkDynState* dyn, Sym name,
+ const char* tag) {
+ Slice nm_s = pool_slice(c->global, name);
+ const char* nm = nm_s.s;
+ size_t namelen = nm_s.len;
+ if (nm && namelen) {
+ u32 si;
+ for (si = 0; si + namelen < dyn->dynstr_len; ++si) {
+ if (dyn->dynstr[si + namelen] == 0 &&
+ memcmp(dyn->dynstr + si, nm, namelen) == 0)
+ return si;
+ }
+ }
+ compiler_panic(c, SRCLOC_NONE, "link_emit_elf: %s missing from .dynstr",
+ tag ? tag : "dynamic string");
+ return 0;
+}
+
/* ---- class-aware header serializers ----
*
* The writer builds every header in the wide Ehdr64/Phdr64/Shdr64
@@ -602,7 +620,7 @@ static i64 rv_pcrel_lo12_disp(LinkImage* img, u64 auipc_vaddr, u64 img_base) {
static void apply_all_relocs(LinkImage* img, u64 img_base) {
u32 i;
- int pie = img->pie;
+ int dyn_output = img->pie || img->shared;
int tls_vi = (int)link_arch_desc_for(img->c)->tls_variant_ii;
for (i = 0; i < LinkRelocs_count(&img->relocs); ++i) {
LinkRelocApply* r = LinkRelocs_at(&img->relocs, i);
@@ -766,7 +784,8 @@ static void apply_all_relocs(LinkImage* img, u64 img_base) {
* S above is already image-relative — the apply writes that into
* the site, and the RELATIVE record tells the loader to add
* load_base on top. */
- if (pie && reloc_is_abs(r->kind) && tgt->defined && tgt->kind != SK_ABS) {
+ if (dyn_output && reloc_is_abs(r->kind) && tgt->defined &&
+ tgt->kind != SK_ABS) {
/* RELA RELATIVE ignores the in-place site value: the loader writes
* (load_base + r_addend) into the slot. So the addend must be the
* full image-relative target — symbol vaddr plus the reloc's own
@@ -994,7 +1013,7 @@ void link_emit_elf(LinkImage* img, Writer* w) {
if (class32 && (img->pie || img->dyn))
compiler_panic(c, SRCLOC_NONE,
"rv32: dynamic/PIE linking unsupported; static only");
- if (img->entry_sym == LINK_SYM_NONE)
+ if (!img->shared && img->entry_sym == LINK_SYM_NONE)
compiler_panic(c, SRCLOC_NONE, "link_emit_elf: no resolved entry symbol");
/* IFUNC trampolines: layout_iplt builds the .iplt stubs + .igot.plt
* slots and (when emit_static_exe was set) synthesizes a
@@ -1013,11 +1032,12 @@ void link_emit_elf(LinkImage* img, Writer* w) {
* raw loader (qemu -kernel, a bootloader) that doesn't need a
* self-describing memory image. */
int pie = img->pie;
+ int shared = img->shared;
int scripted = img->scripted;
/* Static ET_EXEC base: a `kit ld -Ttext ADDR` override (e.g. 0x80000000 for a
* qemu `virt` image) wins over IMAGE_BASE_STATIC; PIE/scripted keep base 0.
*/
- u64 img_base = (pie || scripted) ? 0ULL
+ u64 img_base = (pie || shared || scripted) ? 0ULL
: img->text_base_set ? img->text_base
: IMAGE_BASE_STATIC;
@@ -1025,8 +1045,9 @@ void link_emit_elf(LinkImage* img, Writer* w) {
*
* 1 headers PT_LOAD + nsegments PT_LOAD + 1 PT_NOTE (build-id)
* + 1 PT_TLS when this image carries any TLS sections.
- * + 4 dyn phdrs (PT_PHDR / PT_INTERP / PT_DYNAMIC / PT_GNU_STACK) when PIE
- * also has real dynamic-link state.
+ * + dynamic phdrs when PIE/shared also has real dynamic-link state. PIE
+ * executables get PT_PHDR / PT_INTERP / PT_DYNAMIC / PT_GNU_STACK; DSOs
+ * get PT_PHDR / PT_DYNAMIC / PT_GNU_STACK.
*
* Scripted images skip the headers PT_LOAD and PT_NOTE: phdrs are
* just the per-segment PT_LOADs. */
@@ -1041,7 +1062,7 @@ void link_emit_elf(LinkImage* img, Writer* w) {
}
}
}
- u32 nphdr_extra_dyn = (pie && img->dyn) ? 4u : 0u;
+ u32 nphdr_extra_dyn = ((pie || shared) && img->dyn) ? (shared ? 3u : 4u) : 0u;
u32 nphdr_headers = scripted ? 0u : 1u;
u32 nphdr_buildid = scripted ? 0u : 1u;
u32 nphdr_total = nphdr_headers + img->nsegments + nphdr_buildid +
@@ -1091,7 +1112,7 @@ void link_emit_elf(LinkImage* img, Writer* w) {
* (the loader adds load_base at runtime). .rela.dyn picked up
* RELATIVE records during apply_all_relocs; rewrite the section
* bytes to include them. */
- if (pie && img->dyn) {
+ if ((pie || shared) && img->dyn) {
LinkDynState* dyn = img->dyn;
const LinkSection* sec_dynamic = &img->sections[dyn->sec_dynamic - 1];
const LinkSection* sec_dynsym = &img->sections[dyn->sec_dynsym - 1];
@@ -1136,37 +1157,17 @@ void link_emit_elf(LinkImage* img, Writer* w) {
u32 ni;
for (ni = 0; ni < dyn->nneeded; ++ni) {
Sym soname = dyn->needed[ni];
- Slice nm_s = pool_slice(c->global, soname);
- const char* nm = nm_s.s;
- size_t namelen = nm_s.len;
- /* Linear search dynstr for this name. */
- u32 off = 0;
- if (nm && namelen) {
- u32 si;
- for (si = 0; si + namelen < dyn->dynstr_len; ++si) {
- if (dyn->dynstr[si + namelen] == 0 &&
- memcmp(dyn->dynstr + si, nm, namelen) == 0) {
- off = si;
- break;
- }
- }
- /* Should always be present — collect_needed populated dynstr
- * via build_dynsym? Actually build_dynsym only added import
- * names. We need to also add NEEDED sonames. */
- if (off == 0) {
- /* Fallback: append to dynstr. Phase 4 layout_dyn pre-sized
- * .dynstr exactly to its current content; appending here
- * would overflow the section. Instead, panic with a clear
- * message — the soname was supposed to be added during
- * layout. */
- compiler_panic(c, SRCLOC_NONE,
- "link_emit_elf: DT_NEEDED soname missing from "
- ".dynstr");
- }
- }
+ u32 off = dynstr_find_sym(c, dyn, soname, "DT_NEEDED soname");
DT_PUT(DT_NEEDED, off);
}
}
+ if (dyn->soname)
+ DT_PUT(DT_SONAME, dynstr_find_sym(c, dyn, dyn->soname, "DT_SONAME"));
+ for (u32 ri = 0; ri < dyn->nrpaths; ++ri)
+ DT_PUT(DT_RPATH, dynstr_find_sym(c, dyn, dyn->rpaths[ri], "DT_RPATH"));
+ for (u32 ri = 0; ri < dyn->nrunpaths; ++ri)
+ DT_PUT(DT_RUNPATH,
+ dynstr_find_sym(c, dyn, dyn->runpaths[ri], "DT_RUNPATH"));
DT_PUT(DT_STRTAB, img_base + sec_dynstr->vaddr);
DT_PUT(DT_STRSZ, sec_dynstr->size);
@@ -1498,7 +1499,7 @@ void link_emit_elf(LinkImage* img, Writer* w) {
* PT_LOAD. Required by the runtime loader for ET_DYN to know
* where its own program headers live. Must appear before the
* first PT_LOAD on dynamic exes (musl checks). */
- if (pie && img->dyn) {
+ if ((pie || shared) && img->dyn) {
phdrs[pi].p_type = PT_PHDR;
phdrs[pi].p_flags = PF_R;
phdrs[pi].p_offset = sizeof(Ehdr64);
@@ -1591,19 +1592,21 @@ void link_emit_elf(LinkImage* img, Writer* w) {
/* Dynamic phdrs. PT_INTERP and PT_DYNAMIC point at the matching
* sections (which layout_dyn placed in the ro/rw_dyn segments).
* PT_GNU_STACK marks the stack as non-executable (filesz=0). */
- if (pie && img->dyn) {
+ if ((pie || shared) && img->dyn) {
LinkDynState* dyn = img->dyn;
- const LinkSection* sec_interp = &img->sections[dyn->sec_interp - 1];
const LinkSection* sec_dynamic = &img->sections[dyn->sec_dynamic - 1];
- phdrs[pi].p_type = PT_INTERP;
- phdrs[pi].p_flags = PF_R;
- phdrs[pi].p_offset = sec_interp->file_offset;
- phdrs[pi].p_vaddr = img_base + sec_interp->vaddr;
- phdrs[pi].p_paddr = phdrs[pi].p_vaddr;
- phdrs[pi].p_filesz = sec_interp->size;
- phdrs[pi].p_memsz = sec_interp->size;
- phdrs[pi].p_align = 1;
- pi++;
+ if (!shared) {
+ const LinkSection* sec_interp = &img->sections[dyn->sec_interp - 1];
+ phdrs[pi].p_type = PT_INTERP;
+ phdrs[pi].p_flags = PF_R;
+ phdrs[pi].p_offset = sec_interp->file_offset;
+ phdrs[pi].p_vaddr = img_base + sec_interp->vaddr;
+ phdrs[pi].p_paddr = phdrs[pi].p_vaddr;
+ phdrs[pi].p_filesz = sec_interp->size;
+ phdrs[pi].p_memsz = sec_interp->size;
+ phdrs[pi].p_align = 1;
+ pi++;
+ }
phdrs[pi].p_type = PT_DYNAMIC;
phdrs[pi].p_flags = PF_R | PF_W;
phdrs[pi].p_offset = sec_dynamic->file_offset;
@@ -1626,7 +1629,7 @@ void link_emit_elf(LinkImage* img, Writer* w) {
* here. Phase 6 leaves it out — it's an optimization the loader
* can live without, and our ro_seg already lives in a PF_R
* PT_LOAD that's never made writable. */
- } else if (pie) {
+ } else if (pie || shared) {
/* dyn was nominally requested but layout_dyn early-out — no
* imports and no DSO inputs. INTERP/DYNAMIC loader headers are skipped.
*/
@@ -1653,10 +1656,13 @@ void link_emit_elf(LinkImage* img, Writer* w) {
* layout), so we set the OSABI on every arch (FreeBSD/clang only sets it on
* amd64/aarch64, but the riscv64 kernel accepts it too). */
if (img->c->target.os == KIT_OS_FREEBSD) ehdr.e_ident[7] = ELFOSABI_FREEBSD;
- ehdr.e_type = pie ? ET_DYN : ET_EXEC;
+ ehdr.e_type = (pie || shared) ? ET_DYN : ET_EXEC;
ehdr.e_machine = (u16)e_machine;
ehdr.e_version = EV_CURRENT;
- ehdr.e_entry = img_base + LinkSyms_at(&img->syms, img->entry_sym - 1)->vaddr;
+ ehdr.e_entry = img->entry_sym == LINK_SYM_NONE
+ ? 0
+ : img_base +
+ LinkSyms_at(&img->syms, img->entry_sym - 1)->vaddr;
ehdr.e_phoff = ehdr_sz;
ehdr.e_shoff = shdr_off;
/* e_flags carries the arch ABI bits (RISC-V float-ABI / RVC). This was
@@ -1801,7 +1807,7 @@ void link_emit_elf(LinkImage* img, Writer* w) {
Sym n_dynsym = 0, n_dynstr = 0, n_gnuhash = 0;
Sym n_reladyn = 0, n_relaplt = 0, n_dynamic = 0;
Sym n_gotplt = 0, n_gnuver = 0, n_gnuver_r = 0;
- if (pie && img->dyn) {
+ if ((pie || shared) && img->dyn) {
n_dynsym = pool_intern_slice(c->global, SLICE_LIT(".dynsym"));
n_dynstr = pool_intern_slice(c->global, SLICE_LIT(".dynstr"));
n_gnuhash = pool_intern_slice(c->global, SLICE_LIT(".gnu.hash"));
@@ -1815,7 +1821,7 @@ void link_emit_elf(LinkImage* img, Writer* w) {
/* Two-pass: first find dynsym/dynstr/gotplt indices for sh_link
* fixups, then emit. */
u32 idx_dynsym = 0, idx_dynstr = 0, idx_gotplt = 0;
- if (pie && img->dyn) {
+ if ((pie || shared) && img->dyn) {
for (i = 0; i < noutshdr; ++i) {
Sym nm = outshdrs[i].name;
u32 ix = outshdrs[i].shdr_idx;
@@ -1852,7 +1858,7 @@ void link_emit_elf(LinkImage* img, Writer* w) {
? 8
: 0;
/* Dyn-section overrides: sh_type / sh_link / sh_info / entsize. */
- if (pie && img->dyn) {
+ if ((pie || shared) && img->dyn) {
if (o->name == n_dynsym) {
sh.sh_type = SHT_DYNSYM;
sh.sh_link = idx_dynstr;
diff --git a/src/obj/elf/link_dyn.c b/src/obj/elf/link_dyn.c
@@ -44,6 +44,7 @@
#include "link/link.h"
#include "link/link_arch.h"
#include "link/link_internal.h"
+#include "link/link_reloc_desc.h"
#include "obj/bytebuf.h"
#include "obj/elf/elf.h"
#include "obj/format.h"
@@ -160,6 +161,22 @@ static int dso_export_is_func(Linker* l, const LinkSymbol* s) {
return is_func;
}
+static int import_has_branch_reloc(Linker* l, LinkImage* img,
+ const LinkSymbol* s) {
+ u32 i;
+ if (!l || !img || !s || s->name == 0) return 0;
+ for (i = 0; i < LinkRelocs_count(&img->relocs); ++i) {
+ const LinkRelocApply* r = LinkRelocs_at(&img->relocs, i);
+ const LinkSymbol* tgt;
+ if (!reloc_kind_is_branch(l->c, r->kind)) continue;
+ if (r->target == LINK_SYM_NONE) continue;
+ tgt = LinkSyms_at(&img->syms, r->target - 1);
+ if (!tgt || !tgt->imported || tgt->name != s->name) continue;
+ return 1;
+ }
+ return 0;
+}
+
static void collect_imports(Linker* l, LinkImage* img, Heap* h,
ImportLists* il) {
u32 i;
@@ -176,14 +193,16 @@ static void collect_imports(Linker* l, LinkImage* img, Heap* h,
if (canonical != LINK_SYM_NONE && canonical != s->id) continue;
if (s->defined && !s->imported &&
(s->bind == SB_GLOBAL || s->bind == SB_WEAK) && s->kind != SK_FILE &&
- s->kind != SK_SECTION) {
+ s->kind != SK_SECTION && s->vis != SV_HIDDEN &&
+ s->vis != SV_INTERNAL) {
if (VEC_GROW(h, il->exports, cap_e, il->nexports + 1u))
compiler_panic(img->c, SRCLOC_NONE, "link: oom on exports");
il->exports[il->nexports++] = s->id;
continue;
}
if (!s->imported) continue;
- int is_func = sym_is_func_import(s) || dso_export_is_func(l, s);
+ int is_func = sym_is_func_import(s) || dso_export_is_func(l, s) ||
+ import_has_branch_reloc(l, img, s);
if (is_func) {
if (VEC_GROW(h, il->funcs, cap_f, il->nfuncs + 1u))
compiler_panic(img->c, SRCLOC_NONE, "link: oom on import-funcs");
@@ -317,7 +336,7 @@ static void build_dynsym(LinkImage* img, LinkDynState* dyn,
u8 elf_bind = elf_st_bind(s->bind);
r->st_name = objbb_append_str(dynstr, nm, (u32)namelen);
r->st_info = ELF64_ST_INFO(elf_bind, elf_type);
- r->st_other = STV_DEFAULT;
+ r->st_other = elf_st_other(s->vis);
/* The emitter refreshes defined-symbol values after the final header
* shift. Any nonzero, non-special section index is enough for rtld to
* treat the symbol as defined; section headers are not part of runtime
@@ -337,7 +356,7 @@ static void build_dynsym(LinkImage* img, LinkDynState* dyn,
size_t namelen = nm_s.len;
r->st_name = objbb_append_str(dynstr, nm, (u32)namelen);
r->st_info = ELF64_ST_INFO(STB_GLOBAL, STT_FUNC);
- r->st_other = STV_DEFAULT;
+ r->st_other = elf_st_other(s->vis);
r->st_shndx = SHN_UNDEF;
r->st_value = 0;
r->st_size = 0;
@@ -358,7 +377,7 @@ static void build_dynsym(LinkImage* img, LinkDynState* dyn,
elf_type = STT_NOTYPE;
r->st_name = objbb_append_str(dynstr, nm, (u32)namelen);
r->st_info = ELF64_ST_INFO(STB_GLOBAL, elf_type);
- r->st_other = STV_DEFAULT;
+ r->st_other = elf_st_other(s->vis);
r->st_shndx = SHN_UNDEF;
r->st_value = 0;
r->st_size = 0;
@@ -688,6 +707,8 @@ static u32 count_dynamic_entries(const LinkDynState* dyn) {
* Plus DT_NEEDED per dependency. */
u32 n = dyn->nneeded;
n += 7; /* 5 fixed + DT_FLAGS_1 + DT_NULL */
+ if (dyn->soname) n += 1;
+ n += dyn->nrpaths + dyn->nrunpaths;
if (dyn->cap_rela_dyn) n += 3; /* DT_RELA + DT_RELASZ + DT_RELAENT */
if (dyn->nrela_plt) n += 4; /* PLT-only entries */
if (dyn->nverneed) n += 3; /* DT_VERSYM + DT_VERNEED + DT_VERNEEDNUM */
@@ -706,7 +727,7 @@ void layout_dyn(Linker* l, LinkImage* img) {
const LinkArchDesc* arch;
const ObjElfArchOps* elf_arch;
- if (!l->emit_pie) return;
+ if (!l->emit_pie && !l->emit_shared) return;
/* The dynamic-section layout below is ELF64-only (Elf64_Sym/Dyn/Rela wire
* sizes, 8-byte GOT slots). rv32 is a static-only v1 target, so a dynamic /
@@ -736,7 +757,8 @@ void layout_dyn(Linker* l, LinkImage* img) {
memset(&dyn_probe, 0, sizeof dyn_probe);
collect_imports(l, img, h, &imports);
collect_needed(l, img, &dyn_probe);
- if (l->emit_static_exe && imports.nfuncs == 0 && imports.ndatas == 0 &&
+ if (!l->emit_shared && l->emit_static_exe && imports.nfuncs == 0 &&
+ imports.ndatas == 0 &&
dyn_probe.nneeded == 0) {
img->pie = 1;
free_imports(h, &imports);
@@ -747,7 +769,8 @@ void layout_dyn(Linker* l, LinkImage* img) {
if (!dyn) compiler_panic(img->c, SRCLOC_NONE, "link: oom on dyn state");
*dyn = dyn_probe;
img->dyn = dyn;
- img->pie = 1;
+ img->pie = l->emit_pie ? 1 : 0;
+ img->shared = l->emit_shared ? 1 : 0;
/* PT_INTERP path. Default to the canonical musl loader matching the
* target arch (per-arch table in src/arch/<arch>/link.c) when the caller
@@ -755,11 +778,34 @@ void layout_dyn(Linker* l, LinkImage* img) {
* link_set_interp_path; this default is correctness for direct
* libkit consumers. glibc users have to set their interp
* explicitly — we don't pick a default for them. */
- dyn->interp_path =
- l->interp_path
- ? l->interp_path
- : pool_intern_slice(l->c->global,
- slice_from_cstr(elf_arch->default_musl_interp));
+ if (!l->emit_shared) {
+ dyn->interp_path =
+ l->interp_path
+ ? l->interp_path
+ : pool_intern_slice(l->c->global,
+ slice_from_cstr(elf_arch->default_musl_interp));
+ }
+ if (l->soname.s && l->soname.len)
+ dyn->soname = pool_intern_slice(l->c->global, l->soname);
+ if (l->nrpaths) {
+ u32 ri;
+ dyn->rpaths =
+ (Sym*)h->alloc(h, sizeof(*dyn->rpaths) * l->nrpaths, _Alignof(Sym));
+ if (!dyn->rpaths) compiler_panic(img->c, SRCLOC_NONE, "link: oom on rpaths");
+ dyn->nrpaths = l->nrpaths;
+ for (ri = 0; ri < l->nrpaths; ++ri)
+ dyn->rpaths[ri] = pool_intern_slice(l->c->global, l->rpaths[ri]);
+ }
+ if (l->nrunpaths) {
+ u32 ri;
+ dyn->runpaths = (Sym*)h->alloc(h, sizeof(*dyn->runpaths) * l->nrunpaths,
+ _Alignof(Sym));
+ if (!dyn->runpaths)
+ compiler_panic(img->c, SRCLOC_NONE, "link: oom on runpaths");
+ dyn->nrunpaths = l->nrunpaths;
+ for (ri = 0; ri < l->nrunpaths; ++ri)
+ dyn->runpaths[ri] = pool_intern_slice(l->c->global, l->runpaths[ri]);
+ }
/* Step 2: build .dynstr + .dynsym. .dynstr must also carry the
* DT_NEEDED soname strings the .dynamic body references; intern
@@ -775,6 +821,21 @@ void layout_dyn(Linker* l, LinkImage* img) {
size_t slen = s_s.len;
if (s && slen) (void)objbb_append_str(&dynstr, s, (u32)slen);
}
+ if (dyn->soname) {
+ Slice s_s = pool_slice(l->c->global, dyn->soname);
+ if (s_s.s && s_s.len)
+ (void)objbb_append_str(&dynstr, s_s.s, (u32)s_s.len);
+ }
+ for (ni = 0; ni < dyn->nrpaths; ++ni) {
+ Slice s_s = pool_slice(l->c->global, dyn->rpaths[ni]);
+ if (s_s.s && s_s.len)
+ (void)objbb_append_str(&dynstr, s_s.s, (u32)s_s.len);
+ }
+ for (ni = 0; ni < dyn->nrunpaths; ++ni) {
+ Slice s_s = pool_slice(l->c->global, dyn->runpaths[ni]);
+ if (s_s.s && s_s.len)
+ (void)objbb_append_str(&dynstr, s_s.s, (u32)s_s.len);
+ }
}
/* Symbol versioning: assign per-import version requirements and append the
* version strings ("FBSD_1.5", ...) to .dynstr. Must run before .dynstr is
@@ -842,10 +903,11 @@ void layout_dyn(Linker* l, LinkImage* img) {
compiler_panic(img->c, SRCLOC_NONE, "link: oom on rela_dyn");
dyn->nrela_dyn = 0;
- Slice interp_s = pool_slice(l->c->global, dyn->interp_path);
+ Slice interp_s = l->emit_shared ? SLICE_NULL
+ : pool_slice(l->c->global, dyn->interp_path);
const char* interp_str = interp_s.s;
size_t namelen = interp_s.len;
- u64 interp_bytes = (u64)namelen + 1u;
+ u64 interp_bytes = l->emit_shared ? 0u : (u64)namelen + 1u;
u64 dynsym_bytes = (u64)dyn->ndynsym * ELF64_SYM_SIZE;
u64 dynstr_bytes = (u64)dyn->dynstr_len;
u64 gnuhash_bytes = (u64)dyn->gnu_hash_len;
@@ -948,7 +1010,7 @@ void layout_dyn(Linker* l, LinkImage* img) {
ro_seg->file_size = ro_seg_size;
ro_seg->mem_size = ro_seg_size;
ro_seg->align = (u32)page;
- ro_seg->nsections = 6u + (has_ver ? 2u : 0u);
+ ro_seg->nsections = (l->emit_shared ? 5u : 6u) + (has_ver ? 2u : 0u);
img->segment_bytes[ro_seg_idx] =
ro_seg_size ? (u8*)h->alloc(h, (size_t)ro_seg_size, 16) : NULL;
img->segment_bytes_cap[ro_seg_idx] = (size_t)ro_seg_size;
@@ -1028,7 +1090,8 @@ void layout_dyn(Linker* l, LinkImage* img) {
/* Step 6: synthetic LinkSection entries. Order in img->sections
* matches the loader-friendly file order and feeds emit's
* outshdr-merge pass. */
- u32 nsec = 7u + (has_plt ? 2u : 0u) + (has_ver ? 2u : 0u);
+ u32 nsec = (l->emit_shared ? 6u : 7u) + (has_plt ? 2u : 0u) +
+ (has_ver ? 2u : 0u);
u32 sec_base = dyn_alloc_sections(img, nsec);
/* helper: populate a fresh LinkSection for a segment-internal range */
@@ -1065,45 +1128,50 @@ void layout_dyn(Linker* l, LinkImage* img) {
ls->sem = (SEM); \
} while (0)
- INIT_SEC(0, name_interp, ro_seg_idx, interp_off, interp_bytes, 1, SF_ALLOC,
- SSEM_PROGBITS);
- INIT_SEC(1, name_dynsym, ro_seg_idx, dynsym_off, dynsym_bytes, 8, SF_ALLOC,
+ u32 si_base = 0;
+ if (!l->emit_shared) {
+ INIT_SEC(0, name_interp, ro_seg_idx, interp_off, interp_bytes, 1, SF_ALLOC,
+ SSEM_PROGBITS);
+ dyn->sec_interp = (LinkSectionId)(sec_base + 0 + 1u);
+ si_base = 1u;
+ }
+ INIT_SEC(si_base + 0u, name_dynsym, ro_seg_idx, dynsym_off, dynsym_bytes, 8, SF_ALLOC,
SSEM_PROGBITS);
- INIT_SEC(2, name_dynstr, ro_seg_idx, dynstr_off, dynstr_bytes, 1, SF_ALLOC,
+ INIT_SEC(si_base + 1u, name_dynstr, ro_seg_idx, dynstr_off, dynstr_bytes, 1, SF_ALLOC,
SSEM_PROGBITS);
- INIT_SEC(3, name_gnu_hash, ro_seg_idx, gnuhash_off, gnuhash_bytes, 8,
+ INIT_SEC(si_base + 2u, name_gnu_hash, ro_seg_idx, gnuhash_off, gnuhash_bytes, 8,
SF_ALLOC, SSEM_PROGBITS);
- INIT_SEC(4, name_rela_dyn, ro_seg_idx, rela_dyn_off, rela_dyn_bytes, 8,
+ INIT_SEC(si_base + 3u, name_rela_dyn, ro_seg_idx, rela_dyn_off, rela_dyn_bytes, 8,
SF_ALLOC, SSEM_PROGBITS);
- INIT_SEC(5, name_rela_plt, ro_seg_idx, rela_plt_off, rela_plt_bytes, 8,
+ INIT_SEC(si_base + 4u, name_rela_plt, ro_seg_idx, rela_plt_off, rela_plt_bytes, 8,
SF_ALLOC, SSEM_PROGBITS);
- INIT_SEC(6, name_dynamic, rw_seg_idx, dynamic_off, dynamic_bytes, 8,
+ INIT_SEC(si_base + 5u, name_dynamic, rw_seg_idx, dynamic_off, dynamic_bytes, 8,
SF_ALLOC | SF_WRITE, SSEM_PROGBITS);
- dyn->sec_interp = (LinkSectionId)(sec_base + 0 + 1u);
- dyn->sec_dynsym = (LinkSectionId)(sec_base + 1 + 1u);
- dyn->sec_dynstr = (LinkSectionId)(sec_base + 2 + 1u);
- dyn->sec_gnu_hash = (LinkSectionId)(sec_base + 3 + 1u);
- dyn->sec_rela_dyn = (LinkSectionId)(sec_base + 4 + 1u);
- dyn->sec_rela_plt = (LinkSectionId)(sec_base + 5 + 1u);
- dyn->sec_dynamic = (LinkSectionId)(sec_base + 6 + 1u);
+ dyn->sec_dynsym = (LinkSectionId)(sec_base + si_base + 0u + 1u);
+ dyn->sec_dynstr = (LinkSectionId)(sec_base + si_base + 1u + 1u);
+ dyn->sec_gnu_hash = (LinkSectionId)(sec_base + si_base + 2u + 1u);
+ dyn->sec_rela_dyn = (LinkSectionId)(sec_base + si_base + 3u + 1u);
+ dyn->sec_rela_plt = (LinkSectionId)(sec_base + si_base + 4u + 1u);
+ dyn->sec_dynamic = (LinkSectionId)(sec_base + si_base + 5u + 1u);
dyn->dynamic_vaddr = img->segments[rw_seg_idx].vaddr + dynamic_off;
dyn->dynamic_size = dynamic_bytes;
if (has_plt) {
- INIT_SEC(7, name_plt, rx_seg_idx, 0, plt_bytes, 16, SF_ALLOC | SF_EXEC,
+ u32 plt0 = si_base + 6u;
+ INIT_SEC(plt0, name_plt, rx_seg_idx, 0, plt_bytes, 16, SF_ALLOC | SF_EXEC,
SSEM_PROGBITS);
- INIT_SEC(8, name_got_plt, rw_seg_idx, gotplt_off, gotplt_bytes, 8,
+ INIT_SEC(plt0 + 1u, name_got_plt, rw_seg_idx, gotplt_off, gotplt_bytes, 8,
SF_ALLOC | SF_WRITE, SSEM_PROGBITS);
- dyn->sec_plt = (LinkSectionId)(sec_base + 7 + 1u);
- dyn->sec_got_plt = (LinkSectionId)(sec_base + 8 + 1u);
+ dyn->sec_plt = (LinkSectionId)(sec_base + plt0 + 1u);
+ dyn->sec_got_plt = (LinkSectionId)(sec_base + plt0 + 1u + 1u);
}
if (has_ver) {
/* Appended after the optional PLT slots; emit sorts the section-header
* table by (segment, vaddr), so array order here is not load-bearing. The
* SSEM_PROGBITS sem just parks the bytes in the ro segment — the runtime
* reads them via DT_VERSYM/DT_VERNEED, not the section headers. */
- u32 vb0 = 7u + (has_plt ? 2u : 0u);
+ u32 vb0 = si_base + 6u + (has_plt ? 2u : 0u);
INIT_SEC(vb0, name_gnu_version, ro_seg_idx, versym_off, versym_bytes, 2,
SF_ALLOC, SSEM_PROGBITS);
INIT_SEC(vb0 + 1u, name_gnu_version_r, ro_seg_idx, verneed_off,
@@ -1241,6 +1309,9 @@ void link_dyn_state_free(LinkImage* img) {
if (dyn->rela_plt)
h->free(h, dyn->rela_plt, sizeof(*dyn->rela_plt) * dyn->nrela_plt);
if (dyn->needed) h->free(h, dyn->needed, sizeof(*dyn->needed) * dyn->nneeded);
+ if (dyn->rpaths) h->free(h, dyn->rpaths, sizeof(*dyn->rpaths) * dyn->nrpaths);
+ if (dyn->runpaths)
+ h->free(h, dyn->runpaths, sizeof(*dyn->runpaths) * dyn->nrunpaths);
if (dyn->sym_dynidx)
h->free(h, dyn->sym_dynidx,
sizeof(*dyn->sym_dynidx) * dyn->sym_dynidx_size);
diff --git a/src/obj/elf/link_dyn.h b/src/obj/elf/link_dyn.h
@@ -56,6 +56,13 @@ typedef struct LinkDynState {
Sym interp_path;
LinkSectionId sec_interp;
+ /* DT_SONAME / DT_RPATH / DT_RUNPATH strings, interned in compiler->global. */
+ Sym soname;
+ Sym* rpaths;
+ u32 nrpaths;
+ Sym* runpaths;
+ u32 nrunpaths;
+
/* .dynsym */
LinkSectionId sec_dynsym;
DynSymRec* dynsym;
diff --git a/test/link/elf-dso-aa64/run.sh b/test/link/elf-dso-aa64/run.sh
@@ -1,23 +1,11 @@
#!/usr/bin/env bash
# test/link/elf-dso-aa64/run.sh — ELF shared-library harness, aarch64 target.
#
-# Three lanes (only lane A is implemented now; B and C follow after the kit
-# linker and codegen support lands):
-#
-# A — all-clang baseline: clang compiles PIC objects, clang (via lld) links
-# the DSO and consumer, readelf structural checks, podman runtime exec.
-# This lane proves the test fixture, sysroot, container, and readelf
-# checks are correct before any kit linker or codegen work is in scope.
-#
-# B — kit ld -shared: clang still compiles PIC objects but kit ld produces
-# the DSO. Isolates ELF DSO production from kit codegen. (NOT YET)
-#
-# C — all-kit: kit cc -fPIC compiles the objects, kit ld -shared links.
-# Validates kit-generated PIC code and DSO interposition choices. (NOT YET)
+# Single kit lane: kit cc compiles PIC objects, kit ld links libfoo.so, kit cc
+# links the consumers, then readelf and podman verify the result.
#
# Prerequisites (checked at runtime; missing prereqs produce SKIP, not FAIL):
-# clang — with --target=aarch64-linux-gnu cross-compilation support
-# lld — ld.lld on PATH (needed for ELF shared library link from macOS)
+# build/kit — built kit binary
# readelf — llvm-readelf or GNU readelf (structural checks)
# podman — for runtime execution inside an aarch64 container
# images: alpine (musl) and/or debian:bookworm-slim (glibc)
@@ -25,7 +13,6 @@
# Usage:
# bash test/link/elf-dso-aa64/run.sh # all structural + runtime
# KIT_TEST_ALLOW_SKIP=1 bash ... # allow skips (CI-friendly)
-# KIT_LANE=A bash ... # explicit lane filter (default A)
#
# Output: structured pass/fail via kit_sh_report.sh; exit 0 = all pass.
@@ -41,19 +28,12 @@ kit_report_init
KIT_SKIP_IS_FAILURE="${KIT_SKIP_IS_FAILURE:-0}"
-CLANG_TARGET="--target=aarch64-linux-gnu"
+KIT="$ROOT/build/kit"
+KIT_TARGET="-target aarch64-linux"
work="$BUILD_DIR"
# ---- tool detection -----------------------------------------------------------
-have_clang_aa64=0
-if clang $CLANG_TARGET -c -x c - -o /dev/null < /dev/null 2>/dev/null; then
- have_clang_aa64=1
-fi
-
-have_lld=0
-command -v ld.lld >/dev/null 2>&1 && have_lld=1
-
have_readelf=0
READELF_BIN=""
if command -v llvm-readelf >/dev/null 2>&1; then
@@ -110,9 +90,9 @@ exec_dso_podman() {
RUN_RC=$?
}
-# ---- lane A: all-clang --------------------------------------------------------
+# ---- kit shared-library path -------------------------------------------------
#
-# Structural checks (require clang + lld + readelf):
+# Structural checks (require build/kit + readelf):
# A/build-lib compile lib_foo.c -fPIC and link libfoo.so
# A/header-dyn ET_DYN, e_entry = 0
# A/no-interp no PT_INTERP in program headers
@@ -128,20 +108,10 @@ exec_dso_podman() {
# A/run-musl consumer-musl exits 0 inside alpine (musl) container
# A/run-glibc consumer-glibc exits 0 inside debian (glibc) container
-LANE="${KIT_LANE:-A}"
-if [ "$LANE" != A ]; then
- skip_test "A" "KIT_LANE=$LANE (skipping lane A)"
- kit_summary test-link-dso-aa64; kit_exit
-fi
-
# ---- A: prerequisites --------------------------------------------------------
-if [ $have_clang_aa64 -eq 0 ]; then
- skip_test "A" "clang --target=aarch64-linux-gnu unavailable"
- kit_summary test-link-dso-aa64; kit_exit
-fi
-if [ $have_lld -eq 0 ]; then
- skip_test "A" "ld.lld unavailable (needed for ELF shared library link)"
+if [ ! -x "$KIT" ]; then
+ skip_test "A" "build/kit unavailable (run: make bin)"
kit_summary test-link-dso-aa64; kit_exit
fi
if [ $have_readelf -eq 0 ]; then
@@ -159,22 +129,19 @@ SRC_DIR="$(cd "$(dirname "$0")" && pwd)"
# ---- A/build-lib: compile lib_foo.c + link libfoo.so -------------------------
-if ! clang $CLANG_TARGET -fuse-ld=lld \
- -O1 -fno-stack-protector -fPIC \
+if ! "$KIT" cc $KIT_TARGET \
+ -O1 -fPIC \
-c "$SRC_DIR/lib_foo.c" -o "$LIBFOO_O" \
>"$work/compile-lib.out" 2>"$work/compile-lib.err"; then
not_ok "A/build-lib" "$work/compile-lib.err"
kit_summary test-link-dso-aa64; kit_exit
fi
-# -Wl,--allow-shlib-undefined: bar_external is intentionally unresolved in the
-# DSO; it will appear as UND in .dynsym and be satisfied at runtime by the
-# consumer. This is the default for -shared with GNU/LLVM linkers but we spell
-# it out to be explicit about the policy.
-if ! clang $CLANG_TARGET -fuse-ld=lld \
- -shared -fPIC -nostdlib \
- -Wl,--allow-shlib-undefined \
- -Wl,-soname,libfoo.so \
+# bar_external is intentionally unresolved in the DSO; it will appear as UND
+# in .dynsym and be satisfied at runtime by the consumer.
+if ! "$KIT" ld \
+ -shared \
+ -soname libfoo.so \
"$LIBFOO_O" -o "$LIBFOO_SO" \
>"$work/link-lib.out" 2>"$work/link-lib.err"; then
not_ok "A/build-lib" "$work/link-lib.err"
@@ -290,11 +257,11 @@ fi
# Musl interpreter (alpine linux/arm64)
MUSL_INTERP="/lib/ld-musl-aarch64.so.1"
-if ! clang $CLANG_TARGET -fuse-ld=lld \
- -O1 -ffreestanding -fno-stack-protector \
+if ! "$KIT" cc $KIT_TARGET \
+ -O1 -ffreestanding \
-nostdlib -rdynamic \
- -Wl,--dynamic-linker="$MUSL_INTERP" \
- -Wl,-e,_start \
+ -e _start \
+ -Wl,-dynamic-linker,"$MUSL_INTERP" \
-Wl,-rpath,\$ORIGIN \
"$SRC_DIR/consumer.c" \
-L"$work" -lfoo \
@@ -308,11 +275,11 @@ fi
# Glibc interpreter (debian:bookworm-slim linux/arm64)
GLIBC_INTERP="/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1"
-if ! clang $CLANG_TARGET -fuse-ld=lld \
- -O1 -ffreestanding -fno-stack-protector \
+if ! "$KIT" cc $KIT_TARGET \
+ -O1 -ffreestanding \
-nostdlib -rdynamic \
- -Wl,--dynamic-linker="$GLIBC_INTERP" \
- -Wl,-e,_start \
+ -e _start \
+ -Wl,-dynamic-linker,"$GLIBC_INTERP" \
-Wl,-rpath,\$ORIGIN \
"$SRC_DIR/consumer.c" \
-L"$work" -lfoo \