diff options
Diffstat (limited to 'utils')
42 files changed, 26024 insertions, 0 deletions
diff --git a/utils/Makefile.am b/utils/Makefile.am new file mode 100644 index 0000000..e59aff9 --- /dev/null +++ b/utils/Makefile.am @@ -0,0 +1,72 @@ + +AM_CPPFLAGS = -I$(top_srcdir)/include $(LIBMPU_CPPFLAGS) $(LIBMPUIO_CPPFLAGS) +AM_CFLAGS = $(LIBMPU_CFLAGS) $(LIBMPUIO_CFLAGS) +AM_LDFLAGS = $(LIBMPU_LDFLAGS) $(LIBMPUIO_LDFLAGS) + +noinst_LTLIBRARIES = libmcpu-version.la +libmcpu_version_la_SOURCES = mcpu-version.c mcpu-version.h + +pkglibexecdir = @MCPU_BINDIR@ +pkglibexec_PROGRAMS = mcpu-as mcpu-run mcpu-ld mcpu-readelf mcpu-ar mcpu-nm mcpu-objdump mcpu-strip mcpu-objcopy + +mcpu_as_SOURCES = mcpu-as.c mcpu-asm.c mcpu-asm.h mcpu-object.c mcpu-object.h mcpu-melf.c mcpu-melf.h +mcpu_as_LDADD = libmcpu-version.la $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + +mcpu_ld_SOURCES = mcpu-ld.c mcpu-ld-options.c mcpu-ld-options.h mcpu-tool-options.c mcpu-tool-options.h mcpu-library-search.c mcpu-library-search.h mcpu-runtime.c mcpu-runtime.h mcpu-config.c mcpu-config.h mcpu-archive.c mcpu-archive.h mcpu-asm.c mcpu-asm.h mcpu-object.c mcpu-object.h mcpu-link.c mcpu-link.h mcpu-exec.c mcpu-exec.h mcpu-dyn.c mcpu-dyn.h mcpu-melf.c mcpu-melf.h +mcpu_ld_LDADD = libmcpu-version.la $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + +mcpu_run_SOURCES = mcpu-run.c mcpu-run-options.c mcpu-run-options.h mcpu-dependency.c mcpu-dependency.h mcpu-tool-options.c mcpu-tool-options.h mcpu-library-search.c mcpu-library-search.h mcpu-runtime.c mcpu-runtime.h mcpu-config.c mcpu-config.h mcpu-exec.c mcpu-exec.h mcpu-dyn.c mcpu-dyn.h mcpu-melf.c mcpu-melf.h +mcpu_run_LDADD = libmcpu-version.la $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + +mcpu_readelf_SOURCES = mcpu-readelf.c mcpu-melf.c mcpu-melf.h +mcpu_readelf_LDADD = libmcpu-version.la $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + +mcpu_ar_SOURCES = mcpu-ar.c mcpu-archive.c mcpu-archive.h mcpu-object.c mcpu-object.h mcpu-asm.c mcpu-asm.h mcpu-melf.c mcpu-melf.h +mcpu_ar_LDADD = libmcpu-version.la $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + +mcpu_nm_SOURCES = mcpu-nm.c mcpu-archive.c mcpu-archive.h mcpu-object.c mcpu-object.h mcpu-asm.c mcpu-asm.h mcpu-melf.c mcpu-melf.h +mcpu_nm_LDADD = libmcpu-version.la $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + +mcpu_objdump_SOURCES = mcpu-objdump.c mcpu-archive.c mcpu-archive.h mcpu-object.c mcpu-object.h mcpu-asm.c mcpu-asm.h mcpu-melf.c mcpu-melf.h +mcpu_objdump_LDADD = libmcpu-version.la $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + +mcpu_strip_SOURCES = mcpu-strip.c mcpu-object-edit.c mcpu-object-edit.h mcpu-archive.c mcpu-archive.h mcpu-object.c mcpu-object.h mcpu-asm.c mcpu-asm.h mcpu-melf.c mcpu-melf.h +mcpu_strip_LDADD = libmcpu-version.la $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + +mcpu_objcopy_SOURCES = mcpu-objcopy.c mcpu-object-edit.c mcpu-object-edit.h mcpu-archive.c mcpu-archive.h mcpu-object.c mcpu-object.h mcpu-asm.c mcpu-asm.h mcpu-melf.c mcpu-melf.h +mcpu_objcopy_LDADD = libmcpu-version.la $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + +install-exec-hook: + $(MKDIR_P) "$(DESTDIR)$(bindir)" + rm -f "$(DESTDIR)$(bindir)/mcpu-as" + rm -f "$(DESTDIR)$(bindir)/mcpu-ld" + rm -f "$(DESTDIR)$(bindir)/mcpu-run" + rm -f "$(DESTDIR)$(bindir)/mcpu-readelf" + rm -f "$(DESTDIR)$(bindir)/mcpu-ar" + rm -f "$(DESTDIR)$(bindir)/mcpu-nm" + rm -f "$(DESTDIR)$(bindir)/mcpu-objdump" + rm -f "$(DESTDIR)$(bindir)/mcpu-strip" + rm -f "$(DESTDIR)$(bindir)/mcpu-objcopy" + $(LN_S) "@MCPU_AS_PUBLIC_LINK_TARGET@" "$(DESTDIR)$(bindir)/mcpu-as" + $(LN_S) "@MCPU_LD_PUBLIC_LINK_TARGET@" "$(DESTDIR)$(bindir)/mcpu-ld" + $(LN_S) "@MCPU_RUN_PUBLIC_LINK_TARGET@" "$(DESTDIR)$(bindir)/mcpu-run" + $(LN_S) "@MCPU_READELF_PUBLIC_LINK_TARGET@" "$(DESTDIR)$(bindir)/mcpu-readelf" + $(LN_S) "@MCPU_AR_PUBLIC_LINK_TARGET@" "$(DESTDIR)$(bindir)/mcpu-ar" + $(LN_S) "@MCPU_NM_PUBLIC_LINK_TARGET@" "$(DESTDIR)$(bindir)/mcpu-nm" + $(LN_S) "@MCPU_OBJDUMP_PUBLIC_LINK_TARGET@" "$(DESTDIR)$(bindir)/mcpu-objdump" + $(LN_S) "@MCPU_STRIP_PUBLIC_LINK_TARGET@" "$(DESTDIR)$(bindir)/mcpu-strip" + $(LN_S) "@MCPU_OBJCOPY_PUBLIC_LINK_TARGET@" "$(DESTDIR)$(bindir)/mcpu-objcopy" + +uninstall-hook: + rm -f "$(DESTDIR)$(bindir)/mcpu-as" + rm -f "$(DESTDIR)$(bindir)/mcpu-ld" + rm -f "$(DESTDIR)$(bindir)/mcpu-run" + rm -f "$(DESTDIR)$(bindir)/mcpu-readelf" + rm -f "$(DESTDIR)$(bindir)/mcpu-ar" + rm -f "$(DESTDIR)$(bindir)/mcpu-nm" + rm -f "$(DESTDIR)$(bindir)/mcpu-objdump" + rm -f "$(DESTDIR)$(bindir)/mcpu-strip" + rm -f "$(DESTDIR)$(bindir)/mcpu-objcopy" + +distclean-local: + -rm -rf $(DEPDIR) diff --git a/utils/mcpu-ar.c b/utils/mcpu-ar.c new file mode 100644 index 0000000..c3fe4bf --- /dev/null +++ b/utils/mcpu-ar.c @@ -0,0 +1,542 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> + +#include "mcpu-archive.h" +#include "mcpu-version.h" + + +typedef struct mcpu_ar_options +{ + char operation; + int create; + int index; + int verbose; +} mcpu_ar_options; + + +static void +usage( FILE *fp, const char *name ) +{ + fprintf( fp, + "usage: %s [options] operation archive [member ...]\n\n" + "Create and maintain deterministic UNIX archives of MCPU MELF ET_REL objects.\n\n" + "Operations:\n" + " r replace existing members or append new members\n" + " q append members without replacement\n" + " d delete named members\n" + " t list archive members\n" + " p print member contents to standard output\n" + " x extract members into the current directory\n" + " s rebuild the archive symbol index\n\n" + "Modifiers:\n" + " c accepted for ar compatibility; archive creation is quiet\n" + " s write/rebuild the standard archive symbol index\n" + " v verbose operation\n\n" + "The operation and modifiers may be combined, for example: rcs, tv, xv.\n" + "Member order is preserved. r replaces a matching member in place and\n" + "appends a member that is not already present. q always appends.\n\n" + "Options:\n" + " --help - show this help\n" + " --version - show program version\n" + " --dump-version - show version number only\n", + name ); + +} /* End of usage() */ + + +static int +parse_operation( const char *text, mcpu_ar_options *options ) +{ + const char *p = text; + int primary = 0; + + memset( options, 0, sizeof( *options ) ); + if( *p == '-' ) + ++p; + if( !*p ) + return( -1 ); + + while( *p ) + { + switch( *p ) + { + case 'r': case 'q': case 'd': case 't': case 'p': case 'x': + if( primary && options->operation != *p ) + return( -1 ); + options->operation = *p; + primary = 1; + break; + case 's': + options->index = 1; + break; + case 'c': + options->create = 1; + break; + case 'v': + options->verbose = 1; + break; + case 'D': + break; /* Deterministic mode is always active. */ + default: + return( -1 ); + } + ++p; + } + + if( !primary ) + { + if( options->index ) + options->operation = 's'; + else + return( -1 ); + } + return( 0 ); + +} /* End of parse_operation() */ + + +static int +archive_exists( const char *path ) +{ + struct stat st; + return( stat( path, &st ) == 0 ); + +} /* End of archive_exists() */ + + +static int +selector_name( const char *arg, const char **name ) +{ + const char *p = mcpu_archive_basename( arg ); + + if( !p || !*p ) + return( -1 ); + *name = p; + return( 0 ); + +} /* End of selector_name() */ + + +static int +name_selected( const char *name, int argc, char **argv, int *found ) +{ + int i; + + if( argc == 0 ) + return( 1 ); + + for( i = 0; i < argc; ++i ) + { + const char *selector; + + if( selector_name( argv[i], &selector ) ) + continue; + if( !strcmp( name, selector ) ) + { + if( found ) + found[i] = 1; + return( 1 ); + } + } + return( 0 ); + +} /* End of name_selected() */ + + +static int +check_all_found( int argc, char **argv, int *found, const char *prog ) +{ + int i; + int rc = 0; + + for( i = 0; i < argc; ++i ) + if( !found[i] ) + { + fprintf( stderr, "%s: member not found: %s\n", prog, argv[i] ); + rc = -1; + } + return( rc ); + +} /* End of check_all_found() */ + + +static int +replace_member( mcpu_archive *archive, mcpu_archive_member *member, + int verbose ) +{ + size_t i; + + for( i = 0; i < archive->member_count; ++i ) + if( !strcmp( archive->members[i].name, member->name ) ) + { + if( verbose ) + fprintf( stderr, "r - %s\n", member->name ); + mcpu_archive_member_free( &archive->members[i] ); + archive->members[i] = *member; + memset( member, 0, sizeof( *member ) ); + return( 0 ); + } + return( 1 ); + +} /* End of replace_member() */ + + +static int +operation_add( mcpu_archive *archive, char operation, + int argc, char **argv, int verbose, + const char *prog, char *err, size_t errsz ) +{ + int i; + + if( argc == 0 ) + { + fprintf( stderr, "%s: operation %c requires at least one member\n", + prog, operation ); + return( -1 ); + } + + for( i = 0; i < argc; ++i ) + { + mcpu_archive_member member; + int rr = 1; + + memset( &member, 0, sizeof( member ) ); + if( mcpu_archive_member_load( argv[i], &member, err, errsz ) ) + { + fprintf( stderr, "%s: %s: %s\n", prog, argv[i], err ); + return( -1 ); + } + + if( operation == 'r' ) + rr = replace_member( archive, &member, verbose ); + if( rr > 0 ) + { + if( verbose ) + fprintf( stderr, "%c - %s\n", operation, member.name ); + if( mcpu_archive_append( archive, &member, err, errsz ) ) + { + fprintf( stderr, "%s: %s\n", prog, err ); + mcpu_archive_member_free( &member ); + return( -1 ); + } + } + } + return( 0 ); + +} /* End of operation_add() */ + + +static int +operation_delete( mcpu_archive *archive, int argc, char **argv, + int verbose, const char *prog ) +{ + int *found; + size_t i, out; + + if( argc == 0 ) + { + fprintf( stderr, "%s: operation d requires at least one member\n", prog ); + return( -1 ); + } + found = (int *)calloc( (size_t)argc, sizeof( *found ) ); + if( !found ) + { + fprintf( stderr, "%s: out of memory\n", prog ); + return( -1 ); + } + + out = 0; + for( i = 0; i < archive->member_count; ++i ) + { + if( name_selected( archive->members[i].name, argc, argv, found ) ) + { + if( verbose ) + fprintf( stderr, "d - %s\n", archive->members[i].name ); + mcpu_archive_member_free( &archive->members[i] ); + continue; + } + if( out != i ) + { + archive->members[out] = archive->members[i]; + memset( &archive->members[i], 0, sizeof( archive->members[i] ) ); + } + ++out; + } + archive->member_count = out; + + i = check_all_found( argc, argv, found, prog ) ? 1 : 0; + free( found ); + return( i ? -1 : 0 ); + +} /* End of operation_delete() */ + + +static int +operation_list( const mcpu_archive *archive, int argc, char **argv, + int verbose, const char *prog ) +{ + int *found = NULL; + size_t i; + int rc; + + if( argc ) + { + found = (int *)calloc( (size_t)argc, sizeof( *found ) ); + if( !found ) + { + fprintf( stderr, "%s: out of memory\n", prog ); + return( -1 ); + } + } + + for( i = 0; i < archive->member_count; ++i ) + if( name_selected( archive->members[i].name, argc, argv, found ) ) + { + if( verbose ) + printf( "rw-r--r-- 0/0 %10zu Jan 1 00:00 1970 %s\n", + archive->members[i].size, archive->members[i].name ); + else + printf( "%s\n", archive->members[i].name ); + } + + rc = check_all_found( argc, argv, found, prog ); + free( found ); + return( rc ); + +} /* End of operation_list() */ + + +static int +operation_print( const mcpu_archive *archive, int argc, char **argv, + int verbose, const char *prog ) +{ + int *found = NULL; + size_t i; + int rc = 0; + + if( argc ) + { + found = (int *)calloc( (size_t)argc, sizeof( *found ) ); + if( !found ) + { + fprintf( stderr, "%s: out of memory\n", prog ); + return( -1 ); + } + } + + for( i = 0; i < archive->member_count; ++i ) + if( name_selected( archive->members[i].name, argc, argv, found ) ) + { + if( verbose ) + fprintf( stderr, "p - %s\n", archive->members[i].name ); + if( archive->members[i].size && + fwrite( archive->members[i].data, 1, archive->members[i].size, + stdout ) != archive->members[i].size ) + { + fprintf( stderr, "%s: cannot write stdout: %s\n", + prog, strerror( errno ) ); + rc = -1; + break; + } + } + + if( !rc && check_all_found( argc, argv, found, prog ) ) + rc = -1; + free( found ); + return( rc ); + +} /* End of operation_print() */ + + +static int +operation_extract( const mcpu_archive *archive, int argc, char **argv, + int verbose, const char *prog ) +{ + int *found = NULL; + size_t i; + int rc = 0; + + if( argc ) + { + found = (int *)calloc( (size_t)argc, sizeof( *found ) ); + if( !found ) + { + fprintf( stderr, "%s: out of memory\n", prog ); + return( -1 ); + } + } + + for( i = 0; i < archive->member_count; ++i ) + if( name_selected( archive->members[i].name, argc, argv, found ) ) + { + FILE *fp; + + if( verbose ) + fprintf( stderr, "x - %s\n", archive->members[i].name ); + fp = fopen( archive->members[i].name, "wb" ); + if( !fp ) + { + fprintf( stderr, "%s: cannot create %s: %s\n", prog, + archive->members[i].name, strerror( errno ) ); + rc = -1; + break; + } + if( archive->members[i].size && + fwrite( archive->members[i].data, 1, archive->members[i].size, + fp ) != archive->members[i].size ) + { + fprintf( stderr, "%s: cannot write %s: %s\n", prog, + archive->members[i].name, strerror( errno ) ); + fclose( fp ); + rc = -1; + break; + } + if( fclose( fp ) ) + { + fprintf( stderr, "%s: cannot close %s: %s\n", prog, + archive->members[i].name, strerror( errno ) ); + rc = -1; + break; + } + chmod( archive->members[i].name, 0644 ); + } + + if( !rc && check_all_found( argc, argv, found, prog ) ) + rc = -1; + free( found ); + return( rc ); + +} /* End of operation_extract() */ + + +int +main( int argc, char **argv ) +{ + const char *prog = argc > 0 ? argv[0] : "mcpu-ar"; + mcpu_ar_options options; + const char *archive_path; + mcpu_archive archive; + char err[512]; + int exists; + int member_argc; + char **member_argv; + int mutation = 0; + int rc = EXIT_FAILURE; + + if( argc == 2 && !strcmp( argv[1], "--help" ) ) + { + usage( stdout, prog ); + return( EXIT_SUCCESS ); + } + if( argc >= 2 && + (!strcmp( argv[1], "--version" ) || + !strcmp( argv[1], "--dump-version" )) ) + { + mcpu_version_action version_action; + + mcpu_version_parse_option( argv[1], &version_action ); + return( mcpu_version_print(prog, version_action) == 0 ? + EXIT_SUCCESS : EXIT_FAILURE ); + } + if( argc < 3 || parse_operation( argv[1], &options ) ) + { + usage( stderr, prog ); + return( EXIT_FAILURE ); + } + + archive_path = argv[2]; + member_argc = argc - 3; + member_argv = argv + 3; + exists = archive_exists( archive_path ); + mcpu_archive_init( &archive ); + + if( exists ) + { + err[0] = 0; + if( mcpu_archive_read( archive_path, &archive, err, sizeof( err ) ) ) + { + fprintf( stderr, "%s: %s: %s\n", prog, archive_path, err ); + goto out; + } + } + else if( options.operation != 'r' && options.operation != 'q' ) + { + fprintf( stderr, "%s: %s: %s\n", prog, archive_path, strerror( ENOENT ) ); + goto out; + } + + err[0] = 0; + switch( options.operation ) + { + case 'r': + case 'q': + if( operation_add( &archive, options.operation, member_argc, member_argv, + options.verbose, prog, err, sizeof( err ) ) ) + goto out; + mutation = 1; + break; + case 'd': + if( operation_delete( &archive, member_argc, member_argv, + options.verbose, prog ) ) + goto out; + mutation = 1; + break; + case 't': + if( operation_list( &archive, member_argc, member_argv, + options.verbose, prog ) ) + goto out; + rc = EXIT_SUCCESS; + goto out; + case 'p': + if( operation_print( &archive, member_argc, member_argv, + options.verbose, prog ) ) + goto out; + rc = EXIT_SUCCESS; + goto out; + case 'x': + if( operation_extract( &archive, member_argc, member_argv, + options.verbose, prog ) ) + goto out; + rc = EXIT_SUCCESS; + goto out; + case 's': + if( member_argc ) + { + fprintf( stderr, "%s: operation s does not accept member operands\n", + prog ); + goto out; + } + mutation = 1; + options.index = 1; + break; + default: + goto out; + } + + if( mutation ) + { + int write_index = options.index || archive.had_symbol_index; + + err[0] = 0; + if( mcpu_archive_write( archive_path, &archive, write_index, + err, sizeof( err ) ) ) + { + fprintf( stderr, "%s: %s: %s\n", prog, archive_path, err ); + goto out; + } + } + + rc = EXIT_SUCCESS; + +out: + mcpu_archive_free( &archive ); + return( rc ); + +} /* End of main() */ diff --git a/utils/mcpu-archive.c b/utils/mcpu-archive.c new file mode 100644 index 0000000..e10dd5c --- /dev/null +++ b/utils/mcpu-archive.c @@ -0,0 +1,1025 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <fcntl.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "mcpu-archive.h" +#include "mcpu-asm.h" +#include "mcpu-melf.h" +#include "mcpu-object.h" + + +#define MCPU_AR_MAGIC "!<arch>\n" +#define MCPU_AR_MAGIC_SIZE 8U +#define MCPU_AR_HEADER_SIZE 60U + + +typedef struct mcpu_ar_raw_header +{ + char name[16]; + char date[12]; + char uid[6]; + char gid[6]; + char mode[8]; + char size[10]; + char fmag[2]; +} mcpu_ar_raw_header; + + +static int +add_size( size_t a, size_t b, size_t *result ) +{ + if( b > SIZE_MAX - a ) + return( -1 ); + *result = a + b; + return( 0 ); + +} /* End of add_size() */ + + +static int +mul_size( size_t a, size_t b, size_t *result ) +{ + if( a && b > SIZE_MAX / a ) + return( -1 ); + *result = a * b; + return( 0 ); + +} /* End of mul_size() */ + + +static char * +copy_string( const char *s, size_t n ) +{ + char *p = (char *)malloc( n + 1 ); + + if( !p ) + return( NULL ); + memcpy( p, s, n ); + p[n] = 0; + return( p ); + +} /* End of copy_string() */ + + +const char * +mcpu_archive_basename( const char *path ) +{ + const char *p; + + if( !path ) + return( NULL ); + p = strrchr( path, '/' ); + return( p ? p + 1 : path ); + +} /* End of mcpu_archive_basename() */ + + +static int +valid_member_name( const char *name ) +{ + const unsigned char *p = (const unsigned char *)name; + + if( !name || !*name || !strcmp( name, "." ) || !strcmp( name, ".." ) ) + return( 0 ); + + while( *p ) + { + if( *p == '/' || *p == '\n' || *p == '\r' || *p == 0x7f ) + return( 0 ); + ++p; + } + return( 1 ); + +} /* End of valid_member_name() */ + + +void +mcpu_archive_member_free( mcpu_archive_member *member ) +{ + size_t i; + + if( !member ) + return; + + free( member->name ); + free( member->data ); + for( i = 0; i < member->symbol_count; ++i ) + free( member->symbols[i] ); + free( member->symbols ); + memset( member, 0, sizeof( *member ) ); + +} /* End of mcpu_archive_member_free() */ + + +void +mcpu_archive_init( mcpu_archive *archive ) +{ + if( archive ) + memset( archive, 0, sizeof( *archive ) ); + +} /* End of mcpu_archive_init() */ + + +void +mcpu_archive_free( mcpu_archive *archive ) +{ + size_t i; + + if( !archive ) + return; + + for( i = 0; i < archive->member_count; ++i ) + mcpu_archive_member_free( &archive->members[i] ); + free( archive->members ); + memset( archive, 0, sizeof( *archive ) ); + +} /* End of mcpu_archive_free() */ + + +static int +member_add_symbol( mcpu_archive_member *member, const char *name, + char *err, size_t errsz ) +{ + char **q; + char *s; + + q = (char **)realloc( member->symbols, + (member->symbol_count + 1) * sizeof( *q ) ); + if( !q ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + + s = strdup( name ); + if( !s ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + + member->symbols = q; + member->symbols[member->symbol_count++] = s; + return( 0 ); + +} /* End of member_add_symbol() */ + + +static int +member_collect_symbols( const char *path, mcpu_archive_member *member, + char *err, size_t errsz ) +{ + cpu_asm_object object; + size_t i; + + memset( &object, 0, sizeof( object ) ); + if( cpu_object_read( path, &object, err, errsz ) ) + return( -1 ); + + for( i = 0; i < object.symbol_count; ++i ) + { + const cpu_asm_symbol *s = &object.symbols[i]; + + if( s->section == CPU_OBJ_SEC_UNDEF || s->binding == CPU_OBJ_BIND_LOCAL ) + continue; + if( member_add_symbol( member, s->name, err, errsz ) ) + { + cpu_asm_object_free( &object ); + return( -1 ); + } + } + + cpu_asm_object_free( &object ); + return( 0 ); + +} /* End of member_collect_symbols() */ + + + + + +static int +member_collect_symbols_data( const unsigned char *data, size_t size, + mcpu_archive_member *member, + char *err, size_t errsz ) +{ + cpu_asm_object object; + size_t i; + + memset( &object, 0, sizeof( object ) ); + if( cpu_object_read_data(data, size, &object, err, errsz) ) + return( -1 ); + + for( i = 0; i < object.symbol_count; ++i ) + { + const cpu_asm_symbol *s = &object.symbols[i]; + + if( s->section == CPU_OBJ_SEC_UNDEF || s->binding == CPU_OBJ_BIND_LOCAL ) + continue; + if( member_add_symbol(member, s->name, err, errsz) ) + { + cpu_asm_object_free( &object ); + return( -1 ); + } + } + + cpu_asm_object_free( &object ); + return( 0 ); + +} /* End of member_collect_symbols_data() */ + + +int +mcpu_archive_member_load( const char *path, mcpu_archive_member *member, + char *err, size_t errsz ) +{ + const char *name; + unsigned char *data = NULL; + size_t size = 0; + + if( !path || !member ) + return( -1 ); + memset( member, 0, sizeof( *member ) ); + + name = mcpu_archive_basename( path ); + if( !valid_member_name( name ) ) + { + snprintf( err, errsz, "invalid archive member name: %s", + name ? name : "<null>" ); + return( -1 ); + } + + if( cpu_melf_readall( path, &data, &size, err, errsz ) ) + return( -1 ); + + member->name = strdup( name ); + member->data = data; + member->size = size; + if( !member->name ) + { + snprintf( err, errsz, "out of memory" ); + mcpu_archive_member_free( member ); + return( -1 ); + } + + if( member_collect_symbols( path, member, err, errsz ) ) + { + mcpu_archive_member_free( member ); + return( -1 ); + } + return( 0 ); + +} /* End of mcpu_archive_member_load() */ + + +int +mcpu_archive_append( mcpu_archive *archive, mcpu_archive_member *member, + char *err, size_t errsz ) +{ + mcpu_archive_member *q; + + if( !archive || !member ) + return( -1 ); + + q = (mcpu_archive_member *)realloc( archive->members, + (archive->member_count + 1) * sizeof( *q ) ); + if( !q ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + + archive->members = q; + archive->members[archive->member_count++] = *member; + memset( member, 0, sizeof( *member ) ); + return( 0 ); + +} /* End of mcpu_archive_append() */ + + +static int +parse_decimal_field( const char *p, size_t n, size_t *value ) +{ + size_t i = 0; + size_t v = 0; + int any = 0; + + while( i < n && p[i] == ' ' ) + ++i; + while( i < n && p[i] != ' ' ) + { + unsigned d; + + if( p[i] < '0' || p[i] > '9' ) + return( -1 ); + d = (unsigned)(p[i] - '0'); + if( v > (SIZE_MAX - d) / 10 ) + return( -1 ); + v = v * 10 + d; + any = 1; + ++i; + } + while( i < n ) + { + if( p[i] != ' ' ) + return( -1 ); + ++i; + } + + if( !any ) + return( -1 ); + *value = v; + return( 0 ); + +} /* End of parse_decimal_field() */ + + +static int +raw_name( const mcpu_ar_raw_header *h, char *name, size_t namesz ) +{ + size_t n = sizeof( h->name ); + + while( n && h->name[n - 1] == ' ' ) + --n; + if( n + 1 > namesz ) + return( -1 ); + memcpy( name, h->name, n ); + name[n] = 0; + return( 0 ); + +} /* End of raw_name() */ + + +static int +long_name_at( const unsigned char *tab, size_t tabsize, size_t off, + char **name, char *err, size_t errsz ) +{ + size_t end; + + if( off >= tabsize ) + { + snprintf( err, errsz, "bad GNU archive long-name offset" ); + return( -1 ); + } + + end = off; + while( end < tabsize && tab[end] != '\n' && tab[end] != 0 ) + ++end; + if( end == tabsize ) + { + snprintf( err, errsz, "unterminated GNU archive long name" ); + return( -1 ); + } + + if( end > off && tab[end - 1] == '/' ) + --end; + *name = copy_string( (const char *)tab + off, end - off ); + if( !*name ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + return( 0 ); + +} /* End of long_name_at() */ + + +static int +read_member_name( const mcpu_ar_raw_header *h, + const unsigned char *payload, size_t payload_size, + const unsigned char *longtab, size_t longtab_size, + char **name, size_t *name_prefix, + int *special, char *err, size_t errsz ) +{ + char raw[17]; + size_t n; + + *name = NULL; + *name_prefix = 0; + *special = 0; + + if( raw_name( h, raw, sizeof( raw ) ) ) + { + snprintf( err, errsz, "bad archive member name" ); + return( -1 ); + } + + if( !strcmp( raw, "/" ) || !strcmp( raw, "/SYM64/" ) ) + { + *special = 1; + return( 0 ); + } + if( !strcmp( raw, "//" ) ) + { + *special = 2; + return( 0 ); + } + + if( !strncmp( raw, "#1/", 3 ) ) + { + if( parse_decimal_field( raw + 3, strlen( raw + 3 ), &n ) || + n == 0 || n > payload_size ) + { + snprintf( err, errsz, "bad BSD archive extended name" ); + return( -1 ); + } + *name = copy_string( (const char *)payload, n ); + if( !*name ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + *name_prefix = n; + } + else if( raw[0] == '/' && raw[1] >= '0' && raw[1] <= '9' ) + { + size_t off; + + if( !longtab || parse_decimal_field( raw + 1, strlen( raw + 1 ), &off ) || + long_name_at( longtab, longtab_size, off, name, err, errsz ) ) + return( -1 ); + } + else + { + n = strlen( raw ); + if( n && raw[n - 1] == '/' ) + --n; + *name = copy_string( raw, n ); + if( !*name ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + } + + if( !valid_member_name( *name ) ) + { + snprintf( err, errsz, "invalid archive member name: %s", *name ); + free( *name ); + *name = NULL; + return( -1 ); + } + return( 0 ); + +} /* End of read_member_name() */ + + +int +mcpu_archive_read( const char *path, mcpu_archive *archive, + char *err, size_t errsz ) +{ + unsigned char *file = NULL; + size_t file_size = 0; + size_t off; + const unsigned char *longtab = NULL; + size_t longtab_size = 0; + int rc = -1; + + if( !path || !archive ) + return( -1 ); + mcpu_archive_init( archive ); + + if( cpu_melf_readall( path, &file, &file_size, err, errsz ) ) + return( -1 ); + if( file_size < MCPU_AR_MAGIC_SIZE || + memcmp( file, MCPU_AR_MAGIC, MCPU_AR_MAGIC_SIZE ) ) + { + snprintf( err, errsz, "not a UNIX archive" ); + goto out; + } + + off = MCPU_AR_MAGIC_SIZE; + while( off < file_size ) + { + const mcpu_ar_raw_header *h; + size_t stored_size; + size_t next; + const unsigned char *payload; + char *name = NULL; + size_t name_prefix = 0; + int special = 0; + mcpu_archive_member member; + + memset( &member, 0, sizeof( member ) ); + + if( file_size - off < MCPU_AR_HEADER_SIZE ) + { + snprintf( err, errsz, "truncated archive member header" ); + goto out; + } + h = (const mcpu_ar_raw_header *)(file + off); + if( h->fmag[0] != '`' || h->fmag[1] != '\n' || + parse_decimal_field( h->size, sizeof( h->size ), &stored_size ) ) + { + snprintf( err, errsz, "bad archive member header" ); + goto out; + } + + if( stored_size > file_size - off - MCPU_AR_HEADER_SIZE ) + { + snprintf( err, errsz, "truncated archive member" ); + goto out; + } + payload = file + off + MCPU_AR_HEADER_SIZE; + + if( read_member_name( h, payload, stored_size, longtab, longtab_size, + &name, &name_prefix, &special, err, errsz ) ) + goto out; + + if( special == 1 ) + archive->had_symbol_index = 1; + else if( special == 2 ) + { + longtab = payload; + longtab_size = stored_size; + } + else + { + if( name_prefix > stored_size ) + { + free( name ); + snprintf( err, errsz, "bad archive extended member size" ); + goto out; + } + + member.name = name; + member.size = stored_size - name_prefix; + member.data = (unsigned char *)malloc( member.size ? member.size : 1 ); + if( !member.data ) + { + snprintf( err, errsz, "out of memory" ); + mcpu_archive_member_free( &member ); + goto out; + } + if( member.size ) + memcpy( member.data, payload + name_prefix, member.size ); + + if( member_collect_symbols_data( member.data, member.size, &member, + err, errsz ) || + mcpu_archive_append( archive, &member, err, errsz ) ) + { + mcpu_archive_member_free( &member ); + goto out; + } + } + + if( add_size( off, MCPU_AR_HEADER_SIZE, &next ) || + add_size( next, stored_size, &next ) ) + { + snprintf( err, errsz, "archive offset overflow" ); + goto out; + } + if( stored_size & 1 ) + { + if( next >= file_size || file[next] != '\n' ) + { + snprintf( err, errsz, "bad archive member padding" ); + goto out; + } + ++next; + } + off = next; + } + + rc = 0; + +out: + free( file ); + if( rc ) + mcpu_archive_free( archive ); + return( rc ); + +} /* End of mcpu_archive_read() */ + + +static int +short_member_name( const char *name ) +{ + const unsigned char *p = (const unsigned char *)name; + size_t n = strlen( name ); + + if( n == 0 || n > 15 ) + return( 0 ); + while( *p ) + { + if( *p == ' ' || *p == '/' || *p == '\t' ) + return( 0 ); + ++p; + } + return( 1 ); + +} /* End of short_member_name() */ + + +static int +member_stored_size( const mcpu_archive_member *member, size_t *size ) +{ + size_t n = short_member_name( member->name ) ? 0 : strlen( member->name ); + + return( add_size( n, member->size, size ) ); + +} /* End of member_stored_size() */ + + +static int +format_field( char *dst, size_t n, const char *text ) +{ + size_t len = strlen( text ); + + if( len > n ) + return( -1 ); + memcpy( dst, text, len ); + memset( dst + len, ' ', n - len ); + return( 0 ); + +} /* End of format_field() */ + + +static int +format_size_field( char *dst, size_t n, size_t value ) +{ + char text[32]; + + snprintf( text, sizeof( text ), "%zu", value ); + return( format_field( dst, n, text ) ); + +} /* End of format_size_field() */ + + +static int +make_header( mcpu_ar_raw_header *h, const char *name, size_t stored_size, + int special ) +{ + char namebuf[32]; + + memset( h, ' ', sizeof( *h ) ); + + if( special ) + { + if( format_field( h->name, sizeof( h->name ), name ) ) + return( -1 ); + } + else if( short_member_name( name ) ) + { + snprintf( namebuf, sizeof( namebuf ), "%s/", name ); + if( format_field( h->name, sizeof( h->name ), namebuf ) ) + return( -1 ); + } + else + { + snprintf( namebuf, sizeof( namebuf ), "#1/%zu", strlen( name ) ); + if( format_field( h->name, sizeof( h->name ), namebuf ) ) + return( -1 ); + } + + if( format_field( h->date, sizeof( h->date ), "0" ) || + format_field( h->uid, sizeof( h->uid ), "0" ) || + format_field( h->gid, sizeof( h->gid ), "0" ) || + format_field( h->mode, sizeof( h->mode ), special ? "0" : "644" ) || + format_size_field( h->size, sizeof( h->size ), stored_size ) ) + return( -1 ); + + h->fmag[0] = '`'; + h->fmag[1] = '\n'; + return( 0 ); + +} /* End of make_header() */ + + +static int +write_bytes( FILE *fp, const void *data, size_t size ) +{ + return( size && fwrite( data, 1, size, fp ) != size ? -1 : 0 ); + +} /* End of write_bytes() */ + + +static void +put_be32( unsigned char *p, uint32_t v ) +{ + p[0] = (unsigned char)(v >> 24); + p[1] = (unsigned char)(v >> 16); + p[2] = (unsigned char)(v >> 8); + p[3] = (unsigned char)v; + +} /* End of put_be32() */ + + +static int +symbol_index_size( const mcpu_archive *archive, size_t *count, + size_t *payload_size, char *err, size_t errsz ) +{ + size_t i, j; + size_t c = 0; + size_t names = 0; + size_t offsets; + size_t total; + + for( i = 0; i < archive->member_count; ++i ) + { + for( j = 0; j < archive->members[i].symbol_count; ++j ) + { + size_t n = strlen( archive->members[i].symbols[j] ) + 1; + + if( add_size( names, n, &names ) || c == SIZE_MAX ) + { + snprintf( err, errsz, "archive symbol index is too large" ); + return( -1 ); + } + ++c; + } + } + + if( c > UINT32_MAX || mul_size( c, 4, &offsets ) || + add_size( 4, offsets, &total ) || add_size( total, names, &total ) ) + { + snprintf( err, errsz, "archive symbol index is too large" ); + return( -1 ); + } + + *count = c; + *payload_size = total; + return( 0 ); + +} /* End of symbol_index_size() */ + + +static int +compute_member_offsets( const mcpu_archive *archive, int write_index, + size_t index_size, uint32_t **offsets, + char *err, size_t errsz ) +{ + uint32_t *v; + size_t off = MCPU_AR_MAGIC_SIZE; + size_t i; + + v = (uint32_t *)calloc( archive->member_count ? archive->member_count : 1, + sizeof( *v ) ); + if( !v ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + + if( write_index ) + { + if( add_size( off, MCPU_AR_HEADER_SIZE, &off ) || + add_size( off, index_size, &off ) ) + { + snprintf( err, errsz, "archive is too large" ); + free( v ); + return( -1 ); + } + if( index_size & 1 ) + { + if( off == SIZE_MAX ) + { + snprintf( err, errsz, "archive is too large" ); + free( v ); + return( -1 ); + } + ++off; + } + } + + for( i = 0; i < archive->member_count; ++i ) + { + size_t stored; + + if( off > UINT32_MAX || member_stored_size( &archive->members[i], &stored ) ) + { + snprintf( err, errsz, "archive is too large for a 32-bit symbol index" ); + free( v ); + return( -1 ); + } + v[i] = (uint32_t)off; + + if( add_size( off, MCPU_AR_HEADER_SIZE, &off ) || + add_size( off, stored, &off ) ) + { + snprintf( err, errsz, "archive is too large" ); + free( v ); + return( -1 ); + } + if( stored & 1 ) + { + if( off == SIZE_MAX ) + { + snprintf( err, errsz, "archive is too large" ); + free( v ); + return( -1 ); + } + ++off; + } + } + + *offsets = v; + return( 0 ); + +} /* End of compute_member_offsets() */ + + +static int +write_armap( FILE *fp, const mcpu_archive *archive, + const uint32_t *member_offsets, size_t count, + size_t payload_size, char *err, size_t errsz ) +{ + mcpu_ar_raw_header h; + unsigned char *payload; + size_t pos; + size_t i, j, k = 0; + + if( make_header( &h, "/", payload_size, 1 ) ) + { + snprintf( err, errsz, "archive symbol index header is too large" ); + return( -1 ); + } + + payload = (unsigned char *)malloc( payload_size ? payload_size : 1 ); + if( !payload ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + + put_be32( payload, (uint32_t)count ); + pos = 4; + for( i = 0; i < archive->member_count; ++i ) + for( j = 0; j < archive->members[i].symbol_count; ++j ) + { + put_be32( payload + pos, member_offsets[i] ); + pos += 4; + ++k; + } + + for( i = 0; i < archive->member_count; ++i ) + for( j = 0; j < archive->members[i].symbol_count; ++j ) + { + size_t n = strlen( archive->members[i].symbols[j] ) + 1; + memcpy( payload + pos, archive->members[i].symbols[j], n ); + pos += n; + } + + if( k != count || pos != payload_size || + write_bytes( fp, &h, sizeof( h ) ) || + write_bytes( fp, payload, payload_size ) || + (payload_size & 1 && fputc( '\n', fp ) == EOF) ) + { + snprintf( err, errsz, "cannot write archive symbol index: %s", + strerror( errno ? errno : EIO ) ); + free( payload ); + return( -1 ); + } + + free( payload ); + return( 0 ); + +} /* End of write_symbol_index() */ + + +static int +write_member( FILE *fp, const mcpu_archive_member *member, + char *err, size_t errsz ) +{ + mcpu_ar_raw_header h; + size_t stored; + int shortname = short_member_name( member->name ); + + if( member_stored_size( member, &stored ) || + make_header( &h, member->name, stored, 0 ) ) + { + snprintf( err, errsz, "archive member is too large: %s", member->name ); + return( -1 ); + } + + if( write_bytes( fp, &h, sizeof( h ) ) || + (!shortname && write_bytes( fp, member->name, strlen( member->name ) )) || + write_bytes( fp, member->data, member->size ) || + (stored & 1 && fputc( '\n', fp ) == EOF) ) + { + snprintf( err, errsz, "cannot write archive member %s: %s", + member->name, strerror( errno ? errno : EIO ) ); + return( -1 ); + } + return( 0 ); + +} /* End of write_member() */ + + +int +mcpu_archive_write( const char *path, const mcpu_archive *archive, + int write_symbol_index, char *err, size_t errsz ) +{ + char *tmp; + size_t tmpsz; + int fd = -1; + FILE *fp = NULL; + size_t symbol_count = 0; + size_t index_size = 0; + uint32_t *member_offsets = NULL; + size_t i; + int rc = -1; + + if( !path || !archive ) + return( -1 ); + + if( write_symbol_index && + symbol_index_size( archive, &symbol_count, &index_size, err, errsz ) ) + return( -1 ); + if( write_symbol_index && + compute_member_offsets( archive, 1, index_size, + &member_offsets, err, errsz ) ) + return( -1 ); + + if( add_size( strlen( path ), sizeof( ".tmp.XXXXXX" ), &tmpsz ) ) + { + snprintf( err, errsz, "archive pathname is too long" ); + goto out; + } + tmp = (char *)malloc( tmpsz ); + if( !tmp ) + { + snprintf( err, errsz, "out of memory" ); + goto out; + } + snprintf( tmp, tmpsz, "%s.tmp.XXXXXX", path ); + + fd = mkstemp( tmp ); + if( fd < 0 ) + { + snprintf( err, errsz, "cannot create archive temporary file: %s", + strerror( errno ) ); + free( tmp ); + goto out; + } + fp = fdopen( fd, "wb" ); + if( !fp ) + { + snprintf( err, errsz, "cannot open archive temporary stream: %s", + strerror( errno ) ); + close( fd ); + fd = -1; + unlink( tmp ); + free( tmp ); + goto out; + } + fd = -1; + + if( write_bytes( fp, MCPU_AR_MAGIC, MCPU_AR_MAGIC_SIZE ) || + (write_symbol_index && + write_armap( fp, archive, member_offsets, symbol_count, + index_size, err, errsz )) ) + goto write_fail; + + for( i = 0; i < archive->member_count; ++i ) + if( write_member( fp, &archive->members[i], err, errsz ) ) + goto write_fail; + + if( fflush( fp ) || fsync( fileno( fp ) ) || fclose( fp ) ) + { + fp = NULL; + snprintf( err, errsz, "cannot finalize archive: %s", strerror( errno ) ); + unlink( tmp ); + free( tmp ); + goto out; + } + fp = NULL; + + if( rename( tmp, path ) ) + { + snprintf( err, errsz, "cannot install archive %s: %s", + path, strerror( errno ) ); + unlink( tmp ); + free( tmp ); + goto out; + } + free( tmp ); + rc = 0; + goto out; + +write_fail: + if( !err[0] ) + snprintf( err, errsz, "cannot write archive: %s", strerror( errno ) ); + fclose( fp ); + fp = NULL; + unlink( tmp ); + free( tmp ); + +out: + if( fp ) + fclose( fp ); + if( fd >= 0 ) + close( fd ); + free( member_offsets ); + return( rc ); + +} /* End of mcpu_archive_write() */ diff --git a/utils/mcpu-archive.h b/utils/mcpu-archive.h new file mode 100644 index 0000000..80634f0 --- /dev/null +++ b/utils/mcpu-archive.h @@ -0,0 +1,41 @@ +#ifndef __MCPU_ARCHIVE_H +#define __MCPU_ARCHIVE_H + +#include <stddef.h> + + +typedef struct mcpu_archive_member +{ + char *name; + unsigned char *data; + size_t size; + char **symbols; + size_t symbol_count; +} mcpu_archive_member; + + +typedef struct mcpu_archive +{ + mcpu_archive_member *members; + size_t member_count; + int had_symbol_index; +} mcpu_archive; + + +void mcpu_archive_init( mcpu_archive *archive ); +void mcpu_archive_free( mcpu_archive *archive ); +void mcpu_archive_member_free( mcpu_archive_member *member ); + +int mcpu_archive_read( const char *path, mcpu_archive *archive, + char *err, size_t errsz ); +int mcpu_archive_write( const char *path, const mcpu_archive *archive, + int write_symbol_index, char *err, size_t errsz ); + +int mcpu_archive_member_load( const char *path, mcpu_archive_member *member, + char *err, size_t errsz ); +int mcpu_archive_append( mcpu_archive *archive, mcpu_archive_member *member, + char *err, size_t errsz ); + +const char *mcpu_archive_basename( const char *path ); + +#endif diff --git a/utils/mcpu-as.c b/utils/mcpu-as.c new file mode 100644 index 0000000..a3b38c1 --- /dev/null +++ b/utils/mcpu-as.c @@ -0,0 +1,612 @@ +#include <ctype.h> +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <libmcpu.h> +#include <libmpuio.h> +#include "mcpu-asm.h" +#include "mcpu-object.h" +#include "mcpu-version.h" + +#define CPU_AS_LINE_MAX 2147483647ULL + +typedef struct source_marker +{ + size_t physical_line; + uint64_t logical_line; + char *filename; +} source_marker; + +typedef struct source_map +{ + source_marker *marker; + size_t count; + size_t capacity; +} source_map; + +static char * +readall( const char *name ) +{ + FILE *f; + long z; + char *p; + + f = fopen( name, "rb" ); + if( !f ) + return( NULL ); + if( fseek( f, 0, SEEK_END ) != 0 ) + { + fclose( f ); + return( NULL ); + } + z = ftell( f ); + if( z < 0 ) + { + fclose( f ); + return( NULL ); + } + rewind( f ); + p = (char *)malloc( (size_t)z + 1U ); + if( !p ) + { + fclose( f ); + return( NULL ); + } + if( fread( p, 1, (size_t)z, f ) != (size_t)z ) + { + free( p ); + fclose( f ); + return( NULL ); + } + p[z] = 0; + fclose( f ); + return( p ); +} + +static void +usage( FILE *fp, const char *name ) +{ + fprintf( fp, + "usage: %s [options] -o output.o input.s\n\n" + "Assemble UTF-8 LibMCPU source into a MELF ET_REL relocatable object.\n\n" + "Options:\n" + " -o <file> - write output to <file>\n" + " -v, --verbose - print output-object summary\n" + " -h, --help - show this help\n" + " --version - show program version\n" + " --dump-version - show version number only\n\n" + "Exactly one input source is required. The input file and -o option may\n" + "appear in either order.\n", + name); +} + +static void +source_map_free( source_map *map ) +{ + size_t i; + + if( !map ) + return; + for( i = 0; i < map->count; ++i ) + free( map->marker[i].filename ); + free( map->marker ); + memset( map, 0, sizeof( *map ) ); +} + +static int +source_map_add( source_map *map, size_t physical_line, uint64_t logical_line, + const char *filename) +{ + source_marker *q; + size_t capacity; + + if( map->count == map->capacity ) + { + capacity = map->capacity ? map->capacity * 2U : 8U; + q = ( source_marker * ) realloc( map->marker, capacity *sizeof( *q ) ); + if( !q ) + return( -1 ); + map->marker = q; + map->capacity = capacity; + } + + map->marker[map->count].physical_line = physical_line; + map->marker[map->count].logical_line = logical_line; + map->marker[map->count].filename = strdup( filename ? filename : "<input>" ); + if( !map->marker[map->count].filename ) + return( -1 ); + ++map->count; + return( 0 ); +} + +static const source_marker * +source_map_lookup( const source_map *map, size_t physical_line ) +{ + size_t i; + const source_marker *found = NULL; + + for( i = 0; i < map->count; ++i ) + { + if( map->marker[i].physical_line > physical_line ) + break; + found = &map->marker[i]; + } + return( found ); +} + +static uint64_t +source_map_logical_line( const source_marker *m, size_t physical_line ) +{ + uint64_t delta; + + if( !m || physical_line < m->physical_line ) + return (uint64_t)physical_line; + delta = (uint64_t)(physical_line - m->physical_line); + if( UINT64_MAX - m->logical_line < delta ) + return( UINT64_MAX ); + return( m->logical_line + delta ); +} + +static int +horizontal_space( int c ) +{ + return( c == ' ' || c == '\t' || c == '\v' || c == '\f' || c == '\r' ); +} + +/* + * Classify source-control lines left by a preprocessor. + * + * Return values: + * 0 ordinary source line + * 1 other '#' line: ignore its contents, but count the line normally + * 2 #pragma / #lang: ignore its contents and do not advance logical line + * 3 preprocessor line marker: # decimal ["filename"] [flags...] + * -1 malformed line marker + * + * A line marker applies to the physical source line immediately following it. + * Its decimal number is always base 10 and is restricted to 0..2147483647. + * If the filename is omitted, the previous logical filename is retained. Any + * text following the closing filename quote is deliberately ignored: those are + * preprocessor flags for later consumers and have no assembler semantics. + * + * The historical #line spelling is intentionally not recognized here. Like + * every other unrecognized '#' line, it is discarded without affecting + * assembly syntax and counts as one ordinary logical source line. + */ +static int +parse_source_control( const char *line, uint64_t *next_line, char **new_filename, + char *why, size_t whysz ) +{ + const char *p = line; + const char *digits; + const char *start; + uint64_t value = 0; + size_t n; + char *filename = NULL; + + while( horizontal_space( (unsigned char)*p ) ) + ++p; + if( *p != '#' ) + return( 0 ); + ++p; + while( horizontal_space( (unsigned char)*p ) ) + ++p; + + if( !strncmp( p, "pragma", 6 ) && + (!p[6] || horizontal_space( (unsigned char)p[6] )) ) + return( 2 ); + if( !strncmp( p, "lang", 4 ) && + (!p[4] || horizontal_space( (unsigned char)p[4] )) ) + return( 2 ); + + if( !isdigit( (unsigned char)*p ) ) + return( 1 ); + + digits = p; + while( isdigit( (unsigned char)*p ) ) + ++p; + + if( *p && !horizontal_space( (unsigned char)*p ) ) + return( 1 ); + + while( horizontal_space( (unsigned char)*p ) ) + ++p; + if( *p && *p != '"' ) + return( 1 ); + + p = digits; + do + { + unsigned int digit = (unsigned int)(*p - '0'); + + if( value > ( CPU_AS_LINE_MAX - digit ) / 10ULL ) + { + snprintf( why, whysz, + "preprocessor line marker value is outside 0..2147483647" ); + return( -1 ); + } + value = value * 10ULL + digit; + ++p; + } + while( isdigit( (unsigned char)*p ) ); + + while( horizontal_space( (unsigned char)*p ) ) + ++p; + if( *p == '"' ) + { + ++p; + start = p; + while( *p && *p != '"' ) + ++p; + if( *p != '"' ) + { + snprintf( why, whysz, + "unterminated filename in preprocessor line marker" ); + return( -1 ); + } + n = (size_t)(p - start); + filename = (char *)malloc( n + 1U ); + if( !filename ) + { + snprintf( why, whysz, "out of memory" ); + return( -1 ); + } + memcpy( filename, start, n ); + filename[n] = 0; + } + + *next_line = value; + *new_filename = filename; + return( 3 ); +} + +static void +format_location_error( char *err, size_t errsz, const char *filename, + uint64_t line, const char *message) +{ + snprintf( err, errsz, "%s:%llu: error: %s", + filename ? filename : "<input>", + (unsigned long long)line, + message ? message : "invalid source"); +} + +/* + * cpu_asm's historical line scanner uses strtok_r(..., "\n", ...), which + * skips zero-length fields between adjacent newlines. That is harmless for + * assembly semantics, but it means its internal diagnostic line number does + * not advance across a physically empty source line. + * + * Preprocessor line markers, however, define logical source coordinates, and + * #pragma/#lang lines may be excluded from the logical counter. The physical + * line counter used by cpu_asm therefore has to stay synchronized with the + * source map maintained by mcpu-as, including across empty lines. + * + * Give each physically empty line a single horizontal-space character before + * passing the source to cpu_asm. trim() still treats the line as empty, so no + * assembly semantics change, while strtok_r now returns one token for every + * physical line and its diagnostic counter remains synchronized with the + * source map maintained by mcpu-as. + */ +static char * +copy_preserving_empty_lines( const char *source ) +{ + size_t len, extra = 0, i; + int at_line_start = 1; + char *copy, *q; + + if( !source ) + return( NULL ); + + len = strlen( source ); + for( i = 0; i < len; ++i ) + { + if( source[i] == '\n' ) + { + if( at_line_start ) + ++extra; + at_line_start = 1; + } + else + at_line_start = 0; + } + + if( extra >= SIZE_MAX - len ) + return( NULL ); + copy = (char *)malloc( len + extra + 1U ); + if( !copy ) + return( NULL ); + + q = copy; + at_line_start = 1; + for( i = 0; i < len; ++i ) + { + if( source[i] == '\n' ) + { + if( at_line_start ) + *q++ = ' '; + *q++ = '\n'; + at_line_start = 1; + } + else + { + *q++ = source[i]; + at_line_start = 0; + } + } + *q = 0; + return( copy ); +} + +/* + * Remove preprocessor-control lines from the token stream without changing + * physical line numbering and build a physical->logical source map for + * diagnostics. + */ +static int +prepare_source( const char *source, const char *input_name, char **prepared, + source_map *map, char *err, size_t errsz) +{ + char *copy; + char *line; + size_t physical_line = 1; + const char *logical_file; + + if( !source || !prepared || !map ) + return( -1 ); + + memset( map, 0, sizeof( *map ) ); + copy = copy_preserving_empty_lines( source ); + if( !copy ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + + logical_file = input_name ? input_name : "<input>"; + if( source_map_add( map, 1U, 1U, logical_file ) ) + { + free( copy ); + source_map_free( map ); + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + + line = copy; + while( line && *line ) + { + char *end = strchr( line, '\n' ); + char saved = 0; + uint64_t requested_line = 0; + char *requested_file = NULL; + char why[160]; + int parsed; + const source_marker *m; + + if( end ) + { + saved = *end; + *end = 0; + } + + why[0] = 0; + parsed = parse_source_control( line, &requested_line, &requested_file, + why, sizeof( why ) ); + if( parsed < 0 ) + { + m = source_map_lookup( map, physical_line ); + format_location_error( err, errsz, + m ? m->filename : logical_file, + source_map_logical_line( m, physical_line ), why); + free( requested_file ); + if( end ) + *end = saved; + free( copy ); + source_map_free( map ); + return( -1 ); + } + + if( parsed > 0 ) + { + size_t len = strlen( line ); + + memset( line, ' ', len ); + if( parsed == 3 ) + { + const source_marker *current = source_map_lookup( map, physical_line ); + const char *next_file = requested_file ? requested_file : + (current ? current->filename : logical_file); + + if( source_map_add( map, physical_line + 1U, requested_line, next_file ) ) + { + free( requested_file ); + if( end ) + *end = saved; + free( copy ); + source_map_free( map ); + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + } + else if( parsed == 2 ) + { + const source_marker *current = source_map_lookup( map, physical_line ); + uint64_t current_line = source_map_logical_line( current, physical_line ); + const char *current_file = current ? current->filename : logical_file; + + if( source_map_add( map, physical_line + 1U, current_line, current_file ) ) + { + free( requested_file ); + if( end ) + *end = saved; + free( copy ); + source_map_free( map ); + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + } + } + + free( requested_file ); + if( end ) + { + *end = saved; + line = end + 1; + ++physical_line; + } + else + break; + } + + *prepared = copy; + return( 0 ); +} + +static void +remap_assembler_error( char *err, size_t errsz, const source_map *map, + const char *input_name) +{ + char old[512]; + const char *p; + char *end; + unsigned long long physical; + const source_marker *m; + uint64_t logical; + + if( !err || !*err ) + return; + if( strncmp( err, "line ", 5 ) != 0 ) + return; + + errno = 0; + p = err + 5; + physical = strtoull( p, &end, 10 ); + if( errno || end == p || *end != ':' || physical == 0ULL || + physical > (unsigned long long)SIZE_MAX) + return; + + snprintf( old, sizeof( old ), "%s", end + 1 ); + p = old; + while( *p == ' ' || *p == '\t' ) + ++p; + + m = source_map_lookup( map, (size_t)physical ); + logical = source_map_logical_line( m, (size_t)physical ); + format_location_error( err, errsz, + m ? m->filename : (input_name ? input_name : "<input>"), + logical, p); +} + +int +main( int ac, char **av ) +{ + const char *input = NULL; + const char *output = NULL; + char *src = NULL; + char *prepared = NULL; + char err[512]; + cpu_asm_object object; + source_map map; + int i; + int rc = 1; + int verbose = 0; + + for( i = 1; i < ac; ++i ) + { + mcpu_version_action version_action; + + if( mcpu_version_parse_option(av[i], &version_action) ) + return( mcpu_version_print(av[0], version_action) == 0 ? 0 : 1 ); + + if( !strcmp( av[i], "-h" ) || !strcmp( av[i], "--help" ) ) + { + usage( stdout, av[0] ); + return( 0 ); + } + else if( !strcmp( av[i], "-o" ) ) + { + if(++i >= ac || output ) + { + usage( stderr, av[0] ); + return( 2 ); + } + output = av[i]; + } + else if( !strcmp( av[i], "-v" ) || !strcmp( av[i], "--verbose" ) ) + verbose = 1; + else if( av[i][0] == '-' ) + { + fprintf( stderr, "mcpu-as: unknown option %s\n", av[i] ); + return( 2 ); + } + else + { + if( input ) + { + fprintf( stderr, "mcpu-as: more than one input file\n" ); + return( 2 ); + } + input = av[i]; + } + } + + if( !input || !output ) + { + usage( stderr, av[0] ); + return( 2 ); + } + + __mpu_init(); + src = readall( input ); + if( !src ) + { + perror( input ); + goto out_mpu; + } + if( !mpu_utf8valid( (const __mpu_char8_t *)src ) ) + { + fprintf( stderr, "mcpu-as: error: assembler source is not valid UTF-8\n" ); + goto out_src; + } + + err[0] = 0; + if( prepare_source( src, input, &prepared, &map, err, sizeof( err ) ) ) + { + fprintf( stderr, "mcpu-as: %s\n", err[0] ? err : "cannot process source" ); + goto out_src; + } + + if( cpu_assemble_object( prepared, &object, err, sizeof( err ) ) ) + { + remap_assembler_error( err, sizeof( err ), &map, input ); + if( !strncmp( err, "line ", 5 ) || strstr( err, ": error: " ) ) + fprintf( stderr, "mcpu-as: %s\n", err ); + else + fprintf( stderr, "mcpu-as: error: %s\n", err ); + goto out_map; + } + + if( cpu_object_write( output, &object, err, sizeof( err ) ) ) + { + fprintf( stderr, "mcpu-as: error: %s\n", err ); + cpu_asm_object_free( &object ); + goto out_map; + } + + if( verbose ) + printf( "MELF ET_REL: %zu symbols, %zu relocations -> %s\n", + object.symbol_count, object.reloc_count, output ); + cpu_asm_object_free( &object ); + rc = 0; + +out_map: + source_map_free( &map ); + free( prepared ); +out_src: + free( src ); +out_mpu: + __mpu_free_context(); + return( rc ); +} diff --git a/utils/mcpu-asm.c b/utils/mcpu-asm.c new file mode 100644 index 0000000..dcc4ead --- /dev/null +++ b/utils/mcpu-asm.c @@ -0,0 +1,4012 @@ +#include <ctype.h> +#include <errno.h> +#include <math.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <libmcpu.h> +#include <libmpuio.h> +#include "mcpu-asm.h" + +#define ASM_NAME_MAX 64 +#define ASM_EXPR_MAX 256 + +typedef struct +{ + char name[ASM_NAME_MAX]; + __mpu_uint64_t addr; +} label_t; +typedef struct +{ + size_t pos; + char expr[ASM_EXPR_MAX]; +} fixup_t; +typedef struct +{ + char name[ASM_NAME_MAX]; + char expr[ASM_EXPR_MAX]; + int line; + int state; /* 0=not evaluated, 1=evaluating, 2=done */ + __int128 value; +} equ_t; +typedef struct +{ + __mpu_UNIT_t *p; + size_t n, cap; +} buf_t; +typedef struct +{ + label_t *p; + size_t n, cap; +} labels_t; +typedef struct +{ + fixup_t *p; + size_t n, cap; +} fixups_t; +typedef struct +{ + equ_t *p; + size_t n, cap; +} equs_t; + +typedef struct +{ + char name[ASM_NAME_MAX]; + int is_global; + int is_extern; + int is_weak; +} symdecl_t; +typedef struct +{ + symdecl_t *p; + size_t n, cap; +} symdecls_t; + +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 ) c *= 2; + q = realloc( *p, c *unit ); + if( !q ) return( -1 ); + *p = q; + *cap = c; + return( 0 ); +} + +static int +put( buf_t *b, const void *p, size_t n ) +{ + if( grow( (void **)&b->p, &b->cap, b->n + n, 1 ) ) return( -1 ); + memcpy( b->p + b->n, p, n ); + b->n += n; + return( 0 ); +} + +static int +put8( buf_t *b, unsigned v ) +{ + __mpu_UNIT_t x = (__mpu_UNIT_t)v; + return( put( b, &x, 1 ) ); +} + +static int +put16( buf_t *b, unsigned v ) +{ + __mpu_UNIT_t x[2]; + __cpu_code_put_u16( x, (__mpu_uint16_t)v ); + return( put( b, x, 2 ) ); +} + +static int +put64( buf_t *b, __mpu_uint64_t v ) +{ + __mpu_UNIT_t x[8]; + __cpu_code_put_u64( x, v ); + return( put( b, x, 8 ) ); +} + +static char * +trim( char *s ) +{ + char *e; + while( isspace( (unsigned char)*s ) ) s++; + e = s + strlen( s ); + while( e > s && isspace( (unsigned char)e[-1] ) ) *--e = 0; + return( s ); +} + +static int +streqi( const char *a, const char *b ) +{ + for( ; *a && *b; a++, b++ ) if( toupper( (unsigned char)*a ) != toupper( (unsigned char)*b ) ) return( 0 ); + return( !*a && !*b ); +} + +/* ';' starts a comment only outside a quoted assembler string. */ +static void +strip_comment( char *s ) +{ + int quote = 0, esc = 0; + for( ; *s; s++ ) + { + if( quote ) + { + if( esc ) + { + esc = 0; + continue; + } + if( *s == '\\' ) + { + esc = 1; + continue; + } + if( *s == '"' ) + { + quote = 0; + continue; + } + } + else + { + if( *s == '"' ) + { + quote = 1; + continue; + } + if( *s == ';' ) + { + *s = 0; + return; + } + } + } +} + +/* A label colon is recognized only in the first token, never inside operands. */ +static char * +label_colon( char *s ) +{ + char *p = s; + while( *p && !isspace( (unsigned char)*p ) ) + { + if( *p == ':' ) return( p ); + p++; + } + return( 0 ); +} + +static int +addlabel( labels_t *l, const char *n, __mpu_uint64_t a ) +{ + size_t i; + for( i = 0; i < l->n; i++ ) if( !strcmp( l->p[i].name, n ) ) return( -1 ); + if( strlen( n ) >= ASM_NAME_MAX ) return( -1 ); + if( grow( (void **)&l->p, &l->cap, l->n + 1, sizeof( *l->p ) ) ) return( -1 ); + strcpy( l->p[l->n].name, n ); + l->p[l->n++].addr = a; + return( 0 ); +} + +static int +findlabel( const labels_t *l, const char *n, __mpu_uint64_t *a ) +{ + size_t i; + for( i = 0; i < l->n; i++ ) if( !strcmp( l->p[i].name, n ) ) + { + *a = l->p[i].addr; + return( 0 ); + } + return( -1 ); +} + +static int +addfix( fixups_t *f, size_t pos, const char *expr ) +{ + if( strlen( expr ) >= ASM_EXPR_MAX ) return( -1 ); + if( grow( (void **)&f->p, &f->cap, f->n + 1, sizeof( *f->p ) ) ) return( -1 ); + f->p[f->n].pos = pos; + strcpy( f->p[f->n].expr, expr ); + f->n++; + return( 0 ); +} + +static int +findequ( const equs_t *e, const char *n, size_t *idx ) +{ + size_t i; + for( i = 0; i < e->n; i++ ) if( !strcmp( e->p[i].name, n ) ) + { + if( idx ) * idx = i; + return( 0 ); + } + return( -1 ); +} + +static int +addequ( equs_t *e, const char *n, const char *expr, int line, char *err, size_t es ) +{ + size_t dummy; + if( !*n || strlen( n ) >= ASM_NAME_MAX || strlen( expr ) >= ASM_EXPR_MAX ) + { + snprintf( err, es, "bad .equ name/expression" ); + return( -1 ); + } + if( !findequ( e, n, &dummy ) ) + { + snprintf( err, es, "duplicate .equ symbol: %s", n ); + return( -1 ); + } + if( grow( (void **)&e->p, &e->cap, e->n + 1, sizeof( *e->p ) ) ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + memset( &e->p[e->n], 0, sizeof( e->p[e->n] ) ); + strcpy( e->p[e->n].name, n ); + strcpy( e->p[e->n].expr, expr ); + e->p[e->n].line = line; + e->n++; + return( 0 ); +} + +/* Split comma-separated arguments while respecting parentheses and quoted strings. */ +static int +splitargs( char *s, char **a, int max ) +{ + int n = 0, depth = 0, quote = 0, esc = 0; + char *p = s, *start = s; + for( ; ; p++ ) + { + int end = ( *p == 0 ); + if( !end ) + { + if( quote ) + { + if( esc ) esc = 0; + else if( *p == '\\' ) esc = 1; + else if( *p == '"' ) quote = 0; + } + else + { + if( *p == '"' ) quote = 1; + else if( *p == '(' ) depth++; + else if( *p == ')' ) depth--; + } + } + if( ( !quote && depth == 0 && *p == ',' ) || end ) + { + if( n >= max ) return( -1 ); + if( !end ) * p = 0; + a[n++] = trim( start ); + if( end ) break; + start = p + 1; + } + } + if( quote || depth != 0 ) return( -1 ); + return( n ); +} + +/* ------------------------- integer expressions ------------------------- */ +typedef struct +{ + const char *p; + equs_t *eqs; + const labels_t *labels; /* optional section-local label namespace */ + char *err; + size_t es; +} expr_parser_t; + +static int eval_expression( const char *, equs_t *, const labels_t *, __int128 *, char *, size_t ); + +static void +expr_skip( expr_parser_t *x ) +{ + while( isspace( (unsigned char)*x->p ) ) x->p++; +} + +static int +symfirst( int c ) +{ + return( isalpha( (unsigned char)c ) || c == '_' || c == '.' || c == '$' ); +} + +static int +symrest( int c ) +{ + return( isalnum( (unsigned char)c ) || c == '_' || c == '.' || c == '$' ); +} + +static int +equ_value( equs_t *eqs, size_t i, __int128 *v, char *err, size_t es ) +{ + equ_t *e = &eqs->p[i]; + if( e->state == 2 ) + { + *v = e->value; + return( 0 ); + } + if( e->state == 1 ) + { + snprintf( err, es, "cyclic .equ definition involving %s", e->name ); + return( -1 ); + } + e->state = 1; + if( eval_expression( e->expr, eqs, 0, &e->value, err, es ) ) + { + e->state = 0; + return( -1 ); + } + e->state = 2; + *v = e->value; + return( 0 ); +} + +static int +expr_symbol( expr_parser_t *x, const char *name, __int128 *v ) +{ + size_t i; + __mpu_uint64_t a; + if( !findequ( x->eqs, name, &i ) ) return( equ_value( x->eqs, i, v, x->err, x->es ) ); + if( x->labels && !findlabel( x->labels, name, &a ) ) + { + *v = ( __int128 ) a; + return( 0 ); + } + snprintf( x->err, x->es, "unknown symbol in expression: %s", name ); + return( -1 ); +} + +static int +expr_primary( expr_parser_t *x, __int128 *v ) +{ + char name[ASM_NAME_MAX], *end; + size_t n = 0; + unsigned long long u; + expr_skip( x ); + if( symfirst( (unsigned char)*x->p ) ) + { + while( symrest( (unsigned char)*x->p ) ) + { + if( n + 1 >= sizeof( name ) ) + { + snprintf( x->err, x->es, "symbol name too long" ); + return( -1 ); + } + name[n++] = *x->p++; + } + name[n] = 0; + return( expr_symbol( x, name, v ) ); + } + errno = 0; + u = strtoull( x->p, &end, 0 ); + if( end == x->p ) + { + snprintf( x->err, x->es, "expected integer or symbol in expression" ); + return( -1 ); + } + if( errno == ERANGE ) + { + snprintf( x->err, x->es, "integer literal out of 64-bit range in expression" ); + return( -1 ); + } + x->p = end; + *v = ( __int128 ) u; + return( 0 ); +} + +/* Forward declarations for recursive-descent parentheses. */ +static int expr_add( expr_parser_t *, __int128 * ); + +static int +expr_atom( expr_parser_t *x, __int128 *v ) +{ + expr_skip( x ); + if( *x->p == '(' ) + { + x->p++; + if( expr_add( x, v ) ) return( -1 ); + expr_skip( x ); + if( *x->p != ')' ) + { + snprintf( x->err, x->es, "missing ')' in expression" ); + return( -1 ); + } + x->p++; + return( 0 ); + } + return( expr_primary( x, v ) ); +} + +static int +expr_unary( expr_parser_t *x, __int128 *v ) +{ + expr_skip( x ); + if( *x->p == '+' ) + { + x->p++; + return( expr_unary( x, v ) ); + } + if( *x->p == '-' ) + { + __int128 t; + x->p++; + if( expr_unary( x, &t ) ) return( -1 ); + if( __builtin_sub_overflow( ( __int128 ) 0, t, v ) ) + { + snprintf( x->err, x->es, "expression overflow" ); + return( -1 ); + } + return( 0 ); + } + return( expr_atom( x, v ) ); +} + +static int +expr_mul( expr_parser_t *x, __int128 *v ) +{ + __int128 a, b, r; + char op; + if( expr_unary( x, &a ) ) return( -1 ); + for( ; ; ) + { + expr_skip( x ); + op = *x->p; + if( op != '*' && op != '/' && op != '%' ) break; + x->p++; + if( expr_unary( x, &b ) ) return( -1 ); + if( op == '*' ) + { + if( __builtin_mul_overflow( a, b, &r ) ) + { + snprintf( x->err, x->es, "expression overflow" ); + return( -1 ); + } + } + else + { + if( !b ) + { + snprintf( x->err, x->es, "division by zero in expression" ); + return( -1 ); + } + if( op == '/' ) r = a / b; + else r = a % b; + } + a = r; + } + *v = a; + return( 0 ); +} + +static int +expr_add( expr_parser_t *x, __int128 *v ) +{ + __int128 a, b, r; + char op; + if( expr_mul( x, &a ) ) return( -1 ); + for( ; ; ) + { + expr_skip( x ); + op = *x->p; + if( op != '+' && op != '-' ) break; + x->p++; + if( expr_mul( x, &b ) ) return( -1 ); + if( op == '+' ) + { + if( __builtin_add_overflow( a, b, &r ) ) + { + snprintf( x->err, x->es, "expression overflow" ); + return( -1 ); + } + } + else + { + if( __builtin_sub_overflow( a, b, &r ) ) + { + snprintf( x->err, x->es, "expression overflow" ); + return( -1 ); + } + } + a = r; + } + *v = a; + return( 0 ); +} + +static int +eval_expression( const char *s, equs_t *eqs, const labels_t *labels, __int128 *v, char *err, size_t es ) +{ + expr_parser_t x = + { + s, eqs, labels, err, es + }; + if( expr_add( &x, v ) ) return( -1 ); + expr_skip( &x ); + if( *x.p ) + { + snprintf( err, es, "unexpected token in expression near: %s", x.p ); + return( -1 ); + } + return( 0 ); +} + +static int +eval_i64( const char *s, equs_t *eqs, const labels_t *l, __mpu_int64_t *v, char *err, size_t es ) +{ + __int128 x; + if( eval_expression( s, eqs, l, &x, err, es ) ) return( -1 ); + if( x < ( __int128 ) INT64_MIN || x > ( __int128 ) INT64_MAX ) return( 1 ); + *v = (__mpu_int64_t)x; + return( 0 ); +} + +static int +eval_u64( const char *s, equs_t *eqs, const labels_t *l, __mpu_uint64_t *v, char *err, size_t es ) +{ + __int128 x; + if( eval_expression( s, eqs, l, &x, err, es ) ) return( -1 ); + if( x < 0 || x > ( __int128 ) UINT64_MAX ) return( 1 ); + *v = (__mpu_uint64_t)x; + return( 0 ); +} + +static int +plain_integer_literal( const char *s ) +{ + const unsigned char *p = (const unsigned char *)s; + int base = 10, nd = 0; + while( isspace( *p ) ) p++; + if( *p == '+' || *p == '-' ) p++; + if( !*p ) return( 0 ); + if( p[0] == '0' && ( p[1] == 'x' || p[1] == 'X' ) ) + { + base = 16; + p += 2; + } + else + if( p[0] == '0' && ( p[1] == 'b' || p[1] == 'B' ) ) + { + base = 2; + p += 2; + } + else + if( p[0] == '0' && isdigit( p[1] ) ) + { + base = 8; + p++; + } + while( *p && !isspace( *p ) ) + { + int ok = ( base == 16 ) ? isxdigit( *p ) : ( base == 10 ) ? isdigit( *p ) : ( base == 8 ) ? ( *p >= '0' && *p <= '7' ) : ( *p == '0' || *p == '1' ); + if( !ok ) return( 0 ); + p++; + nd++; + } + while( isspace( *p ) ) p++; + return( nd && !*p ); +} + +static void +i128_decimal( __int128 v, char out[64] ) +{ + char tmp[64]; + int neg = v < 0, n = 0, i = 0; + unsigned __int128 u; + if( neg ) u = (unsigned __int128)( - ( v + 1 ) ) + 1; + else u = (unsigned __int128)v; + do + { + tmp[n++] = (char)( '0' + u % 10 ); + u /= 10; + } + while( u ); + if( neg ) out[i++] = '-'; + while( n ) out[i++] = tmp[--n]; + out[i] = 0; +} + +/* --------------------------- register syntax --------------------------- */ +static int +regparse( const char *s, cpu_register *r, int *isc, equs_t *eqs, char *err, size_t es ) +{ + const char *p = s, *at; + char *e; + unsigned long bits; + __mpu_uint64_t a; + while( isspace( (unsigned char)*p ) ) p++; + if( *p != 'r' && *p != 'R' && *p != 'c' && *p != 'C' ) return( -1 ); + *isc = ( *p == 'c' || *p == 'C' ); + p++; + errno = 0; + bits = strtoul( p, &e, 10 ); + if( errno || e == p || *e != '@' ) return( -1 ); + at = e + 1; + if( !*at ) return( -1 ); + if( eval_u64( at, eqs, 0, &a, err, es ) != 0 || a > UINT32_MAX ) return( -1 ); + r->bits = (unsigned int)bits; + r->address = (__mpu_uint32_t)a; + if( !__cpu_reg_valid( r->bits, r->address ) ) return( -1 ); + if( *isc && r->bits < 64 ) return( -1 ); + return( 0 ); +} + +static int +preg( buf_t *b, const char *s, int require_c, int require_r, equs_t *eqs, char *err, size_t es ) +{ + cpu_register r; + int c; + __mpu_UNIT_t x[3]; + if( regparse( trim( (char *)s ), &r, &c, eqs, err, es ) ) + { + snprintf( err, es, "bad register: %s", s ); + return( -1 ); + } + if( require_c && !c ) + { + snprintf( err, es, "complex instruction requires c<bits>@addr: %s", s ); + return( -1 ); + } + if( require_r && c ) + { + snprintf( err, es, "instruction requires r<bits>@addr: %s", s ); + return( -1 ); + } + if( __cpu_encode_reg24( x, &r ) ) return( -1 ); + return( put( b, x, 3 ) ); +} + +static int +preg_r64( buf_t *b, const char *s, const char *what, equs_t *eqs, char *err, size_t es ) +{ + cpu_register r; + int c; + __mpu_UNIT_t x[3]; + if( regparse( trim( (char *)s ), &r, &c, eqs, err, es ) || c || r.bits != 64 ) + { + snprintf( err, es, "%s requires r64@addr: %s", what, s ); + return( -1 ); + } + if( __cpu_encode_reg24( x, &r ) ) return( -1 ); + return( put( b, x, 3 ) ); +} + +static int +preg_ptr( buf_t *b, const char *s, equs_t *eqs, char *err, size_t es ) +{ + return( preg_r64( b, s, "PTR64 operand", eqs, err, es ) ); +} + +/* ---------------------------- string literals ---------------------------- */ +static int +hex_digit( int c ) +{ + if( c >= '0' && c <= '9' ) return( c - '0' ); + if( c >= 'a' && c <= 'f' ) return( c - 'a' + 10 ); + if( c >= 'A' && c <= 'F' ) return( c - 'A' + 10 ); + return( -1 ); +} + +static int +put_utf8_cp( buf_t *b, unsigned long cp, char *err, size_t es ) +{ + unsigned char x[4]; + size_t n; + if( cp == 0 ) + { + snprintf( err, es, "embedded NUL is not supported in .utf8/.ucs2; use .byte/.integer" ); + return( -1 ); + } + if( cp <= 0x7f ) + { + x[0] = (unsigned char)cp; + n = 1; + } + else + if( cp <= 0x7ff ) + { + x[0] = 0xc0 | ( cp >> 6 ); + x[1] = 0x80 | ( cp & 0x3f ); + n = 2; + } + else + if( cp <= 0xffff ) + { + if( cp >= 0xd800 && cp <= 0xdfff ) + { + snprintf( err, es, "Unicode surrogate is not a valid scalar value" ); + return( -1 ); + } + x[0] = 0xe0 | ( cp >> 12 ); + x[1] = 0x80 | ( ( cp >> 6 ) & 0x3f ); + x[2] = 0x80 | ( cp & 0x3f ); + n = 3; + } + else + if( cp <= 0x10ffff ) + { + x[0] = 0xf0 | ( cp >> 18 ); + x[1] = 0x80 | ( ( cp >> 12 ) & 0x3f ); + x[2] = 0x80 | ( ( cp >> 6 ) & 0x3f ); + x[3] = 0x80 | ( cp & 0x3f ); + n = 4; + } + else + { + snprintf( err, es, "Unicode code point out of range" ); + return( -1 ); + } + if( put( b, x, n ) ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + return( 0 ); +} + +static int +parse_utf8_string( const char *s, buf_t *out, char *err, size_t es ) +{ + const unsigned char *p = (const unsigned char *)s; + int closed = 0; + memset( out, 0, sizeof( *out ) ); + while( isspace( *p ) ) p++; + if( *p != '"' ) + { + snprintf( err, es, "expected quoted string" ); + return( -1 ); + } + p++; + while( *p ) + { + if( *p == '"' ) + { + p++; + closed = 1; + break; + } + if( *p != '\\' ) + { + if( *p == 0 ) + { + break; + } + if( put8( out, *p++ ) ) + { + snprintf( err, es, "out of memory" ); + goto fail; + } continue; + } + p++; + if( !*p ) + { + snprintf( err, es, "unfinished escape sequence" ); + goto fail; + } + switch( *p++ ) + { + case '\\': if( put8( out, '\\' ) ) goto oom; + break; + case '"': if( put8( out, '"' ) ) goto oom; + break; + case 'n': if( put8( out, '\n' ) ) goto oom; + break; + case 'r': if( put8( out, '\r' ) ) goto oom; + break; + case 't': if( put8( out, '\t' ) ) goto oom; + break; + case 'b': if( put8( out, '\b' ) ) goto oom; + break; + case 'f': if( put8( out, '\f' ) ) goto oom; + break; + case 'v': if( put8( out, '\v' ) ) goto oom; + break; + case 'a': if( put8( out, '\a' ) ) goto oom; + break; + case '0': snprintf( err, es, "embedded NUL is not supported in .utf8/.ucs2; use .byte/.integer" ); + goto fail; + case 'x': { + int h1, h2; + unsigned v; + if( !p[0] || !p[1] ) + { + snprintf( err, es, "\\x escape requires exactly two hex digits" ); + goto fail; + } + h1 = hex_digit( p[0] ); + h2 = hex_digit( p[1] ); + if( h1 < 0 || h2 < 0 ) + { + snprintf( err, es, "\\x escape requires exactly two hex digits" ); + goto fail; + } + v = (unsigned)( ( h1 << 4 ) | h2 ); + p += 2; + if( v == 0 ) + { + snprintf( err, es, "embedded NUL is not supported in .utf8/.ucs2; use .byte/.integer" ); + goto fail; + } + if( put8( out, v ) ) goto oom; + break; + } + case 'u': + case 'U': { + int nd = p[-1] == 'u' ? 4 : 8, i, h; + unsigned long cp = 0; + for( i = 0; i < nd; i++ ) + { + if( !p[i] ) + { + snprintf( err, es, "Unicode escape requires %d hex digits", nd ); + goto fail; + } + h = hex_digit( p[i] ); + if( h < 0 ) + { + snprintf( err, es, "Unicode escape requires %d hex digits", nd ); + goto fail; + } + cp = ( cp << 4 ) | (unsigned)h; + } + p += nd; + if( put_utf8_cp( out, cp, err, es ) ) goto fail; + break; + } + default: snprintf( err, es, "unknown string escape sequence" ); + goto fail; + } + + } + if( !closed ) + { + snprintf( err, es, "unterminated string literal" ); + goto fail; + } + + while( isspace( *p ) ) p++; + if( *p ) + { + snprintf( err, es, "unexpected text after string literal" ); + goto fail; + } + + if( put8( out, 0 ) ) goto oom; + if( !mpu_utf8valid( (const __mpu_char8_t *)out->p ) ) + { + snprintf( err, es, "string literal is not valid UTF-8" ); + goto fail; + } + + return( 0 ); +oom: + snprintf( err, es, "out of memory" ); +fail: + free( out->p ); + memset( out, 0, sizeof( *out ) ); + return( -1 ); +} + +static int +real_literal_exact_value( const char *s, int nb, mpu_real *out ) +{ + mpu_wide_real_t src, narrow, back; + memset( src, 0, sizeof( src ) ); + memset( narrow, 0, sizeof( narrow ) ); + memset( back, 0, sizeof( back ) ); + if( ascii_to_real( ( mpu_real * ) src, (__mpu_char8_t *)s, NB_R65536 ) < 0 ) return( -1 ); + r_cvt( ( mpu_real * ) narrow, ( mpu_real * ) src, nb, NB_R65536 ); + r_cvt( ( mpu_real * ) back, ( mpu_real * ) narrow, NB_R65536, nb ); + if( r_cmp( ( mpu_real * ) src, ( mpu_real * ) back, NB_R65536 ) != 0 ) return( 0 ); + if( out ) memcpy( out, narrow, (size_t)nb ); + return( 1 ); +} + +static int +cimm_exact( const char *s, float *r, float *j, char *err, size_t es ) +{ + const char *rp = strstr( s, "r0" ), *jp; + char *rs = 0, *js = 0; + size_t rn, jn; + int ex; + if( !rp || rp == s ) return( -1 ); + jp = strstr( rp + 2, "j0" ); + if( !jp || jp == rp + 2 || jp[2] ) return( -1 ); + rn = (size_t)( rp - s ); + jn = (size_t)( jp - ( rp + 2 ) ); + rs = (char *)malloc( rn + 1 ); + js = (char *)malloc( jn + 1 ); + if( !rs || !js ) + { + free( rs ); + free( js ); + snprintf( err, es, "out of memory" ); + return( -2 ); + } + memcpy( rs, s, rn ); + rs[rn] = 0; + memcpy( js, rp + 2, jn ); + js[jn] = 0; + { + mpu_real32_t xr, xj; + ex = real_literal_exact_value( rs, NB_R32, ( mpu_real * ) xr ); + if( ex != 1 ) + { + free( rs ); + free( js ); + if( ex == 0 ) snprintf( err, es, "LDC immediate component is not exactly representable as real32;\nuse .complex <bits>, real, imag in DATA and LOAD" ); + return( ex == 0 ? -2 : -1 ); + } + ex = real_literal_exact_value( js, NB_R32, ( mpu_real * ) xj ); + if( ex != 1 ) + { + free( rs ); + free( js ); + if( ex == 0 ) snprintf( err, es, "LDC immediate component is not exactly representable as real32;\nuse .complex <bits>, real, imag in DATA and LOAD" ); + return( ex == 0 ? -2 : -1 ); + } + memcpy( r, xr, NB_R32 ); + memcpy( j, xj, NB_R32 ); + } + free( rs ); + free( js ); + return( 0 ); +} + +static int +constid( const char *s, unsigned *v ) +{ + struct K + { + const char *n; + unsigned v; + } + ks[] = + { + { "ZERO", CPU_RCONST_ZERO }, + { "HALF", CPU_RCONST_HALF }, + { "ONE", CPU_RCONST_ONE }, + { "TWO", CPU_RCONST_TWO }, + { "TEN", CPU_RCONST_TEN }, + { "MTEN", CPU_RCONST_MTEN }, + { "32", CPU_RCONST_32 }, + { "PI", CPU_RCONST_PI }, + { "E", CPU_RCONST_E }, + { "1_LN2", CPU_RCONST_1_LN2 }, + { "LN2", CPU_RCONST_LN2 }, + { "1_LN10", CPU_RCONST_1_LN10 }, + { "LN10", CPU_RCONST_LN10 }, + { "1_LG2", CPU_RCONST_1_LG2 }, + { "LG2", CPU_RCONST_LG2 }, + { "PI_2", CPU_RCONST_PI_2 }, + { "PI_3", CPU_RCONST_PI_3 }, + { "PI_4", CPU_RCONST_PI_4 }, + { "PI_5", CPU_RCONST_PI_5 }, + { "PI_6", CPU_RCONST_PI_6 }, + { "1_PI", CPU_RCONST_1_PI }, + { "2_PI", CPU_RCONST_2_PI }, + { "3_PI", CPU_RCONST_3_PI }, + { "4_PI", CPU_RCONST_4_PI }, + { "5_PI", CPU_RCONST_5_PI }, + { "2PI", CPU_RCONST_2PI }, + { "3PI", CPU_RCONST_3PI }, + { "1_2PI", CPU_RCONST_1_2PI }, + { "1_3PI", CPU_RCONST_1_3PI }, + { "1_4PI", CPU_RCONST_1_4PI }, + { "1_5PI", CPU_RCONST_1_5PI }, + { "1_6PI", CPU_RCONST_1_6PI }, + { "3PI_4", CPU_RCONST_3PI_4 }, + { "SQRTPI", CPU_RCONST_SQRTPI }, + { "1_SQRTPI", CPU_RCONST_1_SQRTPI }, + { "2_SQRTPI", CPU_RCONST_2_SQRTPI }, + { "SQRT2", CPU_RCONST_SQRT2 }, + { "1_SQRT2", CPU_RCONST_1_SQRT2 }, + { "SQRT3", CPU_RCONST_SQRT3 }, + { "1_SQRT3", CPU_RCONST_1_SQRT3 }, + { "DEGREE", CPU_RCONST_DEGREE }, + { "1_DEGREE", CPU_RCONST_1_DEGREE }, + { "GOLDENRATIO", CPU_RCONST_GOLDENRATIO }, + { "EULERGAMMA", CPU_RCONST_EULERGAMMA }, + { "CATALAN", CPU_RCONST_CATALAN }, + { "EPSILON", CPU_RCONST_EPSILON }, + { "IND", CPU_RCONST_IND }, + { "PNAN", CPU_RCONST_PNAN }, + { "NNAN", CPU_RCONST_NNAN }, + { "PNANMAX", CPU_RCONST_PNANMAX }, + { "NNANMAX", CPU_RCONST_NNANMAX }, + { "PNANMIN", CPU_RCONST_PNANMIN }, + { "NNANMIN", CPU_RCONST_NNANMIN }, + { "PINF", CPU_RCONST_PINF }, + { "NINF", CPU_RCONST_NINF }, + { "PREALMIN", CPU_RCONST_PREALMIN }, + { "NREALMIN", CPU_RCONST_NREALMIN }, + { "PREALMAX", CPU_RCONST_PREALMAX }, + { "NREALMAX", CPU_RCONST_NREALMAX }, + { "PZERO", CPU_RCONST_PZERO }, + { "NZERO", CPU_RCONST_NZERO } + }; + size_t i; + for( i = 0; i < sizeof( ks ) / sizeof( ks[0] ); i++ ) if( streqi( s, ks[i].n ) ) + { + *v = ks[i].v; + return( 0 ); + } + return( -1 ); +} + +static int +pswbitid( const char *s, unsigned *v ) +{ + struct B + { + const char *n; + unsigned v; + } + bs[] = + { + { "AF", CPU_PSWBIT_AF }, + { "CF", CPU_PSWBIT_CF }, + { "OF", CPU_PSWBIT_OF }, + { "SF", CPU_PSWBIT_SF }, + { "PF", CPU_PSWBIT_PF }, + { "ZF", CPU_PSWBIT_ZF }, + { "RF", CPU_PSWBIT_RF }, + { "VF", CPU_PSWBIT_VF }, + { "DOMF", CPU_PSWBIT_DOMF }, + { "SNGF", CPU_PSWBIT_SNGF }, + { "OVFF", CPU_PSWBIT_OVFF }, + { "UDFF", CPU_PSWBIT_UDFF }, + { "TLSF", CPU_PSWBIT_TLSF }, + { "PLSF", CPU_PSWBIT_PLSF }, + { "INDF", CPU_PSWBIT_INDF }, + { "INXF", CPU_PSWBIT_INXF }, + { "HF", CPU_PSWBIT_HF }, + { "TF", CPU_PSWBIT_TF }, + { "IF", CPU_PSWBIT_IF } + }; + size_t i; + for( i = 0; i < sizeof( bs ) / sizeof( bs[0] ); i++ ) + if( streqi( s, bs[i].n ) ) + { + *v = bs[i].v; + return( 0 ); + } + return( -1 ); +} + +typedef struct +{ + const char *n; + unsigned op; + int nr; + int c; + int r; +} optab_t; +static const optab_t ops[] = +{ + { "NOP", CPU_OP_NOP, 0, 0, 0 }, + { "HALT", CPU_OP_HALT, 0, 0, 0 }, + { "END", CPU_OP_END, 0, 0, 0 }, + { "SYSCALL", CPU_OP_SYSCALL, 0, 0, 0 }, + { "CLRCPUF", CPU_OP_CLRCPUF, 0, 0, 0 }, + { "CLRMPUF", CPU_OP_CLRMPUF, 0, 0, 0 }, + { "CLR", CPU_OP_CLR, 1, 0, 0 }, + { "MOV", CPU_OP_MOV, 2, 0, 0 }, + { "SWAP", CPU_OP_SWAP, 2, 0, 0 }, + { "BSWAP", CPU_OP_BSWAP, 2, 0, 1 }, + { "PUSH", CPU_OP_PUSH, 1, 0, 0 }, + { "POP", CPU_OP_POP, 1, 0, 0 }, + { "PUSHFP", CPU_OP_PUSHFP, 0, 0, 0 }, + { "SETFP", CPU_OP_SETFP, 0, 0, 0 }, + { "RESTSP", CPU_OP_RESTSP, 0, 0, 0 }, + { "POPFP", CPU_OP_POPFP, 0, 0, 0 }, + { "IADD", CPU_OP_IADD, 3, 0, 1 }, + { "IADC", CPU_OP_IADC, 3, 0, 1 }, + { "ISUB", CPU_OP_ISUB, 3, 0, 1 }, + { "ISBB", CPU_OP_ISBB, 3, 0, 1 }, + { "IAND", CPU_OP_IAND, 3, 0, 1 }, + { "IOR", CPU_OP_IOR, 3, 0, 1 }, + { "IXOR", CPU_OP_IXOR, 3, 0, 1 }, + { "INOT", CPU_OP_INOT, 2, 0, 1 }, + { "INEG", CPU_OP_INEG, 2, 0, 1 }, + { "ICMP", CPU_OP_ICMP, 2, 0, 1 }, + { "ITEST", CPU_OP_ITEST, 2, 0, 1 }, + { "ISHL", CPU_OP_ISHL, 2, 0, 1 }, + { "ISHR", CPU_OP_ISHR, 2, 0, 1 }, + { "ISAL", CPU_OP_ISAL, 2, 0, 1 }, + { "ISAR", CPU_OP_ISAR, 2, 0, 1 }, + { "IROL", CPU_OP_IROL, 2, 0, 1 }, + { "IROR", CPU_OP_IROR, 2, 0, 1 }, + { "IRCL", CPU_OP_IRCL, 2, 0, 1 }, + { "IRCR", CPU_OP_IRCR, 2, 0, 1 }, + { "IINC", CPU_OP_IINC, 2, 0, 1 }, + { "IDEC", CPU_OP_IDEC, 2, 0, 1 }, + { "IXCHG", CPU_OP_IXCHG, 2, 0, 1 }, + { "ICPY", CPU_OP_ICPY, 2, 0, 1 }, + { "ICVT", CPU_OP_ICVT, 2, 0, 1 }, + { "IMUL", CPU_OP_IMUL, 3, 0, 1 }, + { "ISMUL", CPU_OP_ISMUL, 3, 0, 1 }, + { "IDIV", CPU_OP_IDIV, 4, 0, 1 }, + { "ISDIV", CPU_OP_ISDIV, 4, 0, 1 }, + { "IBITSLEN", CPU_OP_IBITSLEN, 2, 0, 1 }, + { "RADD", CPU_OP_RADD, 3, 0, 1 }, + { "RSUB", CPU_OP_RSUB, 3, 0, 1 }, + { "RMUL", CPU_OP_RMUL, 3, 0, 1 }, + { "RDIV", CPU_OP_RDIV, 3, 0, 1 }, + { "RSQRT", CPU_OP_RSQRT, 2, 0, 1 }, + { "RSIN", CPU_OP_RSIN, 2, 0, 1 }, + { "RCOS", CPU_OP_RCOS, 2, 0, 1 }, + { "RTAN", CPU_OP_RTAN, 2, 0, 1 }, + { "RASIN", CPU_OP_RASIN, 2, 0, 1 }, + { "RACOS", CPU_OP_RACOS, 2, 0, 1 }, + { "RATAN", CPU_OP_RATAN, 2, 0, 1 }, + { "RLOG", CPU_OP_RLOG, 2, 0, 1 }, + { "RLOG10", CPU_OP_RLOG10, 2, 0, 1 }, + { "RLOG2", CPU_OP_RLOG2, 2, 0, 1 }, + { "REXP", CPU_OP_REXP, 2, 0, 1 }, + { "RSINH", CPU_OP_RSINH, 2, 0, 1 }, + { "RCOSH", CPU_OP_RCOSH, 2, 0, 1 }, + { "RTANH", CPU_OP_RTANH, 2, 0, 1 }, + { "RPOW", CPU_OP_RPOW, 3, 0, 1 }, + { "RHYPOT", CPU_OP_RHYPOT, 3, 0, 1 }, + { "RATAN2", CPU_OP_RATAN2, 3, 0, 1 }, + { "RNEG", CPU_OP_RNEG, 2, 0, 1 }, + { "RABS", CPU_OP_RABS, 2, 0, 1 }, + { "RCPY", CPU_OP_RCPY, 2, 0, 1 }, + { "RCVT", CPU_OP_RCVT, 2, 0, 1 }, + { "RFLOOR", CPU_OP_RFLOOR, 2, 0, 1 }, + { "RCEIL", CPU_OP_RCEIL, 2, 0, 1 }, + { "RROUND", CPU_OP_RROUND, 2, 0, 1 }, + { "RFREXP", CPU_OP_RFREXP, 3, 0, 1 }, + { "RLDEXP", CPU_OP_RLDEXP, 3, 0, 1 }, + { "RLOGB", CPU_OP_RLOGB, 2, 0, 1 }, + { "RMODF", CPU_OP_RMODF, 3, 0, 1 }, + { "RREMAIN", CPU_OP_RREMAIN, 3, 0, 1 }, + { "RLOG1P", CPU_OP_RLOG1P, 2, 0, 1 }, + { "REXPM1", CPU_OP_REXPM1, 2, 0, 1 }, + { "RASINH", CPU_OP_RASINH, 2, 0, 1 }, + { "RACOSH", CPU_OP_RACOSH, 2, 0, 1 }, + { "RATANH", CPU_OP_RATANH, 2, 0, 1 }, + { "ITOR", CPU_OP_ITOR, 2, 0, 1 }, + { "UITOR", CPU_OP_UITOR, 2, 0, 1 }, + { "RTOI", CPU_OP_RTOI, 3, 0, 1 }, + { "RTOUI", CPU_OP_RTOUI, 3, 0, 1 }, + { "CADD", CPU_OP_CADD, 3, 1, 0 }, + { "CSUB", CPU_OP_CSUB, 3, 1, 0 }, + { "CMUL", CPU_OP_CMUL, 3, 1, 0 }, + { "CDIV", CPU_OP_CDIV, 3, 1, 0 }, + { "CEXP", CPU_OP_CEXP, 2, 1, 0 }, + { "CSIN", CPU_OP_CSIN, 2, 1, 0 }, + { "CCOS", CPU_OP_CCOS, 2, 1, 0 }, + { "CSINH", CPU_OP_CSINH, 2, 1, 0 }, + { "CCOSH", CPU_OP_CCOSH, 2, 1, 0 }, + { "CLOG", CPU_OP_CLOG, 2, 1, 0 }, + { "CSQRT", CPU_OP_CSQRT, 2, 1, 0 }, + { "CPOW", CPU_OP_CPOW, 3, 1, 0 }, + { "CCONJ", CPU_OP_CCONJ, 2, 1, 0 }, + { "CCPY", CPU_OP_CCPY, 2, 1, 0 }, + { "CCVT", CPU_OP_CCVT, 2, 1, 0 } +}; + +static int +emit_instruction( buf_t *c, fixups_t *df, fixups_t *cf, fixups_t *pf, equs_t *eqs, char *mn, char *rest, char *err, size_t es ) +{ + char *av[4]; + int ac = rest && *trim( rest ) ? splitargs( rest, av, 4 ) : 0; + size_t i; + unsigned k; + for( i = 0; i < sizeof( ops ) / sizeof( ops[0] ); i++ ) if( streqi( mn, ops[i].n ) ) + { + buf_t tmp = + { + 0 + }; + if( ac != ops[i].nr ) + { + snprintf( err, es, "wrong operand count for %s", mn ); + return( -1 ); + } + if( put16( &tmp, ops[i].op ) ) return( -1 ); + for( k = 0; k < (unsigned)ac; k++ ) if( preg( &tmp, av[k], ops[i].c, ops[i].r, eqs, err, es ) ) + { + free( tmp.p ); + return( -1 ); + } + if( put( c, tmp.p, tmp.n ) ) + { + free( tmp.p ); + return( -1 ); + } + free( tmp.p ); + return( 0 ); + } + if( streqi( mn, "SETF" ) || streqi( mn, "CLRF" ) ) + { + unsigned bit; + if( ac != 1 || pswbitid( av[0], &bit ) || bit > CPU_PSWBIT_WRITE_MAX ) + { + snprintf( err, es, "%s requires one writable PSW flag in range AF..INXF", mn ); + return( -1 ); + } + if( put16( c, streqi( mn, "SETF" ) ? CPU_OP_SETF : CPU_OP_CLRF ) || + put8( c, bit ) ) return( -1 ); + return( 0 ); + } + if( streqi( mn, "EXIT" ) ) + { + cpu_register r; + int ic; + if( ac != 1 || regparse( av[0], &r, &ic, eqs, err, es ) || ic || r.bits != 32 ) + { + snprintf( err, es, "EXIT requires one r32 status operand" ); + return( -1 ); + } + if( put16( c, CPU_OP_EXIT ) || preg( c, av[0], 0, 1, eqs, err, es ) ) return( -1 ); + return( 0 ); + } + if( streqi( mn, "LDR" ) ) + { + __mpu_real64_t v; + mpu_real64_t xv; + __mpu_uint64_t u; + cpu_register r; + int ic, ex, nb; + if( ac != 2 || regparse( av[0], &r, &ic, eqs, err, es ) || ic ) + { + snprintf( err, es, "bad LDR destination" ); + return( -1 ); + } + if( r.bits < 32 ) + { + snprintf( err, es, "LDR requires r32 or wider" ); + return( -1 ); + } + nb = ( r.bits == 32 ) ? NB_R32 : NB_R64; + if( nb == NB_R32 ) + { + mpu_real32_t x32; + ex = real_literal_exact_value( av[1], NB_R32, ( mpu_real * ) x32 ); + if( ex == 1 ) r_cvt( ( mpu_real * ) xv, ( mpu_real * ) x32, NB_R64, NB_R32 ); + } + else ex = real_literal_exact_value( av[1], NB_R64, ( mpu_real * ) xv ); + if( ex < 0 ) + { + snprintf( err, es, "bad real immediate" ); + return( -1 ); + } + if( !ex ) + { + snprintf( err, es, "LDR immediate is not exactly representable as real%d;\nuse .real <bits>, value in DATA and LOAD", r.bits == 32 ? 32 : 64 ); + return( -1 ); + } + memcpy( &v, xv, NB_R64 ); + if( put16( c, CPU_OP_LDR ) || preg( c, av[0], 0, 1, eqs, err, es ) ) return( -1 ); + memcpy( &u, &v, 8 ); + return( put64( c, u ) ); + } + if( streqi( mn, "LDC" ) ) + { + float r, j; + __mpu_uint32_t ur, ui; + __mpu_uint64_t u; + cpu_register rg; + int ic, cr; + if( ac != 2 || regparse( av[0], &rg, &ic, eqs, err, es ) || !ic ) + { + snprintf( err, es, "bad LDC destination" ); + return( -1 ); + } + cr = cimm_exact( av[1], &r, &j, err, es ); + if( cr == -1 ) + { + snprintf( err, es, "bad complex immediate" ); + return( -1 ); + } + if( cr == -2 ) return( -1 ); + memcpy( &ur, &r, 4 ); + memcpy( &ui, &j, 4 ); + u = ( (__mpu_uint64_t)ur << 32 ) | ui; + if( put16( c, CPU_OP_LDC ) || preg( c, av[0], 1, 0, eqs, err, es ) || put64( c, u ) ) return( -1 ); + return( 0 ); + } + if( streqi( mn, "RCONST" ) ) + { + if( ac != 2 || constid( av[1], &k ) ) + { + snprintf( err, es, "bad RCONST" ); + return( -1 ); + } + if( put16( c, CPU_OP_RCONST ) || preg( c, av[0], 0, 1, eqs, err, es ) || put8( c, k ) ) return( -1 ); + return( 0 ); + } + + if( streqi( mn, "ISHLN" ) || streqi( mn, "ISHRN" ) || streqi( mn, "ISALN" ) || streqi( mn, "ISARN" ) || + streqi( mn, "IROLN" ) || streqi( mn, "IRORN" ) || streqi( mn, "IRCLN" ) || streqi( mn, "IRCRN" )) + { + unsigned op; + if( ac != 3 ) + { + snprintf( err, es, "%s requires dst, src, r64 count", mn ); + return( -1 ); + } + if( streqi( mn, "ISHLN" ) ) op = CPU_OP_ISHLN; + else if( streqi( mn, "ISHRN" ) ) op = CPU_OP_ISHRN; + else if( streqi( mn, "ISALN" ) ) op = CPU_OP_ISALN; + else if( streqi( mn, "ISARN" ) ) op = CPU_OP_ISARN; + else if( streqi( mn, "IROLN" ) ) op = CPU_OP_IROLN; + else if( streqi( mn, "IRORN" ) ) op = CPU_OP_IRORN; + else if( streqi( mn, "IRCLN" ) ) op = CPU_OP_IRCLN; + else op = CPU_OP_IRCRN; + if( put16( c, op ) || preg( c, av[0], 0, 1, eqs, err, es ) || preg( c, av[1], 0, 1, eqs, err, es ) || + preg_r64( c, av[2], "shift count", eqs, err, es )) return( -1 ); + return( 0 ); + } + if( streqi( mn, "RCMP" ) ) + { + if( ac != 3 ) + { + snprintf( err, es, "RCMP requires r64 result, real a, real b" ); + return( -1 ); + } + if( put16( c, CPU_OP_RCMP ) || preg_r64( c, av[0], "RCMP result", eqs, err, es ) || + preg( c, av[1], 0, 1, eqs, err, es ) || preg( c, av[2], 0, 1, eqs, err, es )) return( -1 ); + return( 0 ); + } + if( streqi( mn, "RISNEG" ) || streqi( mn, "RSIGN" ) || streqi( mn, "RISIND" ) || + streqi( mn, "RISNAN" ) || streqi( mn, "RISNANMAX" ) || streqi( mn, "RISNANMIN" ) || + streqi( mn, "RISINF" ) || streqi( mn, "RISSIGNULL" )) + { + unsigned op; + if( ac != 2 ) + { + snprintf( err, es, "%s requires r64 result, real source", mn ); + return( -1 ); + } + if( streqi( mn, "RISNEG" ) ) op = CPU_OP_RISNEG; + else if( streqi( mn, "RSIGN" ) ) op = CPU_OP_RSIGN; + else if( streqi( mn, "RISIND" ) ) op = CPU_OP_RISIND; + else if( streqi( mn, "RISNAN" ) ) op = CPU_OP_RISNAN; + else if( streqi( mn, "RISNANMAX" ) ) op = CPU_OP_RISNANMAX; + else if( streqi( mn, "RISNANMIN" ) ) op = CPU_OP_RISNANMIN; + else if( streqi( mn, "RISINF" ) ) op = CPU_OP_RISINF; + else op = CPU_OP_RISSIGNULL; + if( put16( c, op ) || preg_r64( c, av[0], "real predicate result", eqs, err, es ) || + preg( c, av[1], 0, 1, eqs, err, es )) return( -1 ); + return( 0 ); + } + if( streqi( mn, "CREAL" ) || streqi( mn, "CIMAG" ) || streqi( mn, "CABS" ) || + streqi( mn, "CNORM" ) || streqi( mn, "CARG" )) + { + unsigned op; + if( ac != 2 ) + { + snprintf( err, es, "%s requires real destination, complex source", mn ); + return( -1 ); + } + if( streqi( mn, "CREAL" ) ) op = CPU_OP_CREAL; + else if( streqi( mn, "CIMAG" ) ) op = CPU_OP_CIMAG; + else if( streqi( mn, "CABS" ) ) op = CPU_OP_CABS; + else if( streqi( mn, "CNORM" ) ) op = CPU_OP_CNORM; + else op = CPU_OP_CARG; + if( put16( c, op ) || preg( c, av[0], 0, 1, eqs, err, es ) || preg( c, av[1], 1, 0, eqs, err, es ) ) return( -1 ); + return( 0 ); + } + if( streqi( mn, "CRTOC" ) ) + { + if( ac != 2 ) + { + snprintf( err, es, "CRTOC requires complex destination, real source" ); + return( -1 ); + } + if( put16( c, CPU_OP_CRTOC ) || preg( c, av[0], 1, 0, eqs, err, es ) || preg( c, av[1], 0, 1, eqs, err, es ) ) return( -1 ); + return( 0 ); + } + if( streqi( mn, "CGEN" ) || streqi( mn, "CPOLAR" ) ) + { + unsigned op = streqi( mn, "CGEN" ) ? CPU_OP_CGEN : CPU_OP_CPOLAR; + if( ac != 3 ) + { + snprintf( err, es, "%s requires complex destination, two real sources", mn ); + return( -1 ); + } + if( put16( c, op ) || preg( c, av[0], 1, 0, eqs, err, es ) || preg( c, av[1], 0, 1, eqs, err, es ) || + preg( c, av[2], 0, 1, eqs, err, es )) return( -1 ); + return( 0 ); + } + if( streqi( mn, "CISEQ" ) || streqi( mn, "CISNE" ) ) + { + unsigned op = streqi( mn, "CISEQ" ) ? CPU_OP_CISEQ : CPU_OP_CISNE; + if( ac != 3 ) + { + snprintf( err, es, "%s requires r64 result, complex a, complex b", mn ); + return( -1 ); + } + if( put16( c, op ) || preg_r64( c, av[0], "complex predicate result", eqs, err, es ) || + preg( c, av[1], 1, 0, eqs, err, es ) || preg( c, av[2], 1, 0, eqs, err, es )) return( -1 ); + return( 0 ); + } + if( streqi( mn, "LOAD" ) || streqi( mn, "STORE" ) ) + { + int store = streqi( mn, "STORE" ); + if( ac != 2 ) + { + snprintf( err, es, "wrong operand count" ); + return( -1 ); + } + if( put16( c, store ? CPU_OP_STORE : CPU_OP_LOAD ) ) return( -1 ); + if( store ) + { + if( addfix( df, c->n, av[0] ) || put64( c, 0 ) || preg( c, av[1], 0, 0, eqs, err, es ) ) return( -1 ); + } + else + { + if( preg( c, av[0], 0, 0, eqs, err, es ) || addfix( df, c->n, av[1] ) || put64( c, 0 ) ) return( -1 ); + } + return( 0 ); + } + if( streqi( mn, "LEA" ) ) + { + if( ac != 2 ) + { + snprintf( err, es, "LEA requires r64 destination and pointer expression" ); + return( -1 ); + } + if( put16( c, CPU_OP_LEA ) || preg_ptr( c, av[0], eqs, err, es ) || addfix( pf, c->n, av[1] ) || put64( c, 0 ) ) return( -1 ); + return( 0 ); + } + if( streqi( mn, "LEASP" ) ) + { + __mpu_int64_t off; + if( ac != 2 ) + { + snprintf( err, es, "LEASP requires r64 destination and signed SP offset" ); + return( -1 ); + } + { + int er = eval_i64( av[1], eqs, 0, &off, err, es ); + if( er < 0 ) return( -1 ); + if( er > 0 ) + { + snprintf( err, es, "LEASP offset must fit signed 64 bits" ); + return( -1 ); + } + } + if( put16( c, CPU_OP_LEASP ) || preg_ptr( c, av[0], eqs, err, es ) || put64( c, (__mpu_uint64_t)off ) ) return( -1 ); + return( 0 ); + } + if( streqi( mn, "LEAFP" ) ) + { + __mpu_int64_t off; + if( ac != 2 ) + { + snprintf( err, es, "LEAFP requires r64 destination and signed FP offset" ); + return( -1 ); + } + { + int er = eval_i64( av[1], eqs, 0, &off, err, es ); + if( er < 0 ) return( -1 ); + if( er > 0 ) + { + snprintf( err, es, "LEAFP offset must fit signed 64 bits" ); + return( -1 ); + } + } + if( put16( c, CPU_OP_LEAFP ) || preg_ptr( c, av[0], eqs, err, es ) || put64( c, (__mpu_uint64_t)off ) ) return( -1 ); + return( 0 ); + } + if( streqi( mn, "LOADP" ) || streqi( mn, "STOREP" ) ) + { + int store = streqi( mn, "STOREP" ); + if( ac != 2 ) + { + snprintf( err, es, "%s requires two register operands", mn ); + return( -1 ); + } + if( put16( c, store ? CPU_OP_STOREP : CPU_OP_LOADP ) ) return( -1 ); + if( store ) + { + if( preg_ptr( c, av[0], eqs, err, es ) || preg( c, av[1], 0, 0, eqs, err, es ) ) return( -1 ); + } + else + { + if( preg( c, av[0], 0, 0, eqs, err, es ) || preg_ptr( c, av[1], eqs, err, es ) ) return( -1 ); + } + return( 0 ); + } + if( streqi( mn, "PADD" ) || streqi( mn, "PSUB" ) || streqi( mn, "PDIFF" ) ) + { + unsigned op = streqi( mn, "PADD" ) ? CPU_OP_PADD : ( streqi( mn, "PSUB" ) ? CPU_OP_PSUB : CPU_OP_PDIFF ); + if( ac != 3 ) + { + snprintf( err, es, "%s requires three r64 operands", mn ); + return( -1 ); + } + if( put16( c, op ) || + preg_r64( c, av[0], mn, eqs, err, es ) || + preg_r64( c, av[1], mn, eqs, err, es ) || + preg_r64( c, av[2], mn, eqs, err, es )) return( -1 ); + return( 0 ); + } + if( streqi( mn, "PADDI" ) || streqi( mn, "PSUBI" ) ) + { + __mpu_int64_t off; + unsigned op = streqi( mn, "PADDI" ) ? CPU_OP_PADDI : CPU_OP_PSUBI; + if( ac != 3 ) + { + snprintf( err, es, "%s requires r64 destination, r64 PTR64 source and signed OFF64", mn ); + return( -1 ); + } + { + int er = eval_i64( av[2], eqs, 0, &off, err, es ); + if( er < 0 ) return( -1 ); + if( er > 0 ) + { + snprintf( err, es, "%s offset must fit signed 64 bits", mn ); + return( -1 ); + } + } + if( put16( c, op ) || + preg_r64( c, av[0], mn, eqs, err, es ) || + preg_r64( c, av[1], mn, eqs, err, es ) || + put64( c, (__mpu_uint64_t)off )) return( -1 ); + return( 0 ); + } + if( streqi( mn, "BMOV" ) ) + { + if( ac != 3 ) + { + snprintf( err, es, "BMOV requires r64 destination PTR64, r64 source PTR64 and r64 byte count" ); + return( -1 ); + } + if( put16( c, CPU_OP_BMOV ) || + preg_ptr( c, av[0], eqs, err, es ) || + preg_ptr( c, av[1], eqs, err, es ) || + preg_r64( c, av[2], "BMOV byte count", eqs, err, es )) return( -1 ); + return( 0 ); + } + if( streqi( mn, "BMOVI" ) ) + { + __mpu_uint64_t count; + if( ac != 3 ) + { + snprintf( err, es, "BMOVI requires r64 destination PTR64, r64 source PTR64 and unsigned IMM64 byte count" ); + return( -1 ); + } + { + int er = eval_u64( av[2], eqs, 0, &count, err, es ); + if( er < 0 ) return( -1 ); + if( er > 0 ) + { + snprintf( err, es, "BMOVI byte count must fit unsigned 64 bits" ); + return( -1 ); + } + } + if( put16( c, CPU_OP_BMOVI ) || + preg_ptr( c, av[0], eqs, err, es ) || + preg_ptr( c, av[1], eqs, err, es ) || + put64( c, count )) return( -1 ); + return( 0 ); + } + if( streqi( mn, "JMPP" ) || streqi( mn, "CALLP" ) ) + { + unsigned op = streqi( mn, "CALLP" ) ? CPU_OP_CALLP : CPU_OP_JMPP; + if( ac != 1 ) + { + snprintf( err, es, "%s requires one r64 PROGRAM PTR64 operand", mn ); + return( -1 ); + } + if( put16( c, op ) || preg_ptr( c, av[0], eqs, err, es ) ) return( -1 ); + return( 0 ); + } + if( streqi( mn, "JMP" ) || streqi( mn, "CALL" ) ) + { + int call = streqi( mn, "CALL" ); + if( ac != 1 ) + { + snprintf( err, es, "%s requires one CODE label", mn ); + return( -1 ); + } + if( put16( c, call ? CPU_OP_CALL : CPU_OP_JMP ) || addfix( cf, c->n, av[0] ) || put64( c, 0 ) ) return( -1 ); + return( 0 ); + } + if( streqi( mn, "RET" ) ) + { + if( ac != 0 ) + { + snprintf( err, es, "RET takes no operands" ); + return( -1 ); + } + return( put16( c, CPU_OP_RET ) ); + } + if( streqi( mn, "JSET" ) || streqi( mn, "JCLR" ) ) + { + unsigned bit; + if( ac != 2 || pswbitid( av[0], &bit ) ) + { + snprintf( err, es, "bad PSW bit for %s", mn ); + return( -1 ); + } + if( put16( c, streqi( mn, "JSET" ) ? CPU_OP_JSET : CPU_OP_JCLR ) || + put8( c, bit ) || addfix( cf, c->n, av[1] ) || put64( c, 0 )) return( -1 ); + return( 0 ); + } + { + struct A + { + const char *n; + unsigned op; + unsigned bit; + } + as[] = + { + { "JZ", CPU_OP_JSET, CPU_PSWBIT_ZF }, + { "JNZ", CPU_OP_JCLR, CPU_PSWBIT_ZF }, + { "JC", CPU_OP_JSET, CPU_PSWBIT_CF }, + { "JNC", CPU_OP_JCLR, CPU_PSWBIT_CF }, + { "JO", CPU_OP_JSET, CPU_PSWBIT_OF }, + { "JNO", CPU_OP_JCLR, CPU_PSWBIT_OF }, + { "JS", CPU_OP_JSET, CPU_PSWBIT_SF }, + { "JNS", CPU_OP_JCLR, CPU_PSWBIT_SF } + }; + size_t ai; + for( ai = 0; ai < sizeof( as ) / sizeof( as[0] ); ai++ ) if( streqi( mn, as[ai].n ) ) + { + if( ac != 1 ) + { + snprintf( err, es, "%s requires one CODE label", mn ); + return( -1 ); + } + if( put16( c, as[ai].op ) || put8( c, as[ai].bit ) || addfix( cf, c->n, av[0] ) || put64( c, 0 ) ) return( -1 ); + return( 0 ); + } + } + if( streqi( mn, "LDSP" ) || streqi( mn, "STSP" ) ) + { + __mpu_int64_t off; + int store = streqi( mn, "STSP" ); + if( ac != 2 ) + { + snprintf( err, es, "%s requires two operands", mn ); + return( -1 ); + } + { + int er = eval_i64( store ? av[0] : av[1], eqs, 0, &off, err, es ); + if( er < 0 ) return( -1 ); + if( er > 0 ) + { + snprintf( err, es, "%s offset must fit signed 64 bits", mn ); + return( -1 ); + } + } + if( put16( c, store ? CPU_OP_STSP : CPU_OP_LDSP ) ) return( -1 ); + if( store ) + { + if( put64( c, (__mpu_uint64_t)off ) || preg( c, av[1], 0, 0, eqs, err, es ) ) return( -1 ); + } + else + { + if( preg( c, av[0], 0, 0, eqs, err, es ) || put64( c, (__mpu_uint64_t)off ) ) return( -1 ); + } + return( 0 ); + } + if( streqi( mn, "LDFP" ) || streqi( mn, "STFP" ) ) + { + __mpu_int64_t off; + int store = streqi( mn, "STFP" ); + if( ac != 2 ) + { + snprintf( err, es, "%s requires two operands", mn ); + return( -1 ); + } + { + int er = eval_i64( store ? av[0] : av[1], eqs, 0, &off, err, es ); + if( er < 0 ) return( -1 ); + if( er > 0 ) + { + snprintf( err, es, "%s offset must fit signed 64 bits", mn ); + return( -1 ); + } + } + if( put16( c, store ? CPU_OP_STFP : CPU_OP_LDFP ) ) return( -1 ); + if( store ) + { + if( put64( c, (__mpu_uint64_t)off ) || preg( c, av[1], 0, 0, eqs, err, es ) ) return( -1 ); + } + else + { + if( preg( c, av[0], 0, 0, eqs, err, es ) || put64( c, (__mpu_uint64_t)off ) ) return( -1 ); + } + return( 0 ); + } + if( streqi( mn, "ADJSP" ) ) + { + __mpu_int64_t off; + if( ac != 1 ) + { + snprintf( err, es, "ADJSP requires one signed byte offset" ); + return( -1 ); + } + { + int er = eval_i64( av[0], eqs, 0, &off, err, es ); + if( er < 0 ) return( -1 ); + if( er > 0 ) + { + snprintf( err, es, "ADJSP offset must fit signed 64 bits" ); + return( -1 ); + } + } + if( put16( c, CPU_OP_ADJSP ) || put64( c, (__mpu_uint64_t)off ) ) return( -1 ); + return( 0 ); + } + if( streqi( mn, "SETSP" ) ) + { + if( ac != 1 ) + { + snprintf( err, es, "SETSP requires one r64 PTR64 operand" ); + return( -1 ); + } + if( put16( c, CPU_OP_SETSP ) || preg_ptr( c, av[0], eqs, err, es ) ) return( -1 ); + return( 0 ); + } + snprintf( err, es, "unknown mnemonic: %s", mn ); + return( -1 ); +} + +/* Special LDI emitter kept separate to make signed 64-bit parsing explicit. */ +static int +emit_ldi( buf_t *c, equs_t *eqs, char *rest, char *err, size_t es ) +{ + char *av[3]; + __mpu_int64_t v; + __int128 xv; + cpu_register r; + int ic, ac = splitargs( rest, av, 3 ); + if( ac != 2 ) + { + snprintf( err, es, "wrong operand count for LDI" ); + return( -1 ); + } + if( regparse( av[0], &r, &ic, eqs, err, es ) || ic ) + { + snprintf( err, es, "bad LDI destination" ); + return( -1 ); + } + if( eval_expression( av[1], eqs, 0, &xv, err, es ) ) + { + snprintf( err, es, "bad integer immediate: %s", av[1] ); + return( -1 ); + } + if( xv < ( __int128 ) INT64_MIN || xv > ( __int128 ) INT64_MAX ) + { + snprintf( err, es, "LDI immediate is not exactly representable as integer64;\nuse .integer <bits>, value in DATA and LOAD" ); + return( -1 ); + } + v = (__mpu_int64_t)xv; + if( r.bits < 64 ) + { + __mpu_int64_t lo = - ( (__mpu_int64_t)1 << ( r.bits - 1 ) ), hi = ( (__mpu_int64_t)1 << ( r.bits - 1 ) ) - 1; + if( v < lo || v > hi ) + { + snprintf( err, es, "LDI immediate is not exactly representable as integer%u;\nuse .integer <bits>, value in DATA and LOAD", r.bits ); + return( -1 ); + } + } + if( put16( c, CPU_OP_LDI ) || preg( c, av[0], 0, 1, eqs, err, es ) || put64( c, (__mpu_uint64_t)v ) ) return( -1 ); + return( 0 ); +} + +static int datadir( buf_t *d, char *line, fixups_t *cptr_fix, fixups_t *ptr_fix, + __mpu_uint64_t *section_align, equs_t *eqs, char *err, size_t es) +{ + char *p = trim( line ), *mn = p, *rest, *av[256]; + int ac, bits, nb; + __mpu_uint64_t uv; + while( *p && !isspace( (unsigned char)*p ) ) p++; + if( *p ) + { + *p++ = 0; + rest = trim( p ); + } + else rest = (char *)""; + + if( streqi( mn, ".align" ) ) + { + if( !*rest ) + { + snprintf( err, es, ".align requires a value" ); + return( -1 ); + } + { + int er = eval_u64( rest, eqs, 0, &uv, err, es ); + if( er < 0 ) return( -1 ); + if( er > 0 || !uv || uv > (__mpu_uint64_t)SIZE_MAX ) + { + snprintf( err, es, "bad .align" ); + return( -1 ); + } + } + if( section_align && uv > *section_align ) * section_align = uv; + while( d->n % (size_t)uv ) if( put8( d, 0 ) ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + return( 0 ); + } + + if( streqi( mn, ".utf8" ) || streqi( mn, ".ucs2" ) ) + { + buf_t text = + { + 0 + }; + if( !*rest ) + { + snprintf( err, es, "%s requires a quoted UTF-8 string", mn ); + return( -1 ); + } + if( parse_utf8_string( rest, &text, err, es ) ) return( -1 ); + if( streqi( mn, ".utf8" ) ) + { + if( put( d, text.p, text.n ) ) + { + free( text.p ); + snprintf( err, es, "out of memory" ); + return( -1 ); + } + } + else + { + __mpu_size_t nch = mpu_utf8_to_ucs2( 0, (const __mpu_char8_t *)text.p, 0 ), i; + __mpu_char16_t *ucs; + if( nch == (__mpu_size_t)-1 ) + { + free( text.p ); + snprintf( err, es, ".ucs2 string is not representable as strict UCS-2" ); + return( -1 ); + } + ucs = (__mpu_char16_t *)calloc( (size_t)nch + 1, sizeof( *ucs ) ); + if( !ucs ) + { + free( text.p ); + snprintf( err, es, "out of memory" ); + return( -1 ); + } + if( mpu_utf8_to_ucs2( ucs, (const __mpu_char8_t *)text.p, nch + 1 ) != nch ) + { + free( ucs ); + free( text.p ); + snprintf( err, es, "UTF-8 to UCS-2 conversion failed" ); + return( -1 ); + } + for( i = 0; i < nch; i++ ) + { + __mpu_UNIT_t x[2]; + __cpu_data_put_u16( x, (__mpu_uint16_t)ucs[i] ); + if( put( d, x, 2 ) ) + { + free( ucs ); + free( text.p ); + snprintf( err, es, "out of memory" ); + return( -1 ); + } + } + { + __mpu_UNIT_t z[2]; + __cpu_data_put_u16( z, 0 ); + if( put( d, z, 2 ) ) + { + free( ucs ); + free( text.p ); + snprintf( err, es, "out of memory" ); + return( -1 ); + } + } + free( ucs ); + } + free( text.p ); + return( 0 ); + } + + if( !*rest ) + { + snprintf( err, es, "data directive requires operands" ); + return( -1 ); + } + ac = splitargs( rest, av, 256 ); + if( ac < 0 ) + { + snprintf( err, es, "too many or malformed directive arguments" ); + return( -1 ); + } + + if( streqi( mn, ".byte" ) ) + { + int i; + if( ac < 1 ) + { + snprintf( err, es, ".byte requires one or more values" ); + return( -1 ); + } + for( i = 0; i < ac; i++ ) + { + int er = eval_u64( av[i], eqs, 0, &uv, err, es ); + if( er < 0 ) return( -1 ); + if( er > 0 || uv > 255 ) + { + snprintf( err, es, ".byte value must fit 8 bits: %s", av[i] ); + return( -1 ); + } + if( put8( d, (unsigned)uv ) ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + } + return( 0 ); + } + + if( streqi( mn, ".integer" ) || streqi( mn, ".int" ) ) + { + int i; + mpu_wide_int_t x; + if( ac < 2 ) + { + snprintf( err, es, "%s requires: bits, value[, value...]", mn ); + return( -1 ); + } + { + int er = eval_u64( av[0], eqs, 0, &uv, err, es ); + if( er < 0 ) return( -1 ); + if( er > 0 || uv > 65536 ) + { + snprintf( err, es, "bad .integer width" ); + return( -1 ); + } + } + bits = (int)uv; + nb = bits / 8; + if( bits < 8 || ( bits & ( bits - 1 ) ) ) + { + snprintf( err, es, "bad .integer width" ); + return( -1 ); + } + for( i = 1; i < ac; i++ ) + { + const char *value = av[i]; + char dec[64]; + __int128 ev; + memset( x, 0, sizeof( x ) ); + if( !plain_integer_literal( value ) ) + { + if( eval_expression( value, eqs, 0, &ev, err, es ) ) return( -1 ); + i128_decimal( ev, dec ); + value = dec; + } + iatoi( ( mpu_int * ) x, (__mpu_char8_t *)value, nb ); + if( put( d, x, (size_t)nb ) ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + } + return( 0 ); + } + + if( streqi( mn, ".real" ) ) + { + mpu_wide_real_t x; + if( ac != 2 ) + { + snprintf( err, es, ".real requires: bits, value" ); + return( -1 ); + } + { + int er = eval_u64( av[0], eqs, 0, &uv, err, es ); + if( er < 0 ) return( -1 ); + if( er > 0 || uv > 65536 ) + { + snprintf( err, es, "bad .real width" ); + return( -1 ); + } + } + bits = (int)uv; + nb = bits / 8; + if( bits < 32 || ( bits & ( bits - 1 ) ) ) + { + snprintf( err, es, "bad .real width" ); + return( -1 ); + } + memset( x, 0, sizeof( x ) ); + if( ascii_to_real( ( mpu_real * ) x, (__mpu_char8_t *)av[1], nb ) < 0 ) + { + snprintf( err, es, "bad .real constant" ); + return( -1 ); + } + if( put( d, x, (size_t)nb ) ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + return( 0 ); + } + + if( streqi( mn, ".complex" ) ) + { + mpu_wide_complex_t x; + mpu_wide_real_t r, j; + if( ac != 3 ) + { + snprintf( err, es, ".complex requires: physical-bits, real, imag" ); + return( -1 ); + } + { + int er = eval_u64( av[0], eqs, 0, &uv, err, es ); + if( er < 0 ) return( -1 ); + if( er > 0 || uv > 65536 ) + { + snprintf( err, es, "bad .complex physical width" ); + return( -1 ); + } + } + bits = (int)uv; + nb = bits / 8; + if( bits < 64 || ( bits & ( bits - 1 ) ) ) + { + snprintf( err, es, "bad .complex physical width" ); + return( -1 ); + } + if( ascii_to_real( ( mpu_real * ) r, (__mpu_char8_t *)av[1], nb / 2 ) < 0 || ascii_to_real( ( mpu_real * ) j, (__mpu_char8_t *)av[2], nb / 2 ) < 0 ) + { + snprintf( err, es, "bad .complex component" ); + return( -1 ); + } + c_gen_complex( ( mpu_complex * ) x, ( mpu_real * ) r, ( mpu_real * ) j, nb / 2 ); + if( put( d, x, (size_t)nb ) ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + return( 0 ); + } + + if( streqi( mn, ".cptr" ) || streqi( mn, ".codeptr" ) ) + { + __mpu_uint64_t zero = 0; + if( ac != 1 || !*av[0] ) + { + snprintf( err, es, ".cptr requires one CODE expression" ); + return( -1 ); + } + if( addfix( cptr_fix, d->n, av[0] ) || put( d, &zero, CPU_PTR64_SIZE ) ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + return( 0 ); + } + + if( streqi( mn, ".ptr" ) ) + { + __mpu_uint64_t zero = 0; + if( ac != 1 || !*av[0] ) + { + snprintf( err, es, ".ptr requires one pointer expression" ); + return( -1 ); + } + if( addfix( ptr_fix, d->n, av[0] ) || put( d, &zero, CPU_PTR64_SIZE ) ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + return( 0 ); + } + + snprintf( err, es, "unknown data directive: %s", mn ); + return( -1 ); +} + +static int +valid_symbol_name( const char *s ) +{ + if( !s || !symfirst( (unsigned char)*s ) ) return( 0 ); + for( s++; *s; s++ ) if( !symrest( (unsigned char)*s ) ) return( 0 ); + return( 1 ); +} + +static int +finddecl( const symdecls_t *d, const char *name, size_t *idx ) +{ + size_t i; + for( i = 0; i < d->n; i++ ) if( !strcmp( d->p[i].name, name ) ) + { + if( idx ) * idx = i; + return( 0 ); + } + return( -1 ); +} + +static int adddecl( symdecls_t *d, const char *name, int is_global, int is_extern, int is_weak, + char *err, size_t es) +{ + size_t i; + if( !valid_symbol_name( name ) ) + { + snprintf( err, es, "bad symbol name: %s", name ); + return( -1 ); + } + if( !strcmp( name, "_start_life" ) ) + { + snprintf( err, es, "_start_life is an assembler pseudo-label and cannot be declared" ); + return( -1 ); + } + if( is_weak && ( !strcmp( name, "_start" ) || !strcmp( name, "_life" ) || !strcmp( name, "_fini" ) ) ) + { + snprintf( err, es, "lifecycle symbol %s cannot be weak", name ); + return( -1 ); + } + if( !finddecl( d, name, &i ) ) + { + if( is_global ) d->p[i].is_global = 1; + if( is_extern ) d->p[i].is_extern = 1; + if( is_weak ) d->p[i].is_weak = 1; + return( 0 ); + } + if( grow( (void **)&d->p, &d->cap, d->n + 1, sizeof( *d->p ) ) ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + memset( &d->p[d->n], 0, sizeof( *d->p ) ); + strcpy( d->p[d->n].name, name ); + d->p[d->n].is_global = is_global; + d->p[d->n].is_extern = is_extern; + d->p[d->n].is_weak = is_weak; + d->n++; + return( 0 ); +} + +/* Return 1 for a declaration line, 0 otherwise, -1 on error. */ +static int +collect_decl_line( char *line, symdecls_t *decls, int line_no, char *err, size_t es ) +{ + char *p = trim( line ), *mn = p, *rest, *av[256]; + int ac, i, is_global = 0, is_extern = 0, is_weak = 0; + while( *p && !isspace( (unsigned char)*p ) ) p++; + if( *p ) + { + *p++ = 0; + rest = trim( p ); + } + else rest = (char *)""; + if( streqi( mn, ".global" ) || streqi( mn, ".globl" ) ) is_global = 1; + else + if( streqi( mn, ".extern" ) ) + { + is_global = 1; + is_extern = 1; + } + else + if( streqi( mn, ".weak" ) ) + { + is_weak = 1; + } + else return( 0 ); + if( !*rest ) + { + snprintf( err, es, "line %d: %s requires one or more symbol names", line_no, mn ); + return( -1 ); + } + ac = splitargs( rest, av, 256 ); + if( ac < 1 ) + { + snprintf( err, es, "line %d: malformed %s", line_no, mn ); + return( -1 ); + } + for( i = 0; i < ac; i++ ) if( adddecl( decls, av[i], is_global, is_extern, is_weak, err, es ) ) + { + char t[256]; + snprintf( t, sizeof( t ), "line %d: %s", line_no, err ); + snprintf( err, es, "%s", t ); + return( -1 ); + } + return( 1 ); +} + +/* .bss is SHT_NOBITS: only layout directives are legal and no payload + bytes are emitted into the relocatable object. */ +static int bssdir( size_t *bss_size, char *line, __mpu_uint64_t *section_align, + equs_t *eqs, char *err, size_t es) +{ + char *p = trim( line ), *mn = p, *rest; + __mpu_uint64_t v; + + while( *p && !isspace( (unsigned char)*p ) ) p++; + if( *p ) + { + *p++ = 0; + rest = trim( p ); + } + else rest = (char *)""; + + if( streqi( mn, ".align" ) ) + { + __mpu_uint64_t rem, add; + int er; + if( !*rest ) + { + snprintf( err, es, ".align requires a value" ); + return( -1 ); + } + er = eval_u64( rest, eqs, 0, &v, err, es ); + if( er < 0 ) return( -1 ); + if( er > 0 || !v || v > (__mpu_uint64_t)SIZE_MAX || ( v & ( v - 1 ) ) ) + { + snprintf( err, es, "bad .align" ); + return( -1 ); + } + if( section_align && v > *section_align ) * section_align = v; + rem = (__mpu_uint64_t)( *bss_size ) % v; + add = rem ? v - rem : 0; + if( add > (__mpu_uint64_t)SIZE_MAX - *bss_size ) + { + snprintf( err, es, ".bss size overflow" ); + return( -1 ); + } + *bss_size+=(size_t)add; + return( 0 ); + } + + if( streqi( mn, ".zero" ) ) + { + int er; + if( !*rest ) + { + snprintf( err, es, ".zero requires a byte count" ); + return( -1 ); + } + er = eval_u64( rest, eqs, 0, &v, err, es ); + if( er < 0 ) return( -1 ); + if( er > 0 || v > (__mpu_uint64_t)SIZE_MAX - *bss_size ) + { + snprintf( err, es, "bad .zero byte count" ); + return( -1 ); + } + *bss_size+=(size_t)v; + return( 0 ); + } + + snprintf( err, es, "only .align and .zero are valid in .bss" ); + return( -1 ); +} + +/* Return 1 when line is .equ, 0 when it is not, -1 on malformed .equ. */ +static int +collect_equ_line( char *line, equs_t *eqs, int line_no, char *err, size_t es ) +{ + char *p = trim( line ), *mn = p, *rest, *av[2]; + int ac; + while( *p && !isspace( (unsigned char)*p ) ) p++; + if( *p ) + { + *p++ = 0; + rest = trim( p ); + } + else rest = (char *)""; + if( !streqi( mn, ".equ" ) ) return( 0 ); + if( !*rest ) + { + snprintf( err, es, "line %d: .equ requires NAME, expression", line_no ); + return( -1 ); + } + ac = splitargs( rest, av, 2 ); + if( ac != 2 || !valid_symbol_name( av[0] ) || !*av[1] ) + { + snprintf( err, es, "line %d: .equ requires NAME, expression", line_no ); + return( -1 ); + } + if( addequ( eqs, av[0], av[1], line_no, err, es ) ) + { + char t[256]; + snprintf( t, sizeof( t ), "line %d: %s", line_no, err ); + snprintf( err, es, "%s", t ); + return( -1 ); + } + return( 1 ); +} + +enum asm_section +{ + ASM_SEC_NONE = 0, + ASM_SEC_TEXT, + ASM_SEC_DATA, + ASM_SEC_BSS, + ASM_SEC_INIT, + ASM_SEC_FINI, + ASM_SEC_CONTROL, + ASM_SEC_INPUT, + ASM_SEC_OUTPUT, + ASM_SEC_TRACE +}; + +static int label_in_any( const char *name, const labels_t *a, const labels_t *b, + const labels_t *c, const labels_t *d, const labels_t *e, + const labels_t *f) +{ + __mpu_uint64_t x; + return( a && !findlabel( a, name, &x ) ) || ( b && !findlabel( b, name, &x ) ) || + ( c && !findlabel( c, name, &x ) ) || ( d && !findlabel( d, name, &x ) ) || + ( e && !findlabel( e, name, &x ) ) || ( f && !findlabel( f, name, &x ) ); +} + +static int resolve_ptr_expression( const char *expr, equs_t *eqs, + const labels_t *code, const labels_t *data, + const labels_t *control, const labels_t *input, + const labels_t *output, const labels_t *trace, + const size_t sizes[8], __mpu_uint64_t *ptr, + char *err, size_t es) +{ + const labels_t *ls[6] = + { + data, control, input, output, trace, code + }; + const unsigned segs[6] = + { + CPU_PTR_SEG_DATA, CPU_PTR_SEG_CONTROL, CPU_PTR_SEG_INPUT, + CPU_PTR_SEG_OUTPUT, CPU_PTR_SEG_TRACE, CPU_PTR_SEG_PROGRAM + }; + __mpu_uint64_t value = 0; + int i, found = 0, er; + unsigned found_seg = CPU_PTR_SEG_DATA; + char tmp[256]; + + /* A pure absolute/.equ expression keeps historical LEA semantics: DATA. */ + er = eval_u64( expr, eqs, 0, &value, tmp, sizeof( tmp ) ); + if( er == 0 ) + { + if( value > sizes[CPU_PTR_SEG_DATA] ) + { + snprintf( err, es, "LEA DATA expression is outside DATA: %s", expr ); + return( -1 ); + } + return( __cpu_ptr_make( ptr, CPU_PTR_SEG_DATA, value ) ); + } + + for( i = 0; i < 6; i++ ) + { + __mpu_uint64_t v; + if( eval_u64( expr, eqs, ls[i], &v, tmp, sizeof( tmp ) ) == 0 ) + { + found++; + value = v; + found_seg = segs[i]; + } + } + if( found != 1 ) + { + snprintf( err, es, found ? "ambiguous pointer expression: %s" : "unknown symbol in pointer expression: %s", expr ); + return( -1 ); + } + if( value > sizes[found_seg] ) + { + snprintf( err, es, "pointer expression is outside its segment: %s", expr ); + return( -1 ); + } + if( found_seg == CPU_PTR_SEG_HEAP && value < CPU_HEAP_BEGIN ) + { + snprintf( err, es, "pointer expression enters reserved HEAP guard: %s", expr ); + return( -1 ); + } + if( __cpu_ptr_make( ptr, found_seg, value ) ) + { + snprintf( err, es, "cannot encode PTR64 expression: %s", expr ); + return( -1 ); + } + return( 0 ); +} + +static int apply_cptr_fixups( buf_t *dst, const fixups_t *fx, equs_t *eqs, + const labels_t *clabels, size_t code_size, + char *err, size_t es) +{ + size_t i; + for( i = 0; i < fx->n; i++ ) + { + __mpu_uint64_t a, ptr; + int er = eval_u64( fx->p[i].expr, eqs, clabels, &a, err, es ); + if( er < 0 ) + { + char cause[256]; + snprintf( cause, sizeof( cause ), "%s", err ); + snprintf( err, es, ".cptr expression '%s': %s", fx->p[i].expr, cause ); + return( -1 ); + } + if( er > 0 || a >= code_size ) + { + snprintf( err, es, ".cptr expression does not address PROGRAM: %s", fx->p[i].expr ); + return( -1 ); + } + if( __cpu_ptr_make( &ptr, CPU_PTR_SEG_PROGRAM, a ) ) + { + snprintf( err, es, "cannot encode PROGRAM PTR64 for: %s", fx->p[i].expr ); + return( -1 ); + } + __cpu_data_put_u64( dst->p + fx->p[i].pos, ptr ); + } + return( 0 ); +} + +static int apply_ptr_data_fixups( buf_t *dst, const fixups_t *fx, equs_t *eqs, + const labels_t *code, const labels_t *data, + const labels_t *control, const labels_t *input, + const labels_t *output, const labels_t *trace, + const size_t sizes[8], char *err, size_t es) +{ + size_t i; + for( i = 0; i < fx->n; i++ ) + { + __mpu_uint64_t ptr; + if( resolve_ptr_expression( fx->p[i].expr, eqs, code, data, control, input, output, trace, + sizes, &ptr, err, es)) + { + char cause[256]; + snprintf( cause, sizeof( cause ), "%s", err ); + snprintf( err, es, ".ptr expression '%s': %s", fx->p[i].expr, cause ); + return( -1 ); + } + __cpu_data_put_u64( dst->p + fx->p[i].pos, ptr ); + } + return( 0 ); +} + +int +cpu_assemble_text( const char *src, cpu_asm_image *im, char *err, size_t es ) +{ + char *copy = 0, *scan = 0, *save, *line; + buf_t code = + { + 0 + } + , data = + { + 0 + } + , control = + { + 0 + } + , input = + { + 0 + } + , output = + { + 0 + } + , trace = + { + 0 + }; + labels_t clabels = + { + 0 + } + , dlabels = + { + 0 + } + , ctlabels = + { + 0 + } + , ilabels = + { + 0 + } + , olabels = + { + 0 + } + , tlabels = + { + 0 + }; + fixups_t dfix = + { + 0 + } + , cfix = + { + 0 + } + , pfix = + { + 0 + }; + fixups_t dcptr = + { + 0 + } + , ccptr = + { + 0 + } + , icptr = + { + 0 + } + , ocptr = + { + 0 + } + , tcptr = + { + 0 + }; + fixups_t dptr = + { + 0 + } + , ctptr = + { + 0 + } + , iptr = + { + 0 + } + , optr = + { + 0 + } + , tptr = + { + 0 + }; + __mpu_uint64_t dalign = 1, ctalign = 1, ialign = 1, oalign = 1, talign = 1; + equs_t eqs = + { + 0 + }; + int section = ASM_SEC_NONE, line_no = 0, rc = -1; + int init_seen = 0, text_seen = 0, fini_seen = 0, start_seen = 0, life_seen = 0, fini_entry_seen = 0; + __mpu_uint64_t first_text = 0, start_entry = 0, life_entry = 0, fini_entry = 0; + + if( !src || !im ) return( -1 ); + memset( im, 0, sizeof( *im ) ); + if( !mpu_utf8valid( (const __mpu_char8_t *)src ) ) + { + snprintf( err, es, "assembler source is not valid UTF-8" ); + return( -1 ); + } + + /* Resolve absolute .equ symbols first. Section labels intentionally do not + participate in .equ, preserving the distinction between absolute symbols + and relocatable/segment-relative labels. */ + scan = strdup( src ); + if( !scan ) return( -1 ); + for( line = strtok_r( scan, "\n", &save ); line; line = strtok_r( NULL, "\n", &save ) ) + { + int er; + line_no++; + strip_comment( line ); + if( !*trim( line ) ) continue; + er = collect_equ_line( line, &eqs, line_no, err, es ); + if( er < 0 ) goto out; + } + free( scan ); + scan = 0; + { + size_t i; + __int128 v; + for( i = 0; i < eqs.n; i++ ) if( equ_value( &eqs, i, &v, err, es ) ) + { + char t[256]; + snprintf( t, sizeof( t ), "line %d: %s", eqs.p[i].line, err ); + snprintf( err, es, "%s", t ); + goto out; + } + } + + /* DATA keeps its ABI NULL guard. Other image-backed data spaces begin at + offset zero because their non-zero segment selector already distinguishes + them from the all-zero NULL PTR64. */ + while( data.n < CPU_DATA_NULL_GUARD_SIZE ) + if( put8( &data, 0 ) ) + { + snprintf( err, es, "out of memory" ); + goto out; + } + + copy = strdup( src ); + if( !copy ) goto out; + line_no = 0; + for( line = strtok_r( copy, "\n", &save ); line; line = strtok_r( NULL, "\n", &save ) ) + { + char *p, *colon, *mn, *rest, *q; + buf_t *db = 0; + labels_t *dl = 0; + fixups_t *cpfx = 0, *pfx = 0; + __mpu_uint64_t *salign = 0; + line_no++; + strip_comment( line ); + p = trim( line ); + if( !*p ) continue; + + q = p; + while( *q && !isspace( (unsigned char)*q ) ) q++; + if( (size_t)( q - p ) == 4 && !strncasecmp( p, ".equ", 4 ) ) continue; + + if( streqi( p, ".text" ) ) + { + if( !text_seen ) + { + first_text = (__mpu_uint64_t)code.n; + text_seen = 1; + } + section = ASM_SEC_TEXT; + continue; + } + if( streqi( p, ".init" ) ) + { + init_seen = 1; + section = ASM_SEC_INIT; + continue; + } + if( streqi( p, ".fini" ) ) + { + fini_seen = 1; + section = ASM_SEC_FINI; + continue; + } + if( streqi( p, ".data" ) ) + { + section = ASM_SEC_DATA; + continue; + } + if( streqi( p, ".control" ) ) + { + section = ASM_SEC_CONTROL; + continue; + } + if( streqi( p, ".input" ) ) + { + section = ASM_SEC_INPUT; + continue; + } + if( streqi( p, ".output" ) ) + { + section = ASM_SEC_OUTPUT; + continue; + } + if( streqi( p, ".trace" ) ) + { + section = ASM_SEC_TRACE; + continue; + } + + colon = label_colon( p ); + if( colon ) + { + size_t ei; + char *name; + *colon = 0; + name = trim( p ); + if( !valid_symbol_name( name ) ) + { + snprintf( err, es, "line %d: bad label name", line_no ); + goto out; + } + if( !findequ( &eqs, name, &ei ) ) + { + snprintf( err, es, "line %d: label conflicts with .equ symbol: %s", line_no, name ); + goto out; + } + if( label_in_any( name, &clabels, &dlabels, &ctlabels, &ilabels, &olabels, &tlabels ) ) + { + snprintf( err, es, "line %d: duplicate label: %s", line_no, name ); + goto out; + } + if( section == ASM_SEC_TEXT || section == ASM_SEC_INIT || section == ASM_SEC_FINI ) + { + if( addlabel( &clabels, name, code.n ) ) + { + snprintf( err, es, "line %d: bad/duplicate CODE label", line_no ); + goto out; + } + if( !strcmp( name, "_start" ) ) + { + if( section != ASM_SEC_INIT ) + { + snprintf( err, es, "line %d: _start must be in .init", line_no ); + goto out; + } + start_seen = 1; + start_entry = code.n; + } + if( !strcmp( name, "_life" ) ) + { + if( section != ASM_SEC_TEXT ) + { + snprintf( err, es, "line %d: _life must be in .text", line_no ); + goto out; + } + life_seen = 1; + life_entry = code.n; + } + if( !strcmp( name, "_fini" ) ) + { + if( section != ASM_SEC_FINI ) + { + snprintf( err, es, "line %d: _fini must be in .fini", line_no ); + goto out; + } + fini_entry_seen = 1; + fini_entry = code.n; + } + } + else + { + switch( section ) + { + case ASM_SEC_DATA: dl = &dlabels; + break; + case ASM_SEC_CONTROL: dl = &ctlabels; + break; + case ASM_SEC_INPUT: dl = &ilabels; + break; + case ASM_SEC_OUTPUT: dl = &olabels; + break; + case ASM_SEC_TRACE: dl = &tlabels; + break; + default: break; + } + if( !dl || addlabel( dl, name, ( section == ASM_SEC_DATA ) ? data.n : ( section == ASM_SEC_CONTROL ) ? control.n : ( section == ASM_SEC_INPUT ) ? input.n : ( section == ASM_SEC_OUTPUT ) ? output.n : trace.n ) ) + { + snprintf( err, es, "line %d: label outside a valid section or duplicate label", line_no ); + goto out; + } + } + p = trim( colon + 1 ); + if( !*p ) continue; + } + + switch( section ) + { + case ASM_SEC_DATA: db = &data; + cpfx = &dcptr; + pfx = &dptr; + salign = &dalign; + break; + case ASM_SEC_CONTROL: db = &control; + cpfx = &ccptr; + pfx = &ctptr; + salign = &ctalign; + break; + case ASM_SEC_INPUT: db = &input; + cpfx = &icptr; + pfx = &iptr; + salign = &ialign; + break; + case ASM_SEC_OUTPUT: db = &output; + cpfx = &ocptr; + pfx = &optr; + salign = &oalign; + break; + case ASM_SEC_TRACE: db = &trace; + cpfx = &tcptr; + pfx = &tptr; + salign = &talign; + break; + default:break; + } + if( db ) + { + if( datadir( db, p, cpfx, pfx, salign, &eqs, err, es ) ) + { + char t[256]; + snprintf( t, sizeof( t ), "line %d: %s", line_no, err ); + snprintf( err, es, "%s", t ); + goto out; + } continue; + } + if( section != ASM_SEC_TEXT && section != ASM_SEC_INIT && section != ASM_SEC_FINI ) + { + snprintf( err, es, "line %d: select a code or data section", line_no ); + goto out; + } + + mn = p; + while( *p && !isspace( (unsigned char)*p ) ) p++; + if( *p ) + { + *p++ = 0; + rest = trim( p ); + } + else rest = (char *)""; + if( streqi( mn, "LDI" ) ) + { + if( !*rest || emit_ldi( &code, &eqs, rest, err, es ) ) goto badline; + } + else + { + if( emit_instruction( &code, &dfix, &cfix, &pfix, &eqs, mn, rest, err, es ) ) goto badline; + } continue; +badline: + { + char t[256]; + snprintf( t, sizeof( t ), "line %d: %s", line_no, err ); + snprintf( err, es, "%s", t ); + goto out; + } + } + + if( init_seen && !start_seen ) + { + snprintf( err, es, ".init section requires ABI entry label _start" ); + goto out; + } + if( fini_seen && !fini_entry_seen ) + { + snprintf( err, es, ".fini section requires ABI entry label _fini" ); + goto out; + } + if( !text_seen ) + { + snprintf( err, es, "program requires a .text section" ); + goto out; + } + if( !life_seen ) life_entry = first_text; + /* backward-compatible legacy .text image */ + + { + size_t i; + size_t sizes[8] = + { + data.n, 0, code.n, control.n, input.n, output.n, trace.n, 0 + }; + for( i = 0; i < dfix.n; i++ ) + { + __mpu_uint64_t a; + int er = eval_u64( dfix.p[i].expr, &eqs, &dlabels, &a, err, es ); + if( er < 0 ) + { + char cause[256]; + snprintf( cause, sizeof( cause ), "%s", err ); + snprintf( err, es, "DATA expression '%s': %s", dfix.p[i].expr, cause ); + goto out; + } + if( er > 0 ) + { + snprintf( err, es, "DATA expression out of unsigned 64-bit range: %s", dfix.p[i].expr ); + goto out; + } + __cpu_code_put_u64( code.p + dfix.p[i].pos, a ); + } + for( i = 0; i < cfix.n; i++ ) + { + __mpu_uint64_t a; + int er = eval_u64( cfix.p[i].expr, &eqs, &clabels, &a, err, es ); + if( er < 0 ) + { + char cause[256]; + snprintf( cause, sizeof( cause ), "%s", err ); + snprintf( err, es, "CODE expression '%s': %s", cfix.p[i].expr, cause ); + goto out; + } + if( er > 0 || a >= code.n ) + { + snprintf( err, es, "CODE expression does not address PROGRAM: %s", cfix.p[i].expr ); + goto out; + } + __cpu_code_put_u64( code.p + cfix.p[i].pos, a ); + } + for( i = 0; i < pfix.n; i++ ) + { + __mpu_uint64_t ptr; + if( resolve_ptr_expression( pfix.p[i].expr, &eqs, &clabels, &dlabels, &ctlabels, &ilabels, &olabels, &tlabels, sizes, &ptr, err, es ) ) goto out; + __cpu_code_put_u64( code.p + pfix.p[i].pos, ptr ); + } + if( apply_cptr_fixups( &data, &dcptr, &eqs, &clabels, code.n, err, es ) || + apply_cptr_fixups( &control, &ccptr, &eqs, &clabels, code.n, err, es ) || + apply_cptr_fixups( &input, &icptr, &eqs, &clabels, code.n, err, es ) || + apply_cptr_fixups( &output, &ocptr, &eqs, &clabels, code.n, err, es ) || + apply_cptr_fixups( &trace, &tcptr, &eqs, &clabels, code.n, err, es ) || + apply_ptr_data_fixups( &data, &dptr, &eqs, &clabels, &dlabels, &ctlabels, &ilabels, &olabels, &tlabels, sizes, err, es ) || + apply_ptr_data_fixups( &control, &ctptr, &eqs, &clabels, &dlabels, &ctlabels, &ilabels, &olabels, &tlabels, sizes, err, es ) || + apply_ptr_data_fixups( &input, &iptr, &eqs, &clabels, &dlabels, &ctlabels, &ilabels, &olabels, &tlabels, sizes, err, es ) || + apply_ptr_data_fixups( &output, &optr, &eqs, &clabels, &dlabels, &ctlabels, &ilabels, &olabels, &tlabels, sizes, err, es ) || + apply_ptr_data_fixups( &trace, &tptr, &eqs, &clabels, &dlabels, &ctlabels, &ilabels, &olabels, &tlabels, sizes, err, es )) goto out; + } + + im->code = code.p; + im->code_size = code.n; + code.p = 0; + im->data = data.p; + im->data_size = data.n; + data.p = 0; + im->control = control.p; + im->control_size = control.n; + control.p = 0; + im->input = input.p; + im->input_size = input.n; + input.p = 0; + im->output = output.p; + im->output_size = output.n; + output.p = 0; + im->trace = trace.p; + im->trace_size = trace.n; + trace.p = 0; + im->has_init = init_seen; + im->init_entry = start_entry; + im->has_life = 1; + im->life_entry = life_entry; + im->has_fini = fini_seen; + im->fini_entry = fini_entry; + rc = 0; + +out: + free( scan ); + free( copy ); + free( code.p ); + free( data.p ); + free( control.p ); + free( input.p ); + free( output.p ); + free( trace.p ); + free( clabels.p ); + free( dlabels.p ); + free( ctlabels.p ); + free( ilabels.p ); + free( olabels.p ); + free( tlabels.p ); + free( dfix.p ); + free( cfix.p ); + free( pfix.p ); + free( dcptr.p ); + free( ccptr.p ); + free( icptr.p ); + free( ocptr.p ); + free( tcptr.p ); + free( dptr.p ); + free( ctptr.p ); + free( iptr.p ); + free( optr.p ); + free( tptr.p ); + free( eqs.p ); + return( rc ); +} + +void +cpu_asm_image_free( cpu_asm_image *im ) +{ + size_t i; + if( !im ) return; + free( im->code ); + free( im->data ); + free( im->control ); + free( im->input ); + free( im->output ); + free( im->trace ); + for( i = 0; i < im->dyn_symbol_count; i++ ) free( im->dyn_symbols[i].name ); + free( im->dyn_symbols ); + free( im->dyn_relocs ); + memset( im, 0, sizeof( *im ) ); +} + +/* ------------------------------------------------------------------------- + * Relocatable assembler path -- MELF logical sections. + * + * ET_REL keeps .init/.text/.fini distinct and stores explicit RELA-like + * semantic records in cpu_asm_object. cpu-object.c maps this logical object + * one-to-one onto MELF sections/symbols/RELA records. + * ------------------------------------------------------------------------- */ + +static int +obj_symbol_defined( const cpu_asm_object *o, const char *name, size_t *idx ) +{ + size_t i; + for( i = 0; i < o->symbol_count; i++ ) + if( !strcmp( o->symbols[i].name, name ) && o->symbols[i].section != CPU_OBJ_SEC_UNDEF ) + { + if( idx ) * idx = i; + return( 1 ); + } + return( 0 ); +} + +static int +obj_find_symbol( const cpu_asm_object *o, const char *name, size_t *idx ) +{ + size_t i; + for( i = 0; i < o->symbol_count; i++ ) if( !strcmp( o->symbols[i].name, name ) ) + { + if( idx ) * idx = i; + return( 0 ); + } + return( -1 ); +} + +static int obj_add_symbol( cpu_asm_object *o, const char *name, unsigned section, unsigned binding, unsigned type, + __mpu_uint64_t value, char *err, size_t es) +{ + cpu_asm_symbol *q; + size_t i; + if( !obj_find_symbol( o, name, &i ) ) + { + cpu_asm_symbol *x = &o->symbols[i]; + if( x->section != CPU_OBJ_SEC_UNDEF && section != CPU_OBJ_SEC_UNDEF ) + { + snprintf( err, es, "duplicate object symbol: %s", name ); + return( -1 ); + } + if( x->section == CPU_OBJ_SEC_UNDEF && section != CPU_OBJ_SEC_UNDEF ) + { + x->section = section; + x->value = value; + x->type = type; + } + if( binding == CPU_OBJ_BIND_WEAK ) x->binding = CPU_OBJ_BIND_WEAK; + else if( binding == CPU_OBJ_BIND_GLOBAL && x->binding != CPU_OBJ_BIND_WEAK ) x->binding = CPU_OBJ_BIND_GLOBAL; + return( 0 ); + } + q = (cpu_asm_symbol *)realloc( o->symbols, ( o->symbol_count + 1 ) * sizeof( *q ) ); + if( !q ) + { + 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( name ); + if( !o->symbols[o->symbol_count].name ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + o->symbols[o->symbol_count].section = section; + o->symbols[o->symbol_count].binding = binding; + o->symbols[o->symbol_count].type = type; + o->symbols[o->symbol_count].value = value; + o->symbol_count++; + return( 0 ); +} + +static int obj_add_reloc( cpu_asm_object *o, unsigned section, unsigned type, size_t sym, + __mpu_uint64_t off, __mpu_int64_t addend, char *err, size_t es) +{ + cpu_asm_reloc *q = (cpu_asm_reloc *)realloc( o->relocs, ( o->reloc_count + 1 ) * sizeof( *q ) ); + if( !q ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + o->relocs = q; + o->relocs[o->reloc_count].section = section; + o->relocs[o->reloc_count].type = type; + o->relocs[o->reloc_count].symbol_index = sym; + o->relocs[o->reloc_count].offset = off; + o->relocs[o->reloc_count].addend = addend; + o->reloc_count++; + return( 0 ); +} + +static int obj_label_lookup( const char *name, + const labels_t *init, const labels_t *text, const labels_t *fini, + const labels_t *data, const labels_t *bss, const labels_t *control, const labels_t *input, + const labels_t *output, const labels_t *trace, + unsigned *section, __mpu_uint64_t *value) +{ + if( init && !findlabel( init, name, value ) ) + { + *section = CPU_OBJ_SEC_INIT; + return( 0 ); + } + if( text && !findlabel( text, name, value ) ) + { + *section = CPU_OBJ_SEC_TEXT; + return( 0 ); + } + if( fini && !findlabel( fini, name, value ) ) + { + *section = CPU_OBJ_SEC_FINI; + return( 0 ); + } + if( data && !findlabel( data, name, value ) ) + { + *section = CPU_OBJ_SEC_DATA; + return( 0 ); + } + if( bss && !findlabel( bss, name, value ) ) + { + *section = CPU_OBJ_SEC_BSS; + return( 0 ); + } + if( control && !findlabel( control, name, value ) ) + { + *section = CPU_OBJ_SEC_CONTROL; + return( 0 ); + } + if( input && !findlabel( input, name, value ) ) + { + *section = CPU_OBJ_SEC_INPUT; + return( 0 ); + } + if( output && !findlabel( output, name, value ) ) + { + *section = CPU_OBJ_SEC_OUTPUT; + return( 0 ); + } + if( trace && !findlabel( trace, name, value ) ) + { + *section = CPU_OBJ_SEC_TRACE; + return( 0 ); + } + return( -1 ); +} + +static int obj_name_defined_any( const char *name, + const labels_t *init, const labels_t *text, const labels_t *fini, + const labels_t *data, const labels_t *bss, const labels_t *control, const labels_t *input, + const labels_t *output, const labels_t *trace) +{ + unsigned s; + __mpu_uint64_t v; + return( !obj_label_lookup( name, init, text, fini, data, bss, control, input, output, trace, &s, &v ) ); +} + +static int reloc_expression_melf( const char *expr, equs_t *eqs, const symdecls_t *decls, + const labels_t *init, const labels_t *text, const labels_t *fini, + const labels_t *data, const labels_t *bss, const labels_t *control, const labels_t *input, + const labels_t *output, const labels_t *trace, + char sym[ASM_NAME_MAX], int *has_sym, __mpu_int64_t *addend, + __mpu_uint64_t *absolute, char *err, size_t es) +{ + const char *p = expr; + char found[ASM_NAME_MAX] = ""; + int seen = 0; + while( *p ) + { + if( isdigit( (unsigned char)*p ) ) + { + char *end = 0; + errno = 0; + (void)strtoull( p, &end, 0 ); + if( end && end > p ) + { + p = end; + continue; + } + } + if( symfirst( (unsigned char)*p ) ) + { + char n[ASM_NAME_MAX]; + size_t k = 0, ei, di; + unsigned sec; + __mpu_uint64_t val; + while( symrest( (unsigned char)*p ) ) + { + if( k + 1 >= sizeof( n ) ) + { + snprintf( err, es, "symbol name too long" ); + return( -1 ); + } + n[k++] = *p++; + } + n[k] = 0; + if( !findequ( eqs, n, &ei ) ) continue; + if( !obj_label_lookup( n, init, text, fini, data, bss, control, input, output, trace, &sec, &val ) || !finddecl( decls, n, &di ) ) + { + if( !seen ) + { + strcpy( found, n ); + seen = 1; + } + else + if( strcmp( found, n ) ) + { + snprintf( err, es, "relocatable expression contains more than one symbol: %s", expr ); + return( -1 ); + } continue; + } + snprintf( err, es, "unknown symbol in relocatable expression: %s", n ); + return( -1 ); + } + p++; + } + if( !seen ) + { + __int128 v; + if( eval_expression( expr, eqs, 0, &v, err, es ) ) return( -1 ); + if( v < 0 || v > ( __int128 ) UINT64_MAX ) + { + snprintf( err, es, "absolute expression out of unsigned 64-bit range: %s", expr ); + return( -1 ); + } + *has_sym = 0; + *absolute = (__mpu_uint64_t)v; + return( 0 ); + } + else + { + labels_t fake = + { + 0 + }; + label_t one; + __int128 v0, v1, d; + memset( &one, 0, sizeof( one ) ); + strcpy( one.name, found ); + fake.p = &one; + fake.n = fake.cap = 1; + one.addr = 0; + if( eval_expression( expr, eqs, &fake, &v0, err, es ) ) return( -1 ); + one.addr = 1; + if( eval_expression( expr, eqs, &fake, &v1, err, es ) ) return( -1 ); + d = v1 - v0; + if( d == 0 ) + { + if( v0 < 0 || v0 > ( __int128 ) UINT64_MAX ) + { + snprintf( err, es, "absolute expression out of unsigned 64-bit range: %s", expr ); + return( -1 ); + } + *has_sym = 0; + *absolute = (__mpu_uint64_t)v0; + return( 0 ); + } + if( d != 1 ) + { + snprintf( err, es, "relocatable expression must be SYMBOL + constant: %s", expr ); + return( -1 ); + } + if( v0 < ( __int128 ) INT64_MIN || v0 > ( __int128 ) INT64_MAX ) + { + snprintf( err, es, "relocation addend out of signed 64-bit range: %s", expr ); + return( -1 ); + } + strcpy( sym, found ); + *has_sym = 1; + *addend = (__mpu_int64_t)v0; + return( 0 ); + } +} + +static int process_obj_fixups_melf( cpu_asm_object *o, buf_t *src, unsigned srcsec, const fixups_t *fx, + unsigned type, equs_t *eqs, const symdecls_t *decls, + const labels_t *init, const labels_t *text, const labels_t *fini, + const labels_t *data, const labels_t *bss, const labels_t *control, const labels_t *input, + const labels_t *output, const labels_t *trace, char *err, size_t es) +{ + size_t i; + for( i = 0; i < fx->n; i++ ) + { + char name[ASM_NAME_MAX]; + int hs = 0; + __mpu_int64_t add = 0; + __mpu_uint64_t abs = 0, ptr; + size_t si; + if( reloc_expression_melf( fx->p[i].expr, eqs, decls, init, text, fini, data, bss, control, input, output, trace, name, &hs, &add, &abs, err, es ) ) return( -1 ); + if( hs ) + { + if( obj_find_symbol( o, name, &si ) ) + { + snprintf( err, es, "internal assembler symbol lookup failed: %s", name ); + return( -1 ); + } + if( o->symbols[si].section != CPU_OBJ_SEC_UNDEF ) + { + unsigned ts = o->symbols[si].section; + if( type == CPU_RELOC_CODE64 && !cpu_obj_sec_is_program( ts ) ) + { + snprintf( err, es, "CODE64 relocation target is not PROGRAM: %s", name ); + return( -1 ); + } + if( type == CPU_RELOC_DATA64 && ts != CPU_OBJ_SEC_DATA && ts != CPU_OBJ_SEC_BSS ) + { + snprintf( err, es, "ADDR64 relocation target is not DATA: %s", name ); + return( -1 ); + } + if( type == CPU_RELOC_CPTR64_DATA && !cpu_obj_sec_is_program( ts ) ) + { + snprintf( err, es, ".cptr relocation target is not PROGRAM: %s", name ); + return( -1 ); + } + } + if( obj_add_reloc( o, srcsec, type, si, fx->p[i].pos, add, err, es ) ) return( -1 ); + continue; + } + switch( type ) + { + case CPU_RELOC_CODE64: case CPU_RELOC_DATA64: __cpu_code_put_u64( src->p + fx->p[i].pos, abs ); + break; + case CPU_RELOC_PTR64_CODE: + if( __cpu_ptr_make( &ptr, CPU_PTR_SEG_DATA, abs ) ) + { + snprintf( err, es, "absolute LEA pointer cannot be encoded: %s", fx->p[i].expr ); + return( -1 ); + } + __cpu_code_put_u64( src->p + fx->p[i].pos, ptr ); + break; + case CPU_RELOC_PTR64_DATA: + if( __cpu_ptr_make( &ptr, CPU_PTR_SEG_DATA, abs ) ) + { + snprintf( err, es, "absolute .ptr cannot be encoded: %s", fx->p[i].expr ); + return( -1 ); + } + __cpu_data_put_u64( src->p + fx->p[i].pos, ptr ); + break; + case CPU_RELOC_CPTR64_DATA: + if( __cpu_ptr_make( &ptr, CPU_PTR_SEG_PROGRAM, abs ) ) + { + snprintf( err, es, "absolute .cptr cannot be encoded: %s", fx->p[i].expr ); + return( -1 ); + } + __cpu_data_put_u64( src->p + fx->p[i].pos, ptr ); + break; + default: snprintf( err, es, "internal unknown relocation type" ); + return( -1 ); + } + } + return( 0 ); +} + +static unsigned +declared_binding( const symdecls_t *d, const char *name ) +{ + size_t i; + if( !strcmp( name, "_start" ) || !strcmp( name, "_life" ) || !strcmp( name, "_fini" ) ) return( CPU_OBJ_BIND_GLOBAL ); + if( !finddecl( d, name, &i ) ) + { + if( d->p[i].is_weak ) return( CPU_OBJ_BIND_WEAK ); + if( d->p[i].is_global ) return( CPU_OBJ_BIND_GLOBAL ); + } + return( CPU_OBJ_BIND_LOCAL ); +} + +static int build_obj_symbols_melf( cpu_asm_object *o, const symdecls_t *decls, + const labels_t *init, const labels_t *text, const labels_t *fini, + const labels_t *data, const labels_t *bss, const labels_t *control, const labels_t *input, + const labels_t *output, const labels_t *trace, char *err, size_t es) +{ + const labels_t *ls[9] = + { + init, text, fini, data, bss, control, input, output, trace + }; + const unsigned sec[9] = + { + CPU_OBJ_SEC_INIT, CPU_OBJ_SEC_TEXT, CPU_OBJ_SEC_FINI, CPU_OBJ_SEC_DATA, CPU_OBJ_SEC_BSS, CPU_OBJ_SEC_CONTROL, CPU_OBJ_SEC_INPUT, CPU_OBJ_SEC_OUTPUT, CPU_OBJ_SEC_TRACE + }; + size_t j, i; + for( j = 0; j < 9; j++ ) for( i = 0; i < ls[j]->n; i++ ) + { + unsigned type = cpu_obj_sec_is_program( sec[j] ) ? CPU_OBJ_TYPE_FUNC : CPU_OBJ_TYPE_OBJECT; + if( obj_add_symbol( o, ls[j]->p[i].name, sec[j], declared_binding( decls, ls[j]->p[i].name ), type, ls[j]->p[i].addr, err, es ) ) return( -1 ); + } + for( i = 0; i < decls->n; i++ ) if( !obj_symbol_defined( o, decls->p[i].name, 0 ) ) + { + const char *name = decls->p[i].name; + unsigned b = decls->p[i].is_weak ? CPU_OBJ_BIND_WEAK : CPU_OBJ_BIND_GLOBAL; + unsigned type = ( !strcmp( name, "_start" ) || !strcmp( name, "_life" ) || !strcmp( name, "_fini" ) ) + ?CPU_OBJ_TYPE_FUNC:CPU_OBJ_TYPE_NOTYPE; + if( obj_add_symbol( o, name, CPU_OBJ_SEC_UNDEF, b, type, 0, err, es ) ) return( -1 ); + } + return( 0 ); +} + +int +cpu_assemble_object( const char *src, cpu_asm_object *o, char *err, size_t es ) +{ + char *copy = 0, *scan = 0, *save = 0, *line; + buf_t init = + { + 0 + } + , text = + { + 0 + } + , fini = + { + 0 + } + , data = + { + 0 + } + , control = + { + 0 + } + , input = + { + 0 + } + , output = + { + 0 + } + , trace = + { + 0 + }; + size_t bss_size = 0; + labels_t ilabs = + { + 0 + } + , tlabs = + { + 0 + } + , flabs = + { + 0 + } + , dlabs = + { + 0 + } + , blabs = + { + 0 + } + , ctlabs = + { + 0 + } + , inlabs = + { + 0 + } + , olabs = + { + 0 + } + , trlabs = + { + 0 + }; + fixups_t idfix = + { + 0 + } + , icfix = + { + 0 + } + , ipfix = + { + 0 + } + , tdfix = + { + 0 + } + , tcfix = + { + 0 + } + , tpfix = + { + 0 + } + , fdfix = + { + 0 + } + , fcfix = + { + 0 + } + , fpfix = + { + 0 + }; + fixups_t dcptr = + { + 0 + } + , ccptr = + { + 0 + } + , incptr = + { + 0 + } + , ocptr = + { + 0 + } + , trcptr = + { + 0 + }; + fixups_t dptr = + { + 0 + } + , ctptr = + { + 0 + } + , inptr = + { + 0 + } + , optr = + { + 0 + } + , trptr = + { + 0 + }; + __mpu_uint64_t dalign = 1, balign = 1, ctalign = 1, inalign = 1, oalign = 1, tralign = 1; + equs_t eqs = + { + 0 + }; + symdecls_t decls = + { + 0 + }; + int section = ASM_SEC_NONE, line_no = 0, rc = -1; + int init_seen = 0, text_seen = 0, fini_seen = 0, bss_seen = 0, pseudo_seen = 0; + + if( !src || !o ) return( -1 ); + memset( o, 0, sizeof( *o ) ); + if( !mpu_utf8valid( (const __mpu_char8_t *)src ) ) + { + snprintf( err, es, "assembler source is not valid UTF-8" ); + return( -1 ); + } + + scan = strdup( src ); + if( !scan ) return( -1 ); + for( line = strtok_r( scan, "\n", &save ); line; line = strtok_r( NULL, "\n", &save ) ) + { + int er; + line_no++; + strip_comment( line ); + if( !*trim( line ) ) continue; + er = collect_equ_line( line, &eqs, line_no, err, es ); + if( er < 0 ) goto out; + } + free( scan ); + scan = 0; + save = 0; + { + size_t i; + __int128 v; + for( i = 0; i < eqs.n; i++ ) if( equ_value( &eqs, i, &v, err, es ) ) + { + char t[256]; + snprintf( t, sizeof( t ), "line %d: %s", eqs.p[i].line, err ); + snprintf( err, es, "%s", t ); + goto out; + } + } + + scan = strdup( src ); + if( !scan ) goto out; + line_no = 0; + for( line = strtok_r( scan, "\n", &save ); line; line = strtok_r( NULL, "\n", &save ) ) + { + int er; + line_no++; + strip_comment( line ); + if( !*trim( line ) ) continue; + er = collect_decl_line( line, &decls, line_no, err, es ); + if( er < 0 ) goto out; + } + free( scan ); + scan = 0; + save = 0; + { + size_t i, j; + for( i = 0; i < eqs.n; i++ ) for( j = 0; j < decls.n; j++ ) if( !strcmp( eqs.p[i].name, decls.p[j].name ) ) + { + snprintf( err, es, "symbol declared global/extern/weak conflicts with .equ: %s", eqs.p[i].name ); + goto out; + } + } + + copy = strdup( src ); + if( !copy ) goto out; + line_no = 0; + for( line = strtok_r( copy, "\n", &save ); line; line = strtok_r( NULL, "\n", &save ) ) + { + char *p, *colon, *mn, *rest, *q; + buf_t *cb = 0, *db = 0; + labels_t *labs = 0; + fixups_t *df = 0, *cf = 0, *pf = 0, *cpfx = 0, *gptr = 0; + __mpu_uint64_t *salign = 0; + int in_bss = 0; + line_no++; + strip_comment( line ); + p = trim( line ); + if( !*p ) continue; + q = p; + while( *q && !isspace( (unsigned char)*q ) ) q++; + if( ( (size_t)( q - p ) == 4 && !strncasecmp( p, ".equ", 4 ) ) || ( (size_t)( q - p ) == 7 && !strncasecmp( p, ".global", 7 ) ) || ( (size_t)( q - p ) == 6 && !strncasecmp( p, ".globl", 6 ) ) || ( (size_t)( q - p ) == 7 && !strncasecmp( p, ".extern", 7 ) ) || ( (size_t)( q - p ) == 5 && !strncasecmp( p, ".weak", 5 ) ) ) continue; + + if( streqi( p, ".init" ) ) + { + init_seen = 1; + section = ASM_SEC_INIT; + continue; + } + if( streqi( p, ".text" ) ) + { + text_seen = 1; + section = ASM_SEC_TEXT; + continue; + } + if( streqi( p, ".fini" ) ) + { + fini_seen = 1; + section = ASM_SEC_FINI; + continue; + } + if( streqi( p, ".data" ) ) + { + section = ASM_SEC_DATA; + continue; + } + if( streqi( p, ".bss" ) ) + { + bss_seen = 1; + section = ASM_SEC_BSS; + continue; + } + if( streqi( p, ".control" ) ) + { + section = ASM_SEC_CONTROL; + continue; + } + if( streqi( p, ".input" ) ) + { + section = ASM_SEC_INPUT; + continue; + } + if( streqi( p, ".output" ) ) + { + section = ASM_SEC_OUTPUT; + continue; + } + if( streqi( p, ".trace" ) ) + { + section = ASM_SEC_TRACE; + continue; + } + + switch( section ) + { + case ASM_SEC_INIT: cb = &init; + labs = &ilabs; + df = &idfix; + cf = &icfix; + pf = &ipfix; + break; + case ASM_SEC_TEXT: cb = &text; + labs = &tlabs; + df = &tdfix; + cf = &tcfix; + pf = &tpfix; + break; + case ASM_SEC_FINI: cb = &fini; + labs = &flabs; + df = &fdfix; + cf = &fcfix; + pf = &fpfix; + break; + case ASM_SEC_DATA: db = &data; + labs = &dlabs; + cpfx = &dcptr; + gptr = &dptr; + salign = &dalign; + break; + case ASM_SEC_BSS: labs = &blabs; + salign = &balign; + in_bss = 1; + break; + case ASM_SEC_CONTROL: db = &control; + labs = &ctlabs; + cpfx = &ccptr; + gptr = &ctptr; + salign = &ctalign; + break; + case ASM_SEC_INPUT: db = &input; + labs = &inlabs; + cpfx = &incptr; + gptr = &inptr; + salign = &inalign; + break; + case ASM_SEC_OUTPUT: db = &output; + labs = &olabs; + cpfx = &ocptr; + gptr = &optr; + salign = &oalign; + break; + case ASM_SEC_TRACE: db = &trace; + labs = &trlabs; + cpfx = &trcptr; + gptr = &trptr; + salign = &tralign; + break; + default:break; + } + + colon = label_colon( p ); + if( colon ) + { + size_t ei; + char *name; + __mpu_uint64_t here; + *colon = 0; + name = trim( p ); + if( !valid_symbol_name( name ) ) + { + snprintf( err, es, "line %d: bad label name", line_no ); + goto out; + } + if( !findequ( &eqs, name, &ei ) ) + { + snprintf( err, es, "line %d: label conflicts with .equ symbol: %s", line_no, name ); + goto out; + } + if( !strcmp( name, "_start_life" ) ) + { + if( section != ASM_SEC_TEXT ) + { + snprintf( err, es, "line %d: _start_life is valid only in .text", line_no ); + goto out; + } + if( pseudo_seen || obj_name_defined_any( "_start", &ilabs, &tlabs, &flabs, &dlabs, &blabs, &ctlabs, &inlabs, &olabs, &trlabs ) || obj_name_defined_any( "_life", &ilabs, &tlabs, &flabs, &dlabs, &blabs, &ctlabs, &inlabs, &olabs, &trlabs ) ) + { + snprintf( err, es, "line %d: _start_life conflicts with an existing _start/_life definition", line_no ); + goto out; + } + here = (__mpu_uint64_t)text.n; + if( addlabel( &tlabs, "_start", here ) || addlabel( &tlabs, "_life", here ) ) + { + snprintf( err, es, "line %d: cannot define _start_life", line_no ); + goto out; + } + pseudo_seen = 1; + } + else + { + if( obj_name_defined_any( name, &ilabs, &tlabs, &flabs, &dlabs, &blabs, &ctlabs, &inlabs, &olabs, &trlabs ) ) + { + snprintf( err, es, "line %d: duplicate label: %s", line_no, name ); + goto out; + } + if( !labs ) + { + snprintf( err, es, "line %d: label outside a valid section", line_no ); + goto out; + } + here = cb ? (__mpu_uint64_t)cb->n : in_bss ? (__mpu_uint64_t)bss_size : (__mpu_uint64_t)db->n; + if( addlabel( labs, name, here ) ) + { + snprintf( err, es, "line %d: bad/duplicate label", line_no ); + goto out; + } + if( !strcmp( name, "_start" ) && section != ASM_SEC_INIT && section != ASM_SEC_TEXT ) + { + snprintf( err, es, "line %d: _start must be in .init or .text", line_no ); + goto out; + } + if( !strcmp( name, "_life" ) && section != ASM_SEC_TEXT ) + { + snprintf( err, es, "line %d: _life must be in .text", line_no ); + goto out; + } + if( !strcmp( name, "_fini" ) && section != ASM_SEC_FINI ) + { + snprintf( err, es, "line %d: _fini must be in .fini", line_no ); + goto out; + } + } + p = trim( colon + 1 ); + if( !*p ) continue; + } + + if( in_bss ) + { + if( bssdir( &bss_size, p, salign, &eqs, err, es ) ) + { + char t[256]; + snprintf( t, sizeof( t ), "line %d: %s", line_no, err ); + snprintf( err, es, "%s", t ); + goto out; + } continue; + } + if( db ) + { + if( datadir( db, p, cpfx, gptr, salign, &eqs, err, es ) ) + { + char t[256]; + snprintf( t, sizeof( t ), "line %d: %s", line_no, err ); + snprintf( err, es, "%s", t ); + goto out; + } continue; + } + if( !cb ) + { + snprintf( err, es, "line %d: select a code or data section", line_no ); + goto out; + } + mn = p; + while( *p && !isspace( (unsigned char)*p ) ) p++; + if( *p ) + { + *p++ = 0; + rest = trim( p ); + } + else rest = (char *)""; + if( streqi( mn, "LDI" ) ) + { + if( !*rest || emit_ldi( cb, &eqs, rest, err, es ) ) goto badline_obj; + } + else if( emit_instruction( cb, df, cf, pf, &eqs, mn, rest, err, es ) ) goto badline_obj; + continue; +badline_obj: +{ + char t[256]; + snprintf( t, sizeof( t ), "line %d: %s", line_no, err ); + snprintf( err, es, "%s", t ); + goto out; +} + } + + if( init_seen && obj_name_defined_any( "_start", 0, &tlabs, 0, 0, 0, 0, 0, 0, 0 ) ) + { + snprintf( err, es, "_start must be in .init when .init exists" ); + goto out; + } + if( init_seen && pseudo_seen ) + { + snprintf( err, es, "_start_life is invalid when .init exists" ); + goto out; + } + + if( build_obj_symbols_melf( o, &decls, &ilabs, &tlabs, &flabs, &dlabs, &blabs, &ctlabs, &inlabs, &olabs, &trlabs, err, es ) ) goto out; +#define PF(SRC,SEC,FX,TYPE) process_obj_fixups_melf(o,&(SRC),(SEC),&(FX),(TYPE),&eqs,&decls,&ilabs,&tlabs,&flabs,&dlabs,&blabs,&ctlabs,&inlabs,&olabs,&trlabs,err,es) + if( PF( init, CPU_OBJ_SEC_INIT, idfix, CPU_RELOC_DATA64 ) || PF( init, CPU_OBJ_SEC_INIT, icfix, CPU_RELOC_CODE64 ) || PF( init, CPU_OBJ_SEC_INIT, ipfix, CPU_RELOC_PTR64_CODE ) || + PF( text, CPU_OBJ_SEC_TEXT, tdfix, CPU_RELOC_DATA64 ) || PF( text, CPU_OBJ_SEC_TEXT, tcfix, CPU_RELOC_CODE64 ) || PF( text, CPU_OBJ_SEC_TEXT, tpfix, CPU_RELOC_PTR64_CODE ) || + PF( fini, CPU_OBJ_SEC_FINI, fdfix, CPU_RELOC_DATA64 ) || PF( fini, CPU_OBJ_SEC_FINI, fcfix, CPU_RELOC_CODE64 ) || PF( fini, CPU_OBJ_SEC_FINI, fpfix, CPU_RELOC_PTR64_CODE ) || + PF( data, CPU_OBJ_SEC_DATA, dcptr, CPU_RELOC_CPTR64_DATA ) || PF( control, CPU_OBJ_SEC_CONTROL, ccptr, CPU_RELOC_CPTR64_DATA ) || PF( input, CPU_OBJ_SEC_INPUT, incptr, CPU_RELOC_CPTR64_DATA ) || PF( output, CPU_OBJ_SEC_OUTPUT, ocptr, CPU_RELOC_CPTR64_DATA ) || PF( trace, CPU_OBJ_SEC_TRACE, trcptr, CPU_RELOC_CPTR64_DATA ) || + PF( data, CPU_OBJ_SEC_DATA, dptr, CPU_RELOC_PTR64_DATA ) || PF( control, CPU_OBJ_SEC_CONTROL, ctptr, CPU_RELOC_PTR64_DATA ) || PF( input, CPU_OBJ_SEC_INPUT, inptr, CPU_RELOC_PTR64_DATA ) || PF( output, CPU_OBJ_SEC_OUTPUT, optr, CPU_RELOC_PTR64_DATA ) || PF( trace, CPU_OBJ_SEC_TRACE, trptr, CPU_RELOC_PTR64_DATA )) goto out; +#undef PF + + o->init = init.p; + o->init_size = init.n; + o->init_align = 1; + o->has_init = init_seen; + init.p = 0; + o->text = text.p; + o->text_size = text.n; + o->text_align = 1; + o->has_text = text_seen; + text.p = 0; + o->fini = fini.p; + o->fini_size = fini.n; + o->fini_align = 1; + o->has_fini = fini_seen; + fini.p = 0; + o->data = data.p; + o->data_size = data.n; + o->data_align = dalign; + o->has_data = ( data.n != 0 ); + data.p = 0; + o->bss_size = bss_size; + o->bss_align = balign; + o->has_bss = bss_seen; + o->control = control.p; + o->control_size = control.n; + o->control_align = ctalign; + o->has_control = ( control.n != 0 ); + control.p = 0; + o->input = input.p; + o->input_size = input.n; + o->input_align = inalign; + o->has_input = ( input.n != 0 ); + input.p = 0; + o->output = output.p; + o->output_size = output.n; + o->output_align = oalign; + o->has_output = ( output.n != 0 ); + output.p = 0; + o->trace = trace.p; + o->trace_size = trace.n; + o->trace_align = tralign; + o->has_trace = ( trace.n != 0 ); + trace.p = 0; + o->code_size = o->init_size + o->text_size + o->fini_size; + o->code_align = 1; + if( o->code_size ) + { + o->code = (__mpu_UNIT_t *)malloc( o->code_size ); + if( !o->code ) + { + snprintf( err, es, "out of memory" ); + goto out; + } + memcpy( o->code, o->init, o->init_size ); + memcpy( o->code + o->init_size, o->text, o->text_size ); + memcpy( o->code + o->init_size + o->text_size, o->fini, o->fini_size ); + } + rc = 0; +out: + free( scan ); + free( copy ); + free( init.p ); + free( text.p ); + free( fini.p ); + free( data.p ); + free( control.p ); + free( input.p ); + free( output.p ); + free( trace.p ); + free( ilabs.p ); + free( tlabs.p ); + free( flabs.p ); + free( dlabs.p ); + free( blabs.p ); + free( ctlabs.p ); + free( inlabs.p ); + free( olabs.p ); + free( trlabs.p ); + free( idfix.p ); + free( icfix.p ); + free( ipfix.p ); + free( tdfix.p ); + free( tcfix.p ); + free( tpfix.p ); + free( fdfix.p ); + free( fcfix.p ); + free( fpfix.p ); + free( dcptr.p ); + free( ccptr.p ); + free( incptr.p ); + free( ocptr.p ); + free( trcptr.p ); + free( dptr.p ); + free( ctptr.p ); + free( inptr.p ); + free( optr.p ); + free( trptr.p ); + free( eqs.p ); + free( decls.p ); + if( rc ) cpu_asm_object_free( o ); + return( rc ); +} + +void +cpu_asm_object_free( cpu_asm_object *o ) +{ + size_t i; + if( !o ) return; + free( o->init ); + free( o->text ); + free( o->fini ); + free( o->data ); + free( o->control ); + free( o->input ); + free( o->output ); + free( o->trace ); + free( o->code ); + for( i = 0; i < o->symbol_count; i++ ) free( o->symbols[i].name ); + free( o->symbols ); + free( o->relocs ); + memset( o, 0, sizeof( *o ) ); +} diff --git a/utils/mcpu-asm.h b/utils/mcpu-asm.h new file mode 100644 index 0000000..3ea7f42 --- /dev/null +++ b/utils/mcpu-asm.h @@ -0,0 +1,205 @@ +#ifndef __CPU_ASM_H +#define __CPU_ASM_H + +#include <stddef.h> +#include <libmcpu.h> + + +/* Runtime dynamic-link metadata carried by ABI-v2/ABI-v3 ET_EXEC/ET_DYN images. */ +typedef struct cpu_dyn_symbol +{ + char *name; + unsigned int section; /* logical output section, or CPU_OBJ_SEC_UNDEF */ + unsigned int binding; /* GLOBAL/WEAK */ + unsigned int type; /* NOTYPE/OBJECT/FUNC */ + __mpu_uint64_t value; /* output-section-relative byte offset */ + __mpu_uint64_t size; +} cpu_dyn_symbol; + +typedef struct cpu_dyn_reloc +{ + unsigned int section; /* logical output section containing field */ + unsigned int type; + size_t symbol_index; /* index in cpu_asm_image.dyn_symbols */ + __mpu_uint64_t offset; /* output-section-relative field offset */ + __mpu_int64_t addend; +} cpu_dyn_reloc; + +/* Final linked/runtime image used internally by mcpu-ld. */ +typedef struct cpu_asm_image +{ + __mpu_UNIT_t *code; + size_t code_size; + __mpu_UNIT_t *data; + size_t data_size; + size_t data_mem_size; + __mpu_UNIT_t *control; + size_t control_size; + __mpu_UNIT_t *input; + size_t input_size; + __mpu_UNIT_t *output; + size_t output_size; + __mpu_UNIT_t *trace; + size_t trace_size; + + int has_start; + int has_init; + int has_life; + int has_fini; + __mpu_uint64_t start_entry; + __mpu_uint64_t init_entry; + __mpu_uint64_t life_entry; + __mpu_uint64_t fini_entry; + + /* Final PROGRAM subsection placement. */ + int init_present; + __mpu_uint64_t init_offset, init_size, init_align; + int text_present; + __mpu_uint64_t text_offset, text_size, text_align; + int fini_present; + __mpu_uint64_t fini_offset, fini_size, fini_align; + + /* Final data-like section placement in its architectural segment. */ + int data_present; + __mpu_uint64_t data_offset, data_section_size, data_align; + int bss_present; + __mpu_uint64_t bss_offset, bss_size, bss_align; + int control_present; + __mpu_uint64_t control_offset, control_section_size, control_align; + int input_present; + __mpu_uint64_t input_offset, input_section_size, input_align; + int output_present; + __mpu_uint64_t output_offset, output_section_size, output_align; + int trace_present; + __mpu_uint64_t trace_offset, trace_section_size, trace_align; + + /* ABI-v2/ABI-v3 startup dynamic-link metadata. */ + cpu_dyn_symbol *dyn_symbols; + size_t dyn_symbol_count; + cpu_dyn_reloc *dyn_relocs; + size_t dyn_reloc_count; +} cpu_asm_image; + +/* Logical input-section identifiers used between assembler, MELF reader and + static linker. Data-like values intentionally match PTR64 selectors. + PROGRAM subsections use private non-selector IDs so .init/.text/.fini stay + distinct until the final PROGRAM layout is assigned by mcpu-ld. */ +#define CPU_OBJ_SEC_DATA CPU_PTR_SEG_DATA +#define CPU_OBJ_SEC_CONTROL CPU_PTR_SEG_CONTROL +#define CPU_OBJ_SEC_INPUT CPU_PTR_SEG_INPUT +#define CPU_OBJ_SEC_OUTPUT CPU_PTR_SEG_OUTPUT +#define CPU_OBJ_SEC_TRACE CPU_PTR_SEG_TRACE +#define CPU_OBJ_SEC_INIT 0x20U +#define CPU_OBJ_SEC_TEXT 0x21U +#define CPU_OBJ_SEC_FINI 0x22U +#define CPU_OBJ_SEC_BSS 0x23U +#define CPU_OBJ_SEC_UNDEF 0xffU + +static inline int +cpu_obj_sec_is_program( unsigned s ) +{ + return( s == CPU_OBJ_SEC_INIT || s == CPU_OBJ_SEC_TEXT || s == CPU_OBJ_SEC_FINI ); +} + +static inline unsigned +cpu_obj_sec_segment( unsigned s ) +{ + if( cpu_obj_sec_is_program( s ) ) return( CPU_PTR_SEG_PROGRAM ); + if( s == CPU_OBJ_SEC_BSS ) return( CPU_PTR_SEG_DATA ); + return( s ); +} + +#define CPU_OBJ_BIND_LOCAL 0U +#define CPU_OBJ_BIND_GLOBAL 1U +#define CPU_OBJ_BIND_WEAK 2U + +#define CPU_OBJ_TYPE_NOTYPE 0U +#define CPU_OBJ_TYPE_OBJECT 1U +#define CPU_OBJ_TYPE_FUNC 2U + +typedef enum cpu_reloc_type +{ + CPU_RELOC_CODE64 = 1, + CPU_RELOC_DATA64 = 2, + CPU_RELOC_PTR64_CODE = 3, + CPU_RELOC_PTR64_DATA = 4, + CPU_RELOC_CPTR64_DATA = 5 +} cpu_reloc_type; + +typedef struct cpu_asm_symbol +{ + char *name; + unsigned int section; + unsigned int binding; + unsigned int type; + __mpu_uint64_t value; /* section-relative byte offset */ + __mpu_uint64_t size; /* zero when unknown */ +} cpu_asm_symbol; + +typedef struct cpu_asm_reloc +{ + unsigned int section; /* logical source section */ + unsigned int type; + size_t symbol_index; + __mpu_uint64_t offset; /* source-section-relative field offset */ + __mpu_int64_t addend; +} cpu_asm_reloc; + +typedef struct cpu_asm_object +{ + __mpu_UNIT_t *init; + size_t init_size; + __mpu_uint64_t init_align; + int has_init; + __mpu_UNIT_t *text; + size_t text_size; + __mpu_uint64_t text_align; + int has_text; + __mpu_UNIT_t *fini; + size_t fini_size; + __mpu_uint64_t fini_align; + int has_fini; + + __mpu_UNIT_t *data; + size_t data_size; + __mpu_uint64_t data_align; + int has_data; + size_t bss_size; + __mpu_uint64_t bss_align; + int has_bss; + __mpu_UNIT_t *control; + size_t control_size; + __mpu_uint64_t control_align; + int has_control; + __mpu_UNIT_t *input; + size_t input_size; + __mpu_uint64_t input_align; + int has_input; + __mpu_UNIT_t *output; + size_t output_size; + __mpu_uint64_t output_align; + int has_output; + __mpu_UNIT_t *trace; + size_t trace_size; + __mpu_uint64_t trace_align; + int has_trace; + + /* Compatibility concatenation for legacy in-process assembler tests only. + It is never serialized as a MELF section. */ + __mpu_UNIT_t *code; + size_t code_size; + __mpu_uint64_t code_align; + + cpu_asm_symbol *symbols; + size_t symbol_count; + cpu_asm_reloc *relocs; + size_t reloc_count; +} cpu_asm_object; + +int cpu_assemble_text( const char *source, cpu_asm_image *image, char *err, size_t errsz ); +void cpu_asm_image_free( cpu_asm_image *image ); + +int cpu_assemble_object( const char *source, cpu_asm_object *object, char *err, size_t errsz ); +void cpu_asm_object_free( cpu_asm_object *object ); + +#endif diff --git a/utils/mcpu-config.c b/utils/mcpu-config.c new file mode 100644 index 0000000..2d44d27 --- /dev/null +++ b/utils/mcpu-config.c @@ -0,0 +1,704 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <libmpuio.h> + +#include "mcpu-config.h" + +typedef struct mcpu_text mcpu_text; +struct mcpu_text +{ + __mpu_char16_t *data; + size_t length; + size_t capacity; +}; + +static void +mcpu_text_init( mcpu_text *text ) +{ + if( text == NULL ) + return; + + text->data = NULL; + text->length = 0; + text->capacity = 0; +} + +static void +mcpu_text_free( mcpu_text *text ) +{ + if( text == NULL ) + return; + + free( text->data ); + text->data = NULL; + text->length = 0; + text->capacity = 0; +} + +static int +mcpu_text_reserve( mcpu_text *text, size_t need ) +{ + __mpu_char16_t *p; + size_t capacity; + + if( text == NULL ) + return( -1 ); + + if( need <= text->capacity ) + return( 0 ); + + capacity = text->capacity ? text->capacity : 256; + while( capacity < need ) + { + if( capacity > SIZE_MAX / 2 ) + { + errno = EOVERFLOW; + return( -1 ); + } + capacity *= 2; + } + + if( capacity > SIZE_MAX / sizeof(__mpu_char16_t) ) + { + errno = EOVERFLOW; + return( -1 ); + } + + p = (__mpu_char16_t *)realloc( text->data, + capacity * sizeof(__mpu_char16_t) ); + if( p == NULL ) + return( -1 ); + + text->data = p; + text->capacity = capacity; + return( 0 ); +} + +static int +mcpu_text_append( mcpu_text *text, + const __mpu_char16_t *data, size_t length ) +{ + if( text == NULL || (data == NULL && length != 0) ) + { + errno = EINVAL; + return( -1 ); + } + + if( length > SIZE_MAX - text->length - 1 ) + { + errno = EOVERFLOW; + return( -1 ); + } + + if( mcpu_text_reserve(text, text->length + length + 1) != 0 ) + return( -1 ); + + if( length != 0 ) + memcpy( text->data + text->length, data, + length * sizeof(__mpu_char16_t) ); + + text->length += length; + text->data[text->length] = 0; + return( 0 ); +} + +static int +mcpu_text_from_utf8( mcpu_text *text, const char *s ) +{ + __mpu_char16_t *u; + __mpu_size_t n; + int rc; + + if( text == NULL || s == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + mcpu_text_free( text ); + mcpu_text_init( text ); + + n = mpu_utf8_to_ucs2( NULL, (const __mpu_char8_t *)s, 0 ); + if( n == (__mpu_size_t)-1 ) + return( -1 ); + + u = (__mpu_char16_t *)calloc( (size_t)n + 1, sizeof(__mpu_char16_t) ); + if( u == NULL ) + return( -1 ); + + if( mpu_utf8_to_ucs2(u, (const __mpu_char8_t *)s, + (size_t)n + 1) == (__mpu_size_t)-1 ) + { + free( u ); + return( -1 ); + } + + rc = mcpu_text_append( text, u, (size_t)n ); + free( u ); + return( rc ); +} + +static char * +mcpu_text_to_utf8( const __mpu_char16_t *data, size_t length ) +{ + __mpu_char16_t *tmp; + __mpu_char8_t *out; + __mpu_size_t n; + + if( data == NULL && length != 0 ) + { + errno = EINVAL; + return( NULL ); + } + + tmp = (__mpu_char16_t *)calloc( length + 1, sizeof(__mpu_char16_t) ); + if( tmp == NULL ) + return( NULL ); + + if( length != 0 ) + memcpy( tmp, data, length * sizeof(__mpu_char16_t) ); + + n = mpu_ucs2_to_utf8( NULL, tmp, 0 ); + if( n == (__mpu_size_t)-1 ) + { + free( tmp ); + return( NULL ); + } + + out = (__mpu_char8_t *)calloc( (size_t)n + 1, 1 ); + if( out == NULL ) + { + free( tmp ); + return( NULL ); + } + + if( mpu_ucs2_to_utf8(out, tmp, (size_t)n + 1) == (__mpu_size_t)-1 ) + { + free( out ); + free( tmp ); + return( NULL ); + } + + free( tmp ); + return( (char *)out ); +} + + +static char * +read_bytes( const char *filename, size_t *file_length ) +{ + FILE *fp; + char *data; + long size; + size_t n; + + if( file_length == NULL ) + { + errno = EINVAL; + return( NULL ); + } + + *file_length = 0; + + fp = fopen( filename, "rb" ); + if( fp == NULL ) + return( NULL ); + + if( fseek( fp, 0, SEEK_END ) != 0 ) + { + fclose( fp ); + return( NULL ); + } + + size = ftell( fp ); + if( size < 0 || fseek( fp, 0, SEEK_SET ) != 0 ) + { + fclose( fp ); + return( NULL ); + } + + data = (char *)calloc( (size_t)size + 1, 1 ); + if( data == NULL ) + { + fclose( fp ); + return( NULL ); + } + + n = fread( data, 1, (size_t)size, fp ); + if( n != (size_t)size && ferror(fp) ) + { + free( data ); + fclose( fp ); + return( NULL ); + } + + data[n] = 0; + fclose( fp ); + *file_length = n; + + return( data ); +} + +static int +is_space16( __mpu_char16_t c ) +{ + return( c == ' ' || c == '\t' || c == '\r' || c == '\n' || + c == '\f' || c == '\v' ); +} + +static int +is_name16( __mpu_char16_t c ) +{ + return( (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || c == '_' ); +} + +static void +skip_space_and_comments( const __mpu_char16_t *s, size_t n, size_t *pos ) +{ + size_t p = *pos; + + for( ;; ) + { + while( p < n && is_space16(s[p]) ) + ++p; + + if( p + 1 < n && s[p] == '/' && s[p + 1] == '*' ) + { + p += 2; + while( p + 1 < n && !(s[p] == '*' && s[p + 1] == '/') ) + ++p; + if( p + 1 < n ) + p += 2; + continue; + } + + if( p + 1 < n && s[p] == '/' && s[p + 1] == '/' ) + { + p += 2; + while( p < n && s[p] != '\n' ) + ++p; + continue; + } + + if( p < n && s[p] == '#' ) + { + while( p < n && s[p] != '\n' ) + ++p; + continue; + } + + break; + } + + *pos = p; +} + +static char * +expand_value( const mcpu_config *config, const char *value ) +{ + size_t length = strlen( value ); + size_t capacity = length + 64; + char *out = (char *)calloc( capacity, 1 ); + size_t i = 0; + size_t o = 0; + + if( out == NULL ) + return( NULL ); + + while( i < length ) + { + if( value[i] == '$' ) + { + char name[256]; + size_t ni = 0; + size_t j = i + 1; + int braced = 0; + const char *replacement; + + if( j < length && value[j] == '{' ) + { + braced = 1; + ++j; + } + + while( j < length && ni + 1 < sizeof(name) && + ((value[j] >= 'a' && value[j] <= 'z') || + (value[j] >= 'A' && value[j] <= 'Z') || + (value[j] >= '0' && value[j] <= '9') || value[j] == '_') ) + name[ni++] = value[j++]; + + if( braced ) + { + if( j >= length || value[j] != '}' ) + { + free( out ); + errno = EINVAL; + return( NULL ); + } + ++j; + } + + if( ni == 0 ) + { + out[o++] = value[i++]; + continue; + } + + name[ni] = 0; + replacement = mcpu_config_get( config, name ); + if( replacement == NULL ) + replacement = getenv( name ); + if( replacement == NULL ) + replacement = ""; + + { + size_t rl = strlen( replacement ); + if( o + rl + 1 > capacity ) + { + char *p; + while( o + rl + 1 > capacity ) + capacity *= 2; + p = (char *)realloc( out, capacity ); + if( p == NULL ) + { + free( out ); + return( NULL ); + } + out = p; + } + memcpy( out + o, replacement, rl ); + o += rl; + } + + i = j; + continue; + } + + if( o + 2 > capacity ) + { + char *p; + capacity *= 2; + p = (char *)realloc( out, capacity ); + if( p == NULL ) + { + free( out ); + return( NULL ); + } + out = p; + } + + out[o++] = value[i++]; + } + + out[o] = 0; + return( out ); +} + +void +mcpu_config_init( mcpu_config *config ) +{ + if( config == NULL ) + return; + + config->first = NULL; + config->verbose = 0; +} + +void +mcpu_config_free( mcpu_config *config ) +{ + mcpu_config_entry *p; + + if( config == NULL ) + return; + + p = config->first; + while( p ) + { + mcpu_config_entry *next = p->next; + free( p->name ); + free( p->value ); + free( p ); + p = next; + } + + config->first = NULL; +} + +const char * +mcpu_config_get( const mcpu_config *config, const char *name ) +{ + mcpu_config_entry *p; + + if( config == NULL || name == NULL ) + return( NULL ); + + for( p = config->first; p; p = p->next ) + if( strcmp( p->name, name ) == 0 ) + return( p->value ); + + return( NULL ); +} + +int +mcpu_config_set( mcpu_config *config, const char *name, + const char *value ) +{ + mcpu_config_entry *p; + char *new_value; + + if( config == NULL || name == NULL || value == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + new_value = expand_value( config, value ); + if( new_value == NULL ) + return( -1 ); + + for( p = config->first; p; p = p->next ) + { + if( strcmp( p->name, name ) == 0 ) + { + free( p->value ); + p->value = new_value; + return( 0 ); + } + } + + p = (mcpu_config_entry *)calloc( 1, sizeof(*p) ); + if( p == NULL ) + { + free( new_value ); + return( -1 ); + } + + p->name = strdup( name ); + if( p->name == NULL ) + { + free( new_value ); + free( p ); + return( -1 ); + } + + p->value = new_value; + p->next = config->first; + config->first = p; + + return( 0 ); +} + + +static int +config_entry_compare( const void *a, const void *b ) +{ + const mcpu_config_entry *ea = *(const mcpu_config_entry * const *)a; + const mcpu_config_entry *eb = *(const mcpu_config_entry * const *)b; + return( strcmp(ea->name, eb->name) ); +} + + +int +mcpu_config_dump( const mcpu_config *config, FILE *stream ) +{ + const mcpu_config_entry *p; + mcpu_config_entry **list; + size_t count = 0; + size_t i = 0; + + if( config == NULL || stream == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + for( p = config->first; p; p = p->next ) + ++count; + + list = count ? (mcpu_config_entry **)calloc( count, sizeof(*list) ) : NULL; + if( count != 0 && list == NULL ) + return( -1 ); + + for( p = config->first; p; p = p->next ) + list[i++] = (mcpu_config_entry *)p; + + if( count > 1 ) + qsort( list, count, sizeof(*list), config_entry_compare ); + + for( i = 0; i < count; ++i ) + { + if( fprintf(stream, "%s = %s;\n", list[i]->name, list[i]->value) < 0 ) + { + free( list ); + return( -1 ); + } + } + + free( list ); + return( ferror(stream) ? -1 : 0 ); +} + + +int +mcpu_config_read( mcpu_config *config, const char *filename, + int missing_is_error ) +{ + char *bytes; + size_t byte_length; + mcpu_text text; + size_t p = 0; + int rc = -1; + + if( config == NULL || filename == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + bytes = read_bytes( filename, &byte_length ); + if( bytes == NULL ) + { + if( !missing_is_error && errno == ENOENT ) + return( 0 ); + return( -1 ); + } + + mcpu_text_init( &text ); + { + const char *input = bytes; + + if( memchr( bytes, 0, byte_length ) != NULL ) + { + fprintf( stderr, "%s: NUL character is not allowed in configuration text\n", + filename ); + free( bytes ); + return( -1 ); + } + + if( byte_length >= 3 && + (unsigned char)bytes[0] == 0xef && + (unsigned char)bytes[1] == 0xbb && + (unsigned char)bytes[2] == 0xbf ) + input += 3; + + if( mcpu_text_from_utf8( &text, input ) != 0 ) + { + fprintf( stderr, "%s: invalid UTF-8 or non-UCS-2 character\n", + filename ); + free( bytes ); + return( -1 ); + } + } + free( bytes ); + + while( p < text.length ) + { + size_t name_start; + size_t name_end; + size_t value_start; + size_t value_end; + char *name = NULL; + char *value = NULL; + + skip_space_and_comments( text.data, text.length, &p ); + if( p >= text.length ) + break; + + name_start = p; + while( p < text.length && is_name16(text.data[p]) ) + ++p; + name_end = p; + + if( name_end == name_start ) + { + fprintf( stderr, "%s: malformed configuration variable\n", filename ); + goto done; + } + + skip_space_and_comments( text.data, text.length, &p ); + if( p >= text.length || text.data[p] != '=' ) + { + fprintf( stderr, "%s: '=' expected after configuration variable\n", filename ); + goto done; + } + ++p; + + while( p < text.length && is_space16(text.data[p]) ) + ++p; + + value_start = p; + if( p < text.length && (text.data[p] == '"' || text.data[p] == '\'') ) + { + __mpu_char16_t quote = text.data[p++]; + value_start = p; + while( p < text.length && text.data[p] != quote ) + { + if( text.data[p] == '\\' && p + 1 < text.length ) + p += 2; + else + ++p; + } + if( p >= text.length ) + { + fprintf( stderr, "%s: unterminated quoted configuration value\n", filename ); + goto done; + } + value_end = p++; + while( p < text.length && is_space16(text.data[p]) ) + ++p; + } + else + { + while( p < text.length && text.data[p] != ';' && text.data[p] != '\n' ) + ++p; + value_end = p; + while( value_end > value_start && is_space16(text.data[value_end - 1]) ) + --value_end; + } + + if( p >= text.length || text.data[p] != ';' ) + { + fprintf( stderr, "%s: ';' expected after configuration value\n", filename ); + goto done; + } + ++p; + + name = mcpu_text_to_utf8( text.data + name_start, name_end - name_start ); + value = mcpu_text_to_utf8( text.data + value_start, value_end - value_start ); + if( name == NULL || value == NULL ) + { + free( name ); + free( value ); + goto done; + } + + if( mcpu_config_set( config, name, value ) != 0 ) + { + free( name ); + free( value ); + goto done; + } + + free( name ); + free( value ); + name = NULL; + value = NULL; + } + + rc = 0; + +done: + mcpu_text_free( &text ); + return( rc ); +} diff --git a/utils/mcpu-config.h b/utils/mcpu-config.h new file mode 100644 index 0000000..5d9ab0e --- /dev/null +++ b/utils/mcpu-config.h @@ -0,0 +1,30 @@ +#ifndef __MCPU_CONFIG_H__ +#define __MCPU_CONFIG_H__ 1 + +#include <stdio.h> + +typedef struct mcpu_config_entry mcpu_config_entry; +struct mcpu_config_entry +{ + char *name; + char *value; + mcpu_config_entry *next; +}; + +typedef struct mcpu_config mcpu_config; +struct mcpu_config +{ + mcpu_config_entry *first; + int verbose; +}; + +void mcpu_config_init( mcpu_config *config ); +void mcpu_config_free( mcpu_config *config ); +int mcpu_config_read( mcpu_config *config, const char *filename, + int missing_is_error ); +const char *mcpu_config_get( const mcpu_config *config, const char *name ); +int mcpu_config_set( mcpu_config *config, const char *name, + const char *value ); +int mcpu_config_dump( const mcpu_config *config, FILE *stream ); + +#endif /* __MCPU_CONFIG_H__ */ diff --git a/utils/mcpu-dependency.c b/utils/mcpu-dependency.c new file mode 100644 index 0000000..a7d12a8 --- /dev/null +++ b/utils/mcpu-dependency.c @@ -0,0 +1,487 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <mcpu-elf.h> + +#include "mcpu-dependency.h" +#include "mcpu-dyn.h" +#include "mcpu-exec.h" + + +typedef struct dependency_node dependency_node; +struct dependency_node +{ + char *path; + cpu_dyn_dependency_info info; +}; + + +typedef struct dependency_graph dependency_graph; +struct dependency_graph +{ + const mcpu_tool_options *options; + char **preload_path; + cpu_dyn_dependency_info *preload_info; + size_t preload_count; + dependency_node *node; + size_t node_count; + size_t node_capacity; + char *err; + size_t errsz; +}; + + +static void +free_string_vector( char **v, size_t n ) +{ + while( n != 0 ) + free( v[--n] ); + free( v ); + +} /* End of free_string_vector() */ + + +static int +split_path_list( const char *list, char ***result, size_t *count, + char *err, size_t errsz ) +{ + const char *begin; + char **v = NULL; + size_t n = 0, capacity = 0; + + if( result == NULL || count == NULL ) + { + if( err != NULL && errsz != 0 ) + snprintf( err, errsz, "invalid module-list arguments" ); + return( -1 ); + } + + *result = NULL; + *count = 0; + if( list == NULL || *list == 0 ) + return( 0 ); + + begin = list; + for( ;; ) + { + const char *colon = strchr( begin, ':' ); + size_t length = colon ? (size_t)(colon - begin) : strlen(begin); + char *copy; + + if( length == 0 ) + { + snprintf( err, errsz, "empty path in resolved module list" ); + goto bad; + } + + if( n == capacity ) + { + size_t new_capacity = capacity ? capacity * 2U : 4U; + char **p; + + if( new_capacity < capacity || + new_capacity > SIZE_MAX / sizeof(*p) ) + { + snprintf( err, errsz, "too many resolved modules" ); + goto bad; + } + p = (char **)realloc( v, new_capacity * sizeof(*p) ); + if( p == NULL ) + { + snprintf( err, errsz, "out of memory" ); + goto bad; + } + v = p; + capacity = new_capacity; + } + + copy = (char *)malloc( length + 1U ); + if( copy == NULL ) + { + snprintf( err, errsz, "out of memory" ); + goto bad; + } + memcpy( copy, begin, length ); + copy[length] = 0; + v[n++] = copy; + + if( colon == NULL ) + break; + begin = colon + 1; + } + + *result = v; + *count = n; + return( 0 ); + +bad: + free_string_vector( v, n ); + return( -1 ); + +} /* End of split_path_list() */ + + +static void +dependency_graph_free( dependency_graph *graph ) +{ + size_t i; + + if( graph == NULL ) + return; + + for( i = 0; i < graph->preload_count; ++i ) + { + free( graph->preload_path[i] ); + cpu_dyn_dependency_free( &graph->preload_info[i] ); + } + free( graph->preload_path ); + free( graph->preload_info ); + + for( i = 0; i < graph->node_count; ++i ) + { + free( graph->node[i].path ); + cpu_dyn_dependency_free( &graph->node[i].info ); + } + free( graph->node ); + memset( graph, 0, sizeof(*graph) ); + +} /* End of dependency_graph_free() */ + + +static int +preload_has_soname( const dependency_graph *graph, const char *soname ) +{ + size_t i; + + for( i = graph->preload_count; i != 0; --i ) + { + const cpu_dyn_dependency_info *info = &graph->preload_info[i - 1U]; + + if( info->soname != NULL && strcmp(info->soname, soname) == 0 ) + return( 1 ); + } + return( 0 ); + +} /* End of preload_has_soname() */ + + +static dependency_node * +find_node( dependency_graph *graph, const char *soname ) +{ + size_t i; + + for( i = 0; i < graph->node_count; ++i ) + if( graph->node[i].info.soname != NULL && + strcmp(graph->node[i].info.soname, soname) == 0 ) + return( &graph->node[i] ); + + return( NULL ); + +} /* End of find_node() */ + + +static dependency_node * +add_node( dependency_graph *graph, char *path, + cpu_dyn_dependency_info *info ) +{ + dependency_node *node; + + if( graph->node_count == graph->node_capacity ) + { + size_t capacity = graph->node_capacity ? + graph->node_capacity * 2U : 8U; + dependency_node *p; + + if( capacity < graph->node_capacity || + capacity > SIZE_MAX / sizeof(*p) ) + { + errno = EOVERFLOW; + return( NULL ); + } + p = (dependency_node *)realloc( graph->node, + capacity * sizeof(*p) ); + if( p == NULL ) + return( NULL ); + graph->node = p; + graph->node_capacity = capacity; + } + + node = &graph->node[graph->node_count++]; + memset( node, 0, sizeof(*node) ); + node->path = path; + node->info = *info; + memset( info, 0, sizeof(*info) ); + return( node ); + +} /* End of add_node() */ + + +static int +enqueue_needed( dependency_graph *graph, const char *soname ) +{ + dependency_node *node; + cpu_dyn_dependency_info info; + char *path = NULL; + + if( preload_has_soname(graph, soname) ) + return( 0 ); + + node = find_node( graph, soname ); + if( node != NULL ) + return( 0 ); + + if( mcpu_tool_resolve_library(graph->options, soname, &path) != 0 ) + { + snprintf( graph->err, graph->errsz, + "cannot resolve DT_NEEDED '%s': %s", soname, strerror(errno) ); + return( -1 ); + } + + memset( &info, 0, sizeof(info) ); + if( cpu_dyn_dependency_read(path, 0, &info, + graph->err, graph->errsz) != 0 ) + { + free( path ); + return( -1 ); + } + + if( info.abi != MELF_ABIVERSION_DYNAMIC_NEEDED || info.soname == NULL ) + { + snprintf( graph->err, graph->errsz, + "%s: DT_NEEDED '%s' did not resolve to an ABI-v3 library", + path, soname ); + cpu_dyn_dependency_free( &info ); + free( path ); + return( -1 ); + } + if( strcmp(info.soname, soname) != 0 ) + { + snprintf( graph->err, graph->errsz, + "%s: DT_NEEDED '%s' resolved to SONAME '%s'", + path, soname, info.soname ); + cpu_dyn_dependency_free( &info ); + free( path ); + return( -1 ); + } + + node = add_node( graph, path, &info ); + if( node == NULL ) + { + snprintf( graph->err, graph->errsz, "out of memory" ); + cpu_dyn_dependency_free( &info ); + free( path ); + return( -1 ); + } + + return( 0 ); + +} /* End of enqueue_needed() */ + + +static int +inspect_preloads( dependency_graph *graph, const char *resolved_preload ) +{ + size_t i; + + if( split_path_list(resolved_preload, &graph->preload_path, + &graph->preload_count, graph->err, graph->errsz) != 0 ) + return( -1 ); + + if( graph->preload_count == 0 ) + return( 0 ); + + graph->preload_info = (cpu_dyn_dependency_info *)calloc( + graph->preload_count, sizeof(*graph->preload_info) ); + if( graph->preload_info == NULL ) + { + snprintf( graph->err, graph->errsz, "out of memory" ); + return( -1 ); + } + + for( i = 0; i < graph->preload_count; ++i ) + if( cpu_dyn_dependency_read(graph->preload_path[i], 0, + &graph->preload_info[i], + graph->err, graph->errsz) != 0 ) + return( -1 ); + + return( 0 ); + +} /* End of inspect_preloads() */ + + +static int +append_load_path( char **buffer, size_t *length, size_t *capacity, + const char *path ) +{ + size_t path_length = strlen( path ); + size_t add = path_length + (*length != 0 ? 1U : 0U); + char *p; + + if( strchr(path, ':') != NULL ) + { + errno = EINVAL; + return( -1 ); + } + if( add > SIZE_MAX - *length - 1U ) + { + errno = EOVERFLOW; + return( -1 ); + } + + if( *length + add + 1U > *capacity ) + { + size_t new_capacity = *capacity ? *capacity : 128U; + + while( new_capacity < *length + add + 1U ) + { + if( new_capacity > SIZE_MAX / 2U ) + { + errno = EOVERFLOW; + return( -1 ); + } + new_capacity *= 2U; + } + p = (char *)realloc( *buffer, new_capacity ); + if( p == NULL ) + return( -1 ); + *buffer = p; + *capacity = new_capacity; + } + + if( *length != 0 ) + (*buffer)[(*length)++] = ':'; + memcpy( *buffer + *length, path, path_length ); + *length += path_length; + (*buffer)[*length] = 0; + return( 0 ); + +} /* End of append_load_path() */ + + +static int +build_load_list( dependency_graph *graph, char **load_list ) +{ + char *buffer = NULL; + size_t length = 0, capacity = 0, i; + + for( i = 0; i < graph->node_count; ++i ) + if( append_load_path(&buffer, &length, &capacity, + graph->node[i].path) != 0 ) + goto bad; + + /* Explicit preloads remain after normal dependencies in their original + command-line order. The loader receives preload_count separately and + applies the ABI-v3 scope policy explicitly rather than inferring it from + this flattened transport list. */ + for( i = 0; i < graph->preload_count; ++i ) + if( append_load_path(&buffer, &length, &capacity, + graph->preload_path[i]) != 0 ) + goto bad; + + *load_list = buffer; + return( 0 ); + +bad: + free( buffer ); + snprintf( graph->err, graph->errsz, + "cannot build runtime module list: %s", strerror(errno) ); + return( -1 ); + +} /* End of build_load_list() */ + + +int +mcpu_dependency_build_load_list( const mcpu_tool_options *options, + const char *executable, + const char *resolved_preload, + char **load_list, + size_t *preload_count, + char *err, size_t errsz ) +{ + dependency_graph graph; + cpu_dyn_dependency_info main_info; + unsigned abi; + size_t i, j; + int rc = -1; + + if( options == NULL || executable == NULL || load_list == NULL || + preload_count == NULL || err == NULL || errsz == 0 ) + return( -1 ); + + *load_list = NULL; + *preload_count = 0; + memset( &graph, 0, sizeof(graph) ); + memset( &main_info, 0, sizeof(main_info) ); + graph.options = options; + graph.err = err; + graph.errsz = errsz; + + if( cpu_exec_abi_version(executable, &abi, err, errsz) != 0 ) + goto done; + + if( abi == MELF_ABIVERSION_MCPU_1 ) + { + if( resolved_preload != NULL && *resolved_preload != 0 ) + { + *load_list = strdup( resolved_preload ); + if( *load_list == NULL ) + { + snprintf( err, errsz, "out of memory" ); + goto done; + } + } + rc = 0; + goto done; + } + + if( inspect_preloads(&graph, resolved_preload) != 0 ) + goto done; + + if( cpu_dyn_dependency_read(executable, 1, &main_info, + err, errsz) != 0 ) + goto done; + + /* Seed the breadth-first scope with every direct dependency before + descending into any transitive dependency. Main DT_NEEDED entries come + first in their recorded order; explicit-preload dependencies follow in + command-line order. */ + for( i = 0; i < main_info.needed_count; ++i ) + if( enqueue_needed(&graph, main_info.needed[i]) != 0 ) + goto done; + + for( i = 0; i < graph.preload_count; ++i ) + for( j = 0; j < graph.preload_info[i].needed_count; ++j ) + if( enqueue_needed(&graph, graph.preload_info[i].needed[j]) != 0 ) + goto done; + + /* node[] is a queue. Appending children while walking it yields a stable + breadth-first dependency order, naturally deduplicating cycles by SONAME. */ + for( i = 0; i < graph.node_count; ++i ) + for( j = 0; j < graph.node[i].info.needed_count; ++j ) + if( enqueue_needed(&graph, graph.node[i].info.needed[j]) != 0 ) + goto done; + + if( build_load_list(&graph, load_list) != 0 ) + goto done; + + *preload_count = graph.preload_count; + rc = 0; + +done: + cpu_dyn_dependency_free( &main_info ); + dependency_graph_free( &graph ); + if( rc != 0 ) + { + free( *load_list ); + *load_list = NULL; + } + return( rc ); + +} /* End of mcpu_dependency_build_load_list() */ diff --git a/utils/mcpu-dependency.h b/utils/mcpu-dependency.h new file mode 100644 index 0000000..aa6fbf5 --- /dev/null +++ b/utils/mcpu-dependency.h @@ -0,0 +1,15 @@ +#ifndef __MCPU_DEPENDENCY_H__ +#define __MCPU_DEPENDENCY_H__ 1 + +#include <stddef.h> + +#include "mcpu-tool-options.h" + +int mcpu_dependency_build_load_list( const mcpu_tool_options *options, + const char *executable, + const char *resolved_preload, + char **load_list, + size_t *preload_count, + char *err, size_t errsz ); + +#endif /* __MCPU_DEPENDENCY_H__ */ diff --git a/utils/mcpu-dyn.c b/utils/mcpu-dyn.c new file mode 100644 index 0000000..4d0ab2e --- /dev/null +++ b/utils/mcpu-dyn.c @@ -0,0 +1,2242 @@ +#include <errno.h> +#include <limits.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <libmcpu.h> +#include <mcpu-elf.h> + +#include "mcpu-dyn.h" +#include "mcpu-melf.h" + +typedef struct blob +{ + unsigned char *p; + size_t n, cap; +} blob; +typedef struct xph +{ + __mpu_uint32_t type, flags; + __mpu_uint64_t off, vaddr, paddr, filesz, memsz, maxsz, align; +} xph; +typedef struct xsec +{ + const char *name; + __mpu_uint32_t type; + __mpu_uint64_t flags, addr, off, size, align, entsize; + __mpu_uint32_t link, info, nameoff; + unsigned logical; + const unsigned char *payload; + unsigned char *owned; +} xsec; +typedef struct rsh +{ + __mpu_uint32_t name, type; + __mpu_uint64_t flags, addr, off, size; + __mpu_uint32_t link, info; + __mpu_uint64_t align, entsize; +} rsh; +typedef xph rph; + +typedef struct dsym +{ + char *name; + unsigned binding, type; + __mpu_uint16_t shndx; + __mpu_uint64_t value, size; +} dsym; +typedef struct drela +{ + __mpu_uint16_t target_shndx; + __mpu_uint64_t offset; + size_t sym; + unsigned type; + __mpu_int64_t addend; +} drela; +typedef struct dmod +{ + char *path; + unsigned char *file; + size_t file_size; + int data; + int abi; + __mpu_uint16_t etype; + rph *ph; + size_t phnum; + rsh *sh; + size_t shnum; + const unsigned char *shstr; + size_t shstrn; + dsym *sym; + size_t nsym; + drela *rela; + size_t nrela; + __mpu_uint64_t base[8]; + int load_index[8]; + int stacki, heapi; + int lifecycle_seen; + __mpu_uint64_t entry, init, life, fini; + __mpu_uint64_t data_section_begin, data_section_size; + int has_data_section; + char *soname; + char **needed; + size_t nneeded; +} dmod; + +typedef struct winner +{ + const char *name; + size_t mod, sym; + unsigned binding; +} winner; + +static int +grow( blob *b, size_t add ) +{ + size_t need, c; + unsigned char *q; + if( add > SIZE_MAX - b->n ) return( -1 ); + need = b->n + add; + if( need <= b->cap ) return( 0 ); + c = b->cap ? b->cap : 64; + while( c < need ) + { + if( c > SIZE_MAX / 2 ) return( -1 ); + c *= 2; + } + q = (unsigned char *)realloc( b->p, c ); + if( !q ) return( -1 ); + b->p = q; + b->cap = c; + return( 0 ); +} + +static int +bput( blob *b, const void *p, size_t n ) +{ + if( grow( b, n ) ) return( -1 ); + if( n ) memcpy( b->p + b->n, p, n ); + b->n += n; + return( 0 ); +} + +static int +bstr( blob *b, const char *s, __mpu_uint32_t *off ) +{ + size_t n = strlen( s ) + 1; + if( b->n > UINT32_MAX || n > UINT32_MAX - b->n ) return( -1 ); + *off = (__mpu_uint32_t)b->n; + return( bput( b, s, n ) ); +} + +static __mpu_uint64_t +nzalign( __mpu_uint64_t a ) +{ + return( a ? a : 1 ); +} + +static __mpu_uint64_t +maxu( __mpu_uint64_t a, __mpu_uint64_t b ) +{ + return( a > b ? a : b ); +} + +static int +addu64( __mpu_uint64_t a, __mpu_uint64_t b, __mpu_uint64_t *r ) +{ + if( a > UINT64_MAX - b ) return( -1 ); + *r = a + b; + return( 0 ); +} + +static int +addsigned( __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 +span( size_t n, __mpu_uint64_t off, __mpu_uint64_t sz ) +{ + return( off <= n && sz <= n - off ); +} + +static const char * +sec_name( unsigned s ) +{ + switch( s ) + { + case CPU_OBJ_SEC_INIT: return( MELF_SEC_INIT ); + case CPU_OBJ_SEC_TEXT: return( MELF_SEC_TEXT ); + case CPU_OBJ_SEC_FINI: return( MELF_SEC_FINI ); + case CPU_OBJ_SEC_DATA: return( MELF_SEC_DATA ); + case CPU_OBJ_SEC_BSS: return( MELF_SEC_BSS ); + case CPU_OBJ_SEC_CONTROL: return( MELF_SEC_CONTROL ); + case CPU_OBJ_SEC_INPUT: return( MELF_SEC_INPUT ); + case CPU_OBJ_SEC_OUTPUT: return( MELF_SEC_OUTPUT ); + case CPU_OBJ_SEC_TRACE: return( MELF_SEC_TRACE ); + default: return( 0 ); + } +} + +static const char * +rela_name( unsigned s ) +{ + switch( s ) + { + case CPU_OBJ_SEC_INIT: return( MELF_SEC_RELA_INIT ); + case CPU_OBJ_SEC_TEXT: return( MELF_SEC_RELA_TEXT ); + case CPU_OBJ_SEC_FINI: return( MELF_SEC_RELA_FINI ); + case CPU_OBJ_SEC_DATA: return( MELF_SEC_RELA_DATA ); + case CPU_OBJ_SEC_CONTROL: return( MELF_SEC_RELA_CONTROL ); + case CPU_OBJ_SEC_INPUT: return( MELF_SEC_RELA_INPUT ); + case CPU_OBJ_SEC_OUTPUT: return( MELF_SEC_RELA_OUTPUT ); + case CPU_OBJ_SEC_TRACE: return( MELF_SEC_RELA_TRACE ); + default: return( 0 ); + } +} + +static __mpu_uint64_t +sec_flags( unsigned s ) +{ + switch( s ) + { + case CPU_OBJ_SEC_INIT: return( MELF_SHFLAGS_INIT ); + case CPU_OBJ_SEC_TEXT: return( MELF_SHFLAGS_TEXT ); + case CPU_OBJ_SEC_FINI: return( MELF_SHFLAGS_FINI ); + case CPU_OBJ_SEC_DATA: return( MELF_SHFLAGS_DATA ); + case CPU_OBJ_SEC_BSS: return( MELF_SHFLAGS_BSS ); + case CPU_OBJ_SEC_CONTROL: return( MELF_SHFLAGS_CONTROL ); + case CPU_OBJ_SEC_INPUT: return( MELF_SHFLAGS_INPUT ); + case CPU_OBJ_SEC_OUTPUT: return( MELF_SHFLAGS_OUTPUT ); + case CPU_OBJ_SEC_TRACE: return( MELF_SHFLAGS_TRACE ); + default: return( 0 ); + } +} + +static unsigned +sec_seg( unsigned s ) +{ + return( cpu_obj_sec_segment( s ) ); +} + +static int +image_sec( const cpu_asm_image *im, unsigned s, const unsigned char **p, __mpu_uint64_t *off, __mpu_uint64_t *sz, __mpu_uint64_t *al, int *present, int *nobits ) +{ + *p = 0; + *off = *sz = 0; + *al = 1; + *present = 0; + *nobits = 0; + switch( s ) + { + case CPU_OBJ_SEC_INIT: *p = im->code; + *off = im->init_offset; + *sz = im->init_size; + *al = im->init_align; + *present = im->init_present; + break; + case CPU_OBJ_SEC_TEXT: *p = im->code; + *off = im->text_offset; + *sz = im->text_size; + *al = im->text_align; + *present = im->text_present; + break; + case CPU_OBJ_SEC_FINI: *p = im->code; + *off = im->fini_offset; + *sz = im->fini_size; + *al = im->fini_align; + *present = im->fini_present; + break; + case CPU_OBJ_SEC_DATA: *p = im->data; + *off = im->data_offset; + *sz = im->data_section_size; + *al = im->data_align; + *present = im->data_present; + break; + case CPU_OBJ_SEC_BSS: *off = im->bss_offset; + *sz = im->bss_size; + *al = im->bss_align; + *present = im->bss_present; + *nobits = 1; + break; + case CPU_OBJ_SEC_CONTROL: *p = im->control; + *off = im->control_offset; + *sz = im->control_section_size; + *al = im->control_align; + *present = im->control_present; + break; + case CPU_OBJ_SEC_INPUT: *p = im->input; + *off = im->input_offset; + *sz = im->input_section_size; + *al = im->input_align; + *present = im->input_present; + break; + case CPU_OBJ_SEC_OUTPUT: *p = im->output; + *off = im->output_offset; + *sz = im->output_section_size; + *al = im->output_align; + *present = im->output_present; + break; + case CPU_OBJ_SEC_TRACE: *p = im->trace; + *off = im->trace_offset; + *sz = im->trace_section_size; + *al = im->trace_align; + *present = im->trace_present; + break; + default: return( -1 ); + } + if( *p ) * p += *off; + return( 0 ); +} + +static void +put_ph( unsigned char *p, const xph *x, int data ) +{ + memset( p, 0, MELF64_PHDR_SIZE ); + cpu_melf_put32( p + MELF64_PHDR_P_TYPE_OFF, x->type, data ); + cpu_melf_put32( p + MELF64_PHDR_P_FLAGS_OFF, x->flags, data ); + cpu_melf_put64( p + MELF64_PHDR_P_OFFSET_OFF, x->off, data ); + cpu_melf_put64( p + MELF64_PHDR_P_VADDR_OFF, x->vaddr, data ); + cpu_melf_put64( p + MELF64_PHDR_P_PADDR_OFF, x->paddr, data ); + cpu_melf_put64( p + MELF64_PHDR_P_FILESZ_OFF, x->filesz, data ); + cpu_melf_put64( p + MELF64_PHDR_P_MEMSZ_OFF, x->memsz, data ); + cpu_melf_put64( p + MELF64_PHDR_P_MAXSZ_OFF, x->maxsz, data ); + cpu_melf_put64( p + MELF64_PHDR_P_ALIGN_OFF, x->align, data ); +} + +static void +put_sh( unsigned char *p, const xsec *x, int data ) +{ + memset( p, 0, MELF64_SHDR_SIZE ); + cpu_melf_put32( p + MELF64_SHDR_SH_NAME_OFF, x->nameoff, data ); + cpu_melf_put32( p + MELF64_SHDR_SH_TYPE_OFF, x->type, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_FLAGS_OFF, x->flags, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_ADDR_OFF, x->addr, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_OFFSET_OFF, x->off, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_SIZE_OFF, x->size, data ); + cpu_melf_put32( p + MELF64_SHDR_SH_LINK_OFF, x->link, data ); + cpu_melf_put32( p + MELF64_SHDR_SH_INFO_OFF, x->info, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_ADDRALIGN_OFF, x->align, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_ENTSIZE_OFF, x->entsize, data ); +} + +static void +get_ph( const unsigned char *p, int data, rph *x ) +{ + x->type = cpu_melf_get32( p + MELF64_PHDR_P_TYPE_OFF, data ); + x->flags = cpu_melf_get32( p + MELF64_PHDR_P_FLAGS_OFF, data ); + x->off = cpu_melf_get64( p + MELF64_PHDR_P_OFFSET_OFF, data ); + x->vaddr = cpu_melf_get64( p + MELF64_PHDR_P_VADDR_OFF, data ); + x->paddr = cpu_melf_get64( p + MELF64_PHDR_P_PADDR_OFF, data ); + x->filesz = cpu_melf_get64( p + MELF64_PHDR_P_FILESZ_OFF, data ); + x->memsz = cpu_melf_get64( p + MELF64_PHDR_P_MEMSZ_OFF, data ); + x->maxsz = cpu_melf_get64( p + MELF64_PHDR_P_MAXSZ_OFF, data ); + x->align = cpu_melf_get64( p + MELF64_PHDR_P_ALIGN_OFF, data ); +} + +static void +get_sh( const unsigned char *p, int data, rsh *x ) +{ + x->name = cpu_melf_get32( p + MELF64_SHDR_SH_NAME_OFF, data ); + x->type = cpu_melf_get32( p + MELF64_SHDR_SH_TYPE_OFF, data ); + x->flags = cpu_melf_get64( p + MELF64_SHDR_SH_FLAGS_OFF, data ); + x->addr = cpu_melf_get64( p + MELF64_SHDR_SH_ADDR_OFF, data ); + x->off = cpu_melf_get64( p + MELF64_SHDR_SH_OFFSET_OFF, data ); + x->size = cpu_melf_get64( p + MELF64_SHDR_SH_SIZE_OFF, data ); + x->link = cpu_melf_get32( p + MELF64_SHDR_SH_LINK_OFF, data ); + x->info = cpu_melf_get32( p + MELF64_SHDR_SH_INFO_OFF, data ); + x->align = cpu_melf_get64( p + MELF64_SHDR_SH_ADDRALIGN_OFF, data ); + x->entsize = cpu_melf_get64( p + MELF64_SHDR_SH_ENTSIZE_OFF, data ); +} + +static int +add_load( xph *ph, size_t *np, __mpu_uint64_t *cur, unsigned seg, __mpu_uint64_t filesz, __mpu_uint64_t memsz, __mpu_uint32_t flags, __mpu_uint64_t align, char *err, size_t es ) +{ + int ov = 0; + align = nzalign( align ); + if( !cpu_melf_is_pow2( align ) || filesz > memsz || memsz > CPU_PTR_OFFSET_MASK || filesz > SIZE_MAX ) + { + snprintf( err, es, "invalid dynamic load descriptor" ); + return( -1 ); + } + *cur = cpu_melf_align_up( *cur, align, &ov ); + if( ov ) + { + snprintf( err, es, "dynamic file layout overflow" ); + return( -1 ); + } + ph[*np] = ( xph ) + { + MELF_PT_LOAD, flags, *cur, MELF_MCPU_PTR64( seg, 0 ), 0, filesz, memsz, memsz, align + }; + ++ *np; + if( addu64( *cur, filesz, cur ) ) + { + snprintf( err, es, "dynamic file layout overflow" ); + return( -1 ); + } + return( 0 ); +} + +static int +cpu_dyn_write_version( const char *path, const cpu_asm_image *im, + const cpu_exec_options *op, int shared, int abi, + const cpu_dyn_metadata *metadata, + char *err, size_t es ) +{ + static const unsigned logicals[] = + { + CPU_OBJ_SEC_INIT, CPU_OBJ_SEC_TEXT, CPU_OBJ_SEC_FINI, CPU_OBJ_SEC_DATA, CPU_OBJ_SEC_BSS, CPU_OBJ_SEC_CONTROL, CPU_OBJ_SEC_INPUT, CPU_OBJ_SEC_OUTPUT, CPU_OBJ_SEC_TRACE + }; + xph ph[8]; + size_t np = 0; + int phidx[8] = + { + -1, -1, -1, -1, -1, -1, -1, -1 + }; + xsec sec[40]; + size_t ns = 1, i, j, nlocal = 0; + __mpu_uint16_t secidx[0x24]; + blob shstr = + { + 0 + } + , dynstr = + { + 0 + } + , dynsym = + { + 0 + } + , dynamic = + { + 0 + } + , relab[9] = + { + { + 0 + } + }; + int data = cpu_melf_data_encoding(), ov = 0; + __mpu_uint64_t cur = MELF64_EHDR_SIZE, shoff, total, data_mem; + unsigned char *file = 0, tbuf[MELF_MCPU_TARGET_SIZE], lbuf[MELF_MCPU_LIFECYCLE_SIZE]; + MElf_McpuTarget mt; + MElf_McpuLifecycle ml; + __mpu_uint16_t dynstridx, dynsymidx, dynamicidx = 0, shstridx; + int have_data; + memset( sec, 0, sizeof( sec ) ); + memset( secidx, 0, sizeof( secidx ) ); + if( !path || !im || ( !shared && !op ) ) + { + snprintf( err, es, "bad dynamic writer arguments" ); + goto fail; + } + + if( abi != MELF_ABIVERSION_DYNAMIC_EXPLICIT && + abi != MELF_ABIVERSION_DYNAMIC_NEEDED ) + { + snprintf( err, es, "unsupported dynamic ABI version" ); + goto fail; + } + + if( abi == MELF_ABIVERSION_DYNAMIC_EXPLICIT ) + { + if( metadata != NULL ) + { + snprintf( err, es, "ABI-v2 writer does not accept dependency metadata" ); + goto fail; + } + } + else + { + size_t k; + + if( shared ) + { + if( metadata == NULL || metadata->soname == NULL || + metadata->soname[0] == 0 || strchr( metadata->soname, '/' ) != NULL ) + { + snprintf( err, es, "ABI-v3 ET_DYN requires a logical SONAME" ); + goto fail; + } + } + else if( metadata != NULL && metadata->soname != NULL ) + { + snprintf( err, es, "ABI-v3 ET_EXEC must not define SONAME" ); + goto fail; + } + + if( metadata != NULL ) + for( k = 0; k < metadata->needed_count; ++k ) + { + if( metadata->needed == NULL || metadata->needed[k] == NULL || + metadata->needed[k][0] == 0 || + strchr( metadata->needed[k], '/' ) != NULL ) + { + snprintf( err, es, "ABI-v3 NEEDED name must be logical" ); + goto fail; + } + } + } + + if( shared ) + { + if( im->has_start || im->has_life || im->init_present || im->fini_present || im->control_present || im->input_present || im->output_present || im->trace_present ) + { + snprintf( err, es, "ET_DYN contains forbidden executable/model sections" ); + goto fail; + } + if( !im->text_present && !im->data_present && !im->bss_present ) + { + snprintf( err, es, "empty shared object" ); + goto fail; + } + } + else + { + if( !im->has_start || !im->has_life || !im->text_present || !im->code_size ) + { + snprintf( err, es, "incomplete dynamic executable" ); + goto fail; + } + if( !op->stack_size || op->stack_size % MCPU_STARTUP_ALIGN || op->stack_size > CPU_PTR_OFFSET_MASK || op->stack_size > SIZE_MAX ) + { + snprintf( err, es, "invalid STACK size" ); + goto fail; + } + if( op->have_heap && ( op->heap_size < CPU_HEAP_BEGIN || op->heap_limit < op->heap_size || op->heap_limit > CPU_PTR_OFFSET_MASK || op->heap_size > SIZE_MAX ) ) + { + snprintf( err, es, "invalid HEAP size/limit" ); + goto fail; + } + } + data_mem = im->data_mem_size ? im->data_mem_size : im->data_size; + have_data = ( data_mem || im->data_present || im->bss_present ); + /* count program headers then lay file images */ + np = ( im->code_size ? 1 : 0 ) + ( have_data ? 1 : 0 ) + ( shared ? 0 : ( ( im->control_present || im->control_size ) + ( im->input_present || im->input_size ) + ( im->output_present || im->output_size ) + ( im->trace_present || im->trace_size ) + 1 + ( op->have_heap ? 1 : 0 ) ) ); + if( np > 8 || cur > UINT64_MAX - np *MELF64_PHDR_SIZE ) + { + snprintf( err, es, "too many dynamic program headers" ); + goto fail; + } + cur += np *MELF64_PHDR_SIZE; + np = 0; + if( im->code_size ) + { + __mpu_uint64_t a = maxu( maxu( nzalign( im->init_align ), nzalign( im->text_align ) ), nzalign( im->fini_align ) ); + phidx[MELF_MCPU_SEG_PROGRAM] = (int)np; + if( add_load( ph, &np, &cur, MELF_MCPU_SEG_PROGRAM, im->code_size, im->code_size, MELF_PFLAGS_PROGRAM, a, err, es ) ) goto fail; + } + if( have_data ) + { + phidx[MELF_MCPU_SEG_DATA] = (int)np; + if( add_load( ph, &np, &cur, MELF_MCPU_SEG_DATA, im->data_size, data_mem, MELF_PFLAGS_DATA, maxu( 1, maxu( nzalign( im->data_align ), nzalign( im->bss_align ) ) ), err, es ) ) goto fail; + } + if( !shared ) + { + if( im->control_present || im->control_size ) + { + phidx[MELF_MCPU_SEG_CONTROL] = (int)np; + if( add_load( ph, &np, &cur, MELF_MCPU_SEG_CONTROL, im->control_size, im->control_size, MELF_PFLAGS_CONTROL, nzalign( im->control_align ), err, es ) ) goto fail; + } + if( im->input_present || im->input_size ) + { + phidx[MELF_MCPU_SEG_INPUT] = (int)np; + if( add_load( ph, &np, &cur, MELF_MCPU_SEG_INPUT, im->input_size, im->input_size, MELF_PFLAGS_INPUT, nzalign( im->input_align ), err, es ) ) goto fail; + } + if( im->output_present || im->output_size ) + { + phidx[MELF_MCPU_SEG_OUTPUT] = (int)np; + if( add_load( ph, &np, &cur, MELF_MCPU_SEG_OUTPUT, im->output_size, im->output_size, MELF_PFLAGS_OUTPUT, nzalign( im->output_align ), err, es ) ) goto fail; + } + if( im->trace_present || im->trace_size ) + { + phidx[MELF_MCPU_SEG_TRACE] = (int)np; + if( add_load( ph, &np, &cur, MELF_MCPU_SEG_TRACE, im->trace_size, im->trace_size, MELF_PFLAGS_TRACE, nzalign( im->trace_align ), err, es ) ) goto fail; + } + phidx[MELF_MCPU_SEG_STACK] = (int)np; + ph[np++] = ( xph ) + { + MELF_PT_MCPU_STACK, MELF_PFLAGS_STACK, 0, MELF_MCPU_VADDR_STACK, 0, 0, op->stack_size, op->stack_size, MCPU_STARTUP_ALIGN + }; + if( op->have_heap ) + { + phidx[MELF_MCPU_SEG_HEAP] = (int)np; + ph[np++] = ( xph ) + { + MELF_PT_MCPU_HEAP, MELF_PFLAGS_HEAP, 0, MELF_MCPU_VADDR_HEAP, 0, 0, op->heap_size, op->heap_limit, 16 + }; + } + } + /* allocated sections */ + for( i = 0; i < sizeof( logicals ) / sizeof( logicals[0] ); ++i ) + { + const unsigned char *p; + __mpu_uint64_t so, sz, al; + int pres, nb; + unsigned lg = logicals[i], seg; + if( image_sec( im, lg, &p, &so, &sz, &al, &pres, &nb ) || !pres ) continue; + if( shared && ( lg != CPU_OBJ_SEC_TEXT && lg != CPU_OBJ_SEC_DATA && lg != CPU_OBJ_SEC_BSS ) ) + { + snprintf( err, es, "forbidden ET_DYN section %s", sec_name( lg ) ); + goto fail; + } + seg = sec_seg( lg ); + if( phidx[seg] < 0 ) + { + snprintf( err, es, "section has no load segment" ); + goto fail; + } + secidx[lg] = (__mpu_uint16_t)ns; + sec[ns] = ( xsec ) + { + sec_name( lg ), nb ? MELF_SHT_NOBITS : MELF_SHT_PROGBITS, sec_flags( lg ), MELF_MCPU_PTR64( seg, so ), nb ? ph[phidx[seg]].off + so : ph[phidx[seg]].off + so, sz, nzalign( al ), 0, 0, 0, 0, lg, p, 0 + }; + ++ns; + } + cpu_melf_fill_target( &mt ); + cpu_melf_encode_target( tbuf, &mt, data ); + sec[ns++] = ( xsec ) + { + MELF_SEC_MCPU_TARGET, MELF_SHT_MCPU_TARGET, 0, 0, 0, MELF_MCPU_TARGET_SIZE, 8, MELF_MCPU_TARGET_SIZE, 0, 0, 0, 0, tbuf, 0 + }; + if( !shared ) + { + cpu_melf_fill_lifecycle( &ml ); + cpu_melf_encode_lifecycle( lbuf, &ml, data ); + sec[ns++] = ( xsec ) + { + MELF_SEC_MCPU_LIFECYCLE, MELF_SHT_MCPU_LIFECYCLE, 0, 0, 0, MELF_MCPU_LIFECYCLE_SIZE, 8, MELF_MCPU_LIFECYCLE_SIZE, 0, 0, 0, 0, lbuf, 0 + }; + } + if( bput( &dynstr, "", 1 ) || bput( &dynsym, ( unsigned char[MELF64_SYM_SIZE] ) + { + 0 + } + , MELF64_SYM_SIZE)) + { + snprintf( err, es, "out of memory" ); + goto fail; + } + for( i = 0; i < im->dyn_symbol_count; ++i ) if( im->dyn_symbols[i].binding == MELF_STB_LOCAL )++nlocal; + for( i = 0; i < im->dyn_symbol_count; ++i ) + { + unsigned char sb[MELF64_SYM_SIZE] = + { + 0 + }; + __mpu_uint32_t no; + const cpu_dyn_symbol *d = &im->dyn_symbols[i]; + __mpu_uint16_t shn = MELF_SHN_UNDEF; + if( bstr( &dynstr, d->name, &no ) ) + { + snprintf( err, es, "dynamic string table overflow" ); + goto fail; + } + if( d->section != CPU_OBJ_SEC_UNDEF ) + { + if( d->section >= sizeof( secidx ) / sizeof( secidx[0] ) || ! ( shn = secidx[d->section] ) ) + { + snprintf( err, es, "dynamic symbol belongs to absent output section: %s", d->name ); + goto fail; + } + } + cpu_melf_put32( sb + MELF64_SYM_ST_NAME_OFF, no, data ); + sb[MELF64_SYM_ST_INFO_OFF] = MELF64_ST_INFO( d->binding, d->type ); + sb[MELF64_SYM_ST_OTHER_OFF] = MELF_STV_DEFAULT; + cpu_melf_put16( sb + MELF64_SYM_ST_SHNDX_OFF, shn, data ); + cpu_melf_put64( sb + MELF64_SYM_ST_VALUE_OFF, d->value, data ); + cpu_melf_put64( sb + MELF64_SYM_ST_SIZE_OFF, d->size, data ); + if( bput( &dynsym, sb, sizeof( sb ) ) ) + { + snprintf( err, es, "out of memory" ); + goto fail; + } + } + if( abi == MELF_ABIVERSION_DYNAMIC_NEEDED ) + { + size_t k; + + if( metadata != NULL ) + { + if( metadata->soname != NULL ) + { + unsigned char db[MELF64_DYN_SIZE] = { 0 }; + __mpu_uint32_t no; + + if( bstr( &dynstr, metadata->soname, &no ) ) + { + snprintf( err, es, "dynamic string table overflow" ); + goto fail; + } + cpu_melf_put64( db + MELF64_DYN_D_TAG_OFF, + (__mpu_uint64_t)MELF_DT_SONAME, data ); + cpu_melf_put64( db + MELF64_DYN_D_VAL_OFF, no, data ); + if( bput( &dynamic, db, sizeof( db ) ) ) + { + snprintf( err, es, "out of memory" ); + goto fail; + } + } + + for( k = 0; k < metadata->needed_count; ++k ) + { + unsigned char db[MELF64_DYN_SIZE] = { 0 }; + __mpu_uint32_t no; + + if( bstr( &dynstr, metadata->needed[k], &no ) ) + { + snprintf( err, es, "dynamic string table overflow" ); + goto fail; + } + cpu_melf_put64( db + MELF64_DYN_D_TAG_OFF, + (__mpu_uint64_t)MELF_DT_NEEDED, data ); + cpu_melf_put64( db + MELF64_DYN_D_VAL_OFF, no, data ); + if( bput( &dynamic, db, sizeof( db ) ) ) + { + snprintf( err, es, "out of memory" ); + goto fail; + } + } + } + + { + unsigned char db[MELF64_DYN_SIZE] = { 0 }; + if( bput( &dynamic, db, sizeof( db ) ) ) + { + snprintf( err, es, "out of memory" ); + goto fail; + } + } + } + + dynstridx = (__mpu_uint16_t)ns; + sec[ns++] = ( xsec ) + { + MELF_SEC_DYNSTR, MELF_SHT_STRTAB, 0, 0, 0, dynstr.n, 1, 0, 0, 0, 0, 0, dynstr.p, 0 + }; + dynsymidx = (__mpu_uint16_t)ns; + sec[ns++] = ( xsec ) + { + MELF_SEC_DYNSYM, MELF_SHT_DYNSYM, 0, 0, 0, dynsym.n, 8, MELF64_SYM_SIZE, dynstridx, (__mpu_uint32_t)( nlocal + 1 ), 0, 0, dynsym.p, 0 + }; + if( abi == MELF_ABIVERSION_DYNAMIC_NEEDED ) + { + dynamicidx = (__mpu_uint16_t)ns; + sec[ns++] = ( xsec ) + { + MELF_SEC_DYNAMIC, MELF_SHT_DYNAMIC, 0, 0, 0, dynamic.n, 8, + MELF64_DYN_SIZE, dynstridx, 0, 0, 0, dynamic.p, 0 + }; + } + for( i = 0; i < im->dyn_reloc_count; ++i ) + { + const cpu_dyn_reloc *r = &im->dyn_relocs[i]; + int li = -1; + for( j = 0; j < sizeof( logicals ) / sizeof( logicals[0] ); ++j ) if( logicals[j] == r->section ) + { + li = (int)j; + break; + } + if( li < 0 || !secidx[r->section] || r->symbol_index >= im->dyn_symbol_count ) + { + snprintf( err, es, "bad retained dynamic relocation" ); + goto fail; + } + unsigned char rb[MELF64_RELA_SIZE] = + { + 0 + }; + cpu_melf_put64( rb + MELF64_RELA_R_OFFSET_OFF, r->offset, data ); + cpu_melf_put64( rb + MELF64_RELA_R_INFO_OFF, MELF64_R_INFO( r->symbol_index + 1, r->type ), data ); + cpu_melf_put64( rb + MELF64_RELA_R_ADDEND_OFF, (__mpu_uint64_t)r->addend, data ); + if( bput( &relab[li], rb, sizeof( rb ) ) ) + { + snprintf( err, es, "out of memory" ); + goto fail; + } + } + for( i = 0; i < sizeof( logicals ) / sizeof( logicals[0] ); ++i ) if( relab[i].n ) + { + const char *rn = rela_name( logicals[i] ); + sec[ns++] = ( xsec ) + { + rn, MELF_SHT_RELA, 0, 0, 0, relab[i].n, 8, MELF64_RELA_SIZE, dynsymidx, secidx[logicals[i]], 0, 0, relab[i].p, 0 + }; + } + /* section names */ + if( bput( &shstr, "", 1 ) ) + { + snprintf( err, es, "out of memory" ); + goto fail; + } + shstridx = (__mpu_uint16_t)ns; + sec[ns++] = ( xsec ) + { + MELF_SEC_SHSTRTAB, MELF_SHT_STRTAB, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 + }; + for( i = 1; i < ns; ++i ) if( bstr( &shstr, sec[i].name, &sec[i].nameoff ) ) + { + snprintf( err, es, "section string table overflow" ); + goto fail; + } + sec[shstridx].payload = shstr.p; + sec[shstridx].size = shstr.n; + /* non-ALLOC payload layout */ + for( i = 1; i < ns; ++i ) + { + if( sec[i].flags & MELF_SHF_ALLOC ) continue; + cur = cpu_melf_align_up( cur, nzalign( sec[i].align ), &ov ); + if( ov ) + { + snprintf( err, es, "dynamic file layout overflow" ); + goto fail; + } + sec[i].off = cur; + if( addu64( cur, sec[i].size, &cur ) ) + { + snprintf( err, es, "dynamic file layout overflow" ); + goto fail; + } + } + cur = cpu_melf_align_up( cur, 8, &ov ); + if( ov || cur > UINT64_MAX - ns *MELF64_SHDR_SIZE ) + { + snprintf( err, es, "dynamic file layout overflow" ); + goto fail; + } + shoff = cur; + total = shoff + ns *MELF64_SHDR_SIZE; + if( total > SIZE_MAX ) + { + snprintf( err, es, "dynamic file too large" ); + goto fail; + } + file = (unsigned char *)calloc( (size_t)total, 1 ); + if( !file ) + { + snprintf( err, es, "out of memory" ); + goto fail; + } + /* ehdr */ + file[MELF_EI_MAG0] = MELFMAG0; + file[MELF_EI_MAG1] = MELFMAG1; + file[MELF_EI_MAG2] = MELFMAG2; + file[MELF_EI_MAG3] = MELFMAG3; + file[MELF_EI_CLASS] = MELF_CLASS_64; + file[MELF_EI_DATA] = (unsigned char)data; + file[MELF_EI_VERSION] = MELF_EV_CURRENT; + file[MELF_EI_OSABI] = MELF_OSABI_MCPU; + file[MELF_EI_ABIVERSION] = (unsigned char)abi; + cpu_melf_put16( file + MELF64_EHDR_E_TYPE_OFF, shared ? MELF_ET_DYN : MELF_ET_EXEC, data ); + cpu_melf_put16( file + MELF64_EHDR_E_MACHINE_OFF, MELF_EM_MCPU, data ); + cpu_melf_put32( file + MELF64_EHDR_E_VERSION_OFF, MELF_EV_CURRENT, data ); + if( !shared ) + { + cpu_melf_put64( file + MELF64_EHDR_E_ENTRY_OFF, MELF_MCPU_PTR64( MELF_MCPU_SEG_PROGRAM, im->start_entry ), data ); + cpu_melf_put64( file + MELF64_EHDR_E_INIT_ENTRY_OFF, im->has_init ? MELF_MCPU_PTR64( MELF_MCPU_SEG_PROGRAM, im->init_entry ) : 0, data ); + cpu_melf_put64( file + MELF64_EHDR_E_LIFE_ENTRY_OFF, MELF_MCPU_PTR64( MELF_MCPU_SEG_PROGRAM, im->life_entry ), data ); + cpu_melf_put64( file + MELF64_EHDR_E_FINI_ENTRY_OFF, im->has_fini ? MELF_MCPU_PTR64( MELF_MCPU_SEG_PROGRAM, im->fini_entry ) : 0, data ); + } + cpu_melf_put64( file + MELF64_EHDR_E_PHOFF_OFF, MELF64_EHDR_SIZE, data ); + cpu_melf_put64( file + MELF64_EHDR_E_SHOFF_OFF, shoff, data ); + cpu_melf_put32( file + MELF64_EHDR_E_FLAGS_OFF, MELF_EF_MCPU_NONE, data ); + cpu_melf_put16( file + MELF64_EHDR_E_EHSIZE_OFF, MELF64_EHDR_SIZE, data ); + cpu_melf_put16( file + MELF64_EHDR_E_PHENTSIZE_OFF, MELF64_PHDR_SIZE, data ); + cpu_melf_put16( file + MELF64_EHDR_E_PHNUM_OFF, (__mpu_uint16_t)np, data ); + cpu_melf_put16( file + MELF64_EHDR_E_SHENTSIZE_OFF, MELF64_SHDR_SIZE, data ); + cpu_melf_put16( file + MELF64_EHDR_E_SHNUM_OFF, (__mpu_uint16_t)ns, data ); + cpu_melf_put16( file + MELF64_EHDR_E_SHSTRNDX_OFF, shstridx, data ); + for( i = 0; i < np; ++i ) put_ph( file + MELF64_EHDR_SIZE + i *MELF64_PHDR_SIZE, &ph[i], data ); + /* load payloads */ + for( i = 0; i < np; ++i ) if( ph[i].type == MELF_PT_LOAD && ph[i].filesz ) + { + unsigned seg = MELF_MCPU_PTR64_SEG( ph[i].vaddr ); + const unsigned char *p = 0; + if( seg == MELF_MCPU_SEG_PROGRAM ) p = im->code; + else if( seg == MELF_MCPU_SEG_DATA ) p = im->data; + else if( seg == MELF_MCPU_SEG_CONTROL ) p = im->control; + else if( seg == MELF_MCPU_SEG_INPUT ) p = im->input; + else if( seg == MELF_MCPU_SEG_OUTPUT ) p = im->output; + else if( seg == MELF_MCPU_SEG_TRACE ) p = im->trace; + if( !p ) + { + snprintf( err, es, "missing load payload" ); + goto fail; + } + memcpy( file + (size_t)ph[i].off, p, (size_t)ph[i].filesz ); + } + for( i = 1; i < ns; ++i ) if( ! ( sec[i].flags & MELF_SHF_ALLOC ) && sec[i].type != MELF_SHT_NOBITS && sec[i].size && sec[i].payload ) memcpy( file + (size_t)sec[i].off, sec[i].payload, (size_t)sec[i].size ); + for( i = 0; i < ns; ++i ) put_sh( file + (size_t)shoff + i *MELF64_SHDR_SIZE, &sec[i], data ); + if( cpu_melf_writeall( path, file, (size_t)total, err, es ) ) goto fail; + free( file ); + free( shstr.p ); + free( dynstr.p ); + free( dynsym.p ); + free( dynamic.p ); + for( i = 0; i < 9; ++i ) free( relab[i].p ); + (void)dynamicidx; + return( 0 ); + +fail: + free( file ); + free( shstr.p ); + free( dynstr.p ); + free( dynsym.p ); + free( dynamic.p ); + for( i = 0; i < 9; ++i ) free( relab[i].p ); + return( -1 ); +} + +int +cpu_dyn_write( const char *path, const cpu_asm_image *im, + const cpu_exec_options *op, int shared, + char *err, size_t es ) +{ + return( cpu_dyn_write_version(path, im, op, shared, + MELF_ABIVERSION_DYNAMIC_EXPLICIT, NULL, + err, es) ); +} + +int +cpu_dyn_write_abi3( const char *path, const cpu_asm_image *im, + const cpu_exec_options *op, int shared, + const cpu_dyn_metadata *metadata, + char *err, size_t es ) +{ + return( cpu_dyn_write_version(path, im, op, shared, + MELF_ABIVERSION_DYNAMIC_NEEDED, metadata, + err, es) ); +} + +static int +name_at( const unsigned char *t, size_t n, __mpu_uint32_t off, const char **out ) +{ + if( off >= n ) return( -1 ); + *out = (const char *)t + off; + return( memchr( *out, 0, n - off ) ? 0 : -1 ); +} + +static int +expected_pflags( unsigned seg ) +{ + switch( seg ) + { + case MELF_MCPU_SEG_PROGRAM: return( MELF_PFLAGS_PROGRAM ); + case MELF_MCPU_SEG_DATA: return( MELF_PFLAGS_DATA ); + case MELF_MCPU_SEG_CONTROL: return( MELF_PFLAGS_CONTROL ); + case MELF_MCPU_SEG_INPUT: return( MELF_PFLAGS_INPUT ); + case MELF_MCPU_SEG_OUTPUT: return( MELF_PFLAGS_OUTPUT ); + case MELF_MCPU_SEG_TRACE: return( MELF_PFLAGS_TRACE ); + case MELF_MCPU_SEG_STACK: return( MELF_PFLAGS_STACK ); + case MELF_MCPU_SEG_HEAP: return( MELF_PFLAGS_HEAP ); + default: return( -1 ); + } +} + +static __mpu_uint64_t +expected_shflags( unsigned seg ) +{ + switch( seg ) + { + case MELF_MCPU_SEG_PROGRAM: return( MELF_SHFLAGS_TEXT ); + case MELF_MCPU_SEG_DATA: return( MELF_SHFLAGS_DATA ); + case MELF_MCPU_SEG_CONTROL: return( MELF_SHFLAGS_CONTROL ); + case MELF_MCPU_SEG_INPUT: return( MELF_SHFLAGS_INPUT ); + case MELF_MCPU_SEG_OUTPUT: return( MELF_SHFLAGS_OUTPUT ); + case MELF_MCPU_SEG_TRACE: return( MELF_SHFLAGS_TRACE ); + default: return( 0 ); + } +} + +static int +image_seg( unsigned seg ) +{ + return( seg <= MELF_MCPU_SEG_TRACE && seg != MELF_MCPU_SEG_STACK ); +} + +static void +free_mod( dmod *m ) +{ + size_t i; + if( !m ) return; + free( m->path ); + free( m->file ); + free( m->ph ); + free( m->sh ); + for( i = 0; i < m->nsym; ++i ) free( m->sym[i].name ); + free( m->sym ); + free( m->rela ); + free( m->soname ); + for( i = 0; i < m->nneeded; ++i ) free( m->needed[i] ); + free( m->needed ); + memset( m, 0, sizeof( *m ) ); +} + +static int +parse_module( const char *path, int want_exec, int reject_needed, dmod *m, char *err, size_t es ) +{ + __mpu_uint64_t phoff, shoff; + __mpu_uint16_t phnum, shnum, shstridx; + size_t i; + int nt = 0, nl = 0, dynsymidx = -1, dynstridx = -1, dynamicidx = -1; + MElf_McpuTarget mt; + MElf_McpuLifecycle ml; + memset( m, 0, sizeof( *m ) ); + for( i = 0; i < 8; ++i ) m->load_index[i] = -1; + m->stacki = m->heapi = -1; + m->path = strdup( path ); + if( !m->path ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + if( cpu_melf_readall( path, &m->file, &m->file_size, err, es ) ) return( -1 ); + if( m->file_size < MELF64_EHDR_SIZE ) + { + snprintf( err, es, "%s: truncated MELF", path ); + return( -1 ); + } + unsigned char *f = m->file; + if( f[MELF_EI_MAG0] != MELFMAG0 || f[MELF_EI_MAG1] != MELFMAG1 || + f[MELF_EI_MAG2] != MELFMAG2 || f[MELF_EI_MAG3] != MELFMAG3 || + f[MELF_EI_CLASS] != MELF_CLASS_64 || + f[MELF_EI_VERSION] != MELF_EV_CURRENT || + f[MELF_EI_OSABI] != MELF_OSABI_MCPU ) + { + snprintf( err, es, "%s: not a supported dynamic MELF file", path ); + return( -1 ); + } + m->abi = f[MELF_EI_ABIVERSION]; + if( m->abi != MELF_ABIVERSION_DYNAMIC_EXPLICIT && + m->abi != MELF_ABIVERSION_DYNAMIC_NEEDED ) + { + snprintf( err, es, "%s: unsupported dynamic MELF ABI %d", path, m->abi ); + return( -1 ); + } + m->data = f[MELF_EI_DATA]; + if( m->data != MELF_DATA_LSB && m->data != MELF_DATA_MSB ) + { + snprintf( err, es, "%s: unsupported EI_DATA", path ); + return( -1 ); + } + m->etype = cpu_melf_get16( f + MELF64_EHDR_E_TYPE_OFF, m->data ); + if( ( want_exec && m->etype != MELF_ET_EXEC ) || ( !want_exec && m->etype != MELF_ET_DYN ) || cpu_melf_get16( f + MELF64_EHDR_E_MACHINE_OFF, m->data ) != MELF_EM_MCPU || cpu_melf_get32( f + MELF64_EHDR_E_FLAGS_OFF, m->data ) != 0 || cpu_melf_get16( f + MELF64_EHDR_E_EHSIZE_OFF, m->data ) != MELF64_EHDR_SIZE || cpu_melf_get16( f + MELF64_EHDR_E_PHENTSIZE_OFF, m->data ) != MELF64_PHDR_SIZE || cpu_melf_get16( f + MELF64_EHDR_E_SHENTSIZE_OFF, m->data ) != MELF64_SHDR_SIZE ) + { + snprintf( err, es, "%s: invalid dynamic MELF header", path ); + return( -1 ); + } + m->entry = cpu_melf_get64( f + MELF64_EHDR_E_ENTRY_OFF, m->data ); + m->init = cpu_melf_get64( f + MELF64_EHDR_E_INIT_ENTRY_OFF, m->data ); + m->life = cpu_melf_get64( f + MELF64_EHDR_E_LIFE_ENTRY_OFF, m->data ); + m->fini = cpu_melf_get64( f + MELF64_EHDR_E_FINI_ENTRY_OFF, m->data ); + phoff = cpu_melf_get64( f + MELF64_EHDR_E_PHOFF_OFF, m->data ); + shoff = cpu_melf_get64( f + MELF64_EHDR_E_SHOFF_OFF, m->data ); + phnum = cpu_melf_get16( f + MELF64_EHDR_E_PHNUM_OFF, m->data ); + shnum = cpu_melf_get16( f + MELF64_EHDR_E_SHNUM_OFF, m->data ); + shstridx = cpu_melf_get16( f + MELF64_EHDR_E_SHSTRNDX_OFF, m->data ); + if( !phnum || !shnum || shstridx >= shnum || !span( m->file_size, phoff, (__mpu_uint64_t)phnum *MELF64_PHDR_SIZE ) || !span( m->file_size, shoff, (__mpu_uint64_t)shnum *MELF64_SHDR_SIZE ) ) + { + snprintf( err, es, "%s: invalid MELF tables", path ); + return( -1 ); + } + m->ph = ( rph * ) calloc( phnum, sizeof( *m->ph ) ); + m->sh = ( rsh * ) calloc( shnum, sizeof( *m->sh ) ); + if( !m->ph || !m->sh ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + m->phnum = phnum; + m->shnum = shnum; + for( i = 0; i < phnum; ++i ) get_ph( f + (size_t)phoff + i *MELF64_PHDR_SIZE, m->data, &m->ph[i] ); + for( i = 0; i < shnum; ++i ) get_sh( f + (size_t)shoff + i *MELF64_SHDR_SIZE, m->data, &m->sh[i] ); + if( m->sh[shstridx].type != MELF_SHT_STRTAB || !span( m->file_size, m->sh[shstridx].off, m->sh[shstridx].size ) || m->sh[shstridx].size > SIZE_MAX ) + { + snprintf( err, es, "%s: invalid .shstrtab", path ); + return( -1 ); + } + m->shstr = f + (size_t)m->sh[shstridx].off; + m->shstrn = (size_t)m->sh[shstridx].size; + for( i = 0; i < phnum; ++i ) + { + rph *p = &m->ph[i]; + if( p->paddr || ( p->align > 1 && !cpu_melf_is_pow2( p->align ) ) ) + { + snprintf( err, es, "%s: invalid program header", path ); + return( -1 ); + } + if( p->type == MELF_PT_LOAD ) + { + unsigned seg = MELF_MCPU_PTR64_SEG( p->vaddr ); + if( !image_seg( seg ) || MELF_MCPU_PTR64_OFFSET( p->vaddr ) != 0 || m->load_index[seg] >= 0 || p->flags != (unsigned)expected_pflags( seg ) || p->filesz > p->memsz || p->maxsz != p->memsz || !span( m->file_size, p->off, p->filesz ) || p->memsz > CPU_PTR_OFFSET_MASK ) + { + snprintf( err, es, "%s: invalid PT_LOAD", path ); + return( -1 ); + } + if( !want_exec && seg != MELF_MCPU_SEG_PROGRAM && seg != MELF_MCPU_SEG_DATA ) + { + snprintf( err, es, "%s: ET_DYN has forbidden load segment", path ); + return( -1 ); + } + m->load_index[seg] = (int)i; + } + else if( p->type == MELF_PT_MCPU_STACK ) + { + if( !want_exec || m->stacki >= 0 || p->vaddr != MELF_MCPU_VADDR_STACK || p->flags != MELF_PFLAGS_STACK || p->off || p->filesz || !p->memsz || p->maxsz != p->memsz || p->memsz % MCPU_STARTUP_ALIGN ) + { + snprintf( err, es, "%s: invalid STACK descriptor", path ); + return( -1 ); + } + m->stacki = (int)i; + } + else if( p->type == MELF_PT_MCPU_HEAP ) + { + if( !want_exec || m->heapi >= 0 || p->vaddr != MELF_MCPU_VADDR_HEAP || p->flags != MELF_PFLAGS_HEAP || p->off || p->filesz || p->memsz < CPU_HEAP_BEGIN || p->maxsz < p->memsz ) + { + snprintf( err, es, "%s: invalid HEAP descriptor", path ); + return( -1 ); + } + m->heapi = (int)i; + } + else + { + snprintf( err, es, "%s: unsupported program header", path ); + return( -1 ); + } + } + if( want_exec && ( m->load_index[MELF_MCPU_SEG_PROGRAM] < 0 || m->stacki < 0 ) ) + { + snprintf( err, es, "%s: dynamic executable lacks PROGRAM/STACK", path ); + return( -1 ); + } + if( !want_exec && ( m->stacki >= 0 || m->heapi >= 0 || m->entry || m->init || m->life || m->fini ) ) + { + snprintf( err, es, "%s: ET_DYN contains process lifecycle state", path ); + return( -1 ); + } + if( want_exec && m->load_index[MELF_MCPU_SEG_DATA] >= 0 ) + { + rph *dp = &m->ph[m->load_index[MELF_MCPU_SEG_DATA]]; + if( dp->filesz < CPU_DATA_NULL_GUARD_SIZE || dp->memsz < CPU_DATA_NULL_GUARD_SIZE ) + { + snprintf( err, es, "%s: DATA segment lacks process NULL guard", path ); + return( -1 ); + } + for( i = 0; i < CPU_DATA_NULL_GUARD_SIZE; ++i ) if( m->file[(size_t)dp->off + i] ) + { + snprintf( err, es, "%s: DATA NULL guard is not zero", path ); + return( -1 ); + } + } + for( i = 1; i < shnum; ++i ) + { + const char *name; + if( name_at( m->shstr, m->shstrn, m->sh[i].name, &name ) ) + { + snprintf( err, es, "%s: bad section name", path ); + return( -1 ); + } + if( m->sh[i].type == MELF_SHT_MCPU_TARGET ) + { + if( nt++ || m->sh[i].size != MELF_MCPU_TARGET_SIZE || !span( m->file_size, m->sh[i].off, m->sh[i].size ) || cpu_melf_decode_target( f + (size_t)m->sh[i].off, &mt, m->data ) || cpu_melf_validate_target( &mt, m->data, err, es ) ) return( -1 ); + } + else if( m->sh[i].type == MELF_SHT_MCPU_LIFECYCLE ) + { + if( !want_exec || nl++ || m->sh[i].size != MELF_MCPU_LIFECYCLE_SIZE || !span( m->file_size, m->sh[i].off, m->sh[i].size ) || cpu_melf_decode_lifecycle( f + (size_t)m->sh[i].off, &ml, m->data ) || cpu_melf_validate_lifecycle( &ml, err, es ) ) return( -1 ); + } + else if( m->sh[i].type == MELF_SHT_DYNSYM ) + { + if( dynsymidx >= 0 ) + { + snprintf( err, es, "%s: duplicate .dynsym", path ); + return( -1 ); + } + dynsymidx = (int)i; + } + else if( m->sh[i].type == MELF_SHT_DYNAMIC ) + { + if( dynamicidx >= 0 || strcmp( name, MELF_SEC_DYNAMIC ) ) + { + snprintf( err, es, "%s: invalid or duplicate .dynamic", path ); + return( -1 ); + } + dynamicidx = (int)i; + } + else if( m->sh[i].type == MELF_SHT_STRTAB && !strcmp( name, MELF_SEC_DYNSTR ) ) + { + if( dynstridx >= 0 ) + { + snprintf( err, es, "%s: duplicate .dynstr", path ); + return( -1 ); + } + dynstridx = (int)i; + } + if( m->sh[i].flags & MELF_SHF_ALLOC ) + { + unsigned seg = MELF_MCPU_PTR64_SEG( m->sh[i].addr ), flagseg = MELF_SHF_MCPU_GET_SEG( m->sh[i].flags ); + if( ( m->sh[i].flags & MELF_SHF_MCPU_SEG_VALID ) == 0 || m->load_index[seg] < 0 || seg != flagseg || m->sh[i].flags != expected_shflags( seg ) || m->sh[i].align == 0 || !cpu_melf_is_pow2( m->sh[i].align ) ) + { + snprintf( err, es, "%s: invalid allocated section %s", path, name ); + return( -1 ); + } + if( !want_exec && strcmp( name, MELF_SEC_TEXT ) && strcmp( name, MELF_SEC_DATA ) && strcmp( name, MELF_SEC_BSS ) ) + { + snprintf( err, es, "%s: ET_DYN has forbidden allocated section %s", path, name ); + return( -1 ); + } + __mpu_uint64_t off = MELF_MCPU_PTR64_OFFSET( m->sh[i].addr ); + rph *p = &m->ph[m->load_index[seg]]; + if( off > p->memsz || m->sh[i].size > p->memsz - off ) + { + snprintf( err, es, "%s: section outside PT_LOAD", path ); + return( -1 ); + } + if( m->sh[i].type == MELF_SHT_PROGBITS && ( m->sh[i].off < p->off || m->sh[i].off - p->off != off || m->sh[i].size > p->filesz - off || !span( m->file_size, m->sh[i].off, m->sh[i].size ) ) ) + { + snprintf( err, es, "%s: section/file mapping mismatch", path ); + return( -1 ); + } + if( seg == MELF_MCPU_SEG_DATA && !strcmp( name, MELF_SEC_DATA ) ) + { + m->has_data_section = 1; + m->data_section_begin = off; + m->data_section_size = m->sh[i].size; + } + } + } + if( nt != 1 || nl != ( want_exec ? 1 : 0 ) || + dynsymidx < 0 || dynstridx < 0 ) + { + snprintf( err, es, "%s: required dynamic metadata missing", path ); + return( -1 ); + } + if( m->abi == MELF_ABIVERSION_DYNAMIC_EXPLICIT && dynamicidx >= 0 ) + { + snprintf( err, es, "%s: ABI-v2 file must not contain .dynamic", path ); + return( -1 ); + } + if( m->abi == MELF_ABIVERSION_DYNAMIC_NEEDED && dynamicidx < 0 ) + { + snprintf( err, es, "%s: ABI-v3 file lacks .dynamic", path ); + return( -1 ); + } + if( want_exec ) + { + rph *pp = &m->ph[m->load_index[MELF_MCPU_SEG_PROGRAM]]; + if( !m->entry || !m->life || MELF_MCPU_PTR64_SEG( m->entry ) != MELF_MCPU_SEG_PROGRAM || MELF_MCPU_PTR64_SEG( m->life ) != MELF_MCPU_SEG_PROGRAM || MELF_MCPU_PTR64_OFFSET( m->entry ) >= pp->memsz || MELF_MCPU_PTR64_OFFSET( m->life ) >= pp->memsz || ( m->init && m->init != m->entry ) || ( m->init && MELF_MCPU_PTR64_SEG( m->init ) != MELF_MCPU_SEG_PROGRAM ) || ( m->fini && MELF_MCPU_PTR64_SEG( m->fini ) != MELF_MCPU_SEG_PROGRAM ) ) + { + snprintf( err, es, "%s: invalid lifecycle entries", path ); + return( -1 ); + } + } + rsh *dy = &m->sh[dynsymidx], *ds = &m->sh[dynstridx]; + if( dy->link != (unsigned)dynstridx || dy->entsize != MELF64_SYM_SIZE || dy->size % MELF64_SYM_SIZE || !span( m->file_size, dy->off, dy->size ) || !span( m->file_size, ds->off, ds->size ) || ds->size > SIZE_MAX ) + { + snprintf( err, es, "%s: invalid dynamic symbol tables", path ); + return( -1 ); + } + const unsigned char *str = f + (size_t)ds->off; + size_t strn = (size_t)ds->size; + + if( m->abi == MELF_ABIVERSION_DYNAMIC_NEEDED ) + { + rsh *dd = &m->sh[dynamicidx]; + size_t ndyn, k; + int seen_null = 0; + + if( dd->flags != 0 || dd->addr != 0 || + dd->link != (unsigned)dynstridx || dd->info != 0 || + dd->align != 8 || dd->entsize != MELF64_DYN_SIZE || + dd->size < MELF64_DYN_SIZE || dd->size % MELF64_DYN_SIZE || + !span( m->file_size, dd->off, dd->size ) ) + { + snprintf( err, es, "%s: invalid .dynamic section", path ); + return( -1 ); + } + + ndyn = (size_t)( dd->size / MELF64_DYN_SIZE ); + for( k = 0; k < ndyn; ++k ) + { + const unsigned char *dp = + f + (size_t)dd->off + k * MELF64_DYN_SIZE; + __mpu_int64_t tag = + (__mpu_int64_t)cpu_melf_get64( dp + MELF64_DYN_D_TAG_OFF, m->data ); + __mpu_uint64_t val = + cpu_melf_get64( dp + MELF64_DYN_D_VAL_OFF, m->data ); + const char *nm; + + if( tag == MELF_DT_NULL ) + { + if( val != 0 || k + 1 != ndyn ) + { + snprintf( err, es, "%s: non-canonical DT_NULL", path ); + return( -1 ); + } + seen_null = 1; + continue; + } + + if( seen_null ) + { + snprintf( err, es, "%s: dynamic entry follows DT_NULL", path ); + return( -1 ); + } + + if( tag == MELF_DT_SONAME ) + { + if( want_exec || m->soname != NULL || val > UINT32_MAX || + name_at( str, strn, (__mpu_uint32_t)val, &nm ) || + nm[0] == 0 || strchr( nm, '/' ) != NULL ) + { + snprintf( err, es, "%s: invalid DT_SONAME", path ); + return( -1 ); + } + m->soname = strdup( nm ); + if( m->soname == NULL ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + } + else if( tag == MELF_DT_NEEDED ) + { + char **q; + + if( val > UINT32_MAX || + name_at( str, strn, (__mpu_uint32_t)val, &nm ) || + nm[0] == 0 || strchr( nm, '/' ) != NULL ) + { + snprintf( err, es, "%s: invalid DT_NEEDED", path ); + return( -1 ); + } + if( m->nneeded == SIZE_MAX / sizeof( *q ) ) + { + snprintf( err, es, "too many DT_NEEDED entries" ); + return( -1 ); + } + q = (char **)realloc( m->needed, + (m->nneeded + 1) * sizeof( *q ) ); + if( q == NULL ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + m->needed = q; + m->needed[m->nneeded] = strdup( nm ); + if( m->needed[m->nneeded] == NULL ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + ++m->nneeded; + } + else + { + snprintf( err, es, "%s: unsupported dynamic tag %lld", + path, (long long)tag ); + return( -1 ); + } + } + + if( !seen_null ) + { + snprintf( err, es, "%s: .dynamic lacks DT_NULL", path ); + return( -1 ); + } + if( !want_exec && m->soname == NULL ) + { + snprintf( err, es, "%s: ABI-v3 ET_DYN lacks SONAME", path ); + return( -1 ); + } + if( reject_needed && m->nneeded != 0 ) + { + snprintf( err, es, + "%s: ABI-v3 DT_NEEDED loading is not implemented yet", path ); + return( -1 ); + } + } + + m->nsym = (size_t)( dy->size / MELF64_SYM_SIZE ); + m->sym = ( dsym * ) calloc( m->nsym, sizeof( *m->sym ) ); + if( !m->sym ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + for( i = 0; i < m->nsym; ++i ) + { + const unsigned char *sp = f + (size_t)dy->off + i *MELF64_SYM_SIZE; + __mpu_uint32_t no = cpu_melf_get32( sp + MELF64_SYM_ST_NAME_OFF, m->data ); + m->sym[i].binding = MELF64_ST_BIND( sp[MELF64_SYM_ST_INFO_OFF] ); + m->sym[i].type = MELF64_ST_TYPE( sp[MELF64_SYM_ST_INFO_OFF] ); + m->sym[i].shndx = cpu_melf_get16( sp + MELF64_SYM_ST_SHNDX_OFF, m->data ); + m->sym[i].value = cpu_melf_get64( sp + MELF64_SYM_ST_VALUE_OFF, m->data ); + m->sym[i].size = cpu_melf_get64( sp + MELF64_SYM_ST_SIZE_OFF, m->data ); + if( i == 0 ) continue; + const char *nm; + if( name_at( str, strn, no, &nm ) || !no || ( m->sym[i].binding != MELF_STB_LOCAL && m->sym[i].binding != MELF_STB_GLOBAL && m->sym[i].binding != MELF_STB_WEAK ) || sp[MELF64_SYM_ST_OTHER_OFF] != MELF_STV_DEFAULT ) + { + snprintf( err, es, "%s: invalid .dynsym entry", path ); + return( -1 ); + } + m->sym[i].name = strdup( nm ); + if( !m->sym[i].name ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + if( m->sym[i].shndx != MELF_SHN_UNDEF ) + { + if( m->sym[i].shndx >= shnum || ( m->sh[m->sym[i].shndx].flags & MELF_SHF_ALLOC ) == 0 || m->sym[i].value > m->sh[m->sym[i].shndx].size || m->sym[i].size > m->sh[m->sym[i].shndx].size - m->sym[i].value ) + { + snprintf( err, es, "%s: dynamic symbol outside section", path ); + return( -1 ); + } + } + } + for( i = 1; i < shnum; ++i ) if( m->sh[i].type == MELF_SHT_RELA ) + { + rsh *rs = &m->sh[i]; + if( rs->link != (unsigned)dynsymidx || rs->info >= shnum || rs->entsize != MELF64_RELA_SIZE || rs->size % MELF64_RELA_SIZE || !span( m->file_size, rs->off, rs->size ) || ( m->sh[rs->info].flags & MELF_SHF_ALLOC ) == 0 || m->sh[rs->info].type == MELF_SHT_NOBITS ) + { + snprintf( err, es, "%s: invalid dynamic RELA section", path ); + return( -1 ); + } + size_t nr = (size_t)( rs->size / MELF64_RELA_SIZE ), k; + drela *q = ( drela * ) realloc( m->rela, ( m->nrela + nr ) * sizeof( *q ) ); + if( !q ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + m->rela = q; + for( k = 0; k < nr; ++k ) + { + const unsigned char *rp = f + (size_t)rs->off + k *MELF64_RELA_SIZE; + __mpu_uint64_t info = cpu_melf_get64( rp + MELF64_RELA_R_INFO_OFF, m->data ); + size_t sy = (size_t)MELF64_R_SYM( info ); + unsigned rt = MELF64_R_TYPE( info ); + __mpu_uint64_t ro = cpu_melf_get64( rp + MELF64_RELA_R_OFFSET_OFF, m->data ); + if( !sy || sy >= m->nsym || rt < CPU_RELOC_CODE64 || rt > CPU_RELOC_CPTR64_DATA || ro > m->sh[rs->info].size || 8 > m->sh[rs->info].size - ro ) + { + snprintf( err, es, "%s: bad runtime relocation", path ); + return( -1 ); + } + m->rela[m->nrela++] = ( drela ) + { + (__mpu_uint16_t)rs->info, ro, sy, rt, (__mpu_int64_t)cpu_melf_get64( rp + MELF64_RELA_R_ADDEND_OFF, m->data ) + }; + } + } + return( 0 ); +} + +void +cpu_dyn_library_free( cpu_dyn_library_info *info ) +{ + size_t i; + + if( info == NULL ) + return; + + free( info->soname ); + info->soname = NULL; + for( i = 0; i < info->symbol_count; ++i ) + free( info->symbols[i].name ); + free( info->symbols ); + info->symbols = NULL; + info->symbol_count = 0; + +} /* End of cpu_dyn_library_free() */ + + +int +cpu_dyn_library_read( const char *path, cpu_dyn_library_info *info, + char *err, size_t es ) +{ + dmod m; + size_t i, n = 0; + int rc = -1; + + if( path == NULL || info == NULL ) + { + if( err != NULL && es != 0 ) + snprintf( err, es, "invalid shared-library inspection arguments" ); + return( -1 ); + } + + memset( info, 0, sizeof( *info ) ); + memset( &m, 0, sizeof( m ) ); + + if( parse_module(path, 0, 0, &m, err, es) != 0 ) + goto done; + + if( m.abi != MELF_ABIVERSION_DYNAMIC_NEEDED ) + { + snprintf( err, es, "%s: shared-library dependency requires MELF ABI-v3", + path ); + goto done; + } + if( m.soname == NULL ) + { + snprintf( err, es, "%s: ABI-v3 ET_DYN lacks SONAME", path ); + goto done; + } + + info->soname = strdup( m.soname ); + if( info->soname == NULL ) + { + snprintf( err, es, "out of memory" ); + goto done; + } + + for( i = 1; i < m.nsym; ++i ) + if( m.sym[i].shndx != MELF_SHN_UNDEF && + ( m.sym[i].binding == MELF_STB_GLOBAL || + m.sym[i].binding == MELF_STB_WEAK ) ) + ++n; + + if( n != 0 ) + { + info->symbols = (cpu_dyn_library_symbol *)calloc( n, + sizeof(*info->symbols) ); + if( info->symbols == NULL ) + { + snprintf( err, es, "out of memory" ); + goto done; + } + + for( i = 1; i < m.nsym; ++i ) + if( m.sym[i].shndx != MELF_SHN_UNDEF && + ( m.sym[i].binding == MELF_STB_GLOBAL || + m.sym[i].binding == MELF_STB_WEAK ) ) + { + cpu_dyn_library_symbol *s = &info->symbols[info->symbol_count]; + + s->name = strdup( m.sym[i].name ); + if( s->name == NULL ) + { + snprintf( err, es, "out of memory" ); + goto done; + } + s->binding = m.sym[i].binding; + s->type = m.sym[i].type; + ++info->symbol_count; + } + } + + rc = 0; + +done: + free_mod( &m ); + if( rc != 0 ) + cpu_dyn_library_free( info ); + return( rc ); + +} /* End of cpu_dyn_library_read() */ + + +int +cpu_dyn_library_find_symbol( const cpu_dyn_library_info *info, + const char *name, + const cpu_dyn_library_symbol **symbol ) +{ + size_t i; + + if( symbol != NULL ) + *symbol = NULL; + if( info == NULL || name == NULL ) + return( -1 ); + + for( i = 0; i < info->symbol_count; ++i ) + if( strcmp(info->symbols[i].name, name) == 0 ) + { + if( symbol != NULL ) + *symbol = &info->symbols[i]; + return( 0 ); + } + + return( -1 ); + +} /* End of cpu_dyn_library_find_symbol() */ + + +void +cpu_dyn_dependency_free( cpu_dyn_dependency_info *info ) +{ + size_t i; + + if( info == NULL ) + return; + + free( info->soname ); + info->soname = NULL; + for( i = 0; i < info->needed_count; ++i ) + free( info->needed[i] ); + free( info->needed ); + info->needed = NULL; + info->needed_count = 0; + info->abi = 0; + +} /* End of cpu_dyn_dependency_free() */ + + +int +cpu_dyn_dependency_read( const char *path, int want_exec, + cpu_dyn_dependency_info *info, + char *err, size_t es ) +{ + dmod m; + size_t i; + int rc = -1; + + if( path == NULL || info == NULL ) + { + if( err != NULL && es != 0 ) + snprintf( err, es, "invalid dynamic dependency inspection arguments" ); + return( -1 ); + } + + memset( info, 0, sizeof(*info) ); + memset( &m, 0, sizeof(m) ); + + if( parse_module(path, want_exec, 0, &m, err, es) != 0 ) + goto done; + + info->abi = m.abi; + if( m.soname != NULL ) + { + info->soname = strdup( m.soname ); + if( info->soname == NULL ) + { + snprintf( err, es, "out of memory" ); + goto done; + } + } + + if( m.nneeded != 0 ) + { + info->needed = (char **)calloc( m.nneeded, sizeof(*info->needed) ); + if( info->needed == NULL ) + { + snprintf( err, es, "out of memory" ); + goto done; + } + for( i = 0; i < m.nneeded; ++i ) + { + info->needed[i] = strdup( m.needed[i] ); + if( info->needed[i] == NULL ) + { + snprintf( err, es, "out of memory" ); + goto done; + } + ++info->needed_count; + } + } + + rc = 0; + +done: + free_mod( &m ); + if( rc != 0 ) + cpu_dyn_dependency_free( info ); + return( rc ); + +} /* End of cpu_dyn_dependency_read() */ + + +static int +seg_alloc( unsigned seg, __mpu_uint64_t n, __mpu_uint64_t max, __mpu_UNIT_t **dst ) +{ + int r = -1; + *dst = 0; + switch( seg ) + { + case MELF_MCPU_SEG_PROGRAM: r = __cpu_program_alloc( n ); + *dst = __cpu_program_ptr(); + break; + case MELF_MCPU_SEG_DATA: r = __cpu_data_alloc( n ); + *dst = __cpu_data_ptr(); + break; + case MELF_MCPU_SEG_CONTROL: r = __cpu_control_alloc( n ); + *dst = __cpu_control_ptr(); + break; + case MELF_MCPU_SEG_INPUT: r = __cpu_input_alloc( n ); + *dst = __cpu_input_ptr(); + break; + case MELF_MCPU_SEG_OUTPUT: r = __cpu_output_alloc( n ); + *dst = __cpu_output_ptr(); + break; + case MELF_MCPU_SEG_TRACE: r = __cpu_trace_alloc( n ); + *dst = __cpu_trace_ptr(); + break; + case MELF_MCPU_SEG_STACK: r = __cpu_stack_alloc( n ); + *dst = __cpu_stack_ptr(); + break; + case MELF_MCPU_SEG_HEAP: r = __cpu_heap_alloc( n, max ); + *dst = __cpu_heap_ptr(); + break; + } + return( r ); +} + +static __mpu_UNIT_t * +seg_ptr( unsigned seg ) +{ + switch( seg ) + { + case MELF_MCPU_SEG_PROGRAM: return( __cpu_program_ptr() ); + case MELF_MCPU_SEG_DATA: return( __cpu_data_ptr() ); + case MELF_MCPU_SEG_CONTROL: return( __cpu_control_ptr() ); + case MELF_MCPU_SEG_INPUT: return( __cpu_input_ptr() ); + case MELF_MCPU_SEG_OUTPUT: return( __cpu_output_ptr() ); + case MELF_MCPU_SEG_TRACE: return( __cpu_trace_ptr() ); + default: return( 0 ); + } +} + +static int +put_startup( int argc, char **argv, __mpu_uint64_t stack_size, __mpu_uint64_t *base, char *err, size_t es ) +{ + __mpu_uint64_t count, arr, strings = 0, raw, b; + size_t i; + __mpu_UNIT_t *sp = __cpu_stack_ptr(); + if( argc < 0 || ( argc && !argv ) ) + { + snprintf( err, es, "bad argc/argv" ); + return( -1 ); + } + count = (__mpu_uint64_t)(unsigned)argc; + if( count == UINT64_MAX || count + 1 > UINT64_MAX / MCPU_STARTUP_ARGV_SIZE ) + { + snprintf( err, es, "startup argv overflow" ); + return( -1 ); + } + arr = ( count + 1 ) * MCPU_STARTUP_ARGV_SIZE; + for( i = 0; i < (size_t)argc; ++i ) + { + size_t sl = strlen( argv[i] ); + __mpu_uint64_t z = (__mpu_uint64_t)sl + 1; + if( !z || strings > UINT64_MAX - z ) + { + snprintf( err, es, "startup strings overflow" ); + return( -1 ); + } + strings += z; + } + if( MCPU_STARTUP_SIZE > UINT64_MAX - arr || MCPU_STARTUP_SIZE + arr > UINT64_MAX - strings ) + { + snprintf( err, es, "startup size overflow" ); + return( -1 ); + } + raw = MCPU_STARTUP_SIZE + arr + strings; + if( raw > stack_size ) + { + snprintf( err, es, "command line does not fit executable STACK" ); + return( -1 ); + } + b = ( stack_size - raw ) & ~ ( (__mpu_uint64_t)MCPU_STARTUP_ALIGN - 1 ); + if( raw > stack_size - b ) + { + snprintf( err, es, "command line does not fit aligned executable STACK" ); + return( -1 ); + } + memset( sp + (size_t)b, 0, (size_t)( stack_size - b ) ); + __cpu_data_put_u32( sp + (size_t)b + MCPU_STARTUP_VERSION_OFF, MCPU_STARTUP_VERSION_CURRENT ); + __cpu_data_put_u32( sp + (size_t)b + MCPU_STARTUP_SIZE_OFF, MCPU_STARTUP_SIZE ); + __cpu_data_put_u64( sp + (size_t)b + MCPU_STARTUP_ARGC_OFF, count ); + __cpu_data_put_u64( sp + (size_t)b + MCPU_STARTUP_ARGV_OFF, MELF_MCPU_PTR64( MELF_MCPU_SEG_STACK, b + MCPU_STARTUP_SIZE ) ); + __cpu_data_put_u64( sp + (size_t)b + MCPU_STARTUP_RESERVED_OFF, 0 ); + { + __mpu_uint64_t pos = b + MCPU_STARTUP_SIZE + arr; + for( i = 0; i < (size_t)argc; ++i ) + { + size_t z = strlen( argv[i] ) + 1; + __cpu_data_put_u64( sp + (size_t)( b + MCPU_STARTUP_SIZE + i * 8 ), MELF_MCPU_PTR64( MELF_MCPU_SEG_STACK, pos ) ); + memcpy( sp + (size_t)pos, argv[i], z ); + pos += z; + } + __cpu_data_put_u64( sp + (size_t)( b + MCPU_STARTUP_SIZE + (size_t)argc * 8 ), 0 ); + } + *base = b; + return( 0 ); +} + +static int +split_preloads( const char *s, char ***out, size_t *n, char *err, size_t es ) +{ + char *copy = 0, *p, *q; + char **v = 0; + size_t nv = 0, cap = 0; + *out = 0; + *n = 0; + if( !s || !*s ) return( 0 ); + copy = strdup( s ); + if( !copy ) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + p = copy; + for( ; ; ) + { + q = strchr( p, ':' ); + if( q ) * q = 0; + if( !*p ) + { + snprintf( err, es, "empty path in --ldpreload list" ); + goto bad; + } + if( nv == cap ) + { + size_t nc = cap ? cap * 2 : 4; + char **z = (char **)realloc( v, nc *sizeof( *z ) ); + if( !z ) + { + snprintf( err, es, "out of memory" ); + goto bad; + } + v = z; + cap = nc; + } + v[nv] = strdup( p ); + if( !v[nv] ) + { + snprintf( err, es, "out of memory" ); + goto bad; + } + ++nv; + if( !q ) break; + p = q + 1; + } + free( copy ); + *out = v; + *n = nv; + return( 0 ); + bad: free( copy ); + while( nv ) free( v[--nv] ); + free( v ); + return( -1 ); +} + +static void +free_paths( char **v, size_t n ) +{ + while( n ) free( v[--n] ); + free( v ); +} + +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") ); + +} /* End of is_runtime_hook_name() */ + + +static int +find_winner( winner *w, size_t nw, const char *name, size_t *idx ) +{ + size_t i; + for( i = 0; i < nw; ++i ) if( !strcmp( w[i].name, name ) ) + { + if( idx ) * idx = i; + return( 0 ); + } + return( -1 ); +} + +static int +scope_class( size_t mod, size_t preload_start ) +{ + if( mod >= preload_start ) + return( 3 ); + if( mod == 0 ) + return( 2 ); + return( 1 ); + +} /* End of scope_class() */ + +static int +scoped_candidate_better( unsigned binding, size_t mod, + unsigned old_binding, size_t old_mod, + size_t preload_start ) +{ + int cls, old_cls; + + if( binding != old_binding ) + return( binding == MELF_STB_GLOBAL ); + + cls = scope_class( mod, preload_start ); + old_cls = scope_class( old_mod, preload_start ); + if( cls != old_cls ) + return( cls > old_cls ); + + if( cls == 3 ) + return( mod > old_mod ); + if( cls == 1 ) + return( mod < old_mod ); + + return( 0 ); + +} /* End of scoped_candidate_better() */ + +static int +build_winners( dmod *m, size_t nm, size_t preload_start, int scoped, + winner **wp, size_t *np, char *err, size_t es ) +{ + winner *w = 0; + size_t nw = 0, cap = 0, mi, si, x; + for( mi = 0; mi < nm; ++mi ) for( si = 1; si < m[mi].nsym; ++si ) + { + dsym *s = &m[mi].sym[si]; + if( s->binding == MELF_STB_LOCAL || s->shndx == MELF_SHN_UNDEF ) continue; + if( !strcmp( s->name, "_start" ) || !strcmp( s->name, "_life" ) || !strcmp( s->name, "_fini" ) ) continue; + + /* + * HLL runtime hooks may be supplied by the normal MCPU Libc dependency, + * but explicit preloads are never allowed to interpose them. Main- + * executable definitions still win naturally through scoped resolution. + */ + if( scoped && mi >= preload_start && is_runtime_hook_name(s->name) ) + continue; + if( !find_winner( w, nw, s->name, &x ) ) + { + winner *o = &w[x]; + int replace; + + if( scoped ) + replace = scoped_candidate_better(s->binding, mi, o->binding, o->mod, + preload_start); + else + replace = ( s->binding == MELF_STB_GLOBAL || + o->binding == MELF_STB_WEAK ); + + if( replace ) + { + o->mod = mi; + o->sym = si; + o->binding = s->binding; + } + continue; + } + if( nw == cap ) + { + size_t nc = cap ? cap * 2 : 32; + winner *q = ( winner * ) realloc( w, nc *sizeof( *q ) ); + if( !q ) + { + free( w ); + snprintf( err, es, "out of memory" ); + return( -1 ); + } + w = q; + cap = nc; + } + w[nw++] = ( winner ) + { + s->name, mi, si, s->binding + }; + } + *wp = w; + *np = nw; + return( 0 ); +} + +static int +symbol_addr( dmod *m, size_t mi, size_t si, __mpu_uint64_t *ptr, unsigned *seg, __mpu_uint64_t *off, char *err, size_t es ) +{ + dsym *s = &m[mi].sym[si]; + if( s->shndx == MELF_SHN_UNDEF || s->shndx >= m[mi].shnum ) + { + snprintf( err, es, "undefined dynamic symbol" ); + return( -1 ); + } + rsh *sh = &m[mi].sh[s->shndx]; + unsigned sg = MELF_MCPU_PTR64_SEG( sh->addr ); + __mpu_uint64_t rel = MELF_MCPU_PTR64_OFFSET( sh->addr ), o; + if( addu64( m[mi].base[sg], rel, &o ) || addu64( o, s->value, &o ) || o > CPU_PTR_OFFSET_MASK || __cpu_ptr_make( ptr, sg, o ) ) + { + snprintf( err, es, "dynamic symbol address overflow: %s", s->name ); + return( -1 ); + } + *seg = sg; + *off = o; + return( 0 ); +} + +static int +cpu_dyn_load_internal( const char *path, const char *preload_list, + int allow_needed, size_t preload_count, + int scoped_resolution, int argc, char **argv, + cpu_exec_loaded *ld, char *err, size_t es ) +{ + char **paths = 0; + size_t np = 0, nm = 0, preload_start = 0, i, j; + dmod *mods = 0; + winner *w = 0; + size_t nw = 0; + __mpu_uint64_t cursor[8] = + { + 0 + } + , segsize[8] = + { + 0 + }; + int rc = -1; + __mpu_UNIT_t *dst; + memset( ld, 0, sizeof( *ld ) ); + if( split_preloads( preload_list, &paths, &np, err, es ) ) return( -1 ); + if( scoped_resolution && preload_count > np ) + { + snprintf( err, es, "invalid resolved preload count" ); + free_paths( paths, np ); + return( -1 ); + } + nm = np + 1; + preload_start = scoped_resolution ? nm - preload_count : nm; + mods = ( dmod * ) calloc( nm, sizeof( *mods ) ); + if( !mods ) + { + snprintf( err, es, "out of memory" ); + goto out; + } + if( parse_module( path, 1, allow_needed ? 0 : 1, &mods[0], err, es ) ) + goto out; + for( i = 0; i < np; ++i ) + if( parse_module(paths[i], 0, allow_needed ? 0 : 1, + &mods[i + 1], err, es) ) + goto out; + /* process layout: main bases are zero; shared objects append per segment */ + for( i = 0; i < 8; ++i ) + { + if( mods[0].load_index[i] >= 0 ) cursor[i] = mods[0].ph[mods[0].load_index[i]].memsz; + } + for( i = 1; i < nm; ++i ) for( j = 0; j < 8; ++j ) if( mods[i].load_index[j] >= 0 ) + { + rph *p = &mods[i].ph[mods[i].load_index[j]]; + int ov = 0; + if( j == MELF_MCPU_SEG_DATA && cursor[j] == 0 ) cursor[j] = CPU_DATA_NULL_GUARD_SIZE; + cursor[j] = cpu_melf_align_up( cursor[j], nzalign( p->align ), &ov ); + if( ov || cursor[j] > CPU_PTR_OFFSET_MASK - p->memsz ) + { + snprintf( err, es, "dynamic segment layout overflow" ); + goto out; + } + mods[i].base[j] = cursor[j]; + cursor[j] += p->memsz; + } + for( i = 0; i < 8; ++i ) segsize[i] = cursor[i]; + /* allocate/copy image-backed process segments */ + for( i = 0; i <= MELF_MCPU_SEG_TRACE; ++i ) if( segsize[i] ) + { + if( seg_alloc( (unsigned)i, segsize[i], segsize[i], &dst ) ) + { + snprintf( err, es, "cannot allocate process segment %zu", i ); + goto out; + } + memset( dst, 0, (size_t)segsize[i] ); + } + for( i = 0; i < nm; ++i ) for( j = 0; j < 8; ++j ) if( mods[i].load_index[j] >= 0 ) + { + rph *p = &mods[i].ph[mods[i].load_index[j]]; + dst = seg_ptr( (unsigned)j ); + if( p->filesz ) memcpy( dst + (size_t)mods[i].base[j], mods[i].file + (size_t)p->off, (size_t)p->filesz ); + } + if( build_winners(mods, nm, preload_start, scoped_resolution, + &w, &nw, err, es) ) goto out; + /* apply every retained startup relocation */ + for( i = 0; i < nm; ++i ) for( j = 0; j < mods[i].nrela; ++j ) + { + drela *r = &mods[i].rela[j]; + dsym *ref = &mods[i].sym[r->sym]; + size_t wi = 0; + __mpu_uint64_t tptr = 0, toff = 0, final = 0, field; + unsigned tseg = 0, sseg; + int unresolved = 0; + int local = ( ref->binding == MELF_STB_LOCAL ); + rsh *ssh = &mods[i].sh[r->target_shndx]; + sseg = MELF_MCPU_PTR64_SEG( ssh->addr ); + if( local ) + { + if( ref->shndx == MELF_SHN_UNDEF ) + { + snprintf( err, es, "undefined LOCAL dynamic symbol" ); + goto out; + } + if( symbol_addr( mods, i, r->sym, &tptr, &tseg, &toff, err, es ) ) goto out; + } + else unresolved = find_winner( w, nw, ref->name, &wi ); + if( addu64( mods[i].base[sseg], MELF_MCPU_PTR64_OFFSET( ssh->addr ), &field ) || addu64( field, r->offset, &field ) || field > segsize[sseg] || 8 > segsize[sseg] - field ) + { + snprintf( err, es, "runtime relocation field outside process image" ); + goto out; + } + if( unresolved ) + { + if( ref->binding == MELF_STB_WEAK && ( r->type == CPU_RELOC_PTR64_CODE || r->type == CPU_RELOC_PTR64_DATA || r->type == CPU_RELOC_CPTR64_DATA ) && r->addend == 0 ) + { + final = 0; + } + else + { + snprintf( err, es, "unresolved dynamic symbol: %s", ref->name ); + goto out; + } + } + else + { + if( !local && symbol_addr( mods, w[wi].mod, w[wi].sym, &tptr, &tseg, &toff, err, es ) ) goto out; + if( addsigned( toff, r->addend, &final ) ) + { + snprintf( err, es, "dynamic relocation overflow: %s", ref->name ); + goto out; + } + } + unsigned char *fp = seg_ptr( sseg ) + (size_t)field; + switch( r->type ) + { + case CPU_RELOC_CODE64: if( unresolved || tseg != MELF_MCPU_SEG_PROGRAM || final >= segsize[MELF_MCPU_SEG_PROGRAM] ) + { + snprintf( err, es, "CODE64 dynamic target is not PROGRAM: %s", ref->name ); + goto out; + } + __cpu_code_put_u64( fp, final ); + break; + case CPU_RELOC_DATA64: if( unresolved || tseg != MELF_MCPU_SEG_DATA || final > segsize[MELF_MCPU_SEG_DATA] ) + { + snprintf( err, es, "DATA64 dynamic target is not DATA: %s", ref->name ); + goto out; + } + __cpu_code_put_u64( fp, final ); + break; + case CPU_RELOC_PTR64_CODE: if( unresolved ) __cpu_code_put_u64( fp, 0 ); + else + { + if( final > CPU_PTR_OFFSET_MASK || __cpu_ptr_make( &tptr, tseg, final ) ) + { + snprintf( err, es, "PTR64 dynamic target overflow" ); + goto out; + } + __cpu_code_put_u64( fp, tptr ); + } break; + case CPU_RELOC_PTR64_DATA: if( unresolved ) __cpu_data_put_u64( fp, 0 ); + else + { + if( final > CPU_PTR_OFFSET_MASK || __cpu_ptr_make( &tptr, tseg, final ) ) + { + snprintf( err, es, "PTR64 dynamic target overflow" ); + goto out; + } + __cpu_data_put_u64( fp, tptr ); + } break; + case CPU_RELOC_CPTR64_DATA: if( unresolved ) __cpu_data_put_u64( fp, 0 ); + else + { + if( tseg != MELF_MCPU_SEG_PROGRAM || final > CPU_PTR_OFFSET_MASK || __cpu_ptr_make( &tptr, MELF_MCPU_SEG_PROGRAM, final ) ) + { + snprintf( err, es, "CPTR64 dynamic target is not PROGRAM" ); + goto out; + } + __cpu_data_put_u64( fp, tptr ); + } break; + default: snprintf( err, es, "unsupported runtime relocation" ); + goto out; + } + } + /* instantiate process-owned STACK/HEAP only after all startup linking succeeds */ + { + rph *sp = &mods[0].ph[mods[0].stacki]; + if( seg_alloc( MELF_MCPU_SEG_STACK, sp->memsz, sp->memsz, &dst ) ) + { + snprintf( err, es, "cannot allocate STACK" ); + goto out; + } + ld->stack_size = sp->memsz; + if( mods[0].heapi >= 0 ) + { + rph *hp = &mods[0].ph[mods[0].heapi]; + if( seg_alloc( MELF_MCPU_SEG_HEAP, hp->memsz, hp->maxsz, &dst ) ) + { + snprintf( err, es, "cannot allocate HEAP" ); + goto out; + } + } + } + if( put_startup( argc, argv, ld->stack_size, &ld->startup_base, err, es ) ) + goto out; + if( __cpu_program_protect() ) + { + snprintf( err, es, "cannot protect PROGRAM" ); + goto out; + } + ld->start_entry = MELF_MCPU_PTR64_OFFSET( mods[0].entry ); + ld->life_entry = MELF_MCPU_PTR64_OFFSET( mods[0].life ); + ld->has_init = mods[0].init != 0; + ld->init_entry = ld->has_init ? MELF_MCPU_PTR64_OFFSET( mods[0].init ) : 0; + ld->has_fini = mods[0].fini != 0; + ld->fini_entry = ld->has_fini ? MELF_MCPU_PTR64_OFFSET( mods[0].fini ) : 0; + ld->has_data_section = mods[0].has_data_section; + ld->data_section_begin = mods[0].data_section_begin; + ld->data_section_size = mods[0].data_section_size; + __cpu_pc = ld->start_entry; + __cpu_sp = ld->startup_base; + __cpu_fp = ld->stack_size; + rc = 0; +out: free( w ); +if( mods ) +{ + for( i = 0; i < nm; ++i ) free_mod( &mods[i] ); + free( mods ); +} +free_paths( paths, np ); +return( rc ); +} + + +int +cpu_dyn_load( const char *path, const char *preload_list, + int argc, char **argv, cpu_exec_loaded *ld, + char *err, size_t es ) +{ + return( cpu_dyn_load_internal(path, preload_list, 0, 0, 0, + argc, argv, ld, err, es) ); + +} /* End of cpu_dyn_load() */ + + +int +cpu_dyn_load_resolved( const char *path, const char *module_list, + size_t preload_count, + int argc, char **argv, cpu_exec_loaded *ld, + char *err, size_t es ) +{ + return( cpu_dyn_load_internal(path, module_list, 1, preload_count, 1, + argc, argv, ld, err, es) ); + +} /* End of cpu_dyn_load_resolved() */ diff --git a/utils/mcpu-dyn.h b/utils/mcpu-dyn.h new file mode 100644 index 0000000..1fc0aa2 --- /dev/null +++ b/utils/mcpu-dyn.h @@ -0,0 +1,66 @@ +#ifndef __CPU_DYN_H +#define __CPU_DYN_H + +#include "mcpu-asm.h" +#include "mcpu-exec.h" + + +typedef struct cpu_dyn_library_symbol +{ + char *name; + unsigned binding; + unsigned type; +} cpu_dyn_library_symbol; + +typedef struct cpu_dyn_library_info +{ + char *soname; + cpu_dyn_library_symbol *symbols; + size_t symbol_count; +} cpu_dyn_library_info; + +typedef struct cpu_dyn_metadata +{ + const char *soname; + const char *const *needed; + size_t needed_count; +} cpu_dyn_metadata; + +typedef struct cpu_dyn_dependency_info +{ + int abi; + char *soname; + char **needed; + size_t needed_count; +} cpu_dyn_dependency_info; + +int cpu_dyn_write( const char *path, const cpu_asm_image *image, + const cpu_exec_options *options, int shared, + char *err, size_t errsz); + +int cpu_dyn_write_abi3( const char *path, const cpu_asm_image *image, + const cpu_exec_options *options, int shared, + const cpu_dyn_metadata *metadata, + char *err, size_t errsz); + +int cpu_dyn_library_read( const char *path, cpu_dyn_library_info *info, + char *err, size_t errsz ); +void cpu_dyn_library_free( cpu_dyn_library_info *info ); +int cpu_dyn_library_find_symbol( const cpu_dyn_library_info *info, + const char *name, + const cpu_dyn_library_symbol **symbol ); + +int cpu_dyn_dependency_read( const char *path, int want_exec, + cpu_dyn_dependency_info *info, + char *err, size_t errsz ); +void cpu_dyn_dependency_free( cpu_dyn_dependency_info *info ); + +int cpu_dyn_load( const char *path, const char *preload_list, + int argc, char **argv, cpu_exec_loaded *loaded, + char *err, size_t errsz); +int cpu_dyn_load_resolved( const char *path, const char *module_list, + size_t preload_count, + int argc, char **argv, cpu_exec_loaded *loaded, + char *err, size_t errsz ); + +#endif diff --git a/utils/mcpu-exec.c b/utils/mcpu-exec.c new file mode 100644 index 0000000..09b5256 --- /dev/null +++ b/utils/mcpu-exec.c @@ -0,0 +1,1483 @@ +#include <errno.h> +#include <limits.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <libmcpu.h> +#include <mcpu-elf.h> + +#include "mcpu-exec.h" +#include "mcpu-melf.h" +#include "mcpu-dyn.h" + +typedef struct blob +{ + unsigned char *p; + size_t n; + size_t cap; +} blob; + +typedef struct xsec +{ + const char *name; + __mpu_uint32_t type; + __mpu_uint64_t flags; + __mpu_uint64_t addr; + __mpu_uint64_t off; + __mpu_uint64_t size; + __mpu_uint64_t align; + __mpu_uint64_t entsize; + __mpu_uint32_t link; + __mpu_uint32_t info; + __mpu_uint32_t nameoff; +} xsec; + +typedef struct xph +{ + __mpu_uint32_t type; + __mpu_uint32_t flags; + __mpu_uint64_t off; + __mpu_uint64_t vaddr; + __mpu_uint64_t paddr; + __mpu_uint64_t filesz; + __mpu_uint64_t memsz; + __mpu_uint64_t maxsz; + __mpu_uint64_t align; +} xph; + +typedef struct rsh +{ + __mpu_uint32_t name; + __mpu_uint32_t type; + __mpu_uint64_t flags; + __mpu_uint64_t addr; + __mpu_uint64_t off; + __mpu_uint64_t size; + __mpu_uint32_t link; + __mpu_uint32_t info; + __mpu_uint64_t align; + __mpu_uint64_t entsize; +} rsh; + +typedef xph rph; + +typedef struct exec_section_ranges +{ + int has_init; + int has_text; + int has_fini; + __mpu_uint64_t init_begin, init_end; + __mpu_uint64_t text_begin, text_end; + __mpu_uint64_t fini_begin, fini_end; +} exec_section_ranges; + +static int +grow( blob *b, size_t add ) +{ + size_t need, c; + unsigned char *q; + + if( add > SIZE_MAX - b->n ) + return( -1 ); + need = b->n + add; + c = b->cap ? b->cap : 64; + while( c < need ) + { + if( c > SIZE_MAX / 2 ) + return( -1 ); + c *= 2; + } + if( c == b->cap ) + return( 0 ); + q = (unsigned char *)realloc( b->p, c ); + if( !q ) + return( -1 ); + b->p = q; + b->cap = c; + return( 0 ); +} + +static int +bput( blob *b, const void *p, size_t n ) +{ + if( grow( b, n ) ) + return( -1 ); + if( n ) + memcpy( b->p + b->n, p, n ); + b->n += n; + return( 0 ); +} + +static int +bstr( blob *b, const char *s, __mpu_uint32_t *off ) +{ + size_t n = strlen( s ) + 1; + if( b->n > UINT32_MAX || n > UINT32_MAX - b->n ) + return( -1 ); + *off = (__mpu_uint32_t)b->n; + return( bput( b, s, n ) ); +} + +static __mpu_uint64_t +maxu( __mpu_uint64_t a, __mpu_uint64_t b ) +{ + return( a > b ? a : b ); +} + +static __mpu_uint64_t +nzalign( __mpu_uint64_t a ) +{ + return( a ? a : 1 ); +} + +static int +add_u64( __mpu_uint64_t a, __mpu_uint64_t b, __mpu_uint64_t *r ) +{ + if( a > UINT64_MAX - b ) + return( -1 ); + *r = a + b; + return( 0 ); +} + +static void +put_ehdr( unsigned char *p, int data, const cpu_asm_image *im, + __mpu_uint64_t phoff, __mpu_uint16_t phnum, + __mpu_uint64_t shoff, __mpu_uint16_t shnum, + __mpu_uint16_t shstr) +{ + memset( p, 0, MELF64_EHDR_SIZE ); + p[MELF_EI_MAG0] = MELFMAG0; + p[MELF_EI_MAG1] = MELFMAG1; + p[MELF_EI_MAG2] = MELFMAG2; + p[MELF_EI_MAG3] = MELFMAG3; + p[MELF_EI_CLASS] = MELF_CLASS_64; + p[MELF_EI_DATA] = (unsigned char)data; + p[MELF_EI_VERSION] = MELF_EV_CURRENT; + p[MELF_EI_OSABI] = MELF_OSABI_MCPU; + p[MELF_EI_ABIVERSION] = MELF_ABIVERSION_CURRENT; + + cpu_melf_put16( p + MELF64_EHDR_E_TYPE_OFF, MELF_ET_EXEC, data ); + cpu_melf_put16( p + MELF64_EHDR_E_MACHINE_OFF, MELF_EM_MCPU, data ); + cpu_melf_put32( p + MELF64_EHDR_E_VERSION_OFF, MELF_EV_CURRENT, data ); + cpu_melf_put64( p + MELF64_EHDR_E_ENTRY_OFF, + MELF_MCPU_PTR64( MELF_MCPU_SEG_PROGRAM, im->start_entry ), data); + cpu_melf_put64( p + MELF64_EHDR_E_INIT_ENTRY_OFF, + im->has_init ? MELF_MCPU_PTR64( MELF_MCPU_SEG_PROGRAM, im->init_entry ) : 0, + data); + cpu_melf_put64( p + MELF64_EHDR_E_LIFE_ENTRY_OFF, + MELF_MCPU_PTR64( MELF_MCPU_SEG_PROGRAM, im->life_entry ), data); + cpu_melf_put64( p + MELF64_EHDR_E_FINI_ENTRY_OFF, + im->has_fini ? MELF_MCPU_PTR64( MELF_MCPU_SEG_PROGRAM, im->fini_entry ) : 0, + data); + cpu_melf_put64( p + MELF64_EHDR_E_PHOFF_OFF, phoff, data ); + cpu_melf_put64( p + MELF64_EHDR_E_SHOFF_OFF, shoff, data ); + cpu_melf_put32( p + MELF64_EHDR_E_FLAGS_OFF, MELF_EF_MCPU_NONE, data ); + cpu_melf_put16( p + MELF64_EHDR_E_EHSIZE_OFF, MELF64_EHDR_SIZE, data ); + cpu_melf_put16( p + MELF64_EHDR_E_PHENTSIZE_OFF, MELF64_PHDR_SIZE, data ); + cpu_melf_put16( p + MELF64_EHDR_E_PHNUM_OFF, phnum, data ); + cpu_melf_put16( p + MELF64_EHDR_E_SHENTSIZE_OFF, MELF64_SHDR_SIZE, data ); + cpu_melf_put16( p + MELF64_EHDR_E_SHNUM_OFF, shnum, data ); + cpu_melf_put16( p + MELF64_EHDR_E_SHSTRNDX_OFF, shstr, data ); +} + +static void +put_phdr( unsigned char *p, const xph *x, int data ) +{ + memset( p, 0, MELF64_PHDR_SIZE ); + cpu_melf_put32( p + MELF64_PHDR_P_TYPE_OFF, x->type, data ); + cpu_melf_put32( p + MELF64_PHDR_P_FLAGS_OFF, x->flags, data ); + cpu_melf_put64( p + MELF64_PHDR_P_OFFSET_OFF, x->off, data ); + cpu_melf_put64( p + MELF64_PHDR_P_VADDR_OFF, x->vaddr, data ); + cpu_melf_put64( p + MELF64_PHDR_P_PADDR_OFF, x->paddr, data ); + cpu_melf_put64( p + MELF64_PHDR_P_FILESZ_OFF, x->filesz, data ); + cpu_melf_put64( p + MELF64_PHDR_P_MEMSZ_OFF, x->memsz, data ); + cpu_melf_put64( p + MELF64_PHDR_P_MAXSZ_OFF, x->maxsz, data ); + cpu_melf_put64( p + MELF64_PHDR_P_ALIGN_OFF, x->align, data ); +} + +static void +put_shdr( unsigned char *p, const xsec *x, int data ) +{ + memset( p, 0, MELF64_SHDR_SIZE ); + cpu_melf_put32( p + MELF64_SHDR_SH_NAME_OFF, x->nameoff, data ); + cpu_melf_put32( p + MELF64_SHDR_SH_TYPE_OFF, x->type, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_FLAGS_OFF, x->flags, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_ADDR_OFF, x->addr, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_OFFSET_OFF, x->off, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_SIZE_OFF, x->size, data ); + cpu_melf_put32( p + MELF64_SHDR_SH_LINK_OFF, x->link, data ); + cpu_melf_put32( p + MELF64_SHDR_SH_INFO_OFF, x->info, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_ADDRALIGN_OFF, x->align, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_ENTSIZE_OFF, x->entsize, data ); +} + +static void +get_phdr( const unsigned char *p, int data, rph *x ) +{ + x->type = cpu_melf_get32( p + MELF64_PHDR_P_TYPE_OFF, data ); + x->flags = cpu_melf_get32( p + MELF64_PHDR_P_FLAGS_OFF, data ); + x->off = cpu_melf_get64( p + MELF64_PHDR_P_OFFSET_OFF, data ); + x->vaddr = cpu_melf_get64( p + MELF64_PHDR_P_VADDR_OFF, data ); + x->paddr = cpu_melf_get64( p + MELF64_PHDR_P_PADDR_OFF, data ); + x->filesz = cpu_melf_get64( p + MELF64_PHDR_P_FILESZ_OFF, data ); + x->memsz = cpu_melf_get64( p + MELF64_PHDR_P_MEMSZ_OFF, data ); + x->maxsz = cpu_melf_get64( p + MELF64_PHDR_P_MAXSZ_OFF, data ); + x->align = cpu_melf_get64( p + MELF64_PHDR_P_ALIGN_OFF, data ); +} + +static void +get_shdr( const unsigned char *p, int data, rsh *x ) +{ + x->name = cpu_melf_get32( p + MELF64_SHDR_SH_NAME_OFF, data ); + x->type = cpu_melf_get32( p + MELF64_SHDR_SH_TYPE_OFF, data ); + x->flags = cpu_melf_get64( p + MELF64_SHDR_SH_FLAGS_OFF, data ); + x->addr = cpu_melf_get64( p + MELF64_SHDR_SH_ADDR_OFF, data ); + x->off = cpu_melf_get64( p + MELF64_SHDR_SH_OFFSET_OFF, data ); + x->size = cpu_melf_get64( p + MELF64_SHDR_SH_SIZE_OFF, data ); + x->link = cpu_melf_get32( p + MELF64_SHDR_SH_LINK_OFF, data ); + x->info = cpu_melf_get32( p + MELF64_SHDR_SH_INFO_OFF, data ); + x->align = cpu_melf_get64( p + MELF64_SHDR_SH_ADDRALIGN_OFF, data ); + x->entsize = cpu_melf_get64( p + MELF64_SHDR_SH_ENTSIZE_OFF, data ); +} + +static int +add_load( xph *ph, size_t *np, __mpu_uint64_t *cur, + unsigned seg, __mpu_uint64_t filesz, __mpu_uint64_t memsz, + __mpu_uint32_t flags, __mpu_uint64_t align, + char *err, size_t es) +{ + int ov = 0; + + align = nzalign( align ); + if( !cpu_melf_is_pow2( align ) ) + { + snprintf( err, es, "invalid load alignment" ); + return( -1 ); + } + if( filesz > memsz || memsz > CPU_PTR_OFFSET_MASK || filesz > SIZE_MAX ) + { + snprintf( err, es, "invalid load size" ); + return( -1 ); + } + + *cur = cpu_melf_align_up( *cur, align, &ov ); + if( ov ) + { + snprintf( err, es, "executable layout overflow" ); + return( -1 ); + } + + ph[*np] = ( xph ) + { + MELF_PT_LOAD, flags, *cur, MELF_MCPU_PTR64( seg, 0 ), 0, + filesz, memsz, memsz, align + }; + ++*np; + + if( add_u64( *cur, filesz, cur ) ) + { + snprintf( err, es, "executable layout overflow" ); + return( -1 ); + } + return( 0 ); +} + +static int +image_range( __mpu_uint64_t off, __mpu_uint64_t size, __mpu_uint64_t limit ) +{ + return( off <= limit && size <= limit - off ); +} + +int +cpu_exec_write( const char *path, const cpu_asm_image *im, + const cpu_exec_options *op, char *err, size_t es) +{ + xph ph[8]; + size_t np = 0; + xsec s[20]; + size_t ns = 1; + blob shstr = {0}; + int data = cpu_melf_data_encoding(), ov = 0; + __mpu_uint64_t phoff = MELF64_EHDR_SIZE, cur, shoff, total; + __mpu_uint64_t data_mem_size; + unsigned char *file = 0; + unsigned char tbuf[MELF_MCPU_TARGET_SIZE], lbuf[MELF_MCPU_LIFECYCLE_SIZE]; + MElf_McpuTarget mt; + MElf_McpuLifecycle ml; + __mpu_uint16_t shstridx, targetidx, lifeidx; + size_t i; + int phidx[8] = + { + -1, -1, -1, -1, -1, -1, -1, -1 + }; + int have_data; + __mpu_uint64_t palign; + + if( !path || !im || !op || !im->has_start || !im->has_life || !im->code_size ) + { + snprintf( err, es, "incomplete executable image" ); + return( -1 ); + } + if( im->start_entry >= im->code_size || im->life_entry >= im->code_size || + (im->has_init && im->init_entry >= im->code_size) || + (im->has_fini && im->fini_entry >= im->code_size)) + { + snprintf( err, es, "lifecycle entry is outside PROGRAM" ); + return( -1 ); + } + if( im->has_init && im->start_entry != im->init_entry ) + { + snprintf( err, es, "INIT entry must equal _start" ); + return( -1 ); + } + if( !im->has_init && im->start_entry != im->life_entry ) + { + snprintf( err, es, "without INIT, _start must equal _life" ); + return( -1 ); + } + if( !im->text_present || + !image_range( im->text_offset, im->text_size, im->code_size ) || + im->life_entry < im->text_offset || + im->life_entry - im->text_offset >= im->text_size) + { + snprintf( err, es, "_life entry must be inside .text" ); + return( -1 ); + } + if( im->has_init && + (!im->init_present || + !image_range( im->init_offset, im->init_size, im->code_size ) || + im->start_entry < im->init_offset || + im->start_entry - im->init_offset >= im->init_size)) + { + snprintf( err, es, "_start entry must be inside .init" ); + return( -1 ); + } + if( im->has_fini && + (!im->fini_present || + !image_range( im->fini_offset, im->fini_size, im->code_size ) || + im->fini_entry < im->fini_offset || + im->fini_entry - im->fini_offset >= im->fini_size)) + { + snprintf( err, es, "_fini entry must be inside .fini" ); + return( -1 ); + } + if( !op->stack_size || op->stack_size % MCPU_STARTUP_ALIGN || + op->stack_size > CPU_PTR_OFFSET_MASK || op->stack_size > SIZE_MAX) + { + snprintf( err, es, "STACK size must be a positive 16-byte-aligned PTR64 offset" ); + return( -1 ); + } + if( op->have_heap && + (op->heap_size < CPU_HEAP_BEGIN || op->heap_limit < op->heap_size || + op->heap_limit > CPU_PTR_OFFSET_MASK || op->heap_size > SIZE_MAX)) + { + snprintf( err, es, "invalid HEAP size/limit" ); + return( -1 ); + } + + data_mem_size = im->data_mem_size ? im->data_mem_size : im->data_size; + if( data_mem_size < im->data_size ) + { + snprintf( err, es, "DATA memory size is smaller than DATA file image" ); + return( -1 ); + } + have_data = data_mem_size || im->data_present || im->bss_present; + if( have_data && ( data_mem_size < CPU_DATA_NULL_GUARD_SIZE || + im->data_size < CPU_DATA_NULL_GUARD_SIZE)) + { + snprintf( err, es, "DATA image lacks the mandatory 16-byte NULL guard" ); + return( -1 ); + } + + palign = maxu( maxu( nzalign( im->init_align ), nzalign( im->text_align ) ), + nzalign( im->fini_align )); + + /* Count PHDRs first because file payload begins after the whole table. */ + np = 1 + (size_t)have_data + + (im->control_size || im->control_present) + + (im->input_size || im->input_present) + + (im->output_size || im->output_present) + + (im->trace_size || im->trace_present) + + 1 + (op->have_heap ? 1 : 0); + if( np > 8 || phoff > UINT64_MAX - np *MELF64_PHDR_SIZE ) + { + snprintf( err, es, "too many program headers" ); + return( -1 ); + } + cur = phoff + np * MELF64_PHDR_SIZE; + np = 0; + + phidx[MELF_MCPU_SEG_PROGRAM] = (int)np; + if( add_load( ph, &np, &cur, MELF_MCPU_SEG_PROGRAM, + im->code_size, im->code_size, MELF_PFLAGS_PROGRAM, + palign, err, es)) + goto fail; + + if( have_data ) + { + phidx[MELF_MCPU_SEG_DATA] = (int)np; + if( add_load( ph, &np, &cur, MELF_MCPU_SEG_DATA, + im->data_size, data_mem_size, MELF_PFLAGS_DATA, + maxu( 16, maxu( nzalign( im->data_align ), nzalign( im->bss_align ) ) ), + err, es)) + goto fail; + } + if( im->control_size || im->control_present ) + { + phidx[MELF_MCPU_SEG_CONTROL] = (int)np; + if( add_load( ph, &np, &cur, MELF_MCPU_SEG_CONTROL, + im->control_size, im->control_size, MELF_PFLAGS_CONTROL, + nzalign( im->control_align ), err, es)) + goto fail; + } + if( im->input_size || im->input_present ) + { + phidx[MELF_MCPU_SEG_INPUT] = (int)np; + if( add_load( ph, &np, &cur, MELF_MCPU_SEG_INPUT, + im->input_size, im->input_size, MELF_PFLAGS_INPUT, + nzalign( im->input_align ), err, es)) + goto fail; + } + if( im->output_size || im->output_present ) + { + phidx[MELF_MCPU_SEG_OUTPUT] = (int)np; + if( add_load( ph, &np, &cur, MELF_MCPU_SEG_OUTPUT, + im->output_size, im->output_size, MELF_PFLAGS_OUTPUT, + nzalign( im->output_align ), err, es)) + goto fail; + } + if( im->trace_size || im->trace_present ) + { + phidx[MELF_MCPU_SEG_TRACE] = (int)np; + if( add_load( ph, &np, &cur, MELF_MCPU_SEG_TRACE, + im->trace_size, im->trace_size, MELF_PFLAGS_TRACE, + nzalign( im->trace_align ), err, es)) + goto fail; + } + + phidx[MELF_MCPU_SEG_STACK] = (int)np; + ph[np++] = ( xph ) + { + MELF_PT_MCPU_STACK, MELF_PFLAGS_STACK, 0, MELF_MCPU_VADDR_STACK, 0, + 0, op->stack_size, op->stack_size, MCPU_STARTUP_ALIGN + }; + if( op->have_heap ) + { + phidx[MELF_MCPU_SEG_HEAP] = (int)np; + ph[np++] = ( xph ) + { + MELF_PT_MCPU_HEAP, MELF_PFLAGS_HEAP, 0, MELF_MCPU_VADDR_HEAP, 0, + 0, op->heap_size, op->heap_limit, 16 + }; + } + + memset( s, 0, sizeof( s ) ); +#define ADDSEC(N,T,F,A,O,Z,AL,E) do { \ + if(ns >= sizeof(s)/sizeof(s[0])) { snprintf(err,es,"too many sections"); goto fail; } \ + s[ns] = (xsec){(N),(T),(F),(A),(O),(Z),(AL),(E),0,0,0}; \ + ++ns; \ + } while(0) + + if( im->init_present ) + { + if( !image_range( im->init_offset, im->init_size, im->code_size ) ) + { + snprintf( err, es, ".init range outside PROGRAM" ); + goto fail; + } + ADDSEC( MELF_SEC_INIT, MELF_SHT_PROGBITS, MELF_SHFLAGS_INIT, + MELF_MCPU_PTR64( MELF_MCPU_SEG_PROGRAM, im->init_offset ), + ph[phidx[MELF_MCPU_SEG_PROGRAM]].off + im->init_offset, + im->init_size, nzalign( im->init_align ), 0); + } + + if( im->text_present ) + { + if( !image_range( im->text_offset, im->text_size, im->code_size ) ) + { + snprintf( err, es, ".text range outside PROGRAM" ); + goto fail; + } + ADDSEC( MELF_SEC_TEXT, MELF_SHT_PROGBITS, MELF_SHFLAGS_TEXT, + MELF_MCPU_PTR64( MELF_MCPU_SEG_PROGRAM, im->text_offset ), + ph[phidx[MELF_MCPU_SEG_PROGRAM]].off + im->text_offset, + im->text_size, nzalign( im->text_align ), 0); + } + + if( im->fini_present ) + { + if( !image_range( im->fini_offset, im->fini_size, im->code_size ) ) + { + snprintf( err, es, ".fini range outside PROGRAM" ); + goto fail; + } + ADDSEC( MELF_SEC_FINI, MELF_SHT_PROGBITS, MELF_SHFLAGS_FINI, + MELF_MCPU_PTR64( MELF_MCPU_SEG_PROGRAM, im->fini_offset ), + ph[phidx[MELF_MCPU_SEG_PROGRAM]].off + im->fini_offset, + im->fini_size, nzalign( im->fini_align ), 0); + } + + if( im->data_present ) + { + if( phidx[MELF_MCPU_SEG_DATA] < 0 || + !image_range( im->data_offset, im->data_section_size, im->data_size )) + { + snprintf( err, es, ".data range outside DATA file image" ); + goto fail; + } + ADDSEC( MELF_SEC_DATA, MELF_SHT_PROGBITS, MELF_SHFLAGS_DATA, + MELF_MCPU_PTR64( MELF_MCPU_SEG_DATA, im->data_offset ), + ph[phidx[MELF_MCPU_SEG_DATA]].off + im->data_offset, + im->data_section_size, nzalign( im->data_align ), 0); + } + + if( im->bss_present ) + { + if( phidx[MELF_MCPU_SEG_DATA] < 0 || + !image_range( im->bss_offset, im->bss_size, data_mem_size )) + { + snprintf( err, es, ".bss range outside DATA memory image" ); + goto fail; + } + ADDSEC( MELF_SEC_BSS, MELF_SHT_NOBITS, MELF_SHFLAGS_BSS, + MELF_MCPU_PTR64( MELF_MCPU_SEG_DATA, im->bss_offset ), + ph[phidx[MELF_MCPU_SEG_DATA]].off + im->bss_offset, + im->bss_size, nzalign( im->bss_align ), 0); + } + +#define ADD_DATA_LIKE(PRESENT,SEG,NAME,FLAGS,OFFSET,SIZE,ALIGN) do { \ + if((PRESENT)) { \ + if(phidx[(SEG)] < 0 || !image_range((OFFSET),(SIZE),ph[phidx[(SEG)]].memsz)) { \ + snprintf(err,es,"%s range outside segment",(NAME)); goto fail; \ + } \ + ADDSEC((NAME),MELF_SHT_PROGBITS,(FLAGS),MELF_MCPU_PTR64((SEG),(OFFSET)), \ + ph[phidx[(SEG)]].off+(OFFSET),(SIZE),nzalign((ALIGN)),0); \ + } \ + } while(0) + ADD_DATA_LIKE( im->control_present, MELF_MCPU_SEG_CONTROL, MELF_SEC_CONTROL, + MELF_SHFLAGS_CONTROL, im->control_offset, im->control_section_size, + im->control_align); + ADD_DATA_LIKE( im->input_present, MELF_MCPU_SEG_INPUT, MELF_SEC_INPUT, + MELF_SHFLAGS_INPUT, im->input_offset, im->input_section_size, + im->input_align); + ADD_DATA_LIKE( im->output_present, MELF_MCPU_SEG_OUTPUT, MELF_SEC_OUTPUT, + MELF_SHFLAGS_OUTPUT, im->output_offset, im->output_section_size, + im->output_align); + ADD_DATA_LIKE( im->trace_present, MELF_MCPU_SEG_TRACE, MELF_SEC_TRACE, + MELF_SHFLAGS_TRACE, im->trace_offset, im->trace_section_size, + im->trace_align); +#undef ADD_DATA_LIKE + + cpu_melf_fill_target( &mt ); + cpu_melf_encode_target( tbuf, &mt, data ); + cur = cpu_melf_align_up( cur, 8, &ov ); + if( ov ) goto overflow; + targetidx = (__mpu_uint16_t)ns; + ADDSEC( MELF_SEC_MCPU_TARGET, MELF_SHT_MCPU_TARGET, 0, 0, cur, + MELF_MCPU_TARGET_SIZE, 8, MELF_MCPU_TARGET_SIZE); + if( add_u64( cur, MELF_MCPU_TARGET_SIZE, &cur ) ) goto overflow; + + cpu_melf_fill_lifecycle( &ml ); + cpu_melf_encode_lifecycle( lbuf, &ml, data ); + cur = cpu_melf_align_up( cur, 8, &ov ); + if( ov ) goto overflow; + lifeidx = (__mpu_uint16_t)ns; + ADDSEC( MELF_SEC_MCPU_LIFECYCLE, MELF_SHT_MCPU_LIFECYCLE, 0, 0, cur, + MELF_MCPU_LIFECYCLE_SIZE, 8, MELF_MCPU_LIFECYCLE_SIZE); + if( add_u64( cur, MELF_MCPU_LIFECYCLE_SIZE, &cur ) ) goto overflow; + + if( bput( &shstr, "", 1 ) ) + { + snprintf( err, es, "out of memory" ); + goto fail; + } + + shstridx = (__mpu_uint16_t)ns; + ADDSEC( MELF_SEC_SHSTRTAB, MELF_SHT_STRTAB, 0, 0, 0, 0, 1, 0 ); + for( i = 1; i < ns; ++i ) + if( bstr( &shstr, s[i].name, &s[i].nameoff ) ) + { + snprintf( err, es, "section string table too large" ); + goto fail; + } + + s[shstridx].off = cur; + s[shstridx].size = shstr.n; + if( add_u64( cur, shstr.n, &cur ) ) goto overflow; +#undef ADDSEC + + shoff = cpu_melf_align_up( cur, 8, &ov ); + if( ov || ns > UINT64_MAX / MELF64_SHDR_SIZE || + shoff > UINT64_MAX - ns * MELF64_SHDR_SIZE) + goto overflow; + total = shoff + ns * MELF64_SHDR_SIZE; + if( total > SIZE_MAX ) + { + snprintf( err, es, "executable too large for this host" ); + goto fail; + } + + file = (unsigned char *)calloc( (size_t)total, 1 ); + if( !file ) + { + snprintf( err, es, "out of memory" ); + goto fail; + } + + put_ehdr( file, data, im, phoff, (__mpu_uint16_t)np, + shoff, (__mpu_uint16_t)ns, shstridx); + for( i = 0; i < np; ++i ) + put_phdr( file + (size_t)phoff + i *MELF64_PHDR_SIZE, &ph[i], data ); + + for( i = 0; i < np; ++i ) + if( ph[i].type == MELF_PT_LOAD && ph[i].filesz ) + { + const unsigned char *payload = 0; + unsigned seg = MELF_MCPU_PTR64_SEG( ph[i].vaddr ); + switch( seg ) + { + case MELF_MCPU_SEG_PROGRAM: payload = im->code; + break; + case MELF_MCPU_SEG_DATA: payload = im->data; + break; + case MELF_MCPU_SEG_CONTROL: payload = im->control; + break; + case MELF_MCPU_SEG_INPUT: payload = im->input; + break; + case MELF_MCPU_SEG_OUTPUT: payload = im->output; + break; + case MELF_MCPU_SEG_TRACE: payload = im->trace; + break; + default: break; + } + if( !payload ) + { + snprintf( err, es, "missing PT_LOAD payload" ); + goto fail; + } + memcpy( file + (size_t)ph[i].off, payload, (size_t)ph[i].filesz ); + } + + memcpy( file + (size_t)s[targetidx].off, tbuf, MELF_MCPU_TARGET_SIZE ); + memcpy( file + (size_t)s[lifeidx].off, lbuf, MELF_MCPU_LIFECYCLE_SIZE ); + memcpy( file + (size_t)s[shstridx].off, shstr.p, shstr.n ); + for( i = 0; i < ns; ++i ) + put_shdr( file + (size_t)shoff + i *MELF64_SHDR_SIZE, &s[i], data ); + + if( cpu_melf_writeall( path, file, (size_t)total, err, es ) ) + goto fail; + + free( file ); + free( shstr.p ); + return( 0 ); + +overflow: + snprintf( err, es, "executable layout overflow" ); +fail: + free( file ); + free( shstr.p ); + return( -1 ); +} + +static int +validate_ident( const unsigned char *f, size_t fn, int *data, + char *err, size_t es) +{ + if( fn < MELF64_EHDR_SIZE || + f[MELF_EI_MAG0] != MELFMAG0 || f[MELF_EI_MAG1] != MELFMAG1 || + f[MELF_EI_MAG2] != MELFMAG2 || f[MELF_EI_MAG3] != MELFMAG3) + { + snprintf( err, es, "not a MELF executable" ); + return( -1 ); + } + if( f[MELF_EI_CLASS] != MELF_CLASS_64 ) + { + snprintf( err, es, "unsupported MELF class" ); + return( -1 ); + } + *data = f[MELF_EI_DATA]; + if( *data != MELF_DATA_LSB && *data != MELF_DATA_MSB ) + { + snprintf( err, es, "unsupported EI_DATA" ); + return( -1 ); + } + if( f[MELF_EI_VERSION] != MELF_EV_CURRENT || + f[MELF_EI_OSABI] != MELF_OSABI_MCPU || + f[MELF_EI_ABIVERSION] != MELF_ABIVERSION_CURRENT || + f[9] || f[10] || f[11] || f[12] || f[13] || f[14] || f[15] || + cpu_melf_get16( f + MELF64_EHDR_E_TYPE_OFF, *data ) != MELF_ET_EXEC || + cpu_melf_get16( f + MELF64_EHDR_E_MACHINE_OFF, *data ) != MELF_EM_MCPU || + cpu_melf_get32( f + MELF64_EHDR_E_VERSION_OFF, *data ) != MELF_EV_CURRENT || + cpu_melf_get32( f + MELF64_EHDR_E_FLAGS_OFF, *data ) != MELF_EF_MCPU_NONE || + cpu_melf_get16( f + MELF64_EHDR_E_EHSIZE_OFF, *data ) != MELF64_EHDR_SIZE || + cpu_melf_get16( f + MELF64_EHDR_E_PHENTSIZE_OFF, *data ) != MELF64_PHDR_SIZE || + cpu_melf_get16( f + MELF64_EHDR_E_SHENTSIZE_OFF, *data ) != MELF64_SHDR_SIZE) + { + snprintf( err, es, "unsupported MELF executable header" ); + return( -1 ); + } + return( 0 ); +} + +static int +seg_alloc( unsigned seg, __mpu_uint64_t n, __mpu_uint64_t max, __mpu_UNIT_t **dst ) +{ + int r = -1; + *dst = 0; + switch( seg ) + { + case MELF_MCPU_SEG_PROGRAM: r = __cpu_program_alloc( n ); + *dst = __cpu_program_ptr(); + break; + case MELF_MCPU_SEG_DATA: r = __cpu_data_alloc( n ); + *dst = __cpu_data_ptr(); + break; + case MELF_MCPU_SEG_CONTROL: r = __cpu_control_alloc( n ); + *dst = __cpu_control_ptr(); + break; + case MELF_MCPU_SEG_INPUT: r = __cpu_input_alloc( n ); + *dst = __cpu_input_ptr(); + break; + case MELF_MCPU_SEG_OUTPUT: r = __cpu_output_alloc( n ); + *dst = __cpu_output_ptr(); + break; + case MELF_MCPU_SEG_TRACE: r = __cpu_trace_alloc( n ); + *dst = __cpu_trace_ptr(); + break; + case MELF_MCPU_SEG_STACK: r = __cpu_stack_alloc( n ); + *dst = __cpu_stack_ptr(); + break; + case MELF_MCPU_SEG_HEAP: r = __cpu_heap_alloc( n, max ); + *dst = __cpu_heap_ptr(); + break; + default: break; + } + return( r ); +} + +static int +expected_pflags( unsigned seg ) +{ + switch( seg ) + { + case MELF_MCPU_SEG_PROGRAM: return( MELF_PFLAGS_PROGRAM ); + case MELF_MCPU_SEG_DATA: return( MELF_PFLAGS_DATA ); + case MELF_MCPU_SEG_CONTROL: return( MELF_PFLAGS_CONTROL ); + case MELF_MCPU_SEG_INPUT: return( MELF_PFLAGS_INPUT ); + case MELF_MCPU_SEG_OUTPUT: return( MELF_PFLAGS_OUTPUT ); + case MELF_MCPU_SEG_TRACE: return( MELF_PFLAGS_TRACE ); + case MELF_MCPU_SEG_STACK: return( MELF_PFLAGS_STACK ); + case MELF_MCPU_SEG_HEAP: return( MELF_PFLAGS_HEAP ); + default: return( -1 ); + } +} + +static __mpu_uint64_t +expected_shflags( unsigned seg ) +{ + switch( seg ) + { + case MELF_MCPU_SEG_PROGRAM: return( MELF_SHFLAGS_TEXT ); + case MELF_MCPU_SEG_DATA: return( MELF_SHFLAGS_DATA ); + case MELF_MCPU_SEG_CONTROL: return( MELF_SHFLAGS_CONTROL ); + case MELF_MCPU_SEG_INPUT: return( MELF_SHFLAGS_INPUT ); + case MELF_MCPU_SEG_OUTPUT: return( MELF_SHFLAGS_OUTPUT ); + case MELF_MCPU_SEG_TRACE: return( MELF_SHFLAGS_TRACE ); + default: return( 0 ); + } +} + +static int +image_backed_seg( unsigned seg ) +{ + return seg == MELF_MCPU_SEG_PROGRAM || seg == MELF_MCPU_SEG_DATA || + seg == MELF_MCPU_SEG_CONTROL || seg == MELF_MCPU_SEG_INPUT || + seg == MELF_MCPU_SEG_OUTPUT || seg == MELF_MCPU_SEG_TRACE; +} + +static int +name_at( const unsigned char *tab, size_t n, __mpu_uint32_t off, const char **name ) +{ + if( off >= n ) + return( -1 ); + *name = (const char *)tab + off; + return( memchr( *name, 0, n - off ) ? 0 : -1 ); +} + +static int +range_contains( __mpu_uint64_t begin, __mpu_uint64_t end, __mpu_uint64_t value ) +{ + return( value >= begin && value < end ); +} + +static int +record_lifecycle_section( exec_section_ranges *r, const char *name, + __mpu_uint64_t begin, __mpu_uint64_t size, + char *err, size_t es) +{ + __mpu_uint64_t end; + if( add_u64( begin, size, &end ) ) + { + snprintf( err, es, "section address overflow" ); + return( -1 ); + } + if( !strcmp( name, MELF_SEC_INIT ) ) + { + if( r->has_init++ ) + { + snprintf( err, es, "duplicate .init section" ); + return( -1 ); + } + r->init_begin = begin; + r->init_end = end; + } + else if( !strcmp( name, MELF_SEC_TEXT ) ) + { + if( r->has_text++ ) + { + snprintf( err, es, "duplicate .text section" ); + return( -1 ); + } + r->text_begin = begin; + r->text_end = end; + } + else if( !strcmp( name, MELF_SEC_FINI ) ) + { + if( r->has_fini++ ) + { + snprintf( err, es, "duplicate .fini section" ); + return( -1 ); + } + r->fini_begin = begin; + r->fini_end = end; + } + return( 0 ); +} + +static int +put_startup( int argc, char **argv, __mpu_uint64_t stack_size, + __mpu_uint64_t *base, char *err, size_t es) +{ + __mpu_uint64_t count, arr, strings = 0, raw, b; + size_t i; + __mpu_UNIT_t *sp = __cpu_stack_ptr(); + + if( argc < 0 || ( argc && !argv ) ) + { + snprintf( err, es, "bad argc/argv" ); + return( -1 ); + } + count = (__mpu_uint64_t)(unsigned)argc; + if( count == UINT64_MAX || count + 1 > UINT64_MAX / MCPU_STARTUP_ARGV_SIZE ) + { + snprintf( err, es, "startup argv overflow" ); + return( -1 ); + } + arr = (count + 1) * MCPU_STARTUP_ARGV_SIZE; + + for( i = 0; i < (size_t)argc; ++i ) + { + size_t sl = strlen( argv[i] ); + __mpu_uint64_t z = (__mpu_uint64_t)sl + 1; + if( z == 0 || strings > UINT64_MAX - z ) + { + snprintf( err, es, "startup strings overflow" ); + return( -1 ); + } + strings += z; + } + if( MCPU_STARTUP_SIZE > UINT64_MAX - arr || + MCPU_STARTUP_SIZE + arr > UINT64_MAX - strings) + { + snprintf( err, es, "startup size overflow" ); + return( -1 ); + } + raw = MCPU_STARTUP_SIZE + arr + strings; + if( raw > stack_size ) + { + snprintf( err, es, "command line does not fit executable STACK" ); + return( -1 ); + } + + b = (stack_size - raw) & ~((__mpu_uint64_t)MCPU_STARTUP_ALIGN - 1); + if( b > stack_size || raw > stack_size - b ) + { + snprintf( err, es, "command line does not fit aligned executable STACK" ); + return( -1 ); + } + + memset( sp + (size_t)b, 0, (size_t)( stack_size - b ) ); + __cpu_data_put_u32( sp + (size_t)b + MCPU_STARTUP_VERSION_OFF, + MCPU_STARTUP_VERSION_CURRENT); + __cpu_data_put_u32( sp + (size_t)b + MCPU_STARTUP_SIZE_OFF, + MCPU_STARTUP_SIZE); + __cpu_data_put_u64( sp + (size_t)b + MCPU_STARTUP_ARGC_OFF, count ); + __cpu_data_put_u64( sp + (size_t)b + MCPU_STARTUP_ARGV_OFF, + MELF_MCPU_PTR64( MELF_MCPU_SEG_STACK, + b + MCPU_STARTUP_SIZE)); + __cpu_data_put_u64( sp + (size_t)b + MCPU_STARTUP_RESERVED_OFF, 0 ); + + { + __mpu_uint64_t pos = b + MCPU_STARTUP_SIZE + arr; + for( i = 0; i < (size_t)argc; ++i ) + { + size_t z = strlen( argv[i] ) + 1; + __cpu_data_put_u64( sp + (size_t)( b + MCPU_STARTUP_SIZE + i * 8 ), + MELF_MCPU_PTR64( MELF_MCPU_SEG_STACK, pos )); + memcpy( sp + (size_t)pos, argv[i], z ); + pos += z; + } + __cpu_data_put_u64( sp + (size_t)( b + MCPU_STARTUP_SIZE + (size_t)argc * 8 ), 0 ); + } + + *base = b; + return( 0 ); +} + +static int +cpu_exec_load_v1( const char *path, int argc, char **argv, cpu_exec_loaded *ld, + char *err, size_t es) +{ + unsigned char *f = 0; + size_t fn = 0; + int data, rc = -1; + __mpu_uint64_t phoff, shoff, entry, init, life, fini; + __mpu_uint16_t phnum, shnum, shstrndx; + size_t i; + rph ph[8]; + int seen[8] = {0}; + int stacki = -1, heapi = -1, progi = -1, datai = -1; + rsh *sh = 0; + int nt = 0, nl = 0; + MElf_McpuTarget mt; + MElf_McpuLifecycle ml; + const unsigned char *shstr; + size_t shstrn; + exec_section_ranges ranges = {0}; + int data_section_seen = 0; + __mpu_uint64_t data_section_begin = 0, data_section_size = 0; + + if( !path || !ld ) + return( -1 ); + memset( ld, 0, sizeof( *ld ) ); + + if( cpu_melf_readall( path, &f, &fn, err, es ) ) + return( -1 ); + if( validate_ident( f, fn, &data, err, es ) ) + goto out; + + phoff = cpu_melf_get64( f + MELF64_EHDR_E_PHOFF_OFF, data ); + shoff = cpu_melf_get64( f + MELF64_EHDR_E_SHOFF_OFF, data ); + phnum = cpu_melf_get16( f + MELF64_EHDR_E_PHNUM_OFF, data ); + shnum = cpu_melf_get16( f + MELF64_EHDR_E_SHNUM_OFF, data ); + shstrndx = cpu_melf_get16( f + MELF64_EHDR_E_SHSTRNDX_OFF, data ); + entry = cpu_melf_get64( f + MELF64_EHDR_E_ENTRY_OFF, data ); + init = cpu_melf_get64( f + MELF64_EHDR_E_INIT_ENTRY_OFF, data ); + life = cpu_melf_get64( f + MELF64_EHDR_E_LIFE_ENTRY_OFF, data ); + fini = cpu_melf_get64( f + MELF64_EHDR_E_FINI_ENTRY_OFF, data ); + + if( !phnum || phnum > 8 || + !cpu_melf_span( fn, phoff, (__mpu_uint64_t)phnum *MELF64_PHDR_SIZE ) || + !shnum || shstrndx == MELF_SHN_UNDEF || shstrndx >= shnum || + !cpu_melf_span( fn, shoff, (__mpu_uint64_t)shnum *MELF64_SHDR_SIZE )) + { + snprintf( err, es, "bad MELF table ranges" ); + goto out; + } + + sh = ( rsh * ) calloc( shnum, sizeof( *sh ) ); + if( !sh ) + { + snprintf( err, es, "out of memory" ); + goto out; + } + for( i = 0; i < shnum; ++i ) + get_shdr( f + (size_t)shoff + i *MELF64_SHDR_SIZE, data, &sh[i] ); + + /* Section zero is the all-zero null entry. */ + if( sh[0].name || sh[0].type || sh[0].flags || sh[0].addr || sh[0].off || + sh[0].size || sh[0].link || sh[0].info || sh[0].align || sh[0].entsize) + { + snprintf( err, es, "section zero is not MELF_SHT_NULL/all-zero" ); + goto out; + } + + if( sh[shstrndx].type != MELF_SHT_STRTAB || sh[shstrndx].flags || + sh[shstrndx].addr || sh[shstrndx].link || sh[shstrndx].info || + sh[shstrndx].align != 1 || sh[shstrndx].entsize || + sh[shstrndx].size > SIZE_MAX || + !cpu_melf_span( fn, sh[shstrndx].off, sh[shstrndx].size )) + { + snprintf( err, es, "bad .shstrtab" ); + goto out; + } + shstr = f + (size_t)sh[shstrndx].off; + shstrn = (size_t)sh[shstrndx].size; + if( !shstrn || shstr[0] != 0 ) + { + snprintf( err, es, "bad .shstrtab contents" ); + goto out; + } + { + const char *shstrname; + if( name_at( shstr, shstrn, sh[shstrndx].name, &shstrname ) || + strcmp( shstrname, MELF_SEC_SHSTRTAB )) + { + snprintf( err, es, "section-name table is not canonical .shstrtab" ); + goto out; + } + } + + for( i = 1; i < shnum; ++i ) + { + const char *name; + if( name_at( shstr, shstrn, sh[i].name, &name ) ) + { + snprintf( err, es, "bad section name offset" ); + goto out; + } + + if( sh[i].type == MELF_SHT_RELA ) + { + snprintf( err, es, "ET_EXEC must not contain SHT_RELA sections" ); + goto out; + } + + if( sh[i].type == MELF_SHT_MCPU_TARGET ) + { + if( nt++ || strcmp( name, MELF_SEC_MCPU_TARGET ) || sh[i].flags || sh[i].addr || + sh[i].size != MELF_MCPU_TARGET_SIZE || sh[i].link || sh[i].info || + sh[i].align != 8 || sh[i].entsize != MELF_MCPU_TARGET_SIZE || + !cpu_melf_span( fn, sh[i].off, sh[i].size ) || + cpu_melf_decode_target( f + (size_t)sh[i].off, &mt, data ) || + cpu_melf_validate_target( &mt, data, err, es )) + goto out; + } + else if( sh[i].type == MELF_SHT_MCPU_LIFECYCLE ) + { + if( nl++ || strcmp( name, MELF_SEC_MCPU_LIFECYCLE ) || sh[i].flags || sh[i].addr || + sh[i].size != MELF_MCPU_LIFECYCLE_SIZE || sh[i].link || sh[i].info || + sh[i].align != 8 || sh[i].entsize != MELF_MCPU_LIFECYCLE_SIZE || + !cpu_melf_span( fn, sh[i].off, sh[i].size ) || + cpu_melf_decode_lifecycle( f + (size_t)sh[i].off, &ml, data ) || + cpu_melf_validate_lifecycle( &ml, err, es )) + goto out; + } + } + if( nt != 1 || nl != 1 ) + { + snprintf( err, es, + "MELF executable requires exactly one target and lifecycle metadata section"); + goto out; + } + + /* Validate Program Headers without allocating guest memory. */ + for( i = 0; i < phnum; ++i ) + { + unsigned seg; + get_phdr( f + (size_t)phoff + i *MELF64_PHDR_SIZE, data, &ph[i] ); + + if( ph[i].paddr ) + { + snprintf( err, es, "p_paddr must be zero" ); + goto out; + } + if( ph[i].align > 1 && !cpu_melf_is_pow2( ph[i].align ) ) + { + snprintf( err, es, "bad p_align" ); + goto out; + } + + if( ph[i].type == MELF_PT_LOAD ) + { + seg = MELF_MCPU_PTR64_SEG( ph[i].vaddr ); + if( !image_backed_seg( seg ) || MELF_MCPU_PTR64_OFFSET( ph[i].vaddr ) != 0 || + seen[seg]++ || ph[i].flags != (unsigned)expected_pflags( seg ) || + ph[i].filesz > ph[i].memsz || ph[i].maxsz != ph[i].memsz || + !cpu_melf_span( fn, ph[i].off, ph[i].filesz ) || + ph[i].memsz > CPU_PTR_OFFSET_MASK || ph[i].memsz > SIZE_MAX || + (ph[i].align > 1 && ph[i].off % ph[i].align != 0)) + { + snprintf( err, es, "invalid PT_LOAD descriptor" ); + goto out; + } + if( seg == MELF_MCPU_SEG_PROGRAM ) + progi = (int)i; + if( seg == MELF_MCPU_SEG_DATA ) + datai = (int)i; + } + else if( ph[i].type == MELF_PT_MCPU_STACK ) + { + seg = MELF_MCPU_SEG_STACK; + if( seen[seg]++ || ph[i].vaddr != MELF_MCPU_VADDR_STACK || + ph[i].flags != MELF_PFLAGS_STACK || ph[i].off || ph[i].filesz || + !ph[i].memsz || ph[i].maxsz != ph[i].memsz || + ph[i].memsz % MCPU_STARTUP_ALIGN || ph[i].align < MCPU_STARTUP_ALIGN || + !cpu_melf_is_pow2( ph[i].align ) || + ph[i].memsz > CPU_PTR_OFFSET_MASK || ph[i].memsz > SIZE_MAX) + { + snprintf( err, es, "invalid PT_MCPU_STACK descriptor" ); + goto out; + } + stacki = (int)i; + } + else if( ph[i].type == MELF_PT_MCPU_HEAP ) + { + seg = MELF_MCPU_SEG_HEAP; + if( seen[seg]++ || ph[i].vaddr != MELF_MCPU_VADDR_HEAP || + ph[i].flags != MELF_PFLAGS_HEAP || ph[i].off || ph[i].filesz || + ph[i].memsz < CPU_HEAP_BEGIN || ph[i].maxsz < ph[i].memsz || + ph[i].align < 16 || !cpu_melf_is_pow2( ph[i].align ) || + ph[i].maxsz > CPU_PTR_OFFSET_MASK || ph[i].memsz > SIZE_MAX) + { + snprintf( err, es, "invalid PT_MCPU_HEAP descriptor" ); + goto out; + } + heapi = (int)i; + } + else if( ph[i].type != MELF_PT_NULL ) + { + snprintf( err, es, "unsupported program-header type" ); + goto out; + } + } + + if( progi < 0 || stacki < 0 ) + { + snprintf( err, es, "MELF executable requires PROGRAM and STACK descriptors" ); + goto out; + } + + if( datai >= 0 ) + { + if( ph[datai].memsz < CPU_DATA_NULL_GUARD_SIZE || + ph[datai].filesz < CPU_DATA_NULL_GUARD_SIZE) + { + snprintf( err, es, "DATA segment is too small for NULL guard" ); + goto out; + } + for( i = 0; i < CPU_DATA_NULL_GUARD_SIZE; ++i ) + if( f[(size_t)ph[datai].off + i] ) + { + snprintf( err, es, "DATA NULL guard is not zero" ); + goto out; + } + } + + /* Section-to-segment validation. */ + for( i = 1; i < shnum; ++i ) + { + const char *name; + if( name_at( shstr, shstrn, sh[i].name, &name ) ) + { + snprintf( err, es, "bad section name offset" ); + goto out; + } + + if( sh[i].flags & MELF_SHF_ALLOC ) + { + unsigned seg, flagseg; + size_t k; + int found = 0; + __mpu_uint64_t off; + + if( sh[i].type != MELF_SHT_PROGBITS && sh[i].type != MELF_SHT_NOBITS ) + { + snprintf( err, es, "allocated section has unsupported type: %s", name ); + goto out; + } + if( ( sh[i].flags & MELF_SHF_MCPU_SEG_VALID ) == 0 || + !sh[i].align || !cpu_melf_is_pow2( sh[i].align )) + { + snprintf( err, es, "invalid allocated section attributes: %s", name ); + goto out; + } + seg = MELF_MCPU_PTR64_SEG( sh[i].addr ); + flagseg = MELF_SHF_MCPU_GET_SEG( sh[i].flags ); + if( !image_backed_seg( seg ) || flagseg != seg || + sh[i].flags != expected_shflags( seg )) + { + snprintf( err, es, "allocated section segment/flags disagree: %s", name ); + goto out; + } + if( sh[i].type == MELF_SHT_NOBITS && + ( seg != MELF_MCPU_SEG_DATA || strcmp( name, MELF_SEC_BSS ) )) + { + snprintf( err, es, "ABI-v1 SHT_NOBITS is only canonical .bss in DATA" ); + goto out; + } + + off = MELF_MCPU_PTR64_OFFSET( sh[i].addr ); + for( k = 0; k < phnum; ++k ) + if( ph[k].type == MELF_PT_LOAD && + MELF_MCPU_PTR64_SEG( ph[k].vaddr ) == seg) + { + if( off > ph[k].memsz || sh[i].size > ph[k].memsz - off || + sh[i].align > nzalign( ph[k].align )) + { + snprintf( err, es, "allocated section outside/incompatible with PT_LOAD" ); + goto out; + } + if( sh[i].type == MELF_SHT_PROGBITS ) + { + if( sh[i].off < ph[k].off || off > ph[k].filesz || + sh[i].off - ph[k].off != off || + sh[i].size > ph[k].filesz - off || + !cpu_melf_span( fn, sh[i].off, sh[i].size )) + { + snprintf( err, es, "section/file mapping disagrees with PT_LOAD" ); + goto out; + } + } + found = 1; + break; + } + if( !found ) + { + snprintf( err, es, "allocated section has no PT_LOAD" ); + goto out; + } + + if( seg == MELF_MCPU_SEG_DATA && !strcmp( name, MELF_SEC_DATA ) ) + { + if( sh[i].type != MELF_SHT_PROGBITS || data_section_seen++ ) + { + snprintf( err, es, "invalid or duplicate .data section" ); + goto out; + } + data_section_begin = off; + data_section_size = sh[i].size; + } + + if( seg == MELF_MCPU_SEG_PROGRAM && + record_lifecycle_section( &ranges, name, off, sh[i].size, err, es )) + goto out; + } + else if( sh[i].type == MELF_SHT_MCPU_TARGET || + sh[i].type == MELF_SHT_MCPU_LIFECYCLE) + { + if( sh[i].addr ) + { + snprintf( err, es, "metadata section must not have guest address" ); + goto out; + } + } + } + + /* Lifecycle entries are numeric Ehdr authority; sections validate their role. */ + if( !entry || !life || + MELF_MCPU_PTR64_SEG( entry ) != MELF_MCPU_SEG_PROGRAM || + MELF_MCPU_PTR64_SEG( life ) != MELF_MCPU_SEG_PROGRAM || + MELF_MCPU_PTR64_OFFSET( entry ) >= ph[progi].memsz || + MELF_MCPU_PTR64_OFFSET( life ) >= ph[progi].memsz) + { + snprintf( err, es, "invalid mandatory lifecycle entry" ); + goto out; + } + if( !ranges.has_text || + !range_contains( ranges.text_begin, ranges.text_end, + MELF_MCPU_PTR64_OFFSET( life ))) + { + snprintf( err, es, "e_life_entry is not inside .text" ); + goto out; + } + + if( init ) + { + if( MELF_MCPU_PTR64_SEG( init ) != MELF_MCPU_SEG_PROGRAM || + MELF_MCPU_PTR64_OFFSET( init ) >= ph[progi].memsz || init != entry || + !ranges.has_init || + !range_contains( ranges.init_begin, ranges.init_end, + MELF_MCPU_PTR64_OFFSET( entry ))) + { + snprintf( err, es, "invalid INIT entry" ); + goto out; + } + } + else + { + if( entry != life || ranges.has_init ) + { + snprintf( err, es, "without INIT, e_entry must equal e_life_entry and .init must be absent" ); + goto out; + } + } + + if( fini ) + { + if( MELF_MCPU_PTR64_SEG( fini ) != MELF_MCPU_SEG_PROGRAM || + MELF_MCPU_PTR64_OFFSET( fini ) >= ph[progi].memsz || + !ranges.has_fini || + !range_contains( ranges.fini_begin, ranges.fini_end, + MELF_MCPU_PTR64_OFFSET( fini ))) + { + snprintf( err, es, "invalid FINI entry" ); + goto out; + } + } + else if( ranges.has_fini ) + { + snprintf( err, es, ".fini exists but e_fini_entry is zero" ); + goto out; + } + + /* Validation complete: only now instantiate guest memory. */ + for( i = 0; i < phnum; ++i ) + if( ph[i].type == MELF_PT_LOAD ) + { + unsigned seg = MELF_MCPU_PTR64_SEG( ph[i].vaddr ); + __mpu_UNIT_t *dst; + if( seg_alloc( seg, ph[i].memsz, ph[i].maxsz, &dst ) ) + { + snprintf( err, es, "cannot allocate guest segment %u", seg ); + goto out; + } + if( ph[i].memsz ) + memset( dst, 0, (size_t)ph[i].memsz ); + if( ph[i].filesz ) + memcpy( dst, f + (size_t)ph[i].off, (size_t)ph[i].filesz ); + } + + { + __mpu_UNIT_t *dst; + if( seg_alloc( MELF_MCPU_SEG_STACK, ph[stacki].memsz, ph[stacki].memsz, &dst ) ) + { + snprintf( err, es, "cannot allocate STACK" ); + goto out; + } + (void)dst; + if( heapi >= 0 && + seg_alloc( MELF_MCPU_SEG_HEAP, ph[heapi].memsz, ph[heapi].maxsz, &dst )) + { + snprintf( err, es, "cannot allocate HEAP" ); + goto out; + } + } + + if( put_startup( argc, argv, ph[stacki].memsz, &ld->startup_base, err, es ) ) + goto out; + if( __cpu_program_protect() ) + { + snprintf( err, es, "cannot protect PROGRAM" ); + goto out; + } + + ld->stack_size = ph[stacki].memsz; + ld->has_data_section = data_section_seen; + ld->data_section_begin = data_section_begin; + ld->data_section_size = data_section_size; + ld->start_entry = MELF_MCPU_PTR64_OFFSET( entry ); + ld->life_entry = MELF_MCPU_PTR64_OFFSET( life ); + ld->has_init = init != 0; + ld->init_entry = init ? MELF_MCPU_PTR64_OFFSET( init ) : 0; + ld->has_fini = fini != 0; + ld->fini_entry = fini ? MELF_MCPU_PTR64_OFFSET( fini ) : 0; + + __cpu_pc = ld->start_entry; + __cpu_sp = ld->startup_base; + __cpu_fp = ld->stack_size; + rc = 0; + +out: + free( f ); + free( sh ); + return( rc ); +} + + +int +cpu_exec_abi_version( const char *path, unsigned *abi, char *err, size_t es ) +{ + FILE *fp; + unsigned char h[MELF_EI_NIDENT]; + size_t n; + + fp = fopen( path, "rb" ); + if( !fp ) + { + snprintf( err, es, "%s: %s", path, strerror( errno ) ); + return( -1 ); + } + n = fread( h, 1, sizeof( h ), fp ); + if( fclose( fp ) != 0 && n == sizeof( h ) ) + { + snprintf( err, es, "%s: %s", path, strerror( errno ) ); + return( -1 ); + } + if( n != sizeof( h ) || h[MELF_EI_MAG0] != MELFMAG0 || + h[MELF_EI_MAG1] != MELFMAG1 || h[MELF_EI_MAG2] != MELFMAG2 || + h[MELF_EI_MAG3] != MELFMAG3) + { + snprintf( err, es, "%s: not a MELF image", path ); + return( -1 ); + } + *abi = h[MELF_EI_ABIVERSION]; + return( 0 ); +} + +int +cpu_exec_load_preload( const char *path, const char *preload_list, + int argc, char **argv, cpu_exec_loaded *ld, + char *err, size_t es) +{ + unsigned abi; + if( cpu_exec_abi_version( path, &abi, err, es ) ) + return( -1 ); + if( abi == MELF_ABIVERSION_MCPU_1 ) + { + if( preload_list && *preload_list ) + { + snprintf( err, es, + "--ldpreload requires an ABI-v2/ABI-v3 executable linked with mcpu-ld --dynamic"); + return( -1 ); + } + return( cpu_exec_load_v1( path, argc, argv, ld, err, es ) ); + } + if( abi == MELF_ABIVERSION_MCPU_2 || + abi == MELF_ABIVERSION_MCPU_3 ) + return( cpu_dyn_load( path, preload_list, argc, argv, ld, err, es ) ); + snprintf( err, es, "unsupported MELF ABI version %u", abi ); + return( -1 ); +} + + +int +cpu_exec_load_resolved( const char *path, const char *module_list, + size_t preload_count, + int argc, char **argv, cpu_exec_loaded *ld, + char *err, size_t es ) +{ + unsigned abi; + + if( cpu_exec_abi_version(path, &abi, err, es) ) + return( -1 ); + + if( abi == MELF_ABIVERSION_MCPU_1 ) + { + if( module_list != NULL && *module_list != 0 ) + { + snprintf( err, es, + "dynamic modules require an ABI-v2/ABI-v3 executable linked with mcpu-ld --dynamic" ); + return( -1 ); + } + return( cpu_exec_load_v1(path, argc, argv, ld, err, es) ); + } + + if( abi == MELF_ABIVERSION_MCPU_2 || + abi == MELF_ABIVERSION_MCPU_3 ) + return( cpu_dyn_load_resolved(path, module_list, preload_count, + argc, argv, ld, err, es) ); + + snprintf( err, es, "unsupported MELF ABI version %u", abi ); + return( -1 ); + +} /* End of cpu_exec_load_resolved() */ + +int +cpu_exec_load( const char *path, int argc, char **argv, cpu_exec_loaded *ld, + char *err, size_t es) +{ + return( cpu_exec_load_preload( path, NULL, argc, argv, ld, err, es ) ); +} diff --git a/utils/mcpu-exec.h b/utils/mcpu-exec.h new file mode 100644 index 0000000..54c206f --- /dev/null +++ b/utils/mcpu-exec.h @@ -0,0 +1,42 @@ +#ifndef __CPU_EXEC_H +#define __CPU_EXEC_H +#include <stddef.h> +#include "mcpu-asm.h" + +typedef struct cpu_exec_options +{ + __mpu_uint64_t stack_size; + int have_heap; + __mpu_uint64_t heap_size; + __mpu_uint64_t heap_limit; +} cpu_exec_options; + +typedef struct cpu_exec_loaded +{ + int has_init; + int has_fini; + __mpu_uint64_t start_entry; + __mpu_uint64_t init_entry; + __mpu_uint64_t life_entry; + __mpu_uint64_t fini_entry; + __mpu_uint64_t startup_base; + __mpu_uint64_t stack_size; + int has_data_section; + __mpu_uint64_t data_section_begin; + __mpu_uint64_t data_section_size; +} cpu_exec_loaded; + +int cpu_exec_write( const char *path, const cpu_asm_image *image, + const cpu_exec_options *options, char *err, size_t errsz); +int cpu_exec_load( const char *path, int argc, char **argv, cpu_exec_loaded *loaded, + char *err, size_t errsz); +int cpu_exec_abi_version( const char *path, unsigned *abi, + char *err, size_t errsz ); +int cpu_exec_load_preload( const char *path, const char *preload_list, + int argc, char **argv, cpu_exec_loaded *loaded, + char *err, size_t errsz); +int cpu_exec_load_resolved( const char *path, const char *module_list, + size_t preload_count, + int argc, char **argv, cpu_exec_loaded *loaded, + char *err, size_t errsz ); +#endif diff --git a/utils/mcpu-ld-options.c b/utils/mcpu-ld-options.c new file mode 100644 index 0000000..6b64654 --- /dev/null +++ b/utils/mcpu-ld-options.c @@ -0,0 +1,463 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <libmcpu.h> + +#include "mcpu-ld-options.h" + +static int +parse_size( const char *s, __mpu_uint64_t *v ) +{ + unsigned long long x; + __mpu_uint64_t m = 1; + char *e; + + if( !s || !*s || *s == '-' ) return( -1 ); + + errno = 0; + x = strtoull( s, &e, 0 ); + if( errno || e == s ) return( -1 ); + + if( *e == '\0' ) + m = 1; + else if( !strcmp( e, "K" ) || !strcmp( e, "KiB" ) ) + m = __mpu_UINT64_C( 1024 ); + else if( !strcmp( e, "M" ) || !strcmp( e, "MiB" ) ) + m = __mpu_UINT64_C( 1024 ) * __mpu_UINT64_C( 1024 ); + else if( !strcmp( e, "G" ) || !strcmp( e, "GiB" ) ) + m = __mpu_UINT64_C( 1024 ) * __mpu_UINT64_C( 1024 ) * __mpu_UINT64_C( 1024 ); + else + return( -1 ); + + if( (__mpu_uint64_t)x != x || (__mpu_uint64_t)x > UINT64_MAX / m ) + return( -1 ); + + *v = (__mpu_uint64_t)x * m; + return( 0 ); + +} /* End of parse_size() */ + + +static int +append_input( mcpu_ld_options *options, char *name ) +{ + char **p; + size_t capacity; + + if( options->input_count == options->input_capacity ) + { + capacity = options->input_capacity ? options->input_capacity * 2U : 8U; + if( capacity < options->input_capacity || + capacity > SIZE_MAX / sizeof(*p) ) + { + errno = EOVERFLOW; + return( -1 ); + } + + p = (char **)realloc( options->inputs, capacity * sizeof(*p) ); + if( p == NULL ) + return( -1 ); + + options->inputs = p; + options->input_capacity = capacity; + } + + options->inputs[options->input_count++] = name; + return( 0 ); + +} /* End of append_input() */ + + +static int +append_library( mcpu_ld_options *options, char *name ) +{ + char **p; + size_t capacity; + + if( options->library_count == options->library_capacity ) + { + capacity = options->library_capacity ? options->library_capacity * 2U : 8U; + if( capacity < options->library_capacity || + capacity > SIZE_MAX / sizeof(*p) ) + { + errno = EOVERFLOW; + return( -1 ); + } + + p = (char **)realloc( options->libraries, capacity * sizeof(*p) ); + if( p == NULL ) + return( -1 ); + + options->libraries = p; + options->library_capacity = capacity; + } + + options->libraries[options->library_count++] = name; + return( 0 ); + +} /* End of append_library() */ + + +void +mcpu_ld_options_init( mcpu_ld_options *options, const char *progname ) +{ + if( options == NULL ) + return; + + memset( options, 0, sizeof(*options) ); + mcpu_tool_options_init( &options->common, progname ); + options->exec.stack_size = 1048576U; + options->exec.have_heap = 1; + options->exec.heap_size = 1048576U; + options->exec.heap_limit = 1048576U; + options->mode = CPU_LINK_STATIC_EXEC; + +} /* End of mcpu_ld_options_init() */ + + +void +mcpu_ld_options_free( mcpu_ld_options *options ) +{ + if( options == NULL ) + return; + + free( options->inputs ); + options->inputs = NULL; + options->input_count = 0; + options->input_capacity = 0; + free( options->libraries ); + options->libraries = NULL; + options->library_count = 0; + options->library_capacity = 0; + mcpu_tool_options_free( &options->common ); + +} /* End of mcpu_ld_options_free() */ + + +void +mcpu_ld_usage( const mcpu_ld_options *options ) +{ + const char *name = options ? options->common.progname : "mcpu-ld"; + + fprintf( stderr, + "usage: %s [link-options] -o output input.o ...\n\n" + "Link LibMCPU MELF ET_REL object files.\n\n" + "Output mode (at most one):\n" + " -r - produce one partial-link MELF ET_REL object\n" + " --shared - produce ABI-v3 MELF ET_DYN shared object\n" + " --dynamic - produce ABI-v3 startup-dynamic MELF ET_EXEC\n" + " (default without these options: ABI-v1 static ET_EXEC)\n\n" + "MCPU system options:\n" + " --no-config - do not read mcpu-ld.conf files\n" + " --sys-root=PATH - use PATH as MCPU root and do not read config files\n" + " --dump-config - print effective MCPU configuration and exit\n" + " --print-search-dirs - print effective library search directories and exit\n\n" + "Library dependency options:\n" + " -L <dir>, -L<dir> - add explicit MCPU library search directory\n" + " -l <name>, -l<name> - link against lib<name>; ABI-v1 and -r search .a,\n" + " ABI-v3 searches .so then .a in each directory\n" + " --static - force every -l<name> lookup to use lib<name>.a\n" + " without changing the selected output mode\n" + " (selected .so files alone create DT_NEEDED)\n\n" + "Shared-library identity:\n" + " --soname=NAME - set mandatory ABI-v3 ET_DYN SONAME\n" + " --soname NAME - NAME must be a logical name without '/'\n\n" + "Link options:\n" + " -o <file> - write output to <file>\n" + " --stack-size=<size> - set fixed STACK size; positive and %u-byte aligned\n" + " (default for executables: 1048576)\n" + " --heap-size=<size> - set initial HEAP size (default: 1048576)\n" + " --heap-limit=<size> - set immutable maximum HEAP size (default: 1048576)\n" + " --no-heap - omit PT_MCPU_HEAP from an executable\n" + " -v, --verbose - print output-object summary\n" + " -h, --help - show this help\n" + " --version - show program version\n" + " --dump-version - show version number only\n\n" + "<size> is bytes by default; K/KiB, M/MiB and G/GiB suffixes are accepted.\n" + "K, M and G use binary multipliers (1024, 1024^2 and 1024^3).\n\n" + "Partial -r output is MELF ET_REL: STACK/HEAP/SONAME and shared objects are\n" + "not part of this mode; unresolved symbols and RELA records are preserved.\n" + "Shared objects may contain only .text/.data/.bss allocated sections,\n" + "must define --soname and must not use STACK/HEAP sizing options. ABI-v3\n" + "dynamic executables retain preemptible GLOBAL/WEAK relocations; mcpu-run\n" + "continues to accept explicit --ldpreload and legacy ABI-v2 files.\n", + name, CPU_ABI_STACK_ALIGN ); + +} /* End of mcpu_ld_usage() */ + + +int +mcpu_ld_handle_options( mcpu_ld_options *options, int argc, char **argv ) +{ + int i; + + if( options == NULL || argv == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + for( i = 1; i < argc; ++i ) + { + int common; + + if( !strcmp( argv[i], "-h" ) || !strcmp( argv[i], "--help" ) ) + { + options->common.help = 1; + return( 0 ); + } + + common = mcpu_tool_parse_common_option( &options->common, + argc, argv, &i ); + if( common < 0 ) + return( -1 ); + if( common > 0 ) + { + if( options->common.version_action != MCPU_VERSION_NONE ) + return( 0 ); + continue; + } + + if( !strcmp( argv[i], "-v" ) || !strcmp( argv[i], "--verbose" ) ) + { + options->verbose = 1; + } + else if( !strcmp( argv[i], "-r" ) ) + { + if( options->mode_seen++ ) + { + fprintf( stderr, "%s: -r/--shared/--dynamic are mutually exclusive\n", + options->common.progname ); + return( -1 ); + } + options->mode = CPU_LINK_RELOCATABLE; + } + else if( !strcmp( argv[i], "--shared" ) ) + { + if( options->mode_seen++ ) + { + fprintf( stderr, "%s: -r/--shared/--dynamic are mutually exclusive\n", + options->common.progname ); + return( -1 ); + } + options->mode = CPU_LINK_SHARED; + } + else if( !strcmp( argv[i], "--dynamic" ) ) + { + if( options->mode_seen++ ) + { + fprintf( stderr, "%s: -r/--shared/--dynamic are mutually exclusive\n", + options->common.progname ); + return( -1 ); + } + options->mode = CPU_LINK_DYNAMIC_EXEC; + } + else if( !strcmp( argv[i], "--static" ) ) + { + options->force_static = 1; + } + else if( !strncmp( argv[i], "--soname=", 9 ) ) + { + const char *name = argv[i] + 9; + + if( options->soname != NULL || name[0] == 0 || + strchr( name, '/' ) != NULL ) + { + fprintf( stderr, "%s: invalid or duplicate --soname\n", + options->common.progname ); + return( -1 ); + } + options->soname = name; + } + else if( !strcmp( argv[i], "--soname" ) ) + { + const char *name; + + if( ++i >= argc ) + { + fprintf( stderr, "%s: argument missing after --soname\n", + options->common.progname ); + return( -1 ); + } + name = argv[i]; + if( options->soname != NULL || name[0] == 0 || + strchr( name, '/' ) != NULL ) + { + fprintf( stderr, "%s: invalid or duplicate --soname\n", + options->common.progname ); + return( -1 ); + } + options->soname = name; + } + else if( !strcmp( argv[i], "-L" ) ) + { + if( ++i >= argc || argv[i][0] == 0 || + mcpu_tool_add_library_directory(&options->common, argv[i]) != 0 ) + { + fprintf( stderr, "%s: argument missing or invalid after -L\n", + options->common.progname ); + return( -1 ); + } + } + else if( !strncmp( argv[i], "-L", 2 ) ) + { + if( argv[i][2] == 0 || + mcpu_tool_add_library_directory(&options->common, argv[i] + 2) != 0 ) + { + fprintf( stderr, "%s: bad -L directory: %s\n", + options->common.progname, argv[i] + 2 ); + return( -1 ); + } + } + else if( !strcmp( argv[i], "-l" ) ) + { + char *name; + + if( ++i >= argc ) + { + fprintf( stderr, "%s: argument missing after -l\n", + options->common.progname ); + return( -1 ); + } + name = argv[i]; + if( name[0] == 0 || strchr(name, '/') != NULL || + append_library(options, name) != 0 ) + { + fprintf( stderr, "%s: invalid library name after -l: %s\n", + options->common.progname, name ); + return( -1 ); + } + } + else if( !strncmp( argv[i], "-l", 2 ) ) + { + char *name = argv[i] + 2; + + if( name[0] == 0 || strchr(name, '/') != NULL || + append_library(options, name) != 0 ) + { + fprintf( stderr, "%s: invalid library option: %s\n", + options->common.progname, argv[i] ); + return( -1 ); + } + } + else if( !strcmp( argv[i], "-o" ) ) + { + if( ++i >= argc || options->out_fname != NULL ) + { + mcpu_ld_usage( options ); + return( -1 ); + } + options->out_fname = argv[i]; + } + else if( !strncmp( argv[i], "--stack-size=", 13 ) ) + { + options->size_option_seen = 1; + if( parse_size(argv[i] + 13, &options->exec.stack_size) || + !options->exec.stack_size || + options->exec.stack_size % CPU_ABI_STACK_ALIGN ) + { + fprintf( stderr, "%s: stack size must be a positive multiple of %u\n", + options->common.progname, CPU_ABI_STACK_ALIGN ); + return( -1 ); + } + } + else if( !strncmp( argv[i], "--heap-size=", 12 ) ) + { + options->size_option_seen = 1; + if( parse_size(argv[i] + 12, &options->exec.heap_size) || + options->exec.heap_size < CPU_HEAP_BEGIN ) + { + fprintf( stderr, "%s: heap size must be at least %u\n", + options->common.progname, CPU_HEAP_BEGIN ); + return( -1 ); + } + options->exec.have_heap = 1; + } + else if( !strncmp( argv[i], "--heap-limit=", 13 ) ) + { + options->size_option_seen = 1; + if( parse_size(argv[i] + 13, &options->exec.heap_limit) || + options->exec.heap_limit < CPU_HEAP_BEGIN ) + { + fprintf( stderr, "%s: bad heap limit\n", options->common.progname ); + return( -1 ); + } + options->exec.have_heap = 1; + } + else if( !strcmp( argv[i], "--no-heap" ) ) + { + options->size_option_seen = 1; + options->exec.have_heap = 0; + options->exec.heap_size = options->exec.heap_limit = 0; + } + else if( argv[i][0] == '-' ) + { + fprintf( stderr, "%s: unknown option %s\n", + options->common.progname, argv[i] ); + return( -1 ); + } + else if( append_input(options, argv[i]) != 0 ) + { + perror( "realloc" ); + return( -1 ); + } + } + + if( options->common.help || + mcpu_tool_has_information_action(&options->common) ) + return( 0 ); + + if( options->out_fname == NULL || options->input_count == 0 ) + { + mcpu_ld_usage( options ); + return( -1 ); + } + + if( options->mode == CPU_LINK_RELOCATABLE && options->size_option_seen ) + { + fprintf( stderr, "%s: STACK/HEAP options are invalid with -r\n", + options->common.progname ); + return( -1 ); + } + + if( options->mode == CPU_LINK_SHARED && options->size_option_seen ) + { + fprintf( stderr, "%s: STACK/HEAP options are invalid with --shared\n", + options->common.progname ); + return( -1 ); + } + + if( options->mode == CPU_LINK_SHARED && options->soname == NULL ) + { + fprintf( stderr, "%s: ABI-v3 --shared requires --soname=NAME\n", + options->common.progname ); + return( -1 ); + } + + if( options->mode != CPU_LINK_SHARED && options->soname != NULL ) + { + fprintf( stderr, "%s: --soname is valid only with --shared\n", + options->common.progname ); + return( -1 ); + } + + if( options->mode != CPU_LINK_SHARED && + options->mode != CPU_LINK_RELOCATABLE && + options->exec.have_heap && + options->exec.heap_limit < options->exec.heap_size ) + { + fprintf( stderr, "%s: heap limit is smaller than heap size\n", + options->common.progname ); + return( -1 ); + } + + return( 0 ); + +} /* End of mcpu_ld_handle_options() */ diff --git a/utils/mcpu-ld-options.h b/utils/mcpu-ld-options.h new file mode 100644 index 0000000..b72a92e --- /dev/null +++ b/utils/mcpu-ld-options.h @@ -0,0 +1,36 @@ +#ifndef __MCPU_LD_OPTIONS_H__ +#define __MCPU_LD_OPTIONS_H__ 1 + +#include <stddef.h> + +#include "mcpu-exec.h" +#include "mcpu-link.h" +#include "mcpu-tool-options.h" + +typedef struct mcpu_ld_options mcpu_ld_options; +struct mcpu_ld_options +{ + mcpu_tool_options common; + const char *out_fname; + cpu_exec_options exec; + cpu_link_mode mode; + int mode_seen; + int force_static; + int verbose; + int size_option_seen; + const char *soname; + char **inputs; + size_t input_count; + size_t input_capacity; + char **libraries; + size_t library_count; + size_t library_capacity; +}; + +void mcpu_ld_options_init( mcpu_ld_options *options, const char *progname ); +void mcpu_ld_options_free( mcpu_ld_options *options ); +int mcpu_ld_handle_options( mcpu_ld_options *options, + int argc, char **argv ); +void mcpu_ld_usage( const mcpu_ld_options *options ); + +#endif /* __MCPU_LD_OPTIONS_H__ */ diff --git a/utils/mcpu-ld.c b/utils/mcpu-ld.c new file mode 100644 index 0000000..cf793ea --- /dev/null +++ b/utils/mcpu-ld.c @@ -0,0 +1,1038 @@ +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <libmcpu.h> + +#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() */ diff --git a/utils/mcpu-library-search.c b/utils/mcpu-library-search.c new file mode 100644 index 0000000..dc877b9 --- /dev/null +++ b/utils/mcpu-library-search.c @@ -0,0 +1,307 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> + +#include "mcpu-library-search.h" + + +static char * +path_join( const char *directory, const char *name ) +{ + size_t directory_length; + size_t name_length; + int need_slash; + char *result; + + if( directory == NULL || name == NULL ) + { + errno = EINVAL; + return( NULL ); + } + + directory_length = strlen( directory ); + name_length = strlen( name ); + need_slash = directory_length != 0 && directory[directory_length - 1] != '/'; + + if( directory_length > SIZE_MAX - name_length - (size_t)need_slash - 1 ) + { + errno = EOVERFLOW; + return( NULL ); + } + + result = (char *)malloc( directory_length + name_length + + (size_t)need_slash + 1 ); + if( result == NULL ) + return( NULL ); + + memcpy( result, directory, directory_length ); + if( need_slash ) + result[directory_length++] = '/'; + memcpy( result + directory_length, name, name_length ); + result[directory_length + name_length] = 0; + return( result ); + +} /* End of path_join() */ + + +static int +regular_file( const char *path ) +{ + struct stat st; + + if( stat(path, &st) != 0 ) + return( 0 ); + + return( S_ISREG(st.st_mode) ); + +} /* End of regular_file() */ + + +void +mcpu_library_search_init( mcpu_library_search *search ) +{ + if( search == NULL ) + return; + + search->directory = NULL; + search->count = 0; + search->capacity = 0; + +} /* End of mcpu_library_search_init() */ + + +void +mcpu_library_search_free( mcpu_library_search *search ) +{ + size_t i; + + if( search == NULL ) + return; + + for( i = 0; i < search->count; ++i ) + free( search->directory[i] ); + free( search->directory ); + mcpu_library_search_init( search ); + +} /* End of mcpu_library_search_free() */ + + +int +mcpu_library_search_add_directory( mcpu_library_search *search, + const char *directory ) +{ + char **p; + char *copy; + size_t capacity; + + if( search == NULL || directory == NULL || *directory == 0 ) + { + errno = EINVAL; + return( -1 ); + } + + if( search->count == search->capacity ) + { + capacity = search->capacity ? search->capacity * 2U : 4U; + if( capacity < search->capacity || capacity > SIZE_MAX / sizeof(*p) ) + { + errno = EOVERFLOW; + return( -1 ); + } + + p = (char **)realloc( search->directory, capacity * sizeof(*p) ); + if( p == NULL ) + return( -1 ); + + search->directory = p; + search->capacity = capacity; + } + + copy = strdup( directory ); + if( copy == NULL ) + return( -1 ); + + search->directory[search->count++] = copy; + return( 0 ); + +} /* End of mcpu_library_search_add_directory() */ + + +int +mcpu_library_search_add_list( mcpu_library_search *search, const char *list ) +{ + const char *begin; + + if( search == NULL || list == NULL || *list == 0 ) + { + errno = EINVAL; + return( -1 ); + } + + begin = list; + for( ;; ) + { + const char *colon = strchr( begin, ':' ); + size_t length = colon ? (size_t)(colon - begin) : strlen(begin); + char *directory; + int rc; + + if( length == 0 ) + { + errno = EINVAL; + return( -1 ); + } + + directory = (char *)malloc( length + 1 ); + if( directory == NULL ) + return( -1 ); + + memcpy( directory, begin, length ); + directory[length] = 0; + rc = mcpu_library_search_add_directory( search, directory ); + free( directory ); + if( rc != 0 ) + return( -1 ); + + if( colon == NULL ) + break; + begin = colon + 1; + } + + return( 0 ); + +} /* End of mcpu_library_search_add_list() */ + + +static int +try_directory( const char *directory, const char *name, char **resolved ) +{ + char *candidate; + + if( directory == NULL || *directory == 0 ) + return( 0 ); + + candidate = path_join( directory, name ); + if( candidate == NULL ) + return( -1 ); + + if( regular_file(candidate) ) + { + *resolved = candidate; + return( 1 ); + } + + free( candidate ); + return( 0 ); + +} /* End of try_directory() */ + + +int +mcpu_library_search_resolve( const mcpu_library_search *search, + const char *system_directory, + const char *name, char **resolved ) +{ + size_t i; + int rc; + + if( search == NULL || name == NULL || *name == 0 || resolved == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + *resolved = NULL; + + if( strchr(name, '/') != NULL ) + { + *resolved = strdup( name ); + return( *resolved == NULL ? -1 : 0 ); + } + + for( i = 0; i < search->count; ++i ) + { + rc = try_directory( search->directory[i], name, resolved ); + if( rc < 0 ) + return( -1 ); + if( rc > 0 ) + return( 0 ); + } + + rc = try_directory( system_directory, name, resolved ); + if( rc < 0 ) + return( -1 ); + if( rc > 0 ) + return( 0 ); + + errno = ENOENT; + return( -1 ); + +} /* End of mcpu_library_search_resolve() */ + + +int +mcpu_library_search_resolve_alternatives( + const mcpu_library_search *search, + const char *system_directory, + const char *const *names, size_t name_count, + char **resolved, size_t *selected_index ) +{ + size_t i, j; + int rc; + + if( search == NULL || names == NULL || name_count == 0 || + resolved == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + for( j = 0; j < name_count; ++j ) + if( names[j] == NULL || names[j][0] == 0 || strchr(names[j], '/') != NULL ) + { + errno = EINVAL; + return( -1 ); + } + + *resolved = NULL; + if( selected_index ) + *selected_index = 0; + + for( i = 0; i < search->count; ++i ) + for( j = 0; j < name_count; ++j ) + { + rc = try_directory( search->directory[i], names[j], resolved ); + if( rc < 0 ) + return( -1 ); + if( rc > 0 ) + { + if( selected_index ) + *selected_index = j; + return( 0 ); + } + } + + for( j = 0; j < name_count; ++j ) + { + rc = try_directory( system_directory, names[j], resolved ); + if( rc < 0 ) + return( -1 ); + if( rc > 0 ) + { + if( selected_index ) + *selected_index = j; + return( 0 ); + } + } + + errno = ENOENT; + return( -1 ); + +} /* End of mcpu_library_search_resolve_alternatives() */ diff --git a/utils/mcpu-library-search.h b/utils/mcpu-library-search.h new file mode 100644 index 0000000..66331a6 --- /dev/null +++ b/utils/mcpu-library-search.h @@ -0,0 +1,29 @@ +#ifndef __MCPU_LIBRARY_SEARCH_H__ +#define __MCPU_LIBRARY_SEARCH_H__ 1 + +#include <stddef.h> + +typedef struct mcpu_library_search mcpu_library_search; +struct mcpu_library_search +{ + char **directory; + size_t count; + size_t capacity; +}; + +void mcpu_library_search_init( mcpu_library_search *search ); +void mcpu_library_search_free( mcpu_library_search *search ); +int mcpu_library_search_add_directory( mcpu_library_search *search, + const char *directory ); +int mcpu_library_search_add_list( mcpu_library_search *search, + const char *list ); +int mcpu_library_search_resolve( const mcpu_library_search *search, + const char *system_directory, + const char *name, char **resolved ); +int mcpu_library_search_resolve_alternatives( + const mcpu_library_search *search, + const char *system_directory, + const char *const *names, size_t name_count, + char **resolved, size_t *selected_index ); + +#endif /* __MCPU_LIBRARY_SEARCH_H__ */ 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 ) ); +} diff --git a/utils/mcpu-link.h b/utils/mcpu-link.h new file mode 100644 index 0000000..5d28158 --- /dev/null +++ b/utils/mcpu-link.h @@ -0,0 +1,18 @@ +#ifndef __CPU_LINK_H +#define __CPU_LINK_H +#include "mcpu-asm.h" + +typedef enum cpu_link_mode +{ + CPU_LINK_STATIC_EXEC = 0, + CPU_LINK_DYNAMIC_EXEC, + CPU_LINK_SHARED, + CPU_LINK_RELOCATABLE +} cpu_link_mode; + +int cpu_link_objects( cpu_asm_object *objs, size_t nobjs, cpu_asm_image *image, char *err, size_t errsz ); +int cpu_link_objects_mode( cpu_asm_object *objs, size_t nobjs, cpu_asm_image *image, + cpu_link_mode mode, char *err, size_t errsz); +int cpu_link_relocatable( cpu_asm_object *objs, size_t nobjs, cpu_asm_object *object, + char *err, size_t errsz ); +#endif diff --git a/utils/mcpu-melf.c b/utils/mcpu-melf.c new file mode 100644 index 0000000..1178c6f --- /dev/null +++ b/utils/mcpu-melf.c @@ -0,0 +1,435 @@ +#include <errno.h> +#include <limits.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "mcpu-melf.h" + +int +cpu_melf_data_encoding( void ) +{ +#if CPU_DATA_BYTE_ORDER_LITTLE_ENDIAN == 1 + return( MELF_DATA_LSB ); +#elif CPU_DATA_BYTE_ORDER_BIG_ENDIAN == 1 + return( MELF_DATA_MSB ); +#else +# error "Unsupported CPU_DATA_BYTE_ORDER" +#endif +} + +static int +is_le( int data ) +{ + return( data == MELF_DATA_LSB ); +} + +void +cpu_melf_put16( unsigned char *p, __mpu_uint16_t v, int data ) +{ + if( is_le( data ) ) + { + p[0] = (unsigned char)v; + p[1] = (unsigned char)(v >> 8); + } + else + { + p[0] = (unsigned char)(v >> 8); + p[1] = (unsigned char)v; + } +} + +void +cpu_melf_put32( unsigned char *p, __mpu_uint32_t v, int data ) +{ + unsigned i; + + if( is_le( data ) ) + for( i = 0; i < 4; ++i ) + p[i] = (unsigned char)(v >> (8 * i)); + else + for( i = 0; i < 4; ++i ) + p[i] = (unsigned char)(v >> (8 * (3 - i))); +} + +void +cpu_melf_put64( unsigned char *p, __mpu_uint64_t v, int data ) +{ + unsigned i; + + if( is_le( data ) ) + for( i = 0; i < 8; ++i ) + p[i] = (unsigned char)(v >> (8 * i)); + else + for( i = 0; i < 8; ++i ) + p[i] = (unsigned char)(v >> (8 * (7 - i))); +} + +__mpu_uint16_t +cpu_melf_get16( const unsigned char *p, int data ) +{ + if( is_le( data ) ) + return (__mpu_uint16_t)(p[0] | ((__mpu_uint16_t)p[1] << 8)); + return (__mpu_uint16_t)(((__mpu_uint16_t)p[0] << 8) | p[1]); +} + +__mpu_uint32_t +cpu_melf_get32( const unsigned char *p, int data ) +{ + __mpu_uint32_t v = 0; + unsigned i; + + if( is_le( data ) ) + for( i = 0; i < 4; ++i ) + v |= (__mpu_uint32_t)p[i] << (8 * i); + else + for( i = 0; i < 4; ++i ) + v = (v << 8) | p[i]; + return( v ); +} + +__mpu_uint64_t +cpu_melf_get64( const unsigned char *p, int data ) +{ + __mpu_uint64_t v = 0; + unsigned i; + + if( is_le( data ) ) + for( i = 0; i < 8; ++i ) + v |= (__mpu_uint64_t)p[i] << (8 * i); + else + for( i = 0; i < 8; ++i ) + v = (v << 8) | p[i]; + return( v ); +} + +int +cpu_melf_span( size_t n, __mpu_uint64_t off, __mpu_uint64_t len ) +{ + return( off <= (__mpu_uint64_t)n && len <= (__mpu_uint64_t)n - off ); +} + +int +cpu_melf_is_pow2( __mpu_uint64_t v ) +{ + return( v != 0 && ( v & ( v - 1 ) ) == 0 ); +} + +__mpu_uint64_t +cpu_melf_align_up( __mpu_uint64_t v, __mpu_uint64_t a, int *ov ) +{ + __mpu_uint64_t r; + + if( ov ) + *ov = 0; + if( a <= 1 ) + return( v ); + if( !cpu_melf_is_pow2( a ) ) + { + if( ov ) + *ov = 1; + return( 0 ); + } + + r = v & (a - 1); + if( !r ) + return( v ); + if( v > UINT64_MAX - ( a - r ) ) + { + if( ov ) + *ov = 1; + return( 0 ); + } + return( v + ( a - r ) ); +} + +int +cpu_melf_readall( const char *path, unsigned char **pp, size_t *pn, + char *err, size_t es) +{ + FILE *f; + long z; + unsigned char *p; + + if( !path || !pp || !pn ) + return( -1 ); + + f = fopen( path, "rb" ); + if( !f ) + { + snprintf( err, es, "%s: %s", path, strerror( errno ) ); + return( -1 ); + } + if( fseek( f, 0, SEEK_END ) || ( z = ftell( f ) ) < 0 || fseek( f, 0, SEEK_SET ) ) + { + snprintf( err, es, "cannot size %s", path ); + fclose( f ); + return( -1 ); + } + + p = (unsigned char *)malloc( z ? (size_t)z : 1 ); + if( !p ) + { + snprintf( err, es, "out of memory" ); + fclose( f ); + return( -1 ); + } + if( z && fread( p, 1, (size_t)z, f ) != (size_t)z ) + { + snprintf( err, es, "cannot read %s", path ); + free( p ); + fclose( f ); + return( -1 ); + } + if( fclose( f ) ) + { + snprintf( err, es, "cannot close %s", path ); + free( p ); + return( -1 ); + } + + *pp = p; + *pn = (size_t)z; + return( 0 ); +} + +int +cpu_melf_writeall( const char *path, const void *data, size_t n, + char *err, size_t es) +{ + FILE *f = fopen( path, "wb" ); + + if( !f ) + { + snprintf( err, es, "%s: %s", path, strerror( errno ) ); + return( -1 ); + } + if( n && fwrite( data, 1, n, f ) != n ) + { + snprintf( err, es, "cannot write %s", path ); + fclose( f ); + return( -1 ); + } + if( fclose( f ) ) + { + snprintf( err, es, "cannot close %s", path ); + return( -1 ); + } + return( 0 ); +} + +void +cpu_melf_fill_target( MElf_McpuTarget *t ) +{ + memset( t, 0, sizeof( *t ) ); + t->mt_version = MELF_MCPU_TARGET_VERSION_CURRENT; + t->mt_size = MELF_MCPU_TARGET_SIZE; + t->mt_isa_revision = MELF_MCPU_ISA_REVISION_CURRENT; + t->mt_abi_revision = MELF_MCPU_ABI_REVISION_CURRENT; + t->mt_program_model = MELF_MCPU_MODEL_NONE; + t->mt_data_byte_order = (__mpu_uint32_t)cpu_melf_data_encoding(); +#if CPU_DATA_WORD_ORDER_LITTLE_ENDIAN == 1 + t->mt_data_word_order = MELF_MCPU_WORD_ORDER_LSB; +#elif CPU_DATA_WORD_ORDER_BIG_ENDIAN == 1 + t->mt_data_word_order = MELF_MCPU_WORD_ORDER_MSB; +#else +# error "Unsupported CPU_DATA_WORD_ORDER" +#endif + t->mt_integer_model = MELF_MCPU_INTEGER_MODEL_TWOS_COMPLEMENT; + t->mt_address_model = MELF_MCPU_ADDRESS_MODEL_PTR64_SEG4_OFF60; + t->mt_register_model = MELF_MCPU_REGISTER_MODEL_REG24_512K; + t->mt_real_model = MELF_MCPU_REAL_MODEL_LIBMPU; + t->mt_complex_model = MELF_MCPU_COMPLEX_MODEL_LIBMPU; + t->mt_max_align = MELF_MCPU_ABI_MAX_ALIGN; + t->mt_flags = MELF_MCPU_TARGET_FLAGS_NONE; + t->mt_required_features = MELF_MCPU_REQUIRED_FEATURES_NONE; +} + +void +cpu_melf_encode_target( unsigned char out[MELF_MCPU_TARGET_SIZE], + const MElf_McpuTarget *t, int data) +{ + memset( out, 0, MELF_MCPU_TARGET_SIZE ); + cpu_melf_put32( out + MELF_MCPU_TARGET_MT_VERSION_OFF, t->mt_version, data ); + cpu_melf_put32( out + MELF_MCPU_TARGET_MT_SIZE_OFF, t->mt_size, data ); + cpu_melf_put32( out + MELF_MCPU_TARGET_MT_ISA_REVISION_OFF, + t->mt_isa_revision, data); + cpu_melf_put32( out + MELF_MCPU_TARGET_MT_ABI_REVISION_OFF, + t->mt_abi_revision, data); + cpu_melf_put32( out + MELF_MCPU_TARGET_MT_PROGRAM_MODEL_OFF, + t->mt_program_model, data); + cpu_melf_put32( out + MELF_MCPU_TARGET_MT_DATA_BYTE_ORDER_OFF, + t->mt_data_byte_order, data); + cpu_melf_put32( out + MELF_MCPU_TARGET_MT_DATA_WORD_ORDER_OFF, + t->mt_data_word_order, data); + cpu_melf_put32( out + MELF_MCPU_TARGET_MT_INTEGER_MODEL_OFF, + t->mt_integer_model, data); + cpu_melf_put32( out + MELF_MCPU_TARGET_MT_ADDRESS_MODEL_OFF, + t->mt_address_model, data); + cpu_melf_put32( out + MELF_MCPU_TARGET_MT_REGISTER_MODEL_OFF, + t->mt_register_model, data); + cpu_melf_put32( out + MELF_MCPU_TARGET_MT_REAL_MODEL_OFF, + t->mt_real_model, data); + cpu_melf_put32( out + MELF_MCPU_TARGET_MT_COMPLEX_MODEL_OFF, + t->mt_complex_model, data); + cpu_melf_put32( out + MELF_MCPU_TARGET_MT_MAX_ALIGN_OFF, + t->mt_max_align, data); + cpu_melf_put32( out + MELF_MCPU_TARGET_MT_FLAGS_OFF, + t->mt_flags, data); + cpu_melf_put64( out + MELF_MCPU_TARGET_MT_REQUIRED_FEATURES_OFF, + t->mt_required_features, data); +} + +int +cpu_melf_decode_target( const unsigned char in[MELF_MCPU_TARGET_SIZE], + MElf_McpuTarget *t, int data) +{ + if( data != MELF_DATA_LSB && data != MELF_DATA_MSB ) + return( -1 ); + + memset( t, 0, sizeof( *t ) ); + t->mt_version = cpu_melf_get32( in + MELF_MCPU_TARGET_MT_VERSION_OFF, data ); + t->mt_size = cpu_melf_get32( in + MELF_MCPU_TARGET_MT_SIZE_OFF, data ); + t->mt_isa_revision = cpu_melf_get32( + in + MELF_MCPU_TARGET_MT_ISA_REVISION_OFF, data); + t->mt_abi_revision = cpu_melf_get32( + in + MELF_MCPU_TARGET_MT_ABI_REVISION_OFF, data); + t->mt_program_model = cpu_melf_get32( + in + MELF_MCPU_TARGET_MT_PROGRAM_MODEL_OFF, data); + t->mt_data_byte_order = cpu_melf_get32( + in + MELF_MCPU_TARGET_MT_DATA_BYTE_ORDER_OFF, data); + t->mt_data_word_order = cpu_melf_get32( + in + MELF_MCPU_TARGET_MT_DATA_WORD_ORDER_OFF, data); + t->mt_integer_model = cpu_melf_get32( + in + MELF_MCPU_TARGET_MT_INTEGER_MODEL_OFF, data); + t->mt_address_model = cpu_melf_get32( + in + MELF_MCPU_TARGET_MT_ADDRESS_MODEL_OFF, data); + t->mt_register_model = cpu_melf_get32( + in + MELF_MCPU_TARGET_MT_REGISTER_MODEL_OFF, data); + t->mt_real_model = cpu_melf_get32( + in + MELF_MCPU_TARGET_MT_REAL_MODEL_OFF, data); + t->mt_complex_model = cpu_melf_get32( + in + MELF_MCPU_TARGET_MT_COMPLEX_MODEL_OFF, data); + t->mt_max_align = cpu_melf_get32( + in + MELF_MCPU_TARGET_MT_MAX_ALIGN_OFF, data); + t->mt_flags = cpu_melf_get32( in + MELF_MCPU_TARGET_MT_FLAGS_OFF, data ); + t->mt_required_features = cpu_melf_get64( + in + MELF_MCPU_TARGET_MT_REQUIRED_FEATURES_OFF, data); + return( 0 ); +} + +int +cpu_melf_validate_target( const MElf_McpuTarget *t, int data, + char *err, size_t es) +{ + MElf_McpuTarget x; + + cpu_melf_fill_target( &x ); +#define EQ(FIELD) \ + do { \ + if(t->FIELD != x.FIELD) { \ + snprintf(err, es, "incompatible MCPU target profile field %s", #FIELD); \ + return -1; \ + } \ + } while(0) + + EQ( mt_version ); + if( t->mt_size != MELF_MCPU_TARGET_SIZE ) + { + snprintf( err, es, "bad .mcpu.target record size" ); + return( -1 ); + } + + EQ( mt_isa_revision ); + EQ( mt_abi_revision ); + EQ( mt_program_model ); + EQ( mt_data_byte_order ); + EQ( mt_data_word_order ); + EQ( mt_integer_model ); + EQ( mt_address_model ); + EQ( mt_register_model ); + EQ( mt_real_model ); + EQ( mt_complex_model ); + EQ( mt_max_align ); + EQ( mt_flags ); + EQ( mt_required_features ); +#undef EQ + + if( (int)t->mt_data_byte_order != data ) + { + snprintf( err, es, "MCPU target byte order disagrees with EI_DATA" ); + return( -1 ); + } + + return( 0 ); +} + +void +cpu_melf_fill_lifecycle( MElf_McpuLifecycle *l ) +{ + memset( l, 0, sizeof( *l ) ); + l->ml_version = MELF_MCPU_LIFECYCLE_VERSION_CURRENT; + l->ml_size = MELF_MCPU_LIFECYCLE_SIZE; + l->ml_abi = MELF_MCPU_LIFECYCLE_ABI_CURRENT; + l->ml_flags = MELF_MCPU_LIFECYCLE_ABI1_FLAGS; +} + +void +cpu_melf_encode_lifecycle( unsigned char out[MELF_MCPU_LIFECYCLE_SIZE], + const MElf_McpuLifecycle *l, int data) +{ + memset( out, 0, MELF_MCPU_LIFECYCLE_SIZE ); + cpu_melf_put32( out + MELF_MCPU_LIFECYCLE_ML_VERSION_OFF, + l->ml_version, data); + cpu_melf_put32( out + MELF_MCPU_LIFECYCLE_ML_SIZE_OFF, + l->ml_size, data); + cpu_melf_put32( out + MELF_MCPU_LIFECYCLE_ML_ABI_OFF, + l->ml_abi, data); + cpu_melf_put32( out + MELF_MCPU_LIFECYCLE_ML_RESERVED0_OFF, + l->ml_reserved0, data); + cpu_melf_put64( out + MELF_MCPU_LIFECYCLE_ML_FLAGS_OFF, + l->ml_flags, data); + cpu_melf_put64( out + MELF_MCPU_LIFECYCLE_ML_RESERVED1_OFF, + l->ml_reserved1, data); +} + +int +cpu_melf_decode_lifecycle( const unsigned char in[MELF_MCPU_LIFECYCLE_SIZE], + MElf_McpuLifecycle *l, int data) +{ + if( data != MELF_DATA_LSB && data != MELF_DATA_MSB ) + return( -1 ); + + memset( l, 0, sizeof( *l ) ); + l->ml_version = cpu_melf_get32( + in + MELF_MCPU_LIFECYCLE_ML_VERSION_OFF, data); + l->ml_size = cpu_melf_get32( + in + MELF_MCPU_LIFECYCLE_ML_SIZE_OFF, data); + l->ml_abi = cpu_melf_get32( + in + MELF_MCPU_LIFECYCLE_ML_ABI_OFF, data); + l->ml_reserved0 = cpu_melf_get32( + in + MELF_MCPU_LIFECYCLE_ML_RESERVED0_OFF, data); + l->ml_flags = cpu_melf_get64( + in + MELF_MCPU_LIFECYCLE_ML_FLAGS_OFF, data); + l->ml_reserved1 = cpu_melf_get64( + in + MELF_MCPU_LIFECYCLE_ML_RESERVED1_OFF, data); + return( 0 ); +} + +int +cpu_melf_validate_lifecycle( const MElf_McpuLifecycle *l, + char *err, size_t es) +{ + if( l->ml_version != MELF_MCPU_LIFECYCLE_VERSION_CURRENT || + l->ml_size != MELF_MCPU_LIFECYCLE_SIZE || + l->ml_abi != MELF_MCPU_LIFECYCLE_ABI_CURRENT || + l->ml_reserved0 != 0 || + l->ml_flags != MELF_MCPU_LIFECYCLE_ABI1_FLAGS || + l->ml_reserved1 != 0) + { + snprintf( err, es, "unsupported or malformed .mcpu.lifecycle record" ); + return( -1 ); + } + return( 0 ); +} diff --git a/utils/mcpu-melf.h b/utils/mcpu-melf.h new file mode 100644 index 0000000..f6c234f --- /dev/null +++ b/utils/mcpu-melf.h @@ -0,0 +1,42 @@ +#ifndef __CPU_MELF_H +#define __CPU_MELF_H + +#include <stddef.h> +#include <stdio.h> +#include <libmcpu.h> +#include <mcpu-elf.h> + +int cpu_melf_data_encoding( void ); +void cpu_melf_put16( unsigned char *p, __mpu_uint16_t v, int data ); +void cpu_melf_put32( unsigned char *p, __mpu_uint32_t v, int data ); +void cpu_melf_put64( unsigned char *p, __mpu_uint64_t v, int data ); +__mpu_uint16_t cpu_melf_get16( const unsigned char *p, int data ); +__mpu_uint32_t cpu_melf_get32( const unsigned char *p, int data ); +__mpu_uint64_t cpu_melf_get64( const unsigned char *p, int data ); + +int cpu_melf_span( size_t file_size, __mpu_uint64_t off, __mpu_uint64_t len ); +__mpu_uint64_t cpu_melf_align_up( __mpu_uint64_t v, __mpu_uint64_t align, int *overflow ); +int cpu_melf_readall( const char *path, unsigned char **data, size_t *size, + char *err, size_t errsz); +int cpu_melf_writeall( const char *path, const void *data, size_t size, + char *err, size_t errsz); + +void cpu_melf_fill_target( MElf_McpuTarget *t ); +void cpu_melf_encode_target( unsigned char out[MELF_MCPU_TARGET_SIZE], + const MElf_McpuTarget *t, int data); +int cpu_melf_decode_target( const unsigned char in[MELF_MCPU_TARGET_SIZE], + MElf_McpuTarget *t, int data); +int cpu_melf_validate_target( const MElf_McpuTarget *t, int data, + char *err, size_t errsz); + +void cpu_melf_fill_lifecycle( MElf_McpuLifecycle *l ); +void cpu_melf_encode_lifecycle( unsigned char out[MELF_MCPU_LIFECYCLE_SIZE], + const MElf_McpuLifecycle *l, int data); +int cpu_melf_decode_lifecycle( const unsigned char in[MELF_MCPU_LIFECYCLE_SIZE], + MElf_McpuLifecycle *l, int data); +int cpu_melf_validate_lifecycle( const MElf_McpuLifecycle *l, + char *err, size_t errsz); + +int cpu_melf_is_pow2( __mpu_uint64_t v ); + +#endif diff --git a/utils/mcpu-nm.c b/utils/mcpu-nm.c new file mode 100644 index 0000000..63c3bfd --- /dev/null +++ b/utils/mcpu-nm.c @@ -0,0 +1,716 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <inttypes.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <libmcpu.h> +#include <mcpu-elf.h> + +#include "mcpu-archive.h" +#include "mcpu-melf.h" +#include "mcpu-version.h" + + +typedef struct mcpu_nm_shdr +{ + __mpu_uint32_t type; + __mpu_uint64_t flags; + __mpu_uint64_t offset; + __mpu_uint64_t size; + __mpu_uint32_t link; + __mpu_uint64_t entsize; +} mcpu_nm_shdr; + + +typedef struct mcpu_nm_file +{ + const unsigned char *data; + size_t size; + int encoding; + __mpu_uint16_t shnum; + mcpu_nm_shdr *shdr; +} mcpu_nm_file; + + +typedef struct mcpu_nm_symbol +{ + char *name; + __mpu_uint64_t value; + __mpu_uint64_t size; + __mpu_uint16_t shndx; + unsigned int bind; + unsigned int type; + char code; + size_t order; +} mcpu_nm_symbol; + + +typedef struct mcpu_nm_symbols +{ + mcpu_nm_symbol *symbol; + size_t count; +} mcpu_nm_symbols; + + +typedef struct mcpu_nm_options +{ + int all_symbols; + int extern_only; + int undefined_only; + int numeric_sort; + int reverse_sort; + int no_sort; + int dynamic; + int print_file_name; +} mcpu_nm_options; + + +static const mcpu_nm_options *sort_options; + + +static void +usage( FILE *fp, const char *name ) +{ + fprintf( fp, + "usage: %s [options] MELF-file(s)\n\n" + "List symbols from LibMCPU MELF objects, executables, shared objects and archives.\n\n" + "Options:\n" + " -a, --debug-syms - display all named symbols, including FILE/SECTION\n" + " -g, --extern-only - display only external GLOBAL/WEAK symbols\n" + " -u, --undefined-only - display only undefined symbols\n" + " -n, --numeric-sort - sort symbols numerically by value\n" + " -r, --reverse-sort - reverse the selected sort order\n" + " -p, --no-sort - preserve symbol-table order\n" + " -D, --dynamic - display .dynsym instead of .symtab\n" + " -A, --print-file-name - prefix each symbol with its input file name\n" + " --help - show this help\n" + " --version - show program version\n" + " --dump-version - show version number only\n", + name ); + +} /* End of usage() */ + + +static void +nm_file_free( mcpu_nm_file *file ) +{ + if( !file ) + return; + free( file->shdr ); + memset( file, 0, sizeof( *file ) ); + +} /* End of nm_file_free() */ + + +static void +nm_symbols_free( mcpu_nm_symbols *symbols ) +{ + size_t i; + + if( !symbols ) + return; + for( i = 0; i < symbols->count; ++i ) + free( symbols->symbol[i].name ); + free( symbols->symbol ); + memset( symbols, 0, sizeof( *symbols ) ); + +} /* End of nm_symbols_free() */ + + +static void +get_shdr( const unsigned char *p, int data, mcpu_nm_shdr *sh ) +{ + memset( sh, 0, sizeof( *sh ) ); + sh->type = cpu_melf_get32( p + MELF64_SHDR_SH_TYPE_OFF, data ); + sh->flags = cpu_melf_get64( p + MELF64_SHDR_SH_FLAGS_OFF, data ); + sh->offset = cpu_melf_get64( p + MELF64_SHDR_SH_OFFSET_OFF, data ); + sh->size = cpu_melf_get64( p + MELF64_SHDR_SH_SIZE_OFF, data ); + sh->link = cpu_melf_get32( p + MELF64_SHDR_SH_LINK_OFF, data ); + sh->entsize = cpu_melf_get64( p + MELF64_SHDR_SH_ENTSIZE_OFF, data ); + +} /* End of get_shdr() */ + + +static int +nm_file_open_data( const unsigned char *data, size_t size, mcpu_nm_file *file, + char *err, size_t errsz ) +{ + __mpu_uint64_t shoff; + __mpu_uint16_t shentsize; + size_t i; + + memset( file, 0, sizeof( *file ) ); + file->data = data; + file->size = size; + + if( size < MELF64_EHDR_SIZE ) + { + snprintf( err, errsz, "file is too small for a MELF64 header" ); + return( -1 ); + } + if( data[MELF_EI_MAG0] != MELFMAG0 || data[MELF_EI_MAG1] != MELFMAG1 || + data[MELF_EI_MAG2] != MELFMAG2 || data[MELF_EI_MAG3] != MELFMAG3 ) + { + snprintf( err, errsz, "not a MELF file" ); + return( -1 ); + } + if( data[MELF_EI_CLASS] != MELF_CLASS_64 ) + { + snprintf( err, errsz, "unsupported MELF class %u", + (unsigned int)data[MELF_EI_CLASS] ); + return( -1 ); + } + + file->encoding = data[MELF_EI_DATA]; + if( file->encoding != MELF_DATA_LSB && file->encoding != MELF_DATA_MSB ) + { + snprintf( err, errsz, "invalid MELF data encoding %d", file->encoding ); + return( -1 ); + } + if( cpu_melf_get16( data + MELF64_EHDR_E_EHSIZE_OFF, file->encoding ) != + MELF64_EHDR_SIZE ) + { + snprintf( err, errsz, "unsupported MELF64 header size" ); + return( -1 ); + } + + shoff = cpu_melf_get64( data + MELF64_EHDR_E_SHOFF_OFF, file->encoding ); + shentsize = cpu_melf_get16( data + MELF64_EHDR_E_SHENTSIZE_OFF, + file->encoding ); + file->shnum = cpu_melf_get16( data + MELF64_EHDR_E_SHNUM_OFF, + file->encoding ); + + if( !file->shnum ) + return( 0 ); + if( shentsize != MELF64_SHDR_SIZE || + !cpu_melf_span( size, shoff, + (__mpu_uint64_t)file->shnum * MELF64_SHDR_SIZE ) ) + { + snprintf( err, errsz, "malformed section-header table" ); + return( -1 ); + } + + file->shdr = (mcpu_nm_shdr *)calloc( file->shnum, sizeof( *file->shdr ) ); + if( !file->shdr ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + + for( i = 0; i < file->shnum; ++i ) + get_shdr( data + (size_t)shoff + i * MELF64_SHDR_SIZE, + file->encoding, &file->shdr[i] ); + + return( 0 ); + +} /* End of nm_file_open_data() */ + + +static const char * +string_at( const unsigned char *table, size_t size, __mpu_uint64_t offset ) +{ + const unsigned char *p; + + if( !table || offset >= size ) + return( NULL ); + p = table + (size_t)offset; + if( !memchr( p, '\0', size - (size_t)offset ) ) + return( NULL ); + return( (const char *)p ); + +} /* End of string_at() */ + + +static int +symbol_code( const mcpu_nm_file *file, unsigned int bind, + __mpu_uint16_t shndx, char *code ) +{ + char c; + + if( bind == MELF_STB_WEAK ) + { + *code = shndx == MELF_SHN_UNDEF ? 'w' : 'W'; + return( 0 ); + } + if( shndx == MELF_SHN_UNDEF ) + { + *code = 'U'; + return( 0 ); + } + if( shndx == MELF_SHN_ABS ) + c = 'A'; + else if( shndx == MELF_SHN_COMMON ) + c = 'C'; + else if( shndx >= file->shnum ) + c = '?'; + else if( file->shdr[shndx].flags & MELF_SHF_EXECINSTR ) + c = 'T'; + else if( file->shdr[shndx].type == MELF_SHT_NOBITS && + (file->shdr[shndx].flags & MELF_SHF_WRITE) ) + c = 'B'; + else if( file->shdr[shndx].flags & MELF_SHF_WRITE ) + c = 'D'; + else if( file->shdr[shndx].flags & MELF_SHF_ALLOC ) + c = 'R'; + else + c = 'N'; + + if( bind == MELF_STB_LOCAL && c >= 'A' && c <= 'Z' ) + c = (char)(c - 'A' + 'a'); + *code = c; + return( c == '?' ? -1 : 0 ); + +} /* End of symbol_code() */ + + +static int +symbols_append( mcpu_nm_symbols *symbols, const char *name, + __mpu_uint64_t value, __mpu_uint64_t size, + __mpu_uint16_t shndx, unsigned int bind, unsigned int type, + char code, size_t order, char *err, size_t errsz ) +{ + mcpu_nm_symbol *q; + char *copy; + + q = (mcpu_nm_symbol *)realloc( symbols->symbol, + (symbols->count + 1) * sizeof( *q ) ); + if( !q ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + copy = strdup( name ); + if( !copy ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + + symbols->symbol = q; + q = &symbols->symbol[symbols->count++]; + memset( q, 0, sizeof( *q ) ); + q->name = copy; + q->value = value; + q->size = size; + q->shndx = shndx; + q->bind = bind; + q->type = type; + q->code = code; + q->order = order; + return( 0 ); + +} /* End of symbols_append() */ + + +static int +collect_table( const mcpu_nm_file *file, size_t table_index, + const mcpu_nm_options *options, mcpu_nm_symbols *symbols, + size_t *order, char *err, size_t errsz ) +{ + const mcpu_nm_shdr *sh = &file->shdr[table_index]; + const mcpu_nm_shdr *str; + const unsigned char *strtab; + size_t strsize; + size_t count; + size_t i; + + if( sh->entsize != MELF64_SYM_SIZE || sh->size % MELF64_SYM_SIZE || + sh->size > SIZE_MAX || !cpu_melf_span( file->size, sh->offset, sh->size ) ) + { + snprintf( err, errsz, "malformed symbol table" ); + return( -1 ); + } + if( sh->link >= file->shnum ) + { + snprintf( err, errsz, "symbol table has invalid string-table link" ); + return( -1 ); + } + str = &file->shdr[sh->link]; + if( str->type != MELF_SHT_STRTAB || str->size > SIZE_MAX || + !cpu_melf_span( file->size, str->offset, str->size ) ) + { + snprintf( err, errsz, "malformed symbol string table" ); + return( -1 ); + } + + strtab = file->data + (size_t)str->offset; + strsize = (size_t)str->size; + count = (size_t)(sh->size / MELF64_SYM_SIZE); + + for( i = 0; i < count; ++i ) + { + const unsigned char *p; + __mpu_uint32_t name_off; + unsigned int bind; + unsigned int type; + __mpu_uint16_t shndx; + __mpu_uint64_t value; + __mpu_uint64_t ssize; + const char *name; + char code; + + p = file->data + (size_t)sh->offset + i * MELF64_SYM_SIZE; + name_off = cpu_melf_get32( p + MELF64_SYM_ST_NAME_OFF, file->encoding ); + bind = MELF64_ST_BIND( p[MELF64_SYM_ST_INFO_OFF] ); + type = MELF64_ST_TYPE( p[MELF64_SYM_ST_INFO_OFF] ); + shndx = cpu_melf_get16( p + MELF64_SYM_ST_SHNDX_OFF, file->encoding ); + value = cpu_melf_get64( p + MELF64_SYM_ST_VALUE_OFF, file->encoding ); + ssize = cpu_melf_get64( p + MELF64_SYM_ST_SIZE_OFF, file->encoding ); + name = string_at( strtab, strsize, name_off ); + + if( !name ) + { + snprintf( err, errsz, "symbol has invalid name offset" ); + return( -1 ); + } + if( !*name ) + continue; + if( !options->all_symbols && + (type == MELF_STT_FILE || type == MELF_STT_SECTION) ) + continue; + if( options->extern_only && bind == MELF_STB_LOCAL ) + continue; + if( options->undefined_only && shndx != MELF_SHN_UNDEF ) + continue; + if( symbol_code( file, bind, shndx, &code ) ) + { + snprintf( err, errsz, "symbol '%s' has invalid section index", name ); + return( -1 ); + } + + if( symbols_append( symbols, name, value, ssize, shndx, bind, type, + code, (*order)++, err, errsz ) ) + return( -1 ); + } + return( 0 ); + +} /* End of collect_table() */ + + +static int +collect_symbols( const unsigned char *data, size_t size, + const mcpu_nm_options *options, mcpu_nm_symbols *symbols, + char *err, size_t errsz ) +{ + mcpu_nm_file file; + size_t i; + size_t order = 0; + int want; + + memset( symbols, 0, sizeof( *symbols ) ); + if( nm_file_open_data( data, size, &file, err, errsz ) ) + return( -1 ); + + want = options->dynamic ? MELF_SHT_DYNSYM : MELF_SHT_SYMTAB; + for( i = 0; i < file.shnum; ++i ) + { + if( file.shdr[i].type != (unsigned int)want ) + continue; + if( collect_table( &file, i, options, symbols, &order, err, errsz ) ) + { + nm_file_free( &file ); + nm_symbols_free( symbols ); + return( -1 ); + } + } + + nm_file_free( &file ); + return( 0 ); + +} /* End of collect_symbols() */ + + +static int +compare_symbols( const void *ap, const void *bp ) +{ + const mcpu_nm_symbol *a = (const mcpu_nm_symbol *)ap; + const mcpu_nm_symbol *b = (const mcpu_nm_symbol *)bp; + int c; + + if( sort_options->numeric_sort ) + { + if( a->value < b->value ) + c = -1; + else if( a->value > b->value ) + c = 1; + else + { + c = strcmp( a->name, b->name ); + if( !c ) + c = a->order < b->order ? -1 : a->order > b->order ? 1 : 0; + } + } + else + { + c = strcmp( a->name, b->name ); + if( !c ) + c = a->order < b->order ? -1 : a->order > b->order ? 1 : 0; + } + + return( sort_options->reverse_sort ? -c : c ); + +} /* End of compare_symbols() */ + + +static void +print_one_symbol( const mcpu_nm_symbol *symbol, const char *prefix, + const mcpu_nm_options *options ) +{ + if( options->print_file_name && prefix ) + printf( "%s: ", prefix ); + + if( symbol->shndx == MELF_SHN_UNDEF ) + printf( "%16s %c %s\n", "", symbol->code, symbol->name ); + else + printf( "%016" PRIx64 " %c %s\n", + (uint64_t)symbol->value, symbol->code, symbol->name ); + +} /* End of print_one_symbol() */ + + +static int +print_symbols_data( const unsigned char *data, size_t size, + const char *header, const char *prefix, + const mcpu_nm_options *options, int show_header, + char *err, size_t errsz ) +{ + mcpu_nm_symbols symbols; + size_t i; + + if( collect_symbols( data, size, options, &symbols, err, errsz ) ) + return( -1 ); + + if( !options->no_sort && symbols.count > 1 ) + { + sort_options = options; + qsort( symbols.symbol, symbols.count, sizeof( *symbols.symbol ), + compare_symbols ); + } + + if( show_header && !options->print_file_name ) + printf( "%s:\n", header ); + for( i = 0; i < symbols.count; ++i ) + print_one_symbol( &symbols.symbol[i], prefix, options ); + + nm_symbols_free( &symbols ); + return( 0 ); + +} /* End of print_symbols_data() */ + + +static int +is_archive_file( const unsigned char *data, size_t size ) +{ + static const unsigned char magic[] = "!<arch>\n"; + + return( size >= sizeof( magic ) - 1 && + !memcmp( data, magic, sizeof( magic ) - 1 ) ); + +} /* End of is_archive_file() */ + + +static int +process_archive( const char *path, const mcpu_nm_options *options, + char *err, size_t errsz ) +{ + mcpu_archive archive; + size_t i; + + if( mcpu_archive_read( path, &archive, err, errsz ) ) + return( -1 ); + + for( i = 0; i < archive.member_count; ++i ) + { + const mcpu_archive_member *member = &archive.members[i]; + char label[1024]; + + if( snprintf( label, sizeof( label ), "%s:%s", path, member->name ) >= + (int)sizeof( label ) ) + { + snprintf( err, errsz, "archive/member name is too long" ); + mcpu_archive_free( &archive ); + return( -1 ); + } + + if( print_symbols_data( member->data, member->size, label, label, + options, 1, err, errsz ) ) + { + mcpu_archive_free( &archive ); + return( -1 ); + } + if( !options->print_file_name && i + 1 < archive.member_count ) + printf( "\n" ); + } + + mcpu_archive_free( &archive ); + return( 0 ); + +} /* End of process_archive() */ + + +static int +process_path( const char *path, const mcpu_nm_options *options, + int show_header, char *err, size_t errsz ) +{ + unsigned char *data = NULL; + size_t size = 0; + int rc; + + if( cpu_melf_readall( path, &data, &size, err, errsz ) ) + return( -1 ); + + if( is_archive_file( data, size ) ) + { + free( data ); + return( process_archive( path, options, err, errsz ) ); + } + + rc = print_symbols_data( data, size, path, path, options, + show_header, err, errsz ); + free( data ); + return( rc ); + +} /* End of process_path() */ + + +static int +parse_short_options( const char *arg, mcpu_nm_options *options ) +{ + const char *p = arg + 1; + + while( *p ) + { + switch( *p ) + { + case 'a': options->all_symbols = 1; break; + case 'g': options->extern_only = 1; break; + case 'u': options->undefined_only = 1; break; + case 'n': options->numeric_sort = 1; options->no_sort = 0; break; + case 'r': options->reverse_sort = 1; break; + case 'p': options->no_sort = 1; options->numeric_sort = 0; break; + case 'D': options->dynamic = 1; break; + case 'A': options->print_file_name = 1; break; + default: return( -1 ); + } + ++p; + } + return( 0 ); + +} /* End of parse_short_options() */ + + +int +main( int argc, char **argv ) +{ + mcpu_nm_options options; + char **files = NULL; + size_t file_count = 0; + size_t file_capacity = 0; + int i; + int rc = 0; + + memset( &options, 0, sizeof( options ) ); + + for( i = 1; i < argc; ++i ) + { + const char *arg = argv[i]; + + if( !strcmp( arg, "--help" ) ) + { + usage( stdout, argv[0] ); + free( files ); + return( 0 ); + } + else if( !strcmp( arg, "--version" ) || + !strcmp( arg, "--dump-version" ) ) + { + mcpu_version_action version_action; + int rc; + + mcpu_version_parse_option( arg, &version_action ); + rc = mcpu_version_print( argv[0], version_action ); + free( files ); + return( rc == 0 ? 0 : 1 ); + } + else if( !strcmp( arg, "--debug-syms" ) ) + options.all_symbols = 1; + else if( !strcmp( arg, "--extern-only" ) ) + options.extern_only = 1; + else if( !strcmp( arg, "--undefined-only" ) ) + options.undefined_only = 1; + else if( !strcmp( arg, "--numeric-sort" ) ) + { + options.numeric_sort = 1; + options.no_sort = 0; + } + else if( !strcmp( arg, "--reverse-sort" ) ) + options.reverse_sort = 1; + else if( !strcmp( arg, "--no-sort" ) ) + { + options.no_sort = 1; + options.numeric_sort = 0; + } + else if( !strcmp( arg, "--dynamic" ) ) + options.dynamic = 1; + else if( !strcmp( arg, "--print-file-name" ) ) + options.print_file_name = 1; + else if( arg[0] == '-' && arg[1] != '\0' ) + { + if( parse_short_options( arg, &options ) ) + { + fprintf( stderr, "mcpu-nm: unknown option '%s'\n", arg ); + usage( stderr, argv[0] ); + free( files ); + return( 1 ); + } + } + else + { + if( file_count == file_capacity ) + { + size_t next = file_capacity ? file_capacity * 2U : 4U; + char **q = (char **)realloc( files, next * sizeof( *q ) ); + + if( !q ) + { + fprintf( stderr, "mcpu-nm: out of memory\n" ); + free( files ); + return( 1 ); + } + files = q; + file_capacity = next; + } + files[file_count++] = argv[i]; + } + } + + if( !file_count ) + { + usage( stderr, argv[0] ); + free( files ); + return( 1 ); + } + + for( i = 0; i < (int)file_count; ++i ) + { + char err[512]; + + if( i && !options.print_file_name ) + printf( "\n" ); + if( process_path( files[i], &options, + file_count > 1, err, sizeof( err ) ) ) + { + fprintf( stderr, "mcpu-nm: %s: %s\n", files[i], err ); + rc = 1; + } + } + + free( files ); + return( rc ); + +} /* End of main() */ diff --git a/utils/mcpu-objcopy.c b/utils/mcpu-objcopy.c new file mode 100644 index 0000000..c977140 --- /dev/null +++ b/utils/mcpu-objcopy.c @@ -0,0 +1,596 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "mcpu-archive.h" +#include "mcpu-melf.h" +#include "mcpu-object-edit.h" +#include "mcpu-version.h" + + +typedef struct cli_action +{ + mcpu_object_edit_action edit; + char *name; + char *new_name; + unsigned char *data; +} cli_action; + + +typedef struct dump_action +{ + char *name; + char *path; +} dump_action; + + +static void +usage( FILE *fp, const char *name ) +{ + fprintf( fp, + "usage: %s [options] INPUT OUTPUT\n\n" + "Copy and safely edit LibMCPU MELF files and deterministic archives.\n" + "The output format is always MELF. Runtime-visible sections and frozen\n" + "MELF ABI3 execution metadata cannot be removed, renamed or updated.\n\n" + "Options:\n" + " -j, --only-section=NAME - copy only the named sections\n" + " -R, --remove-section=NAME - remove a safe non-runtime section\n" + " --dump-section=NAME=FILE - write section contents to FILE\n" + " --add-section=NAME=FILE - add non-ALLOC SHT_PROGBITS metadata\n" + " --update-section=NAME=FILE - replace safe non-ALLOC section contents\n" + " --rename-section=OLD=NEW - rename a safe non-ALLOC section\n" + " --help - show this help\n" + " --version - show program version\n" + " --dump-version - show version number only\n", + name ); + +} /* End of usage() */ + + +static int +append_string( char ***items, size_t *count, const char *s ) +{ + char **q; + char *p; + + if( !s || !*s ) + return( -1 ); + q = (char **)realloc( *items, (*count + 1) * sizeof( *q ) ); + if( !q ) + return( -1 ); + p = strdup( s ); + if( !p ) + return( -1 ); + *items = q; + (*items)[(*count)++] = p; + return( 0 ); + +} /* End of append_string() */ + + +static void +free_strings( char **items, size_t count ) +{ + size_t i; + + for( i = 0; i < count; ++i ) + free( items[i] ); + free( items ); + +} /* End of free_strings() */ + + +static int +split_pair( const char *arg, char **left, char **right ) +{ + const char *eq = strchr( arg, '=' ); + size_t n; + + if( !eq || eq == arg || !eq[1] ) + return( -1 ); + n = (size_t)( eq - arg ); + *left = (char *)malloc( n + 1 ); + if( !*left ) + return( -1 ); + memcpy( *left, arg, n ); + (*left)[n] = 0; + *right = strdup( eq + 1 ); + if( !*right ) + { + free( *left ); + *left = NULL; + return( -1 ); + } + return( 0 ); + +} /* End of split_pair() */ + + +static int +append_edit_action( cli_action **actions, size_t *count, + mcpu_object_edit_action_type type, + const char *spec, int needs_file, + char *err, size_t errsz ) +{ + cli_action *q; + cli_action *a; + char *left = NULL; + char *right = NULL; + + if( split_pair( spec, &left, &right ) ) + { + snprintf( err, errsz, "expected NAME=VALUE: %s", spec ); + return( -1 ); + } + q = (cli_action *)realloc( *actions, (*count + 1) * sizeof( *q ) ); + if( !q ) + { + free( left ); + free( right ); + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + *actions = q; + a = &q[*count]; + memset( a, 0, sizeof( *a ) ); + a->name = left; + a->edit.type = type; + a->edit.name = a->name; + + if( needs_file ) + { + if( cpu_melf_readall( right, &a->data, &a->edit.size, err, errsz ) ) + { + free( right ); + free( a->name ); + memset( a, 0, sizeof( *a ) ); + return( -1 ); + } + a->edit.data = a->data; + free( right ); + } + else + { + a->new_name = right; + a->edit.new_name = a->new_name; + } + ++*count; + return( 0 ); + +} /* End of append_edit_action() */ + + +static int +append_remove_action( cli_action **actions, size_t *count, + const char *name, char *err, size_t errsz ) +{ + cli_action *q; + cli_action *a; + + if( !name || !*name ) + { + snprintf( err, errsz, "empty section name" ); + return( -1 ); + } + q = (cli_action *)realloc( *actions, (*count + 1) * sizeof( *q ) ); + if( !q ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + *actions = q; + a = &q[*count]; + memset( a, 0, sizeof( *a ) ); + a->name = strdup( name ); + if( !a->name ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + a->edit.type = MCPU_OBJECT_EDIT_REMOVE; + a->edit.name = a->name; + ++*count; + return( 0 ); + +} /* End of append_remove_action() */ + + +static void +free_edit_actions( cli_action *actions, size_t count ) +{ + size_t i; + + for( i = 0; i < count; ++i ) + { + free( actions[i].name ); + free( actions[i].new_name ); + free( actions[i].data ); + } + free( actions ); + +} /* End of free_edit_actions() */ + + +static int +append_dump_action( dump_action **actions, size_t *count, + const char *spec, char *err, size_t errsz ) +{ + dump_action *q; + char *name = NULL; + char *path = NULL; + + if( split_pair( spec, &name, &path ) ) + { + snprintf( err, errsz, "expected NAME=FILE: %s", spec ); + return( -1 ); + } + q = (dump_action *)realloc( *actions, (*count + 1) * sizeof( *q ) ); + if( !q ) + { + free( name ); + free( path ); + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + *actions = q; + q[*count].name = name; + q[*count].path = path; + ++*count; + return( 0 ); + +} /* End of append_dump_action() */ + + +static void +free_dump_actions( dump_action *actions, size_t count ) +{ + size_t i; + + for( i = 0; i < count; ++i ) + { + free( actions[i].name ); + free( actions[i].path ); + } + free( actions ); + +} /* End of free_dump_actions() */ + + +static int +write_output( const char *input, const char *output, + const unsigned char *data, size_t size, + char *err, size_t errsz ) +{ + struct stat st; + + if( stat( input, &st ) ) + { + snprintf( err, errsz, "%s: %s", input, strerror( errno ) ); + return( -1 ); + } + if( cpu_melf_writeall( output, data, size, err, errsz ) ) + return( -1 ); + if( chmod( output, st.st_mode & 07777 ) ) + { + snprintf( err, errsz, "%s: %s", output, strerror( errno ) ); + unlink( output ); + return( -1 ); + } + return( 0 ); + +} /* End of write_output() */ + + +static int +perform_dumps( const unsigned char *data, size_t size, + const dump_action *actions, size_t count, + char *err, size_t errsz ) +{ + size_t i; + + for( i = 0; i < count; ++i ) + { + unsigned char *p = NULL; + size_t n = 0; + + if( mcpu_object_dump_section( data, size, actions[i].name, + &p, &n, err, errsz ) ) + return( -1 ); + if( cpu_melf_writeall( actions[i].path, p, n, err, errsz ) ) + { + free( p ); + return( -1 ); + } + free( p ); + } + return( 0 ); + +} /* End of perform_dumps() */ + + +static int +copy_archive( const char *input, const char *output, + const mcpu_object_edit_options *options, + char *err, size_t errsz ) +{ + mcpu_archive archive; + struct stat st; + size_t i; + int rc = -1; + + mcpu_archive_init( &archive ); + if( stat( input, &st ) ) + { + snprintf( err, errsz, "%s: %s", input, strerror( errno ) ); + return( -1 ); + } + if( mcpu_archive_read( input, &archive, err, errsz ) ) + return( -1 ); + + for( i = 0; i < archive.member_count; ++i ) + { + unsigned char *p = NULL; + size_t n = 0; + int changed = 0; + + if( mcpu_object_edit_data( archive.members[i].data, + archive.members[i].size, + options, &p, &n, &changed, err, errsz ) ) + goto out; + free( archive.members[i].data ); + archive.members[i].data = p; + archive.members[i].size = n; + (void)changed; + } + + if( mcpu_archive_write( output, &archive, archive.had_symbol_index, + err, errsz ) ) + goto out; + if( chmod( output, st.st_mode & 07777 ) ) + { + snprintf( err, errsz, "%s: %s", output, strerror( errno ) ); + unlink( output ); + goto out; + } + rc = 0; + +out: + mcpu_archive_free( &archive ); + return( rc ); + +} /* End of copy_archive() */ + + +int +main( int argc, char **argv ) +{ + const char *prog = argv[0]; + const char *input = NULL; + const char *output = NULL; + char **only = NULL; + size_t only_count = 0; + cli_action *actions = NULL; + size_t action_count = 0; + dump_action *dumps = NULL; + size_t dump_count = 0; + unsigned char *data = NULL; + size_t size = 0; + mcpu_object_edit_options options; + mcpu_object_edit_action *edit_actions = NULL; + char err[512]; + int i; + int rc = EXIT_FAILURE; + + err[0] = 0; + for( i = 1; i < argc; ++i ) + { + const char *arg = argv[i]; + + if( !strcmp( arg, "--help" ) ) + { + usage( stdout, prog ); + rc = EXIT_SUCCESS; + goto out; + } + if( !strcmp( arg, "--version" ) || + !strcmp( arg, "--dump-version" ) ) + { + mcpu_version_action version_action; + + mcpu_version_parse_option( arg, &version_action ); + rc = mcpu_version_print(prog, version_action) == 0 ? + EXIT_SUCCESS : EXIT_FAILURE; + goto out; + } + if( !strcmp( arg, "-j" ) ) + { + if( ++i >= argc || append_string( &only, &only_count, argv[i] ) ) + { + snprintf( err, sizeof( err ), "option -j requires a section name" ); + goto bad_usage; + } + continue; + } + if( !strncmp( arg, "-j", 2 ) && arg[2] ) + { + if( append_string( &only, &only_count, arg + 2 ) ) + { + snprintf( err, sizeof( err ), "out of memory or empty section name" ); + goto bad_usage; + } + continue; + } + if( !strncmp( arg, "--only-section=", 15 ) ) + { + if( append_string( &only, &only_count, arg + 15 ) ) + { + snprintf( err, sizeof( err ), "--only-section requires a section name" ); + goto bad_usage; + } + continue; + } + if( !strcmp( arg, "-R" ) ) + { + if( ++i >= argc || append_remove_action( &actions, &action_count, + argv[i], err, sizeof( err ) ) ) + goto bad_usage; + continue; + } + if( !strncmp( arg, "-R", 2 ) && arg[2] ) + { + if( append_remove_action( &actions, &action_count, arg + 2, + err, sizeof( err ) ) ) + goto bad_usage; + continue; + } + if( !strncmp( arg, "--remove-section=", 17 ) ) + { + if( append_remove_action( &actions, &action_count, arg + 17, + err, sizeof( err ) ) ) + goto bad_usage; + continue; + } + if( !strncmp( arg, "--dump-section=", 15 ) ) + { + if( append_dump_action( &dumps, &dump_count, arg + 15, + err, sizeof( err ) ) ) + goto bad_usage; + continue; + } + if( !strncmp( arg, "--add-section=", 14 ) ) + { + if( append_edit_action( &actions, &action_count, MCPU_OBJECT_EDIT_ADD, + arg + 14, 1, err, sizeof( err ) ) ) + goto bad_usage; + continue; + } + if( !strncmp( arg, "--update-section=", 17 ) ) + { + if( append_edit_action( &actions, &action_count, MCPU_OBJECT_EDIT_UPDATE, + arg + 17, 1, err, sizeof( err ) ) ) + goto bad_usage; + continue; + } + if( !strncmp( arg, "--rename-section=", 17 ) ) + { + if( append_edit_action( &actions, &action_count, MCPU_OBJECT_EDIT_RENAME, + arg + 17, 0, err, sizeof( err ) ) ) + goto bad_usage; + continue; + } + if( arg[0] == '-' && arg[1] ) + { + snprintf( err, sizeof( err ), "unknown option: %s", arg ); + goto bad_usage; + } + if( !input ) + input = arg; + else if( !output ) + output = arg; + else + { + snprintf( err, sizeof( err ), "exactly INPUT and OUTPUT are required" ); + goto bad_usage; + } + } + + if( !input || !output ) + { + snprintf( err, sizeof( err ), "both INPUT and OUTPUT are required" ); + goto bad_usage; + } + if( !strcmp( input, output ) ) + { + snprintf( err, sizeof( err ), "INPUT and OUTPUT must be different files" ); + goto fail; + } + + if( action_count ) + { + size_t k; + + edit_actions = (mcpu_object_edit_action *)calloc( action_count, + sizeof( *edit_actions ) ); + if( !edit_actions ) + { + snprintf( err, sizeof( err ), "out of memory" ); + goto fail; + } + for( k = 0; k < action_count; ++k ) + edit_actions[k] = actions[k].edit; + } + memset( &options, 0, sizeof( options ) ); + options.only_sections = (const char *const *)only; + options.only_count = only_count; + options.actions = edit_actions; + options.action_count = action_count; + + if( cpu_melf_readall( input, &data, &size, err, sizeof( err ) ) ) + goto fail; + + if( size >= 8 && !memcmp( data, "!<arch>\n", 8 ) ) + { + if( dump_count ) + { + snprintf( err, sizeof( err ), + "--dump-section is ambiguous for archive input" ); + goto fail; + } + if( !only_count && !action_count ) + { + if( write_output( input, output, data, size, err, sizeof( err ) ) ) + goto fail; + } + else if( copy_archive( input, output, &options, err, sizeof( err ) ) ) + goto fail; + } + else + { + unsigned char *result = NULL; + size_t result_size = 0; + int changed = 0; + + if( perform_dumps( data, size, dumps, dump_count, err, sizeof( err ) ) ) + goto fail; + if( mcpu_object_edit_data( data, size, &options, + &result, &result_size, &changed, + err, sizeof( err ) ) ) + goto fail; + if( write_output( input, output, result, result_size, + err, sizeof( err ) ) ) + { + free( result ); + goto fail; + } + free( result ); + (void)changed; + } + + rc = EXIT_SUCCESS; + goto out; + +bad_usage: + fprintf( stderr, "%s: %s\n", prog, err ); + usage( stderr, prog ); + goto out; + +fail: + fprintf( stderr, "%s: %s\n", prog, err ); + +out: + free( data ); + free( edit_actions ); + free_dump_actions( dumps, dump_count ); + free_edit_actions( actions, action_count ); + free_strings( only, only_count ); + return( rc ); + +} /* End of main() */ diff --git a/utils/mcpu-objdump.c b/utils/mcpu-objdump.c new file mode 100644 index 0000000..f61435e --- /dev/null +++ b/utils/mcpu-objdump.c @@ -0,0 +1,1747 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <ctype.h> +#include <inttypes.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <libmcpu.h> +#include <mcpu-elf.h> + +#include "mcpu-archive.h" +#include "mcpu-melf.h" +#include "mcpu-version.h" + + +typedef struct mcpu_objdump_shdr +{ + __mpu_uint32_t name; + __mpu_uint32_t type; + __mpu_uint64_t flags; + __mpu_uint64_t addr; + __mpu_uint64_t offset; + __mpu_uint64_t size; + __mpu_uint32_t link; + __mpu_uint32_t info; + __mpu_uint64_t align; + __mpu_uint64_t entsize; +} mcpu_objdump_shdr; + + +typedef struct mcpu_objdump_file +{ + const char *name; + const unsigned char *data; + size_t size; + int encoding; + __mpu_uint16_t type; + __mpu_uint16_t shnum; + __mpu_uint16_t shstrndx; + mcpu_objdump_shdr *shdr; + const unsigned char *shstr; + size_t shstr_size; +} mcpu_objdump_file; + + +typedef struct mcpu_objdump_options +{ + int disassemble; + int disassemble_all; + int relocations; + int full_contents; + int symbols; + int dynamic_symbols; + int section_headers; + char **sections; + size_t section_count; +} mcpu_objdump_options; + + +static void +usage( FILE *fp, const char *name ) +{ + fprintf( fp, + "usage: %s [options] MELF-file(s)\n\n" + "Display and disassemble LibMCPU MELF objects, executables, shared objects and archives.\n\n" + "Options:\n" + " -d, --disassemble - disassemble executable sections\n" + " -D, --disassemble-all - disassemble all PROGBITS sections\n" + " -r, --reloc - display relocation entries\n" + " -s, --full-contents - display full contents of sections\n" + " -t, --syms - display .symtab symbols\n" + " -T, --dynamic-syms - display .dynsym symbols\n" + " -h, --section-headers - display section headers\n" + " -j, --section=NAME - restrict output to section NAME (repeatable)\n" + " --help - show this help\n" + " --version - show program version\n" + " --dump-version - show version number only\n", + name ); + +} /* End of usage() */ + + +static void +objdump_file_free( mcpu_objdump_file *file ) +{ + if( !file ) + return; + free( file->shdr ); + memset( file, 0, sizeof( *file ) ); + +} /* End of objdump_file_free() */ + + +static void +get_shdr( const unsigned char *p, int encoding, mcpu_objdump_shdr *sh ) +{ + memset( sh, 0, sizeof( *sh ) ); + sh->name = cpu_melf_get32( p + MELF64_SHDR_SH_NAME_OFF, encoding ); + sh->type = cpu_melf_get32( p + MELF64_SHDR_SH_TYPE_OFF, encoding ); + sh->flags = cpu_melf_get64( p + MELF64_SHDR_SH_FLAGS_OFF, encoding ); + sh->addr = cpu_melf_get64( p + MELF64_SHDR_SH_ADDR_OFF, encoding ); + sh->offset = cpu_melf_get64( p + MELF64_SHDR_SH_OFFSET_OFF, encoding ); + sh->size = cpu_melf_get64( p + MELF64_SHDR_SH_SIZE_OFF, encoding ); + sh->link = cpu_melf_get32( p + MELF64_SHDR_SH_LINK_OFF, encoding ); + sh->info = cpu_melf_get32( p + MELF64_SHDR_SH_INFO_OFF, encoding ); + sh->align = cpu_melf_get64( p + MELF64_SHDR_SH_ADDRALIGN_OFF, encoding ); + sh->entsize = cpu_melf_get64( p + MELF64_SHDR_SH_ENTSIZE_OFF, encoding ); + +} /* End of get_shdr() */ + + +static const char * +string_at( const unsigned char *table, size_t size, __mpu_uint64_t offset ) +{ + const unsigned char *p; + + if( !table || offset >= size ) + return( NULL ); + p = table + (size_t)offset; + if( !memchr( p, '\0', size - (size_t)offset ) ) + return( NULL ); + return( (const char *)p ); + +} /* End of string_at() */ + + +static int +objdump_file_open_data( const char *name, const unsigned char *data, size_t size, + mcpu_objdump_file *file, char *err, size_t errsz ) +{ + __mpu_uint64_t shoff; + __mpu_uint16_t shentsize; + size_t i; + + memset( file, 0, sizeof( *file ) ); + file->name = name; + file->data = data; + file->size = size; + + if( size < MELF64_EHDR_SIZE ) + { + snprintf( err, errsz, "file is too small for a MELF64 header" ); + return( -1 ); + } + if( data[MELF_EI_MAG0] != MELFMAG0 || data[MELF_EI_MAG1] != MELFMAG1 || + data[MELF_EI_MAG2] != MELFMAG2 || data[MELF_EI_MAG3] != MELFMAG3 ) + { + snprintf( err, errsz, "not a MELF file" ); + return( -1 ); + } + if( data[MELF_EI_CLASS] != MELF_CLASS_64 ) + { + snprintf( err, errsz, "unsupported MELF class %u", + (unsigned int)data[MELF_EI_CLASS] ); + return( -1 ); + } + + file->encoding = data[MELF_EI_DATA]; + if( file->encoding != MELF_DATA_LSB && file->encoding != MELF_DATA_MSB ) + { + snprintf( err, errsz, "invalid MELF data encoding %d", file->encoding ); + return( -1 ); + } + if( cpu_melf_get16( data + MELF64_EHDR_E_EHSIZE_OFF, file->encoding ) != + MELF64_EHDR_SIZE ) + { + snprintf( err, errsz, "unsupported MELF64 header size" ); + return( -1 ); + } + + file->type = cpu_melf_get16( data + MELF64_EHDR_E_TYPE_OFF, file->encoding ); + shoff = cpu_melf_get64( data + MELF64_EHDR_E_SHOFF_OFF, file->encoding ); + shentsize = cpu_melf_get16( data + MELF64_EHDR_E_SHENTSIZE_OFF, + file->encoding ); + file->shnum = cpu_melf_get16( data + MELF64_EHDR_E_SHNUM_OFF, + file->encoding ); + file->shstrndx = cpu_melf_get16( data + MELF64_EHDR_E_SHSTRNDX_OFF, + file->encoding ); + + if( !file->shnum ) + return( 0 ); + if( shentsize != MELF64_SHDR_SIZE || + !cpu_melf_span( size, shoff, + (__mpu_uint64_t)file->shnum * MELF64_SHDR_SIZE ) ) + { + snprintf( err, errsz, "malformed section-header table" ); + return( -1 ); + } + + file->shdr = (mcpu_objdump_shdr *)calloc( file->shnum, + sizeof( *file->shdr ) ); + if( !file->shdr ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + for( i = 0; i < file->shnum; ++i ) + get_shdr( data + (size_t)shoff + i * MELF64_SHDR_SIZE, + file->encoding, &file->shdr[i] ); + + if( file->shstrndx >= file->shnum ) + { + snprintf( err, errsz, "invalid section-name string table index" ); + goto fail; + } + if( file->shdr[file->shstrndx].type != MELF_SHT_STRTAB || + file->shdr[file->shstrndx].size > SIZE_MAX || + !cpu_melf_span( size, file->shdr[file->shstrndx].offset, + file->shdr[file->shstrndx].size ) ) + { + snprintf( err, errsz, "malformed section-name string table" ); + goto fail; + } + file->shstr = data + (size_t)file->shdr[file->shstrndx].offset; + file->shstr_size = (size_t)file->shdr[file->shstrndx].size; + + for( i = 0; i < file->shnum; ++i ) + { + if( !string_at( file->shstr, file->shstr_size, file->shdr[i].name ) ) + { + snprintf( err, errsz, "section has invalid name offset" ); + goto fail; + } + if( file->shdr[i].type != MELF_SHT_NOBITS && + file->shdr[i].size && + !cpu_melf_span( size, file->shdr[i].offset, file->shdr[i].size ) ) + { + snprintf( err, errsz, "section payload is outside file" ); + goto fail; + } + } + + return( 0 ); + +fail: + objdump_file_free( file ); + return( -1 ); + +} /* End of objdump_file_open_data() */ + + +static const char * +section_name( const mcpu_objdump_file *file, size_t index ) +{ + if( !file || index >= file->shnum ) + return( "<invalid>" ); + return( string_at( file->shstr, file->shstr_size, file->shdr[index].name ) ); + +} /* End of section_name() */ + + +static int +section_selected( const mcpu_objdump_file *file, size_t index, + const mcpu_objdump_options *options ) +{ + const char *name; + size_t i; + + if( !options->section_count ) + return( 1 ); + name = section_name( file, index ); + for( i = 0; i < options->section_count; ++i ) + if( !strcmp( name, options->sections[i] ) ) + return( 1 ); + return( 0 ); + +} /* End of section_selected() */ + + +static const char * +section_type_name( __mpu_uint32_t type ) +{ + switch( type ) + { + case MELF_SHT_NULL: return( "NULL" ); + case MELF_SHT_PROGBITS: return( "PROGBITS" ); + case MELF_SHT_SYMTAB: return( "SYMTAB" ); + case MELF_SHT_STRTAB: return( "STRTAB" ); + case MELF_SHT_RELA: return( "RELA" ); + case MELF_SHT_DYNAMIC: return( "DYNAMIC" ); + case MELF_SHT_NOBITS: return( "NOBITS" ); + case MELF_SHT_DYNSYM: return( "DYNSYM" ); + case MELF_SHT_MCPU_TARGET: return( "MCPU_TARGET" ); + case MELF_SHT_MCPU_LIFECYCLE: return( "MCPU_LIFECYCLE" ); + default: return( "<unknown>" ); + } + +} /* End of section_type_name() */ + + +static const char * +symbol_bind_name( unsigned int bind ) +{ + switch( bind ) + { + case MELF_STB_LOCAL: return( "LOCAL" ); + case MELF_STB_GLOBAL: return( "GLOBAL" ); + case MELF_STB_WEAK: return( "WEAK" ); + default: return( "<unknown>" ); + } + +} /* End of symbol_bind_name() */ + + +static const char * +symbol_type_name( unsigned int type ) +{ + switch( type ) + { + case MELF_STT_NOTYPE: return( "NOTYPE" ); + case MELF_STT_OBJECT: return( "OBJECT" ); + case MELF_STT_FUNC: return( "FUNC" ); + case MELF_STT_SECTION: return( "SECTION" ); + case MELF_STT_FILE: return( "FILE" ); + default: return( "<unknown>" ); + } + +} /* End of symbol_type_name() */ + + +static const char * +relocation_type_name( unsigned int type ) +{ + switch( type ) + { + case MELF_R_MCPU_NONE: return( "R_MCPU_NONE" ); + case MELF_R_MCPU_CODE64: return( "R_MCPU_CODE64" ); + case MELF_R_MCPU_DATA64: return( "R_MCPU_DATA64" ); + case MELF_R_MCPU_PTR64_CODE: return( "R_MCPU_PTR64_CODE" ); + case MELF_R_MCPU_PTR64_DATA: return( "R_MCPU_PTR64_DATA" ); + case MELF_R_MCPU_CPTR64_DATA: return( "R_MCPU_CPTR64_DATA" ); + default: return( "<unknown>" ); + } + +} /* End of relocation_type_name() */ + + +static const char * +opcode_name( cpu_opcode opcode ) +{ + switch( opcode ) + { + case CPU_OP_NOP: return( "NOP" ); + case CPU_OP_HALT: return( "HALT" ); + case CPU_OP_END: return( "END" ); + case CPU_OP_EXIT: return( "EXIT" ); + case CPU_OP_SYSCALL: return( "SYSCALL" ); + case CPU_OP_SETF: return( "SETF" ); + case CPU_OP_CLRF: return( "CLRF" ); + case CPU_OP_CLRCPUF: return( "CLRCPUF" ); + case CPU_OP_CLRMPUF: return( "CLRMPUF" ); + case CPU_OP_MOV: return( "MOV" ); + case CPU_OP_CLR: return( "CLR" ); + case CPU_OP_SWAP: return( "SWAP" ); + case CPU_OP_BSWAP: return( "BSWAP" ); + case CPU_OP_LDI: return( "LDI" ); + case CPU_OP_LDR: return( "LDR" ); + case CPU_OP_LDC: return( "LDC" ); + case CPU_OP_LOAD: return( "LOAD" ); + case CPU_OP_STORE: return( "STORE" ); + case CPU_OP_LEA: return( "LEA" ); + case CPU_OP_LEASP: return( "LEASP" ); + case CPU_OP_LOADP: return( "LOADP" ); + case CPU_OP_STOREP: return( "STOREP" ); + case CPU_OP_PADD: return( "PADD" ); + case CPU_OP_PADDI: return( "PADDI" ); + case CPU_OP_PSUB: return( "PSUB" ); + case CPU_OP_PSUBI: return( "PSUBI" ); + case CPU_OP_PDIFF: return( "PDIFF" ); + case CPU_OP_BMOV: return( "BMOV" ); + case CPU_OP_BMOVI: return( "BMOVI" ); + case CPU_OP_IADD: return( "IADD" ); + case CPU_OP_IADC: return( "IADC" ); + case CPU_OP_ISUB: return( "ISUB" ); + case CPU_OP_ISBB: return( "ISBB" ); + case CPU_OP_IAND: return( "IAND" ); + case CPU_OP_IOR: return( "IOR" ); + case CPU_OP_IXOR: return( "IXOR" ); + case CPU_OP_INOT: return( "INOT" ); + case CPU_OP_INEG: return( "INEG" ); + case CPU_OP_ICMP: return( "ICMP" ); + case CPU_OP_ITEST: return( "ITEST" ); + case CPU_OP_ISHL: return( "ISHL" ); + case CPU_OP_ISHR: return( "ISHR" ); + case CPU_OP_ISAL: return( "ISAL" ); + case CPU_OP_ISAR: return( "ISAR" ); + case CPU_OP_IROL: return( "IROL" ); + case CPU_OP_IROR: return( "IROR" ); + case CPU_OP_IRCL: return( "IRCL" ); + case CPU_OP_IRCR: return( "IRCR" ); + case CPU_OP_ISHLN: return( "ISHLN" ); + case CPU_OP_ISHRN: return( "ISHRN" ); + case CPU_OP_ISALN: return( "ISALN" ); + case CPU_OP_ISARN: return( "ISARN" ); + case CPU_OP_IROLN: return( "IROLN" ); + case CPU_OP_IRORN: return( "IRORN" ); + case CPU_OP_IRCLN: return( "IRCLN" ); + case CPU_OP_IRCRN: return( "IRCRN" ); + case CPU_OP_IINC: return( "IINC" ); + case CPU_OP_IDEC: return( "IDEC" ); + case CPU_OP_IXCHG: return( "IXCHG" ); + case CPU_OP_ICPY: return( "ICPY" ); + case CPU_OP_ICVT: return( "ICVT" ); + case CPU_OP_IMUL: return( "IMUL" ); + case CPU_OP_ISMUL: return( "ISMUL" ); + case CPU_OP_IDIV: return( "IDIV" ); + case CPU_OP_ISDIV: return( "ISDIV" ); + case CPU_OP_IBITSLEN: return( "IBITSLEN" ); + case CPU_OP_RADD: return( "RADD" ); + case CPU_OP_RSUB: return( "RSUB" ); + case CPU_OP_RMUL: return( "RMUL" ); + case CPU_OP_RDIV: return( "RDIV" ); + case CPU_OP_RSQRT: return( "RSQRT" ); + case CPU_OP_RSIN: return( "RSIN" ); + case CPU_OP_RCOS: return( "RCOS" ); + case CPU_OP_RTAN: return( "RTAN" ); + case CPU_OP_RASIN: return( "RASIN" ); + case CPU_OP_RACOS: return( "RACOS" ); + case CPU_OP_RATAN: return( "RATAN" ); + case CPU_OP_RLOG: return( "RLOG" ); + case CPU_OP_RLOG10: return( "RLOG10" ); + case CPU_OP_RLOG2: return( "RLOG2" ); + case CPU_OP_REXP: return( "REXP" ); + case CPU_OP_RSINH: return( "RSINH" ); + case CPU_OP_RCOSH: return( "RCOSH" ); + case CPU_OP_RTANH: return( "RTANH" ); + case CPU_OP_RPOW: return( "RPOW" ); + case CPU_OP_RHYPOT: return( "RHYPOT" ); + case CPU_OP_RATAN2: return( "RATAN2" ); + case CPU_OP_RNEG: return( "RNEG" ); + case CPU_OP_RABS: return( "RABS" ); + case CPU_OP_RCMP: return( "RCMP" ); + case CPU_OP_RCPY: return( "RCPY" ); + case CPU_OP_RCVT: return( "RCVT" ); + case CPU_OP_RFLOOR: return( "RFLOOR" ); + case CPU_OP_RCEIL: return( "RCEIL" ); + case CPU_OP_RROUND: return( "RROUND" ); + case CPU_OP_RFREXP: return( "RFREXP" ); + case CPU_OP_RLDEXP: return( "RLDEXP" ); + case CPU_OP_RLOGB: return( "RLOGB" ); + case CPU_OP_RMODF: return( "RMODF" ); + case CPU_OP_RREMAIN: return( "RREMAIN" ); + case CPU_OP_RLOG1P: return( "RLOG1P" ); + case CPU_OP_REXPM1: return( "REXPM1" ); + case CPU_OP_RASINH: return( "RASINH" ); + case CPU_OP_RACOSH: return( "RACOSH" ); + case CPU_OP_RATANH: return( "RATANH" ); + case CPU_OP_ITOR: return( "ITOR" ); + case CPU_OP_UITOR: return( "UITOR" ); + case CPU_OP_RTOI: return( "RTOI" ); + case CPU_OP_RTOUI: return( "RTOUI" ); + case CPU_OP_RISNEG: return( "RISNEG" ); + case CPU_OP_RSIGN: return( "RSIGN" ); + case CPU_OP_RISIND: return( "RISIND" ); + case CPU_OP_RISNAN: return( "RISNAN" ); + case CPU_OP_RISNANMAX: return( "RISNANMAX" ); + case CPU_OP_RISNANMIN: return( "RISNANMIN" ); + case CPU_OP_RISINF: return( "RISINF" ); + case CPU_OP_RISSIGNULL: return( "RISSIGNULL" ); + case CPU_OP_RCONST: return( "RCONST" ); + case CPU_OP_CADD: return( "CADD" ); + case CPU_OP_CSUB: return( "CSUB" ); + case CPU_OP_CMUL: return( "CMUL" ); + case CPU_OP_CDIV: return( "CDIV" ); + case CPU_OP_CEXP: return( "CEXP" ); + case CPU_OP_CSIN: return( "CSIN" ); + case CPU_OP_CCOS: return( "CCOS" ); + case CPU_OP_CSINH: return( "CSINH" ); + case CPU_OP_CCOSH: return( "CCOSH" ); + case CPU_OP_CLOG: return( "CLOG" ); + case CPU_OP_CSQRT: return( "CSQRT" ); + case CPU_OP_CPOW: return( "CPOW" ); + case CPU_OP_CREAL: return( "CREAL" ); + case CPU_OP_CIMAG: return( "CIMAG" ); + case CPU_OP_CRTOC: return( "CRTOC" ); + case CPU_OP_CGEN: return( "CGEN" ); + case CPU_OP_CPOLAR: return( "CPOLAR" ); + case CPU_OP_CCONJ: return( "CCONJ" ); + case CPU_OP_CABS: return( "CABS" ); + case CPU_OP_CNORM: return( "CNORM" ); + case CPU_OP_CARG: return( "CARG" ); + case CPU_OP_CISEQ: return( "CISEQ" ); + case CPU_OP_CISNE: return( "CISNE" ); + case CPU_OP_CCPY: return( "CCPY" ); + case CPU_OP_CCVT: return( "CCVT" ); + case CPU_OP_JMP: return( "JMP" ); + case CPU_OP_JSET: return( "JSET" ); + case CPU_OP_JCLR: return( "JCLR" ); + case CPU_OP_JMPP: return( "JMPP" ); + case CPU_OP_CALL: return( "CALL" ); + case CPU_OP_RET: return( "RET" ); + case CPU_OP_CALLP: return( "CALLP" ); + case CPU_OP_PUSH: return( "PUSH" ); + case CPU_OP_POP: return( "POP" ); + case CPU_OP_LDSP: return( "LDSP" ); + case CPU_OP_STSP: return( "STSP" ); + case CPU_OP_ADJSP: return( "ADJSP" ); + case CPU_OP_PUSHFP: return( "PUSHFP" ); + case CPU_OP_SETFP: return( "SETFP" ); + case CPU_OP_RESTSP: return( "RESTSP" ); + case CPU_OP_POPFP: return( "POPFP" ); + case CPU_OP_LDFP: return( "LDFP" ); + case CPU_OP_STFP: return( "STFP" ); + case CPU_OP_LEAFP: return( "LEAFP" ); + case CPU_OP_SETSP: return( "SETSP" ); + default: return( NULL ); + } + +} /* End of opcode_name() */ + + +static const char * +rconst_name( unsigned int value ) +{ + switch( value ) + { + case CPU_RCONST_ZERO: return( "ZERO" ); + case CPU_RCONST_HALF: return( "HALF" ); + case CPU_RCONST_ONE: return( "ONE" ); + case CPU_RCONST_TWO: return( "TWO" ); + case CPU_RCONST_TEN: return( "TEN" ); + case CPU_RCONST_MTEN: return( "MTEN" ); + case CPU_RCONST_32: return( "32" ); + case CPU_RCONST_PI: return( "PI" ); + case CPU_RCONST_E: return( "E" ); + case CPU_RCONST_1_LN2: return( "1_LN2" ); + case CPU_RCONST_LN2: return( "LN2" ); + case CPU_RCONST_1_LN10: return( "1_LN10" ); + case CPU_RCONST_LN10: return( "LN10" ); + case CPU_RCONST_1_LG2: return( "1_LG2" ); + case CPU_RCONST_LG2: return( "LG2" ); + case CPU_RCONST_PI_2: return( "PI_2" ); + case CPU_RCONST_PI_3: return( "PI_3" ); + case CPU_RCONST_PI_4: return( "PI_4" ); + case CPU_RCONST_PI_5: return( "PI_5" ); + case CPU_RCONST_PI_6: return( "PI_6" ); + case CPU_RCONST_1_PI: return( "1_PI" ); + case CPU_RCONST_2_PI: return( "2_PI" ); + case CPU_RCONST_3_PI: return( "3_PI" ); + case CPU_RCONST_4_PI: return( "4_PI" ); + case CPU_RCONST_5_PI: return( "5_PI" ); + case CPU_RCONST_2PI: return( "2PI" ); + case CPU_RCONST_3PI: return( "3PI" ); + case CPU_RCONST_1_2PI: return( "1_2PI" ); + case CPU_RCONST_1_3PI: return( "1_3PI" ); + case CPU_RCONST_1_4PI: return( "1_4PI" ); + case CPU_RCONST_1_5PI: return( "1_5PI" ); + case CPU_RCONST_1_6PI: return( "1_6PI" ); + case CPU_RCONST_3PI_4: return( "3PI_4" ); + case CPU_RCONST_SQRTPI: return( "SQRTPI" ); + case CPU_RCONST_1_SQRTPI: return( "1_SQRTPI" ); + case CPU_RCONST_2_SQRTPI: return( "2_SQRTPI" ); + case CPU_RCONST_SQRT2: return( "SQRT2" ); + case CPU_RCONST_1_SQRT2: return( "1_SQRT2" ); + case CPU_RCONST_SQRT3: return( "SQRT3" ); + case CPU_RCONST_1_SQRT3: return( "1_SQRT3" ); + case CPU_RCONST_DEGREE: return( "DEGREE" ); + case CPU_RCONST_1_DEGREE: return( "1_DEGREE" ); + case CPU_RCONST_GOLDENRATIO: return( "GOLDENRATIO" ); + case CPU_RCONST_EULERGAMMA: return( "EULERGAMMA" ); + case CPU_RCONST_CATALAN: return( "CATALAN" ); + case CPU_RCONST_EPSILON: return( "EPSILON" ); + case CPU_RCONST_IND: return( "IND" ); + case CPU_RCONST_PNAN: return( "PNAN" ); + case CPU_RCONST_NNAN: return( "NNAN" ); + case CPU_RCONST_PNANMAX: return( "PNANMAX" ); + case CPU_RCONST_NNANMAX: return( "NNANMAX" ); + case CPU_RCONST_PNANMIN: return( "PNANMIN" ); + case CPU_RCONST_NNANMIN: return( "NNANMIN" ); + case CPU_RCONST_PINF: return( "PINF" ); + case CPU_RCONST_NINF: return( "NINF" ); + case CPU_RCONST_PREALMIN: return( "PREALMIN" ); + case CPU_RCONST_NREALMIN: return( "NREALMIN" ); + case CPU_RCONST_PREALMAX: return( "PREALMAX" ); + case CPU_RCONST_NREALMAX: return( "NREALMAX" ); + case CPU_RCONST_PZERO: return( "PZERO" ); + default: return( NULL ); + } + +} /* End of rconst_name() */ + + +static const char * +pswbit_name( unsigned int value ) +{ + switch( value ) + { + case CPU_PSWBIT_AF: return( "AF" ); + case CPU_PSWBIT_CF: return( "CF" ); + case CPU_PSWBIT_OF: return( "OF" ); + case CPU_PSWBIT_SF: return( "SF" ); + case CPU_PSWBIT_PF: return( "PF" ); + case CPU_PSWBIT_ZF: return( "ZF" ); + case CPU_PSWBIT_RF: return( "RF" ); + case CPU_PSWBIT_VF: return( "VF" ); + case CPU_PSWBIT_DOMF: return( "DOMF" ); + case CPU_PSWBIT_SNGF: return( "SNGF" ); + case CPU_PSWBIT_OVFF: return( "OVFF" ); + case CPU_PSWBIT_UDFF: return( "UDFF" ); + case CPU_PSWBIT_TLSF: return( "TLSF" ); + case CPU_PSWBIT_PLSF: return( "PLSF" ); + case CPU_PSWBIT_INDF: return( "INDF" ); + case CPU_PSWBIT_INXF: return( "INXF" ); + case CPU_PSWBIT_HF: return( "HF" ); + case CPU_PSWBIT_TF: return( "TF" ); + case CPU_PSWBIT_IF: return( "IF" ); + default: return( NULL ); + } + +} /* End of pswbit_name() */ + + +static int +opcode_complex_operand( cpu_opcode opcode, unsigned int operand ) +{ + switch( opcode ) + { + case CPU_OP_LDC: + return( operand == 0 ); + + case CPU_OP_CADD: case CPU_OP_CSUB: case CPU_OP_CMUL: + case CPU_OP_CDIV: case CPU_OP_CPOW: + return( operand < 3 ); + + case CPU_OP_CEXP: case CPU_OP_CSIN: case CPU_OP_CCOS: + case CPU_OP_CSINH: case CPU_OP_CCOSH: case CPU_OP_CLOG: + case CPU_OP_CSQRT: case CPU_OP_CCONJ: case CPU_OP_CCPY: + case CPU_OP_CCVT: + return( operand < 2 ); + + case CPU_OP_CREAL: case CPU_OP_CIMAG: case CPU_OP_CABS: + case CPU_OP_CNORM: case CPU_OP_CARG: + return( operand == 1 ); + + case CPU_OP_CRTOC: + return( operand == 0 ); + + case CPU_OP_CGEN: case CPU_OP_CPOLAR: + return( operand == 0 ); + + case CPU_OP_CISEQ: case CPU_OP_CISNE: + return( operand == 1 || operand == 2 ); + + default: + return( 0 ); + } + +} /* End of opcode_complex_operand() */ + + +static __mpu_uint64_t +symbol_section_offset( const mcpu_objdump_file *file, + const mcpu_objdump_shdr *section, + __mpu_uint64_t value ) +{ + if( file->type == MELF_ET_EXEC && + MELF_SHF_MCPU_HAS_SEG( section->flags ) ) + { + __mpu_uint64_t base = MELF_MCPU_PTR64_OFFSET( section->addr ); + __mpu_uint64_t pos = MELF_MCPU_PTR64_OFFSET( value ); + if( pos >= base ) + return( pos - base ); + } + return( value ); + +} /* End of symbol_section_offset() */ + + +static const char * +symbol_at( const mcpu_objdump_file *file, size_t section_index, + __mpu_uint64_t offset ) +{ + unsigned int pass; + + for( pass = 0; pass < 2; ++pass ) + { + __mpu_uint32_t wanted = pass == 0 ? MELF_SHT_SYMTAB : MELF_SHT_DYNSYM; + size_t i; + + for( i = 0; i < file->shnum; ++i ) + { + const mcpu_objdump_shdr *sh = &file->shdr[i]; + const mcpu_objdump_shdr *str; + const unsigned char *strtab; + size_t strsize; + size_t count; + size_t k; + + if( sh->type != wanted ) + continue; + if( sh->entsize != MELF64_SYM_SIZE || sh->size % MELF64_SYM_SIZE || + sh->link >= file->shnum ) + continue; + str = &file->shdr[sh->link]; + if( str->type != MELF_SHT_STRTAB || str->size > SIZE_MAX || + !cpu_melf_span( file->size, str->offset, str->size ) ) + continue; + strtab = file->data + (size_t)str->offset; + strsize = (size_t)str->size; + count = (size_t)(sh->size / MELF64_SYM_SIZE); + + for( k = 1; k < count; ++k ) + { + const unsigned char *p = + file->data + (size_t)sh->offset + k * MELF64_SYM_SIZE; + __mpu_uint16_t shndx = + cpu_melf_get16( p + MELF64_SYM_ST_SHNDX_OFF, file->encoding ); + __mpu_uint64_t value = + cpu_melf_get64( p + MELF64_SYM_ST_VALUE_OFF, file->encoding ); + __mpu_uint32_t name_off = + cpu_melf_get32( p + MELF64_SYM_ST_NAME_OFF, file->encoding ); + const char *name; + + if( shndx != section_index ) + continue; + if( symbol_section_offset( file, &file->shdr[section_index], value ) != + offset ) + continue; + name = string_at( strtab, strsize, name_off ); + if( name && *name ) + return( name ); + } + } + } + + return( NULL ); + +} /* End of symbol_at() */ + + +static const char * +code_symbol_at( const mcpu_objdump_file *file, __mpu_uint64_t target ) +{ + unsigned int pass; + + if( file->type == MELF_ET_REL ) + return( NULL ); + + for( pass = 0; pass < 2; ++pass ) + { + __mpu_uint32_t wanted = pass == 0 ? MELF_SHT_SYMTAB : MELF_SHT_DYNSYM; + size_t i; + + for( i = 0; i < file->shnum; ++i ) + { + const mcpu_objdump_shdr *sh = &file->shdr[i]; + const mcpu_objdump_shdr *str; + const unsigned char *strtab; + size_t strsize; + size_t count; + size_t k; + + if( sh->type != wanted ) + continue; + if( sh->entsize != MELF64_SYM_SIZE || sh->size % MELF64_SYM_SIZE || + sh->link >= file->shnum ) + continue; + str = &file->shdr[sh->link]; + if( str->type != MELF_SHT_STRTAB || str->size > SIZE_MAX || + !cpu_melf_span( file->size, str->offset, str->size ) ) + continue; + strtab = file->data + (size_t)str->offset; + strsize = (size_t)str->size; + count = (size_t)(sh->size / MELF64_SYM_SIZE); + + for( k = 1; k < count; ++k ) + { + const unsigned char *p = + file->data + (size_t)sh->offset + k * MELF64_SYM_SIZE; + __mpu_uint16_t shndx = + cpu_melf_get16( p + MELF64_SYM_ST_SHNDX_OFF, file->encoding ); + __mpu_uint64_t value = + cpu_melf_get64( p + MELF64_SYM_ST_VALUE_OFF, file->encoding ); + __mpu_uint32_t name_off = + cpu_melf_get32( p + MELF64_SYM_ST_NAME_OFF, file->encoding ); + __mpu_uint64_t code_value; + const char *name; + + if( shndx >= file->shnum || + !(file->shdr[shndx].flags & MELF_SHF_EXECINSTR) ) + continue; + + if( file->type == MELF_ET_EXEC ) + code_value = MELF_MCPU_PTR64_OFFSET( value ); + else if( MELF_SHF_MCPU_HAS_SEG( file->shdr[shndx].flags ) ) + code_value = MELF_MCPU_PTR64_OFFSET( file->shdr[shndx].addr ) + value; + else + code_value = value; + + if( code_value != target ) + continue; + name = string_at( strtab, strsize, name_off ); + if( name && *name ) + return( name ); + } + } + } + + return( NULL ); + +} /* End of code_symbol_at() */ + + +static void +print_section_headers( const mcpu_objdump_file *file, + const mcpu_objdump_options *options ) +{ + size_t i; + + printf( "Sections:\n" ); + printf( "Idx Name Size VMA " + "File off Algn Type\n" ); + for( i = 0; i < file->shnum; ++i ) + { + const mcpu_objdump_shdr *sh = &file->shdr[i]; + + if( !section_selected( file, i, options ) ) + continue; + printf( "%3zu %-20s %016" PRIx64 " %016" PRIx64 " %016" PRIx64 + " %4" PRIu64 " %s", + i, section_name( file, i ), + (uint64_t)sh->size, (uint64_t)sh->addr, + (uint64_t)sh->offset, (uint64_t)sh->align, + section_type_name( sh->type ) ); + if( sh->flags & MELF_SHF_ALLOC ) printf( " ALLOC" ); + if( sh->flags & MELF_SHF_WRITE ) printf( " WRITE" ); + if( sh->flags & MELF_SHF_EXECINSTR ) printf( " CODE" ); + printf( "\n" ); + } + +} /* End of print_section_headers() */ + + +static int +print_symbol_table( const mcpu_objdump_file *file, __mpu_uint32_t table_type, + const mcpu_objdump_options *options, char *err, size_t errsz ) +{ + size_t i; + int printed = 0; + + for( i = 0; i < file->shnum; ++i ) + { + const mcpu_objdump_shdr *sh = &file->shdr[i]; + const mcpu_objdump_shdr *str; + const unsigned char *strtab; + size_t strsize; + size_t count; + size_t k; + + if( sh->type != table_type ) + continue; + if( sh->entsize != MELF64_SYM_SIZE || sh->size % MELF64_SYM_SIZE || + sh->size > SIZE_MAX || + !cpu_melf_span( file->size, sh->offset, sh->size ) || + sh->link >= file->shnum ) + { + snprintf( err, errsz, "malformed symbol table" ); + return( -1 ); + } + str = &file->shdr[sh->link]; + if( str->type != MELF_SHT_STRTAB || str->size > SIZE_MAX || + !cpu_melf_span( file->size, str->offset, str->size ) ) + { + snprintf( err, errsz, "malformed symbol string table" ); + return( -1 ); + } + strtab = file->data + (size_t)str->offset; + strsize = (size_t)str->size; + count = (size_t)(sh->size / MELF64_SYM_SIZE); + + if( !printed ) + { + printf( "%sSYMBOL TABLE:\n", + table_type == MELF_SHT_DYNSYM ? "DYNAMIC " : "" ); + printf( "Value Size Bind Type Ndx Name\n" ); + printed = 1; + } + + for( k = 1; k < count; ++k ) + { + const unsigned char *p = + file->data + (size_t)sh->offset + k * MELF64_SYM_SIZE; + __mpu_uint32_t name_off = + cpu_melf_get32( p + MELF64_SYM_ST_NAME_OFF, file->encoding ); + unsigned int bind = MELF64_ST_BIND( p[MELF64_SYM_ST_INFO_OFF] ); + unsigned int type = MELF64_ST_TYPE( p[MELF64_SYM_ST_INFO_OFF] ); + __mpu_uint16_t shndx = + cpu_melf_get16( p + MELF64_SYM_ST_SHNDX_OFF, file->encoding ); + __mpu_uint64_t value = + cpu_melf_get64( p + MELF64_SYM_ST_VALUE_OFF, file->encoding ); + __mpu_uint64_t ssize = + cpu_melf_get64( p + MELF64_SYM_ST_SIZE_OFF, file->encoding ); + const char *name = string_at( strtab, strsize, name_off ); + char ndx[32]; + + if( !name ) + { + snprintf( err, errsz, "symbol has invalid name offset" ); + return( -1 ); + } + if( shndx < file->shnum && !section_selected( file, shndx, options ) ) + continue; + + if( shndx == MELF_SHN_UNDEF ) + strcpy( ndx, "UND" ); + else if( shndx == MELF_SHN_ABS ) + strcpy( ndx, "ABS" ); + else if( shndx == MELF_SHN_COMMON ) + strcpy( ndx, "COM" ); + else + snprintf( ndx, sizeof( ndx ), "%u", (unsigned int)shndx ); + + printf( "%016" PRIx64 " %8" PRIu64 " %-6s %-8s %3s %s\n", + (uint64_t)value, (uint64_t)ssize, + symbol_bind_name( bind ), symbol_type_name( type ), + ndx, name ); + } + } + + return( 0 ); + +} /* End of print_symbol_table() */ + + +static int +relocation_symbol( const mcpu_objdump_file *file, + const mcpu_objdump_shdr *rela, __mpu_uint64_t symidx, + const char **name, char *err, size_t errsz ) +{ + const mcpu_objdump_shdr *symtab; + const mcpu_objdump_shdr *str; + const unsigned char *p; + const unsigned char *strtab; + size_t count; + __mpu_uint32_t name_off; + + *name = ""; + if( rela->link >= file->shnum ) + { + snprintf( err, errsz, "relocation section has invalid symbol-table link" ); + return( -1 ); + } + symtab = &file->shdr[rela->link]; + if( (symtab->type != MELF_SHT_SYMTAB && symtab->type != MELF_SHT_DYNSYM) || + symtab->entsize != MELF64_SYM_SIZE || + symtab->size % MELF64_SYM_SIZE || + symtab->link >= file->shnum ) + { + snprintf( err, errsz, "relocation section has malformed symbol table" ); + return( -1 ); + } + count = (size_t)(symtab->size / MELF64_SYM_SIZE); + if( symidx >= count ) + { + snprintf( err, errsz, "relocation has invalid symbol index" ); + return( -1 ); + } + str = &file->shdr[symtab->link]; + if( str->type != MELF_SHT_STRTAB || str->size > SIZE_MAX || + !cpu_melf_span( file->size, str->offset, str->size ) ) + { + snprintf( err, errsz, "relocation symbol table has malformed strings" ); + return( -1 ); + } + p = file->data + (size_t)symtab->offset + + (size_t)symidx * MELF64_SYM_SIZE; + name_off = cpu_melf_get32( p + MELF64_SYM_ST_NAME_OFF, file->encoding ); + strtab = file->data + (size_t)str->offset; + *name = string_at( strtab, (size_t)str->size, name_off ); + if( !*name ) + { + snprintf( err, errsz, "relocation symbol has invalid name offset" ); + return( -1 ); + } + return( 0 ); + +} /* End of relocation_symbol() */ + + +static int +print_relocations( const mcpu_objdump_file *file, + const mcpu_objdump_options *options, + int skip_disassembled, char *err, size_t errsz ) +{ + size_t i; + + for( i = 0; i < file->shnum; ++i ) + { + const mcpu_objdump_shdr *sh = &file->shdr[i]; + size_t count; + size_t k; + + if( sh->type != MELF_SHT_RELA ) + continue; + if( sh->info >= file->shnum ) + { + snprintf( err, errsz, "relocation section has invalid target section" ); + return( -1 ); + } + if( !section_selected( file, sh->info, options ) ) + continue; + if( skip_disassembled ) + { + const mcpu_objdump_shdr *target = &file->shdr[sh->info]; + int eligible; + + if( options->disassemble_all ) + eligible = target->type == MELF_SHT_PROGBITS && target->size; + else + eligible = target->type == MELF_SHT_PROGBITS && target->size && + (target->flags & MELF_SHF_EXECINSTR) != 0; + if( eligible ) + continue; + } + if( sh->entsize != MELF64_RELA_SIZE || sh->size % MELF64_RELA_SIZE || + sh->size > SIZE_MAX || + !cpu_melf_span( file->size, sh->offset, sh->size ) ) + { + snprintf( err, errsz, "malformed relocation section" ); + return( -1 ); + } + + count = (size_t)(sh->size / MELF64_RELA_SIZE); + printf( "RELOCATION RECORDS FOR [%s]:\n", + section_name( file, sh->info ) ); + printf( "OFFSET TYPE VALUE\n" ); + for( k = 0; k < count; ++k ) + { + const unsigned char *p = + file->data + (size_t)sh->offset + k * MELF64_RELA_SIZE; + __mpu_uint64_t offset = + cpu_melf_get64( p + MELF64_RELA_R_OFFSET_OFF, file->encoding ); + __mpu_uint64_t info = + cpu_melf_get64( p + MELF64_RELA_R_INFO_OFF, file->encoding ); + __mpu_uint64_t addbits = + cpu_melf_get64( p + MELF64_RELA_R_ADDEND_OFF, file->encoding ); + __mpu_int64_t addend; + __mpu_uint64_t symidx = MELF64_R_SYM( info ); + unsigned int rtype = MELF64_R_TYPE( info ); + const char *name; + + memcpy( &addend, &addbits, sizeof( addend ) ); + if( relocation_symbol( file, sh, symidx, &name, err, errsz ) ) + return( -1 ); + printf( "%016" PRIx64 " %-23s %s", + (uint64_t)offset, relocation_type_name( rtype ), name ); + if( addend > 0 ) + printf( "+0x%" PRIx64, (uint64_t)addend ); + else if( addend < 0 ) + { + __mpu_uint64_t mag = + (__mpu_uint64_t)(-(addend + 1)) + __mpu_UINT64_C( 1 ); + printf( "-0x%" PRIx64, (uint64_t)mag ); + } + printf( "\n" ); + } + printf( "\n" ); + } + + return( 0 ); + +} /* End of print_relocations() */ + + +static void +print_contents( const mcpu_objdump_file *file, + const mcpu_objdump_options *options ) +{ + size_t i; + + for( i = 0; i < file->shnum; ++i ) + { + const mcpu_objdump_shdr *sh = &file->shdr[i]; + size_t off; + + if( !section_selected( file, i, options ) || + sh->type == MELF_SHT_NULL || sh->type == MELF_SHT_NOBITS || + !sh->size ) + continue; + + printf( "Contents of section %s:\n", section_name( file, i ) ); + for( off = 0; off < (size_t)sh->size; off += 16 ) + { + size_t k; + size_t n = (size_t)sh->size - off; + __mpu_uint64_t addr = sh->addr + off; + + if( n > 16 ) n = 16; + printf( " %016" PRIx64 " ", (uint64_t)addr ); + for( k = 0; k < 16; ++k ) + { + if( k < n ) + printf( "%02x", file->data[(size_t)sh->offset + off + k] ); + else + printf( " " ); + if( (k & 3U) == 3U ) putchar( ' ' ); + } + putchar( ' ' ); + for( k = 0; k < n; ++k ) + { + unsigned char ch = file->data[(size_t)sh->offset + off + k]; + putchar( isprint( ch ) ? ch : '.' ); + } + putchar( '\n' ); + } + printf( "\n" ); + } + +} /* End of print_contents() */ + + +static const char * +instruction_relocation_symbol( const mcpu_objdump_file *file, + size_t section_index, + __mpu_uint64_t first, + __mpu_uint64_t last ) +{ + size_t i; + + for( i = 0; i < file->shnum; ++i ) + { + const mcpu_objdump_shdr *sh = &file->shdr[i]; + size_t count; + size_t k; + + if( sh->type != MELF_SHT_RELA || sh->info != section_index || + sh->entsize != MELF64_RELA_SIZE || sh->size % MELF64_RELA_SIZE ) + continue; + + count = (size_t)(sh->size / MELF64_RELA_SIZE); + for( k = 0; k < count; ++k ) + { + const unsigned char *p = + file->data + (size_t)sh->offset + k * MELF64_RELA_SIZE; + __mpu_uint64_t off = + cpu_melf_get64( p + MELF64_RELA_R_OFFSET_OFF, file->encoding ); + __mpu_uint64_t info = + cpu_melf_get64( p + MELF64_RELA_R_INFO_OFF, file->encoding ); + const char *name; + char err[128]; + + if( off < first || off >= last ) + continue; + if( !relocation_symbol( file, sh, MELF64_R_SYM( info ), + &name, err, sizeof( err ) ) && + name && *name ) + return( name ); + } + } + + return( NULL ); + +} /* End of instruction_relocation_symbol() */ + + +static void +format_operand( const mcpu_objdump_file *file, size_t section_index, + __mpu_uint64_t instruction_offset, + __mpu_uint64_t instruction_length, + cpu_opcode opcode, unsigned int index, + const cpu_operand *operand, char *buf, size_t bufsz ) +{ + const char *name; + const char *relname = + instruction_relocation_symbol( file, section_index, instruction_offset, + instruction_offset + instruction_length ); + + switch( operand->type ) + { + case CPU_OPERAND_REGISTER: + snprintf( buf, bufsz, "%c%u@0x%05" PRIx32, + opcode_complex_operand( opcode, index ) ? 'c' : 'r', + operand->value.reg.bits, + (uint32_t)operand->value.reg.address ); + break; + + case CPU_OPERAND_IMM64: + if( opcode == CPU_OP_LDI ) + { + __mpu_int64_t v; + memcpy( &v, &operand->value.imm64, sizeof( v ) ); + snprintf( buf, bufsz, "%" PRId64, (int64_t)v ); + } + else if( opcode == CPU_OP_BMOVI ) + snprintf( buf, bufsz, "%" PRIu64, + (uint64_t)operand->value.imm64 ); + else + snprintf( buf, bufsz, "0x%016" PRIx64, + (uint64_t)operand->value.imm64 ); + break; + + case CPU_OPERAND_ADDR64: + if( relname ) + snprintf( buf, bufsz, "0x%016" PRIx64 " <%s>", + (uint64_t)operand->value.addr64, relname ); + else + snprintf( buf, bufsz, "0x%016" PRIx64, + (uint64_t)operand->value.addr64 ); + break; + + case CPU_OPERAND_CODE64: + name = relname ? relname : code_symbol_at( file, operand->value.code64 ); + if( name ) + snprintf( buf, bufsz, "0x%016" PRIx64 " <%s>", + (uint64_t)operand->value.code64, name ); + else + snprintf( buf, bufsz, "0x%016" PRIx64, + (uint64_t)operand->value.code64 ); + break; + + case CPU_OPERAND_OFF64: + snprintf( buf, bufsz, "%" PRId64, (int64_t)operand->value.off64 ); + break; + + case CPU_OPERAND_CONST8: + name = rconst_name( operand->value.const8 ); + if( name ) snprintf( buf, bufsz, "%s", name ); + else snprintf( buf, bufsz, "0x%02x", + (unsigned int)operand->value.const8 ); + break; + + case CPU_OPERAND_PSWBIT8: + name = pswbit_name( operand->value.pswbit8 ); + if( name ) snprintf( buf, bufsz, "%s", name ); + else snprintf( buf, bufsz, "%u", + (unsigned int)operand->value.pswbit8 ); + break; + + case CPU_OPERAND_PTR64: + if( relname ) + snprintf( buf, bufsz, "0x%016" PRIx64 " <%s>", + (uint64_t)operand->value.ptr64, relname ); + else + snprintf( buf, bufsz, "0x%016" PRIx64, + (uint64_t)operand->value.ptr64 ); + break; + + default: + *buf = 0; + break; + } + +} /* End of format_operand() */ + + +static int +print_instruction_relocations( const mcpu_objdump_file *file, + size_t section_index, __mpu_uint64_t first, + __mpu_uint64_t last, char *err, size_t errsz ) +{ + size_t i; + + for( i = 0; i < file->shnum; ++i ) + { + const mcpu_objdump_shdr *sh = &file->shdr[i]; + size_t count; + size_t k; + + if( sh->type != MELF_SHT_RELA || sh->info != section_index ) + continue; + if( sh->entsize != MELF64_RELA_SIZE || sh->size % MELF64_RELA_SIZE ) + continue; + count = (size_t)(sh->size / MELF64_RELA_SIZE); + for( k = 0; k < count; ++k ) + { + const unsigned char *p = + file->data + (size_t)sh->offset + k * MELF64_RELA_SIZE; + __mpu_uint64_t off = + cpu_melf_get64( p + MELF64_RELA_R_OFFSET_OFF, file->encoding ); + __mpu_uint64_t info = + cpu_melf_get64( p + MELF64_RELA_R_INFO_OFF, file->encoding ); + __mpu_uint64_t addbits = + cpu_melf_get64( p + MELF64_RELA_R_ADDEND_OFF, file->encoding ); + __mpu_int64_t addend; + const char *name; + + if( off < first || off >= last ) + continue; + memcpy( &addend, &addbits, sizeof( addend ) ); + if( relocation_symbol( file, sh, MELF64_R_SYM( info ), + &name, err, errsz ) ) + return( -1 ); + printf( "\t\t%016" PRIx64 ": %-23s %s", + (uint64_t)off, + relocation_type_name( MELF64_R_TYPE( info ) ), name ); + if( addend > 0 ) + printf( "+0x%" PRIx64, (uint64_t)addend ); + else if( addend < 0 ) + { + __mpu_uint64_t mag = + (__mpu_uint64_t)(-(addend + 1)) + __mpu_UINT64_C( 1 ); + printf( "-0x%" PRIx64, (uint64_t)mag ); + } + putchar( '\n' ); + } + } + + return( 0 ); + +} /* End of print_instruction_relocations() */ + + +static int +disassemble_section( const mcpu_objdump_file *file, size_t section_index, + const mcpu_objdump_options *options, + char *err, size_t errsz ) +{ + const mcpu_objdump_shdr *sh = &file->shdr[section_index]; + const unsigned char *program = file->data + (size_t)sh->offset; + __mpu_uint64_t off = 0; + + printf( "Disassembly of section %s:\n\n", section_name( file, section_index ) ); + + while( off < sh->size ) + { + cpu_instruction insn; + const char *label = symbol_at( file, section_index, off ); + int rc; + + if( label ) + printf( "%016" PRIx64 " <%s>:\n", + (uint64_t)(sh->addr + off), label ); + + rc = __cpu_decode_at( program, sh->size, off, &insn ); + if( rc == CPU_EXEC_SUCCESS ) + { + const char *mn = opcode_name( insn.opcode ); + unsigned int k; + char operand[CPU_INSTRUCTION_MAX_OPERANDS][128]; + size_t b; + + if( !mn || !insn.length || insn.length > sh->size - off ) + rc = CPU_EXEC_DECODE_FAULT; + else + { + printf( " %016" PRIx64 ": ", (uint64_t)(sh->addr + off) ); + for( b = 0; b < 16; ++b ) + { + if( b < insn.length ) + printf( "%02x ", program[(size_t)off + b] ); + else + printf( " " ); + } + printf( " %-10s", mn ); + for( k = 0; k < CPU_INSTRUCTION_MAX_OPERANDS; ++k ) + { + if( insn.operand[k].type == CPU_OPERAND_NONE ) + break; + format_operand( file, section_index, off, insn.length, + insn.opcode, k, &insn.operand[k], + operand[k], sizeof( operand[k] ) ); + printf( "%s%s", k ? ", " : " ", operand[k] ); + } + putchar( '\n' ); + + if( options->relocations && + print_instruction_relocations( file, section_index, off, + off + insn.length, err, errsz ) ) + return( -1 ); + off += insn.length; + continue; + } + } + + printf( " %016" PRIx64 ": %02x", + (uint64_t)(sh->addr + off), program[(size_t)off] ); + printf( "%*s.byte 0x%02x\n", 47, "", + program[(size_t)off] ); + ++off; + } + printf( "\n" ); + return( 0 ); + +} /* End of disassemble_section() */ + + +static int +print_disassembly( const mcpu_objdump_file *file, + const mcpu_objdump_options *options, + char *err, size_t errsz ) +{ + size_t i; + + for( i = 0; i < file->shnum; ++i ) + { + const mcpu_objdump_shdr *sh = &file->shdr[i]; + int eligible; + + if( !section_selected( file, i, options ) || !sh->size || + sh->type == MELF_SHT_NOBITS ) + continue; + + if( options->disassemble_all ) + eligible = sh->type == MELF_SHT_PROGBITS; + else + eligible = sh->type == MELF_SHT_PROGBITS && + (sh->flags & MELF_SHF_EXECINSTR) != 0; + if( !eligible ) + continue; + + if( disassemble_section( file, i, options, err, errsz ) ) + return( -1 ); + } + + return( 0 ); + +} /* End of print_disassembly() */ + + +static int +process_melf_data( const char *name, const unsigned char *data, size_t size, + const mcpu_objdump_options *options ) +{ + mcpu_objdump_file file; + char err[256]; + + if( objdump_file_open_data( name, data, size, &file, err, sizeof( err ) ) ) + { + fprintf( stderr, "mcpu-objdump: %s: %s\n", name, err ); + return( 1 ); + } + + printf( "\n%s: file format melf64-mcpu\n\n", name ); + + if( options->section_headers ) + print_section_headers( &file, options ); + + if( options->symbols ) + { + if( options->section_headers ) putchar( '\n' ); + if( print_symbol_table( &file, MELF_SHT_SYMTAB, options, + err, sizeof( err ) ) ) + goto fail; + } + if( options->dynamic_symbols ) + { + if( options->section_headers || options->symbols ) putchar( '\n' ); + if( print_symbol_table( &file, MELF_SHT_DYNSYM, options, + err, sizeof( err ) ) ) + goto fail; + } + + if( options->full_contents ) + print_contents( &file, options ); + + if( options->relocations ) + { + if( print_relocations( &file, options, + options->disassemble || options->disassemble_all, + err, sizeof( err ) ) ) + goto fail; + } + + if( options->disassemble || options->disassemble_all ) + { + if( print_disassembly( &file, options, err, sizeof( err ) ) ) + goto fail; + } + + objdump_file_free( &file ); + return( 0 ); + +fail: + fprintf( stderr, "mcpu-objdump: %s: %s\n", name, err ); + objdump_file_free( &file ); + return( 1 ); + +} /* End of process_melf_data() */ + + +static int +process_archive( const char *path, const mcpu_objdump_options *options ) +{ + mcpu_archive archive; + char err[256]; + size_t i; + int status = 0; + + mcpu_archive_init( &archive ); + if( mcpu_archive_read( path, &archive, err, sizeof( err ) ) ) + { + fprintf( stderr, "mcpu-objdump: %s: %s\n", path, err ); + return( 1 ); + } + + for( i = 0; i < archive.member_count; ++i ) + { + char *display; + size_t n = strlen( path ) + strlen( archive.members[i].name ) + 3; + + display = (char *)malloc( n ); + if( !display ) + { + fprintf( stderr, "mcpu-objdump: %s: out of memory\n", path ); + status = 1; + break; + } + snprintf( display, n, "%s(%s)", path, archive.members[i].name ); + if( process_melf_data( display, archive.members[i].data, + archive.members[i].size, options ) ) + status = 1; + free( display ); + } + + mcpu_archive_free( &archive ); + return( status ); + +} /* End of process_archive() */ + + +static int +process_path( const char *path, const mcpu_objdump_options *options ) +{ + unsigned char *data = NULL; + size_t size = 0; + char err[256]; + int status; + + if( cpu_melf_readall( path, &data, &size, err, sizeof( err ) ) ) + { + fprintf( stderr, "mcpu-objdump: %s\n", err ); + return( 1 ); + } + + if( size >= 8 && !memcmp( data, "!<arch>\n", 8 ) ) + { + free( data ); + return( process_archive( path, options ) ); + } + + status = process_melf_data( path, data, size, options ); + free( data ); + return( status ); + +} /* End of process_path() */ + + +static int +add_section( mcpu_objdump_options *options, const char *name ) +{ + char **q; + char *copy; + + if( !name || !*name ) + return( -1 ); + q = (char **)realloc( options->sections, + (options->section_count + 1) * sizeof( *q ) ); + if( !q ) + return( -1 ); + copy = strdup( name ); + if( !copy ) + return( -1 ); + options->sections = q; + options->sections[options->section_count++] = copy; + return( 0 ); + +} /* End of add_section() */ + + +static void +free_options( mcpu_objdump_options *options ) +{ + size_t i; + + for( i = 0; i < options->section_count; ++i ) + free( options->sections[i] ); + free( options->sections ); + +} /* End of free_options() */ + + +static int +parse_short_options( const char *arg, mcpu_objdump_options *options, + const char **section_arg ) +{ + const char *p = arg + 1; + + while( *p ) + { + switch( *p ) + { + case 'd': options->disassemble = 1; ++p; break; + case 'D': options->disassemble_all = 1; ++p; break; + case 'r': options->relocations = 1; ++p; break; + case 's': options->full_contents = 1; ++p; break; + case 't': options->symbols = 1; ++p; break; + case 'T': options->dynamic_symbols = 1; ++p; break; + case 'h': options->section_headers = 1; ++p; break; + + case 'j': + ++p; + if( *p ) + { + *section_arg = p; + return( 1 ); + } + return( 2 ); + + default: + return( -1 ); + } + } + return( 0 ); + +} /* End of parse_short_options() */ + + +int +main( int argc, char **argv ) +{ + mcpu_objdump_options options; + int first_file = -1; + int status = 0; + int i; + + memset( &options, 0, sizeof( options ) ); + + for( i = 1; i < argc; ++i ) + { + const char *arg = argv[i]; + + if( !strcmp( arg, "--" ) ) + { + first_file = i + 1; + break; + } + if( arg[0] != '-' || !arg[1] ) + { + first_file = i; + break; + } + + if( !strcmp( arg, "--help" ) ) + { + usage( stdout, argv[0] ); + free_options( &options ); + return( 0 ); + } + if( !strcmp( arg, "--version" ) || + !strcmp( arg, "--dump-version" ) ) + { + mcpu_version_action version_action; + int rc; + + mcpu_version_parse_option( arg, &version_action ); + rc = mcpu_version_print( argv[0], version_action ); + free_options( &options ); + return( rc == 0 ? 0 : 1 ); + } + if( !strcmp( arg, "--disassemble" ) ) options.disassemble = 1; + else if( !strcmp( arg, "--disassemble-all" ) ) options.disassemble_all = 1; + else if( !strcmp( arg, "--reloc" ) ) options.relocations = 1; + else if( !strcmp( arg, "--full-contents" ) ) options.full_contents = 1; + else if( !strcmp( arg, "--syms" ) ) options.symbols = 1; + else if( !strcmp( arg, "--dynamic-syms" ) ) options.dynamic_symbols = 1; + else if( !strcmp( arg, "--section-headers" ) ) options.section_headers = 1; + else if( !strncmp( arg, "--section=", 10 ) ) + { + if( add_section( &options, arg + 10 ) ) + { + fprintf( stderr, "mcpu-objdump: out of memory or empty section name\n" ); + free_options( &options ); + return( 1 ); + } + } + else if( !strcmp( arg, "-j" ) || !strcmp( arg, "--section" ) ) + { + if( i + 1 >= argc || add_section( &options, argv[++i] ) ) + { + fprintf( stderr, "mcpu-objdump: option %s requires a section name\n", + arg ); + free_options( &options ); + return( 1 ); + } + } + else if( arg[0] == '-' && arg[1] != '-' ) + { + const char *section_arg = NULL; + int rc = parse_short_options( arg, &options, §ion_arg ); + + if( rc < 0 ) + { + fprintf( stderr, "mcpu-objdump: unknown option: %s\n", arg ); + usage( stderr, argv[0] ); + free_options( &options ); + return( 1 ); + } + if( rc == 1 ) + { + if( add_section( &options, section_arg ) ) + { + fprintf( stderr, "mcpu-objdump: out of memory or empty section name\n" ); + free_options( &options ); + return( 1 ); + } + } + else if( rc == 2 ) + { + if( i + 1 >= argc || add_section( &options, argv[++i] ) ) + { + fprintf( stderr, "mcpu-objdump: option -j requires a section name\n" ); + free_options( &options ); + return( 1 ); + } + } + } + else + { + fprintf( stderr, "mcpu-objdump: unknown option: %s\n", arg ); + usage( stderr, argv[0] ); + free_options( &options ); + return( 1 ); + } + } + + if( first_file < 0 || first_file >= argc ) + { + fprintf( stderr, "mcpu-objdump: no input file\n" ); + usage( stderr, argv[0] ); + free_options( &options ); + return( 1 ); + } + if( !options.disassemble && !options.disassemble_all && + !options.relocations && !options.full_contents && + !options.symbols && !options.dynamic_symbols && + !options.section_headers ) + { + fprintf( stderr, "mcpu-objdump: at least one display option is required\n" ); + usage( stderr, argv[0] ); + free_options( &options ); + return( 1 ); + } + + for( i = first_file; i < argc; ++i ) + { + if( process_path( argv[i], &options ) ) + status = 1; + } + + free_options( &options ); + return( status ); + +} /* End of main() */ diff --git a/utils/mcpu-object-edit.c b/utils/mcpu-object-edit.c new file mode 100644 index 0000000..ac0db3c --- /dev/null +++ b/utils/mcpu-object-edit.c @@ -0,0 +1,1195 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <libmcpu.h> +#include <mcpu-elf.h> + +#include "mcpu-melf.h" +#include "mcpu-object-edit.h" + + +typedef struct mcpu_edit_shdr +{ + __mpu_uint32_t name; + __mpu_uint32_t type; + __mpu_uint64_t flags; + __mpu_uint64_t addr; + __mpu_uint64_t offset; + __mpu_uint64_t size; + __mpu_uint32_t link; + __mpu_uint32_t info; + __mpu_uint64_t align; + __mpu_uint64_t entsize; +} mcpu_edit_shdr; + + +typedef struct mcpu_edit_file +{ + const unsigned char *data; + size_t size; + int encoding; + __mpu_uint16_t type; + __mpu_uint64_t phoff; + __mpu_uint16_t phnum; + __mpu_uint64_t shoff; + __mpu_uint16_t shnum; + __mpu_uint16_t shstrndx; + mcpu_edit_shdr *shdr; + const unsigned char *shstr; + size_t shstr_size; +} mcpu_edit_file; + + +typedef struct mcpu_edit_section +{ + mcpu_edit_shdr shdr; + char *name; + const unsigned char *data; + unsigned char *owned_data; + size_t data_size; + __mpu_uint16_t old_index; + int is_new; + int removed; +} mcpu_edit_section; + + +static void +get_shdr( const unsigned char *p, int data, mcpu_edit_shdr *sh ) +{ + memset( sh, 0, sizeof( *sh ) ); + sh->name = cpu_melf_get32( p + MELF64_SHDR_SH_NAME_OFF, data ); + sh->type = cpu_melf_get32( p + MELF64_SHDR_SH_TYPE_OFF, data ); + sh->flags = cpu_melf_get64( p + MELF64_SHDR_SH_FLAGS_OFF, data ); + sh->addr = cpu_melf_get64( p + MELF64_SHDR_SH_ADDR_OFF, data ); + sh->offset = cpu_melf_get64( p + MELF64_SHDR_SH_OFFSET_OFF, data ); + sh->size = cpu_melf_get64( p + MELF64_SHDR_SH_SIZE_OFF, data ); + sh->link = cpu_melf_get32( p + MELF64_SHDR_SH_LINK_OFF, data ); + sh->info = cpu_melf_get32( p + MELF64_SHDR_SH_INFO_OFF, data ); + sh->align = cpu_melf_get64( p + MELF64_SHDR_SH_ADDRALIGN_OFF, data ); + sh->entsize = cpu_melf_get64( p + MELF64_SHDR_SH_ENTSIZE_OFF, data ); + +} /* End of get_shdr() */ + + +static void +put_shdr( unsigned char *p, int data, const mcpu_edit_shdr *sh ) +{ + memset( p, 0, MELF64_SHDR_SIZE ); + cpu_melf_put32( p + MELF64_SHDR_SH_NAME_OFF, sh->name, data ); + cpu_melf_put32( p + MELF64_SHDR_SH_TYPE_OFF, sh->type, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_FLAGS_OFF, sh->flags, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_ADDR_OFF, sh->addr, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_OFFSET_OFF, sh->offset, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_SIZE_OFF, sh->size, data ); + cpu_melf_put32( p + MELF64_SHDR_SH_LINK_OFF, sh->link, data ); + cpu_melf_put32( p + MELF64_SHDR_SH_INFO_OFF, sh->info, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_ADDRALIGN_OFF, sh->align, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_ENTSIZE_OFF, sh->entsize, data ); + +} /* End of put_shdr() */ + + +static int +shdr_is_zero( const mcpu_edit_shdr *sh ) +{ + return( sh->name == 0 && sh->type == MELF_SHT_NULL && sh->flags == 0 && + sh->addr == 0 && sh->offset == 0 && sh->size == 0 && + sh->link == 0 && sh->info == 0 && sh->align == 0 && + sh->entsize == 0 ); + +} /* End of shdr_is_zero() */ + + +static const char * +name_at( const mcpu_edit_file *file, __mpu_uint32_t offset ) +{ + const unsigned char *p; + + if( offset >= file->shstr_size ) + return( NULL ); + p = file->shstr + offset; + if( !memchr( p, 0, file->shstr_size - offset ) ) + return( NULL ); + return( (const char *)p ); + +} /* End of name_at() */ + + +static void +edit_file_free( mcpu_edit_file *file ) +{ + if( !file ) + return; + free( file->shdr ); + memset( file, 0, sizeof( *file ) ); + +} /* End of edit_file_free() */ + + +static int +edit_file_open( const unsigned char *data, size_t size, mcpu_edit_file *file, + char *err, size_t errsz ) +{ + __mpu_uint16_t ehsize; + __mpu_uint16_t phentsize; + __mpu_uint16_t shentsize; + size_t i; + + memset( file, 0, sizeof( *file ) ); + file->data = data; + file->size = size; + + if( size < MELF64_EHDR_SIZE ) + { + snprintf( err, errsz, "file is too small for a MELF64 header" ); + return( -1 ); + } + if( data[MELF_EI_MAG0] != MELFMAG0 || data[MELF_EI_MAG1] != MELFMAG1 || + data[MELF_EI_MAG2] != MELFMAG2 || data[MELF_EI_MAG3] != MELFMAG3 ) + { + snprintf( err, errsz, "not a MELF file" ); + return( -1 ); + } + if( data[MELF_EI_CLASS] != MELF_CLASS_64 ) + { + snprintf( err, errsz, "unsupported MELF class %u", + (unsigned int)data[MELF_EI_CLASS] ); + return( -1 ); + } + + file->encoding = data[MELF_EI_DATA]; + if( file->encoding != MELF_DATA_LSB && file->encoding != MELF_DATA_MSB ) + { + snprintf( err, errsz, "invalid MELF data encoding %d", file->encoding ); + return( -1 ); + } + if( data[MELF_EI_VERSION] != MELF_EV_CURRENT || + data[MELF_EI_OSABI] != MELF_OSABI_MCPU || + cpu_melf_get16( data + MELF64_EHDR_E_MACHINE_OFF, file->encoding ) != + MELF_EM_MCPU || + cpu_melf_get32( data + MELF64_EHDR_E_VERSION_OFF, file->encoding ) != + MELF_EV_CURRENT ) + { + snprintf( err, errsz, "unsupported MELF header" ); + return( -1 ); + } + + ehsize = cpu_melf_get16( data + MELF64_EHDR_E_EHSIZE_OFF, file->encoding ); + phentsize = cpu_melf_get16( data + MELF64_EHDR_E_PHENTSIZE_OFF, + file->encoding ); + shentsize = cpu_melf_get16( data + MELF64_EHDR_E_SHENTSIZE_OFF, + file->encoding ); + file->type = cpu_melf_get16( data + MELF64_EHDR_E_TYPE_OFF, + file->encoding ); + file->phoff = cpu_melf_get64( data + MELF64_EHDR_E_PHOFF_OFF, + file->encoding ); + file->phnum = cpu_melf_get16( data + MELF64_EHDR_E_PHNUM_OFF, + file->encoding ); + file->shoff = cpu_melf_get64( data + MELF64_EHDR_E_SHOFF_OFF, + file->encoding ); + file->shnum = cpu_melf_get16( data + MELF64_EHDR_E_SHNUM_OFF, + file->encoding ); + file->shstrndx = cpu_melf_get16( data + MELF64_EHDR_E_SHSTRNDX_OFF, + file->encoding ); + + if( ehsize != MELF64_EHDR_SIZE || + ( file->type != MELF_ET_REL && file->type != MELF_ET_EXEC && + file->type != MELF_ET_DYN ) ) + { + snprintf( err, errsz, "unsupported MELF64 file type/header size" ); + return( -1 ); + } + + if( file->phnum ) + { + if( phentsize != MELF64_PHDR_SIZE || + !cpu_melf_span( size, file->phoff, + (__mpu_uint64_t)file->phnum * MELF64_PHDR_SIZE ) ) + { + snprintf( err, errsz, "malformed program-header table" ); + return( -1 ); + } + } + else if( phentsize != 0 && file->type == MELF_ET_REL ) + { + snprintf( err, errsz, "ET_REL has unexpected program-header entry size" ); + return( -1 ); + } + + if( !file->shnum || shentsize != MELF64_SHDR_SIZE || + file->shstrndx == MELF_SHN_UNDEF || file->shstrndx >= file->shnum || + !cpu_melf_span( size, file->shoff, + (__mpu_uint64_t)file->shnum * MELF64_SHDR_SIZE ) ) + { + snprintf( err, errsz, "malformed section-header table" ); + return( -1 ); + } + + file->shdr = (mcpu_edit_shdr *)calloc( file->shnum, sizeof( *file->shdr ) ); + if( !file->shdr ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + + for( i = 0; i < file->shnum; ++i ) + { + get_shdr( data + (size_t)file->shoff + i * MELF64_SHDR_SIZE, + file->encoding, &file->shdr[i] ); + if( file->shdr[i].type != MELF_SHT_NOBITS && file->shdr[i].size && + !cpu_melf_span( size, file->shdr[i].offset, file->shdr[i].size ) ) + { + snprintf( err, errsz, "section %zu has an invalid file range", i ); + edit_file_free( file ); + return( -1 ); + } + } + + if( !shdr_is_zero( &file->shdr[0] ) || + file->shdr[file->shstrndx].type != MELF_SHT_STRTAB || + !file->shdr[file->shstrndx].size || + file->shdr[file->shstrndx].size > SIZE_MAX ) + { + snprintf( err, errsz, "malformed MELF section-name table" ); + edit_file_free( file ); + return( -1 ); + } + + file->shstr = data + (size_t)file->shdr[file->shstrndx].offset; + file->shstr_size = (size_t)file->shdr[file->shstrndx].size; + for( i = 1; i < file->shnum; ++i ) + if( !name_at( file, file->shdr[i].name ) ) + { + snprintf( err, errsz, "section %zu has an invalid name", i ); + edit_file_free( file ); + return( -1 ); + } + + return( 0 ); + +} /* End of edit_file_open() */ + + +static int +copy_original( const unsigned char *data, size_t size, + unsigned char **out, size_t *out_size, + char *err, size_t errsz ) +{ + unsigned char *p = (unsigned char *)malloc( size ? size : 1 ); + + if( !p ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + if( size ) + memcpy( p, data, size ); + *out = p; + *out_size = size; + return( 0 ); + +} /* End of copy_original() */ + + +static void +sections_free( mcpu_edit_section *sections, size_t count ) +{ + size_t i; + + if( !sections ) + return; + for( i = 0; i < count; ++i ) + { + free( sections[i].name ); + free( sections[i].owned_data ); + } + free( sections ); + +} /* End of sections_free() */ + + +static int +sections_from_file( const mcpu_edit_file *file, + mcpu_edit_section **result, size_t *count, + char *err, size_t errsz ) +{ + mcpu_edit_section *sections; + size_t i; + + sections = (mcpu_edit_section *)calloc( file->shnum, sizeof( *sections ) ); + if( !sections ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + + for( i = 0; i < file->shnum; ++i ) + { + const char *name = i ? name_at( file, file->shdr[i].name ) : ""; + + sections[i].shdr = file->shdr[i]; + sections[i].name = strdup( name ? name : "" ); + sections[i].old_index = (__mpu_uint16_t)i; + if( !sections[i].name ) + { + snprintf( err, errsz, "out of memory" ); + sections_free( sections, file->shnum ); + return( -1 ); + } + if( file->shdr[i].type != MELF_SHT_NOBITS && file->shdr[i].size ) + { + sections[i].data = file->data + (size_t)file->shdr[i].offset; + sections[i].data_size = (size_t)file->shdr[i].size; + } + } + + *result = sections; + *count = file->shnum; + return( 0 ); + +} /* End of sections_from_file() */ + + +static size_t +find_section( const mcpu_edit_section *sections, size_t count, + const char *name ) +{ + size_t i; + + for( i = 1; i < count; ++i ) + if( !sections[i].removed && !strcmp( sections[i].name, name ) ) + return( i ); + return( SIZE_MAX ); + +} /* End of find_section() */ + + +static int +name_selected( const char *name, const char *const *only, size_t count ) +{ + size_t i; + + for( i = 0; i < count; ++i ) + if( !strcmp( name, only[i] ) ) + return( 1 ); + return( 0 ); + +} /* End of name_selected() */ + + + +static int +reserved_section_name( const char *name ) +{ + static const char *const reserved[] = + { + MELF_SEC_INIT, MELF_SEC_TEXT, MELF_SEC_FINI, + MELF_SEC_DATA, MELF_SEC_BSS, MELF_SEC_CONTROL, + MELF_SEC_INPUT, MELF_SEC_OUTPUT, MELF_SEC_TRACE, + MELF_SEC_SYMTAB, MELF_SEC_STRTAB, MELF_SEC_SHSTRTAB, + MELF_SEC_DYNSYM, MELF_SEC_DYNSTR, MELF_SEC_DYNAMIC, + MELF_SEC_MCPU_TARGET, MELF_SEC_MCPU_LIFECYCLE, + MELF_SEC_RELA_INIT, MELF_SEC_RELA_TEXT, MELF_SEC_RELA_FINI, + MELF_SEC_RELA_DATA, MELF_SEC_RELA_CONTROL, MELF_SEC_RELA_INPUT, + MELF_SEC_RELA_OUTPUT, MELF_SEC_RELA_TRACE + }; + size_t i; + + if( !name || !*name ) + return( 1 ); + if( !strncmp( name, ".rela.", 6 ) ) + return( 1 ); + for( i = 0; i < sizeof( reserved ) / sizeof( reserved[0] ); ++i ) + if( !strcmp( name, reserved[i] ) ) + return( 1 ); + return( 0 ); + +} /* End of reserved_section_name() */ + +static int +section_runtime_critical( const mcpu_edit_file *file, + const mcpu_edit_section *section ) +{ + const mcpu_edit_shdr *sh = §ion->shdr; + + if( sh->flags & MELF_SHF_ALLOC ) + return( 1 ); + if( section->old_index == file->shstrndx ) + return( 1 ); + if( sh->type == MELF_SHT_DYNAMIC || sh->type == MELF_SHT_DYNSYM || + sh->type == MELF_SHT_RELA || sh->type == MELF_SHT_MCPU_TARGET || + sh->type == MELF_SHT_MCPU_LIFECYCLE ) + return( 1 ); + if( file->type == MELF_ET_REL && + ( sh->type == MELF_SHT_SYMTAB || sh->type == MELF_SHT_STRTAB ) ) + return( 1 ); + return( 0 ); + +} /* End of section_runtime_critical() */ + + +static int +section_user_editable( const mcpu_edit_file *file, + const mcpu_edit_section *section ) +{ + if( section_runtime_critical( file, section ) ) + return( 0 ); + return( section->shdr.type == MELF_SHT_PROGBITS && + !( section->shdr.flags & MELF_SHF_ALLOC ) ); + +} /* End of section_user_editable() */ + + +static int +section_link_references( const mcpu_edit_section *sections, size_t count, + size_t target ) +{ + size_t i; + + for( i = 1; i < count; ++i ) + { + if( sections[i].removed || sections[i].is_new ) + continue; + if( sections[i].shdr.link == target ) + return( 1 ); + if( sections[i].shdr.type == MELF_SHT_RELA && + sections[i].shdr.info == target ) + return( 1 ); + } + return( 0 ); + +} /* End of section_link_references() */ + + +static int +safe_remove_section( const mcpu_edit_file *file, + mcpu_edit_section *sections, size_t count, + size_t index, const char *reason, + char *err, size_t errsz ) +{ + if( index == 0 || index >= count || sections[index].removed ) + return( 0 ); + if( section_runtime_critical( file, §ions[index] ) ) + { + snprintf( err, errsz, "%s cannot remove section %s", + reason, sections[index].name ); + return( -1 ); + } + if( section_link_references( sections, count, index ) ) + { + snprintf( err, errsz, "%s cannot remove referenced section %s", + reason, sections[index].name ); + return( -1 ); + } + sections[index].removed = 1; + return( 0 ); + +} /* End of safe_remove_section() */ + + +static int +replace_section_data( mcpu_edit_section *section, + const unsigned char *data, size_t size, + char *err, size_t errsz ) +{ + unsigned char *p = NULL; + + if( size ) + { + p = (unsigned char *)malloc( size ); + if( !p ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + memcpy( p, data, size ); + } + free( section->owned_data ); + section->owned_data = p; + section->data = p; + section->data_size = size; + section->shdr.size = size; + return( 0 ); + +} /* End of replace_section_data() */ + + +static int +add_section( mcpu_edit_section **psections, size_t *pcount, + const char *name, const unsigned char *data, size_t size, + char *err, size_t errsz ) +{ + mcpu_edit_section *sections = *psections; + size_t count = *pcount; + mcpu_edit_section *q; + mcpu_edit_section *s; + + if( !name || !*name ) + { + snprintf( err, errsz, "section name must not be empty" ); + return( -1 ); + } + if( reserved_section_name( name ) ) + { + snprintf( err, errsz, "cannot add reserved section name %s", name ); + return( -1 ); + } + if( count == UINT16_MAX ) + { + snprintf( err, errsz, "too many sections" ); + return( -1 ); + } + q = (mcpu_edit_section *)realloc( sections, + (count + 1) * sizeof( *q ) ); + if( !q ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + sections = q; + memset( §ions[count], 0, sizeof( sections[count] ) ); + s = §ions[count]; + s->name = strdup( name ); + if( !s->name ) + { + snprintf( err, errsz, "out of memory" ); + *psections = sections; + return( -1 ); + } + s->is_new = 1; + s->old_index = MELF_SHN_UNDEF; + s->shdr.type = MELF_SHT_PROGBITS; + s->shdr.align = 1; + if( replace_section_data( s, data, size, err, errsz ) ) + { + free( s->name ); + memset( s, 0, sizeof( *s ) ); + *psections = sections; + return( -1 ); + } + + *psections = sections; + *pcount = count + 1; + return( 0 ); + +} /* End of add_section() */ + + +static int +apply_options( const mcpu_edit_file *file, + mcpu_edit_section **psections, size_t *pcount, + const mcpu_object_edit_options *options, + int *changed, char *err, size_t errsz ) +{ + mcpu_edit_section *sections = *psections; + size_t count = *pcount; + size_t i; + + if( options->only_count ) + { + int any = 0; + + /* Mark the whole filter first so dependencies between sections that are + both being discarded do not make the result depend on section order. */ + for( i = 1; i < count; ++i ) + if( !name_selected( sections[i].name, options->only_sections, + options->only_count ) ) + { + if( section_runtime_critical( file, §ions[i] ) ) + { + snprintf( err, errsz, "--only-section cannot remove section %s", + sections[i].name ); + return( -1 ); + } + sections[i].removed = 1; + any = 1; + } + + for( i = 1; i < count; ++i ) + if( sections[i].removed && section_link_references( sections, count, i ) ) + { + snprintf( err, errsz, + "--only-section cannot remove referenced section %s", + sections[i].name ); + return( -1 ); + } + if( any ) + *changed = 1; + } + + for( i = 0; i < options->action_count; ++i ) + { + const mcpu_object_edit_action *a = &options->actions[i]; + size_t index; + + if( a->type == MCPU_OBJECT_EDIT_ADD ) + { + if( find_section( sections, count, a->name ) != SIZE_MAX ) + { + snprintf( err, errsz, "section %s already exists", a->name ); + return( -1 ); + } + if( add_section( §ions, &count, a->name, a->data, a->size, + err, errsz ) ) + return( -1 ); + *psections = sections; + *pcount = count; + *changed = 1; + continue; + } + + index = find_section( sections, count, a->name ); + if( index == SIZE_MAX ) + { + snprintf( err, errsz, "section %s does not exist", a->name ); + return( -1 ); + } + + if( a->type == MCPU_OBJECT_EDIT_REMOVE ) + { + if( safe_remove_section( file, sections, count, index, + "--remove-section", err, errsz ) ) + return( -1 ); + *changed = 1; + } + else if( a->type == MCPU_OBJECT_EDIT_RENAME ) + { + char *p; + + if( !section_user_editable( file, §ions[index] ) ) + { + snprintf( err, errsz, "--rename-section cannot rename section %s", + a->name ); + return( -1 ); + } + if( !a->new_name || !*a->new_name ) + { + snprintf( err, errsz, "new section name must not be empty" ); + return( -1 ); + } + if( reserved_section_name( a->new_name ) ) + { + snprintf( err, errsz, "cannot rename metadata to reserved name %s", + a->new_name ); + return( -1 ); + } + if( find_section( sections, count, a->new_name ) != SIZE_MAX ) + { + snprintf( err, errsz, "section %s already exists", a->new_name ); + return( -1 ); + } + p = strdup( a->new_name ); + if( !p ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + free( sections[index].name ); + sections[index].name = p; + *changed = 1; + } + else if( a->type == MCPU_OBJECT_EDIT_UPDATE ) + { + if( !section_user_editable( file, §ions[index] ) ) + { + snprintf( err, errsz, "--update-section cannot update section %s", + a->name ); + return( -1 ); + } + if( replace_section_data( §ions[index], a->data, a->size, + err, errsz ) ) + return( -1 ); + *changed = 1; + } + else + { + snprintf( err, errsz, "internal error: invalid edit action" ); + return( -1 ); + } + } + + *psections = sections; + *pcount = count; + return( 0 ); + +} /* End of apply_options() */ + + +static int +build_output( const mcpu_edit_file *file, + mcpu_edit_section *sections, size_t count, + unsigned char **out, size_t *out_size, + char *err, size_t errsz ) +{ + __mpu_uint16_t *map = NULL; + mcpu_edit_shdr *nsh = NULL; + size_t *source_index = NULL; + unsigned char *result = NULL; + unsigned char *newshstr = NULL; + size_t newshstr_size = 1; + size_t newshstr_cap = 64; + __mpu_uint16_t newnum = 0; + __mpu_uint16_t newshstrndx = MELF_SHN_UNDEF; + __mpu_uint64_t fixed_end = MELF64_EHDR_SIZE; + __mpu_uint64_t cur; + __mpu_uint64_t shoff; + __mpu_uint64_t total; + int overflow = 0; + size_t i, j; + int rc = -1; + + map = (__mpu_uint16_t *)malloc( (size_t)file->shnum * sizeof( *map ) ); + if( !map ) + { + snprintf( err, errsz, "out of memory" ); + goto out; + } + for( i = 0; i < file->shnum; ++i ) + map[i] = MELF_SHN_UNDEF; + + for( i = 0; i < count; ++i ) + if( !sections[i].removed ) + { + if( newnum == UINT16_MAX ) + { + snprintf( err, errsz, "too many retained sections" ); + goto out; + } + if( !sections[i].is_new ) + map[sections[i].old_index] = newnum; + if( !sections[i].is_new && sections[i].old_index == file->shstrndx ) + newshstrndx = newnum; + ++newnum; + } + + if( newshstrndx == MELF_SHN_UNDEF ) + { + snprintf( err, errsz, "internal error: .shstrtab was removed" ); + goto out; + } + + nsh = (mcpu_edit_shdr *)calloc( newnum, sizeof( *nsh ) ); + source_index = (size_t *)calloc( newnum, sizeof( *source_index ) ); + newshstr = (unsigned char *)malloc( newshstr_cap ); + if( !nsh || !source_index || !newshstr ) + { + snprintf( err, errsz, "out of memory" ); + goto out; + } + newshstr[0] = 0; + + for( i = 0, j = 0; i < count; ++i ) + if( !sections[i].removed ) + { + size_t n = strlen( sections[i].name ) + 1; + mcpu_edit_shdr sh = sections[i].shdr; + + if( i == 0 ) + sh.name = 0; + else + { + if( n > SIZE_MAX - newshstr_size || + newshstr_size + n > UINT32_MAX ) + { + snprintf( err, errsz, "section-name table overflow" ); + goto out; + } + if( newshstr_size + n > newshstr_cap ) + { + size_t cap = newshstr_cap; + unsigned char *q; + + while( cap < newshstr_size + n ) + { + if( cap > SIZE_MAX / 2 ) + { + snprintf( err, errsz, "section-name table overflow" ); + goto out; + } + cap *= 2; + } + q = (unsigned char *)realloc( newshstr, cap ); + if( !q ) + { + snprintf( err, errsz, "out of memory" ); + goto out; + } + newshstr = q; + newshstr_cap = cap; + } + sh.name = (__mpu_uint32_t)newshstr_size; + memcpy( newshstr + newshstr_size, sections[i].name, n ); + newshstr_size += n; + } + + if( !sections[i].is_new ) + { + if( sh.link ) + { + if( sh.link >= file->shnum || map[sh.link] == MELF_SHN_UNDEF ) + { + snprintf( err, errsz, "retained section %s depends on removed section", + sections[i].name ); + goto out; + } + sh.link = map[sh.link]; + } + if( sh.type == MELF_SHT_RELA && sh.info ) + { + if( sh.info >= file->shnum || map[sh.info] == MELF_SHN_UNDEF ) + { + snprintf( err, errsz, "retained relocation section targets removed section" ); + goto out; + } + sh.info = map[sh.info]; + } + } + if( !sections[i].is_new && sections[i].old_index == file->shstrndx ) + sh.size = newshstr_size; + else + sh.size = sections[i].data_size; + nsh[j] = sh; + source_index[j] = i; + ++j; + } + + /* .shstrtab size is final only after all retained names were appended. */ + nsh[newshstrndx].size = newshstr_size; + + if( file->phnum ) + { + __mpu_uint64_t end = file->phoff + + (__mpu_uint64_t)file->phnum * MELF64_PHDR_SIZE; + + if( end > fixed_end ) + fixed_end = end; + for( i = 0; i < file->phnum; ++i ) + { + const unsigned char *p = file->data + (size_t)file->phoff + + i * MELF64_PHDR_SIZE; + __mpu_uint32_t type = cpu_melf_get32( p + MELF64_PHDR_P_TYPE_OFF, + file->encoding ); + __mpu_uint64_t off = cpu_melf_get64( p + MELF64_PHDR_P_OFFSET_OFF, + file->encoding ); + __mpu_uint64_t filesz = cpu_melf_get64( p + MELF64_PHDR_P_FILESZ_OFF, + file->encoding ); + + if( type == MELF_PT_LOAD && filesz ) + { + if( !cpu_melf_span( file->size, off, filesz ) ) + { + snprintf( err, errsz, "PT_LOAD has an invalid file range" ); + goto out; + } + if( off + filesz > fixed_end ) + fixed_end = off + filesz; + } + } + } + + for( i = 0; i < count; ++i ) + if( !sections[i].removed && !sections[i].is_new && + ( sections[i].shdr.flags & MELF_SHF_ALLOC ) && + sections[i].shdr.type != MELF_SHT_NOBITS && + sections[i].shdr.size ) + { + __mpu_uint64_t end = sections[i].shdr.offset + sections[i].shdr.size; + + if( end > fixed_end ) + fixed_end = end; + } + + if( fixed_end > file->size ) + { + snprintf( err, errsz, "fixed MELF image exceeds file size" ); + goto out; + } + + cur = fixed_end; + for( i = 0; i < newnum; ++i ) + { + size_t si = source_index[i]; + + if( nsh[i].flags & MELF_SHF_ALLOC ) + continue; + if( nsh[i].type == MELF_SHT_NOBITS || !nsh[i].size ) + continue; + cur = cpu_melf_align_up( cur, nsh[i].align ? nsh[i].align : 1, + &overflow ); + if( overflow ) + { + snprintf( err, errsz, "MELF layout overflow" ); + goto out; + } + nsh[i].offset = cur; + if( nsh[i].size > UINT64_MAX - cur ) + { + snprintf( err, errsz, "MELF layout overflow" ); + goto out; + } + cur += nsh[i].size; + (void)si; + } + + shoff = cpu_melf_align_up( cur, 8, &overflow ); + if( overflow || (__mpu_uint64_t)newnum > UINT64_MAX / MELF64_SHDR_SIZE || + shoff > UINT64_MAX - (__mpu_uint64_t)newnum * MELF64_SHDR_SIZE ) + { + snprintf( err, errsz, "section table overflow" ); + goto out; + } + total = shoff + (__mpu_uint64_t)newnum * MELF64_SHDR_SIZE; + if( total > SIZE_MAX ) + { + snprintf( err, errsz, "MELF file is too large for this host" ); + goto out; + } + + result = (unsigned char *)calloc( (size_t)total ? (size_t)total : 1, 1 ); + if( !result ) + { + snprintf( err, errsz, "out of memory" ); + goto out; + } + if( fixed_end ) + memcpy( result, file->data, (size_t)fixed_end ); + + for( i = 0; i < newnum; ++i ) + { + size_t si = source_index[i]; + const mcpu_edit_section *s = §ions[si]; + + if( nsh[i].type == MELF_SHT_NOBITS || !nsh[i].size ) + continue; + if( !cpu_melf_span( (size_t)total, nsh[i].offset, nsh[i].size ) ) + { + snprintf( err, errsz, "section copy exceeds output bounds" ); + goto out; + } + if( !s->is_new && s->old_index == file->shstrndx ) + memcpy( result + (size_t)nsh[i].offset, newshstr, newshstr_size ); + else if( s->data_size ) + memcpy( result + (size_t)nsh[i].offset, s->data, s->data_size ); + } + + /* Remap section indices stored inside retained symbol tables. */ + for( i = 0; i < newnum; ++i ) + if( nsh[i].type == MELF_SHT_SYMTAB || nsh[i].type == MELF_SHT_DYNSYM ) + { + size_t n; + + if( nsh[i].entsize != MELF64_SYM_SIZE || + nsh[i].size % MELF64_SYM_SIZE ) + { + snprintf( err, errsz, "retained symbol table is malformed" ); + goto out; + } + n = (size_t)( nsh[i].size / MELF64_SYM_SIZE ); + for( j = 0; j < n; ++j ) + { + unsigned char *p = result + (size_t)nsh[i].offset + + j * MELF64_SYM_SIZE; + __mpu_uint16_t shndx = cpu_melf_get16( + p + MELF64_SYM_ST_SHNDX_OFF, file->encoding ); + + if( shndx == MELF_SHN_UNDEF || shndx >= MELF_SHN_LORESERVE ) + continue; + if( shndx >= file->shnum || map[shndx] == MELF_SHN_UNDEF ) + { + snprintf( err, errsz, "retained symbol refers to removed section" ); + goto out; + } + cpu_melf_put16( p + MELF64_SYM_ST_SHNDX_OFF, map[shndx], + file->encoding ); + } + } + + cpu_melf_put64( result + MELF64_EHDR_E_SHOFF_OFF, shoff, file->encoding ); + cpu_melf_put16( result + MELF64_EHDR_E_SHNUM_OFF, newnum, file->encoding ); + cpu_melf_put16( result + MELF64_EHDR_E_SHSTRNDX_OFF, + newshstrndx, file->encoding ); + + for( i = 0; i < newnum; ++i ) + put_shdr( result + (size_t)shoff + i * MELF64_SHDR_SIZE, + file->encoding, &nsh[i] ); + + *out = result; + *out_size = (size_t)total; + result = NULL; + rc = 0; + +out: + free( result ); + free( newshstr ); + free( source_index ); + free( nsh ); + free( map ); + return( rc ); + +} /* End of build_output() */ + + +int +mcpu_object_edit_data( const unsigned char *data, size_t size, + const mcpu_object_edit_options *options, + unsigned char **out, size_t *out_size, + int *changed, char *err, size_t errsz ) +{ + mcpu_edit_file file; + mcpu_edit_section *sections = NULL; + size_t count = 0; + int local_changed = 0; + int rc = -1; + + if( !data || !options || !out || !out_size || !changed ) + return( -1 ); + *out = NULL; + *out_size = 0; + *changed = 0; + memset( &file, 0, sizeof( file ) ); + + if( edit_file_open( data, size, &file, err, errsz ) || + sections_from_file( &file, §ions, &count, err, errsz ) ) + goto out; + + if( apply_options( &file, §ions, &count, options, &local_changed, + err, errsz ) ) + goto out; + + if( !local_changed ) + { + rc = copy_original( data, size, out, out_size, err, errsz ); + goto out; + } + if( build_output( &file, sections, count, out, out_size, err, errsz ) ) + goto out; + + *changed = 1; + rc = 0; + +out: + sections_free( sections, count ); + edit_file_free( &file ); + return( rc ); + +} /* End of mcpu_object_edit_data() */ + + +int +mcpu_object_strip_data( const unsigned char *data, size_t size, + unsigned char **out, size_t *out_size, + int *changed, char *err, size_t errsz ) +{ + mcpu_edit_file file; + mcpu_edit_section *sections = NULL; + size_t count = 0; + size_t i; + int any = 0; + int rc = -1; + + if( !data || !out || !out_size || !changed ) + return( -1 ); + *out = NULL; + *out_size = 0; + *changed = 0; + memset( &file, 0, sizeof( file ) ); + + if( edit_file_open( data, size, &file, err, errsz ) || + sections_from_file( &file, §ions, &count, err, errsz ) ) + goto out; + + if( file.type != MELF_ET_REL ) + { + for( i = 1; i < count; ++i ) + if( sections[i].shdr.type == MELF_SHT_SYMTAB && + !( sections[i].shdr.flags & MELF_SHF_ALLOC ) && + !section_link_references( sections, count, i ) ) + { + __mpu_uint32_t link = sections[i].shdr.link; + + sections[i].removed = 1; + any = 1; + if( link && link < file.shnum && link != file.shstrndx && + sections[link].shdr.type == MELF_SHT_STRTAB ) + { + size_t k; + int referenced = 0; + + for( k = 1; k < count; ++k ) + if( !sections[k].removed && !sections[k].is_new && + sections[k].shdr.link == link ) + referenced = 1; + if( !referenced ) + sections[link].removed = 1; + } + } + } + + if( !any ) + { + rc = copy_original( data, size, out, out_size, err, errsz ); + goto out; + } + if( build_output( &file, sections, count, out, out_size, err, errsz ) ) + goto out; + + *changed = 1; + rc = 0; + +out: + sections_free( sections, count ); + edit_file_free( &file ); + return( rc ); + +} /* End of mcpu_object_strip_data() */ + + +int +mcpu_object_dump_section( const unsigned char *data, size_t size, + const char *name, + unsigned char **out, size_t *out_size, + char *err, size_t errsz ) +{ + mcpu_edit_file file; + size_t i; + int rc = -1; + + if( !data || !name || !out || !out_size ) + return( -1 ); + *out = NULL; + *out_size = 0; + memset( &file, 0, sizeof( file ) ); + + if( edit_file_open( data, size, &file, err, errsz ) ) + return( -1 ); + + for( i = 1; i < file.shnum; ++i ) + { + const char *s = name_at( &file, file.shdr[i].name ); + + if( s && !strcmp( s, name ) ) + { + if( file.shdr[i].type == MELF_SHT_NOBITS ) + { + snprintf( err, errsz, "section %s has no file contents", name ); + goto out; + } + rc = copy_original( file.data + (size_t)file.shdr[i].offset, + (size_t)file.shdr[i].size, + out, out_size, err, errsz ); + goto out; + } + } + snprintf( err, errsz, "section %s does not exist", name ); + +out: + edit_file_free( &file ); + return( rc ); + +} /* End of mcpu_object_dump_section() */ diff --git a/utils/mcpu-object-edit.h b/utils/mcpu-object-edit.h new file mode 100644 index 0000000..2e04d98 --- /dev/null +++ b/utils/mcpu-object-edit.h @@ -0,0 +1,49 @@ +#ifndef _MCPU_OBJECT_EDIT_H_ +#define _MCPU_OBJECT_EDIT_H_ 1 + +#include <stddef.h> + + +typedef enum mcpu_object_edit_action_type +{ + MCPU_OBJECT_EDIT_REMOVE = 1, + MCPU_OBJECT_EDIT_RENAME, + MCPU_OBJECT_EDIT_UPDATE, + MCPU_OBJECT_EDIT_ADD +} mcpu_object_edit_action_type; + + +typedef struct mcpu_object_edit_action +{ + mcpu_object_edit_action_type type; + const char *name; + const char *new_name; + const unsigned char *data; + size_t size; +} mcpu_object_edit_action; + + +typedef struct mcpu_object_edit_options +{ + const char *const *only_sections; + size_t only_count; + const mcpu_object_edit_action *actions; + size_t action_count; +} mcpu_object_edit_options; + + +int mcpu_object_strip_data( const unsigned char *data, size_t size, + unsigned char **out, size_t *out_size, + int *changed, char *err, size_t errsz ); + +int mcpu_object_edit_data( const unsigned char *data, size_t size, + const mcpu_object_edit_options *options, + unsigned char **out, size_t *out_size, + int *changed, char *err, size_t errsz ); + +int mcpu_object_dump_section( const unsigned char *data, size_t size, + const char *name, + unsigned char **out, size_t *out_size, + char *err, size_t errsz ); + +#endif diff --git a/utils/mcpu-object.c b/utils/mcpu-object.c new file mode 100644 index 0000000..0b36788 --- /dev/null +++ b/utils/mcpu-object.c @@ -0,0 +1,1489 @@ +#include <errno.h> +#include <limits.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <libmcpu.h> +#include <mcpu-elf.h> + +#include "mcpu-object.h" +#include "mcpu-melf.h" + +typedef struct blob +{ + unsigned char *p; + size_t n; + size_t cap; +} blob; + +typedef struct wsec +{ + const char *name; + __mpu_uint32_t type; + __mpu_uint64_t flags; + __mpu_uint64_t align; + __mpu_uint64_t entsize; + __mpu_uint32_t link; + __mpu_uint32_t info; + unsigned char *owned; + const unsigned char *payload; + size_t size; + __mpu_uint64_t off; + __mpu_uint32_t name_off; + unsigned logical; +} wsec; + +typedef struct rsh +{ + __mpu_uint32_t name; + __mpu_uint32_t type; + __mpu_uint64_t flags; + __mpu_uint64_t addr; + __mpu_uint64_t off; + __mpu_uint64_t size; + __mpu_uint32_t link; + __mpu_uint32_t info; + __mpu_uint64_t align; + __mpu_uint64_t entsize; +} rsh; + +static const unsigned object_sections[] = +{ + CPU_OBJ_SEC_INIT, + CPU_OBJ_SEC_TEXT, + CPU_OBJ_SEC_FINI, + CPU_OBJ_SEC_DATA, + CPU_OBJ_SEC_BSS, + CPU_OBJ_SEC_CONTROL, + CPU_OBJ_SEC_INPUT, + CPU_OBJ_SEC_OUTPUT, + CPU_OBJ_SEC_TRACE +}; + +#define OBJECT_SECTION_COUNT \ + (sizeof(object_sections) / sizeof(object_sections[0])) + +static int +object_add_symbol( cpu_asm_object *o, const char *name, unsigned section, + unsigned binding, unsigned type, __mpu_uint64_t value, + char *err, size_t es) +{ + cpu_asm_symbol *q; + + if (!o || !name) + return( -1 ); + + q = (cpu_asm_symbol *)realloc( o->symbols, + (o->symbol_count + 1) * sizeof(*q)); + if (!q) + { + 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( name ); + if (!o->symbols[o->symbol_count].name) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + + o->symbols[o->symbol_count].section = section; + o->symbols[o->symbol_count].binding = binding; + o->symbols[o->symbol_count].type = type; + o->symbols[o->symbol_count].value = value; + ++o->symbol_count; + return( 0 ); +} + +static int +object_add_reloc( cpu_asm_object *o, unsigned section, unsigned type, + size_t symbol_index, __mpu_uint64_t offset, + __mpu_int64_t addend, char *err, size_t es) +{ + cpu_asm_reloc *q; + + q = (cpu_asm_reloc *)realloc( o->relocs, + (o->reloc_count + 1) * sizeof(*q)); + if (!q) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + + o->relocs = q; + o->relocs[o->reloc_count].section = section; + o->relocs[o->reloc_count].type = type; + o->relocs[o->reloc_count].symbol_index = symbol_index; + o->relocs[o->reloc_count].offset = offset; + o->relocs[o->reloc_count].addend = addend; + ++o->reloc_count; + return( 0 ); +} + +static int +blob_grow( blob *b, size_t add ) +{ + size_t need; + size_t cap; + unsigned char *q; + + if (add > SIZE_MAX - b->n) + return( -1 ); + need = b->n + add; + if (need <= b->cap) + return( 0 ); + + cap = b->cap ? b->cap : 64; + while (cap < need) + { + if (cap > SIZE_MAX / 2) + return( -1 ); + cap *= 2; + } + + q = (unsigned char *)realloc( b->p, cap ); + if (!q) + return( -1 ); + b->p = q; + b->cap = cap; + return( 0 ); +} + +static int +blob_put( blob *b, const void *p, size_t n ) +{ + if( blob_grow( b, n ) ) + return( -1 ); + if (n) + memcpy( b->p + b->n, p, n ); + b->n += n; + return( 0 ); +} + +static int +blob_string( blob *b, const char *s, __mpu_uint32_t *off ) +{ + size_t n = strlen( s ) + 1; + + if (b->n > UINT32_MAX || n > (size_t)UINT32_MAX - b->n) + return( -1 ); + *off = (__mpu_uint32_t)b->n; + return( blob_put( b, s, n ) ); +} + +static void +free_wsecs( wsec *s, size_t n ) +{ + size_t i; + for( i = 0; i < n; ++i ) + free( s[i].owned ); +} + +static void +put_ehdr64( unsigned char *p, int data, __mpu_uint16_t type, + __mpu_uint64_t shoff, __mpu_uint16_t shnum, + __mpu_uint16_t shstr) +{ + memset( p, 0, MELF64_EHDR_SIZE ); + p[MELF_EI_MAG0] = MELFMAG0; + p[MELF_EI_MAG1] = MELFMAG1; + p[MELF_EI_MAG2] = MELFMAG2; + p[MELF_EI_MAG3] = MELFMAG3; + p[MELF_EI_CLASS] = MELF_CLASS_64; + p[MELF_EI_DATA] = (unsigned char)data; + p[MELF_EI_VERSION] = MELF_EV_CURRENT; + p[MELF_EI_OSABI] = MELF_OSABI_MCPU; + p[MELF_EI_ABIVERSION] = MELF_ABIVERSION_CURRENT; + + cpu_melf_put16( p + MELF64_EHDR_E_TYPE_OFF, type, data ); + cpu_melf_put16( p + MELF64_EHDR_E_MACHINE_OFF, MELF_EM_MCPU, data ); + cpu_melf_put32( p + MELF64_EHDR_E_VERSION_OFF, MELF_EV_CURRENT, data ); + cpu_melf_put64( p + MELF64_EHDR_E_ENTRY_OFF, 0, data ); + cpu_melf_put64( p + MELF64_EHDR_E_INIT_ENTRY_OFF, 0, data ); + cpu_melf_put64( p + MELF64_EHDR_E_LIFE_ENTRY_OFF, 0, data ); + cpu_melf_put64( p + MELF64_EHDR_E_FINI_ENTRY_OFF, 0, data ); + cpu_melf_put64( p + MELF64_EHDR_E_PHOFF_OFF, 0, data ); + cpu_melf_put64( p + MELF64_EHDR_E_SHOFF_OFF, shoff, data ); + cpu_melf_put32( p + MELF64_EHDR_E_FLAGS_OFF, MELF_EF_MCPU_NONE, data ); + cpu_melf_put16( p + MELF64_EHDR_E_EHSIZE_OFF, MELF64_EHDR_SIZE, data ); + cpu_melf_put16( p + MELF64_EHDR_E_PHENTSIZE_OFF, 0, data ); + cpu_melf_put16( p + MELF64_EHDR_E_PHNUM_OFF, 0, data ); + cpu_melf_put16( p + MELF64_EHDR_E_SHENTSIZE_OFF, MELF64_SHDR_SIZE, data ); + cpu_melf_put16( p + MELF64_EHDR_E_SHNUM_OFF, shnum, data ); + cpu_melf_put16( p + MELF64_EHDR_E_SHSTRNDX_OFF, shstr, data ); +} + +static void +put_shdr64( unsigned char *p, const wsec *s, int data ) +{ + memset( p, 0, MELF64_SHDR_SIZE ); + cpu_melf_put32( p + MELF64_SHDR_SH_NAME_OFF, s->name_off, data ); + cpu_melf_put32( p + MELF64_SHDR_SH_TYPE_OFF, s->type, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_FLAGS_OFF, s->flags, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_ADDR_OFF, 0, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_OFFSET_OFF, s->off, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_SIZE_OFF, s->size, data ); + cpu_melf_put32( p + MELF64_SHDR_SH_LINK_OFF, s->link, data ); + cpu_melf_put32( p + MELF64_SHDR_SH_INFO_OFF, s->info, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_ADDRALIGN_OFF, s->align, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_ENTSIZE_OFF, s->entsize, data ); +} + +static void +get_shdr64( const unsigned char *p, int data, rsh *s ) +{ + memset( s, 0, sizeof( *s ) ); + s->name = cpu_melf_get32( p + MELF64_SHDR_SH_NAME_OFF, data ); + s->type = cpu_melf_get32( p + MELF64_SHDR_SH_TYPE_OFF, data ); + s->flags = cpu_melf_get64( p + MELF64_SHDR_SH_FLAGS_OFF, data ); + s->addr = cpu_melf_get64( p + MELF64_SHDR_SH_ADDR_OFF, data ); + s->off = cpu_melf_get64( p + MELF64_SHDR_SH_OFFSET_OFF, data ); + s->size = cpu_melf_get64( p + MELF64_SHDR_SH_SIZE_OFF, data ); + s->link = cpu_melf_get32( p + MELF64_SHDR_SH_LINK_OFF, data ); + s->info = cpu_melf_get32( p + MELF64_SHDR_SH_INFO_OFF, data ); + s->align = cpu_melf_get64( p + MELF64_SHDR_SH_ADDRALIGN_OFF, data ); + s->entsize = cpu_melf_get64( p + MELF64_SHDR_SH_ENTSIZE_OFF, data ); +} + +static int +shdr_is_zero( const rsh *s ) +{ + return s->name == 0 && s->type == 0 && s->flags == 0 && s->addr == 0 && + s->off == 0 && s->size == 0 && s->link == 0 && s->info == 0 && + s->align == 0 && s->entsize == 0; +} + +static __mpu_uint64_t +sec_flags( unsigned logical ) +{ + switch (logical) + { + case CPU_OBJ_SEC_INIT: return( MELF_SHFLAGS_INIT ); + case CPU_OBJ_SEC_TEXT: return( MELF_SHFLAGS_TEXT ); + case CPU_OBJ_SEC_FINI: return( MELF_SHFLAGS_FINI ); + case CPU_OBJ_SEC_DATA: return( MELF_SHFLAGS_DATA ); + case CPU_OBJ_SEC_BSS: return( MELF_SHFLAGS_BSS ); + case CPU_OBJ_SEC_CONTROL: return( MELF_SHFLAGS_CONTROL ); + case CPU_OBJ_SEC_INPUT: return( MELF_SHFLAGS_INPUT ); + case CPU_OBJ_SEC_OUTPUT: return( MELF_SHFLAGS_OUTPUT ); + case CPU_OBJ_SEC_TRACE: return( MELF_SHFLAGS_TRACE ); + default: return( 0 ); + } +} + +static const char * +sec_name( unsigned logical ) +{ + switch (logical) + { + case CPU_OBJ_SEC_INIT: return( MELF_SEC_INIT ); + case CPU_OBJ_SEC_TEXT: return( MELF_SEC_TEXT ); + case CPU_OBJ_SEC_FINI: return( MELF_SEC_FINI ); + case CPU_OBJ_SEC_DATA: return( MELF_SEC_DATA ); + case CPU_OBJ_SEC_BSS: return( MELF_SEC_BSS ); + case CPU_OBJ_SEC_CONTROL: return( MELF_SEC_CONTROL ); + case CPU_OBJ_SEC_INPUT: return( MELF_SEC_INPUT ); + case CPU_OBJ_SEC_OUTPUT: return( MELF_SEC_OUTPUT ); + case CPU_OBJ_SEC_TRACE: return( MELF_SEC_TRACE ); + default: return( "" ); + } +} + +static const char * +rela_name( unsigned logical ) +{ + switch (logical) + { + case CPU_OBJ_SEC_INIT: return( MELF_SEC_RELA_INIT ); + case CPU_OBJ_SEC_TEXT: return( MELF_SEC_RELA_TEXT ); + case CPU_OBJ_SEC_FINI: return( MELF_SEC_RELA_FINI ); + case CPU_OBJ_SEC_DATA: return( MELF_SEC_RELA_DATA ); + case CPU_OBJ_SEC_CONTROL: return( MELF_SEC_RELA_CONTROL ); + case CPU_OBJ_SEC_INPUT: return( MELF_SEC_RELA_INPUT ); + case CPU_OBJ_SEC_OUTPUT: return( MELF_SEC_RELA_OUTPUT ); + case CPU_OBJ_SEC_TRACE: return( MELF_SEC_RELA_TRACE ); + default: return( NULL ); + } +} + +static int +obj_sec( const cpu_asm_object *o, unsigned logical, const unsigned char **p, + size_t *n, __mpu_uint64_t *a, int *present) +{ + switch (logical) + { + case CPU_OBJ_SEC_INIT: + *p = o->init; + *n = o->init_size; + *a = o->init_align; + *present = o->has_init; + return( 0 ); + case CPU_OBJ_SEC_TEXT: + *p = o->text; + *n = o->text_size; + *a = o->text_align; + *present = o->has_text; + return( 0 ); + case CPU_OBJ_SEC_FINI: + *p = o->fini; + *n = o->fini_size; + *a = o->fini_align; + *present = o->has_fini; + return( 0 ); + case CPU_OBJ_SEC_DATA: + *p = o->data; + *n = o->data_size; + *a = o->data_align; + *present = o->has_data || o->data_size != 0; + return( 0 ); + case CPU_OBJ_SEC_BSS: + *p = NULL; + *n = o->bss_size; + *a = o->bss_align; + *present = o->has_bss || o->bss_size != 0; + return( 0 ); + case CPU_OBJ_SEC_CONTROL: + *p = o->control; + *n = o->control_size; + *a = o->control_align; + *present = o->has_control || o->control_size != 0; + return( 0 ); + case CPU_OBJ_SEC_INPUT: + *p = o->input; + *n = o->input_size; + *a = o->input_align; + *present = o->has_input || o->input_size != 0; + return( 0 ); + case CPU_OBJ_SEC_OUTPUT: + *p = o->output; + *n = o->output_size; + *a = o->output_align; + *present = o->has_output || o->output_size != 0; + return( 0 ); + case CPU_OBJ_SEC_TRACE: + *p = o->trace; + *n = o->trace_size; + *a = o->trace_align; + *present = o->has_trace || o->trace_size != 0; + return( 0 ); + default: + return( -1 ); + } +} + +static int +logical_index( const __mpu_uint16_t idx[OBJECT_SECTION_COUNT], unsigned logical ) +{ + size_t i; + for( i = 0; i < OBJECT_SECTION_COUNT; ++i ) + if (object_sections[i] == logical) + return( idx[i] ); + return( 0 ); +} + +static int +validate_object_for_write( const cpu_asm_object *o, char *err, size_t es ) +{ + size_t i; + + for( i = 0; i < OBJECT_SECTION_COUNT; ++i ) + { + const unsigned char *p; + size_t n; + __mpu_uint64_t a; + int present; + + if( obj_sec( o, object_sections[i], &p, &n, &a, &present ) ) + return( -1 ); + if (!present) + continue; + if( a == 0 || !cpu_melf_is_pow2( a ) ) + { + snprintf( err, es, "invalid alignment for %s", sec_name( object_sections[i] ) ); + return( -1 ); + } + if (object_sections[i] != CPU_OBJ_SEC_BSS && n && !p) + { + snprintf( err, es, "missing payload for %s", sec_name( object_sections[i] ) ); + return( -1 ); + } + } + + for( i = 0; i < o->symbol_count; ++i ) + { + const cpu_asm_symbol *s = &o->symbols[i]; + const unsigned char *p; + size_t n; + __mpu_uint64_t a; + int present; + + if (!s->name || !*s->name) + { + snprintf( err, es, "empty symbol name" ); + return( -1 ); + } + if (s->binding > CPU_OBJ_BIND_WEAK || s->type > CPU_OBJ_TYPE_FUNC) + { + snprintf( err, es, "invalid symbol attributes: %s", s->name ); + return( -1 ); + } + if (s->section == CPU_OBJ_SEC_UNDEF) + { + if (s->binding == CPU_OBJ_BIND_LOCAL || s->value != 0 || s->size != 0) + { + snprintf( err, es, "malformed undefined symbol: %s", s->name ); + return( -1 ); + } continue; + } + if( obj_sec( o, s->section, &p, &n, &a, &present ) || !present ) + { + snprintf( err, es, "symbol %s belongs to absent section", s->name ); + return( -1 ); + } + if (s->value > n || s->size > (__mpu_uint64_t)n - s->value) + { + snprintf( err, es, "symbol range outside section: %s", s->name ); + return( -1 ); + } + } + + for( i = 0; i < o->reloc_count; ++i ) + { + const cpu_asm_reloc *r = &o->relocs[i]; + const unsigned char *p; + size_t n; + __mpu_uint64_t a; + int present; + + if (r->type < CPU_RELOC_CODE64 || r->type > CPU_RELOC_CPTR64_DATA) + { + snprintf( err, es, "invalid relocation type" ); + return( -1 ); + } + if (r->symbol_index >= o->symbol_count) + { + snprintf( err, es, "bad relocation symbol index" ); + return( -1 ); + } + if (r->section == CPU_OBJ_SEC_BSS) + { + snprintf( err, es, "relocations cannot patch SHT_NOBITS .bss" ); + return( -1 ); + } + if( obj_sec( o, r->section, &p, &n, &a, &present ) || !present ) + { + snprintf( err, es, "relocation refers to absent source section" ); + return( -1 ); + } + if (r->offset > n || CPU_PTR64_SIZE > n - (size_t)r->offset) + { + snprintf( err, es, "relocation field outside source section" ); + return( -1 ); + } + } + + return( 0 ); +} + +int +cpu_object_write( const char *path, const cpu_asm_object *o, + char *err, size_t es) +{ + wsec s[32]; + size_t ns = 1; + size_t i, j; + __mpu_uint16_t secidx[OBJECT_SECTION_COUNT] = {0}; + __mpu_uint16_t relaidx[OBJECT_SECTION_COUNT] = {0}; + int data = cpu_melf_data_encoding(); + int ov = 0; + blob str = {0}; + blob shstr = {0}; + __mpu_uint32_t *nameoff = NULL; + __mpu_uint32_t *symmap = NULL; + size_t nlocal = 0, nonlocal = 0, symcount; + unsigned char *sympay = NULL; + unsigned char *file = NULL; + size_t symsz; + __mpu_uint16_t symsec, strsec, targetsec, shstrsec; + MElf_McpuTarget mt; + unsigned char mtbuf[MELF_MCPU_TARGET_SIZE]; + __mpu_uint64_t cur, shoff, total; + + if (!path || !o) + return( -1 ); + if( validate_object_for_write( o, err, es ) ) + return( -1 ); + + memset( s, 0, sizeof( s ) ); + if( blob_put( &str, "", 1 ) || blob_put( &shstr, "", 1 ) ) + { + snprintf( err, es, "out of memory" ); + goto fail; + } + + for( i = 0; i < OBJECT_SECTION_COUNT; ++i ) + { + const unsigned char *p; + size_t n; + __mpu_uint64_t a; + int present; + unsigned logical = object_sections[i]; + + if( obj_sec( o, logical, &p, &n, &a, &present ) ) + goto fail; + if (!present) + continue; + if (ns >= sizeof(s) / sizeof(s[0])) + { + snprintf( err, es, "too many MELF sections" ); + goto fail; + } + + secidx[i] = (__mpu_uint16_t)ns; + s[ns].name = sec_name( logical ); + s[ns].type = logical == CPU_OBJ_SEC_BSS ? MELF_SHT_NOBITS + : MELF_SHT_PROGBITS; + s[ns].flags = sec_flags( logical ); + s[ns].align = a; + s[ns].payload = p; + s[ns].size = n; + s[ns].logical = logical; + ++ns; + } + + for( i = 0; i < OBJECT_SECTION_COUNT; ++i ) + { + size_t cnt = 0; + const char *rn; + + for( j = 0; j < o->reloc_count; ++j ) + if (o->relocs[j].section == object_sections[i]) + ++cnt; + if (!cnt) + continue; + + rn = rela_name( object_sections[i] ); + if (!rn || !secidx[i]) + { + snprintf( err, es, "invalid relocation source section" ); + goto fail; + } + if (ns >= sizeof(s) / sizeof(s[0])) + { + snprintf( err, es, "too many MELF sections" ); + goto fail; + } + + relaidx[i] = (__mpu_uint16_t)ns; + s[ns].name = rn; + s[ns].type = MELF_SHT_RELA; + s[ns].align = 8; + s[ns].entsize = MELF64_RELA_SIZE; + s[ns].info = secidx[i]; + s[ns].size = cnt * MELF64_RELA_SIZE; + s[ns].logical = object_sections[i]; + ++ns; + } + + symsec = (__mpu_uint16_t)ns; + s[ns].name = MELF_SEC_SYMTAB; + s[ns].type = MELF_SHT_SYMTAB; + s[ns].align = 8; + s[ns].entsize = MELF64_SYM_SIZE; + ++ns; + + strsec = (__mpu_uint16_t)ns; + s[ns].name = MELF_SEC_STRTAB; + s[ns].type = MELF_SHT_STRTAB; + s[ns].align = 1; + ++ns; + + targetsec = (__mpu_uint16_t)ns; + s[ns].name = MELF_SEC_MCPU_TARGET; + s[ns].type = MELF_SHT_MCPU_TARGET; + s[ns].align = 8; + s[ns].entsize = MELF_MCPU_TARGET_SIZE; + s[ns].size = MELF_MCPU_TARGET_SIZE; + ++ns; + + shstrsec = (__mpu_uint16_t)ns; + s[ns].name = MELF_SEC_SHSTRTAB; + s[ns].type = MELF_SHT_STRTAB; + s[ns].align = 1; + ++ns; + + nameoff = (__mpu_uint32_t *)calloc( o->symbol_count ? o->symbol_count : 1, + sizeof(*nameoff)); + symmap = (__mpu_uint32_t *)calloc( o->symbol_count ? o->symbol_count : 1, + sizeof(*symmap)); + if (!nameoff || !symmap) + { + snprintf( err, es, "out of memory" ); + goto fail; + } + + for( i = 0; i < o->symbol_count; ++i ) + { + if( blob_string( &str, o->symbols[i].name, &nameoff[i] ) ) + { + snprintf( err, es, "symbol string table too large" ); + goto fail; + } + if (o->symbols[i].binding == CPU_OBJ_BIND_LOCAL) + ++nlocal; + else + ++nonlocal; + } + + if (nlocal > UINT32_MAX - 1 || nonlocal > UINT32_MAX - 1 - nlocal) + { + snprintf( err, es, "too many symbols" ); + goto fail; + } + symcount = 1 + nlocal + nonlocal; + if (symcount > SIZE_MAX / MELF64_SYM_SIZE) + { + snprintf( err, es, "symbol table too large" ); + goto fail; + } + symsz = symcount * MELF64_SYM_SIZE; + sympay = (unsigned char *)calloc( symsz ? symsz : 1, 1 ); + if (!sympay) + { + snprintf( err, es, "out of memory" ); + goto fail; + } + + { + size_t li = 1; + size_t gi = 1 + nlocal; + + for( i = 0; i < o->symbol_count; ++i ) + { + const cpu_asm_symbol *x = &o->symbols[i]; + size_t mi = x->binding == CPU_OBJ_BIND_LOCAL ? li++ : gi++; + unsigned bind; + unsigned type; + __mpu_uint16_t shn = MELF_SHN_UNDEF; + unsigned char *q = sympay + mi * MELF64_SYM_SIZE; + + if (x->binding == CPU_OBJ_BIND_LOCAL) + bind = MELF_STB_LOCAL; + else if (x->binding == CPU_OBJ_BIND_WEAK) + bind = MELF_STB_WEAK; + else + bind = MELF_STB_GLOBAL; + + if (x->type == CPU_OBJ_TYPE_FUNC) + type = MELF_STT_FUNC; + else if (x->type == CPU_OBJ_TYPE_OBJECT) + type = MELF_STT_OBJECT; + else + type = MELF_STT_NOTYPE; + + if (x->section != CPU_OBJ_SEC_UNDEF) + { + int idx = logical_index( secidx, x->section ); + if (!idx) + { + snprintf( err, es, "symbol %s belongs to absent section", x->name ); + goto fail; + } + shn = (__mpu_uint16_t)idx; + } + + symmap[i] = (__mpu_uint32_t)mi; + cpu_melf_put32( q + MELF64_SYM_ST_NAME_OFF, nameoff[i], data ); + q[MELF64_SYM_ST_INFO_OFF] = MELF64_ST_INFO( bind, type ); + q[MELF64_SYM_ST_OTHER_OFF] = MELF_STV_DEFAULT; + cpu_melf_put16( q + MELF64_SYM_ST_SHNDX_OFF, shn, data ); + cpu_melf_put64( q + MELF64_SYM_ST_VALUE_OFF, x->value, data ); + cpu_melf_put64( q + MELF64_SYM_ST_SIZE_OFF, x->size, data ); + } + } + + s[symsec].owned = sympay; + s[symsec].payload = sympay; + s[symsec].size = symsz; + s[symsec].link = strsec; + s[symsec].info = (__mpu_uint32_t)(1 + nlocal); + sympay = NULL; + + s[strsec].owned = str.p; + s[strsec].payload = str.p; + s[strsec].size = str.n; + str.p = NULL; + + cpu_melf_fill_target( &mt ); + cpu_melf_encode_target( mtbuf, &mt, data ); + s[targetsec].owned = (unsigned char *)malloc( MELF_MCPU_TARGET_SIZE ); + if (!s[targetsec].owned) + { + snprintf( err, es, "out of memory" ); + goto fail; + } + memcpy( s[targetsec].owned, mtbuf, MELF_MCPU_TARGET_SIZE ); + s[targetsec].payload = s[targetsec].owned; + + for( i = 0; i < OBJECT_SECTION_COUNT; ++i ) + { + if (relaidx[i]) + { + size_t cnt = 0; + wsec *r = &s[relaidx[i]]; + + r->link = symsec; + r->owned = (unsigned char *)calloc( r->size ? r->size : 1, 1 ); + if (!r->owned) + { + snprintf( err, es, "out of memory" ); + goto fail; + } + r->payload = r->owned; + + for( j = 0; j < o->reloc_count; ++j ) + { + const cpu_asm_reloc *x; + unsigned char *q; + + if (o->relocs[j].section != object_sections[i]) + continue; + x = &o->relocs[j]; + q = r->owned + cnt * MELF64_RELA_SIZE; + cpu_melf_put64( q + MELF64_RELA_R_OFFSET_OFF, x->offset, data ); + cpu_melf_put64( q + MELF64_RELA_R_INFO_OFF, + MELF64_R_INFO( symmap[x->symbol_index], x->type ), data); + cpu_melf_put64( q + MELF64_RELA_R_ADDEND_OFF, + (__mpu_uint64_t)x->addend, data); + ++cnt; + } + } + } + + for( i = 1; i < ns; ++i ) + if( blob_string( &shstr, s[i].name, &s[i].name_off ) ) + { + snprintf( err, es, "section-name string table too large" ); + goto fail; + } + + s[shstrsec].owned = shstr.p; + s[shstrsec].payload = shstr.p; + s[shstrsec].size = shstr.n; + shstr.p = NULL; + + cur = MELF64_EHDR_SIZE; + for( i = 1; i < ns; ++i ) + { + cur = cpu_melf_align_up( cur, s[i].align ? s[i].align : 1, &ov ); + if (ov || cur > SIZE_MAX) + { + snprintf( err, es, "MELF file layout overflow" ); + goto fail; + } + s[i].off = cur; + if (s[i].type != MELF_SHT_NOBITS) + { + if ((__mpu_uint64_t)s[i].size > UINT64_MAX - cur) + { + snprintf( err, es, "MELF file layout overflow" ); + goto fail; + } + cur += s[i].size; + } + } + + shoff = cpu_melf_align_up( cur, 8, &ov ); + if (ov || ns > UINT64_MAX / MELF64_SHDR_SIZE || + shoff > UINT64_MAX - ns * MELF64_SHDR_SIZE) + { + snprintf( err, es, "MELF section table overflow" ); + goto fail; + } + total = shoff + ns * MELF64_SHDR_SIZE; + if (total > SIZE_MAX) + { + snprintf( err, es, "MELF file too large for this host" ); + goto fail; + } + + file = (unsigned char *)calloc( (size_t)total ? (size_t)total : 1, 1 ); + if (!file) + { + snprintf( err, es, "out of memory" ); + goto fail; + } + + put_ehdr64( file, data, MELF_ET_REL, shoff, (__mpu_uint16_t)ns, shstrsec ); + for( i = 1; i < ns; ++i ) + if (s[i].type != MELF_SHT_NOBITS && s[i].size) + memcpy( file + (size_t)s[i].off, s[i].payload, s[i].size ); + for( i = 0; i < ns; ++i ) + put_shdr64( file + (size_t)shoff + i *MELF64_SHDR_SIZE, &s[i], data ); + + if( cpu_melf_writeall( path, file, (size_t)total, err, es ) ) + goto fail; + + free( file ); + free( nameoff ); + free( symmap ); + free_wsecs( s, ns ); + return( 0 ); + +fail: + free( file ); + free( nameoff ); + free( symmap ); + free( sympay ); + free( str.p ); + free( shstr.p ); + free_wsecs( s, ns ); + return( -1 ); +} + +static int +validate_ident( const unsigned char *p, size_t n, __mpu_uint16_t want, + int *data, char *err, size_t es) +{ + size_t i; + + if (n < MELF64_EHDR_SIZE || p[MELF_EI_MAG0] != MELFMAG0 || + p[MELF_EI_MAG1] != MELFMAG1 || p[MELF_EI_MAG2] != MELFMAG2 || + p[MELF_EI_MAG3] != MELFMAG3) + { + snprintf( err, es, "not a MELF file" ); + return( -1 ); + } + if (p[MELF_EI_CLASS] != MELF_CLASS_64) + { + snprintf( err, es, "unsupported MELF class" ); + return( -1 ); + } + + *data = p[MELF_EI_DATA]; + if (*data != MELF_DATA_LSB && *data != MELF_DATA_MSB) + { + snprintf( err, es, "unsupported MELF data encoding" ); + return( -1 ); + } + if (p[MELF_EI_VERSION] != MELF_EV_CURRENT || + p[MELF_EI_OSABI] != MELF_OSABI_MCPU || + p[MELF_EI_ABIVERSION] != MELF_ABIVERSION_CURRENT || + cpu_melf_get16( p + MELF64_EHDR_E_TYPE_OFF, *data ) != want || + cpu_melf_get16( p + MELF64_EHDR_E_MACHINE_OFF, *data ) != MELF_EM_MCPU || + cpu_melf_get32( p + MELF64_EHDR_E_VERSION_OFF, *data ) != MELF_EV_CURRENT || + cpu_melf_get32( p + MELF64_EHDR_E_FLAGS_OFF, *data ) != MELF_EF_MCPU_NONE || + cpu_melf_get16( p + MELF64_EHDR_E_EHSIZE_OFF, *data ) != MELF64_EHDR_SIZE) + { + snprintf( err, es, "unsupported MELF header" ); + return( -1 ); + } + + for( i = MELF_EI_PAD; i < MELF_EI_NIDENT; ++i ) + if (p[i] != 0) + { + snprintf( err, es, "nonzero reserved e_ident byte" ); + return( -1 ); + } + return( 0 ); +} + +static int +name_at( const unsigned char *tab, size_t n, __mpu_uint32_t off, + const char **s) +{ + if (off >= n) + return( -1 ); + *s = (const char *)tab + off; + return( memchr( *s, 0, n - off ) ? 0 : -1 ); +} + +static int +map_name_logical( const char *n, unsigned *logical ) +{ + size_t i; + for( i = 0; i < OBJECT_SECTION_COUNT; ++i ) + if( !strcmp( n, sec_name( object_sections[i] ) ) ) + { + *logical = object_sections[i]; + return( 0 ); + } + return( -1 ); +} + +static int +load_payload( unsigned char **dst, size_t *dn, const unsigned char *f, + size_t fn, const rsh *s, char *err, size_t es) +{ + if (s->type == MELF_SHT_NOBITS) + { + snprintf( err, es, "attempt to load SHT_NOBITS payload" ); + return( -1 ); + } + if( s->size > SIZE_MAX || !cpu_melf_span( fn, s->off, s->size ) ) + { + snprintf( err, es, "bad section file range" ); + return( -1 ); + } + if (s->size) + { + *dst = (unsigned char *)malloc( (size_t)s->size ); + if (!*dst) + { + snprintf( err, es, "out of memory" ); + return( -1 ); + } + memcpy( *dst, f + (size_t)s->off, (size_t)s->size ); + } + *dn = (size_t)s->size; + return( 0 ); +} + +static int +u64_to_s64( __mpu_uint64_t u, __mpu_int64_t *out ) +{ + if (u <= (__mpu_uint64_t)INT64_MAX) + { + *out = (__mpu_int64_t)u; + return( 0 ); + } + *out = -1 - (__mpu_int64_t)(UINT64_MAX - u); + return( 0 ); +} + +static int +load_allocated_section( cpu_asm_object *o, unsigned l, const rsh *s, + const unsigned char *f, size_t fn, + char *err, size_t es) +{ + switch (l) + { + case CPU_OBJ_SEC_INIT: + if (o->has_init) goto duplicate; + if( load_payload( (unsigned char **)&o->init, &o->init_size, + f, fn, s, err, es)) return( -1 ); + o->init_align = s->align; + o->has_init = 1; + return( 0 ); + case CPU_OBJ_SEC_TEXT: + if (o->has_text) goto duplicate; + if( load_payload( (unsigned char **)&o->text, &o->text_size, + f, fn, s, err, es)) return( -1 ); + o->text_align = s->align; + o->has_text = 1; + return( 0 ); + case CPU_OBJ_SEC_FINI: + if (o->has_fini) goto duplicate; + if( load_payload( (unsigned char **)&o->fini, &o->fini_size, + f, fn, s, err, es)) return( -1 ); + o->fini_align = s->align; + o->has_fini = 1; + return( 0 ); + case CPU_OBJ_SEC_DATA: + if (o->has_data) goto duplicate; + if( load_payload( (unsigned char **)&o->data, &o->data_size, + f, fn, s, err, es)) return( -1 ); + o->data_align = s->align; + o->has_data = 1; + return( 0 ); + case CPU_OBJ_SEC_BSS: + if (o->has_bss) goto duplicate; + if (s->size > SIZE_MAX) + { + snprintf( err, es, ".bss too large for this host" ); + return( -1 ); + } + o->bss_size = (size_t)s->size; + o->bss_align = s->align; + o->has_bss = 1; + return( 0 ); + case CPU_OBJ_SEC_CONTROL: + if (o->has_control) goto duplicate; + if( load_payload( (unsigned char **)&o->control, &o->control_size, + f, fn, s, err, es)) return( -1 ); + o->control_align = s->align; + o->has_control = 1; + return( 0 ); + case CPU_OBJ_SEC_INPUT: + if (o->has_input) goto duplicate; + if( load_payload( (unsigned char **)&o->input, &o->input_size, + f, fn, s, err, es)) return( -1 ); + o->input_align = s->align; + o->has_input = 1; + return( 0 ); + case CPU_OBJ_SEC_OUTPUT: + if (o->has_output) goto duplicate; + if( load_payload( (unsigned char **)&o->output, &o->output_size, + f, fn, s, err, es)) return( -1 ); + o->output_align = s->align; + o->has_output = 1; + return( 0 ); + case CPU_OBJ_SEC_TRACE: + if (o->has_trace) goto duplicate; + if( load_payload( (unsigned char **)&o->trace, &o->trace_size, + f, fn, s, err, es)) return( -1 ); + o->trace_align = s->align; + o->has_trace = 1; + return( 0 ); + default: + snprintf( err, es, "unsupported allocated section" ); + return( -1 ); + } + +duplicate: + snprintf( err, es, "duplicate section %s", sec_name( l ) ); + return( -1 ); +} + +static int +cpu_object_read_data_internal( const unsigned char *f, size_t fn, + cpu_asm_object *o, char *err, size_t es ) +{ + int data; + __mpu_uint64_t shoff; + __mpu_uint16_t shnum, shentsz, shstrndx; + size_t i, j; + rsh *sh = NULL; + const unsigned char *shstr = NULL; + size_t shstrn = 0; + unsigned *logical = NULL; + int target_found = 0; + __mpu_uint32_t symsec = 0; + MElf_McpuTarget mt; + __mpu_uint32_t *smap = NULL; + size_t symcount = 0; + int rc = -1; + + if( !f || !o ) + return( -1 ); + memset( o, 0, sizeof( *o ) ); + + if( validate_ident( f, fn, MELF_ET_REL, &data, err, es ) ) + goto out; + + if( cpu_melf_get64( f + MELF64_EHDR_E_ENTRY_OFF, data ) != 0 || + cpu_melf_get64( f + MELF64_EHDR_E_INIT_ENTRY_OFF, data ) != 0 || + cpu_melf_get64( f + MELF64_EHDR_E_LIFE_ENTRY_OFF, data ) != 0 || + cpu_melf_get64( f + MELF64_EHDR_E_FINI_ENTRY_OFF, data ) != 0 || + cpu_melf_get64( f + MELF64_EHDR_E_PHOFF_OFF, data ) != 0 || + cpu_melf_get16( f + MELF64_EHDR_E_PHENTSIZE_OFF, data ) != 0 || + cpu_melf_get16( f + MELF64_EHDR_E_PHNUM_OFF, data ) != 0) + { + snprintf( err, es, "ET_REL contains executable/program-header state" ); + goto out; + } + + shoff = cpu_melf_get64( f + MELF64_EHDR_E_SHOFF_OFF, data ); + shentsz = cpu_melf_get16( f + MELF64_EHDR_E_SHENTSIZE_OFF, data ); + shnum = cpu_melf_get16( f + MELF64_EHDR_E_SHNUM_OFF, data ); + shstrndx = cpu_melf_get16( f + MELF64_EHDR_E_SHSTRNDX_OFF, data ); + if (shentsz != MELF64_SHDR_SIZE || !shnum || shstrndx == MELF_SHN_UNDEF || + shstrndx >= shnum || + !cpu_melf_span( fn, shoff, (__mpu_uint64_t)shnum *MELF64_SHDR_SIZE )) + { + snprintf( err, es, "bad MELF section table" ); + goto out; + } + + sh = ( rsh * ) calloc( shnum, sizeof( *sh ) ); + logical = (unsigned *)malloc( shnum *sizeof( *logical ) ); + if (!sh || !logical) + { + snprintf( err, es, "out of memory" ); + goto out; + } + for( i = 0; i < shnum; ++i ) + { + get_shdr64( f + (size_t)shoff + i *MELF64_SHDR_SIZE, data, &sh[i] ); + logical[i] = CPU_OBJ_SEC_UNDEF; + } + if( !shdr_is_zero( &sh[0] ) ) + { + snprintf( err, es, "section header zero is not all-zero" ); + goto out; + } + + if (sh[shstrndx].type != MELF_SHT_STRTAB || sh[shstrndx].flags != 0 || + sh[shstrndx].addr != 0 || sh[shstrndx].link != 0 || + sh[shstrndx].info != 0 || sh[shstrndx].align != 1 || + sh[shstrndx].entsize != 0 || sh[shstrndx].size == 0 || + sh[shstrndx].size > SIZE_MAX || + !cpu_melf_span( fn, sh[shstrndx].off, sh[shstrndx].size )) + { + snprintf( err, es, "bad .shstrtab" ); + goto out; + } + shstr = f + (size_t)sh[shstrndx].off; + shstrn = (size_t)sh[shstrndx].size; + if (shstr[0] != 0) + { + snprintf( err, es, ".shstrtab does not start with NUL" ); + goto out; + } + + for( i = 1; i < shnum; ++i ) + { + const char *name; + + if( name_at( shstr, shstrn, sh[i].name, &name ) ) + { + snprintf( err, es, "bad section name offset" ); + goto out; + } + + if( i == shstrndx && strcmp( name, MELF_SEC_SHSTRTAB ) ) + { + snprintf( err, es, "section-name table has noncanonical name" ); + goto out; + } + + if ((sh[i].type == MELF_SHT_PROGBITS || sh[i].type == MELF_SHT_NOBITS) && + (sh[i].flags & MELF_SHF_ALLOC)) + { + unsigned l; + + if( map_name_logical( name, &l ) ) + { + snprintf( err, es, "unsupported allocated MELF section: %s", name ); + goto out; + } + logical[i] = l; + if ((l == CPU_OBJ_SEC_BSS) != (sh[i].type == MELF_SHT_NOBITS)) + { + snprintf( err, es, "section type disagrees with section role: %s", name ); + goto out; + } + if( sh[i].flags != sec_flags( l ) || sh[i].addr != 0 || + sh[i].link != 0 || sh[i].info != 0 || sh[i].entsize != 0 || + sh[i].align == 0 || !cpu_melf_is_pow2( sh[i].align )) + { + snprintf( err, es, "invalid ET_REL section attributes: %s", name ); + goto out; + } + if (sh[i].type != MELF_SHT_NOBITS && + !cpu_melf_span( fn, sh[i].off, sh[i].size )) + { + snprintf( err, es, "bad section file range: %s", name ); + goto out; + } + if( load_allocated_section( o, l, &sh[i], f, fn, err, es ) ) + goto out; + } + else if (sh[i].type == MELF_SHT_MCPU_TARGET) + { + if (target_found++) + { + snprintf( err, es, "duplicate .mcpu.target" ); + goto out; + } + if( strcmp( name, MELF_SEC_MCPU_TARGET ) || sh[i].flags != 0 || + sh[i].addr != 0 || sh[i].size != MELF_MCPU_TARGET_SIZE || + sh[i].link != 0 || sh[i].info != 0 || sh[i].align != 8 || + sh[i].entsize != MELF_MCPU_TARGET_SIZE || + !cpu_melf_span( fn, sh[i].off, sh[i].size ) || + cpu_melf_decode_target( f + (size_t)sh[i].off, &mt, data ) || + cpu_melf_validate_target( &mt, data, err, es )) + goto out; + } + else if (sh[i].type == MELF_SHT_SYMTAB) + { + if( strcmp( name, MELF_SEC_SYMTAB ) || symsec ) + { + snprintf( err, es, "multiple/noncanonical symbol table" ); + goto out; + } + symsec = (__mpu_uint32_t)i; + } + } + + if (!target_found) + { + snprintf( err, es, "missing .mcpu.target" ); + goto out; + } + if (!symsec) + { + snprintf( err, es, "missing .symtab" ); + goto out; + } + + { + rsh *sy = &sh[symsec]; + const unsigned char *str; + size_t strn; + const char *strname; + + if (sy->flags != 0 || sy->addr != 0 || sy->align != 8 || + sy->entsize != MELF64_SYM_SIZE || sy->size % MELF64_SYM_SIZE || + sy->link == 0 || sy->link >= shnum || sy->info == 0 || + sy->info > sy->size / MELF64_SYM_SIZE || + sh[sy->link].type != MELF_SHT_STRTAB || + sh[sy->link].flags != 0 || sh[sy->link].addr != 0 || + sh[sy->link].link != 0 || sh[sy->link].info != 0 || + sh[sy->link].align != 1 || sh[sy->link].entsize != 0 || + !cpu_melf_span( fn, sy->off, sy->size ) || + !cpu_melf_span( fn, sh[sy->link].off, sh[sy->link].size ) || + sh[sy->link].size == 0 || sh[sy->link].size > SIZE_MAX) + { + snprintf( err, es, "bad MELF symbol table" ); + goto out; + } + if( name_at( shstr, shstrn, sh[sy->link].name, &strname ) || + strcmp( strname, MELF_SEC_STRTAB )) + { + snprintf( err, es, "noncanonical symbol string table" ); + goto out; + } + + str = f + (size_t)sh[sy->link].off; + strn = (size_t)sh[sy->link].size; + if (str[0] != 0) + { + snprintf( err, es, ".strtab does not start with NUL" ); + goto out; + } + + symcount = (size_t)(sy->size / MELF64_SYM_SIZE); + if (symcount == 0) + { + snprintf( err, es, "empty MELF symbol table" ); + goto out; + } + { + const unsigned char *q0 = f + (size_t)sy->off; + size_t k; + for( k = 0; k < MELF64_SYM_SIZE; ++k ) + if (q0[k] != 0) + { + snprintf( err, es, "symbol table entry zero is not all-zero" ); + goto out; + } + } + + smap = (__mpu_uint32_t *)malloc( symcount *sizeof( *smap ) ); + if (!smap) + { + snprintf( err, es, "out of memory" ); + goto out; + } + for( i = 0; i < symcount; ++i ) + smap[i] = UINT32_MAX; + + for( i = 1; i < symcount; ++i ) + { + const unsigned char *q = f + (size_t)sy->off + i * MELF64_SYM_SIZE; + __mpu_uint32_t no = cpu_melf_get32( q + MELF64_SYM_ST_NAME_OFF, data ); + __mpu_uint16_t shn = cpu_melf_get16( q + MELF64_SYM_ST_SHNDX_OFF, data ); + __mpu_uint64_t value = cpu_melf_get64( q + MELF64_SYM_ST_VALUE_OFF, data ); + __mpu_uint64_t size = cpu_melf_get64( q + MELF64_SYM_ST_SIZE_OFF, data ); + unsigned bind = MELF64_ST_BIND( q[MELF64_SYM_ST_INFO_OFF] ); + unsigned type = MELF64_ST_TYPE( q[MELF64_SYM_ST_INFO_OFF] ); + unsigned b, t; + const char *n; + + if (i < sy->info) + { + if (bind != MELF_STB_LOCAL) + { + snprintf( err, es, "non-local symbol precedes sh_info boundary" ); + goto out; + } + } + else if (bind == MELF_STB_LOCAL) + { + snprintf( err, es, "local symbol follows sh_info boundary" ); + goto out; + } + if (q[MELF64_SYM_ST_OTHER_OFF] != MELF_STV_DEFAULT) + { + snprintf( err, es, "unsupported symbol visibility" ); + goto out; + } + if( name_at( str, strn, no, &n ) || !*n ) + { + snprintf( err, es, "bad/empty symbol name" ); + goto out; + } + + if (bind == MELF_STB_LOCAL) + b = CPU_OBJ_BIND_LOCAL; + else if (bind == MELF_STB_GLOBAL) + b = CPU_OBJ_BIND_GLOBAL; + else if (bind == MELF_STB_WEAK) + b = CPU_OBJ_BIND_WEAK; + else + { + snprintf( err, es, "unsupported symbol binding" ); + goto out; + } + + if (type == MELF_STT_NOTYPE) + t = CPU_OBJ_TYPE_NOTYPE; + else if (type == MELF_STT_OBJECT) + t = CPU_OBJ_TYPE_OBJECT; + else if (type == MELF_STT_FUNC) + t = CPU_OBJ_TYPE_FUNC; + else + { + snprintf( err, es, "unsupported symbol type" ); + goto out; + } + + if (shn == MELF_SHN_UNDEF) + { + if (b == CPU_OBJ_BIND_LOCAL || value != 0 || size != 0) + { + snprintf( err, es, "malformed undefined symbol: %s", n ); + goto out; + } + if( object_add_symbol( o, n, CPU_OBJ_SEC_UNDEF, b, t, 0, err, es ) ) + goto out; + } + else + { + if (shn >= MELF_SHN_LORESERVE || shn >= shnum || + logical[shn] == CPU_OBJ_SEC_UNDEF) + { + snprintf( err, es, "symbol refers to unsupported section" ); + goto out; + } + if (value > sh[shn].size || size > sh[shn].size - value) + { + snprintf( err, es, "symbol range outside section: %s", n ); + goto out; + } + if( object_add_symbol( o, n, logical[shn], b, t, value, err, es ) ) + goto out; + o->symbols[o->symbol_count - 1].size = size; + } + smap[i] = (__mpu_uint32_t)(o->symbol_count - 1); + } + } + + for( i = 1; i < shnum; ++i ) + { + if (sh[i].type == MELF_SHT_RELA) + { + const char *name; + unsigned target_logical; + const char *expected_name; + + if (sh[i].link != symsec || sh[i].info == 0 || sh[i].info >= shnum || + logical[sh[i].info] == CPU_OBJ_SEC_UNDEF || + sh[sh[i].info].type == MELF_SHT_NOBITS || + sh[i].flags != 0 || sh[i].addr != 0 || sh[i].align != 8 || + sh[i].entsize != MELF64_RELA_SIZE || + sh[i].size % MELF64_RELA_SIZE || + !cpu_melf_span( fn, sh[i].off, sh[i].size )) + { + snprintf( err, es, "bad RELA section" ); + goto out; + } + + target_logical = logical[sh[i].info]; + expected_name = rela_name( target_logical ); + if( !expected_name || name_at( shstr, shstrn, sh[i].name, &name ) || + strcmp( name, expected_name )) + { + snprintf( err, es, "noncanonical RELA section name" ); + goto out; + } + + for( j = 0; j < (size_t)( sh[i].size / MELF64_RELA_SIZE ); ++j ) + { + const unsigned char *q = f + (size_t)sh[i].off + j * MELF64_RELA_SIZE; + __mpu_uint64_t roff = cpu_melf_get64( q + MELF64_RELA_R_OFFSET_OFF, data ); + __mpu_uint64_t info = cpu_melf_get64( q + MELF64_RELA_R_INFO_OFF, data ); + __mpu_uint32_t ms = MELF64_R_SYM( info ); + __mpu_uint32_t rt = MELF64_R_TYPE( info ); + __mpu_int64_t addend; + + if (ms == MELF_STN_UNDEF || ms >= symcount || smap[ms] == UINT32_MAX || + rt < CPU_RELOC_CODE64 || rt > CPU_RELOC_CPTR64_DATA) + { + snprintf( err, es, "bad RELA symbol/type" ); + goto out; + } + if (roff > sh[sh[i].info].size || + CPU_PTR64_SIZE > sh[sh[i].info].size - roff) + { + snprintf( err, es, "RELA field outside section" ); + goto out; + } + u64_to_s64( cpu_melf_get64( q + MELF64_RELA_R_ADDEND_OFF, data ), &addend ); + if( object_add_reloc( o, target_logical, rt, smap[ms], roff, + addend, err, es)) + goto out; + } + } + } + + if (o->init_size > SIZE_MAX - o->text_size || + o->init_size + o->text_size > SIZE_MAX - o->fini_size) + { + snprintf( err, es, "PROGRAM compatibility image too large" ); + goto out; + } + o->code_size = o->init_size + o->text_size + o->fini_size; + o->code_align = 1; + if (o->code_size) + { + o->code = (__mpu_UNIT_t *)malloc( o->code_size ); + if (!o->code) + { + snprintf( err, es, "out of memory" ); + goto out; + } + memcpy( o->code, o->init, o->init_size ); + memcpy( o->code + o->init_size, o->text, o->text_size ); + memcpy( o->code + o->init_size + o->text_size, o->fini, o->fini_size ); + } + + rc = 0; +out: + free( sh ); + free( logical ); + free( smap ); + if (rc) + cpu_asm_object_free( o ); + return( rc ); +} + +int +cpu_object_read_data( const void *data, size_t size, cpu_asm_object *o, + char *err, size_t es ) +{ + if( data == NULL || size == 0 || o == NULL ) + return( -1 ); + + return( cpu_object_read_data_internal((const unsigned char *)data, size, + o, err, es) ); + +} /* End of cpu_object_read_data() */ + + +int +cpu_object_read( const char *path, cpu_asm_object *o, char *err, size_t es ) +{ + unsigned char *data = NULL; + size_t size = 0; + int rc; + + if( path == NULL || o == NULL ) + return( -1 ); + + if( cpu_melf_readall(path, &data, &size, err, es) ) + return( -1 ); + + rc = cpu_object_read_data_internal( data, size, o, err, es ); + free( data ); + return( rc ); + +} /* End of cpu_object_read() */ + diff --git a/utils/mcpu-object.h b/utils/mcpu-object.h new file mode 100644 index 0000000..3af938a --- /dev/null +++ b/utils/mcpu-object.h @@ -0,0 +1,12 @@ +#ifndef __CPU_OBJECT_H +#define __CPU_OBJECT_H + +#include "mcpu-asm.h" + +/* MELF ET_REL reader/writer. */ +int cpu_object_write( const char *path, const cpu_asm_object *o, char *err, size_t errsz ); +int cpu_object_read( const char *path, cpu_asm_object *o, char *err, size_t errsz ); +int cpu_object_read_data( const void *data, size_t size, cpu_asm_object *o, + char *err, size_t errsz ); + +#endif diff --git a/utils/mcpu-readelf.c b/utils/mcpu-readelf.c new file mode 100644 index 0000000..bfa95e6 --- /dev/null +++ b/utils/mcpu-readelf.c @@ -0,0 +1,1215 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <inttypes.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <libmcpu.h> +#include <mcpu-elf.h> + +#include "mcpu-melf.h" +#include "mcpu-version.h" + + +typedef struct __readelf_shdr +{ + __mpu_uint32_t name; + __mpu_uint32_t type; + __mpu_uint64_t flags; + __mpu_uint64_t addr; + __mpu_uint64_t offset; + __mpu_uint64_t size; + __mpu_uint32_t link; + __mpu_uint32_t info; + __mpu_uint64_t align; + __mpu_uint64_t entsize; +} readelf_shdr; + + +typedef struct __readelf_phdr +{ + __mpu_uint32_t type; + __mpu_uint32_t flags; + __mpu_uint64_t offset; + __mpu_uint64_t vaddr; + __mpu_uint64_t paddr; + __mpu_uint64_t filesz; + __mpu_uint64_t memsz; + __mpu_uint64_t maxsz; + __mpu_uint64_t align; +} readelf_phdr; + + +typedef struct __readelf_file +{ + const char *path; + unsigned char *file; + size_t file_size; + int data; + + __mpu_uint16_t type; + __mpu_uint16_t machine; + __mpu_uint32_t version; + __mpu_uint64_t entry; + __mpu_uint64_t init_entry; + __mpu_uint64_t life_entry; + __mpu_uint64_t fini_entry; + __mpu_uint64_t phoff; + __mpu_uint64_t shoff; + __mpu_uint32_t flags; + __mpu_uint16_t ehsize; + __mpu_uint16_t phentsize; + __mpu_uint16_t phnum; + __mpu_uint16_t shentsize; + __mpu_uint16_t shnum; + __mpu_uint16_t shstrndx; + + readelf_phdr *phdr; + readelf_shdr *shdr; + const unsigned char *shstr; + size_t shstr_size; +} readelf_file; + + +typedef struct __readelf_options +{ + int file_header; + int program_headers; + int section_headers; + int symbols; + int relocations; + int dynamic; + int arch_specific; + int wide; +} readelf_options; + + +static void +usage( FILE *fp, const char *name ) +{ + fprintf( fp, + "usage: %s <option(s)> MELF-file(s)\n\n" + "Display information about LibMCPU MELF ABI1/ABI2/ABI3 files.\n\n" + "Options:\n" + " -a, --all - equivalent to -h -l -S -s -r -d -A\n" + " -e, --headers - equivalent to -h -l -S\n" + " -h, --file-header - display the MELF file header\n" + " -l, --program-headers - display the program headers\n" + " --segments - alias for --program-headers\n" + " -S, --section-headers - display the section headers\n" + " --sections - alias for --section-headers\n" + " -s, --symbols - display symbol tables (.symtab/.dynsym)\n" + " --syms - alias for --symbols\n" + " -r, --relocs - display RELA relocation sections\n" + " -d, --dynamic - display ABI-v3 dynamic dependency metadata\n" + " -A, --arch-specific - display MCPU target/lifecycle metadata\n" + " -W, --wide - do not truncate output (accepted for compatibility)\n" + " --help - show this help\n" + " --version - show program version\n" + " --dump-version - show version number only\n", + name ); + +} /* End of usage() */ + + +static const char * +file_type_name( unsigned int type ) +{ + switch( type ) + { + case MELF_ET_NONE: return( "NONE (None)" ); + case MELF_ET_REL: return( "REL (Relocatable file)" ); + case MELF_ET_EXEC: return( "EXEC (Executable file)" ); + case MELF_ET_DYN: return( "DYN (Shared object file)" ); + case MELF_ET_CORE: return( "CORE (Core file)" ); + default: return( "<unknown>" ); + } + +} /* End of file_type_name() */ + + +static const char * +section_type_name( __mpu_uint32_t type ) +{ + switch( type ) + { + case MELF_SHT_NULL: return( "NULL" ); + case MELF_SHT_PROGBITS: return( "PROGBITS" ); + case MELF_SHT_SYMTAB: return( "SYMTAB" ); + case MELF_SHT_STRTAB: return( "STRTAB" ); + case MELF_SHT_RELA: return( "RELA" ); + case MELF_SHT_DYNAMIC: return( "DYNAMIC" ); + case MELF_SHT_NOBITS: return( "NOBITS" ); + case MELF_SHT_DYNSYM: return( "DYNSYM" ); + case MELF_SHT_MCPU_TARGET: return( "MCPU_TARGET" ); + case MELF_SHT_MCPU_LIFECYCLE: return( "MCPU_LIFECYCLE" ); + default: return( "<unknown>" ); + } + +} /* End of section_type_name() */ + + +static const char * +program_type_name( __mpu_uint32_t type ) +{ + switch( type ) + { + case MELF_PT_NULL: return( "NULL" ); + case MELF_PT_LOAD: return( "LOAD" ); + case MELF_PT_MCPU_STACK: return( "MCPU_STACK" ); + case MELF_PT_MCPU_HEAP: return( "MCPU_HEAP" ); + default: return( "<unknown>" ); + } + +} /* End of program_type_name() */ + + +static const char * +symbol_bind_name( unsigned int bind ) +{ + switch( bind ) + { + case MELF_STB_LOCAL: return( "LOCAL" ); + case MELF_STB_GLOBAL: return( "GLOBAL" ); + case MELF_STB_WEAK: return( "WEAK" ); + default: return( "<unknown>" ); + } + +} /* End of symbol_bind_name() */ + + +static const char * +symbol_type_name( unsigned int type ) +{ + switch( type ) + { + case MELF_STT_NOTYPE: return( "NOTYPE" ); + case MELF_STT_OBJECT: return( "OBJECT" ); + case MELF_STT_FUNC: return( "FUNC" ); + case MELF_STT_SECTION: return( "SECTION" ); + case MELF_STT_FILE: return( "FILE" ); + default: return( "<unknown>" ); + } + +} /* End of symbol_type_name() */ + + +static const char * +symbol_visibility_name( unsigned int visibility ) +{ + switch( visibility ) + { + case MELF_STV_DEFAULT: return( "DEFAULT" ); + case MELF_STV_INTERNAL: return( "INTERNAL" ); + case MELF_STV_HIDDEN: return( "HIDDEN" ); + case MELF_STV_PROTECTED: return( "PROTECTED" ); + default: return( "<unknown>" ); + } + +} /* End of symbol_visibility_name() */ + + +static const char * +relocation_type_name( __mpu_uint32_t type ) +{ + switch( type ) + { + case MELF_R_MCPU_NONE: return( "R_MCPU_NONE" ); + case MELF_R_MCPU_CODE64: return( "R_MCPU_CODE64" ); + case MELF_R_MCPU_DATA64: return( "R_MCPU_DATA64" ); + case MELF_R_MCPU_PTR64_CODE: return( "R_MCPU_PTR64_CODE" ); + case MELF_R_MCPU_PTR64_DATA: return( "R_MCPU_PTR64_DATA" ); + case MELF_R_MCPU_CPTR64_DATA: return( "R_MCPU_CPTR64_DATA" ); + default: return( "<unknown>" ); + } + +} /* End of relocation_type_name() */ + + +static void +get_shdr( const unsigned char *p, int data, readelf_shdr *sh ) +{ + memset( sh, 0, sizeof( *sh ) ); + sh->name = cpu_melf_get32( p + MELF64_SHDR_SH_NAME_OFF, data ); + sh->type = cpu_melf_get32( p + MELF64_SHDR_SH_TYPE_OFF, data ); + sh->flags = cpu_melf_get64( p + MELF64_SHDR_SH_FLAGS_OFF, data ); + sh->addr = cpu_melf_get64( p + MELF64_SHDR_SH_ADDR_OFF, data ); + sh->offset = cpu_melf_get64( p + MELF64_SHDR_SH_OFFSET_OFF, data ); + sh->size = cpu_melf_get64( p + MELF64_SHDR_SH_SIZE_OFF, data ); + sh->link = cpu_melf_get32( p + MELF64_SHDR_SH_LINK_OFF, data ); + sh->info = cpu_melf_get32( p + MELF64_SHDR_SH_INFO_OFF, data ); + sh->align = cpu_melf_get64( p + MELF64_SHDR_SH_ADDRALIGN_OFF, data ); + sh->entsize = cpu_melf_get64( p + MELF64_SHDR_SH_ENTSIZE_OFF, data ); + +} /* End of get_shdr() */ + + +static void +get_phdr( const unsigned char *p, int data, readelf_phdr *ph ) +{ + memset( ph, 0, sizeof( *ph ) ); + ph->type = cpu_melf_get32( p + MELF64_PHDR_P_TYPE_OFF, data ); + ph->flags = cpu_melf_get32( p + MELF64_PHDR_P_FLAGS_OFF, data ); + ph->offset = cpu_melf_get64( p + MELF64_PHDR_P_OFFSET_OFF, data ); + ph->vaddr = cpu_melf_get64( p + MELF64_PHDR_P_VADDR_OFF, data ); + ph->paddr = cpu_melf_get64( p + MELF64_PHDR_P_PADDR_OFF, data ); + ph->filesz = cpu_melf_get64( p + MELF64_PHDR_P_FILESZ_OFF, data ); + ph->memsz = cpu_melf_get64( p + MELF64_PHDR_P_MEMSZ_OFF, data ); + ph->maxsz = cpu_melf_get64( p + MELF64_PHDR_P_MAXSZ_OFF, data ); + ph->align = cpu_melf_get64( p + MELF64_PHDR_P_ALIGN_OFF, data ); + +} /* End of get_phdr() */ + + +static void +readelf_file_free( readelf_file *m ) +{ + if( !m ) + return; + + free( m->phdr ); + free( m->shdr ); + free( m->file ); + memset( m, 0, sizeof( *m ) ); + +} /* End of readelf_file_free() */ + + +static int +readelf_file_open( const char *path, readelf_file *m, char *err, size_t errsz ) +{ + const unsigned char *h; + size_t i; + + memset( m, 0, sizeof( *m ) ); + m->path = path; + + if( cpu_melf_readall( path, &m->file, &m->file_size, err, errsz ) ) + return( -1 ); + + if( m->file_size < MELF64_EHDR_SIZE ) + { + snprintf( err, errsz, "%s: file is too small for a MELF64 header", path ); + goto fail; + } + + h = m->file; + if( h[MELF_EI_MAG0] != MELFMAG0 || h[MELF_EI_MAG1] != MELFMAG1 || + h[MELF_EI_MAG2] != MELFMAG2 || h[MELF_EI_MAG3] != MELFMAG3 ) + { + snprintf( err, errsz, "%s: not a MELF file", path ); + goto fail; + } + + if( h[MELF_EI_CLASS] != MELF_CLASS_64 ) + { + snprintf( err, errsz, "%s: unsupported MELF class %u", path, + (unsigned int)h[MELF_EI_CLASS] ); + goto fail; + } + + m->data = h[MELF_EI_DATA]; + if( m->data != MELF_DATA_LSB && m->data != MELF_DATA_MSB ) + { + snprintf( err, errsz, "%s: invalid MELF data encoding %d", path, m->data ); + goto fail; + } + + m->type = cpu_melf_get16( h + MELF64_EHDR_E_TYPE_OFF, m->data ); + m->machine = cpu_melf_get16( h + MELF64_EHDR_E_MACHINE_OFF, m->data ); + m->version = cpu_melf_get32( h + MELF64_EHDR_E_VERSION_OFF, m->data ); + m->entry = cpu_melf_get64( h + MELF64_EHDR_E_ENTRY_OFF, m->data ); + m->init_entry = cpu_melf_get64( h + MELF64_EHDR_E_INIT_ENTRY_OFF, m->data ); + m->life_entry = cpu_melf_get64( h + MELF64_EHDR_E_LIFE_ENTRY_OFF, m->data ); + m->fini_entry = cpu_melf_get64( h + MELF64_EHDR_E_FINI_ENTRY_OFF, m->data ); + m->phoff = cpu_melf_get64( h + MELF64_EHDR_E_PHOFF_OFF, m->data ); + m->shoff = cpu_melf_get64( h + MELF64_EHDR_E_SHOFF_OFF, m->data ); + m->flags = cpu_melf_get32( h + MELF64_EHDR_E_FLAGS_OFF, m->data ); + m->ehsize = cpu_melf_get16( h + MELF64_EHDR_E_EHSIZE_OFF, m->data ); + m->phentsize = cpu_melf_get16( h + MELF64_EHDR_E_PHENTSIZE_OFF, m->data ); + m->phnum = cpu_melf_get16( h + MELF64_EHDR_E_PHNUM_OFF, m->data ); + m->shentsize = cpu_melf_get16( h + MELF64_EHDR_E_SHENTSIZE_OFF, m->data ); + m->shnum = cpu_melf_get16( h + MELF64_EHDR_E_SHNUM_OFF, m->data ); + m->shstrndx = cpu_melf_get16( h + MELF64_EHDR_E_SHSTRNDX_OFF, m->data ); + + if( m->ehsize != MELF64_EHDR_SIZE ) + { + snprintf( err, errsz, "%s: unsupported MELF64 header size %u", path, + (unsigned int)m->ehsize ); + goto fail; + } + + if( m->phnum ) + { + if( m->phentsize != MELF64_PHDR_SIZE || + !cpu_melf_span( m->file_size, m->phoff, + (__mpu_uint64_t)m->phnum * MELF64_PHDR_SIZE ) ) + { + snprintf( err, errsz, "%s: malformed program-header table", path ); + goto fail; + } + + m->phdr = (readelf_phdr *)calloc( m->phnum, sizeof( *m->phdr ) ); + if( !m->phdr ) + { + snprintf( err, errsz, "out of memory" ); + goto fail; + } + + for( i = 0; i < m->phnum; ++i ) + get_phdr( m->file + (size_t)m->phoff + i * MELF64_PHDR_SIZE, + m->data, &m->phdr[i] ); + } + else if( m->phentsize != 0 && m->phentsize != MELF64_PHDR_SIZE ) + { + snprintf( err, errsz, "%s: unsupported program-header entry size %u", path, + (unsigned int)m->phentsize ); + goto fail; + } + + if( m->shnum ) + { + if( m->shentsize != MELF64_SHDR_SIZE || + !cpu_melf_span( m->file_size, m->shoff, + (__mpu_uint64_t)m->shnum * MELF64_SHDR_SIZE ) ) + { + snprintf( err, errsz, "%s: malformed section-header table", path ); + goto fail; + } + + m->shdr = (readelf_shdr *)calloc( m->shnum, sizeof( *m->shdr ) ); + if( !m->shdr ) + { + snprintf( err, errsz, "out of memory" ); + goto fail; + } + + for( i = 0; i < m->shnum; ++i ) + get_shdr( m->file + (size_t)m->shoff + i * MELF64_SHDR_SIZE, + m->data, &m->shdr[i] ); + + if( m->shstrndx >= m->shnum || + m->shdr[m->shstrndx].type != MELF_SHT_STRTAB || + !cpu_melf_span( m->file_size, m->shdr[m->shstrndx].offset, + m->shdr[m->shstrndx].size ) ) + { + snprintf( err, errsz, "%s: malformed section-name string table", path ); + goto fail; + } + + m->shstr = m->file + (size_t)m->shdr[m->shstrndx].offset; + m->shstr_size = (size_t)m->shdr[m->shstrndx].size; + } + else if( m->shentsize != 0 && m->shentsize != MELF64_SHDR_SIZE ) + { + snprintf( err, errsz, "%s: unsupported section-header entry size %u", path, + (unsigned int)m->shentsize ); + goto fail; + } + + return( 0 ); + +fail: + readelf_file_free( m ); + return( -1 ); + +} /* End of readelf_file_open() */ + + +static const char * +string_at( const unsigned char *table, size_t size, __mpu_uint64_t offset ) +{ + const unsigned char *p; + + if( !table || offset >= size ) + return( "<corrupt>" ); + + p = table + (size_t)offset; + if( !memchr( p, '\0', size - (size_t)offset ) ) + return( "<corrupt>" ); + + return( (const char *)p ); + +} /* End of string_at() */ + + +static const char * +section_name( const readelf_file *m, size_t index ) +{ + if( !m->shdr || index >= m->shnum ) + return( "<invalid>" ); + + return( string_at( m->shstr, m->shstr_size, m->shdr[index].name ) ); + +} /* End of section_name() */ + + +static int +section_payload( const readelf_file *m, size_t index, + const unsigned char **payload, size_t *size ) +{ + const readelf_shdr *sh; + + if( !m->shdr || index >= m->shnum ) + return( -1 ); + + sh = &m->shdr[index]; + if( sh->type == MELF_SHT_NOBITS ) + { + *payload = NULL; + *size = (size_t)sh->size; + return( 0 ); + } + + if( sh->size > SIZE_MAX || + !cpu_melf_span( m->file_size, sh->offset, sh->size ) ) + return( -1 ); + + *payload = m->file + (size_t)sh->offset; + *size = (size_t)sh->size; + return( 0 ); + +} /* End of section_payload() */ + + +static void +print_file_header( const readelf_file *m ) +{ + const unsigned char *h = m->file; + size_t i; + + printf( "MELF Header:\n" ); + printf( " Magic: " ); + for( i = 0; i < MELF_EI_NIDENT; ++i ) + printf( " %02x", (unsigned int)h[i] ); + printf( "\n" ); + printf( " Class: MELF64\n" ); + printf( " Data: %s endian\n", + m->data == MELF_DATA_LSB ? "little" : "big" ); + printf( " Version: %u\n", + (unsigned int)h[MELF_EI_VERSION] ); + printf( " OS/ABI: %s\n", + h[MELF_EI_OSABI] == MELF_OSABI_MCPU ? "MCPU" : "<unknown>" ); + printf( " ABI Version: %u\n", + (unsigned int)h[MELF_EI_ABIVERSION] ); + printf( " Type: %s\n", file_type_name( m->type ) ); + printf( " Machine: %s (0x%04x)\n", + m->machine == MELF_EM_MCPU ? "MCPU" : "<unknown>", + (unsigned int)m->machine ); + printf( " Version: 0x%x\n", (unsigned int)m->version ); + printf( " Entry point address: 0x%016" PRIx64 "\n", + (uint64_t)m->entry ); + printf( " INIT entry address: 0x%016" PRIx64 "\n", + (uint64_t)m->init_entry ); + printf( " LIFE entry address: 0x%016" PRIx64 "\n", + (uint64_t)m->life_entry ); + printf( " FINI entry address: 0x%016" PRIx64 "\n", + (uint64_t)m->fini_entry ); + printf( " Start of program headers: %" PRIu64 " (bytes into file)\n", + (uint64_t)m->phoff ); + printf( " Start of section headers: %" PRIu64 " (bytes into file)\n", + (uint64_t)m->shoff ); + printf( " Flags: 0x%x\n", (unsigned int)m->flags ); + printf( " Size of this header: %u (bytes)\n", + (unsigned int)m->ehsize ); + printf( " Size of program headers: %u (bytes)\n", + (unsigned int)m->phentsize ); + printf( " Number of program headers: %u\n", (unsigned int)m->phnum ); + printf( " Size of section headers: %u (bytes)\n", + (unsigned int)m->shentsize ); + printf( " Number of section headers: %u\n", (unsigned int)m->shnum ); + printf( " Section header string table index: %u\n", (unsigned int)m->shstrndx ); + +} /* End of print_file_header() */ + + +static void +print_program_headers( const readelf_file *m ) +{ + size_t i; + + if( !m->phnum ) + { + printf( "There are no program headers in this file.\n" ); + return; + } + + printf( "There are %u program headers, starting at offset %" PRIu64 ":\n\n", + (unsigned int)m->phnum, (uint64_t)m->phoff ); + printf( "Program Headers:\n" ); + printf( " Type Offset VirtAddr PhysAddr\n" ); + printf( " FileSiz MemSiz MaxSiz Flg Align\n" ); + + for( i = 0; i < m->phnum; ++i ) + { + const readelf_phdr *ph = &m->phdr[i]; + char flags[4]; + + flags[0] = (ph->flags & MELF_PF_R) ? 'R' : ' '; + flags[1] = (ph->flags & MELF_PF_W) ? 'W' : ' '; + flags[2] = (ph->flags & MELF_PF_X) ? 'E' : ' '; + flags[3] = '\0'; + + printf( " %-13s 0x%016" PRIx64 " 0x%016" PRIx64 " 0x%016" PRIx64 "\n", + program_type_name( ph->type ), (uint64_t)ph->offset, + (uint64_t)ph->vaddr, (uint64_t)ph->paddr ); + printf( " 0x%016" PRIx64 " 0x%016" PRIx64 " 0x%016" PRIx64 + " %3s 0x%" PRIx64 "\n", + (uint64_t)ph->filesz, (uint64_t)ph->memsz, (uint64_t)ph->maxsz, + flags, (uint64_t)ph->align ); + } + +} /* End of print_program_headers() */ + + +static void +section_flags( __mpu_uint64_t flags, char out[8] ) +{ + size_t n = 0; + + if( flags & MELF_SHF_WRITE ) + out[n++] = 'W'; + if( flags & MELF_SHF_ALLOC ) + out[n++] = 'A'; + if( flags & MELF_SHF_EXECINSTR ) + out[n++] = 'X'; + if( MELF_SHF_MCPU_HAS_SEG( flags ) ) + { + unsigned int seg = MELF_SHF_MCPU_GET_SEG( flags ); + + if( seg <= MELF_MCPU_SEG_HEAP ) + { + switch( seg ) + { + case MELF_MCPU_SEG_DATA: out[n++] = 'D'; break; + case MELF_MCPU_SEG_STACK: out[n++] = 'S'; break; + case MELF_MCPU_SEG_PROGRAM: out[n++] = 'P'; break; + case MELF_MCPU_SEG_CONTROL: out[n++] = 'C'; break; + case MELF_MCPU_SEG_INPUT: out[n++] = 'I'; break; + case MELF_MCPU_SEG_OUTPUT: out[n++] = 'O'; break; + case MELF_MCPU_SEG_TRACE: out[n++] = 'T'; break; + case MELF_MCPU_SEG_HEAP: out[n++] = 'H'; break; + } + } + } + out[n] = '\0'; + +} /* End of section_flags() */ + + +static void +print_section_headers( const readelf_file *m ) +{ + size_t i; + + if( !m->shnum ) + { + printf( "There are no section headers in this file.\n" ); + return; + } + + printf( "There are %u section headers, starting at offset 0x%" PRIx64 ":\n\n", + (unsigned int)m->shnum, (uint64_t)m->shoff ); + printf( "Section Headers:\n" ); + printf( " [Nr] Name Type Address Offset " + "Size EntSize Flags Link Info Align\n" ); + + for( i = 0; i < m->shnum; ++i ) + { + const readelf_shdr *sh = &m->shdr[i]; + char flags[8]; + + section_flags( sh->flags, flags ); + printf( " [%2zu] %-20s %-16s %016" PRIx64 " %08" PRIx64 + " %016" PRIx64 " %016" PRIx64 " %-5s %4u %4u %5" PRIu64 "\n", + i, section_name( m, i ), section_type_name( sh->type ), + (uint64_t)sh->addr, (uint64_t)sh->offset, + (uint64_t)sh->size, (uint64_t)sh->entsize, flags, + (unsigned int)sh->link, (unsigned int)sh->info, + (uint64_t)sh->align ); + } + + printf( "Key to Flags: W (write), A (alloc), X (execute), " + "D/S/P/C/I/O/T/H (MCPU segment)\n" ); + +} /* End of print_section_headers() */ + + +static int +get_symbol( const readelf_file *m, size_t table_index, size_t symbol_index, + __mpu_uint32_t *name, unsigned int *bind, unsigned int *type, + unsigned int *visibility, __mpu_uint16_t *shndx, + __mpu_uint64_t *value, __mpu_uint64_t *size ) +{ + const readelf_shdr *sh; + const unsigned char *p; + __mpu_uint8_t info; + __mpu_uint8_t other; + __mpu_uint64_t count; + + if( table_index >= m->shnum ) + return( -1 ); + sh = &m->shdr[table_index]; + if( sh->type != MELF_SHT_SYMTAB && sh->type != MELF_SHT_DYNSYM ) + return( -1 ); + if( sh->entsize != MELF64_SYM_SIZE || sh->size % MELF64_SYM_SIZE ) + return( -1 ); + count = sh->size / MELF64_SYM_SIZE; + if( symbol_index >= count || + !cpu_melf_span( m->file_size, sh->offset, sh->size ) ) + return( -1 ); + + p = m->file + (size_t)sh->offset + symbol_index * MELF64_SYM_SIZE; + *name = cpu_melf_get32( p + MELF64_SYM_ST_NAME_OFF, m->data ); + info = p[MELF64_SYM_ST_INFO_OFF]; + other = p[MELF64_SYM_ST_OTHER_OFF]; + *bind = MELF64_ST_BIND( info ); + *type = MELF64_ST_TYPE( info ); + *visibility = MELF_ST_VISIBILITY( other ); + *shndx = cpu_melf_get16( p + MELF64_SYM_ST_SHNDX_OFF, m->data ); + *value = cpu_melf_get64( p + MELF64_SYM_ST_VALUE_OFF, m->data ); + *size = cpu_melf_get64( p + MELF64_SYM_ST_SIZE_OFF, m->data ); + return( 0 ); + +} /* End of get_symbol() */ + + +static const char * +symbol_name( const readelf_file *m, size_t table_index, __mpu_uint32_t name ) +{ + const readelf_shdr *sh; + const readelf_shdr *str; + const unsigned char *payload; + size_t payload_size; + + if( table_index >= m->shnum ) + return( "<invalid>" ); + sh = &m->shdr[table_index]; + if( sh->link >= m->shnum ) + return( "<invalid>" ); + str = &m->shdr[sh->link]; + if( str->type != MELF_SHT_STRTAB || + section_payload( m, sh->link, &payload, &payload_size ) ) + return( "<invalid>" ); + + return( string_at( payload, payload_size, name ) ); + +} /* End of symbol_name() */ + + +static void +print_symbol_index( __mpu_uint16_t shndx ) +{ + if( shndx == MELF_SHN_UNDEF ) + printf( "%4s", "UND" ); + else if( shndx == MELF_SHN_ABS ) + printf( "%4s", "ABS" ); + else if( shndx == MELF_SHN_COMMON ) + printf( "%4s", "COM" ); + else + printf( "%4u", (unsigned int)shndx ); + +} /* End of print_symbol_index() */ + + +static void +print_symbols( const readelf_file *m ) +{ + size_t i; + + for( i = 0; i < m->shnum; ++i ) + { + const readelf_shdr *sh = &m->shdr[i]; + __mpu_uint64_t count; + size_t j; + + if( sh->type != MELF_SHT_SYMTAB && sh->type != MELF_SHT_DYNSYM ) + continue; + + if( sh->entsize != MELF64_SYM_SIZE || sh->size % MELF64_SYM_SIZE || + !cpu_melf_span( m->file_size, sh->offset, sh->size ) ) + { + printf( "Symbol table '%s' is malformed.\n", section_name( m, i ) ); + continue; + } + + count = sh->size / MELF64_SYM_SIZE; + printf( "Symbol table '%s' contains %" PRIu64 " entries:\n", + section_name( m, i ), (uint64_t)count ); + printf( " Num: Value Size Type Bind Vis Ndx Name\n" ); + + for( j = 0; j < count; ++j ) + { + __mpu_uint32_t name; + unsigned int bind; + unsigned int type; + unsigned int visibility; + __mpu_uint16_t shndx; + __mpu_uint64_t value; + __mpu_uint64_t size; + + if( get_symbol( m, i, j, &name, &bind, &type, &visibility, &shndx, + &value, &size ) ) + break; + + printf( "%6zu: %016" PRIx64 " %5" PRIu64 " %-8s %-8s %-9s ", + j, (uint64_t)value, (uint64_t)size, + symbol_type_name( type ), symbol_bind_name( bind ), + symbol_visibility_name( visibility ) ); + print_symbol_index( shndx ); + printf( " %s\n", symbol_name( m, i, name ) ); + } + printf( "\n" ); + } + +} /* End of print_symbols() */ + + +static void +print_relocations( const readelf_file *m ) +{ + size_t i; + + for( i = 0; i < m->shnum; ++i ) + { + const readelf_shdr *sh = &m->shdr[i]; + __mpu_uint64_t count; + size_t j; + + if( sh->type != MELF_SHT_RELA ) + continue; + + if( sh->entsize != MELF64_RELA_SIZE || sh->size % MELF64_RELA_SIZE || + !cpu_melf_span( m->file_size, sh->offset, sh->size ) || + sh->link >= m->shnum || + (m->shdr[sh->link].type != MELF_SHT_SYMTAB && + m->shdr[sh->link].type != MELF_SHT_DYNSYM) ) + { + printf( "Relocation section '%s' is malformed.\n", section_name( m, i ) ); + continue; + } + + count = sh->size / MELF64_RELA_SIZE; + printf( "Relocation section '%s' at offset 0x%" PRIx64 + " contains %" PRIu64 " entries:\n", + section_name( m, i ), (uint64_t)sh->offset, (uint64_t)count ); + printf( " Offset Info Type Sym.Value Sym.Name + Addend\n" ); + + for( j = 0; j < count; ++j ) + { + const unsigned char *p; + __mpu_uint64_t offset; + __mpu_uint64_t info; + __mpu_int64_t addend; + __mpu_uint32_t sym; + __mpu_uint32_t type; + __mpu_uint32_t name = 0; + unsigned int bind = 0; + unsigned int stype = 0; + unsigned int visibility = 0; + __mpu_uint16_t shndx = 0; + __mpu_uint64_t value = 0; + __mpu_uint64_t ssize = 0; + const char *sname = "<invalid>"; + + p = m->file + (size_t)sh->offset + j * MELF64_RELA_SIZE; + offset = cpu_melf_get64( p + MELF64_RELA_R_OFFSET_OFF, m->data ); + info = cpu_melf_get64( p + MELF64_RELA_R_INFO_OFF, m->data ); + addend = (__mpu_int64_t)cpu_melf_get64( p + MELF64_RELA_R_ADDEND_OFF, + m->data ); + sym = MELF64_R_SYM( info ); + type = MELF64_R_TYPE( info ); + + if( !get_symbol( m, sh->link, sym, &name, &bind, &stype, &visibility, + &shndx, &value, &ssize ) ) + sname = symbol_name( m, sh->link, name ); + + printf( " %016" PRIx64 " %016" PRIx64 " %-20s %016" PRIx64 + " %s %c 0x%" PRIx64 "\n", + (uint64_t)offset, (uint64_t)info, relocation_type_name( type ), + (uint64_t)value, sname, addend < 0 ? '-' : '+', + (uint64_t)(addend < 0 ? -(__mpu_uint64_t)addend : + (__mpu_uint64_t)addend) ); + (void)bind; + (void)stype; + (void)visibility; + (void)shndx; + (void)ssize; + } + printf( "\n" ); + } + +} /* End of print_relocations() */ + + +static const char * +dynamic_tag_name( __mpu_int64_t tag ) +{ + switch( tag ) + { + case MELF_DT_NULL: return( "NULL" ); + case MELF_DT_NEEDED: return( "NEEDED" ); + case MELF_DT_SONAME: return( "SONAME" ); + default: return( "<unknown>" ); + } + +} /* End of dynamic_tag_name() */ + + +static void +print_dynamic( const readelf_file *m ) +{ + size_t i; + int printed = 0; + + for( i = 0; i < m->shnum; ++i ) + { + const readelf_shdr *sh = &m->shdr[i]; + const readelf_shdr *str; + const unsigned char *payload; + const unsigned char *strings; + size_t payload_size; + size_t strings_size; + size_t count; + size_t j; + + if( sh->type != MELF_SHT_DYNAMIC ) + continue; + + printed = 1; + if( sh->link >= m->shnum || + m->shdr[sh->link].type != MELF_SHT_STRTAB || + sh->entsize != MELF64_DYN_SIZE || + sh->size < MELF64_DYN_SIZE || + sh->size % MELF64_DYN_SIZE || + section_payload( m, i, &payload, &payload_size ) || + section_payload( m, sh->link, &strings, &strings_size ) ) + { + printf( "Dynamic section '%s' is malformed.\n", section_name( m, i ) ); + continue; + } + + str = &m->shdr[sh->link]; + (void)str; + count = payload_size / MELF64_DYN_SIZE; + printf( "Dynamic section '%s' at offset 0x%" PRIx64 + " contains %zu entries:\n", + section_name( m, i ), (uint64_t)sh->offset, count ); + printf( " Tag Type Name/Value\n" ); + + for( j = 0; j < count; ++j ) + { + const unsigned char *dp = payload + j * MELF64_DYN_SIZE; + __mpu_int64_t tag = + (__mpu_int64_t)cpu_melf_get64( dp + MELF64_DYN_D_TAG_OFF, m->data ); + __mpu_uint64_t val = + cpu_melf_get64( dp + MELF64_DYN_D_VAL_OFF, m->data ); + + printf( " 0x%016" PRIx64 " %-20s ", + (uint64_t)tag, dynamic_tag_name( tag ) ); + if( tag == MELF_DT_NEEDED ) + printf( "Shared library: [%s]\n", + string_at( strings, strings_size, val ) ); + else if( tag == MELF_DT_SONAME ) + printf( "Library soname: [%s]\n", + string_at( strings, strings_size, val ) ); + else + printf( "0x%" PRIx64 "\n", (uint64_t)val ); + } + printf( "\n" ); + } + + if( !printed ) + printf( "There is no dynamic section in this file.\n" ); + +} /* End of print_dynamic() */ + + +static void +print_target( const MElf_McpuTarget *t ) +{ + printf( "MCPU target attributes:\n" ); + printf( " metadata version: %u\n", (unsigned int)t->mt_version ); + printf( " metadata size: %u\n", (unsigned int)t->mt_size ); + printf( " ISA revision: %u\n", (unsigned int)t->mt_isa_revision ); + printf( " ABI revision: %u\n", (unsigned int)t->mt_abi_revision ); + printf( " program model: %u\n", (unsigned int)t->mt_program_model ); + printf( " data byte order: %u\n", (unsigned int)t->mt_data_byte_order ); + printf( " data word order: %u\n", (unsigned int)t->mt_data_word_order ); + printf( " integer model: %u\n", (unsigned int)t->mt_integer_model ); + printf( " address model: %u\n", (unsigned int)t->mt_address_model ); + printf( " register model: %u\n", (unsigned int)t->mt_register_model ); + printf( " real model: %u\n", (unsigned int)t->mt_real_model ); + printf( " complex model: %u\n", (unsigned int)t->mt_complex_model ); + printf( " maximum ABI alignment: %u\n", (unsigned int)t->mt_max_align ); + printf( " flags: 0x%08x\n", (unsigned int)t->mt_flags ); + printf( " required features: 0x%016" PRIx64 "\n", + (uint64_t)t->mt_required_features ); + +} /* End of print_target() */ + + +static void +print_lifecycle( const MElf_McpuLifecycle *l ) +{ + printf( "MCPU lifecycle attributes:\n" ); + printf( " metadata version: %u\n", (unsigned int)l->ml_version ); + printf( " metadata size: %u\n", (unsigned int)l->ml_size ); + printf( " lifecycle ABI: %u\n", (unsigned int)l->ml_abi ); + printf( " reserved0: 0x%08x\n", (unsigned int)l->ml_reserved0 ); + printf( " flags: 0x%016" PRIx64 "\n", + (uint64_t)l->ml_flags ); + printf( " reserved1: 0x%016" PRIx64 "\n", + (uint64_t)l->ml_reserved1 ); + +} /* End of print_lifecycle() */ + + +static void +print_arch_specific( const readelf_file *m ) +{ + size_t i; + int printed = 0; + + for( i = 0; i < m->shnum; ++i ) + { + const readelf_shdr *sh = &m->shdr[i]; + const unsigned char *payload; + size_t payload_size; + + if( sh->type == MELF_SHT_MCPU_TARGET ) + { + MElf_McpuTarget t; + + if( section_payload( m, i, &payload, &payload_size ) || + payload_size != MELF_MCPU_TARGET_SIZE || + cpu_melf_decode_target( payload, &t, m->data ) ) + { + printf( "MCPU target section '%s' is malformed.\n", section_name( m, i ) ); + continue; + } + print_target( &t ); + printf( "\n" ); + printed = 1; + } + else if( sh->type == MELF_SHT_MCPU_LIFECYCLE ) + { + MElf_McpuLifecycle l; + + if( section_payload( m, i, &payload, &payload_size ) || + payload_size != MELF_MCPU_LIFECYCLE_SIZE || + cpu_melf_decode_lifecycle( payload, &l, m->data ) ) + { + printf( "MCPU lifecycle section '%s' is malformed.\n", + section_name( m, i ) ); + continue; + } + print_lifecycle( &l ); + printf( "\n" ); + printed = 1; + } + } + + if( !printed ) + printf( "There are no MCPU architecture-specific metadata sections in this file.\n" ); + +} /* End of print_arch_specific() */ + + +static int +parse_short_options( const char *arg, readelf_options *opt ) +{ + const char *p; + + for( p = arg + 1; *p; ++p ) + { + switch( *p ) + { + case 'a': + opt->file_header = 1; + opt->program_headers = 1; + opt->section_headers = 1; + opt->symbols = 1; + opt->relocations = 1; + opt->dynamic = 1; + opt->arch_specific = 1; + break; + case 'e': + opt->file_header = 1; + opt->program_headers = 1; + opt->section_headers = 1; + break; + case 'h': opt->file_header = 1; break; + case 'l': opt->program_headers = 1; break; + case 'S': opt->section_headers = 1; break; + case 's': opt->symbols = 1; break; + case 'r': opt->relocations = 1; break; + case 'd': opt->dynamic = 1; break; + case 'A': opt->arch_specific = 1; break; + case 'W': opt->wide = 1; break; + default: return( -1 ); + } + } + + return( 0 ); + +} /* End of parse_short_options() */ + + +static int +have_output_option( const readelf_options *opt ) +{ + return( opt->file_header || opt->program_headers || opt->section_headers || + opt->symbols || opt->relocations || opt->dynamic || + opt->arch_specific ); + +} /* End of have_output_option() */ + + +int +main( int argc, char **argv ) +{ + readelf_options opt; + char **files; + size_t file_count = 0; + size_t file_capacity = 0; + int i; + int rc = 0; + + memset( &opt, 0, sizeof( opt ) ); + files = NULL; + + for( i = 1; i < argc; ++i ) + { + const char *arg = argv[i]; + + if( !strcmp( arg, "--help" ) ) + { + usage( stdout, argv[0] ); + free( files ); + return( 0 ); + } + else if( !strcmp( arg, "--version" ) || + !strcmp( arg, "--dump-version" ) ) + { + mcpu_version_action version_action; + int rc; + + mcpu_version_parse_option( arg, &version_action ); + rc = mcpu_version_print( argv[0], version_action ); + free( files ); + return( rc == 0 ? 0 : 1 ); + } + else if( !strcmp( arg, "--all" ) ) + parse_short_options( "-a", &opt ); + else if( !strcmp( arg, "--headers" ) ) + parse_short_options( "-e", &opt ); + else if( !strcmp( arg, "--file-header" ) ) + opt.file_header = 1; + else if( !strcmp( arg, "--program-headers" ) || !strcmp( arg, "--segments" ) ) + opt.program_headers = 1; + else if( !strcmp( arg, "--section-headers" ) || !strcmp( arg, "--sections" ) ) + opt.section_headers = 1; + else if( !strcmp( arg, "--symbols" ) || !strcmp( arg, "--syms" ) ) + opt.symbols = 1; + else if( !strcmp( arg, "--relocs" ) ) + opt.relocations = 1; + else if( !strcmp( arg, "--dynamic" ) ) + opt.dynamic = 1; + else if( !strcmp( arg, "--arch-specific" ) ) + opt.arch_specific = 1; + else if( !strcmp( arg, "--wide" ) ) + opt.wide = 1; + else if( arg[0] == '-' && arg[1] != '\0' ) + { + if( parse_short_options( arg, &opt ) ) + { + fprintf( stderr, "mcpu-readelf: unknown option '%s'\n", arg ); + usage( stderr, argv[0] ); + free( files ); + return( 1 ); + } + } + else + { + if( file_count == file_capacity ) + { + size_t next = file_capacity ? file_capacity * 2U : 4U; + char **q = (char **)realloc( files, next * sizeof( *q ) ); + + if( !q ) + { + fprintf( stderr, "mcpu-readelf: out of memory\n" ); + free( files ); + return( 1 ); + } + files = q; + file_capacity = next; + } + files[file_count++] = argv[i]; + } + } + + if( !have_output_option( &opt ) || !file_count ) + { + usage( stderr, argv[0] ); + free( files ); + return( 1 ); + } + + for( i = 0; i < (int)file_count; ++i ) + { + readelf_file m; + char err[512]; + + if( file_count > 1 ) + printf( "\nFile: %s\n", files[i] ); + + if( readelf_file_open( files[i], &m, err, sizeof( err ) ) ) + { + fprintf( stderr, "mcpu-readelf: %s\n", err ); + rc = 1; + continue; + } + + if( opt.file_header ) + { + print_file_header( &m ); + if( opt.program_headers || opt.section_headers || opt.symbols || + opt.relocations || opt.dynamic || opt.arch_specific ) + printf( "\n" ); + } + if( opt.program_headers ) + { + print_program_headers( &m ); + if( opt.section_headers || opt.symbols || opt.relocations || + opt.dynamic || opt.arch_specific ) + printf( "\n" ); + } + if( opt.section_headers ) + { + print_section_headers( &m ); + if( opt.symbols || opt.relocations || opt.dynamic || opt.arch_specific ) + printf( "\n" ); + } + if( opt.symbols ) + print_symbols( &m ); + if( opt.relocations ) + print_relocations( &m ); + if( opt.dynamic ) + print_dynamic( &m ); + if( opt.arch_specific ) + print_arch_specific( &m ); + + readelf_file_free( &m ); + } + + free( files ); + return( rc ); + +} /* End of main() */ diff --git a/utils/mcpu-run-options.c b/utils/mcpu-run-options.c new file mode 100644 index 0000000..f3cb881 --- /dev/null +++ b/utils/mcpu-run-options.c @@ -0,0 +1,1169 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <libmcpu.h> +#include <libmpuio.h> + +#include "mcpu-run-options.h" + +void +mcpu_run_usage( const mcpu_run_options *options ) +{ + const char *name = options ? options->common.progname : "mcpu-run"; + + fprintf( stderr, + "usage: %s [runtime-options] -- executable [program-args...]\n" + " %s [runtime-options] executable [program-args...] (compatibility)\n\n" + "Execute a LibMCPU MELF ET_EXEC image. STACK and HEAP sizes are part of\n" + "the executable ABI and cannot be overridden by mcpu-run.\n\n" + "MCPU system options:\n" + " --no-config - do not read mcpu-ld.conf files\n" + " --sys-root=PATH - use PATH as MCPU root and do not read config files\n" + " --dump-config - print effective MCPU configuration and exit\n" + " --print-search-dirs - print effective library search directories and exit\n" + "\nLibrary options (must precede executable or explicit --):\n" + " --library-path=DIR[:DIR...] - add explicit library search directories\n" + " --ldpreload=<a.so:b.so> - preload ABI-v2/ABI-v3 shared objects; names use search path\n" + "\nEnvironment:\n" + " MCPU_LD_PRELOAD=a.so:b.so - preload shared objects before command-line --ldpreload\n" + "\nRuntime options (must precede executable or explicit --):\n" + " --int-reg=r<bits>@<addr> - print a signed integer register after stop\n" + " --real-reg=r<bits>@<addr> - print a real register after stop\n" + " --int-data=<bits>@<addr> - print a signed integer object from DATA memory\n" + " --real-data=<bits>@<addr> - print a real object from DATA memory\n" + " --raw-data=<bytes>@<addr> - dump bytes from .data, clipped at .data end\n" + " --int-control=<bits>@<addr> - print a signed integer object from CONTROL memory\n" + " --real-control=<bits>@<addr> - print a real object from CONTROL memory\n" + " --raw-control=<bytes>@<addr> - dump bytes from .control, clipped at .control end\n" + " --int-heap=<bits>@<addr> - print a signed integer object from HEAP memory\n" + " --real-heap=<bits>@<addr> - print a real object from HEAP memory\n" + " --raw-heap=<bytes>@<addr> - dump bytes from HEAP, clipped at HEAP end\n" + " --int-input=<bits>@<addr> - print a signed integer object from INPUT memory\n" + " --real-input=<bits>@<addr> - print a real object from INPUT memory\n" + " --raw-input=<bytes>@<addr> - dump bytes from .input, clipped at .input end\n" + " --int-output=<bits>@<addr> - print a signed integer object from OUTPUT memory\n" + " --real-output=<bits>@<addr> - print a real object from OUTPUT memory\n" + " --raw-output=<bytes>@<addr> - dump bytes from .output, clipped at .output end\n" + " --int-trace=<bits>@<addr> - print a signed integer object from TRACE memory\n" + " --real-trace=<bits>@<addr> - print a real object from TRACE memory\n" + " --raw-trace=<bytes>@<addr> - dump bytes from .trace, clipped at .trace end\n" + " --before-run=<action> - execute host action before first guest instruction\n" + " --after-init=<action> - execute host action after INIT (or empty INIT stage)\n" + " --on-step=<action> - execute host action after every LIFE END boundary\n" + " --after-run=<action> - execute host action after final execution stop\n" + " --digits=<n> - mantissa digits for real output (default: 80)\n" + " --init-only - execute INIT through END, then stop\n" + " --steps=<n> - stop after n LIFE END boundaries\n" + " (default: stop at first LIFE END)\n" + " -h, --help - show this help\n" + " --version - show program version\n" + " --dump-version - show version number only\n" + "\nBare preload names from MCPU_LD_PRELOAD and --ldpreload are searched in\n" + "--library-path order and then in the effective MCPU system library path.\n" + "Names containing '/' are explicit pathnames and bypass name lookup.\n" + "Environment preloads are ordered before command-line preloads; because the\n" + "rightmost equal-strength preload wins, command-line preloads override the\n" + "environment. Empty preload/path-list elements are errors.\n\n" + "Stage actions:\n" + " print-int-regs:r<bits>@<addr>[,...]\n" + " print-real-regs:r<bits>@<addr>[,...]\n" + " set-int-regs:r<bits>@<addr>=<value>[,...]\n" + " set-real-regs:r<bits>@<addr>=<value>[,...]\n" + " print-int-<memory>:<bits>@<addr>[,...]\n" + " print-real-<memory>:<bits>@<addr>[,...]\n" + " set-int-<memory>:<bits>@<addr>=<value>[,...]\n" + " set-real-<memory>:<bits>@<addr>=<value>[,...]\n" + " print-raw-<memory>:<bytes>@<addr>\n\n" + "<memory> is data, control, heap, input, output or trace. Scalar print\n" + "actions print their comma-separated objects on one line separated by spaces.\n" + "Raw actions contain exactly one dump; repeat the stage option for more dumps.\n" + "There are no set-raw-* actions. Values for set-int-* and set-real-* are\n" + "parsed by LibMPUIO mpu_sscanf(). Repeated stage options execute in command-\n" + "line order. Step-number filters are not implemented.\n\n" + "For --raw-* and print-raw-* <bytes>, K/KiB, M/MiB and G/GiB suffixes are\n" + "accepted. K, M and G use binary multipliers (1024, 1024^2 and 1024^3).\n" + "Use -- to unambiguously separate mcpu-run options from executable/argv.\n", + name, name ); + +} /* End of mcpu_run_usage() */ + + +static int +valid_int_bits( unsigned int bits ) +{ + return( bits >= CPU_REGISTER_MIN_BITS && + bits <= CPU_REGISTER_MAX_BITS && + (bits & (bits - 1U)) == 0U ); + +} /* End of valid_int_bits() */ + + +static int +valid_real_bits( unsigned int bits ) +{ + return( bits >= 32U && + bits <= CPU_REGISTER_MAX_BITS && + bits <= (unsigned int)MPU_REAL_IO_LIMIT && + (bits & (bits - 1U)) == 0U ); + +} /* End of valid_real_bits() */ + + +static int +parse_u64( const char *s, __mpu_uint64_t *v ) +{ + char *e; + unsigned long long x; + + errno = 0; + x = strtoull( s, &e, 0 ); + if( errno || e == s || *e ) return( -1 ); + + *v = (__mpu_uint64_t)x; + + return( 0 ); + +} /* End of parse_u64() */ + + +static int +parse_size( const char *s, __mpu_uint64_t *v ) +{ + unsigned long long x; + __mpu_uint64_t m = 1; + char *e; + + if( !s || !*s || *s == '-' ) return( -1 ); + + errno = 0; + x = strtoull( s, &e, 0 ); + if( errno || e == s ) return( -1 ); + + if( *e == '\0' ) + m = 1; + else if( !strcmp( e, "K" ) || !strcmp( e, "KiB" ) ) + m = __mpu_UINT64_C( 1024 ); + else if( !strcmp( e, "M" ) || !strcmp( e, "MiB" ) ) + m = __mpu_UINT64_C( 1024 ) * __mpu_UINT64_C( 1024 ); + else if( !strcmp( e, "G" ) || !strcmp( e, "GiB" ) ) + m = __mpu_UINT64_C( 1024 ) * __mpu_UINT64_C( 1024 ) * __mpu_UINT64_C( 1024 ); + else + return( -1 ); + + if( (__mpu_uint64_t)x != x || (__mpu_uint64_t)x > UINT64_MAX / m ) + return( -1 ); + + *v = (__mpu_uint64_t)x * m; + + return( 0 ); + +} /* End of parse_size() */ + + + +static int +parse_integer_register( const char *s, print_spec *spec ) +{ + const char *at; + char width[32], *end; + size_t n; + unsigned long bits; + __mpu_uint64_t address; + + if( !s || (s[0] != 'r' && s[0] != 'R') ) return( -1 ); + + at = strchr( s + 1, '@' ); + if( !at ) return( -1 ); + + n = (size_t)(at - (s + 1)); + if( !n || n >= sizeof( width ) ) return( -1 ); + + memcpy( width, s + 1, n ); + width[n] = 0; + + errno = 0; + bits = strtoul( width, &end, 10 ); + if( errno || end == width || *end || bits > (unsigned long)~0U || + !valid_int_bits( (unsigned)bits ) ) + return( -1 ); + + if( parse_u64( at + 1, &address ) || + address > CPU_REGISTER_ADDRESS_MAX || + !__cpu_reg_valid( (unsigned)bits, (__mpu_uint32_t)address ) ) + return( -1 ); + + spec->kind = PRINT_INTEGER_REGISTER; + spec->memory = MEMORY_NONE; + spec->bits = (unsigned)bits; + spec->address = address; + + return( 0 ); + +} /* End of parse_integer_register() */ + + +static int +parse_real_register( const char *s, print_spec *spec ) +{ + const char *at; + char width[32], *end; + size_t n; + unsigned long bits; + __mpu_uint64_t address; + + if( !s || (s[0] != 'r' && s[0] != 'R') ) return( -1 ); + + at = strchr( s + 1, '@' ); + if( !at ) return( -1 ); + + n = (size_t)(at - (s + 1)); + if( !n || n >= sizeof( width ) ) return( -1 ); + + memcpy( width, s + 1, n ); + width[n] = 0; + + errno = 0; + bits = strtoul( width, &end, 10 ); + if( errno || end == width || *end || bits > (unsigned long)~0U || + !valid_real_bits( (unsigned)bits ) ) + return( -1 ); + + if( parse_u64( at + 1, &address ) || + address > CPU_REGISTER_ADDRESS_MAX || + !__cpu_reg_valid( (unsigned)bits, (__mpu_uint32_t)address ) ) + return( -1 ); + + spec->kind = PRINT_REAL_REGISTER; + spec->memory = MEMORY_NONE; + spec->bits = (unsigned)bits; + spec->address = address; + + return( 0 ); + +} /* End of parse_real_register() */ + + +static int +parse_memory_object( const char *s, print_spec *spec, + int real_object, memory_kind memory ) +{ + const char *at; + char width[32], *end; + size_t n; + unsigned long bits; + __mpu_uint64_t address; + + if( !s ) return( -1 ); + if( real_object && (*s == 'r' || *s == 'R') ) ++s; + + at = strchr( s, '@' ); + if( !at ) return( -1 ); + + n = (size_t)(at - s); + if( !n || n >= sizeof( width ) ) return( -1 ); + + memcpy( width, s, n ); + width[n] = 0; + + errno = 0; + bits = strtoul( width, &end, 10 ); + if( errno || end == width || *end || bits > (unsigned long)~0U ) + return( -1 ); + + if( real_object ? !valid_real_bits( (unsigned)bits ) : + !valid_int_bits( (unsigned)bits ) ) + return( -1 ); + + if( parse_u64( at + 1, &address ) ) return( -1 ); + + spec->kind = real_object ? PRINT_REAL_MEMORY : PRINT_INTEGER_MEMORY; + spec->memory = memory; + spec->bits = (unsigned)bits; + spec->address = address; + + return( 0 ); + +} /* End of parse_memory_object() */ + + +static int +parse_raw_memory( const char *s, print_spec *spec, memory_kind memory ) +{ + const char *at; + char countbuf[32]; + size_t n; + __mpu_uint64_t count, address; + + if( !s ) return( -1 ); + + at = strchr( s, '@' ); + if( !at ) return( -1 ); + + n = (size_t)(at - s); + if( !n || n >= sizeof( countbuf ) ) return( -1 ); + + memcpy( countbuf, s, n ); + countbuf[n] = 0; + + if( parse_size( countbuf, &count ) || !count || + parse_u64( at + 1, &address ) ) + return( -1 ); + + spec->kind = PRINT_RAW_MEMORY; + spec->memory = memory; + spec->address = address; + spec->count = count; + + return( 0 ); + +} /* End of parse_raw_memory() */ + + +static void +free_stage_action( stage_action *action ) +{ + size_t i; + + if( !action ) return; + + for( i = 0; i < action->count; ++i ) + free( action->item[i].value ); + free( action->item ); + memset( action, 0, sizeof( *action ) ); + +} /* End of free_stage_action() */ + + +static void +free_stage_action_list( stage_action_list *list ) +{ + size_t i; + + if( !list ) return; + + for( i = 0; i < list->count; ++i ) + free_stage_action( &list->action[i] ); + free( list->action ); + memset( list, 0, sizeof( *list ) ); + +} /* End of free_stage_action_list() */ + + +static int +append_stage_action( stage_action_list *list, stage_action *action ) +{ + stage_action *a; + size_t capacity; + + if( list->count == list->capacity ) + { + capacity = list->capacity ? list->capacity * 2U : 4U; + if( capacity < list->capacity || capacity > SIZE_MAX / sizeof( *a ) ) + return( -1 ); + + a = (stage_action *)realloc( list->action, capacity * sizeof( *a ) ); + if( !a ) return( -1 ); + + list->action = a; + list->capacity = capacity; + } + + list->action[list->count++] = *action; + memset( action, 0, sizeof( *action ) ); + + return( 0 ); + +} /* End of append_stage_action() */ + + +static int +append_action_item( stage_action *action, const action_item *item ) +{ + action_item *p; + + if( action->count == SIZE_MAX / sizeof( *p ) ) return( -1 ); + + p = (action_item *)realloc( action->item, + (action->count + 1U) * sizeof( *p ) ); + if( !p ) return( -1 ); + + action->item = p; + action->item[action->count++] = *item; + + return( 0 ); + +} /* End of append_action_item() */ + + +static char * +dup_range( const char *s, size_t n ) +{ + char *p; + + p = (char *)malloc( n + 1U ); + if( !p ) return( (char *)0 ); + + memcpy( p, s, n ); + p[n] = 0; + + return( p ); + +} /* End of dup_range() */ + + +static int +parse_action_object( const char *text, size_t length, + int is_register, int real_object, int set_object, + memory_kind memory, action_item *item ) +{ + print_spec spec; + char *copy, *eq; + const char *value = (const char *)0; + + memset( item, 0, sizeof( *item ) ); + memset( &spec, 0, sizeof( spec ) ); + + copy = dup_range( text, length ); + if( !copy ) return( -1 ); + + eq = strchr( copy, '=' ); + if( set_object ) + { + if( !eq || eq == copy || !eq[1] || strchr( eq + 1, '=' ) ) + goto fail; + *eq = 0; + value = eq + 1; + } + else if( eq ) + goto fail; + + if( is_register ) + { + if( real_object ? parse_real_register( copy, &spec ) : + parse_integer_register( copy, &spec ) ) + goto fail; + } + else + { + if( parse_memory_object( copy, &spec, real_object, memory ) ) + goto fail; + } + + item->bits = spec.bits; + item->address = spec.address; + if( set_object ) + { + item->value = dup_range( value, strlen( value ) ); + if( !item->value ) goto fail; + } + + free( copy ); + return( 0 ); + +fail: + free( item->value ); + memset( item, 0, sizeof( *item ) ); + free( copy ); + return( -1 ); + +} /* End of parse_action_object() */ + + +typedef struct __action_prefix +{ + const char *name; + action_kind kind; + memory_kind memory; + int is_register; + int real_object; + int set_object; + int raw_object; +} action_prefix; + + +static int +parse_stage_action( const char *text, stage_action *action ) +{ + static const action_prefix prefix[] = + { + { "print-int-regs:", ACTION_PRINT_INTEGER_REGISTER, MEMORY_NONE, 1, 0, 0, 0 }, + { "print-real-regs:", ACTION_PRINT_REAL_REGISTER, MEMORY_NONE, 1, 1, 0, 0 }, + { "set-int-regs:", ACTION_SET_INTEGER_REGISTER, MEMORY_NONE, 1, 0, 1, 0 }, + { "set-real-regs:", ACTION_SET_REAL_REGISTER, MEMORY_NONE, 1, 1, 1, 0 }, + + { "print-int-data:", ACTION_PRINT_INTEGER_MEMORY, MEMORY_DATA, 0, 0, 0, 0 }, + { "print-real-data:", ACTION_PRINT_REAL_MEMORY, MEMORY_DATA, 0, 1, 0, 0 }, + { "set-int-data:", ACTION_SET_INTEGER_MEMORY, MEMORY_DATA, 0, 0, 1, 0 }, + { "set-real-data:", ACTION_SET_REAL_MEMORY, MEMORY_DATA, 0, 1, 1, 0 }, + { "print-raw-data:", ACTION_PRINT_RAW_MEMORY, MEMORY_DATA, 0, 0, 0, 1 }, + + { "print-int-control:", ACTION_PRINT_INTEGER_MEMORY, MEMORY_CONTROL, 0, 0, 0, 0 }, + { "print-real-control:", ACTION_PRINT_REAL_MEMORY, MEMORY_CONTROL, 0, 1, 0, 0 }, + { "set-int-control:", ACTION_SET_INTEGER_MEMORY, MEMORY_CONTROL, 0, 0, 1, 0 }, + { "set-real-control:", ACTION_SET_REAL_MEMORY, MEMORY_CONTROL, 0, 1, 1, 0 }, + { "print-raw-control:", ACTION_PRINT_RAW_MEMORY, MEMORY_CONTROL, 0, 0, 0, 1 }, + + { "print-int-heap:", ACTION_PRINT_INTEGER_MEMORY, MEMORY_HEAP, 0, 0, 0, 0 }, + { "print-real-heap:", ACTION_PRINT_REAL_MEMORY, MEMORY_HEAP, 0, 1, 0, 0 }, + { "set-int-heap:", ACTION_SET_INTEGER_MEMORY, MEMORY_HEAP, 0, 0, 1, 0 }, + { "set-real-heap:", ACTION_SET_REAL_MEMORY, MEMORY_HEAP, 0, 1, 1, 0 }, + { "print-raw-heap:", ACTION_PRINT_RAW_MEMORY, MEMORY_HEAP, 0, 0, 0, 1 }, + + { "print-int-input:", ACTION_PRINT_INTEGER_MEMORY, MEMORY_INPUT, 0, 0, 0, 0 }, + { "print-real-input:", ACTION_PRINT_REAL_MEMORY, MEMORY_INPUT, 0, 1, 0, 0 }, + { "set-int-input:", ACTION_SET_INTEGER_MEMORY, MEMORY_INPUT, 0, 0, 1, 0 }, + { "set-real-input:", ACTION_SET_REAL_MEMORY, MEMORY_INPUT, 0, 1, 1, 0 }, + { "print-raw-input:", ACTION_PRINT_RAW_MEMORY, MEMORY_INPUT, 0, 0, 0, 1 }, + + { "print-int-output:", ACTION_PRINT_INTEGER_MEMORY, MEMORY_OUTPUT, 0, 0, 0, 0 }, + { "print-real-output:", ACTION_PRINT_REAL_MEMORY, MEMORY_OUTPUT, 0, 1, 0, 0 }, + { "set-int-output:", ACTION_SET_INTEGER_MEMORY, MEMORY_OUTPUT, 0, 0, 1, 0 }, + { "set-real-output:", ACTION_SET_REAL_MEMORY, MEMORY_OUTPUT, 0, 1, 1, 0 }, + { "print-raw-output:", ACTION_PRINT_RAW_MEMORY, MEMORY_OUTPUT, 0, 0, 0, 1 }, + + { "print-int-trace:", ACTION_PRINT_INTEGER_MEMORY, MEMORY_TRACE, 0, 0, 0, 0 }, + { "print-real-trace:", ACTION_PRINT_REAL_MEMORY, MEMORY_TRACE, 0, 1, 0, 0 }, + { "set-int-trace:", ACTION_SET_INTEGER_MEMORY, MEMORY_TRACE, 0, 0, 1, 0 }, + { "set-real-trace:", ACTION_SET_REAL_MEMORY, MEMORY_TRACE, 0, 1, 1, 0 }, + { "print-raw-trace:", ACTION_PRINT_RAW_MEMORY, MEMORY_TRACE, 0, 0, 0, 1 } + }; + const action_prefix *ap = (const action_prefix *)0; + const char *payload, *p, *comma; + action_item item; + size_t i, n; + + if( !text || !*text ) return( -1 ); + memset( action, 0, sizeof( *action ) ); + + for( i = 0; i < sizeof( prefix ) / sizeof( prefix[0] ); ++i ) + { + n = strlen( prefix[i].name ); + if( !strncmp( text, prefix[i].name, n ) ) + { + ap = &prefix[i]; + payload = text + n; + break; + } + } + + if( !ap || !*payload ) return( -1 ); + + action->kind = ap->kind; + action->memory = ap->memory; + + if( ap->raw_object ) + { + if( strchr( payload, ',' ) || parse_raw_memory( payload, &action->raw, ap->memory ) ) + goto fail; + return( 0 ); + } + + p = payload; + while( *p ) + { + comma = strchr( p, ',' ); + n = comma ? (size_t)(comma - p) : strlen( p ); + if( !n || parse_action_object( p, n, ap->is_register, ap->real_object, + ap->set_object, ap->memory, &item ) ) + goto fail; + + if( append_action_item( action, &item ) ) + { + free( item.value ); + goto fail; + } + + if( !comma ) break; + p = comma + 1; + if( !*p ) goto fail; + } + + return( action->count ? 0 : -1 ); + +fail: + free_stage_action( action ); + return( -1 ); + +} /* End of parse_stage_action() */ + + +static int +parse_stage_option( const char *text, stage_action_list *list ) +{ + stage_action action; + + memset( &action, 0, sizeof( action ) ); + if( parse_stage_action( text, &action ) ) return( -1 ); + + if( append_stage_action( list, &action ) ) + { + free_stage_action( &action ); + return( -1 ); + } + + return( 0 ); + +} /* End of parse_stage_option() */ + + + +void +mcpu_run_options_init( mcpu_run_options *options, const char *progname ) +{ + const char *env_preload; + + if( options == NULL ) + return; + + memset( options, 0, sizeof(*options) ); + mcpu_tool_options_init( &options->common, progname ); + options->digits = 80U; + + env_preload = getenv( "MCPU_LD_PRELOAD" ); + if( env_preload != NULL && *env_preload != 0 ) + options->env_ldpreload = env_preload; + +} /* End of mcpu_run_options_init() */ + + +void +mcpu_run_options_free( mcpu_run_options *options ) +{ + if( options == NULL ) + return; + + free_stage_action_list( &options->before_run ); + free_stage_action_list( &options->after_init ); + free_stage_action_list( &options->on_step ); + free_stage_action_list( &options->after_run ); + mcpu_tool_options_free( &options->common ); + +} /* End of mcpu_run_options_free() */ + + +static int +append_resolved_preload( char **list, size_t *length, const char *path ) +{ + size_t path_length; + size_t extra; + char *p; + + if( list == NULL || length == NULL || path == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + path_length = strlen( path ); + extra = path_length + (*length != 0 ? 1U : 0U); + if( *length > SIZE_MAX - extra - 1U ) + { + errno = EOVERFLOW; + return( -1 ); + } + + p = (char *)realloc( *list, *length + extra + 1U ); + if( p == NULL ) + return( -1 ); + + *list = p; + if( *length != 0 ) + p[(*length)++] = ':'; + memcpy( p + *length, path, path_length ); + *length += path_length; + p[*length] = 0; + return( 0 ); + +} /* End of append_resolved_preload() */ + + +static int +resolve_preload_list( const mcpu_run_options *options, const char *list, + const char *source, char **result, + size_t *result_length, char *err, size_t errsz ) +{ + const char *begin; + + if( list == NULL || *list == 0 ) + return( 0 ); + + begin = list; + for( ;; ) + { + const char *colon = strchr( begin, ':' ); + size_t length = colon ? (size_t)(colon - begin) : strlen(begin); + char *name; + char *path = NULL; + + if( length == 0 ) + { + snprintf( err, errsz, "empty name in %s list", source ); + return( -1 ); + } + + name = (char *)malloc( length + 1U ); + if( name == NULL ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + + memcpy( name, begin, length ); + name[length] = 0; + if( mcpu_tool_resolve_library(&options->common, name, &path) != 0 ) + { + if( errno == ENOENT && strchr(name, '/') == NULL ) + snprintf( err, errsz, "%s: cannot find MCPU library '%s'", + source, name ); + else + snprintf( err, errsz, "%s: cannot resolve MCPU library '%s': %s", + source, name, strerror(errno) ); + free( name ); + return( -1 ); + } + free( name ); + + if( append_resolved_preload(result, result_length, path) != 0 ) + { + snprintf( err, errsz, "cannot build effective preload list: %s", + strerror(errno) ); + free( path ); + return( -1 ); + } + free( path ); + + if( colon == NULL ) + break; + begin = colon + 1; + } + + return( 0 ); + +} /* End of resolve_preload_list() */ + + +int +mcpu_run_resolve_preload( const mcpu_run_options *options, + char **resolved, char *err, size_t errsz ) +{ + char *result = NULL; + size_t result_length = 0; + + if( options == NULL || resolved == NULL || err == NULL || errsz == 0 ) + { + errno = EINVAL; + return( -1 ); + } + + *resolved = NULL; + + if( resolve_preload_list(options, options->env_ldpreload, + "MCPU_LD_PRELOAD", &result, &result_length, + err, errsz) != 0 ) + goto fail; + + if( resolve_preload_list(options, options->ldpreload, + "--ldpreload", &result, &result_length, + err, errsz) != 0 ) + goto fail; + + *resolved = result; + return( 0 ); + +fail: + free( result ); + return( -1 ); + +} /* End of mcpu_run_resolve_preload() */ + + +int +mcpu_run_handle_options( mcpu_run_options *options, int argc, char **argv ) +{ + int i; + int exearg = 0; + + if( options == NULL || argv == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + for( i = 1; i < argc; ++i ) + { + const char *arg = argv[i]; + int common; + + if( options->exe != NULL ) + break; + + if( !strcmp( arg, "--" ) ) + { + if( i + 1 >= argc ) + { + fprintf( stderr, "%s: -- must be followed by an executable\n", + options->common.progname ); + return( -1 ); + } + exearg = ++i; + options->exe = argv[exearg]; + break; + } + else if( !strcmp( arg, "-h" ) || !strcmp( arg, "--help" ) ) + { + options->common.help = 1; + return( 0 ); + } + + common = mcpu_tool_parse_common_option( &options->common, + argc, argv, &i ); + if( common < 0 ) + return( -1 ); + if( common > 0 ) + { + if( options->common.version_action != MCPU_VERSION_NONE ) + return( 0 ); + continue; + } + + if( !strncmp( arg, "--int-reg=", 10 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_integer_register(arg + 10, &options->output) ) + { + fprintf( stderr, "%s: bad --int-reg operand: %s\n", + options->common.progname, arg + 10 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--real-reg=", 11 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_real_register(arg + 11, &options->output) ) + { + fprintf( stderr, "%s: bad --real-reg operand: %s\n", + options->common.progname, arg + 11 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--int-data=", 11 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_memory_object(arg + 11, &options->output, 0, MEMORY_DATA) ) + { + fprintf( stderr, "%s: bad --int-data operand: %s\n", + options->common.progname, arg + 11 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--real-data=", 12 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_memory_object(arg + 12, &options->output, 1, MEMORY_DATA) ) + { + fprintf( stderr, "%s: bad --real-data operand: %s\n", + options->common.progname, arg + 12 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--raw-data=", 11 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_raw_memory(arg + 11, &options->output, MEMORY_DATA) ) + { + fprintf( stderr, "%s: bad --raw-data operand: %s\n", + options->common.progname, arg + 11 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--int-control=", 14 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_memory_object(arg + 14, &options->output, 0, MEMORY_CONTROL) ) + { + fprintf( stderr, "%s: bad --int-control operand: %s\n", + options->common.progname, arg + 14 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--real-control=", 15 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_memory_object(arg + 15, &options->output, 1, MEMORY_CONTROL) ) + { + fprintf( stderr, "%s: bad --real-control operand: %s\n", + options->common.progname, arg + 15 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--raw-control=", 14 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_raw_memory(arg + 14, &options->output, MEMORY_CONTROL) ) + { + fprintf( stderr, "%s: bad --raw-control operand: %s\n", + options->common.progname, arg + 14 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--int-heap=", 11 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_memory_object(arg + 11, &options->output, 0, MEMORY_HEAP) ) + { + fprintf( stderr, "%s: bad --int-heap operand: %s\n", + options->common.progname, arg + 11 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--real-heap=", 12 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_memory_object(arg + 12, &options->output, 1, MEMORY_HEAP) ) + { + fprintf( stderr, "%s: bad --real-heap operand: %s\n", + options->common.progname, arg + 12 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--raw-heap=", 11 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_raw_memory(arg + 11, &options->output, MEMORY_HEAP) ) + { + fprintf( stderr, "%s: bad --raw-heap operand: %s\n", + options->common.progname, arg + 11 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--int-input=", 12 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_memory_object(arg + 12, &options->output, 0, MEMORY_INPUT) ) + { + fprintf( stderr, "%s: bad --int-input operand: %s\n", + options->common.progname, arg + 12 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--real-input=", 13 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_memory_object(arg + 13, &options->output, 1, MEMORY_INPUT) ) + { + fprintf( stderr, "%s: bad --real-input operand: %s\n", + options->common.progname, arg + 13 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--raw-input=", 12 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_raw_memory(arg + 12, &options->output, MEMORY_INPUT) ) + { + fprintf( stderr, "%s: bad --raw-input operand: %s\n", + options->common.progname, arg + 12 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--int-output=", 13 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_memory_object(arg + 13, &options->output, 0, MEMORY_OUTPUT) ) + { + fprintf( stderr, "%s: bad --int-output operand: %s\n", + options->common.progname, arg + 13 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--real-output=", 14 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_memory_object(arg + 14, &options->output, 1, MEMORY_OUTPUT) ) + { + fprintf( stderr, "%s: bad --real-output operand: %s\n", + options->common.progname, arg + 14 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--raw-output=", 13 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_raw_memory(arg + 13, &options->output, MEMORY_OUTPUT) ) + { + fprintf( stderr, "%s: bad --raw-output operand: %s\n", + options->common.progname, arg + 13 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--int-trace=", 12 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_memory_object(arg + 12, &options->output, 0, MEMORY_TRACE) ) + { + fprintf( stderr, "%s: bad --int-trace operand: %s\n", + options->common.progname, arg + 12 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--real-trace=", 13 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_memory_object(arg + 13, &options->output, 1, MEMORY_TRACE) ) + { + fprintf( stderr, "%s: bad --real-trace operand: %s\n", + options->common.progname, arg + 13 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--raw-trace=", 12 ) ) + { + if( options->output.kind != PRINT_NONE || + parse_raw_memory(arg + 12, &options->output, MEMORY_TRACE) ) + { + fprintf( stderr, "%s: bad --raw-trace operand: %s\n", + options->common.progname, arg + 12 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--before-run=", 13 ) ) + { + if( parse_stage_option(arg + 13, &options->before_run) ) + { + fprintf( stderr, "%s: bad --before-run action: %s\n", + options->common.progname, arg + 13 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--after-init=", 13 ) ) + { + if( parse_stage_option(arg + 13, &options->after_init) ) + { + fprintf( stderr, "%s: bad --after-init action: %s\n", + options->common.progname, arg + 13 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--on-step=", 10 ) ) + { + if( parse_stage_option(arg + 10, &options->on_step) ) + { + fprintf( stderr, "%s: bad --on-step action: %s\n", + options->common.progname, arg + 10 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--after-run=", 12 ) ) + { + if( parse_stage_option(arg + 12, &options->after_run) ) + { + fprintf( stderr, "%s: bad --after-run action: %s\n", + options->common.progname, arg + 12 ); + return( -1 ); + } + } + else if( !strncmp( arg, "--library-path=", 15 ) ) + { + if( !arg[15] || + mcpu_tool_add_library_path(&options->common, arg + 15) != 0 ) + { + fprintf( stderr, "%s: bad --library-path: %s\n", + options->common.progname, arg + 15 ); + return( -1 ); + } + } + else if( !strcmp( arg, "--library-path" ) ) + { + if( ++i >= argc || argv[i][0] == 0 || + mcpu_tool_add_library_path(&options->common, argv[i]) != 0 ) + { + fprintf( stderr, "%s: argument missing or invalid after --library-path\n", + options->common.progname ); + return( -1 ); + } + } + else if( !strncmp( arg, "--ldpreload=", 12 ) ) + { + if( options->ldpreload != NULL || !arg[12] ) + { + fprintf( stderr, + "%s: --ldpreload must be supplied once with a non-empty ':' list\n", + options->common.progname ); + return( -1 ); + } + options->ldpreload = arg + 12; + } + else if( !strncmp( arg, "--steps=", 8 ) ) + { + if( parse_u64(arg + 8, &options->steps) || !options->steps ) + { + fprintf( stderr, "%s: --steps must be positive\n", + options->common.progname ); + return( -1 ); + } + options->steps_set = 1; + } + else if( !strcmp( arg, "--init-only" ) ) + options->init_only = 1; + else if( !strncmp( arg, "--digits=", 9 ) ) + { + char *end; + unsigned long n; + + errno = 0; + n = strtoul( arg + 9, &end, 10 ); + if( errno || end == arg + 9 || *end || !n || n > 1000000UL ) + { + fprintf( stderr, "%s: bad --digits value\n", + options->common.progname ); + return( -1 ); + } + options->digits = (unsigned)n; + } + else if( !strncmp( arg, "--stack-size", 12 ) || + !strncmp( arg, "--heap-size", 11 ) || + !strncmp( arg, "--heap-limit", 12 ) ) + { + fprintf( stderr, + "%s: STACK/HEAP sizing is part of the MELF executable ABI; " + "runtime size options were removed\n", + options->common.progname ); + return( -1 ); + } + else if( arg[0] == '-' ) + { + fprintf( stderr, "%s: unknown option: %s\n", + options->common.progname, arg ); + mcpu_run_usage( options ); + return( -1 ); + } + else + { + options->exe = arg; + exearg = i; + break; + } + } + + if( options->common.help || + mcpu_tool_has_information_action(&options->common) ) + return( 0 ); + + if( options->exe == NULL ) + { + mcpu_run_usage( options ); + return( -1 ); + } + + options->program_argc = argc - exearg; + options->program_argv = argv + exearg; + return( 0 ); + +} /* End of mcpu_run_handle_options() */ diff --git a/utils/mcpu-run-options.h b/utils/mcpu-run-options.h new file mode 100644 index 0000000..06f9412 --- /dev/null +++ b/utils/mcpu-run-options.h @@ -0,0 +1,105 @@ +#ifndef __MCPU_RUN_OPTIONS_H__ +#define __MCPU_RUN_OPTIONS_H__ 1 + +#include <stddef.h> + +#include <libmcpu.h> + +#include "mcpu-tool-options.h" + +typedef enum __print_kind +{ + PRINT_NONE = 0, + PRINT_INTEGER_REGISTER, + PRINT_REAL_REGISTER, + PRINT_INTEGER_MEMORY, + PRINT_REAL_MEMORY, + PRINT_RAW_MEMORY +} print_kind; + +typedef enum __memory_kind +{ + MEMORY_NONE = 0, + MEMORY_DATA, + MEMORY_CONTROL, + MEMORY_HEAP, + MEMORY_INPUT, + MEMORY_OUTPUT, + MEMORY_TRACE +} memory_kind; + +typedef struct __print_spec +{ + print_kind kind; + memory_kind memory; + unsigned int bits; + __mpu_uint64_t address; + __mpu_uint64_t count; +} print_spec; + +typedef enum __action_kind +{ + ACTION_NONE = 0, + ACTION_PRINT_INTEGER_REGISTER, + ACTION_PRINT_REAL_REGISTER, + ACTION_SET_INTEGER_REGISTER, + ACTION_SET_REAL_REGISTER, + ACTION_PRINT_INTEGER_MEMORY, + ACTION_PRINT_REAL_MEMORY, + ACTION_SET_INTEGER_MEMORY, + ACTION_SET_REAL_MEMORY, + ACTION_PRINT_RAW_MEMORY +} action_kind; + +typedef struct __action_item +{ + unsigned int bits; + __mpu_uint64_t address; + char *value; +} action_item; + +typedef struct __stage_action +{ + action_kind kind; + memory_kind memory; + action_item *item; + size_t count; + print_spec raw; +} stage_action; + +typedef struct __stage_action_list +{ + stage_action *action; + size_t count; + size_t capacity; +} stage_action_list; + +typedef struct mcpu_run_options mcpu_run_options; +struct mcpu_run_options +{ + mcpu_tool_options common; + print_spec output; + stage_action_list before_run; + stage_action_list after_init; + stage_action_list on_step; + stage_action_list after_run; + unsigned int digits; + __mpu_uint64_t steps; + int steps_set; + int init_only; + const char *exe; + const char *env_ldpreload; + const char *ldpreload; + int program_argc; + char **program_argv; +}; + +void mcpu_run_options_init( mcpu_run_options *options, const char *progname ); +void mcpu_run_options_free( mcpu_run_options *options ); +int mcpu_run_handle_options( mcpu_run_options *options, + int argc, char **argv ); +void mcpu_run_usage( const mcpu_run_options *options ); +int mcpu_run_resolve_preload( const mcpu_run_options *options, + char **resolved, char *err, size_t errsz ); + +#endif /* __MCPU_RUN_OPTIONS_H__ */ diff --git a/utils/mcpu-run.c b/utils/mcpu-run.c new file mode 100644 index 0000000..d79e034 --- /dev/null +++ b/utils/mcpu-run.c @@ -0,0 +1,870 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <libmcpu.h> +#include <libmpuio.h> +#include "mcpu-exec.h" +#include "mcpu-dependency.h" + +#include "mcpu-run-options.h" + +static const char * +memory_name( memory_kind memory ) +{ + switch( memory ) + { + case MEMORY_DATA: return( "DATA" ); + case MEMORY_CONTROL: return( "CONTROL" ); + case MEMORY_HEAP: return( "HEAP" ); + case MEMORY_INPUT: return( "INPUT" ); + case MEMORY_OUTPUT: return( "OUTPUT" ); + case MEMORY_TRACE: return( "TRACE" ); + default: return( "UNKNOWN" ); + } + +} /* End of memory_name() */ + + +static __mpu_UNIT_t * +memory_ptr( memory_kind memory ) +{ + switch( memory ) + { + case MEMORY_DATA: return( __cpu_data_ptr() ); + case MEMORY_CONTROL: return( __cpu_control_ptr() ); + case MEMORY_HEAP: return( __cpu_heap_ptr() ); + case MEMORY_INPUT: return( __cpu_input_ptr() ); + case MEMORY_OUTPUT: return( __cpu_output_ptr() ); + case MEMORY_TRACE: return( __cpu_trace_ptr() ); + default: return( (__mpu_UNIT_t *)0 ); + } + +} /* End of memory_ptr() */ + + +static __mpu_uint64_t +memory_size( memory_kind memory ) +{ + switch( memory ) + { + case MEMORY_DATA: return( __cpu_data_size() ); + case MEMORY_CONTROL: return( __cpu_control_size() ); + case MEMORY_HEAP: return( __cpu_heap_size() ); + case MEMORY_INPUT: return( __cpu_input_size() ); + case MEMORY_OUTPUT: return( __cpu_output_size() ); + case MEMORY_TRACE: return( __cpu_trace_size() ); + default: return( 0 ); + } + +} /* End of memory_size() */ + + + +static int +make_integer_format( __mpu_char16_t *format, size_t size, + unsigned int bits, int newline ) +{ + char ascii[64]; + int len, i; + + len = snprintf( ascii, sizeof( ascii ), newline ? "%%Z%ud\n" : "%%Z%ud", bits ); + if( len < 0 || (size_t)len >= sizeof( ascii ) || (size_t)len + 1U > size ) + return( -1 ); + + for( i = 0; i <= len; ++i ) + format[i] = (__mpu_char16_t)(unsigned char)ascii[i]; + + return( 0 ); + +} /* End of make_integer_format() */ + + +static __mpu_UNIT_t * +memory_object_ptr( const print_spec *spec, __mpu_uint64_t bytes, + const char *object_name ) +{ + __mpu_UNIT_t *p; + __mpu_uint64_t size; + + p = memory_ptr( spec->memory ); + size = memory_size( spec->memory ); + + if( !p || spec->address > size || bytes > size - spec->address ) + { + fprintf( stderr, "mcpu-run: %s %s object is outside %s memory\n", + object_name, memory_name( spec->memory ), + memory_name( spec->memory ) ); + return( (__mpu_UNIT_t *)0 ); + } + + return( p + (size_t)spec->address ); + +} /* End of memory_object_ptr() */ + + +static int +print_integer_value( const print_spec *spec, int newline ) +{ + __mpu_char16_t format[64]; + __mpu_UNIT_t *value; + __mpu_uint64_t bytes; + + if( make_integer_format( format, sizeof( format ) / sizeof( format[0] ), + spec->bits, newline ) ) + return( -1 ); + + bytes = spec->bits / 8U; + if( spec->kind == PRINT_INTEGER_REGISTER ) + value = __cpu_reg_ptr( spec->bits, (__mpu_uint32_t)spec->address ); + else if( spec->kind == PRINT_INTEGER_MEMORY ) + value = memory_object_ptr( spec, bytes, "integer" ); + else + return( -1 ); + + if( !value ) return( -1 ); + if( fflush( stdout ) ) return( -1 ); + + if( mpu_dprintf( STDOUT_FILENO, format, (mpu_int *)value ) < 0 ) + { + fprintf( stderr, "mcpu-run: LibMPUIO integer output failed\n" ); + return( -1 ); + } + + return( 0 ); + +} /* End of print_integer_value() */ + + +static int +print_integer( const print_spec *spec ) +{ + return( print_integer_value( spec, 1 ) ); + +} /* End of print_integer() */ + + +static int +make_real_format( __mpu_char16_t *format, size_t size, + unsigned int bits, unsigned int digits, int newline ) +{ + char ascii[96]; + int len, i; + + len = snprintf( ascii, sizeof( ascii ), newline ? "%%.%uZ%uR\n" : "%%.%uZ%uR", + digits, bits ); + if( len < 0 || (size_t)len >= sizeof( ascii ) || (size_t)len + 1U > size ) + return( -1 ); + + for( i = 0; i <= len; ++i ) + format[i] = (__mpu_char16_t)(unsigned char)ascii[i]; + + return( 0 ); + +} /* End of make_real_format() */ + + +static int +print_real_value( const print_spec *spec, unsigned int digits, int newline ) +{ + __mpu_char16_t format[96]; + __mpu_UNIT_t *value; + __mpu_uint64_t bytes; + + if( make_real_format( format, sizeof( format ) / sizeof( format[0] ), + spec->bits, digits, newline ) ) + return( -1 ); + + bytes = spec->bits / 8U; + if( spec->kind == PRINT_REAL_REGISTER ) + value = __cpu_reg_ptr( spec->bits, (__mpu_uint32_t)spec->address ); + else if( spec->kind == PRINT_REAL_MEMORY ) + value = memory_object_ptr( spec, bytes, "real" ); + else + return( -1 ); + + if( !value ) return( -1 ); + if( fflush( stdout ) ) return( -1 ); + + if( mpu_dprintf( STDOUT_FILENO, format, (mpu_real *)value ) < 0 ) + { + fprintf( stderr, "mcpu-run: LibMPUIO real output failed\n" ); + return( -1 ); + } + + return( 0 ); + +} /* End of print_real_value() */ + + +static int +print_real( const print_spec *spec, unsigned int digits ) +{ + return( print_real_value( spec, digits, 1 ) ); + +} /* End of print_real() */ + +static int +raw_memory_range( const print_spec *spec, const cpu_exec_loaded *loaded, + __mpu_UNIT_t **ptr, __mpu_uint64_t *count ) +{ + __mpu_uint64_t begin, end, size; + __mpu_UNIT_t *p; + + if( spec->memory == MEMORY_DATA ) + { + if( !loaded->has_data_section ) + { + fprintf( stderr, "mcpu-run: executable has no .data section\n" ); + return( -1 ); + } + + begin = loaded->data_section_begin; + if( loaded->data_section_size > UINT64_MAX - begin ) return( -1 ); + end = begin + loaded->data_section_size; + + if( spec->address < begin || spec->address >= end ) + { + fprintf( stderr, "mcpu-run: raw DATA address is outside .data\n" ); + return( -1 ); + } + + p = __cpu_data_ptr(); + } + else + { + begin = 0; + size = memory_size( spec->memory ); + p = memory_ptr( spec->memory ); + end = size; + + if( !p || spec->address >= end ) + { + fprintf( stderr, "mcpu-run: raw %s address is outside %s memory\n", + memory_name( spec->memory ), memory_name( spec->memory ) ); + return( -1 ); + } + } + + *count = spec->count; + if( *count > end - spec->address ) *count = end - spec->address; + + *ptr = p + (size_t)spec->address; + + return( 0 ); + +} /* End of raw_memory_range() */ + + +static int +print_raw_memory( const print_spec *spec, const cpu_exec_loaded *loaded ) +{ + __mpu_UNIT_t *p; + __mpu_uint64_t count, off; + + if( spec->kind != PRINT_RAW_MEMORY ) return( -1 ); + if( raw_memory_range( spec, loaded, &p, &count ) ) return( -1 ); + + printf( "%s memory dump: (%llu bytes):\n", + memory_name( spec->memory ), (unsigned long long)count ); + + for( off = 0; off < count; off += 16 ) + { + unsigned int j; + unsigned int row = (unsigned int)((count - off) > 16 ? 16 : (count - off)); + + printf( " %04llx : ", (unsigned long long)off ); + for( j = 0; j < 16; ++j ) + { + if( j < row ) + printf( "%02x ", (unsigned int)p[(size_t)off + j] ); + else + printf( " " ); + } + printf( " | " ); + for( j = 0; j < row; ++j ) + { + unsigned int c = (unsigned int)p[(size_t)off + j]; + putchar( c >= 0x20U && c <= 0x7eU ? (int)c : '.' ); + } + putchar( '\n' ); + } + + return( ferror( stdout ) ? -1 : 0 ); + +} /* End of print_raw_memory() */ + + +static int +write_stdout( const char *s, size_t n ) +{ + while( n ) + { + ssize_t rc = write( STDOUT_FILENO, s, n ); + + if( rc < 0 ) + { + if( errno == EINTR ) continue; + return( -1 ); + } + if( !rc ) return( -1 ); + + s += (size_t)rc; + n -= (size_t)rc; + } + + return( 0 ); + +} /* End of write_stdout() */ + + +static __mpu_char16_t * +ascii_to_ucs2( const char *s ) +{ + __mpu_char16_t *p; + size_t i, n; + + if( !s ) return( (__mpu_char16_t *)0 ); + + n = strlen( s ); + if( n > (SIZE_MAX / sizeof( *p )) - 1U ) return( (__mpu_char16_t *)0 ); + + p = (__mpu_char16_t *)malloc( (n + 1U) * sizeof( *p ) ); + if( !p ) return( (__mpu_char16_t *)0 ); + + for( i = 0; i < n; ++i ) + { + unsigned char c = (unsigned char)s[i]; + + if( c >= 0x80U ) + { + free( p ); + return( (__mpu_char16_t *)0 ); + } + p[i] = (__mpu_char16_t)c; + } + p[n] = 0; + + return( p ); + +} /* End of ascii_to_ucs2() */ + + +static int +scan_action_value( const char *value, unsigned int bits, + int real_object, __mpu_UNIT_t *dst ) +{ + __mpu_char16_t *text = (__mpu_char16_t *)0; + __mpu_char16_t *format = (__mpu_char16_t *)0; + __mpu_uint32_t psw; + char ascii[64]; + size_t n; + int len, consumed = -1, rc; + + n = strlen( value ); + len = snprintf( ascii, sizeof( ascii ), real_object ? "%%Z%uR%%n" : "%%Z%ui%%n", + bits ); + if( len < 0 || (size_t)len >= sizeof( ascii ) ) return( -1 ); + + text = ascii_to_ucs2( value ); + format = ascii_to_ucs2( ascii ); + if( !text || !format ) goto fail; + + psw = __cpu_get_psw(); + if( real_object ) + rc = mpu_sscanf( text, format, (mpu_real *)dst, &consumed ); + else + rc = mpu_sscanf( text, format, (mpu_int *)dst, &consumed ); + __cpu_set_psw( psw ); + + free( format ); + free( text ); + + if( rc != 1 || consumed < 0 || (size_t)consumed != n ) return( -1 ); + + return( 0 ); + +fail: + free( format ); + free( text ); + return( -1 ); + +} /* End of scan_action_value() */ + + +static void +make_action_print_spec( const stage_action *action, const action_item *item, + print_spec *spec ) +{ + memset( spec, 0, sizeof( *spec ) ); + spec->memory = action->memory; + spec->bits = item->bits; + spec->address = item->address; + + if( action->kind == ACTION_PRINT_INTEGER_REGISTER || + action->kind == ACTION_SET_INTEGER_REGISTER ) + spec->kind = PRINT_INTEGER_REGISTER; + else if( action->kind == ACTION_PRINT_REAL_REGISTER || + action->kind == ACTION_SET_REAL_REGISTER ) + spec->kind = PRINT_REAL_REGISTER; + else if( action->kind == ACTION_PRINT_INTEGER_MEMORY || + action->kind == ACTION_SET_INTEGER_MEMORY ) + spec->kind = PRINT_INTEGER_MEMORY; + else if( action->kind == ACTION_PRINT_REAL_MEMORY || + action->kind == ACTION_SET_REAL_MEMORY ) + spec->kind = PRINT_REAL_MEMORY; + +} /* End of make_action_print_spec() */ + + +static __mpu_UNIT_t * +action_object_ptr( const stage_action *action, const action_item *item, + const char *object_name ) +{ + print_spec spec; + + make_action_print_spec( action, item, &spec ); + if( spec.kind == PRINT_INTEGER_REGISTER || spec.kind == PRINT_REAL_REGISTER ) + return( __cpu_reg_ptr( spec.bits, (__mpu_uint32_t)spec.address ) ); + + return( memory_object_ptr( &spec, spec.bits / 8U, object_name ) ); + +} /* End of action_object_ptr() */ + + +static int +execute_print_action( const stage_action *action, unsigned int digits ) +{ + print_spec spec; + size_t i; + + /* Validate the whole action before producing any output. */ + for( i = 0; i < action->count; ++i ) + { + if( !action_object_ptr( action, &action->item[i], + (action->kind == ACTION_PRINT_REAL_REGISTER || + action->kind == ACTION_PRINT_REAL_MEMORY) ? + "real" : "integer" ) ) + return( -1 ); + } + + for( i = 0; i < action->count; ++i ) + { + make_action_print_spec( action, &action->item[i], &spec ); + + if( i && write_stdout( " ", 1U ) ) return( -1 ); + + if( spec.kind == PRINT_INTEGER_REGISTER || spec.kind == PRINT_INTEGER_MEMORY ) + { + if( print_integer_value( &spec, 0 ) ) return( -1 ); + } + else + { + if( print_real_value( &spec, digits, 0 ) ) return( -1 ); + } + } + + if( write_stdout( "\n", 1U ) ) return( -1 ); + + return( 0 ); + +} /* End of execute_print_action() */ + + +typedef struct __set_value +{ + __mpu_UNIT_t *dst; + __mpu_UNIT_t *value; + size_t bytes; +} set_value; + + +static int +execute_set_action( const stage_action *action ) +{ + set_value *value; + size_t i, j; + int real_object; + + value = (set_value *)calloc( action->count, sizeof( *value ) ); + if( !value ) return( -1 ); + + real_object = (action->kind == ACTION_SET_REAL_REGISTER || + action->kind == ACTION_SET_REAL_MEMORY); + + /* Parse and validate every assignment before changing guest state. */ + for( i = 0; i < action->count; ++i ) + { + value[i].bytes = action->item[i].bits / 8U; + value[i].dst = action_object_ptr( action, &action->item[i], + real_object ? "real" : "integer" ); + if( !value[i].dst ) goto fail; + + value[i].value = (__mpu_UNIT_t *)calloc( value[i].bytes, 1U ); + if( !value[i].value ) goto fail; + + if( scan_action_value( action->item[i].value, action->item[i].bits, + real_object, value[i].value ) ) + { + fprintf( stderr, "mcpu-run: cannot parse stage value '%s' as %s%u\n", + action->item[i].value, real_object ? "real" : "int", + action->item[i].bits ); + goto fail; + } + } + + for( i = 0; i < action->count; ++i ) + memcpy( value[i].dst, value[i].value, value[i].bytes ); + + for( i = 0; i < action->count; ++i ) free( value[i].value ); + free( value ); + + return( 0 ); + +fail: + for( j = 0; j < action->count; ++j ) free( value[j].value ); + free( value ); + return( -1 ); + +} /* End of execute_set_action() */ + + +static int +execute_stage_action( const stage_action *action, unsigned int digits, + const cpu_exec_loaded *loaded ) +{ + switch( action->kind ) + { + case ACTION_PRINT_INTEGER_REGISTER: + case ACTION_PRINT_REAL_REGISTER: + case ACTION_PRINT_INTEGER_MEMORY: + case ACTION_PRINT_REAL_MEMORY: + return( execute_print_action( action, digits ) ); + + case ACTION_SET_INTEGER_REGISTER: + case ACTION_SET_REAL_REGISTER: + case ACTION_SET_INTEGER_MEMORY: + case ACTION_SET_REAL_MEMORY: + return( execute_set_action( action ) ); + + case ACTION_PRINT_RAW_MEMORY: + if( print_raw_memory( &action->raw, loaded ) ) return( -1 ); + if( fflush( stdout ) ) return( -1 ); + return( 0 ); + + default: + return( -1 ); + } + +} /* End of execute_stage_action() */ + + +static int +execute_stage_action_list( const stage_action_list *list, unsigned int digits, + const cpu_exec_loaded *loaded ) +{ + size_t i; + + for( i = 0; i < list->count; ++i ) + if( execute_stage_action( &list->action[i], digits, loaded ) ) + return( -1 ); + + return( 0 ); + +} /* End of execute_stage_action_list() */ + + +static int +execution_fault( int rc ) +{ + fprintf( stderr, "mcpu-run: execution fault %d at PC=0x%016llx, PSW=0x%08x\n", + rc, (unsigned long long)__cpu_pc, (unsigned int)__cpu_get_psw()); + return( 1 ); +} + + +static int +mcpu_run_execute( const mcpu_run_options *options ) +{ + unsigned long long completed = 0; + cpu_exec_loaded loaded; + char err[512]; + char *resolved_preload = NULL; + char *resolved_modules = NULL; + size_t preload_count = 0; + int rc; + int host_status = 0; + + if( options == NULL ) + return( 1 ); + + if( mcpu_run_resolve_preload(options, &resolved_preload, + err, sizeof(err)) != 0 ) + { + fprintf( stderr, "%s: preload resolution error: %s\n", + options->common.progname, err ); + return( 1 ); + } + + if( mcpu_dependency_build_load_list(&options->common, options->exe, + resolved_preload, &resolved_modules, + &preload_count, err, sizeof(err)) != 0 ) + { + fprintf( stderr, "%s: dependency resolution error: %s\n", + options->common.progname, err ); + free( resolved_preload ); + return( 1 ); + } + + __cpu_init(); + + /* Guest argv begins with the executable spelling supplied to mcpu-run. */ + if( cpu_exec_load_resolved(options->exe, resolved_modules, + preload_count, + options->program_argc, options->program_argv, + &loaded, err, sizeof(err)) ) + { + fprintf( stderr, "%s: load error: %s\n", + options->common.progname, err ); + __cpu_free_context(); + free( resolved_modules ); + free( resolved_preload ); + return( 1 ); + } + + if( execute_stage_action_list(&options->before_run, + options->digits, &loaded) ) + { + fprintf( stderr, "%s: --before-run action failed\n", + options->common.progname ); + host_status = 1; + goto done; + } + + if( loaded.has_init ) + { + __cpu_pc = loaded.init_entry; + rc = __cpu_run(); + if( rc < 0 ) + { + host_status = execution_fault( rc ); + goto done; + } + if( rc == CPU_EXEC_EXIT ) + goto graceful_exit; + if( __cpu_get_stop_reason() == CPU_STOP_HALT ) + goto stopped; + if( rc != CPU_EXEC_END ) + { + fprintf( stderr, "%s: INIT did not terminate with END\n", + options->common.progname ); + host_status = 1; + goto done; + } + if( __cpu_sp != loaded.startup_base || __cpu_fp != loaded.stack_size ) + { + fprintf( stderr, "%s: INIT did not restore top-level STACK state\n", + options->common.progname ); + host_status = 1; + goto done; + } + __cpu_pc = loaded.life_entry; + } + + if( execute_stage_action_list(&options->after_init, + options->digits, &loaded) ) + { + fprintf( stderr, "%s: --after-init action failed\n", + options->common.progname ); + host_status = 1; + goto done; + } + + if( options->init_only ) + goto stopped; + + for( ; ; ) + { + rc = __cpu_run(); + if( rc < 0 ) + { + host_status = execution_fault( rc ); + goto done; + } + if( rc == CPU_EXEC_EXIT ) + goto graceful_exit; + if( __cpu_get_stop_reason() == CPU_STOP_HALT ) + goto stopped; + if( rc != CPU_EXEC_END ) + { + fprintf( stderr, "%s: LIFE phase stopped unexpectedly (%d)\n", + options->common.progname, rc ); + host_status = 1; + goto done; + } + ++completed; + if( execute_stage_action_list(&options->on_step, + options->digits, &loaded) ) + { + fprintf( stderr, + "%s: --on-step action failed after LIFE step %llu\n", + options->common.progname, completed ); + host_status = 1; + goto done; + } + if( !options->steps_set || completed >= options->steps ) + goto stopped; + /* Resume from the PC already advanced past END. */ + } + +graceful_exit: + host_status = (int)__cpu_get_exit_status(); + if( loaded.has_fini ) + { + if( __cpu_sp != loaded.startup_base || __cpu_fp != loaded.stack_size ) + { + fprintf( stderr, + "%s: LIFE did not restore top-level STACK state before FINI\n", + options->common.progname ); + host_status = 1; + goto done; + } + __cpu_pc = loaded.fini_entry; + rc = __cpu_run(); + if( rc < 0 ) + { + host_status = execution_fault( rc ); + goto done; + } + if( rc != CPU_EXEC_END ) + { + fprintf( stderr, "%s: FINI must terminate with END\n", + options->common.progname ); + host_status = 1; + goto done; + } + if( __cpu_sp != loaded.startup_base || __cpu_fp != loaded.stack_size ) + { + fprintf( stderr, "%s: FINI did not restore top-level STACK state\n", + options->common.progname ); + host_status = 1; + goto done; + } + } + +stopped: + if( execute_stage_action_list(&options->after_run, + options->digits, &loaded) ) + { + fprintf( stderr, "%s: --after-run action failed\n", + options->common.progname ); + host_status = 1; + goto done; + } + + if( options->output.kind == PRINT_INTEGER_REGISTER || + options->output.kind == PRINT_INTEGER_MEMORY ) + { + if( print_integer(&options->output) ) + { + host_status = 1; + goto done; + } + } + else if( options->output.kind == PRINT_REAL_REGISTER || + options->output.kind == PRINT_REAL_MEMORY ) + { + if( print_real(&options->output, options->digits) ) + { + host_status = 1; + goto done; + } + } + else if( options->output.kind == PRINT_RAW_MEMORY ) + { + if( print_raw_memory(&options->output, &loaded) ) + { + host_status = 1; + goto done; + } + } + +done: + __cpu_free_context(); + free( resolved_modules ); + free( resolved_preload ); + if( host_status < 0 || host_status > 255 ) + return( 1 ); + return( host_status ); + +} /* End of mcpu_run_execute() */ + + +int +main( int argc, char **argv ) +{ + mcpu_run_options options; + int rc = 2; + + mcpu_run_options_init( &options, argv[0] ); + + if( mcpu_run_handle_options(&options, argc, argv) != 0 ) + goto done; + + if( options.common.help ) + { + mcpu_run_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 ) + { + rc = 1; + goto done; + } + rc = 0; + goto done; + } + + if( mcpu_tool_options_prepare(&options.common, argv[0]) != 0 ) + { + rc = 1; + goto done; + } + + if( mcpu_tool_has_information_action(&options.common) ) + { + if( options.common.dump_config && + mcpu_tool_dump_config(&options.common, stdout) != 0 ) + { + rc = 1; + goto done; + } + if( options.common.print_search_dirs && + mcpu_tool_print_search_dirs(&options.common, stdout) != 0 ) + { + rc = 1; + goto done; + } + rc = 0; + goto done; + } + + rc = mcpu_run_execute( &options ); + +done: + mcpu_run_options_free( &options ); + return( rc ); + +} /* End of main() */ diff --git a/utils/mcpu-runtime.c b/utils/mcpu-runtime.c new file mode 100644 index 0000000..7ed6835 --- /dev/null +++ b/utils/mcpu-runtime.c @@ -0,0 +1,366 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <limits.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "mcpu-runtime.h" + +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif + + +static char * +path_dirname( const char *path ) +{ + const char *slash; + size_t length; + char *result; + + if( path == NULL || *path == 0 ) + { + errno = EINVAL; + return( NULL ); + } + + slash = strrchr( path, '/' ); + if( slash == NULL ) + { + errno = EINVAL; + return( NULL ); + } + + if( slash == path ) + length = 1; + else + length = (size_t)(slash - path); + + result = (char *)malloc( length + 1 ); + if( result == NULL ) + return( NULL ); + + memcpy( result, path, length ); + result[length] = 0; + return( result ); +} + + +static char * +path_join( const char *directory, const char *name ) +{ + size_t directory_length; + size_t name_length; + int need_slash; + char *result; + + if( directory == NULL || name == NULL ) + { + errno = EINVAL; + return( NULL ); + } + + directory_length = strlen( directory ); + name_length = strlen( name ); + need_slash = directory_length != 0 && directory[directory_length - 1] != '/'; + + if( directory_length > SIZE_MAX - name_length - (size_t)need_slash - 1 ) + { + errno = EOVERFLOW; + return( NULL ); + } + + result = (char *)malloc( directory_length + name_length + + (size_t)need_slash + 1 ); + if( result == NULL ) + return( NULL ); + + memcpy( result, directory, directory_length ); + if( need_slash ) + result[directory_length++] = '/'; + memcpy( result + directory_length, name, name_length ); + result[directory_length + name_length] = 0; + return( result ); +} + + +static char * +path_absolute( const char *path ) +{ + char cwd[PATH_MAX]; + char *result; + + if( path == NULL || *path == 0 ) + { + errno = EINVAL; + return( NULL ); + } + + if( path[0] == '/' ) + return( strdup(path) ); + + if( getcwd(cwd, sizeof(cwd)) == NULL ) + return( NULL ); + + result = path_join( cwd, path ); + return( result ); +} + + +static int +resolve_proc_self_exe( char path[PATH_MAX] ) +{ + ssize_t length; + + length = readlink( "/proc/self/exe", path, PATH_MAX - 1 ); + if( length < 0 ) + return( -1 ); + + if( length >= PATH_MAX - 1 ) + { + errno = ENAMETOOLONG; + return( -1 ); + } + + path[length] = 0; + return( 0 ); +} + + +static int +resolve_candidate( const char *candidate, char path[PATH_MAX] ) +{ + if( candidate == NULL || *candidate == 0 ) + { + errno = EINVAL; + return( -1 ); + } + + if( realpath(candidate, path) == NULL ) + return( -1 ); + + if( access(path, X_OK) != 0 ) + return( -1 ); + + return( 0 ); +} + + +static int +resolve_argv0( const char *argv0, char path[PATH_MAX] ) +{ + const char *search; + const char *entry; + + if( argv0 == NULL || *argv0 == 0 ) + { + errno = EINVAL; + return( -1 ); + } + + if( strchr(argv0, '/') != NULL ) + return( resolve_candidate(argv0, path) ); + + search = getenv( "PATH" ); + if( search == NULL ) + { + errno = ENOENT; + return( -1 ); + } + + entry = search; + for( ;; ) + { + const char *colon = strchr( entry, ':' ); + size_t length = colon ? (size_t)(colon - entry) : strlen(entry); + const char *directory = entry; + char directory_buffer[PATH_MAX]; + char *candidate; + int rc; + + if( length == 0 ) + directory = "."; + else + { + if( length >= sizeof(directory_buffer) ) + { + errno = ENAMETOOLONG; + return( -1 ); + } + memcpy( directory_buffer, entry, length ); + directory_buffer[length] = 0; + directory = directory_buffer; + } + + candidate = path_join( directory, argv0 ); + if( candidate == NULL ) + return( -1 ); + rc = resolve_candidate( candidate, path ); + free( candidate ); + if( rc == 0 ) + return( 0 ); + + if( colon == NULL ) + break; + entry = colon + 1; + } + + errno = ENOENT; + return( -1 ); +} + + +void +mcpu_runtime_paths_init( mcpu_runtime_paths *paths ) +{ + if( paths == NULL ) + return; + + paths->executable_path = NULL; + paths->executable_dir = NULL; + paths->installation_root = NULL; + paths->config_file = NULL; + paths->system_library_path = NULL; +} + + +void +mcpu_runtime_paths_free( mcpu_runtime_paths *paths ) +{ + if( paths == NULL ) + return; + + free( paths->executable_path ); + free( paths->executable_dir ); + free( paths->installation_root ); + free( paths->config_file ); + free( paths->system_library_path ); + mcpu_runtime_paths_init( paths ); +} + + +int +mcpu_runtime_paths_discover( mcpu_runtime_paths *paths, const char *argv0 ) +{ + char resolved[PATH_MAX]; + char *executable_path = NULL; + char *executable_dir = NULL; + char *installation_root = NULL; + char *config_file = NULL; + char *system_library_path = NULL; + + if( paths == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + if( resolve_proc_self_exe(resolved) != 0 ) + { + if( resolve_argv0(argv0, resolved) != 0 ) + return( -1 ); + } + + executable_path = strdup( resolved ); + if( executable_path == NULL ) + goto fail; + + executable_dir = path_dirname( executable_path ); + if( executable_dir == NULL ) + goto fail; + + installation_root = path_dirname( executable_dir ); + if( installation_root == NULL ) + goto fail; + + config_file = path_join( installation_root, "etc/mcpu-ld.conf" ); + if( config_file == NULL ) + goto fail; + + system_library_path = path_join( installation_root, "lib" ); + if( system_library_path == NULL ) + goto fail; + + mcpu_runtime_paths_free( paths ); + paths->executable_path = executable_path; + paths->executable_dir = executable_dir; + paths->installation_root = installation_root; + paths->config_file = config_file; + paths->system_library_path = system_library_path; + return( 0 ); + +fail: + free( executable_path ); + free( executable_dir ); + free( installation_root ); + free( config_file ); + free( system_library_path ); + return( -1 ); +} + + +int +mcpu_runtime_paths_set_root( mcpu_runtime_paths *paths, const char *root ) +{ + char *installation_root; + char *config_file; + char *system_library_path; + + if( paths == NULL || root == NULL || *root == 0 ) + { + errno = EINVAL; + return( -1 ); + } + + installation_root = path_absolute( root ); + if( installation_root == NULL ) + return( -1 ); + + config_file = path_join( installation_root, "etc/mcpu-ld.conf" ); + if( config_file == NULL ) + goto fail; + + system_library_path = path_join( installation_root, "lib" ); + if( system_library_path == NULL ) + goto fail; + + free( paths->installation_root ); + free( paths->config_file ); + free( paths->system_library_path ); + paths->installation_root = installation_root; + paths->config_file = config_file; + paths->system_library_path = system_library_path; + return( 0 ); + +fail: + free( installation_root ); + free( config_file ); + return( -1 ); +} + + +int +mcpu_runtime_paths_set_system_library( mcpu_runtime_paths *paths, + const char *path ) +{ + char *copy; + + if( paths == NULL || path == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + copy = strdup( path ); + if( copy == NULL ) + return( -1 ); + + free( paths->system_library_path ); + paths->system_library_path = copy; + return( 0 ); +} diff --git a/utils/mcpu-runtime.h b/utils/mcpu-runtime.h new file mode 100644 index 0000000..d68f7a6 --- /dev/null +++ b/utils/mcpu-runtime.h @@ -0,0 +1,23 @@ +#ifndef __MCPU_RUNTIME_H__ +#define __MCPU_RUNTIME_H__ 1 + +typedef struct mcpu_runtime_paths mcpu_runtime_paths; +struct mcpu_runtime_paths +{ + char *executable_path; + char *executable_dir; + char *installation_root; + char *config_file; + char *system_library_path; +}; + +void mcpu_runtime_paths_init( mcpu_runtime_paths *paths ); +void mcpu_runtime_paths_free( mcpu_runtime_paths *paths ); +int mcpu_runtime_paths_discover( mcpu_runtime_paths *paths, + const char *argv0 ); +int mcpu_runtime_paths_set_root( mcpu_runtime_paths *paths, + const char *root ); +int mcpu_runtime_paths_set_system_library( mcpu_runtime_paths *paths, + const char *path ); + +#endif /* __MCPU_RUNTIME_H__ */ diff --git a/utils/mcpu-strip.c b/utils/mcpu-strip.c new file mode 100644 index 0000000..14f4b08 --- /dev/null +++ b/utils/mcpu-strip.c @@ -0,0 +1,332 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "mcpu-archive.h" +#include "mcpu-melf.h" +#include "mcpu-object-edit.h" +#include "mcpu-version.h" + + +static void +usage( FILE *fp, const char *name ) +{ + fprintf( fp, + "usage: %s [options] MELF-file\n\n" + "Strip non-runtime symbol metadata from LibMCPU MELF files and archives.\n\n" + "The current safe policy removes ordinary SHT_SYMTAB/SHT_STRTAB metadata\n" + "from ET_EXEC/ET_DYN only when no retained section depends on it. ET_REL\n" + "symbol tables, dynamic-linking tables, relocations and MCPU metadata are\n" + "always preserved.\n\n" + "Options:\n" + " -o, --output=FILE - write result to FILE instead of replacing input\n" + " --help - show this help\n" + " --version - show program version\n" + " --dump-version - show version number only\n", + name ); + +} /* End of usage() */ + + +static int +make_temp_name( const char *path, char **name, char *err, size_t errsz ) +{ + size_t n = strlen( path ); + char *p; + + if( n > SIZE_MAX - 32 ) + { + snprintf( err, errsz, "output path is too long" ); + return( -1 ); + } + p = (char *)malloc( n + 32 ); + if( !p ) + { + snprintf( err, errsz, "out of memory" ); + return( -1 ); + } + snprintf( p, n + 32, "%s.mcpu-strip-XXXXXX", path ); + *name = p; + return( 0 ); + +} /* End of make_temp_name() */ + + +static int +commit_bytes( const char *input, const char *output, + const unsigned char *data, size_t size, + char *err, size_t errsz ) +{ + struct stat st; + char *tmp = NULL; + int fd = -1; + int rc = -1; + + if( stat( input, &st ) ) + { + snprintf( err, errsz, "%s: %s", input, strerror( errno ) ); + return( -1 ); + } + if( make_temp_name( output, &tmp, err, errsz ) ) + return( -1 ); + + fd = mkstemp( tmp ); + if( fd < 0 ) + { + snprintf( err, errsz, "%s: %s", tmp, strerror( errno ) ); + goto out; + } + if( close( fd ) ) + { + fd = -1; + snprintf( err, errsz, "%s: %s", tmp, strerror( errno ) ); + goto out; + } + fd = -1; + + if( cpu_melf_writeall( tmp, data, size, err, errsz ) ) + goto out; + if( chmod( tmp, st.st_mode & 07777 ) ) + { + snprintf( err, errsz, "%s: %s", tmp, strerror( errno ) ); + goto out; + } + if( rename( tmp, output ) ) + { + snprintf( err, errsz, "%s: %s", output, strerror( errno ) ); + goto out; + } + rc = 0; + +out: + if( fd >= 0 ) + close( fd ); + if( rc && tmp ) + unlink( tmp ); + free( tmp ); + return( rc ); + +} /* End of commit_bytes() */ + + +static int +commit_archive( const char *input, const char *output, + const mcpu_archive *archive, int index, + char *err, size_t errsz ) +{ + struct stat st; + char *tmp = NULL; + int fd = -1; + int rc = -1; + + if( stat( input, &st ) ) + { + snprintf( err, errsz, "%s: %s", input, strerror( errno ) ); + return( -1 ); + } + if( make_temp_name( output, &tmp, err, errsz ) ) + return( -1 ); + + fd = mkstemp( tmp ); + if( fd < 0 ) + { + snprintf( err, errsz, "%s: %s", tmp, strerror( errno ) ); + goto out; + } + if( close( fd ) ) + { + fd = -1; + snprintf( err, errsz, "%s: %s", tmp, strerror( errno ) ); + goto out; + } + fd = -1; + + if( mcpu_archive_write( tmp, archive, index, err, errsz ) ) + goto out; + if( chmod( tmp, st.st_mode & 07777 ) ) + { + snprintf( err, errsz, "%s: %s", tmp, strerror( errno ) ); + goto out; + } + if( rename( tmp, output ) ) + { + snprintf( err, errsz, "%s: %s", output, strerror( errno ) ); + goto out; + } + rc = 0; + +out: + if( fd >= 0 ) + close( fd ); + if( rc && tmp ) + unlink( tmp ); + free( tmp ); + return( rc ); + +} /* End of commit_archive() */ + + +static int +strip_archive( const char *input, const char *output, + const unsigned char *original, size_t original_size, + char *err, size_t errsz ) +{ + mcpu_archive archive; + size_t i; + int any = 0; + int rc = -1; + + mcpu_archive_init( &archive ); + if( mcpu_archive_read( input, &archive, err, errsz ) ) + return( -1 ); + + for( i = 0; i < archive.member_count; ++i ) + { + unsigned char *p = NULL; + size_t n = 0; + int changed = 0; + + if( mcpu_object_strip_data( archive.members[i].data, + archive.members[i].size, + &p, &n, &changed, err, errsz ) ) + goto out; + if( changed ) + { + free( archive.members[i].data ); + archive.members[i].data = p; + archive.members[i].size = n; + any = 1; + } + else + free( p ); + } + + if( !any ) + rc = commit_bytes( input, output, original, original_size, err, errsz ); + else + rc = commit_archive( input, output, &archive, archive.had_symbol_index, + err, errsz ); + +out: + mcpu_archive_free( &archive ); + return( rc ); + +} /* End of strip_archive() */ + + +int +main( int argc, char **argv ) +{ + const char *prog = argv[0]; + const char *input = NULL; + const char *output = NULL; + unsigned char *data = NULL; + size_t size = 0; + int i; + char err[512]; + int rc = EXIT_FAILURE; + + for( i = 1; i < argc; ++i ) + { + const char *arg = argv[i]; + + if( !strcmp( arg, "--help" ) ) + { + usage( stdout, prog ); + return( EXIT_SUCCESS ); + } + if( !strcmp( arg, "--version" ) || + !strcmp( arg, "--dump-version" ) ) + { + mcpu_version_action version_action; + + mcpu_version_parse_option( arg, &version_action ); + return( mcpu_version_print(prog, version_action) == 0 ? + EXIT_SUCCESS : EXIT_FAILURE ); + } + if( !strcmp( arg, "-o" ) ) + { + if( ++i >= argc || output ) + { + usage( stderr, prog ); + return( EXIT_FAILURE ); + } + output = argv[i]; + continue; + } + if( !strncmp( arg, "--output=", 9 ) ) + { + if( output || !arg[9] ) + { + usage( stderr, prog ); + return( EXIT_FAILURE ); + } + output = arg + 9; + continue; + } + if( arg[0] == '-' && arg[1] ) + { + fprintf( stderr, "%s: unknown option: %s\n", prog, arg ); + usage( stderr, prog ); + return( EXIT_FAILURE ); + } + if( input ) + { + fprintf( stderr, "%s: exactly one input file is required\n", prog ); + usage( stderr, prog ); + return( EXIT_FAILURE ); + } + input = arg; + } + + if( !input ) + { + usage( stderr, prog ); + return( EXIT_FAILURE ); + } + if( !output ) + output = input; + + if( cpu_melf_readall( input, &data, &size, err, sizeof( err ) ) ) + { + fprintf( stderr, "%s: %s\n", prog, err ); + return( EXIT_FAILURE ); + } + + if( size >= 8 && !memcmp( data, "!<arch>\n", 8 ) ) + { + if( strip_archive( input, output, data, size, err, sizeof( err ) ) ) + fprintf( stderr, "%s: %s: %s\n", prog, input, err ); + else + rc = EXIT_SUCCESS; + } + else + { + unsigned char *stripped = NULL; + size_t stripped_size = 0; + int changed = 0; + + if( mcpu_object_strip_data( data, size, &stripped, &stripped_size, + &changed, err, sizeof( err ) ) ) + fprintf( stderr, "%s: %s: %s\n", prog, input, err ); + else if( commit_bytes( input, output, stripped, stripped_size, + err, sizeof( err ) ) ) + fprintf( stderr, "%s: %s\n", prog, err ); + else + rc = EXIT_SUCCESS; + free( stripped ); + (void)changed; + } + + free( data ); + return( rc ); + +} /* End of main() */ diff --git a/utils/mcpu-tool-options.c b/utils/mcpu-tool-options.c new file mode 100644 index 0000000..252c652 --- /dev/null +++ b/utils/mcpu-tool-options.c @@ -0,0 +1,413 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> + +#include "mcpu-tool-options.h" + +#define MCPU_LD_SYSTEM_CONFIG_FILE "/etc/mcpu/mcpu-ld.conf" + +static const char * +program_basename( const char *name ) +{ + const char *p; + + if( name == NULL || *name == 0 ) + return( "mcpu" ); + + p = name + strlen( name ); + while( p != name && p[-1] != '/' ) --p; + return( p ); + +} /* End of program_basename() */ + + +static int +read_optional_config( mcpu_tool_options *options, const char *filename, + int *was_read ) +{ + struct stat st; + + if( was_read != NULL ) + *was_read = 0; + + if( stat(filename, &st) != 0 ) + { + if( errno == ENOENT ) + return( 0 ); + + fprintf( stderr, "%s: cannot stat configuration '%s': %s\n", + options->progname, filename, strerror(errno) ); + return( -1 ); + } + + if( mcpu_config_read(&options->config, filename, 1) != 0 ) + { + fprintf( stderr, "%s: cannot read configuration '%s': %s\n", + options->progname, filename, strerror(errno) ); + return( -1 ); + } + + if( was_read != NULL ) + *was_read = 1; + + return( 0 ); + +} /* End of read_optional_config() */ + +void +mcpu_tool_options_init( mcpu_tool_options *options, const char *progname ) +{ + if( options == NULL ) + return; + + memset( options, 0, sizeof(*options) ); + options->progname = program_basename( progname ); + mcpu_runtime_paths_init( &options->runtime ); + mcpu_config_init( &options->config ); + mcpu_library_search_init( &options->library_search ); + +} /* End of mcpu_tool_options_init() */ + + +void +mcpu_tool_options_free( mcpu_tool_options *options ) +{ + if( options == NULL ) + return; + + free( options->user_config_file ); + options->user_config_file = NULL; + mcpu_runtime_paths_free( &options->runtime ); + mcpu_config_free( &options->config ); + mcpu_library_search_free( &options->library_search ); + +} /* End of mcpu_tool_options_free() */ + + +int +mcpu_tool_parse_common_option( mcpu_tool_options *options, + int argc, char **argv, int *index ) +{ + const char *arg; + + if( options == NULL || argv == NULL || index == NULL || + *index < 0 || *index >= argc ) + { + errno = EINVAL; + return( -1 ); + } + + arg = argv[*index]; + + if( mcpu_version_parse_option(arg, &options->version_action) ) + return( 1 ); + + if( strcmp(arg, "--no-config") == 0 ) + { + options->no_config = 1; + return( 1 ); + } + + if( strcmp(arg, "--dump-config") == 0 ) + { + options->dump_config = 1; + return( 1 ); + } + + if( strcmp(arg, "--print-search-dirs") == 0 ) + { + options->print_search_dirs = 1; + return( 1 ); + } + + if( strncmp(arg, "--sys-root=", 11) == 0 ) + { + if( arg[11] == 0 ) + { + fprintf( stderr, "%s: empty path in '--sys-root='\n", + options->progname ); + return( -1 ); + } + options->sys_root = arg + 11; + options->no_config = 1; + return( 1 ); + } + + if( strcmp(arg, "--sys-root") == 0 ) + { + if( *index + 1 >= argc || argv[*index + 1][0] == 0 ) + { + fprintf( stderr, "%s: argument missing after '--sys-root'\n", + options->progname ); + return( -1 ); + } + ++*index; + options->sys_root = argv[*index]; + options->no_config = 1; + return( 1 ); + } + + return( 0 ); + +} /* End of mcpu_tool_parse_common_option() */ + + +int +mcpu_tool_add_library_directory( mcpu_tool_options *options, + const char *directory ) +{ + if( options == NULL || directory == NULL || *directory == 0 ) + { + errno = EINVAL; + return( -1 ); + } + + return( mcpu_library_search_add_directory(&options->library_search, + directory) ); + +} /* End of mcpu_tool_add_library_directory() */ + + +int +mcpu_tool_add_library_path( mcpu_tool_options *options, const char *path ) +{ + if( options == NULL || path == NULL || *path == 0 ) + { + errno = EINVAL; + return( -1 ); + } + + return( mcpu_library_search_add_list(&options->library_search, path) ); + +} /* End of mcpu_tool_add_library_path() */ + + +int +mcpu_tool_resolve_library( const mcpu_tool_options *options, + const char *name, char **resolved ) +{ + if( options == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + return( mcpu_library_search_resolve(&options->library_search, + options->runtime.system_library_path, name, resolved) ); + +} /* End of mcpu_tool_resolve_library() */ + + +int +mcpu_tool_resolve_library_alternatives( + const mcpu_tool_options *options, + const char *const *names, size_t name_count, + char **resolved, size_t *selected_index ) +{ + if( options == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + return( mcpu_library_search_resolve_alternatives( + &options->library_search, + options->runtime.system_library_path, + names, name_count, resolved, selected_index) ); + +} /* End of mcpu_tool_resolve_library_alternatives() */ + + +int +mcpu_tool_options_prepare( mcpu_tool_options *options, const char *argv0 ) +{ + const char *home; + const char *value; + + if( options == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + options->runtime_config_read = 0; + options->system_config_read = 0; + options->user_config_read = 0; + + if( options->sys_root != NULL ) + { + if( mcpu_runtime_paths_set_root(&options->runtime, + options->sys_root) != 0 ) + { + fprintf( stderr, "%s: cannot use MCPU system root '%s': %s\n", + options->progname, options->sys_root, strerror(errno) ); + return( -1 ); + } + } + else if( mcpu_runtime_paths_discover(&options->runtime, argv0) != 0 ) + { + fprintf( stderr, "%s: cannot determine MCPU installation root: %s\n", + options->progname, strerror(errno) ); + return( -1 ); + } + + if( mcpu_config_set(&options->config, + "MCPU_LD_SYSTEM_LIBRARY_PATH", + options->runtime.system_library_path) != 0 ) + return( -1 ); + + free( options->user_config_file ); + options->user_config_file = NULL; + home = getenv( "HOME" ); + if( home != NULL && *home != 0 ) + { + size_t n = strlen(home) + sizeof("/.mcpu/etc/mcpu-ld.conf"); + + options->user_config_file = (char *)malloc( n ); + if( options->user_config_file == NULL ) + return( -1 ); + snprintf( options->user_config_file, n, + "%s/.mcpu/etc/mcpu-ld.conf", home ); + } + + if( !options->no_config ) + { + if( options->runtime.config_file == NULL || + read_optional_config(options, options->runtime.config_file, + &options->runtime_config_read) != 0 ) + return( -1 ); + + if( read_optional_config(options, MCPU_LD_SYSTEM_CONFIG_FILE, + &options->system_config_read) != 0 ) + return( -1 ); + + if( options->user_config_file != NULL && + read_optional_config(options, options->user_config_file, + &options->user_config_read) != 0 ) + return( -1 ); + } + + value = mcpu_config_get( &options->config, + "MCPU_LD_SYSTEM_LIBRARY_PATH" ); + if( value == NULL || + mcpu_runtime_paths_set_system_library(&options->runtime, value) != 0 ) + return( -1 ); + + return( 0 ); + +} /* End of mcpu_tool_options_prepare() */ + + +int +mcpu_tool_has_information_action( const mcpu_tool_options *options ) +{ + if( options == NULL ) + return( 0 ); + + return( options->dump_config || options->print_search_dirs || + options->version_action != MCPU_VERSION_NONE ); + +} /* End of mcpu_tool_has_information_action() */ + + +static int +print_config_file_state( FILE *stream, const char *path, int was_read ) +{ + if( path == NULL ) + return( 0 ); + + if( fprintf(stream, " config: %s [%s]\n", + path, was_read ? "read" : "not found") < 0 ) + return( -1 ); + + return( 0 ); + +} /* End of print_config_file_state() */ + + +int +mcpu_tool_dump_config( const mcpu_tool_options *options, FILE *stream ) +{ + const char *library_path; + + if( options == NULL || stream == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + library_path = options->runtime.system_library_path; + if( library_path == NULL ) + library_path = ""; + + if( fprintf(stream, "MCPU configuration:\n") < 0 || + fprintf(stream, " runtime root: %s\n", + options->runtime.installation_root ? + options->runtime.installation_root : "") < 0 ) + return( -1 ); + + if( options->sys_root != NULL ) + { + if( fprintf(stream, " configuration: disabled by --sys-root\n") < 0 ) + return( -1 ); + } + else if( options->no_config ) + { + if( fprintf(stream, " configuration: disabled by --no-config\n") < 0 ) + return( -1 ); + } + else + { + if( fprintf(stream, " configuration: enabled\n") < 0 || + print_config_file_state(stream, options->runtime.config_file, + options->runtime_config_read) != 0 || + print_config_file_state(stream, MCPU_LD_SYSTEM_CONFIG_FILE, + options->system_config_read) != 0 || + print_config_file_state(stream, options->user_config_file, + options->user_config_read) != 0 ) + return( -1 ); + } + + if( fprintf(stream, "MCPU_LD_SYSTEM_LIBRARY_PATH = %s;\n", + library_path) < 0 ) + return( -1 ); + + return( ferror(stream) ? -1 : 0 ); + +} /* End of mcpu_tool_dump_config() */ + + +int +mcpu_tool_print_search_dirs( const mcpu_tool_options *options, FILE *stream ) +{ + const char *path; + size_t i; + + if( options == NULL || stream == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + path = options->runtime.system_library_path; + if( fprintf(stream, "libraries:\n") < 0 ) + return( -1 ); + + for( i = 0; i < options->library_search.count; ++i ) + if( fprintf(stream, " %s\n", + options->library_search.directory[i]) < 0 ) + return( -1 ); + + if( path != NULL && *path != 0 && + fprintf(stream, " %s\n", path) < 0 ) + return( -1 ); + + return( ferror(stream) ? -1 : 0 ); + +} /* End of mcpu_tool_print_search_dirs() */ diff --git a/utils/mcpu-tool-options.h b/utils/mcpu-tool-options.h new file mode 100644 index 0000000..f6fe356 --- /dev/null +++ b/utils/mcpu-tool-options.h @@ -0,0 +1,52 @@ +#ifndef __MCPU_TOOL_OPTIONS_H__ +#define __MCPU_TOOL_OPTIONS_H__ 1 + +#include <stdio.h> + +#include "mcpu-config.h" +#include "mcpu-version.h" +#include "mcpu-library-search.h" +#include "mcpu-runtime.h" + +typedef struct mcpu_tool_options mcpu_tool_options; +struct mcpu_tool_options +{ + const char *progname; + const char *sys_root; + int help; + int no_config; + int dump_config; + int print_search_dirs; + mcpu_version_action version_action; + char *user_config_file; + int runtime_config_read; + int system_config_read; + int user_config_read; + mcpu_runtime_paths runtime; + mcpu_config config; + mcpu_library_search library_search; +}; + +void mcpu_tool_options_init( mcpu_tool_options *options, + const char *progname ); +void mcpu_tool_options_free( mcpu_tool_options *options ); +int mcpu_tool_options_prepare( mcpu_tool_options *options, + const char *argv0 ); +int mcpu_tool_parse_common_option( mcpu_tool_options *options, + int argc, char **argv, int *index ); +int mcpu_tool_add_library_directory( mcpu_tool_options *options, + const char *directory ); +int mcpu_tool_add_library_path( mcpu_tool_options *options, + const char *path ); +int mcpu_tool_resolve_library( const mcpu_tool_options *options, + const char *name, char **resolved ); +int mcpu_tool_resolve_library_alternatives( + const mcpu_tool_options *options, + const char *const *names, size_t name_count, + char **resolved, size_t *selected_index ); +int mcpu_tool_has_information_action( const mcpu_tool_options *options ); +int mcpu_tool_dump_config( const mcpu_tool_options *options, FILE *stream ); +int mcpu_tool_print_search_dirs( const mcpu_tool_options *options, + FILE *stream ); + +#endif /* __MCPU_TOOL_OPTIONS_H__ */ diff --git a/utils/mcpu-version.c b/utils/mcpu-version.c new file mode 100644 index 0000000..74e0a83 --- /dev/null +++ b/utils/mcpu-version.c @@ -0,0 +1,67 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdio.h> +#include <string.h> + +#include "mcpu-version.h" + +static const char * +program_basename( const char *name ) +{ + const char *p; + + if( name == NULL || *name == 0 ) + return( "mcpu" ); + + p = name + strlen( name ); + while( p != name && p[-1] != '/' ) --p; + return( p ); + +} /* End of program_basename() */ + + +int +mcpu_version_parse_option( const char *option, mcpu_version_action *action ) +{ + if( option == NULL || action == NULL ) + return( 0 ); + + if( !strcmp( option, "--version" ) ) + { + *action = MCPU_VERSION_FULL; + return( 1 ); + } + + if( !strcmp( option, "--dump-version" ) ) + { + *action = MCPU_VERSION_DUMP; + return( 1 ); + } + + return( 0 ); + +} /* End of mcpu_version_parse_option() */ + + +int +mcpu_version_print( const char *progname, mcpu_version_action action ) +{ + int rc; + + if( action == MCPU_VERSION_FULL ) + rc = printf( "%s (MCPU) %s\n", program_basename( progname ), + PACKAGE_VERSION ); + else if( action == MCPU_VERSION_DUMP ) + rc = printf( "%s\n", PACKAGE_VERSION ); + else + { + errno = EINVAL; + return( -1 ); + } + + return( rc < 0 ? -1 : 0 ); + +} /* End of mcpu_version_print() */ diff --git a/utils/mcpu-version.h b/utils/mcpu-version.h new file mode 100644 index 0000000..5eee32f --- /dev/null +++ b/utils/mcpu-version.h @@ -0,0 +1,15 @@ +#ifndef __MCPU_VERSION_H__ +#define __MCPU_VERSION_H__ 1 + +typedef enum mcpu_version_action +{ + MCPU_VERSION_NONE = 0, + MCPU_VERSION_FULL, + MCPU_VERSION_DUMP +} mcpu_version_action; + +int mcpu_version_parse_option( const char *option, + mcpu_version_action *action ); +int mcpu_version_print( const char *progname, mcpu_version_action action ); + +#endif /* __MCPU_VERSION_H__ */ |
