#include #include #include #include #include #include #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 ) ); }