kit

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

commit 65f74c2d6c1c5d3c32063cd1818d7cb40ca3215d
parent e364e60ef0f3fab4cc3ca9a074933888e414d543
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue, 19 May 2026 22:28:30 -0700

Implement relocatable ld links

Diffstat:
Mdriver/ld.c | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Minclude/cfree/link.h | 7+++++++
Msrc/api/link.c | 29++++++++++++++++++++++++++---
Msrc/link/link.h | 6++++++
Asrc/link/link_relocatable.c | 459+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/obj/obj.c | 10++++++++++
Msrc/obj/obj.h | 1+
Mtest/driver/run.sh | 41+++++++++++++++++++++++++++++++++++++++++
8 files changed, 605 insertions(+), 19 deletions(-)

diff --git a/driver/ld.c b/driver/ld.c @@ -1,18 +1,17 @@ +#include <cfree/core.h> +#include <cfree/link.h> +#include <cfree/object.h> #include <stdint.h> #include "driver.h" #include "lib_resolve.h" -#include <cfree/core.h> -#include <cfree/link.h> -#include <cfree/object.h> - -/* `cfree ld` — link object/archive inputs into an executable or shared - * library. The driver loads each input via env.file_io, optionally parses a - * `-T` linker script into the structured form, and calls cfree_link_exe or - * cfree_link_shared directly so the full link surface (per-archive flags, - * group cycling, build-id, soname/rpath/exports) is reachable without - * thickening the convenience CfreeOptions struct. +/* `cfree ld` — link object/archive inputs into an executable, shared + * library, or relocatable object. The driver loads each input via + * env.file_io, optionally parses a `-T` linker script into the structured + * form, and calls the public link APIs directly so the full link surface + * (per-archive flags, group cycling, build-id, soname/rpath/exports) is + * reachable without thickening the convenience CfreeOptions struct. * * Supported flags: * -o out output path (required, exactly one) @@ -22,6 +21,7 @@ * -l name resolves to lib<name>.a via -L * -static / -pie / -no-pie target.pic * -shared emit a shared library / dylib + * -r / --relocatable emit a relocatable partial-link object * -soname NAME DT_SONAME / LC_ID_DYLIB * -rpath DIR DT_RPATH/DT_RUNPATH entry (repeatable) * -rpath-link DIR shared-lib search at link time @@ -98,6 +98,7 @@ typedef struct LdOptions { /* Shared-library output state. */ int shared; /* -shared */ + int relocatable; /* -r / --relocatable */ const char* soname; /* -soname NAME */ const char** rpaths; /* -rpath DIR (repeatable) */ uint32_t nrpaths; @@ -134,6 +135,7 @@ void driver_help_ld(void) { "USAGE\n" " cfree ld -o OUT [options] inputs.o ... inputs.a ...\n" " cfree ld -shared -o libfoo.so [options] inputs.o ...\n" + " cfree ld -r -o partial.o inputs.o ...\n" "\n" "DESCRIPTION\n" " Loads each input via host file I/O, optionally parses a -T linker\n" @@ -146,6 +148,7 @@ void driver_help_ld(void) { " -shared Emit a position-independent shared library " "/\n" " dylib instead of an executable\n" + " -r, --relocatable Emit a relocatable partial-link object\n" "\n" "ENTRY / SCRIPT\n" " -e SYMBOL Entry symbol\n" @@ -212,8 +215,8 @@ static int ld_alloc_arrays(LdOptions* o, int argc) { o->lib_dirs = driver_alloc_zeroed(o->env, bound * sizeof(*o->lib_dirs)); o->rpaths = driver_alloc_zeroed(o->env, bound * sizeof(*o->rpaths)); o->rpath_links = driver_alloc_zeroed(o->env, bound * sizeof(*o->rpath_links)); - if (!o->object_files || !o->archives || !o->dsos || !o->order || !o->lib_dirs || - !o->rpaths || !o->rpath_links) { + if (!o->object_files || !o->archives || !o->dsos || !o->order || + !o->lib_dirs || !o->rpaths || !o->rpath_links) { driver_errf(LD_TOOL, "out of memory"); return 1; } @@ -467,6 +470,10 @@ static int ld_parse(int argc, char** argv, LdOptions* o) { if (o->target.pic == CFREE_PIC_NONE) o->target.pic = CFREE_PIC_PIC; continue; } + if (driver_streq(a, "-r") || driver_streq(a, "--relocatable")) { + o->relocatable = 1; + continue; + } if (driver_streq(a, "-soname")) { if (++i >= argc) { driver_errf(LD_TOOL, "-soname requires an argument"); @@ -608,6 +615,32 @@ static int ld_parse(int argc, char** argv, LdOptions* o) { ld_usage(); return 1; } + if (o->relocatable) { + if (o->shared) { + driver_errf(LD_TOOL, "-r and -shared are incompatible"); + return 1; + } + if (o->pie || o->target.pic == CFREE_PIC_PIE) { + driver_errf(LD_TOOL, "-r and -pie are incompatible"); + return 1; + } + if (o->interp_path) { + driver_errf(LD_TOOL, "-dynamic-linker requires executable output"); + return 1; + } + if (o->soname || o->nrpaths || o->nrpath_links) { + driver_errf(LD_TOOL, "-soname/-rpath/-rpath-link require -shared"); + return 1; + } + if (o->ndsos) { + driver_errf(LD_TOOL, "-r does not support shared-object inputs"); + return 1; + } + if (o->script_path) { + driver_errf(LD_TOOL, "-r does not support linker scripts yet"); + return 1; + } + } return 0; } @@ -693,6 +726,7 @@ static int ld_run_link(LdOptions* o) { CfreeLinkInputs inputs; CfreeExeLinkOptions link_opts; CfreeSharedLinkOptions shared_opts; + CfreeRelocatableLinkOptions reloc_opts; uint32_t i; int rc = 1; @@ -822,7 +856,13 @@ static int ld_run_link(LdOptions* o) { inputs.build_id_bytes = o->build_id_bytes; inputs.build_id_len = o->build_id_len; - if (o->shared) { + if (o->relocatable) { + CfreeRelocatableLinkOptions zero = {0}; + reloc_opts = zero; + reloc_opts.inputs = inputs; + rc = cfree_link_relocatable(compiler, &reloc_opts, writer) == CFREE_OK ? 0 + : 1; + } else if (o->shared) { CfreeSharedLinkOptions zero = {0}; shared_opts = zero; shared_opts.inputs = inputs; @@ -866,10 +906,9 @@ out: /* Match compiler/linker drivers: successful link outputs get executable * file modes while still respecting the process umask. Done after closing * the writer so the bits are stable on disk. */ - if (rc == 0 && o->output_path) { + if (rc == 0 && o->output_path && !o->relocatable) { if (driver_mark_executable_output(o->output_path) != 0) { - driver_errf(LD_TOOL, "failed to set executable mode: %s", - o->output_path); + driver_errf(LD_TOOL, "failed to set executable mode: %s", o->output_path); rc = 1; } } diff --git a/include/cfree/link.h b/include/cfree/link.h @@ -182,6 +182,10 @@ typedef struct CfreeSharedLinkOptions { bool gc_sections; } CfreeSharedLinkOptions; +typedef struct CfreeRelocatableLinkOptions { + CfreeLinkInputs inputs; +} CfreeRelocatableLinkOptions; + typedef struct CfreeJitLinkOptions { CfreeLinkInputs inputs; bool gc_sections; @@ -193,6 +197,9 @@ CfreeStatus cfree_link_exe(CfreeCompiler *, const CfreeExeLinkOptions *, CfreeWriter *out); CfreeStatus cfree_link_shared(CfreeCompiler *, const CfreeSharedLinkOptions *, CfreeWriter *out); +CfreeStatus cfree_link_relocatable(CfreeCompiler *, + const CfreeRelocatableLinkOptions *, + CfreeWriter *out); CfreeStatus cfree_link_jit(CfreeCompiler *, const CfreeJitLinkOptions *, const CfreeJitHost *, CfreeJit **out_jit); diff --git a/src/api/link.c b/src/api/link.c @@ -55,9 +55,8 @@ static void load_inputs(Linker* l, const CfreeLinkInputs* in) { compiler_panic(l->c, no_loc(), "link: ordered archive input index out of range"); a = &in->archives[ord->index]; - link_add_archive_bytes(l, a->bytes.name, a->bytes.data, - a->bytes.len, a->whole_archive, - a->link_mode, a->group_id); + link_add_archive_bytes(l, a->bytes.name, a->bytes.data, a->bytes.len, + a->whole_archive, a->link_mode, a->group_id); break; } case CFREE_LINK_INPUT_DSO: { @@ -179,6 +178,30 @@ CfreeStatus cfree_link_shared(CfreeCompiler* c, return CFREE_OK; } +CfreeStatus cfree_link_relocatable(CfreeCompiler* c, + const CfreeRelocatableLinkOptions* opts, + CfreeWriter* out) { + PanicSave saved; + Linker* l; + if (!c || !opts || !out) return CFREE_INVALID; + compiler_panic_save(c, &saved); + if (setjmp(c->panic)) { + compiler_run_cleanups(c); + compiler_panic_restore(c, &saved); + return CFREE_ERR; + } + l = link_new(c); + if (!l) { + compiler_panic_restore(c, &saved); + return CFREE_NOMEM; + } + load_inputs(l, &opts->inputs); + link_emit_relocatable_writer(l, out); + link_free(l); + compiler_panic_restore(c, &saved); + return CFREE_OK; +} + CfreeStatus cfree_link_jit(CfreeCompiler* c, const CfreeJitLinkOptions* opts, const CfreeJitHost* host, CfreeJit** out_jit) { PanicSave saved; diff --git a/src/link/link.h b/src/link/link.h @@ -249,6 +249,12 @@ const LinkRelocApply* link_reloc_apply_get(LinkImage*, u32 id); * caller-provided Writer. Path-based emit lives in the driver. */ void link_emit_image_writer(LinkImage*, Writer*); +/* Writes an ET_REL / MH_OBJECT relocatable partial-link output. This consumes + * the Linker's object/archive inputs and emits a fresh ObjBuilder through the + * active object-format writer; it does not perform executable layout, section + * GC, entry resolution, GOT/IPLT synthesis, or DSO binding. */ +void link_emit_relocatable_writer(Linker*, Writer*); + /* JIT: maps the image into executable memory and returns an owning handle. * The returned CfreeJit takes ownership of the LinkImage (undefers it from * the cleanup stack registered by link_resolve); on cfree_jit_free both the diff --git a/src/link/link_relocatable.c b/src/link/link_relocatable.c @@ -0,0 +1,459 @@ +/* Relocatable partial linking (`ld -r`). + * + * This path deliberately builds a fresh ObjBuilder instead of routing through + * LinkImage. LinkImage is an executable/JIT image model: it drops non-alloc + * sections, resolves strong undefs, synthesizes boundary/GOT/IPLT state, and + * assigns final vaddrs. A relocatable output must preserve object-file + * structure and leave unresolved externals as relocatable symbol references. + */ + +#include <string.h> + +#include "core/buf.h" +#include "core/heap.h" +#include "core/pool.h" +#include "core/vec.h" +#include "link/link.h" +#include "link/link_internal.h" + +static SrcLoc no_loc(void) { + SrcLoc l = {0, 0, 0}; + return l; +} + +typedef struct RelObjSecMap { + ObjSecId out_sec; + u32 delta; +} RelObjSecMap; + +typedef struct RelInputMap { + ObjSymId* sym; + u32 nsym; + RelObjSecMap* section; + u32 nsection; +} RelInputMap; + +typedef struct RelGlobal { + Sym name; + u8 has_def; + u8 has_undef; + u8 undef_bind; + u8 pad0; + u32 def_input_idx; + ObjSymId def_sym; + u64 common_size; + u64 common_align; + ObjSymId out_sym; +} RelGlobal; + +static int rel_bind_strength(u16 bind) { + switch (bind) { + case SB_GLOBAL: + return 3; + case SB_WEAK: + return 2; + case SB_LOCAL: + return 1; + default: + return 0; + } +} + +static int rel_sym_is_def(const ObjSym* s) { + return s && s->kind != SK_UNDEF && + (s->kind == SK_ABS || s->kind == SK_COMMON || s->kind == SK_FILE || + s->section_id != OBJ_SEC_NONE); +} + +static int rel_sym_is_spurious_undef(const ObjSym* s) { + return s && s->section_id == OBJ_SEC_NONE && s->kind != SK_ABS && + s->kind != SK_COMMON && !s->referenced && + (s->bind == SB_GLOBAL || s->bind == SB_WEAK); +} + +static u32 rel_symbol_count(ObjBuilder* ob) { + ObjSymIter* it; + ObjSymEntry e; + u32 n = 0; + it = obj_symiter_new(ob); + while (obj_symiter_next(it, &e)) ++n; + obj_symiter_free(it); + return n; +} + +static const ObjSym* rel_input_sym(Linker* l, u32 input_idx, ObjSymId id) { + LinkInput* in; + if (input_idx >= LinkInputs_count(&l->inputs)) return NULL; + in = LinkInputs_at(&l->inputs, input_idx); + return in && in->obj ? obj_symbol_get(in->obj, id) : NULL; +} + +static void rel_maps_free(Heap* h, RelInputMap* maps, u32 ninputs) { + u32 i; + if (!maps) return; + for (i = 0; i < ninputs; ++i) { + if (maps[i].sym) + h->free(h, maps[i].sym, sizeof(*maps[i].sym) * maps[i].nsym); + if (maps[i].section) + h->free(h, maps[i].section, sizeof(*maps[i].section) * maps[i].nsection); + } + h->free(h, maps, sizeof(*maps) * ninputs); +} + +static void rel_globals_free(Heap* h, RelGlobal* globals, u32 cap, + SymHash* by_name) { + if (by_name) symhash_fini(by_name); + if (globals) h->free(h, globals, sizeof(*globals) * cap); +} + +static RelGlobal* rel_global_for(Linker* l, RelGlobal** globals, u32* nglobals, + u32* cap, SymHash* by_name, Sym name) { + LinkSymId hit = symhash_get(by_name, name); + RelGlobal* g; + if (hit != LINK_SYM_NONE) return &(*globals)[hit - 1u]; + if (VEC_GROW(l->heap, *globals, *cap, *nglobals + 1u)) + compiler_panic(l->c, no_loc(), "link -r: oom on global symbols"); + g = &(*globals)[*nglobals]; + memset(g, 0, sizeof(*g)); + g->name = name; + (*nglobals)++; + symhash_set(by_name, name, (LinkSymId)*nglobals); + return g; +} + +static void rel_record_global(Linker* l, RelGlobal** globals, u32* nglobals, + u32* cap, SymHash* by_name, u32 input_idx, + ObjSymId id, const ObjSym* s) { + RelGlobal* g; + if (!s || s->name == 0 || s->bind == SB_LOCAL) return; + if (rel_sym_is_spurious_undef(s)) return; + g = rel_global_for(l, globals, nglobals, cap, by_name, s->name); + if (!rel_sym_is_def(s)) { + if (!g->has_undef || + rel_bind_strength(s->bind) > rel_bind_strength(g->undef_bind)) { + g->undef_bind = (u8)s->bind; + } + g->has_undef = 1u; + return; + } + + if (!g->has_def) { + g->has_def = 1u; + g->def_input_idx = input_idx; + g->def_sym = id; + if (s->kind == SK_COMMON) { + g->common_size = s->size; + g->common_align = s->common_align; + } + return; + } + + { + const ObjSym* prev = rel_input_sym(l, g->def_input_idx, g->def_sym); + int old_strength = prev ? rel_bind_strength(prev->bind) : 0; + int new_strength = rel_bind_strength(s->bind); + if (prev && prev->kind == SK_COMMON && s->kind == SK_COMMON) { + if (s->size > g->common_size) g->common_size = s->size; + if (s->common_align > g->common_align) g->common_align = s->common_align; + if (new_strength > old_strength) { + g->def_input_idx = input_idx; + g->def_sym = id; + } + } else if (s->kind == SK_COMMON) { + return; + } else if (prev && prev->kind == SK_COMMON) { + g->def_input_idx = input_idx; + g->def_sym = id; + g->common_size = 0; + g->common_align = 0; + } else if (new_strength > old_strength) { + g->def_input_idx = input_idx; + g->def_sym = id; + } else if (new_strength == old_strength && + new_strength == rel_bind_strength(SB_GLOBAL)) { + size_t namelen; + const char* nm = pool_str(l->c->global, s->name, &namelen); + obj_format_demangle_c(l->c, &nm, &namelen); + compiler_panic(l->c, no_loc(), + "link -r: duplicate definition of global symbol '%.*s'", + (int)namelen, nm); + } + } +} + +static ObjSymId rel_copy_symbol(Linker* l, ObjBuilder* out, + const RelInputMap* maps, u32 input_idx, + const ObjSym* s, int force_common, + u64 common_size, u64 common_align) { + ObjSecId sec = OBJ_SEC_NONE; + u64 value = s->value; + u64 align = s->common_align; + ObjSymId id; + if (!force_common && s->section_id != OBJ_SEC_NONE && + s->section_id < maps[input_idx].nsection) { + const RelObjSecMap* sm = &maps[input_idx].section[s->section_id]; + sec = sm->out_sec; + value += sm->delta; + } + if (force_common) { + value = 0; + align = common_align; + } + id = obj_symbol_ex(out, s->name, (SymBind)s->bind, (SymVis)s->vis, + force_common ? SK_COMMON : (SymKind)s->kind, sec, value, + force_common ? common_size : s->size, align); + if (id == OBJ_SYM_NONE) + compiler_panic(l->c, no_loc(), "link -r: oom copying symbol"); + if (s->flags) obj_symbol_set_flags(out, id, s->flags); + obj_sym_mark_referenced(out, id); + return id; +} + +static ObjSymId rel_global_out(Linker* l, ObjBuilder* out, RelInputMap* maps, + RelGlobal* g) { + const ObjSym* s; + if (g->out_sym != OBJ_SYM_NONE) return g->out_sym; + if (g->has_def) { + s = rel_input_sym(l, g->def_input_idx, g->def_sym); + if (!s) compiler_panic(l->c, no_loc(), "link -r: bad global symbol map"); + g->out_sym = + rel_copy_symbol(l, out, maps, g->def_input_idx, s, s->kind == SK_COMMON, + g->common_size, g->common_align); + } else { + ObjSymId id; + id = obj_symbol(out, g->name, + g->has_undef ? (SymBind)g->undef_bind : SB_GLOBAL, SK_UNDEF, + OBJ_SEC_NONE, 0, 0); + if (id == OBJ_SYM_NONE) + compiler_panic(l->c, no_loc(), "link -r: oom creating undef symbol"); + obj_sym_mark_referenced(out, id); + g->out_sym = id; + } + return g->out_sym; +} + +static void rel_copy_sections(Linker* l, ObjBuilder* out, RelInputMap* maps, + u32 ninputs) { + u32 ii, j; + int have_eflags = 0; + u32 eflags = 0; + for (ii = 0; ii < ninputs; ++ii) { + LinkInput* in = LinkInputs_at(&l->inputs, ii); + ObjBuilder* ob = in->obj; + u32 nsec; + u32 in_eflags; + if (in->kind == LINK_INPUT_DSO_BYTES) + compiler_panic(l->c, no_loc(), "link -r: DSO inputs are not supported"); + if (!ob) continue; + if (obj_get_elf_e_flags(ob, &in_eflags)) { + if (!have_eflags) { + eflags = in_eflags; + have_eflags = 1; + } else if (eflags != in_eflags) { + compiler_panic(l->c, no_loc(), "link -r: incompatible ELF e_flags"); + } + } + nsec = obj_section_count(ob); + maps[ii].nsection = nsec; + maps[ii].section = (RelObjSecMap*)l->heap->alloc( + l->heap, sizeof(*maps[ii].section) * nsec, _Alignof(RelObjSecMap)); + if (!maps[ii].section) + compiler_panic(l->c, no_loc(), "link -r: oom on section map"); + memset(maps[ii].section, 0, sizeof(*maps[ii].section) * nsec); + for (j = 1; j < nsec; ++j) { + const Section* s = obj_section_get(ob, (ObjSecId)j); + ObjSecId out_sec; + if (!s) continue; + out_sec = + obj_section_ex(out, s->name, (SecKind)s->kind, (SecSem)s->sem, + s->flags, s->align, s->entsize, OBJ_SEC_NONE, s->info); + if (out_sec == OBJ_SEC_NONE) + compiler_panic(l->c, no_loc(), "link -r: oom copying section"); + if (s->ext_kind != OBJ_EXT_NONE || s->ext_type || s->ext_flags) + obj_section_set_ext(out, out_sec, (ObjExtKind)s->ext_kind, s->ext_type, + s->ext_flags); + if (s->sem == SSEM_NOBITS || s->kind == SEC_BSS) { + obj_reserve_bss(out, out_sec, s->bss_size, s->align); + } else if (s->bytes.total) { + u8* flat = (u8*)l->heap->alloc(l->heap, s->bytes.total, 1u); + if (!flat) + compiler_panic(l->c, no_loc(), "link -r: oom copying section bytes"); + buf_flatten(&s->bytes, flat); + obj_write(out, out_sec, flat, s->bytes.total); + l->heap->free(l->heap, flat, s->bytes.total); + } + maps[ii].section[j].out_sec = out_sec; + maps[ii].section[j].delta = 0; + } + } + if (have_eflags) obj_set_elf_e_flags(out, eflags); + + for (ii = 0; ii < ninputs; ++ii) { + LinkInput* in = LinkInputs_at(&l->inputs, ii); + ObjBuilder* ob = in->obj; + if (!ob) continue; + for (j = 1; j < maps[ii].nsection; ++j) { + const Section* s = obj_section_get(ob, (ObjSecId)j); + ObjSecId link = OBJ_SEC_NONE; + ObjSecId out_sec = maps[ii].section[j].out_sec; + if (!s || out_sec == OBJ_SEC_NONE) continue; + if (s->link != OBJ_SEC_NONE && s->link < maps[ii].nsection) + link = maps[ii].section[s->link].out_sec; + obj_section_set_link_info(out, out_sec, link, s->info); + } + } +} + +static void rel_scan_globals(Linker* l, RelGlobal** globals, u32* nglobals, + u32* cap, SymHash* by_name) { + u32 ii; + for (ii = 0; ii < LinkInputs_count(&l->inputs); ++ii) { + LinkInput* in = LinkInputs_at(&l->inputs, ii); + ObjSymIter* it; + ObjSymEntry e; + if (!in->obj || in->kind == LINK_INPUT_DSO_BYTES) continue; + it = obj_symiter_new(in->obj); + while (obj_symiter_next(it, &e)) + rel_record_global(l, globals, nglobals, cap, by_name, ii, e.id, e.sym); + obj_symiter_free(it); + } +} + +static void rel_copy_symbols(Linker* l, ObjBuilder* out, RelInputMap* maps, + SymHash* globals_by_name, RelGlobal* globals) { + u32 ii; + for (ii = 0; ii < LinkInputs_count(&l->inputs); ++ii) { + LinkInput* in = LinkInputs_at(&l->inputs, ii); + ObjSymIter* it; + ObjSymEntry e; + u32 nsym; + if (!in->obj || in->kind == LINK_INPUT_DSO_BYTES) continue; + nsym = rel_symbol_count(in->obj) + 1u; + maps[ii].nsym = nsym; + maps[ii].sym = (ObjSymId*)l->heap->alloc( + l->heap, sizeof(*maps[ii].sym) * nsym, _Alignof(ObjSymId)); + if (!maps[ii].sym) + compiler_panic(l->c, no_loc(), "link -r: oom on symbol map"); + memset(maps[ii].sym, 0, sizeof(*maps[ii].sym) * nsym); + it = obj_symiter_new(in->obj); + while (obj_symiter_next(it, &e)) { + const ObjSym* s = e.sym; + if (e.id >= nsym) continue; + if (rel_sym_is_spurious_undef(s)) continue; + if (s->name != 0 && s->bind != SB_LOCAL) { + LinkSymId rec_idx = symhash_get(globals_by_name, s->name); + if (rec_idx != LINK_SYM_NONE) + maps[ii].sym[e.id] = + rel_global_out(l, out, maps, &globals[rec_idx - 1u]); + } else { + maps[ii].sym[e.id] = rel_copy_symbol(l, out, maps, ii, s, 0, 0, 0); + } + } + obj_symiter_free(it); + } +} + +static void rel_copy_groups(Linker* l, ObjBuilder* out, RelInputMap* maps) { + u32 ii, gi, j; + for (ii = 0; ii < LinkInputs_count(&l->inputs); ++ii) { + LinkInput* in = LinkInputs_at(&l->inputs, ii); + ObjBuilder* ob = in->obj; + if (!ob || in->kind == LINK_INPUT_DSO_BYTES) continue; + for (gi = 1; gi < obj_group_count(ob); ++gi) { + const ObjGroup* g = obj_group_get(ob, (ObjGroupId)gi); + ObjSymId sig = OBJ_SYM_NONE; + ObjGroupId out_gid; + if (!g) continue; + if (g->signature != OBJ_SYM_NONE && g->signature < maps[ii].nsym) + sig = maps[ii].sym[g->signature]; + out_gid = obj_group(out, g->name, sig, g->flags); + if (out_gid == OBJ_GROUP_NONE) + compiler_panic(l->c, no_loc(), "link -r: oom copying group"); + for (j = 0; j < g->nsections; ++j) { + ObjSecId in_sec = g->sections[j]; + ObjSecId out_sec = OBJ_SEC_NONE; + if (in_sec != OBJ_SEC_NONE && in_sec < maps[ii].nsection) + out_sec = maps[ii].section[in_sec].out_sec; + if (out_sec != OBJ_SEC_NONE) { + obj_group_add_section(out, out_gid, out_sec); + obj_section_set_group(out, out_sec, out_gid); + } + } + } + } +} + +static void rel_copy_relocs(Linker* l, ObjBuilder* out, RelInputMap* maps) { + u32 ii, ri; + for (ii = 0; ii < LinkInputs_count(&l->inputs); ++ii) { + LinkInput* in = LinkInputs_at(&l->inputs, ii); + ObjBuilder* ob = in->obj; + u32 total; + if (!ob || in->kind == LINK_INPUT_DSO_BYTES) continue; + total = obj_reloc_total(ob); + for (ri = 0; ri < total; ++ri) { + const Reloc* r = obj_reloc_at(ob, ri); + ObjSecId out_sec; + ObjSymId out_sym = OBJ_SYM_NONE; + u32 out_off; + if (!r || r->section_id == OBJ_SEC_NONE || + r->section_id >= maps[ii].nsection) + continue; + out_sec = maps[ii].section[r->section_id].out_sec; + if (out_sec == OBJ_SEC_NONE) continue; + if (r->sym != OBJ_SYM_NONE && r->sym < maps[ii].nsym) + out_sym = maps[ii].sym[r->sym]; + out_off = r->offset + maps[ii].section[r->section_id].delta; + obj_reloc_ex(out, out_sec, out_off, (RelocKind)r->kind, out_sym, + r->addend, r->has_explicit_addend, r->pair); + } + } +} + +static void rel_obj_cleanup(void* arg) { obj_free((ObjBuilder*)arg); } + +void link_emit_relocatable_writer(Linker* l, Writer* w) { + Heap* h; + ObjBuilder* out; + CompilerCleanup* cleanup; + RelInputMap* maps; + u32 ninputs; + RelGlobal* globals = NULL; + u32 nglobals = 0, globals_cap = 0; + SymHash globals_by_name; + + if (!l || !w) return; + h = l->heap; + + if (l->script) + compiler_panic(l->c, no_loc(), + "link -r: linker scripts are not supported"); + + link_ingest_archives(l); + ninputs = LinkInputs_count(&l->inputs); + maps = ninputs ? (RelInputMap*)h->alloc(h, sizeof(*maps) * ninputs, + _Alignof(RelInputMap)) + : NULL; + if (ninputs && !maps) + compiler_panic(l->c, no_loc(), "link -r: oom on input maps"); + if (ninputs) memset(maps, 0, sizeof(*maps) * ninputs); + + out = obj_new(l->c); + if (!out) compiler_panic(l->c, no_loc(), "link -r: obj_new failed"); + cleanup = compiler_defer(l->c, rel_obj_cleanup, out); + + symhash_init(&globals_by_name, h); + rel_copy_sections(l, out, maps, ninputs); + rel_scan_globals(l, &globals, &nglobals, &globals_cap, &globals_by_name); + rel_copy_symbols(l, out, maps, &globals_by_name, globals); + rel_copy_groups(l, out, maps); + rel_copy_relocs(l, out, maps); + + obj_finalize(out); + cfree_obj_builder_emit(out, w); + + if (cleanup) compiler_undefer(l->c, cleanup); + obj_free(out); + rel_globals_free(h, globals, globals_cap, &globals_by_name); + rel_maps_free(h, maps, ninputs); +} diff --git a/src/obj/obj.c b/src/obj/obj.c @@ -188,6 +188,16 @@ void obj_section_set_group(ObjBuilder* ob, ObjSecId id, ObjGroupId gid) { if (s && id != OBJ_SEC_NONE) s->group_id = gid; } +void obj_section_set_link_info(ObjBuilder* ob, ObjSecId id, ObjSecId link, + u32 info) { + Section* s; + if (id == OBJ_SEC_NONE) return; + s = Sections_at(&ob->sections, id); + if (!s) return; + s->link = link; + s->info = info; +} + void obj_section_set_ext(ObjBuilder* ob, ObjSecId id, ObjExtKind ek, u32 ext_type, u32 ext_flags) { Section* s; diff --git a/src/obj/obj.h b/src/obj/obj.h @@ -330,6 +330,7 @@ ObjSecId obj_section_ex(ObjBuilder*, Sym name, SecKind, SecSem, u16 flags, void obj_section_set_flags(ObjBuilder*, ObjSecId, u16 flags); void obj_section_set_align(ObjBuilder*, ObjSecId, u32 align); void obj_section_set_group(ObjBuilder*, ObjSecId, ObjGroupId); +void obj_section_set_link_info(ObjBuilder*, ObjSecId, ObjSecId link, u32 info); /* Set format-specific raw sh_type/sh_flags overrides (see Section.ext_type * comment). Zero ext_type means "no override". */ void obj_section_set_ext(ObjBuilder*, ObjSecId, ObjExtKind, u32 ext_type, diff --git a/test/driver/run.sh b/test/driver/run.sh @@ -92,6 +92,47 @@ else fail=$((fail + 1)) fi +cat > "$work/partial-main.c" <<'SRC' +int foo(void); +int _start(void) { return foo(); } +SRC +cat > "$work/partial-foo.c" <<'SRC' +int foo(void) { return 0; } +SRC + +if "$CFREE" cc -target x86_64-linux -c "$work/partial-main.c" \ + -o "$work/partial-main.o" > "$work/partial-main.out" \ + 2> "$work/partial-main.err" && + "$CFREE" cc -target x86_64-linux -c "$work/partial-foo.c" \ + -o "$work/partial-foo.o" > "$work/partial-foo.out" \ + 2> "$work/partial-foo.err"; then + : > "$work/partial.o" + chmod 0644 "$work/partial.o" + if (umask 077; "$CFREE" ld -r "$work/partial-main.o" \ + "$work/partial-foo.o" -o "$work/partial.o") \ + > "$work/ld-r.out" 2> "$work/ld-r.err"; then + check_mode "ld-r-output-mode" "$work/partial.o" 644 + if "$CFREE" ld "$work/partial.o" -o "$work/partial-exe" \ + > "$work/ld-r-final.out" 2> "$work/ld-r-final.err"; then + printf 'PASS %s\n' "ld-r-final-link" + pass=$((pass + 1)) + else + printf 'FAIL %s (final link of partial object failed)\n' \ + "ld-r-final-link" + sed 's/^/ | /' "$work/ld-r-final.err" + fail=$((fail + 1)) + fi + else + printf 'FAIL ld-r-output-mode (cfree ld -r failed)\n' + sed 's/^/ | /' "$work/ld-r.err" + fail=$((fail + 1)) + fi +else + printf 'FAIL ld-r-output-mode (setup compile failed)\n' + sed 's/^/ | /' "$work/partial-main.err" "$work/partial-foo.err" + fail=$((fail + 1)) +fi + rm -f "$work/main.o" if (cd "$work" && "$CFREE" cc -c main.c) \ > "$work/cc-c-default.out" 2> "$work/cc-c-default.err"; then