summaryrefslogtreecommitdiff
path: root/utils/mcpu-dependency.c
diff options
context:
space:
mode:
Diffstat (limited to 'utils/mcpu-dependency.c')
-rw-r--r--utils/mcpu-dependency.c487
1 files changed, 487 insertions, 0 deletions
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() */