diff options
Diffstat (limited to 'utils/mcpu-link.c')
| -rw-r--r-- | utils/mcpu-link.c | 1647 |
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(§ions[si], a) ) + { + snprintf( err, es, "cannot align relocatable output section" ); + goto done; + } + bases[oi * LSEC_COUNT + si] = sections[si].n; + if( put(§ions[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, §ions[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 ) ); +} |
