diff --git a/utils/capture-libs.c b/utils/capture-libs.c
index 15660340339693262e0a81c7a243371b38ebb831..d9423fd3a997d3335a486e9afe3c68bb597e374f 100644
--- a/utils/capture-libs.c
+++ b/utils/capture-libs.c
@@ -96,6 +96,24 @@ enum
   OPTION_VERSION,
 };
 
+/*
+ * string_set_diff_flags:
+ * @STRING_SET_DIFF_ONLY_IN_FIRST: At least one element is in the first set but not the second
+ * @STRING_SET_DIFF_ONLY_IN_SECOND: At least one element is in the second set but not the first
+ * @STRING_SET_DIFF_NONE: All elements are equal
+ *
+ * The result of comparing two sets of strings. If each set
+ * contains elements that the other does not, then
+ * both @STRING_SET_DIFF_ONLY_IN_FIRST
+ * and @STRING_SET_DIFF_ONLY_IN_SECOND will be set.
+ */
+typedef enum
+{
+  STRING_SET_DIFF_ONLY_IN_FIRST = (1 << 0),
+  STRING_SET_DIFF_ONLY_IN_SECOND = (1 << 1),
+  STRING_SET_DIFF_NONE = 0
+} string_set_diff_flags;
+
 static const char * const *arg_patterns = NULL;
 static const char *option_container = "/";
 static const char *option_dest = ".";
@@ -314,6 +332,558 @@ static bool capture_patterns( const char * const *patterns,
                               capture_flags flags,
                               int *code, char **message );
 
+/* Exclude empty symbols */
+static int
+symbol_excluded( const char *name )
+{
+    if( name == NULL ||
+        !strcmp(name, "") )
+        return 1;
+
+    return 0;
+}
+
+static int
+bsearch_strcmp_cb( const void *n, const void *ip )
+{
+    const char *needle = n;
+    const char * const *item_p = ip;
+    return strcmp( needle, *item_p );
+}
+
+static int
+qsort_strcmp_cb( const void* s1, const void* s2 )
+{
+    const char * const *a = (const char* const*) s1;
+    const char* const *b = (const char* const*) s2;
+    return strcmp( *a, *b );
+}
+
+/*
+ * parse_map_symbols:
+ * @base: (type filename): A `link_map` base address
+ * @dyn: (type filename): The dynamic section of the shared object
+ * @symbols_number: (out) (not optional): The number of symbols found
+ * @code: (out) (optional): Used to return an error code on
+ *  failure
+ * @message: (out) (optional) (nullable): Used to return an error message
+ *  on failure
+ *
+ * Returns: (transfer container): The list of symbols that the
+ *  shared object has, on failure %NULL.
+ */
+static const char **
+parse_map_symbols( ElfW(Addr) base,
+                   ElfW(Dyn) *dyn,
+                   size_t* symbols_number,
+                   int *code, char **message )
+{
+    size_t x = 0;
+    const char *strtab = NULL;
+    ElfW(Sym) *symtab = NULL;
+    const ElfW(Dyn) *entry = NULL;
+    const ElfW(Sym) *entry_sym = NULL;
+    /* The array is calloc'd, but the members point into the string table */
+    const char **symbols = NULL;
+
+    assert( symbols_number != NULL );
+
+    strtab = dynamic_section_find_strtab( dyn, (const void *) base, NULL );
+
+    if ( strtab == NULL )
+    {
+        _capsule_set_error( code, message, EINVAL,
+                            "String table is unexpectedly missing or inaccessible" );
+        return NULL;
+    }
+
+    for( entry = dyn; entry->d_tag != DT_NULL; entry++ )
+    {
+        switch( entry->d_tag )
+        {
+          case DT_SYMTAB:
+            symtab = (ElfW(Sym) *) entry->d_un.d_ptr;
+            break;
+
+          default:
+            break;
+        }
+    }
+
+    if ( symtab == NULL )
+    {
+        _capsule_set_error( code, message, EINVAL,
+                            "DT_SYMTAB is unexpectedly missing or inaccessible" );
+        return NULL;
+    }
+
+    x = 0;
+    /* We perform two times the for cycle in order to get the number of items.
+     * Skip all the excluded symbols. */
+    for( entry_sym = symtab;
+         ( (ELFW_ST_TYPE(entry_sym->st_info) < STT_NUM) &&
+           (ELFW_ST_BIND(entry_sym->st_info) < STB_NUM) );
+         entry_sym++ )
+    {
+        if ( !symbol_excluded( strtab + entry_sym->st_name ) )
+            x++;
+    }
+
+    *symbols_number = x;
+    symbols = calloc( *symbols_number + 1, sizeof(char *) );
+
+    x = 0;
+    for( entry_sym = symtab;
+         ( (ELFW_ST_TYPE(entry_sym->st_info) < STT_NUM) &&
+           (ELFW_ST_BIND(entry_sym->st_info) < STB_NUM) );
+         entry_sym++ )
+    {
+        if ( !symbol_excluded( strtab + entry_sym->st_name ) )
+        {
+            symbols[x] = strtab + entry_sym->st_name;
+            x++;
+        }
+    }
+
+    symbols[x] = NULL;
+    qsort( symbols, *symbols_number, sizeof(char *), qsort_strcmp_cb );
+    return symbols;
+}
+
+/*
+ * parse_map_versions:
+ * @base: (type filename): A `link_map` base address
+ * @dyn: (type filename): The dynamic section of the shared object
+ * @versions_number: (out) (optional): The number of versions found
+ * @code: (out) (optional): Used to return an error code on
+ *  failure
+ * @message: (out) (optional) (nullable): Used to return an error message
+ *  on failure
+ *
+ * Returns: (transfer container): The list of versions that the
+ *  shared object has, on failure %NULL.
+ */
+static const char **
+parse_map_versions( ElfW(Addr) base, ElfW(Dyn) *dyn,
+                    size_t* versions_number,
+                    int *code, char **message )
+{
+    size_t verdefnum = (size_t) -1;
+    void *start = NULL;
+    const ElfW(Verdef) *verdef = NULL;
+    const char *strtab = NULL;
+    const char *version = NULL;
+    ElfW(Verdaux) *aux = NULL;
+    const char *vd = NULL;
+    const char **versions = NULL;
+
+    start  = (void *) ((ElfW(Addr)) dyn - base );
+    strtab = dynamic_section_find_strtab( dyn, (const void *) base, NULL );
+    verdefnum = find_value( base, start, DT_VERDEFNUM );
+    verdef = (const ElfW(Verdef) *) find_ptr( base, start, DT_VERDEF );
+
+    /* The library doesn't have versions */
+    if( verdefnum == (size_t) -1 && verdef == NULL )
+    {
+        versions = malloc( sizeof(char *) );
+        versions[0] = NULL;
+        if( versions_number != NULL)
+            *versions_number = 0;
+
+        return versions;
+    }
+
+    if( verdefnum == (size_t) -1 || verdef == NULL )
+    {
+        _capsule_set_error( code, message, EINVAL,
+                            "Found one of DT_VERDEF or DT_VERDEFNUM, but not the other" );
+        return NULL;
+    }
+
+    vd = (const char *) verdef;
+
+    versions = calloc( verdefnum + 1, sizeof(char *) );
+
+    for( size_t x = 0; x < verdefnum; x++ )
+    {
+        ElfW(Verdef) *the_entry = (ElfW(Verdef) *) vd;
+        aux = (ElfW(Verdaux) *) ( vd + the_entry->vd_aux );
+        version = strtab + aux->vda_name;
+        versions[x] = version;
+        vd = vd + the_entry->vd_next;
+    }
+
+    versions[verdefnum] = NULL;
+    qsort( versions, verdefnum, sizeof(char *), qsort_strcmp_cb );
+    if( versions_number != NULL)
+        *versions_number = verdefnum;
+
+    return versions;
+}
+
+/*
+ * get_link_map:
+ * @soname: (type filename): The library name to load
+ * @path: (type filename): If not %NULL, load libraries as though
+ *  this base path was the root directory
+ * @ns: (inout) (not optional): The namespace that will be used
+ *  to search for @soname
+ * @code: (out) (optional): Used to return an error code on
+ *  failure
+ * @message: (out) (optional) (nullable): Used to return an error message
+ *  on failure
+ *
+ * Returns: A link_map of the loaded shared library @soname,
+ *  on failure %NULL.
+ */
+static struct link_map *
+get_link_map ( const char *soname, const char *path,
+               Lmid_t *ns, int *code, char **message )
+{
+    const char *libname;
+    ld_libs ldlibs = {};
+    void *handle;
+    int dlcode = 0;
+    struct link_map *map = NULL;
+
+    assert( ns != NULL );
+
+    if( !ld_libs_init( &ldlibs, NULL, path, 0, code, message ) )
+        return NULL;
+
+    if( !ld_libs_set_target( &ldlibs, soname, code, message ) )
+        return NULL;
+
+    if( ( handle = ld_libs_load( &ldlibs, ns, 0, code, message ) ) )
+    {
+        if( (libname = strrchr( soname, '/' ) ) )
+            libname = libname + 1;
+        else
+            libname = soname;
+
+        // dl_iterate_phdr won't work with private dlmopen namespaces:
+        if( ( dlcode = dlinfo( handle, RTLD_DI_LINKMAP, &map ) ) )
+        {
+            _capsule_set_error( code, message, EINVAL,
+                                "cannot access symbols for %s via handle %p [%d]: %s",
+                                libname, handle, dlcode, dlerror() );
+            return NULL;
+        }
+    }
+    else
+    {
+        return NULL;
+    }
+    return map;
+}
+
+/*
+ * get_symbols:
+ * @soname: (type filename): The library name to load
+ * @path: (type filename): If not %NULL, load libraries as though
+ *  this base path was the root directory
+ * @symbols_number: (out) (not optional): The number of symbols found
+ * @ns: (inout) (not optional): The namespace that will be used
+ *  to search for @soname
+ * @code: (out) (optional): Used to return an error code on
+ *  failure
+ * @message: (out) (optional) (nullable): Used to return an error message
+ *  on failure
+ *
+ * Returns: (transfer container): The list of symbols that the
+ *  shared object has, on failure %NULL.
+ */
+static const char **
+get_symbols ( const char *soname, const char *path, size_t *symbols_number,
+              Lmid_t *ns, int *code, char **message )
+{
+    struct link_map *map;
+    const char **symbols = NULL;
+
+    map = get_link_map( soname, path, ns, code, message );
+
+    // We expect to have a pointer directly to the library we are interested in.
+    if( map != NULL )
+        symbols = parse_map_symbols( map->l_addr, map->l_ld, symbols_number,
+                                     code, message );
+
+    return symbols;
+}
+
+/*
+ * get_versions:
+ * @soname: (type filename): The library name to load
+ * @path: (type filename): If not %NULL, load libraries as though
+ *  this base path was the root directory
+ * @versions_number: (out) (optional): The number of versions found
+ * @ns: (inout) (not optional): The namespace that will be used
+ *  to search for @soname
+ * @code: (out) (optional): Used to return an error code on
+ *  failure
+ * @message: (out) (optional) (nullable): Used to return an error message
+ *  on failure
+ *
+ * Returns: (transfer container): The list of versions that the
+ *  shared object has, on failure %NULL.
+ */
+static const char **
+get_versions ( const char *soname, const char *path, size_t* versions_number,
+               Lmid_t *ns, int *code, char **message )
+{
+    struct link_map *map;
+    const char **versions = NULL;
+
+    map = get_link_map( soname, path, ns, code, message );
+
+    // We expect to have a pointer directly to the library we are interested in.
+    if( map != NULL )
+        versions = parse_map_versions( map->l_addr, map->l_ld, versions_number,
+                                       code, message );
+
+    return versions;
+}
+
+/*
+ * compare_string_sets:
+ * @first: the first set to compare
+ * @first_length: number of elements in the first set
+ * @second: the second set to compare
+ * @second_length: number of elements in the second set
+ *
+ * The two sets needs to be ordered because we will use a binary search to do
+ * the comparison.
+ */
+static string_set_diff_flags
+compare_string_sets ( const char **first, size_t first_length,
+                      const char **second, size_t second_length )
+{
+    string_set_diff_flags result = STRING_SET_DIFF_NONE;
+
+    assert( first != NULL );
+    assert( second != NULL );
+
+    if( first_length > second_length )
+    {
+        result |= STRING_SET_DIFF_ONLY_IN_FIRST;
+    }
+    else
+    {
+        for( size_t i = 0; i < first_length; i++ )
+        {
+            char *found = bsearch( first[i], second, second_length, sizeof(char *), bsearch_strcmp_cb );
+            if( found == NULL )
+            {
+                result |= STRING_SET_DIFF_ONLY_IN_FIRST;
+                break;
+            }
+        }
+    }
+
+    if( first_length < second_length )
+    {
+        result |= STRING_SET_DIFF_ONLY_IN_SECOND;
+    }
+    else
+    {
+        for( size_t i = 0; i < second_length; i++ )
+        {
+            char *found = bsearch( second[i], first, first_length, sizeof(char *), bsearch_strcmp_cb );
+            if( found == NULL )
+            {
+                result |= STRING_SET_DIFF_ONLY_IN_SECOND;
+                break;
+            }
+        }
+    }
+    return result;
+}
+
+/*
+ * library_cmp_by_symbols:
+ * @soname: The library we are interested in
+ * @container_namespace: (inout): The namespace that will be used
+ *  to search @soname in the container
+ * @provider_namespace: (inout): The namespace that will be used
+ *  to search @soname in the provider
+ *
+ * Attempt to determine whether @soname is older, newer or
+ * the same in the container or the provider inspecting their
+ * symbols.
+ *
+ * Return a strcmp-style result: negative if container < provider,
+ * positive if container > provider, zero if container == provider
+ * or if container and provider are non-comparable.
+ */
+static int
+library_cmp_by_symbols( const char *soname, Lmid_t *container_namespace,
+                        Lmid_t *provider_namespace )
+{
+    const char **container_symbols = NULL;
+    const char **provider_symbols = NULL;
+    size_t container_symbols_number = 0;
+    size_t provider_symbols_number = 0;
+    string_set_diff_flags symbol_result = STRING_SET_DIFF_NONE;
+    int cmp_result = 0;
+    int code = 0;
+    char *message = NULL;
+
+    container_symbols = get_symbols( soname, option_container,
+                                     &container_symbols_number,
+                                     container_namespace, &code, &message );
+
+    if( container_symbols == NULL )
+    {
+        warnx( "failed to get container symbols for %s (%d): %s",
+               soname, code, message );
+        goto out;
+    }
+
+    provider_symbols = get_symbols( soname, option_provider,
+                                    &provider_symbols_number,
+                                    provider_namespace, &code, &message );
+
+    if( provider_symbols == NULL )
+    {
+        warnx( "failed to get provider symbols for %s (%d): %s",
+               soname, code, message );
+        goto out;
+    }
+
+    symbol_result = compare_string_sets( container_symbols, container_symbols_number,
+                                         provider_symbols, provider_symbols_number );
+
+    /* In container we have strictly more symbols: don't symlink the one
+     * from the provider */
+    if( symbol_result == STRING_SET_DIFF_ONLY_IN_FIRST )
+    {
+        DEBUG( DEBUG_TOOL,
+               "%s in the container is newer because its symbols are a strict superset",
+               soname );
+        cmp_result = 1;
+    }
+    /* In provider we have strictly more symbols: create the symlink */
+    else if( symbol_result == STRING_SET_DIFF_ONLY_IN_SECOND )
+    {
+        DEBUG( DEBUG_TOOL,
+               "%s in the provider is newer because its symbols are a strict superset",
+               soname );
+        cmp_result = -1;
+    }
+    /* With the following two cases we are still unsure which library is newer, so we
+     * will choose the provider */
+    else if( symbol_result == STRING_SET_DIFF_NONE )
+    {
+        DEBUG( DEBUG_TOOL,
+               "%s in the container and the provider have the same symbols",
+               soname );
+    }
+    else
+    {
+        DEBUG( DEBUG_TOOL,
+               "%s in the container and the provider have different symbols and neither is a superset of the other",
+               soname );
+    }
+
+    out:
+        _capsule_clear( &message );
+        free( container_symbols );
+        free( provider_symbols );
+        return cmp_result;
+}
+
+/*
+ * library_cmp_by_versions:
+ * @soname: The library we are interested in
+ * @container_namespace: (inout): The namespace that will be used
+ *  to search @soname in the container
+ * @provider_namespace: (inout): The namespace that will be used
+ *  to search @soname in the provider
+ *
+ * Attempt to determine whether @soname is older, newer or
+ * the same in the container or the provider inspecting their
+ * symbols versions.
+ *
+ * Return a strcmp-style result: negative if container < provider,
+ * positive if container > provider, zero if container == provider
+ * or if container and provider are non-comparable.
+ */
+static int
+library_cmp_by_versions( const char *soname, Lmid_t *container_namespace,
+                         Lmid_t *provider_namespace )
+{
+    const char **container_versions = NULL;
+    const char **provider_versions = NULL;
+    size_t container_versions_number = 0;
+    size_t provider_versions_number = 0;
+    string_set_diff_flags version_result = STRING_SET_DIFF_NONE;
+    int cmp_result = 0;
+    int code = 0;
+    char *message = NULL;
+
+    container_versions = get_versions( soname, option_container,
+                                       &container_versions_number,
+                                       container_namespace, &code, &message );
+
+    if( container_versions == NULL )
+    {
+        warnx( "failed to get container versions for %s (%d): %s",
+               soname, code, message );
+        goto out;
+    }
+
+    provider_versions = get_versions( soname, option_provider,
+                                      &provider_versions_number,
+                                      provider_namespace, &code, &message );
+
+    if( provider_versions == NULL )
+    {
+        warnx( "failed to get provider versions for %s (%d): %s",
+               soname, code, message );
+        goto out;
+    }
+
+    version_result = compare_string_sets( container_versions, container_versions_number,
+                                          provider_versions, provider_versions_number );
+
+    /* Version in container is strictly newer: don't symlink the one
+     * from the provider */
+    if( version_result == STRING_SET_DIFF_ONLY_IN_FIRST )
+    {
+        DEBUG( DEBUG_TOOL,
+               "%s in the container is newer because its version definitions are a strict superset",
+               soname );
+        cmp_result = 1;
+    }
+    /* Version in the provider is strictly newer: create the symlink */
+    else if( version_result == STRING_SET_DIFF_ONLY_IN_SECOND )
+    {
+        DEBUG( DEBUG_TOOL,
+               "%s in the provider is newer because its version definitions are a strict superset",
+               soname );
+        cmp_result = -1;
+    }
+    /* With the following two cases we are still unsure which library is newer */
+    else if( version_result == STRING_SET_DIFF_NONE )
+    {
+        DEBUG( DEBUG_TOOL,
+               "%s in the container and the provider have the same symbol versions",
+               soname );
+    }
+    else
+    {
+        DEBUG( DEBUG_TOOL,
+               "%s in the container and the provider have different symbol versions and neither is a superset of the other",
+               soname );
+    }
+
+    out:
+        code = 0;
+        _capsule_clear( &message );
+        free( container_versions );
+        free( provider_versions );
+        return cmp_result;
+}
+
 /*
  * library_cmp_by_name:
  * @soname: The library we are interested in, used in debug logging
@@ -402,6 +972,8 @@ capture_one( const char *soname, capture_flags flags,
     _capsule_cleanup(ld_libs_finish) ld_libs provider = {};
     int local_code = 0;
     _capsule_autofree char *local_message = NULL;
+    Lmid_t container_namespace = LM_ID_NEWLM;
+    Lmid_t provider_namespace = LM_ID_NEWLM;
 
     if( !init_with_target( &provider, option_provider, soname,
                            &local_code, &local_message ) )
@@ -460,31 +1032,29 @@ capture_one( const char *soname, capture_flags flags,
 
         needed_basename = my_basename( needed_name );
 
-        if( !option_glibc )
+        size_t j;
+        bool libc_lib = false;
+        for( j = 0; j < N_ELEMENTS( libc_patterns ); j++ )
         {
-            unsigned int j;
-            bool capture = true;
-
-            for( j = 0; j < N_ELEMENTS( libc_patterns ); j++ )
-            {
-                if( libc_patterns[j] == NULL )
-                    break;
+            if( libc_patterns[j] == NULL )
+                break;
 
-                assert( strstarts( libc_patterns[j], "soname:" ) );
+            assert( strstarts( libc_patterns[j], "soname:" ) );
 
-                if( strcmp( libc_patterns[j] + strlen( "soname:" ),
-                            needed_basename ) == 0 )
-                {
-                    DEBUG( DEBUG_TOOL,
-                           "Not capturing \"%s\" because it is part of glibc",
-                           needed_name );
-                    capture = false;
-                    break;
-                }
+            if( strcmp( libc_patterns[j] + strlen( "soname:" ),
+                        needed_basename ) == 0 )
+            {
+                libc_lib = true;
+                break;
             }
+        }
 
-            if( !capture )
-                continue;
+        if( !option_glibc && libc_lib )
+        {
+            DEBUG( DEBUG_TOOL,
+                   "Not capturing \"%s\" because it is part of glibc",
+                   needed_name );
+            continue;
         }
 
         if( fstatat( dest_fd, needed_basename, &statbuf,
@@ -523,33 +1093,39 @@ capture_one( const char *soname, capture_flags flags,
                                   &local_code, &local_message ) )
             {
                 const char *needed_path_in_container = container.needed[0].path;
+                int decision = 0;
 
-                /* TODO: Ideally we would actually dlopen the libraries and
-                 * inspect their symbol tables, either in ambiguous cases
-                 * or always - but that's easier said than done, because
-                 * private symbols exist. For now we just use
-                 * library_cmp_by_name(). */
-
-                /* If equal, we prefer the provider over the container */
-                if( library_cmp_by_name( needed_name,
-                                         needed_path_in_container,
-                                         option_container,
-                                         needed_path_in_provider,
-                                         option_provider) > 0 )
+                /* Compare the version definitions.
+                 * We skip all libc related libraries to avoid problems with dlmopen() */
+                if( !libc_lib )
                 {
-                    /* Version in container is strictly newer: don't
-                     * symlink in the one from the provider */
-                    DEBUG( DEBUG_TOOL,
-                           "%s is strictly newer in the container",
-                           needed_name );
-                    continue;
+                    decision = library_cmp_by_versions( needed_name, &container_namespace,
+                                                        &provider_namespace);
                 }
-                else
+
+                /* Compare the numeric tail */
+                if( decision == 0 )
+                {
+                    decision = library_cmp_by_name( needed_name,
+                                                    needed_path_in_container,
+                                                    option_container,
+                                                    needed_path_in_provider,
+                                                    option_provider );
+                }
+
+                /* Compare the symbols.
+                 * We skip all libc related libraries to avoid problems with dlmopen() */
+                if( decision == 0 && !libc_lib )
                 {
-                    DEBUG( DEBUG_TOOL,
-                           "%s is newer or equal in the provider",
-                           needed_name );
+                    decision = library_cmp_by_symbols( needed_name, &container_namespace,
+                                                       &provider_namespace);
                 }
+
+                /* If the container library is newer (decision > 0) we skip the link creation.
+                 * In all the other cases, even if we were unable to determine which library
+                 * was newer, we use the one from the provider. */
+                if ( decision > 0 )
+                    continue;
             }
             else if( local_code == ENOENT )
             {