kit

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

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:
Mdriver/cmd/build.c | 14++++++--------
Mdriver/cmd/cc.c | 16+++++++++++-----
Mdriver/cmd/ld.c | 5-----
Msrc/api/link.c | 15++++++++-------
Msrc/link/link.c | 5+++++
Msrc/link/link.h | 4+++-
Msrc/link/link_internal.h | 10++++++++++
Msrc/link/link_layout.c | 4+++-
Msrc/link/link_reloc_layout.c | 4++++
Msrc/link/link_resolve.c | 8++++++++
Msrc/obj/elf/link.c | 114++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Msrc/obj/elf/link_dyn.c | 145+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Msrc/obj/elf/link_dyn.h | 7+++++++
Mtest/link/elf-dso-aa64/run.sh | 81++++++++++++++++++++++++-------------------------------------------------------
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 \