#include #include #include #include #include #include #include "mcpu-asm.h" #include "mcpu-object.h" #include "mcpu-link.h" #include "mcpu-exec.h" #include "mcpu-dyn.h" #include "mcpu-archive.h" #include "mcpu-ld-options.h" typedef struct mcpu_ld_library { char *path; cpu_dyn_library_info info; } mcpu_ld_library; typedef enum mcpu_hll_program_kind { MCPU_HLL_NONE = 0, MCPU_HLL_PROGRAM, MCPU_HLL_MODEL } mcpu_hll_program_kind; static void free_libraries( mcpu_ld_library *libraries, size_t count ) { size_t i; for( i = 0; i < count; ++i ) { free( libraries[i].path ); cpu_dyn_library_free( &libraries[i].info ); } free( libraries ); } /* End of free_libraries() */ static int find_soname( const mcpu_ld_library *libraries, size_t count, const char *soname ) { size_t i; for( i = 0; i < count; ++i ) if( strcmp(libraries[i].info.soname, soname) == 0 ) return( (int)i ); return( -1 ); } /* End of find_soname() */ static int append_link_object( cpu_asm_object **objects, size_t *count, size_t *capacity, cpu_asm_object *object, char *err, size_t es ) { cpu_asm_object *p; size_t c; if( *count == *capacity ) { c = *capacity ? *capacity * 2U : 8U; if( c < *capacity || c > SIZE_MAX / sizeof( *p ) ) { snprintf( err, es, "too many input objects" ); return( -1 ); } p = (cpu_asm_object *)realloc( *objects, c * sizeof( *p ) ); if( p == NULL ) { snprintf( err, es, "out of memory" ); return( -1 ); } *objects = p; *capacity = c; } (*objects)[*count] = *object; memset( object, 0, sizeof( *object ) ); ++*count; return( 0 ); } /* End of append_link_object() */ static int hll_symbol_is_strong_func( const cpu_asm_symbol *symbol ) { return( symbol != NULL && symbol->section != CPU_OBJ_SEC_UNDEF && symbol->binding == CPU_OBJ_BIND_GLOBAL && symbol->type == CPU_OBJ_TYPE_FUNC ); } /* End of hll_symbol_is_strong_func() */ static int hll_record_definition( const cpu_asm_object *objects, size_t object_index, size_t symbol_index, size_t *definition_object, const cpu_asm_symbol **definition_symbol, char *err, size_t es ) { const cpu_asm_symbol *symbol = &objects[object_index].symbols[symbol_index]; if( *definition_symbol != NULL ) { snprintf( err, es, "multiple HLL complete-program definitions of %s", symbol->name ); return( -1 ); } *definition_object = object_index; *definition_symbol = symbol; return( 0 ); } /* End of hll_record_definition() */ static int classify_hll_program( const cpu_asm_object *objects, size_t count, mcpu_hll_program_kind *kind, char *err, size_t es ) { const cpu_asm_symbol *main_symbol = NULL; const cpu_asm_symbol *init_symbol = NULL; const cpu_asm_symbol *life_symbol = NULL; const cpu_asm_symbol *fini_symbol = NULL; size_t main_object = 0, init_object = 0, life_object = 0, fini_object = 0; size_t i, j; int raw_lifecycle = 0; *kind = MCPU_HLL_NONE; for( i = 0; i < count; ++i ) for( j = 0; j < objects[i].symbol_count; ++j ) { const cpu_asm_symbol *symbol = &objects[i].symbols[j]; if( symbol->section == CPU_OBJ_SEC_UNDEF ) continue; if( !strncmp(symbol->name, "__mcpu_crt_", 11) ) { snprintf( err, es, "input object defines reserved CRT symbol: %s", symbol->name ); return( -1 ); } if( !strcmp(symbol->name, "_start") || !strcmp(symbol->name, "_life") || !strcmp(symbol->name, "_fini") ) { raw_lifecycle = 1; continue; } if( symbol->binding == CPU_OBJ_BIND_LOCAL ) continue; if( !strcmp(symbol->name, "main") ) { if( hll_record_definition(objects, i, j, &main_object, &main_symbol, err, es) ) return( -1 ); } else if( !strcmp(symbol->name, "init") ) { if( hll_record_definition(objects, i, j, &init_object, &init_symbol, err, es) ) return( -1 ); } else if( !strcmp(symbol->name, "life") ) { if( hll_record_definition(objects, i, j, &life_object, &life_symbol, err, es) ) return( -1 ); } else if( !strcmp(symbol->name, "fini") ) { if( hll_record_definition(objects, i, j, &fini_object, &fini_symbol, err, es) ) return( -1 ); } } if( raw_lifecycle && (main_symbol != NULL || init_symbol != NULL || life_symbol != NULL) ) { snprintf( err, es, "cannot mix low-level lifecycle with HLL complete-program definitions" ); return( -1 ); } if( main_symbol != NULL ) { if( init_symbol != NULL || life_symbol != NULL ) { snprintf( err, es, "HLL PROGRAM main is incompatible with MODEL init/life" ); return( -1 ); } if( !hll_symbol_is_strong_func(main_symbol) ) { snprintf( err, es, "HLL PROGRAM main must be strong GLOBAL/FUNC" ); return( -1 ); } if( fini_symbol == NULL || fini_object != main_object || !hll_symbol_is_strong_func(fini_symbol) ) { snprintf( err, es, "HLL PROGRAM requires strong GLOBAL/FUNC main and fini in one ET_REL" ); return( -1 ); } *kind = MCPU_HLL_PROGRAM; return( 0 ); } if( init_symbol != NULL || life_symbol != NULL ) { if( init_symbol == NULL || life_symbol == NULL ) { snprintf( err, es, "HLL MODEL requires both init and life" ); return( -1 ); } if( init_object != life_object || fini_symbol == NULL || fini_object != init_object || !hll_symbol_is_strong_func(init_symbol) || !hll_symbol_is_strong_func(life_symbol) || !hll_symbol_is_strong_func(fini_symbol) ) { snprintf( err, es, "HLL MODEL requires strong GLOBAL/FUNC init, life and fini in one ET_REL" ); return( -1 ); } *kind = MCPU_HLL_MODEL; } return( 0 ); } /* End of classify_hll_program() */ static char * hll_crt_path( const mcpu_ld_options *options, const char *name ) { const char *root; char *path; size_t n; root = options->common.runtime.installation_root; if( root == NULL || *root == 0 || name == NULL || *name == 0 ) return( NULL ); if( strlen(root) > SIZE_MAX - strlen(name) - sizeof("/lib/mcpu/") ) return( NULL ); n = strlen(root) + strlen(name) + sizeof("/lib/mcpu/"); path = (char *)malloc( n ); if( path != NULL ) snprintf( path, n, "%s/lib/mcpu/%s", root, name ); return( path ); } /* End of hll_crt_path() */ static int read_hll_crt_object( const mcpu_ld_options *options, const char *name, cpu_asm_object *object, char *err, size_t es ) { char detail[512]; char *path; memset( object, 0, sizeof( *object ) ); path = hll_crt_path( options, name ); if( path == NULL ) { snprintf( err, es, "cannot construct HLL CRT path for %s", name ); return( -1 ); } if( cpu_object_read(path, object, detail, sizeof(detail)) ) { snprintf( err, es, "cannot load HLL CRT object %.120s: %.300s", path, detail ); free( path ); return( -1 ); } free( path ); return( 0 ); } /* End of read_hll_crt_object() */ static int inject_hll_crt( const mcpu_ld_options *options, mcpu_hll_program_kind kind, cpu_asm_object **objects, size_t *count, size_t *capacity, char *err, size_t es ) { cpu_asm_object adapter, crti, crtn; cpu_asm_object *combined; const char *adapter_name; size_t new_count; memset( &adapter, 0, sizeof(adapter) ); memset( &crti, 0, sizeof(crti) ); memset( &crtn, 0, sizeof(crtn) ); adapter_name = kind == MCPU_HLL_PROGRAM ? "crt0.o" : "crt1.o"; if( read_hll_crt_object(options, adapter_name, &adapter, err, es) || read_hll_crt_object(options, "crti.o", &crti, err, es) || read_hll_crt_object(options, "crtn.o", &crtn, err, es) ) goto bad; if( *count > SIZE_MAX - 3 || *count + 3 > SIZE_MAX / sizeof(*combined) ) { snprintf( err, es, "too many input objects after HLL CRT insertion" ); goto bad; } new_count = *count + 3; combined = (cpu_asm_object *)calloc( new_count, sizeof(*combined) ); if( combined == NULL ) { snprintf( err, es, "out of memory" ); goto bad; } combined[0] = adapter; combined[1] = crti; if( *count != 0 ) memcpy( combined + 2, *objects, *count * sizeof(*combined) ); combined[new_count - 1] = crtn; memset( &adapter, 0, sizeof(adapter) ); memset( &crti, 0, sizeof(crti) ); memset( &crtn, 0, sizeof(crtn) ); free( *objects ); *objects = combined; *count = new_count; *capacity = new_count; return( 0 ); bad: cpu_asm_object_free( &adapter ); cpu_asm_object_free( &crti ); cpu_asm_object_free( &crtn ); return( -1 ); } /* End of inject_hll_crt() */ static int object_defines_symbol( const cpu_asm_object *objects, size_t count, const char *name ) { size_t i, j; for( i = 0; i < count; ++i ) for( j = 0; j < objects[i].symbol_count; ++j ) { const cpu_asm_symbol *s = &objects[i].symbols[j]; if( s->section != CPU_OBJ_SEC_UNDEF && s->binding != CPU_OBJ_BIND_LOCAL && strcmp(s->name, name) == 0 ) return( 1 ); } return( 0 ); } /* End of object_defines_symbol() */ static int library_defines_symbol( const mcpu_ld_library *libraries, size_t count, const char *name ) { size_t i; for( i = 0; i < count; ++i ) if( cpu_dyn_library_find_symbol(&libraries[i].info, name, NULL) == 0 ) return( 1 ); return( 0 ); } /* End of library_defines_symbol() */ static int name_in_list( const char *const *names, size_t count, const char *name ) { size_t i; for( i = 0; i < count; ++i ) if( strcmp(names[i], name) == 0 ) return( 1 ); return( 0 ); } /* End of name_in_list() */ static int collect_strong_unresolved( const cpu_asm_object *objects, size_t object_count, const mcpu_ld_library *libraries, size_t library_count, const char ***names_out, size_t *count_out, char *err, size_t es ) { const char **names = NULL; size_t count = 0; size_t i, j; *names_out = NULL; *count_out = 0; for( i = 0; i < object_count; ++i ) for( j = 0; j < objects[i].symbol_count; ++j ) { const cpu_asm_symbol *s = &objects[i].symbols[j]; const char **p; if( s->section != CPU_OBJ_SEC_UNDEF || s->binding != CPU_OBJ_BIND_GLOBAL ) continue; if( object_defines_symbol(objects, object_count, s->name) || library_defines_symbol(libraries, library_count, s->name) || name_in_list(names, count, s->name) ) continue; if( count == SIZE_MAX / sizeof( *p ) ) { free( names ); snprintf( err, es, "too many unresolved symbols" ); return( -1 ); } p = (const char **)realloc( names, (count + 1) * sizeof( *p ) ); if( p == NULL ) { free( names ); snprintf( err, es, "out of memory" ); return( -1 ); } names = p; names[count++] = s->name; } *names_out = names; *count_out = count; return( 0 ); } /* End of collect_strong_unresolved() */ static int archive_member_is_needed( const mcpu_archive_member *member, const char *const *unresolved, size_t unresolved_count ) { size_t i, j; for( i = 0; i < member->symbol_count; ++i ) for( j = 0; j < unresolved_count; ++j ) if( strcmp(member->symbols[i], unresolved[j]) == 0 ) return( 1 ); return( 0 ); } /* End of archive_member_is_needed() */ static int extract_archive_members( const char *path, const mcpu_archive *archive, cpu_asm_object **objects, size_t *object_count, size_t *object_capacity, const mcpu_ld_library *libraries, size_t library_count, char *err, size_t es ) { unsigned char *selected = NULL; int changed; size_t i; if( archive->member_count != 0 ) { selected = (unsigned char *)calloc( archive->member_count, 1 ); if( selected == NULL ) { snprintf( err, es, "out of memory" ); return( -1 ); } } do { const char **unresolved = NULL; size_t unresolved_count = 0; changed = 0; if( collect_strong_unresolved(*objects, *object_count, libraries, library_count, &unresolved, &unresolved_count, err, es) ) { free( selected ); return( -1 ); } for( i = 0; i < archive->member_count && unresolved_count != 0; ++i ) { cpu_asm_object object; char detail[512]; if( selected[i] || !archive_member_is_needed(&archive->members[i], unresolved, unresolved_count) ) continue; memset( &object, 0, sizeof( object ) ); if( cpu_object_read_data(archive->members[i].data, archive->members[i].size, &object, detail, sizeof(detail)) ) { snprintf( err, es, "%.160s(%.120s): %.180s", path, archive->members[i].name, detail ); free( unresolved ); free( selected ); return( -1 ); } if( append_link_object(objects, object_count, object_capacity, &object, err, es) ) { cpu_asm_object_free( &object ); free( unresolved ); free( selected ); return( -1 ); } selected[i] = 1; changed = 1; free( unresolved ); unresolved = NULL; unresolved_count = 0; if( collect_strong_unresolved(*objects, *object_count, libraries, library_count, &unresolved, &unresolved_count, err, es) ) { free( selected ); return( -1 ); } } free( unresolved ); } while( changed ); free( selected ); return( 0 ); } /* End of extract_archive_members() */ static char * library_filename( const char *name, const char *suffix ) { size_t n; char *result; if( strlen(name) > SIZE_MAX - strlen(suffix) - sizeof("lib") ) return( NULL ); n = strlen(name) + strlen(suffix) + sizeof("lib"); result = (char *)malloc( n ); if( result != NULL ) snprintf( result, n, "lib%s%s", name, suffix ); return( result ); } /* End of library_filename() */ static int process_libraries( const mcpu_ld_options *options, cpu_asm_object **objects, size_t *object_count, size_t *object_capacity, mcpu_ld_library **libraries_out, size_t *count_out, char *err, size_t es ) { mcpu_ld_library *libraries = NULL; size_t count = 0, i; *libraries_out = NULL; *count_out = 0; if( options->library_count == 0 ) return( 0 ); libraries = (mcpu_ld_library *)calloc( options->library_count, sizeof(*libraries) ); if( libraries == NULL ) { snprintf( err, es, "out of memory" ); return( -1 ); } for( i = 0; i < options->library_count; ++i ) { const char *name = options->libraries[i]; char *shared_name = NULL; char *archive_name = NULL; const char *alternatives[2]; size_t alternative_count = 0; size_t selected_index = 0; char *path = NULL; if( !options->force_static && (options->mode == CPU_LINK_DYNAMIC_EXEC || options->mode == CPU_LINK_SHARED) ) { shared_name = library_filename( name, ".so" ); if( shared_name == NULL ) { snprintf( err, es, "library name is too long: %s", name ); goto bad; } alternatives[alternative_count++] = shared_name; } archive_name = library_filename( name, ".a" ); if( archive_name == NULL ) { free( shared_name ); snprintf( err, es, "library name is too long: %s", name ); goto bad; } alternatives[alternative_count++] = archive_name; if( mcpu_tool_resolve_library_alternatives(&options->common, alternatives, alternative_count, &path, &selected_index) != 0 ) { if( options->force_static || options->mode == CPU_LINK_STATIC_EXEC || options->mode == CPU_LINK_RELOCATABLE ) snprintf( err, es, "cannot find -l%s (%s): %s", name, archive_name, strerror(errno) ); else snprintf( err, es, "cannot find -l%s (%s or %s): %s", name, shared_name, archive_name, strerror(errno) ); free( shared_name ); free( archive_name ); goto bad; } if( !options->force_static && (options->mode == CPU_LINK_DYNAMIC_EXEC || options->mode == CPU_LINK_SHARED) && selected_index == 0 ) { cpu_dyn_library_info info; memset( &info, 0, sizeof(info) ); if( cpu_dyn_library_read(path, &info, err, es) != 0 ) { free( path ); free( shared_name ); free( archive_name ); goto bad; } if( find_soname(libraries, count, info.soname) >= 0 ) { free( path ); cpu_dyn_library_free( &info ); } else { libraries[count].path = path; libraries[count].info = info; ++count; } } else { mcpu_archive archive; mcpu_archive_init( &archive ); if( mcpu_archive_read(path, &archive, err, es) != 0 ) { free( path ); free( shared_name ); free( archive_name ); goto bad; } if( extract_archive_members(path, &archive, objects, object_count, object_capacity, libraries, count, err, es) ) { mcpu_archive_free( &archive ); free( path ); free( shared_name ); free( archive_name ); goto bad; } mcpu_archive_free( &archive ); free( path ); } free( shared_name ); free( archive_name ); } *libraries_out = libraries; *count_out = count; return( 0 ); bad: free_libraries( libraries, count ); return( -1 ); } /* End of process_libraries() */ static int validate_executable_imports( const cpu_asm_image *image, const mcpu_ld_library *libraries, size_t count, char *err, size_t es ) { size_t i, j; for( i = 0; i < image->dyn_symbol_count; ++i ) { const cpu_dyn_symbol *need = &image->dyn_symbols[i]; int found = 0; if( need->section != CPU_OBJ_SEC_UNDEF || need->binding == CPU_OBJ_BIND_WEAK ) continue; for( j = 0; j < count; ++j ) { const cpu_dyn_library_symbol *have = NULL; if( cpu_dyn_library_find_symbol(&libraries[j].info, need->name, &have) == 0 ) { if( need->type != CPU_OBJ_TYPE_NOTYPE && have->type != CPU_OBJ_TYPE_NOTYPE && need->type != have->type ) { snprintf( err, es, "symbol type mismatch for %s in %s", need->name, libraries[j].path ); return( -1 ); } found = 1; break; } } if( !found ) { snprintf( err, es, "undefined symbol not provided by linked libraries: %s", need->name ); return( -1 ); } } return( 0 ); } /* End of validate_executable_imports() */ static int mcpu_ld_link( const mcpu_ld_options *options ) { cpu_asm_object *objs = NULL; cpu_asm_image im; mcpu_ld_library *libraries = NULL; size_t object_count; size_t object_capacity; size_t library_count = 0; mcpu_hll_program_kind hll_kind = MCPU_HLL_NONE; char err[512]; size_t i; int rc = 1; if( options == NULL ) return( 1 ); object_count = options->input_count; object_capacity = options->input_count; objs = (cpu_asm_object *)calloc( object_capacity, sizeof(*objs) ); if( objs == NULL ) { perror( "calloc" ); return( 1 ); } __mpu_init(); memset( &im, 0, sizeof(im) ); for( i = 0; i < options->input_count; ++i ) { if( cpu_object_read(options->inputs[i], &objs[i], err, sizeof(err)) ) { fprintf( stderr, "%s: %s\n", options->common.progname, err ); goto cleanup_mpu; } } if( options->mode == CPU_LINK_STATIC_EXEC || options->mode == CPU_LINK_DYNAMIC_EXEC ) { if( classify_hll_program(objs, object_count, &hll_kind, err, sizeof(err)) ) { fprintf( stderr, "%s: %s\n", options->common.progname, err ); goto cleanup_mpu; } if( hll_kind != MCPU_HLL_NONE && inject_hll_crt(options, hll_kind, &objs, &object_count, &object_capacity, err, sizeof(err)) ) { fprintf( stderr, "%s: %s\n", options->common.progname, err ); goto cleanup_mpu; } } if( process_libraries(options, &objs, &object_count, &object_capacity, &libraries, &library_count, err, sizeof(err)) != 0 ) { fprintf( stderr, "%s: %s\n", options->common.progname, err ); goto cleanup_mpu; } if( options->mode == CPU_LINK_RELOCATABLE ) { cpu_asm_object merged; memset( &merged, 0, sizeof(merged) ); if( cpu_link_relocatable(objs, object_count, &merged, err, sizeof(err)) ) { fprintf( stderr, "%s: %s\n", options->common.progname, err ); goto cleanup_libraries; } if( cpu_object_write(options->out_fname, &merged, err, sizeof(err)) ) { fprintf( stderr, "%s: %s\n", options->common.progname, err ); cpu_asm_object_free( &merged ); goto cleanup_libraries; } if( options->verbose ) printf( "MELF ET_REL partial link: %zu symbols, %zu relocations -> %s\n", merged.symbol_count, merged.reloc_count, options->out_fname ); cpu_asm_object_free( &merged ); rc = 0; goto cleanup_libraries; } if( cpu_link_objects_mode(objs, object_count, &im, options->mode, err, sizeof(err)) ) { fprintf( stderr, "%s: %s\n", options->common.progname, err ); goto cleanup_libraries; } if( options->mode == CPU_LINK_DYNAMIC_EXEC && options->library_count != 0 && validate_executable_imports(&im, libraries, library_count, err, sizeof(err)) != 0 ) { fprintf( stderr, "%s: %s\n", options->common.progname, err ); goto cleanup_image; } if( options->mode == CPU_LINK_STATIC_EXEC ) { if( cpu_exec_write(options->out_fname, &im, &options->exec, err, sizeof(err)) ) { fprintf( stderr, "%s: %s\n", options->common.progname, err ); goto cleanup_image; } if( options->verbose ) { printf( "MELF ABI1 ET_EXEC: PROGRAM=%zu DATA=%zu STACK=%llu", im.code_size, im.data_size, (unsigned long long)options->exec.stack_size ); if( options->exec.have_heap ) printf( " HEAP=%llu/%llu", (unsigned long long)options->exec.heap_size, (unsigned long long)options->exec.heap_limit ); printf( " -> %s\n", options->out_fname ); } } else { cpu_dyn_metadata metadata; const char **needed = NULL; memset( &metadata, 0, sizeof( metadata ) ); metadata.soname = options->soname; if( library_count != 0 ) { needed = (const char **)calloc( library_count, sizeof(*needed) ); if( needed == NULL ) { fprintf( stderr, "%s: out of memory\n", options->common.progname ); goto cleanup_image; } for( i = 0; i < library_count; ++i ) needed[i] = libraries[i].info.soname; metadata.needed = needed; metadata.needed_count = library_count; } if( cpu_dyn_write_abi3(options->out_fname, &im, options->mode == CPU_LINK_SHARED ? NULL : &options->exec, options->mode == CPU_LINK_SHARED, &metadata, err, sizeof(err)) ) { free( needed ); fprintf( stderr, "%s: %s\n", options->common.progname, err ); goto cleanup_image; } free( needed ); if( options->verbose ) { if( options->mode == CPU_LINK_SHARED ) printf( "MELF ABI3 ET_DYN: PROGRAM=%zu DATA=%zu DYNSYM=%zu RELA=%zu NEEDED=%zu -> %s\n", im.code_size, im.data_size, im.dyn_symbol_count, im.dyn_reloc_count, library_count, options->out_fname ); else { printf( "MELF ABI3 ET_EXEC: PROGRAM=%zu DATA=%zu DYNSYM=%zu RELA=%zu NEEDED=%zu STACK=%llu", im.code_size, im.data_size, im.dyn_symbol_count, im.dyn_reloc_count, library_count, (unsigned long long)options->exec.stack_size ); if( options->exec.have_heap ) printf( " HEAP=%llu/%llu", (unsigned long long)options->exec.heap_size, (unsigned long long)options->exec.heap_limit ); printf( " -> %s\n", options->out_fname ); } } } rc = 0; cleanup_image: cpu_asm_image_free( &im ); cleanup_libraries: free_libraries( libraries, library_count ); cleanup_mpu: for( i = 0; i < object_count; ++i ) cpu_asm_object_free( &objs[i] ); free( objs ); __mpu_free_context(); return( rc ); } /* End of mcpu_ld_link() */ int main( int argc, char **argv ) { mcpu_ld_options options; int rc = 1; mcpu_ld_options_init( &options, argv[0] ); if( mcpu_ld_handle_options(&options, argc, argv) != 0 ) goto done; if( options.common.help ) { mcpu_ld_usage( &options ); rc = 0; goto done; } if( options.common.version_action != MCPU_VERSION_NONE ) { if( mcpu_version_print(options.common.progname, options.common.version_action) != 0 ) goto done; rc = 0; goto done; } if( mcpu_tool_options_prepare(&options.common, argv[0]) != 0 ) goto done; if( mcpu_tool_has_information_action(&options.common) ) { if( options.common.dump_config && mcpu_tool_dump_config(&options.common, stdout) != 0 ) goto done; if( options.common.print_search_dirs && mcpu_tool_print_search_dirs(&options.common, stdout) != 0 ) goto done; rc = 0; goto done; } rc = mcpu_ld_link( &options ); done: mcpu_ld_options_free( &options ); return( rc ); } /* End of main() */