summaryrefslogtreecommitdiff
path: root/utils/mcpu-link.c
diff options
context:
space:
mode:
Diffstat (limited to 'utils/mcpu-link.c')
-rw-r--r--utils/mcpu-link.c1647
1 files changed, 1647 insertions, 0 deletions
diff --git a/utils/mcpu-link.c b/utils/mcpu-link.c
new file mode 100644
index 0000000..b2a9abe
--- /dev/null
+++ b/utils/mcpu-link.c
@@ -0,0 +1,1647 @@
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <libmcpu.h>
+
+#include "mcpu-link.h"
+#include "mcpu-melf.h"
+
+typedef struct lbuf
+{
+ __mpu_UNIT_t *p;
+ size_t n;
+ size_t cap;
+} lbuf;
+
+typedef struct gsym
+{
+ const char *name;
+ size_t obj;
+ size_t sym;
+ unsigned binding;
+} gsym;
+
+enum link_section_index
+{
+ LSEC_INIT = 0,
+ LSEC_TEXT,
+ LSEC_FINI,
+ LSEC_DATA,
+ LSEC_CONTROL,
+ LSEC_INPUT,
+ LSEC_OUTPUT,
+ LSEC_TRACE,
+ LSEC_BSS,
+ LSEC_COUNT
+};
+
+static int
+grow( void **p, size_t *cap, size_t need, size_t unit )
+{
+ size_t c = *cap ? *cap : 64;
+ void *q;
+
+ while( c < need )
+ {
+ if( c > SIZE_MAX / 2 )
+ return( -1 );
+ c *= 2;
+ }
+
+ q = realloc( *p, c *unit );
+ if( !q )
+ return( -1 );
+
+ *p = q;
+ *cap = c;
+ return( 0 );
+}
+
+static int
+put( lbuf *b, const void *p, size_t n )
+{
+ if( n > SIZE_MAX - b->n || grow( (void **)&b->p, &b->cap, b->n + n, 1 ) )
+ return( -1 );
+ if( n )
+ memcpy( b->p + b->n, p, n );
+ b->n += n;
+ return( 0 );
+}
+
+static int
+padalign( lbuf *b, __mpu_uint64_t a )
+{
+ if( !a || ( a & ( a - 1 ) ) != 0 )
+ return( -1 );
+
+ while( (__mpu_uint64_t)b->n % a )
+ {
+ __mpu_UNIT_t z = 0;
+ if( put( b, &z, 1 ) )
+ return( -1 );
+ }
+ return( 0 );
+}
+
+static int
+align_u64( __mpu_uint64_t v, __mpu_uint64_t a, __mpu_uint64_t *out )
+{
+ __mpu_uint64_t rem, add;
+
+ if( !a || ( a & ( a - 1 ) ) != 0 )
+ return( -1 );
+ rem = v % a;
+ add = rem ? a - rem : 0;
+ if( v > UINT64_MAX - add )
+ return( -1 );
+ *out = v + add;
+ return( 0 );
+}
+
+static int
+sidx( unsigned s )
+{
+ switch( s )
+ {
+ case CPU_OBJ_SEC_INIT: return( LSEC_INIT );
+ case CPU_OBJ_SEC_TEXT: return( LSEC_TEXT );
+ case CPU_OBJ_SEC_FINI: return( LSEC_FINI );
+ case CPU_OBJ_SEC_DATA: return( LSEC_DATA );
+ case CPU_OBJ_SEC_CONTROL: return( LSEC_CONTROL );
+ case CPU_OBJ_SEC_INPUT: return( LSEC_INPUT );
+ case CPU_OBJ_SEC_OUTPUT: return( LSEC_OUTPUT );
+ case CPU_OBJ_SEC_TRACE: return( LSEC_TRACE );
+ case CPU_OBJ_SEC_BSS: return( LSEC_BSS );
+ default: return( -1 );
+ }
+}
+
+static const __mpu_UNIT_t *
+objdata( const cpu_asm_object *o, int i, size_t *n,
+ __mpu_uint64_t *a, int *present)
+{
+ switch( i )
+ {
+ case LSEC_INIT:
+ *n = o->init_size;
+ *a = o->init_align;
+ *present = o->has_init;
+ return( o->init );
+ case LSEC_TEXT:
+ *n = o->text_size;
+ *a = o->text_align;
+ *present = o->has_text;
+ return( o->text );
+ case LSEC_FINI:
+ *n = o->fini_size;
+ *a = o->fini_align;
+ *present = o->has_fini;
+ return( o->fini );
+ case LSEC_DATA:
+ *n = o->data_size;
+ *a = o->data_align;
+ *present = o->has_data || o->data_size;
+ return( o->data );
+ case LSEC_CONTROL:
+ *n = o->control_size;
+ *a = o->control_align;
+ *present = o->has_control || o->control_size;
+ return( o->control );
+ case LSEC_INPUT:
+ *n = o->input_size;
+ *a = o->input_align;
+ *present = o->has_input || o->input_size;
+ return( o->input );
+ case LSEC_OUTPUT:
+ *n = o->output_size;
+ *a = o->output_align;
+ *present = o->has_output || o->output_size;
+ return( o->output );
+ case LSEC_TRACE:
+ *n = o->trace_size;
+ *a = o->trace_align;
+ *present = o->has_trace || o->trace_size;
+ return( o->trace );
+ default:
+ *n = 0;
+ *a = 1;
+ *present = 0;
+ return( 0 );
+ }
+}
+
+static int
+find_global( const gsym *g, size_t ng, const char *n, size_t *idx )
+{
+ size_t i;
+ for( i = 0; i < ng; ++i )
+ if( !strcmp( g[i].name, n ) )
+ {
+ if( idx )
+ *idx = i;
+ return( 0 );
+ }
+ return( -1 );
+}
+
+static int
+add_global( gsym **g, size_t *ng, size_t *cap,
+ const cpu_asm_object *objs, size_t oi, size_t si,
+ char *err, size_t es)
+{
+ const cpu_asm_symbol *s = &objs[oi].symbols[si];
+ size_t x;
+
+ if( !find_global( *g, *ng, s->name, &x ) )
+ {
+ gsym *old = &(*g)[x];
+ const cpu_asm_symbol *os = &objs[old->obj].symbols[old->sym];
+
+ if( os->binding == CPU_OBJ_BIND_GLOBAL && s->binding == CPU_OBJ_BIND_GLOBAL )
+ {
+ snprintf( err, es, "multiple definition of global symbol: %s", s->name );
+ return( -1 );
+ }
+ if( os->binding == CPU_OBJ_BIND_WEAK && s->binding == CPU_OBJ_BIND_GLOBAL )
+ {
+ old->obj = oi;
+ old->sym = si;
+ old->binding = s->binding;
+ }
+ /* A strong GLOBAL already beats WEAK; first WEAK wins deterministically. */
+ return( 0 );
+ }
+
+ if( grow( (void **)g, cap, *ng + 1, sizeof( **g ) ) )
+ {
+ snprintf( err, es, "out of memory" );
+ return( -1 );
+ }
+ ( *g )[*ng] = ( gsym )
+ {
+ s->name, oi, si, s->binding
+ };
+ ++*ng;
+ return( 0 );
+}
+
+typedef enum cpu_hll_link_kind
+{
+ CPU_HLL_LINK_NONE = 0,
+ CPU_HLL_LINK_PROGRAM,
+ CPU_HLL_LINK_MODEL
+} cpu_hll_link_kind;
+
+
+static int
+is_lifecycle_name( const char *name )
+{
+ return name && ( !strcmp( name, "_start" ) || !strcmp( name, "_life" ) ||
+ !strcmp( name, "_fini" ));
+}
+
+
+static cpu_hll_link_kind
+hll_link_kind( const cpu_asm_object *objs, size_t nobjs )
+{
+ size_t oi, si;
+ int program = 0, model = 0;
+
+ for( oi = 0; oi < nobjs; ++oi )
+ for( si = 0; si < objs[oi].symbol_count; ++si )
+ if( objs[oi].symbols[si].section != CPU_OBJ_SEC_UNDEF )
+ {
+ if( !strcmp(objs[oi].symbols[si].name,
+ "__mcpu_crt_hll_program") )
+ program = 1;
+ else if( !strcmp(objs[oi].symbols[si].name,
+ "__mcpu_crt_hll_model") )
+ model = 1;
+ }
+
+ if( program && !model )
+ return( CPU_HLL_LINK_PROGRAM );
+ if( model && !program )
+ return( CPU_HLL_LINK_MODEL );
+ return( CPU_HLL_LINK_NONE );
+}
+
+
+static int
+is_runtime_hook_name( const char *name )
+{
+ if( name == NULL )
+ return( 0 );
+
+ return( !strcmp(name, "__mcpu_libc_init") ||
+ !strcmp(name, "__mcpu_init_array") ||
+ !strcmp(name, "__mcpu_fini_array") ||
+ !strcmp(name, "__mcpu_libc_fini") );
+}
+
+
+static int
+is_nonpreemptible_name( const char *name, cpu_hll_link_kind kind )
+{
+ if( is_lifecycle_name(name) )
+ return( 1 );
+
+ if( name != NULL && !strncmp(name, "__mcpu_crt_", 11) )
+ return( 1 );
+
+ if( kind == CPU_HLL_LINK_PROGRAM )
+ return( name && (!strcmp(name, "main") || !strcmp(name, "fini")) );
+
+ if( kind == CPU_HLL_LINK_MODEL )
+ return( name && (!strcmp(name, "init") || !strcmp(name, "life") ||
+ !strcmp(name, "fini")) );
+
+ return( 0 );
+}
+
+static int
+find_dyn_symbol( const cpu_asm_image *im, const char *name, size_t *idx )
+{
+ size_t i;
+ for( i = 0; i < im->dyn_symbol_count; ++i )
+ if( !strcmp( im->dyn_symbols[i].name, name ) )
+ {
+ if( idx ) * idx = i;
+ return( 0 );
+ }
+ return( -1 );
+}
+
+static int
+add_dyn_symbol( cpu_asm_image *im, const char *name, unsigned section,
+ unsigned binding, unsigned type, __mpu_uint64_t value,
+ __mpu_uint64_t size, size_t *idx, char *err, size_t es)
+{
+ cpu_dyn_symbol *q;
+ size_t i;
+
+ if( !find_dyn_symbol( im, name, &i ) )
+ {
+ cpu_dyn_symbol *d = &im->dyn_symbols[i];
+ if( d->section == CPU_OBJ_SEC_UNDEF && section != CPU_OBJ_SEC_UNDEF )
+ {
+ d->section = section;
+ d->value = value;
+ d->size = size;
+ d->type = type;
+ d->binding = binding;
+ }
+ else if( d->section == CPU_OBJ_SEC_UNDEF && binding == CPU_OBJ_BIND_GLOBAL )
+ d->binding = CPU_OBJ_BIND_GLOBAL;
+ if( idx ) * idx = i;
+ return( 0 );
+ }
+
+ q = (cpu_dyn_symbol *)realloc( im->dyn_symbols,
+ (im->dyn_symbol_count + 1) * sizeof(*q));
+ if( !q )
+ {
+ snprintf( err, es, "out of memory" );
+ return( -1 );
+ }
+ im->dyn_symbols = q;
+ memset( &im->dyn_symbols[im->dyn_symbol_count], 0,
+ sizeof(im->dyn_symbols[im->dyn_symbol_count]));
+ im->dyn_symbols[im->dyn_symbol_count].name = strdup( name );
+ if( !im->dyn_symbols[im->dyn_symbol_count].name )
+ {
+ snprintf( err, es, "out of memory" );
+ return( -1 );
+ }
+ im->dyn_symbols[im->dyn_symbol_count].section = section;
+ im->dyn_symbols[im->dyn_symbol_count].binding = binding;
+ im->dyn_symbols[im->dyn_symbol_count].type = type;
+ im->dyn_symbols[im->dyn_symbol_count].value = value;
+ im->dyn_symbols[im->dyn_symbol_count].size = size;
+ if( idx ) * idx = im->dyn_symbol_count;
+ ++im->dyn_symbol_count;
+ return( 0 );
+}
+
+static int
+add_dyn_reloc( cpu_asm_image *im, unsigned section, unsigned type,
+ size_t symbol_index, __mpu_uint64_t offset,
+ __mpu_int64_t addend, char *err, size_t es)
+{
+ cpu_dyn_reloc *q = (cpu_dyn_reloc *)realloc(
+ im->dyn_relocs, (im->dyn_reloc_count + 1) * sizeof(*q));
+ if( !q )
+ {
+ snprintf( err, es, "out of memory" );
+ return( -1 );
+ }
+ im->dyn_relocs = q;
+ im->dyn_relocs[im->dyn_reloc_count] =
+ ( cpu_dyn_reloc )
+ {
+ section, type, symbol_index, offset, addend
+ };
+ ++im->dyn_reloc_count;
+ return( 0 );
+}
+
+static int
+add_signed( __mpu_uint64_t b, __mpu_int64_t a, __mpu_uint64_t *out )
+{
+ if( a >= 0 )
+ {
+ __mpu_uint64_t u = (__mpu_uint64_t)a;
+ if( b > UINT64_MAX - u )
+ return( -1 );
+ *out = b + u;
+ }
+ else
+ {
+ __mpu_uint64_t u = (__mpu_uint64_t)(-(a + 1)) + 1;
+ if( b < u )
+ return( -1 );
+ *out = b - u;
+ }
+ return( 0 );
+}
+
+static int
+linker_end_pointer( __mpu_int64_t addend, __mpu_uint64_t *ptr )
+{
+ __mpu_uint64_t off;
+
+ if( add_signed( CPU_HEAP_BEGIN, addend, &off ) ||
+ off > CPU_PTR_OFFSET_MASK ||
+ __cpu_ptr_make( ptr, CPU_PTR_SEG_HEAP, off ))
+ return( -1 );
+
+ return( 0 );
+}
+
+static int
+strong_lifecycle( const cpu_asm_symbol *s, const char *n, char *err, size_t es )
+{
+ if( s->binding != CPU_OBJ_BIND_GLOBAL || s->type != CPU_OBJ_TYPE_FUNC )
+ {
+ snprintf( err, es, "lifecycle symbol %s must be strong GLOBAL/FUNC", n );
+ return( -1 );
+ }
+ return( 0 );
+}
+
+static int
+source_field( cpu_asm_object *objs, size_t oi, const __mpu_uint64_t *bases,
+ int srcidx, __mpu_uint64_t rel_off,
+ lbuf *program, lbuf space[5],
+ __mpu_UNIT_t **dst, size_t *dsz, __mpu_uint64_t *field,
+ char *err, size_t es)
+{
+ (void)objs;
+ if( srcidx < 0 || srcidx == LSEC_BSS )
+ {
+ snprintf( err, es, "bad relocation source section" );
+ return( -1 );
+ }
+
+ if( bases[oi *LSEC_COUNT + (size_t)srcidx] > UINT64_MAX - rel_off )
+ {
+ snprintf( err, es, "relocation field address overflow" );
+ return( -1 );
+ }
+ *field = bases[oi * LSEC_COUNT + (size_t)srcidx] + rel_off;
+
+ if( srcidx < 3 )
+ {
+ *dst = program->p;
+ *dsz = program->n;
+ }
+ else
+ {
+ int spidx;
+ switch( srcidx )
+ {
+ case LSEC_DATA: spidx = 0;
+ break;
+ case LSEC_CONTROL: spidx = 1;
+ break;
+ case LSEC_INPUT: spidx = 2;
+ break;
+ case LSEC_OUTPUT: spidx = 3;
+ break;
+ case LSEC_TRACE: spidx = 4;
+ break;
+ default:
+ snprintf( err, es, "bad relocation source section" );
+ return( -1 );
+ }
+ *dst = space[spidx].p;
+ *dsz = space[spidx].n;
+ }
+
+ if( *field > *dsz || 8 > *dsz - (size_t)*field )
+ {
+ snprintf( err, es, "relocation field outside section" );
+ return( -1 );
+ }
+ return( 0 );
+}
+
+int
+cpu_link_objects_mode( cpu_asm_object *objs, size_t nobjs, cpu_asm_image *im,
+ cpu_link_mode mode, char *err, size_t es)
+{
+ lbuf program = {0};
+ lbuf space[5] = {{0}}; /* DATA, CONTROL, INPUT, OUTPUT, TRACE file images */
+ __mpu_uint64_t *bases = 0;
+ __mpu_uint64_t sec_start[LSEC_COUNT] = {0};
+ __mpu_uint64_t sec_end[LSEC_COUNT] = {0};
+ __mpu_uint64_t sec_align[LSEC_COUNT] =
+ {
+ 1, 1, 1, 1, 1, 1, 1, 1, 1
+ };
+ int sec_present[LSEC_COUNT] = {0};
+ __mpu_uint64_t data_mem_size = 0;
+ gsym *globals = 0;
+ size_t ng = 0, cg = 0, oi, si, ri;
+ cpu_hll_link_kind hll_kind;
+ int rc = -1;
+
+ if( !objs || !nobjs || !im )
+ return( -1 );
+ memset( im, 0, sizeof( *im ) );
+
+ if( mode != CPU_LINK_STATIC_EXEC && mode != CPU_LINK_DYNAMIC_EXEC &&
+ mode != CPU_LINK_SHARED)
+ {
+ snprintf( err, es, "invalid link mode" );
+ goto done;
+ }
+
+ hll_kind = hll_link_kind( objs, nobjs );
+
+ if( mode == CPU_LINK_SHARED )
+ {
+ for( oi = 0; oi < nobjs; ++oi )
+ {
+ size_t sj;
+ if( objs[oi].has_init || objs[oi].init_size ||
+ objs[oi].has_fini || objs[oi].fini_size ||
+ objs[oi].has_control || objs[oi].control_size ||
+ objs[oi].has_input || objs[oi].input_size ||
+ objs[oi].has_output || objs[oi].output_size ||
+ objs[oi].has_trace || objs[oi].trace_size)
+ {
+ snprintf( err, es,
+ "shared object may contain only .text/.data/.bss allocated sections");
+ goto done;
+ }
+ for( sj = 0; sj < objs[oi].symbol_count; ++sj )
+ if( objs[oi].symbols[sj].section != CPU_OBJ_SEC_UNDEF &&
+ is_lifecycle_name( objs[oi].symbols[sj].name ))
+ {
+ snprintf( err, es, "shared object must not define lifecycle symbol %s",
+ objs[oi].symbols[sj].name);
+ goto done;
+ }
+ }
+ }
+
+ if( nobjs > SIZE_MAX / LSEC_COUNT )
+ {
+ snprintf( err, es, "too many input objects" );
+ goto done;
+ }
+ bases = (__mpu_uint64_t *)calloc( nobjs *LSEC_COUNT, sizeof( *bases ) );
+ if( !bases )
+ {
+ snprintf( err, es, "out of memory" );
+ goto done;
+ }
+
+ /* DATA owns one NULL guard if either .data or .bss makes DATA exist. */
+ {
+ int data_needed = 0;
+ for( oi = 0; oi < nobjs; ++oi )
+ if( objs[oi].has_data || objs[oi].data_size ||
+ objs[oi].has_bss || objs[oi].bss_size)
+ {
+ data_needed = 1;
+ break;
+ }
+
+ if( data_needed && mode != CPU_LINK_SHARED )
+ while( space[0].n < CPU_DATA_NULL_GUARD_SIZE )
+ {
+ __mpu_UNIT_t z = 0;
+ if( put( &space[0], &z, 1 ) )
+ {
+ snprintf( err, es, "out of memory" );
+ goto done;
+ }
+ }
+ }
+
+ /* PROGRAM: all .init contributions, then all .text, then all .fini. */
+ for( si = LSEC_INIT; si <= LSEC_FINI; ++si )
+ {
+ int first = 1;
+ for( oi = 0; oi < nobjs; ++oi )
+ {
+ const __mpu_UNIT_t *p;
+ size_t n;
+ __mpu_uint64_t a;
+ int present;
+
+ p = objdata( &objs[oi], (int)si, &n, &a, &present );
+ if( !present )
+ continue;
+ if( !a )
+ a = 1;
+ if( !cpu_melf_is_pow2( a ) )
+ {
+ snprintf( err, es, "invalid PROGRAM contribution alignment" );
+ goto done;
+ }
+ sec_present[si] = 1;
+ if( a > sec_align[si] )
+ sec_align[si] = a;
+ if( padalign( &program, a ) )
+ {
+ snprintf( err, es, "cannot align PROGRAM contribution" );
+ goto done;
+ }
+ if( first )
+ {
+ sec_start[si] = program.n;
+ first = 0;
+ }
+ bases[oi * LSEC_COUNT + si] = program.n;
+ if( put( &program, p, n ) )
+ {
+ snprintf( err, es, "out of memory" );
+ goto done;
+ }
+ }
+ sec_end[si] = program.n;
+ }
+
+ /* File-backed data-like spaces. */
+ for( si = LSEC_DATA; si <= LSEC_TRACE; ++si )
+ {
+ lbuf *b;
+ int first = 1;
+
+ if( si == LSEC_DATA ) b = &space[0];
+ else if( si == LSEC_CONTROL ) b = &space[1];
+ else if( si == LSEC_INPUT ) b = &space[2];
+ else if( si == LSEC_OUTPUT ) b = &space[3];
+ else b = &space[4];
+
+ for( oi = 0; oi < nobjs; ++oi )
+ {
+ const __mpu_UNIT_t *p;
+ size_t n;
+ __mpu_uint64_t a;
+ int present;
+
+ p = objdata( &objs[oi], (int)si, &n, &a, &present );
+ if( !present )
+ continue;
+ if( !a )
+ a = 1;
+ if( !cpu_melf_is_pow2( a ) )
+ {
+ snprintf( err, es, "invalid section contribution alignment" );
+ goto done;
+ }
+ sec_present[si] = 1;
+ if( a > sec_align[si] )
+ sec_align[si] = a;
+ if( padalign( b, a ) )
+ {
+ snprintf( err, es, "cannot align output section" );
+ goto done;
+ }
+ if( first )
+ {
+ sec_start[si] = b->n;
+ first = 0;
+ }
+ bases[oi * LSEC_COUNT + si] = b->n;
+ if( put( b, p, n ) )
+ {
+ snprintf( err, es, "out of memory" );
+ goto done;
+ }
+ }
+ sec_end[si] = b->n;
+ }
+
+ /* .bss follows .data inside DATA but consumes no executable file bytes. */
+ data_mem_size = space[0].n;
+ {
+ int first = 1;
+ for( oi = 0; oi < nobjs; ++oi )
+ {
+ __mpu_uint64_t a = objs[oi].bss_align ? objs[oi].bss_align : 1;
+ __mpu_uint64_t aligned;
+ int present = objs[oi].has_bss || objs[oi].bss_size;
+
+ if( !present )
+ continue;
+ if( !cpu_melf_is_pow2( a ) || align_u64( data_mem_size, a, &aligned ) )
+ {
+ snprintf( err, es, "cannot align .bss contribution" );
+ goto done;
+ }
+ data_mem_size = aligned;
+ sec_present[LSEC_BSS] = 1;
+ if( a > sec_align[LSEC_BSS] )
+ sec_align[LSEC_BSS] = a;
+ if( first )
+ {
+ sec_start[LSEC_BSS] = data_mem_size;
+ first = 0;
+ }
+ bases[oi * LSEC_COUNT + LSEC_BSS] = data_mem_size;
+ if( (__mpu_uint64_t)objs[oi].bss_size > UINT64_MAX - data_mem_size )
+ {
+ snprintf( err, es, ".bss layout overflow" );
+ goto done;
+ }
+ data_mem_size += (__mpu_uint64_t)objs[oi].bss_size;
+ }
+ sec_end[LSEC_BSS] = data_mem_size;
+ }
+
+ if( data_mem_size > CPU_PTR_OFFSET_MASK || data_mem_size > SIZE_MAX )
+ {
+ snprintf( err, es, "final DATA space exceeds supported range" );
+ goto done;
+ }
+
+ /* _end is defined by mcpu-ld as HEAP:CPU_HEAP_BEGIN. */
+ for( oi = 0; oi < nobjs; ++oi )
+ for( si = 0; si < objs[oi].symbol_count; ++si )
+ if( objs[oi].symbols[si].section != CPU_OBJ_SEC_UNDEF &&
+ !strcmp( objs[oi].symbols[si].name, "_end" ))
+ {
+ snprintf( err, es, "_end is a reserved linker-defined symbol" );
+ goto done;
+ }
+
+ /* Collect defined non-local symbols. */
+ for( oi = 0; oi < nobjs; ++oi )
+ for( si = 0; si < objs[oi].symbol_count; ++si )
+ {
+ cpu_asm_symbol *s = &objs[oi].symbols[si];
+ if( s->section != CPU_OBJ_SEC_UNDEF &&
+ (s->binding == CPU_OBJ_BIND_GLOBAL || s->binding == CPU_OBJ_BIND_WEAK))
+ if( add_global( &globals, &ng, &cg, objs, oi, si, err, es ) )
+ goto done;
+ }
+
+ /*
+ * ET_DYN local relocations need startup rebasing too. Represent their
+ * targets by LOCAL dynamic symbols so the runtime linker can add the
+ * module segment base without making them preemptible.
+ */
+ if( mode == CPU_LINK_SHARED )
+ for( oi = 0; oi < nobjs; ++oi )
+ for( ri = 0; ri < objs[oi].reloc_count; ++ri )
+ {
+ cpu_asm_reloc *r = &objs[oi].relocs[ri];
+ cpu_asm_symbol *s;
+ int ix;
+ __mpu_uint64_t v;
+ char lname[64];
+
+ if( r->symbol_index >= objs[oi].symbol_count )
+ {
+ snprintf( err, es, "bad relocation symbol index" );
+ goto done;
+ }
+ s = &objs[oi].symbols[r->symbol_index];
+ if( s->binding != CPU_OBJ_BIND_LOCAL || s->section == CPU_OBJ_SEC_UNDEF )
+ continue;
+
+ ix = sidx( s->section );
+ if( ix < 0 || bases[oi *LSEC_COUNT + (size_t)ix] < sec_start[ix] ||
+ s->value > UINT64_MAX - bases[oi * LSEC_COUNT + (size_t)ix])
+ {
+ snprintf( err, es, "bad local dynamic symbol placement: %s", s->name );
+ goto done;
+ }
+ v = bases[oi * LSEC_COUNT + (size_t)ix] + s->value - sec_start[ix];
+ snprintf( lname, sizeof( lname ), ".L$%zu$%zu", oi, r->symbol_index );
+ if( add_dyn_symbol( im, lname, s->section, CPU_OBJ_BIND_LOCAL, s->type,
+ v, s->size, 0, err, es))
+ goto done;
+ }
+
+ /* Build ABI-v2/ABI-v3 runtime symbol definitions before retaining relocations. */
+ if( mode != CPU_LINK_STATIC_EXEC )
+ for( si = 0; si < ng; ++si )
+ {
+ cpu_asm_symbol *s = &objs[globals[si].obj].symbols[globals[si].sym];
+ int ix = sidx( s->section );
+ __mpu_uint64_t v;
+ if( is_nonpreemptible_name(s->name, hll_kind) )
+ continue;
+ if( ix < 0 || bases[globals[si].obj *LSEC_COUNT + (size_t)ix] < sec_start[ix] ||
+ s->value > UINT64_MAX - bases[globals[si].obj * LSEC_COUNT + (size_t)ix])
+ {
+ snprintf( err, es, "bad dynamic symbol placement: %s", s->name );
+ goto done;
+ }
+ v = bases[globals[si].obj * LSEC_COUNT + (size_t)ix] + s->value - sec_start[ix];
+ if( add_dyn_symbol( im, s->name, s->section, s->binding, s->type,
+ v, s->size, 0, err, es))
+ goto done;
+ }
+
+ /* Apply static relocations, or retain preemptible ABI-v2/ABI-v3 relocations. */
+ for( oi = 0; oi < nobjs; ++oi )
+ for( ri = 0; ri < objs[oi].reloc_count; ++ri )
+ {
+ cpu_asm_reloc *r = &objs[oi].relocs[ri];
+ cpu_asm_symbol *orig, *s;
+ size_t toi = oi, tsi = r->symbol_index, gi;
+ int tidx, srcidx;
+ unsigned tseg;
+ __mpu_uint64_t sval, final, field, ptr;
+ __mpu_UNIT_t *dst;
+ size_t dsz;
+
+ if( tsi >= objs[oi].symbol_count )
+ {
+ snprintf( err, es, "bad relocation symbol index" );
+ goto done;
+ }
+ orig = &objs[oi].symbols[tsi];
+ s = orig;
+ srcidx = sidx( r->section );
+ if( srcidx < 0 || srcidx == LSEC_BSS )
+ {
+ snprintf( err, es, "bad relocation source section" );
+ goto done;
+ }
+
+ /* _end is an executable-only, non-preemptible linker-defined symbol. */
+ if( !strcmp( orig->name, "_end" ) )
+ {
+ if( mode == CPU_LINK_SHARED )
+ {
+ snprintf( err, es, "_end is not defined for ET_DYN shared objects" );
+ goto done;
+ }
+ if( r->type != CPU_RELOC_PTR64_CODE && r->type != CPU_RELOC_PTR64_DATA )
+ {
+ snprintf( err, es, "_end requires a PTR64 relocation" );
+ goto done;
+ }
+ if( linker_end_pointer( r->addend, &ptr ) )
+ {
+ snprintf( err, es, "_end relocation overflow" );
+ goto done;
+ }
+ if( source_field( objs, oi, bases, srcidx, r->offset,
+ &program, space, &dst, &dsz, &field, err, es))
+ goto done;
+ if( r->type == CPU_RELOC_PTR64_CODE )
+ __cpu_code_put_u64( dst + (size_t)field, ptr );
+ else
+ __cpu_data_put_u64( dst + (size_t)field, ptr );
+ continue;
+ }
+
+ /*
+ * A local ET_DYN reference is not preemptible, but its section-relative
+ * value still needs the shared object's runtime segment base.
+ */
+ if( mode == CPU_LINK_SHARED && orig->binding == CPU_OBJ_BIND_LOCAL &&
+ orig->section != CPU_OBJ_SEC_UNDEF)
+ {
+ size_t di;
+ __mpu_uint64_t roff;
+ char lname[64];
+
+ if( source_field( objs, oi, bases, srcidx, r->offset,
+ &program, space, &dst, &dsz, &field, err, es))
+ goto done;
+ if( field < sec_start[srcidx] )
+ {
+ snprintf( err, es, "local dynamic relocation precedes output section" );
+ goto done;
+ }
+ roff = field - sec_start[srcidx];
+ snprintf( lname, sizeof( lname ), ".L$%zu$%zu", oi, r->symbol_index );
+ if( find_dyn_symbol( im, lname, &di ) )
+ {
+ snprintf( err, es, "missing local dynamic symbol: %s", orig->name );
+ goto done;
+ }
+ if( add_dyn_reloc( im, r->section, r->type, di, roff,
+ r->addend, err, es))
+ goto done;
+ continue;
+ }
+
+ /*
+ * ABI-v2/ABI-v3 rule: every reference through a DEFAULT GLOBAL/WEAK name is
+ * preemptible, even when this output itself defines the name. The
+ * startup linker in mcpu-run chooses the final definition. Lifecycle
+ * entry symbols are deliberately non-preemptible.
+ */
+ if( mode != CPU_LINK_STATIC_EXEC &&
+ (orig->binding == CPU_OBJ_BIND_GLOBAL || orig->binding == CPU_OBJ_BIND_WEAK) &&
+ !is_nonpreemptible_name(orig->name, hll_kind) )
+ {
+ size_t di;
+ __mpu_uint64_t roff;
+
+ if( source_field( objs, oi, bases, srcidx, r->offset,
+ &program, space, &dst, &dsz, &field, err, es))
+ goto done;
+ if( field < sec_start[srcidx] )
+ {
+ snprintf( err, es, "dynamic relocation precedes output section" );
+ goto done;
+ }
+ roff = field - sec_start[srcidx];
+
+ if( !find_global( globals, ng, orig->name, &gi ) )
+ {
+ cpu_asm_symbol *def = &objs[globals[gi].obj].symbols[globals[gi].sym];
+ int dix = sidx( def->section );
+ __mpu_uint64_t dv;
+ if( dix < 0 || bases[globals[gi].obj *LSEC_COUNT + (size_t)dix] < sec_start[dix] ||
+ def->value > UINT64_MAX - bases[globals[gi].obj * LSEC_COUNT + (size_t)dix])
+ {
+ snprintf( err, es, "bad dynamic symbol placement: %s", def->name );
+ goto done;
+ }
+ dv = bases[globals[gi].obj * LSEC_COUNT + (size_t)dix] + def->value - sec_start[dix];
+ if( add_dyn_symbol( im, def->name, def->section, def->binding, def->type,
+ dv, def->size, &di, err, es))
+ goto done;
+ }
+ else if( add_dyn_symbol( im, orig->name, CPU_OBJ_SEC_UNDEF,
+ orig->binding, orig->type, 0, 0,
+ &di, err, es))
+ goto done;
+
+ if( add_dyn_reloc( im, r->section, r->type, di, roff,
+ r->addend, err, es))
+ goto done;
+ continue;
+ }
+
+ if( s->section == CPU_OBJ_SEC_UNDEF )
+ {
+ if( find_global( globals, ng, s->name, &gi ) )
+ {
+ if( s->binding == CPU_OBJ_BIND_WEAK )
+ {
+ if( ( r->type == CPU_RELOC_PTR64_CODE ||
+ r->type == CPU_RELOC_PTR64_DATA ||
+ r->type == CPU_RELOC_CPTR64_DATA) && r->addend == 0)
+ {
+ if( source_field( objs, oi, bases, srcidx, r->offset,
+ &program, space, &dst, &dsz, &field, err, es))
+ goto done;
+ if( r->type == CPU_RELOC_PTR64_CODE )
+ __cpu_code_put_u64( dst + (size_t)field, 0 );
+ else
+ __cpu_data_put_u64( dst + (size_t)field, 0 );
+ continue;
+ }
+ snprintf( err, es,
+ "unresolved weak symbol cannot be used by this relocation: %s",
+ s->name);
+ goto done;
+ }
+ snprintf( err, es, "undefined symbol: %s", s->name );
+ goto done;
+ }
+
+ toi = globals[gi].obj;
+ tsi = globals[gi].sym;
+ s = &objs[toi].symbols[tsi];
+ }
+
+ tidx = sidx( s->section );
+ if( tidx < 0 )
+ {
+ snprintf( err, es, "bad relocation target section" );
+ goto done;
+ }
+
+ if( s->value > UINT64_MAX - bases[toi *LSEC_COUNT + (size_t)tidx] )
+ {
+ snprintf( err, es, "symbol address overflow: %s", s->name );
+ goto done;
+ }
+ sval = bases[toi * LSEC_COUNT + (size_t)tidx] + s->value;
+ if( add_signed( sval, r->addend, &final ) )
+ {
+ snprintf( err, es, "relocation overflow: %s", s->name );
+ goto done;
+ }
+
+ if( source_field( objs, oi, bases, srcidx, r->offset,
+ &program, space, &dst, &dsz, &field, err, es))
+ goto done;
+
+ tseg = cpu_obj_sec_segment( s->section );
+ switch( r->type )
+ {
+ case CPU_RELOC_CODE64:
+ if( tseg != CPU_PTR_SEG_PROGRAM || final >= program.n )
+ {
+ snprintf( err, es, "CODE64 target is not valid PROGRAM: %s", s->name );
+ goto done;
+ }
+ __cpu_code_put_u64( dst + (size_t)field, final );
+ break;
+
+ case CPU_RELOC_DATA64:
+ if( tseg != CPU_PTR_SEG_DATA || final > data_mem_size )
+ {
+ snprintf( err, es, "ADDR64 target is not valid DATA: %s", s->name );
+ goto done;
+ }
+ __cpu_code_put_u64( dst + (size_t)field, final );
+ break;
+
+ case CPU_RELOC_PTR64_CODE:
+ if( final > CPU_PTR_OFFSET_MASK || __cpu_ptr_make( &ptr, tseg, final ) )
+ {
+ snprintf( err, es, "PTR64 target cannot be encoded: %s", s->name );
+ goto done;
+ }
+ __cpu_code_put_u64( dst + (size_t)field, ptr );
+ break;
+
+ case CPU_RELOC_PTR64_DATA:
+ if( final > CPU_PTR_OFFSET_MASK || __cpu_ptr_make( &ptr, tseg, final ) )
+ {
+ snprintf( err, es, "PTR64 target cannot be encoded: %s", s->name );
+ goto done;
+ }
+ __cpu_data_put_u64( dst + (size_t)field, ptr );
+ break;
+
+ case CPU_RELOC_CPTR64_DATA:
+ if( tseg != CPU_PTR_SEG_PROGRAM )
+ {
+ snprintf( err, es, ".cptr target is not PROGRAM: %s", s->name );
+ goto done;
+ }
+ if( final > CPU_PTR_OFFSET_MASK ||
+ __cpu_ptr_make( &ptr, CPU_PTR_SEG_PROGRAM, final ))
+ {
+ snprintf( err, es, "PROGRAM PTR64 cannot be encoded: %s", s->name );
+ goto done;
+ }
+ __cpu_data_put_u64( dst + (size_t)field, ptr );
+ break;
+
+ default:
+ snprintf( err, es, "unknown relocation type %u", r->type );
+ goto done;
+ }
+ }
+
+ /* Lifecycle is a property of an executable, never of ET_DYN. */
+ if( mode != CPU_LINK_SHARED )
+ {
+ size_t gs, gl, gf;
+ cpu_asm_symbol *ss, *sl, *sf = 0;
+ __mpu_uint64_t sv, lv, fv = 0;
+ int six, lix, fix;
+ int have_init = sec_present[LSEC_INIT];
+ int have_fini = sec_present[LSEC_FINI];
+
+ if( find_global( globals, ng, "_start", &gs ) )
+ {
+ snprintf( err, es, "linked executable requires _start" );
+ goto done;
+ }
+ if( find_global( globals, ng, "_life", &gl ) )
+ {
+ snprintf( err, es, "linked executable requires _life" );
+ goto done;
+ }
+
+ ss = &objs[globals[gs].obj].symbols[globals[gs].sym];
+ sl = &objs[globals[gl].obj].symbols[globals[gl].sym];
+ if( strong_lifecycle( ss, "_start", err, es ) ||
+ strong_lifecycle( sl, "_life", err, es ))
+ goto done;
+
+ six = sidx( ss->section );
+ lix = sidx( sl->section );
+ if( six < 0 || lix < 0 )
+ {
+ snprintf( err, es, "bad lifecycle section" );
+ goto done;
+ }
+ sv = bases[globals[gs].obj * LSEC_COUNT + (size_t)six] + ss->value;
+ lv = bases[globals[gl].obj * LSEC_COUNT + (size_t)lix] + sl->value;
+
+ if( have_init )
+ {
+ if( ss->section != CPU_OBJ_SEC_INIT || sl->section != CPU_OBJ_SEC_TEXT )
+ {
+ snprintf( err, es,
+ "with .init, _start must be in .init and _life in .text");
+ goto done;
+ }
+ }
+ else
+ {
+ if( ss->section != CPU_OBJ_SEC_TEXT || sl->section != CPU_OBJ_SEC_TEXT || sv != lv )
+ {
+ snprintf( err, es,
+ "without .init, _start and _life must have the same .text address");
+ goto done;
+ }
+ }
+
+ if( have_fini )
+ {
+ if( find_global( globals, ng, "_fini", &gf ) )
+ {
+ snprintf( err, es, ".fini requires _fini" );
+ goto done;
+ }
+ sf = &objs[globals[gf].obj].symbols[globals[gf].sym];
+ if( strong_lifecycle( sf, "_fini", err, es ) ||
+ sf->section != CPU_OBJ_SEC_FINI)
+ {
+ snprintf( err, es, "_fini must be strong GLOBAL/FUNC in .fini" );
+ goto done;
+ }
+ fix = sidx( sf->section );
+ fv = bases[globals[gf].obj * LSEC_COUNT + (size_t)fix] + sf->value;
+ }
+ else if( !find_global( globals, ng, "_fini", &gf ) )
+ {
+ snprintf( err, es, "_fini is defined but no .fini section exists" );
+ goto done;
+ }
+
+ im->has_start = 1;
+ im->has_life = 1;
+ im->has_init = have_init;
+ im->has_fini = have_fini;
+ im->start_entry = sv;
+ im->life_entry = lv;
+ im->init_entry = have_init ? sv : 0;
+ im->fini_entry = have_fini ? fv : 0;
+ }
+
+ im->code = program.p;
+ im->code_size = program.n;
+ program.p = 0;
+ im->data = space[0].p;
+ im->data_size = space[0].n;
+ space[0].p = 0;
+ im->data_mem_size = (size_t)data_mem_size;
+ im->control = space[1].p;
+ im->control_size = space[1].n;
+ space[1].p = 0;
+ im->input = space[2].p;
+ im->input_size = space[2].n;
+ space[2].p = 0;
+ im->output = space[3].p;
+ im->output_size = space[3].n;
+ space[3].p = 0;
+ im->trace = space[4].p;
+ im->trace_size = space[4].n;
+ space[4].p = 0;
+
+ im->init_present = sec_present[LSEC_INIT];
+ im->init_offset = sec_start[LSEC_INIT];
+ im->init_size = sec_end[LSEC_INIT] - sec_start[LSEC_INIT];
+ im->init_align = sec_align[LSEC_INIT];
+
+ im->text_present = sec_present[LSEC_TEXT];
+ im->text_offset = sec_start[LSEC_TEXT];
+ im->text_size = sec_end[LSEC_TEXT] - sec_start[LSEC_TEXT];
+ im->text_align = sec_align[LSEC_TEXT];
+
+ im->fini_present = sec_present[LSEC_FINI];
+ im->fini_offset = sec_start[LSEC_FINI];
+ im->fini_size = sec_end[LSEC_FINI] - sec_start[LSEC_FINI];
+ im->fini_align = sec_align[LSEC_FINI];
+
+ im->data_present = sec_present[LSEC_DATA];
+ im->data_offset = sec_start[LSEC_DATA];
+ im->data_section_size = sec_end[LSEC_DATA] - sec_start[LSEC_DATA];
+ im->data_align = sec_align[LSEC_DATA];
+
+ im->bss_present = sec_present[LSEC_BSS];
+ im->bss_offset = sec_start[LSEC_BSS];
+ im->bss_size = sec_end[LSEC_BSS] - sec_start[LSEC_BSS];
+ im->bss_align = sec_align[LSEC_BSS];
+
+ im->control_present = sec_present[LSEC_CONTROL];
+ im->control_offset = sec_start[LSEC_CONTROL];
+ im->control_section_size = sec_end[LSEC_CONTROL] - sec_start[LSEC_CONTROL];
+ im->control_align = sec_align[LSEC_CONTROL];
+
+ im->input_present = sec_present[LSEC_INPUT];
+ im->input_offset = sec_start[LSEC_INPUT];
+ im->input_section_size = sec_end[LSEC_INPUT] - sec_start[LSEC_INPUT];
+ im->input_align = sec_align[LSEC_INPUT];
+
+ im->output_present = sec_present[LSEC_OUTPUT];
+ im->output_offset = sec_start[LSEC_OUTPUT];
+ im->output_section_size = sec_end[LSEC_OUTPUT] - sec_start[LSEC_OUTPUT];
+ im->output_align = sec_align[LSEC_OUTPUT];
+
+ im->trace_present = sec_present[LSEC_TRACE];
+ im->trace_offset = sec_start[LSEC_TRACE];
+ im->trace_section_size = sec_end[LSEC_TRACE] - sec_start[LSEC_TRACE];
+ im->trace_align = sec_align[LSEC_TRACE];
+
+ rc = 0;
+
+done:
+ free( program.p );
+ for( si = 0; si < 5; ++si )
+ free( space[si].p );
+ free( bases );
+ free( globals );
+ if( rc )
+ cpu_asm_image_free( im );
+ return( rc );
+}
+
+
+static int
+reloc_append_symbol( cpu_asm_object *o, const cpu_asm_symbol *s,
+ unsigned section, __mpu_uint64_t value,
+ size_t *idx, char *err, size_t es )
+{
+ cpu_asm_symbol *q;
+
+ q = (cpu_asm_symbol *)realloc( o->symbols,
+ (o->symbol_count + 1) * sizeof(*q) );
+ if( q == NULL )
+ {
+ snprintf( err, es, "out of memory" );
+ return( -1 );
+ }
+
+ o->symbols = q;
+ memset( &o->symbols[o->symbol_count], 0,
+ sizeof(o->symbols[o->symbol_count]) );
+ o->symbols[o->symbol_count].name = strdup( s->name );
+ if( o->symbols[o->symbol_count].name == NULL )
+ {
+ snprintf( err, es, "out of memory" );
+ return( -1 );
+ }
+
+ o->symbols[o->symbol_count].section = section;
+ o->symbols[o->symbol_count].binding = s->binding;
+ o->symbols[o->symbol_count].type = s->type;
+ o->symbols[o->symbol_count].value = value;
+ o->symbols[o->symbol_count].size = s->size;
+ if( idx != NULL )
+ *idx = o->symbol_count;
+ ++o->symbol_count;
+ return( 0 );
+
+} /* End of reloc_append_symbol() */
+
+
+static int
+reloc_find_nonlocal( const cpu_asm_object *o, const char *name, size_t *idx )
+{
+ size_t i;
+
+ for( i = 0; i < o->symbol_count; ++i )
+ if( o->symbols[i].binding != CPU_OBJ_BIND_LOCAL &&
+ strcmp(o->symbols[i].name, name) == 0 )
+ {
+ if( idx != NULL )
+ *idx = i;
+ return( 0 );
+ }
+
+ return( -1 );
+
+} /* End of reloc_find_nonlocal() */
+
+
+static int
+reloc_merge_nonlocal( cpu_asm_object *o, const cpu_asm_symbol *s,
+ unsigned section, __mpu_uint64_t value,
+ size_t *idx, char *err, size_t es )
+{
+ cpu_asm_symbol *d;
+ size_t i;
+
+ if( reloc_find_nonlocal(o, s->name, &i) != 0 )
+ return( reloc_append_symbol(o, s, section, value, idx, err, es) );
+
+ d = &o->symbols[i];
+
+ if( section != CPU_OBJ_SEC_UNDEF )
+ {
+ if( d->section != CPU_OBJ_SEC_UNDEF )
+ {
+ if( d->binding == CPU_OBJ_BIND_GLOBAL &&
+ s->binding == CPU_OBJ_BIND_GLOBAL )
+ {
+ snprintf( err, es, "multiple definition of global symbol: %s", s->name );
+ return( -1 );
+ }
+
+ if( d->binding == CPU_OBJ_BIND_WEAK &&
+ s->binding == CPU_OBJ_BIND_GLOBAL )
+ {
+ d->section = section;
+ d->binding = s->binding;
+ d->type = s->type;
+ d->value = value;
+ d->size = s->size;
+ }
+ }
+ else
+ {
+ d->section = section;
+ d->binding = s->binding;
+ d->type = s->type;
+ d->value = value;
+ d->size = s->size;
+ }
+ }
+ else if( d->section == CPU_OBJ_SEC_UNDEF )
+ {
+ if( d->binding == CPU_OBJ_BIND_WEAK &&
+ s->binding == CPU_OBJ_BIND_GLOBAL )
+ d->binding = CPU_OBJ_BIND_GLOBAL;
+ if( d->type == CPU_OBJ_TYPE_NOTYPE && s->type != CPU_OBJ_TYPE_NOTYPE )
+ d->type = s->type;
+ }
+
+ if( idx != NULL )
+ *idx = i;
+ return( 0 );
+
+} /* End of reloc_merge_nonlocal() */
+
+
+static int
+reloc_take_section( cpu_asm_object *o, int si, lbuf *b,
+ __mpu_uint64_t align, int present,
+ char *err, size_t es )
+{
+ if( !present )
+ return( 0 );
+
+ if( align == 0 )
+ align = 1;
+
+ switch( si )
+ {
+ case LSEC_INIT:
+ o->init = b->p;
+ o->init_size = b->n;
+ o->init_align = align;
+ o->has_init = 1;
+ break;
+ case LSEC_TEXT:
+ o->text = b->p;
+ o->text_size = b->n;
+ o->text_align = align;
+ o->has_text = 1;
+ break;
+ case LSEC_FINI:
+ o->fini = b->p;
+ o->fini_size = b->n;
+ o->fini_align = align;
+ o->has_fini = 1;
+ break;
+ case LSEC_DATA:
+ o->data = b->p;
+ o->data_size = b->n;
+ o->data_align = align;
+ o->has_data = 1;
+ break;
+ case LSEC_CONTROL:
+ o->control = b->p;
+ o->control_size = b->n;
+ o->control_align = align;
+ o->has_control = 1;
+ break;
+ case LSEC_INPUT:
+ o->input = b->p;
+ o->input_size = b->n;
+ o->input_align = align;
+ o->has_input = 1;
+ break;
+ case LSEC_OUTPUT:
+ o->output = b->p;
+ o->output_size = b->n;
+ o->output_align = align;
+ o->has_output = 1;
+ break;
+ case LSEC_TRACE:
+ o->trace = b->p;
+ o->trace_size = b->n;
+ o->trace_align = align;
+ o->has_trace = 1;
+ break;
+ default:
+ snprintf( err, es, "bad relocatable output section" );
+ return( -1 );
+ }
+
+ b->p = NULL;
+ b->n = b->cap = 0;
+ return( 0 );
+
+} /* End of reloc_take_section() */
+
+
+int
+cpu_link_relocatable( cpu_asm_object *objs, size_t nobjs,
+ cpu_asm_object *out, char *err, size_t es )
+{
+ lbuf sections[LSEC_COUNT] = {{0}};
+ __mpu_uint64_t *bases = NULL;
+ __mpu_uint64_t aligns[LSEC_COUNT] = {1,1,1,1,1,1,1,1,1};
+ int present[LSEC_COUNT] = {0};
+ size_t *symbase = NULL;
+ size_t *symmap = NULL;
+ size_t total_symbols = 0;
+ size_t total_relocs = 0;
+ __mpu_uint64_t bss_size = 0;
+ size_t oi, si, ri;
+ int rc = -1;
+
+ if( objs == NULL || nobjs == 0 || out == NULL )
+ return( -1 );
+
+ memset( out, 0, sizeof(*out) );
+
+ if( nobjs > SIZE_MAX / LSEC_COUNT )
+ {
+ snprintf( err, es, "too many input objects" );
+ goto done;
+ }
+
+ bases = (__mpu_uint64_t *)calloc( nobjs * LSEC_COUNT, sizeof(*bases) );
+ symbase = (size_t *)calloc( nobjs + 1, sizeof(*symbase) );
+ if( bases == NULL || symbase == NULL )
+ {
+ snprintf( err, es, "out of memory" );
+ goto done;
+ }
+
+ for( oi = 0; oi < nobjs; ++oi )
+ {
+ if( objs[oi].symbol_count > SIZE_MAX - total_symbols ||
+ objs[oi].reloc_count > SIZE_MAX - total_relocs )
+ {
+ snprintf( err, es, "too many symbols or relocations" );
+ goto done;
+ }
+ symbase[oi] = total_symbols;
+ total_symbols += objs[oi].symbol_count;
+ total_relocs += objs[oi].reloc_count;
+ }
+ symbase[nobjs] = total_symbols;
+
+ if( total_symbols != 0 )
+ {
+ symmap = (size_t *)calloc( total_symbols, sizeof(*symmap) );
+ if( symmap == NULL )
+ {
+ snprintf( err, es, "out of memory" );
+ goto done;
+ }
+ }
+
+ /* Merge file-backed sections independently. ET_REL owns no final DATA
+ guard and therefore every output section starts at offset zero. */
+ for( si = LSEC_INIT; si <= LSEC_TRACE; ++si )
+ for( oi = 0; oi < nobjs; ++oi )
+ {
+ const __mpu_UNIT_t *p;
+ size_t n;
+ __mpu_uint64_t a;
+ int have;
+
+ p = objdata( &objs[oi], (int)si, &n, &a, &have );
+ if( !have )
+ continue;
+ if( a == 0 )
+ a = 1;
+ if( !cpu_melf_is_pow2(a) )
+ {
+ snprintf( err, es, "invalid section contribution alignment" );
+ goto done;
+ }
+ present[si] = 1;
+ if( a > aligns[si] )
+ aligns[si] = a;
+ if( padalign(&sections[si], a) )
+ {
+ snprintf( err, es, "cannot align relocatable output section" );
+ goto done;
+ }
+ bases[oi * LSEC_COUNT + si] = sections[si].n;
+ if( put(&sections[si], p, n) )
+ {
+ snprintf( err, es, "out of memory" );
+ goto done;
+ }
+ }
+
+ /* .bss is merged as NOBITS and therefore advances only its logical size. */
+ for( oi = 0; oi < nobjs; ++oi )
+ {
+ __mpu_uint64_t a = objs[oi].bss_align ? objs[oi].bss_align : 1;
+ __mpu_uint64_t aligned;
+ int have = objs[oi].has_bss || objs[oi].bss_size;
+
+ if( !have )
+ continue;
+ if( !cpu_melf_is_pow2(a) || align_u64(bss_size, a, &aligned) )
+ {
+ snprintf( err, es, "cannot align relocatable .bss contribution" );
+ goto done;
+ }
+ bss_size = aligned;
+ present[LSEC_BSS] = 1;
+ if( a > aligns[LSEC_BSS] )
+ aligns[LSEC_BSS] = a;
+ bases[oi * LSEC_COUNT + LSEC_BSS] = bss_size;
+ if( (__mpu_uint64_t)objs[oi].bss_size > UINT64_MAX - bss_size )
+ {
+ snprintf( err, es, "relocatable .bss layout overflow" );
+ goto done;
+ }
+ bss_size += (__mpu_uint64_t)objs[oi].bss_size;
+ }
+
+ if( bss_size > SIZE_MAX )
+ {
+ snprintf( err, es, "relocatable .bss exceeds supported host size" );
+ goto done;
+ }
+
+ /* Build one canonical non-local symbol per name. Defined GLOBAL beats
+ WEAK, a definition beats an undefined reference, and LOCAL symbols
+ remain private even when different input objects use the same name. */
+ for( oi = 0; oi < nobjs; ++oi )
+ for( si = 0; si < objs[oi].symbol_count; ++si )
+ {
+ const cpu_asm_symbol *s = &objs[oi].symbols[si];
+ unsigned section = s->section;
+ __mpu_uint64_t value = s->value;
+ size_t mi;
+
+ if( section != CPU_OBJ_SEC_UNDEF )
+ {
+ int ix = sidx( section );
+ if( ix < 0 || value > UINT64_MAX - bases[oi * LSEC_COUNT + (size_t)ix] )
+ {
+ snprintf( err, es, "bad relocatable symbol placement: %s", s->name );
+ goto done;
+ }
+ value += bases[oi * LSEC_COUNT + (size_t)ix];
+ }
+
+ if( s->binding == CPU_OBJ_BIND_LOCAL )
+ {
+ if( reloc_append_symbol(out, s, section, value, &mi, err, es) )
+ goto done;
+ }
+ else if( reloc_merge_nonlocal(out, s, section, value,
+ &mi, err, es) )
+ goto done;
+
+ symmap[symbase[oi] + si] = mi;
+ }
+
+ if( total_relocs != 0 )
+ {
+ out->relocs = (cpu_asm_reloc *)calloc( total_relocs,
+ sizeof(*out->relocs) );
+ if( out->relocs == NULL )
+ {
+ snprintf( err, es, "out of memory" );
+ goto done;
+ }
+ }
+
+ for( oi = 0; oi < nobjs; ++oi )
+ for( ri = 0; ri < objs[oi].reloc_count; ++ri )
+ {
+ const cpu_asm_reloc *r = &objs[oi].relocs[ri];
+ cpu_asm_reloc *d;
+ int ix = sidx( r->section );
+
+ if( r->symbol_index >= objs[oi].symbol_count || ix < 0 )
+ {
+ snprintf( err, es, "bad relocatable relocation" );
+ goto done;
+ }
+ if( r->offset > UINT64_MAX - bases[oi * LSEC_COUNT + (size_t)ix] )
+ {
+ snprintf( err, es, "relocatable relocation offset overflow" );
+ goto done;
+ }
+
+ d = &out->relocs[out->reloc_count++];
+ d->section = r->section;
+ d->type = r->type;
+ d->symbol_index = symmap[symbase[oi] + r->symbol_index];
+ d->offset = bases[oi * LSEC_COUNT + (size_t)ix] + r->offset;
+ d->addend = r->addend;
+ }
+
+ for( si = LSEC_INIT; si <= LSEC_TRACE; ++si )
+ if( reloc_take_section(out, (int)si, &sections[si], aligns[si],
+ present[si], err, es) )
+ goto done;
+
+ if( present[LSEC_BSS] )
+ {
+ out->has_bss = 1;
+ out->bss_size = (size_t)bss_size;
+ out->bss_align = aligns[LSEC_BSS];
+ }
+
+ rc = 0;
+
+done:
+ for( si = 0; si < LSEC_COUNT; ++si )
+ free( sections[si].p );
+ free( bases );
+ free( symbase );
+ free( symmap );
+ if( rc != 0 )
+ cpu_asm_object_free( out );
+ return( rc );
+
+} /* End of cpu_link_relocatable() */
+
+
+int
+cpu_link_objects( cpu_asm_object *objs, size_t nobjs, cpu_asm_image *im,
+ char *err, size_t es)
+{
+ return( cpu_link_objects_mode( objs, nobjs, im, CPU_LINK_STATIC_EXEC, err, es ) );
+}