summaryrefslogtreecommitdiff
path: root/utils
diff options
context:
space:
mode:
Diffstat (limited to 'utils')
-rw-r--r--utils/Makefile.am72
-rw-r--r--utils/mcpu-ar.c542
-rw-r--r--utils/mcpu-archive.c1025
-rw-r--r--utils/mcpu-archive.h41
-rw-r--r--utils/mcpu-as.c612
-rw-r--r--utils/mcpu-asm.c4012
-rw-r--r--utils/mcpu-asm.h205
-rw-r--r--utils/mcpu-config.c704
-rw-r--r--utils/mcpu-config.h30
-rw-r--r--utils/mcpu-dependency.c487
-rw-r--r--utils/mcpu-dependency.h15
-rw-r--r--utils/mcpu-dyn.c2242
-rw-r--r--utils/mcpu-dyn.h66
-rw-r--r--utils/mcpu-exec.c1483
-rw-r--r--utils/mcpu-exec.h42
-rw-r--r--utils/mcpu-ld-options.c463
-rw-r--r--utils/mcpu-ld-options.h36
-rw-r--r--utils/mcpu-ld.c1038
-rw-r--r--utils/mcpu-library-search.c307
-rw-r--r--utils/mcpu-library-search.h29
-rw-r--r--utils/mcpu-link.c1647
-rw-r--r--utils/mcpu-link.h18
-rw-r--r--utils/mcpu-melf.c435
-rw-r--r--utils/mcpu-melf.h42
-rw-r--r--utils/mcpu-nm.c716
-rw-r--r--utils/mcpu-objcopy.c596
-rw-r--r--utils/mcpu-objdump.c1747
-rw-r--r--utils/mcpu-object-edit.c1195
-rw-r--r--utils/mcpu-object-edit.h49
-rw-r--r--utils/mcpu-object.c1489
-rw-r--r--utils/mcpu-object.h12
-rw-r--r--utils/mcpu-readelf.c1215
-rw-r--r--utils/mcpu-run-options.c1169
-rw-r--r--utils/mcpu-run-options.h105
-rw-r--r--utils/mcpu-run.c870
-rw-r--r--utils/mcpu-runtime.c366
-rw-r--r--utils/mcpu-runtime.h23
-rw-r--r--utils/mcpu-strip.c332
-rw-r--r--utils/mcpu-tool-options.c413
-rw-r--r--utils/mcpu-tool-options.h52
-rw-r--r--utils/mcpu-version.c67
-rw-r--r--utils/mcpu-version.h15
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(&sections[si], a) )
+ {
+ snprintf( err, es, "cannot align relocatable output section" );
+ goto done;
+ }
+ bases[oi * LSEC_COUNT + si] = sections[si].n;
+ if( put(&sections[si], p, n) )
+ {
+ snprintf( err, es, "out of memory" );
+ goto done;
+ }
+ }
+
+ /* .bss is merged as NOBITS and therefore advances only its logical size. */
+ for( oi = 0; oi < nobjs; ++oi )
+ {
+ __mpu_uint64_t a = objs[oi].bss_align ? objs[oi].bss_align : 1;
+ __mpu_uint64_t aligned;
+ int have = objs[oi].has_bss || objs[oi].bss_size;
+
+ if( !have )
+ continue;
+ if( !cpu_melf_is_pow2(a) || align_u64(bss_size, a, &aligned) )
+ {
+ snprintf( err, es, "cannot align relocatable .bss contribution" );
+ goto done;
+ }
+ bss_size = aligned;
+ present[LSEC_BSS] = 1;
+ if( a > aligns[LSEC_BSS] )
+ aligns[LSEC_BSS] = a;
+ bases[oi * LSEC_COUNT + LSEC_BSS] = bss_size;
+ if( (__mpu_uint64_t)objs[oi].bss_size > UINT64_MAX - bss_size )
+ {
+ snprintf( err, es, "relocatable .bss layout overflow" );
+ goto done;
+ }
+ bss_size += (__mpu_uint64_t)objs[oi].bss_size;
+ }
+
+ if( bss_size > SIZE_MAX )
+ {
+ snprintf( err, es, "relocatable .bss exceeds supported host size" );
+ goto done;
+ }
+
+ /* Build one canonical non-local symbol per name. Defined GLOBAL beats
+ WEAK, a definition beats an undefined reference, and LOCAL symbols
+ remain private even when different input objects use the same name. */
+ for( oi = 0; oi < nobjs; ++oi )
+ for( si = 0; si < objs[oi].symbol_count; ++si )
+ {
+ const cpu_asm_symbol *s = &objs[oi].symbols[si];
+ unsigned section = s->section;
+ __mpu_uint64_t value = s->value;
+ size_t mi;
+
+ if( section != CPU_OBJ_SEC_UNDEF )
+ {
+ int ix = sidx( section );
+ if( ix < 0 || value > UINT64_MAX - bases[oi * LSEC_COUNT + (size_t)ix] )
+ {
+ snprintf( err, es, "bad relocatable symbol placement: %s", s->name );
+ goto done;
+ }
+ value += bases[oi * LSEC_COUNT + (size_t)ix];
+ }
+
+ if( s->binding == CPU_OBJ_BIND_LOCAL )
+ {
+ if( reloc_append_symbol(out, s, section, value, &mi, err, es) )
+ goto done;
+ }
+ else if( reloc_merge_nonlocal(out, s, section, value,
+ &mi, err, es) )
+ goto done;
+
+ symmap[symbase[oi] + si] = mi;
+ }
+
+ if( total_relocs != 0 )
+ {
+ out->relocs = (cpu_asm_reloc *)calloc( total_relocs,
+ sizeof(*out->relocs) );
+ if( out->relocs == NULL )
+ {
+ snprintf( err, es, "out of memory" );
+ goto done;
+ }
+ }
+
+ for( oi = 0; oi < nobjs; ++oi )
+ for( ri = 0; ri < objs[oi].reloc_count; ++ri )
+ {
+ const cpu_asm_reloc *r = &objs[oi].relocs[ri];
+ cpu_asm_reloc *d;
+ int ix = sidx( r->section );
+
+ if( r->symbol_index >= objs[oi].symbol_count || ix < 0 )
+ {
+ snprintf( err, es, "bad relocatable relocation" );
+ goto done;
+ }
+ if( r->offset > UINT64_MAX - bases[oi * LSEC_COUNT + (size_t)ix] )
+ {
+ snprintf( err, es, "relocatable relocation offset overflow" );
+ goto done;
+ }
+
+ d = &out->relocs[out->reloc_count++];
+ d->section = r->section;
+ d->type = r->type;
+ d->symbol_index = symmap[symbase[oi] + r->symbol_index];
+ d->offset = bases[oi * LSEC_COUNT + (size_t)ix] + r->offset;
+ d->addend = r->addend;
+ }
+
+ for( si = LSEC_INIT; si <= LSEC_TRACE; ++si )
+ if( reloc_take_section(out, (int)si, &sections[si], aligns[si],
+ present[si], err, es) )
+ goto done;
+
+ if( present[LSEC_BSS] )
+ {
+ out->has_bss = 1;
+ out->bss_size = (size_t)bss_size;
+ out->bss_align = aligns[LSEC_BSS];
+ }
+
+ rc = 0;
+
+done:
+ for( si = 0; si < LSEC_COUNT; ++si )
+ free( sections[si].p );
+ free( bases );
+ free( symbase );
+ free( symmap );
+ if( rc != 0 )
+ cpu_asm_object_free( out );
+ return( rc );
+
+} /* End of cpu_link_relocatable() */
+
+
+int
+cpu_link_objects( cpu_asm_object *objs, size_t nobjs, cpu_asm_image *im,
+ char *err, size_t es)
+{
+ return( cpu_link_objects_mode( objs, nobjs, im, CPU_LINK_STATIC_EXEC, err, es ) );
+}
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, &section_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 = &section->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, &sections[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( &sections[count], 0, sizeof( sections[count] ) );
+ s = &sections[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, &sections[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( &sections, &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, &sections[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, &sections[index] ) )
+ {
+ snprintf( err, errsz, "--update-section cannot update section %s",
+ a->name );
+ return( -1 );
+ }
+ if( replace_section_data( &sections[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 = &sections[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, &sections, &count, err, errsz ) )
+ goto out;
+
+ if( apply_options( &file, &sections, &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, &sections, &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__ */