*     ./gendwarfksyms --stable --dump-dies \
  *             examples/kabi_ex.o 2>&1 >/dev/null | \
  *     FileCheck examples/kabi_ex.h --check-prefix=STABLE
+
+ * $ nm examples/kabi_ex.o | awk '{ print $NF }' | \
+ *     ./gendwarfksyms --stable --dump-versions \
+ *             examples/kabi_ex.o 2>&1 >/dev/null | \
+ *     sort | \
+ *     FileCheck examples/kabi_ex.h --check-prefix=VERSIONS
  */
 
 #ifndef __KABI_EX_H__
 /*
  * STABLE:      variable structure_type ex2a {
  * STABLE-NEXT:   member base_type int byte_size(4) encoding(5) a data_member_location(0) ,
- * STABLE-NEXT:   member base_type [[ULONG:long unsigned int|unsigned long]] byte_size(8) encoding(7) b data_member_location(8)
+ * STABLE-NEXT:   member base_type [[ULONG]] byte_size(8) encoding(7) b data_member_location(8)
  * STABLE-NEXT:   member base_type int byte_size(4) encoding(5) c data_member_location(16) ,
  * STABLE-NEXT:   member base_type [[ULONG]] byte_size(8) encoding(7) d data_member_location(24)
  * STABLE-NEXT: } byte_size(32)
 
 /*
  * STABLE:      variable structure_type ex3a {
- * STABLE-NEXT:   member base_type [[ULONG:long unsigned int|unsigned long]] byte_size(8) encoding(7) a data_member_location(0)
+ * STABLE-NEXT:   member base_type [[ULONG]] byte_size(8) encoding(7) a data_member_location(0)
  * STABLE-NEXT:   member base_type [[ULONG]] byte_size(8) encoding(7) unused data_member_location(8)
  * STABLE-NEXT: } byte_size(16)
  */
  * STABLE-NEXT: } byte_size(8)
  */
 
+/*
+ * Example: A type string override.
+ */
+
+struct ex5a {
+       unsigned long a;
+};
+
+/*
+ * This may be safe if the structure is fully opaque to modules, even though
+ * its definition has inadvertently become part of the ABI.
+ */
+KABI_TYPE_STRING(
+       "s#ex5a",
+       "structure_type ex5a { member pointer_type { s#ex4a } byte_size(8) p data_member_location(0) } byte_size(8)");
+
+/*
+ * Make sure the fully expanded type string includes ex4a.
+ *
+ * VERSIONS:      ex5a variable structure_type ex5a {
+ * VERSIONS-SAME:   member pointer_type {
+ * VERSIONS-SAME:     structure_type ex4a {
+ * VERSIONS-SAME:       member base_type [[ULONG:long unsigned int|unsigned long]] byte_size(8) encoding(7) a data_member_location(0)
+ * VERSIONS-SAME:     } byte_size(8)
+ * VERSIONS-SAME:   } byte_size(8) p data_member_location(0)
+ * VERSIONS-SAME: } byte_size(8)
+ */
+
+/*
+ * Example: A type string definition for a non-existent type.
+ */
+
+struct ex5b {
+       unsigned long a;
+};
+
+/* Replace the type string for struct ex5b */
+KABI_TYPE_STRING(
+       "s#ex5b",
+       "structure_type ex5b { member pointer_type { s#ex5c } byte_size(8) p data_member_location(0) } byte_size(8)");
+
+/* Define a type string for a non-existent struct ex5c */
+KABI_TYPE_STRING(
+       "s#ex5c",
+       "structure_type ex5c { member base_type int byte_size(4) encoding(5) n data_member_location(0) } byte_size(8)");
+
+/*
+ * Make sure the fully expanded type string includes the definition for ex5c.
+ *
+ * VERSIONS:      ex5b variable structure_type ex5b {
+ * VERSIONS-SAME:   member pointer_type {
+ * VERSIONS-SAME:     structure_type ex5c {
+ * VERSIONS-SAME:       member base_type int byte_size(4) encoding(5) n data_member_location(0)
+ * VERSIONS-SAME:     } byte_size(8)
+ * VERSIONS-SAME:   } byte_size(8) p data_member_location(0)
+ * VERSIONS-SAME: } byte_size(8)
+ */
+
+/*
+ * Example: A type string override for a symbol.
+ */
+
+KABI_TYPE_STRING("ex6a", "variable s#ex5c");
+
+/*
+ * VERSIONS:      ex6a variable structure_type ex5c {
+ * VERSIONS-SAME:   member base_type int byte_size(4) encoding(5) n data_member_location(0)
+ * VERSIONS-SAME: } byte_size(8)
+ */
 #endif /* __KABI_EX_H__ */
 
 #define TYPE_HASH_BITS 12
 static HASHTABLE_DEFINE(type_map, 1 << TYPE_HASH_BITS);
 
-static int type_map_get(const char *name, struct type_expansion **res)
+static int __type_map_get(const char *name, struct type_expansion **res)
 {
        struct type_expansion *e;
 
        return -1;
 }
 
-static void type_map_add(const char *name, struct type_expansion *type)
+static struct type_expansion *type_map_add(const char *name,
+                                          struct type_expansion *type)
 {
        struct type_expansion *e;
 
-       if (type_map_get(name, &e)) {
+       if (__type_map_get(name, &e)) {
                e = xmalloc(sizeof(struct type_expansion));
                type_expansion_init(e);
                e->name = xstrdup(name);
        } else {
                /* Use the longest available expansion */
                if (type->len <= e->len)
-                       return;
+                       return e;
 
                type_list_free(&e->expanded);
 
                type_list_write(&e->expanded, stderr);
                checkp(fputs("\n", stderr));
        }
+
+       return e;
+}
+
+static void type_parse(const char *name, const char *str,
+                      struct type_expansion *type);
+
+static int type_map_get(const char *name, struct type_expansion **res)
+{
+       struct type_expansion type;
+       const char *override;
+
+       if (!__type_map_get(name, res))
+               return 0;
+
+       /*
+        * If die_map didn't contain a type, we might still have
+        * a type_string kABI rule that defines it.
+        */
+       if (stable && kabi_get_type_string(name, &override)) {
+               type_expansion_init(&type);
+               type_parse(name, override, &type);
+               *res = type_map_add(name, &type);
+               type_expansion_free(&type);
+               return 0;
+       }
+
+       return -1;
 }
 
 static void type_map_write(FILE *file)
        return name;
 }
 
-static void __calculate_version(struct version *version, struct list_head *list)
+static void __calculate_version(struct version *version,
+                               struct type_expansion *type)
 {
        struct type_list_entry *entry;
        struct type_expansion *e;
 
        /* Calculate a CRC over an expanded type string */
-       list_for_each_entry(entry, list, list) {
+       list_for_each_entry(entry, &type->expanded, list) {
                if (is_type_prefix(entry->str)) {
-                       check(type_map_get(entry->str, &e));
+                       if (type_map_get(entry->str, &e))
+                               error("unknown type reference to '%s' when expanding '%s'",
+                                     entry->str, type->name);
 
                        /*
                         * It's sufficient to expand each type reference just
                                version_add(version, entry->str);
                        } else {
                                cache_mark_expanded(&expansion_cache, e);
-                               __calculate_version(version, &e->expanded);
+                               __calculate_version(version, e);
                        }
                } else {
                        version_add(version, entry->str);
        }
 }
 
-static void calculate_version(struct version *version, struct list_head *list)
+static void calculate_version(struct version *version,
+                             struct type_expansion *type)
 {
        version_init(version);
-       __calculate_version(version, list);
+       __calculate_version(version, type);
        cache_free(&expansion_cache);
 }
 
        cache_free(&expansion_cache);
 }
 
+static void type_parse(const char *name, const char *str,
+                      struct type_expansion *type)
+{
+       char *fragment;
+       size_t start = 0;
+       size_t end;
+       size_t pos;
+
+       if (!*str)
+               error("empty type string override for '%s'", name);
+
+       type_expansion_init(type);
+
+       for (pos = 0; str[pos]; ++pos) {
+               bool empty;
+               char marker = ' ';
+
+               if (!is_type_prefix(&str[pos]))
+                       continue;
+
+               end = pos + 2;
+
+               /*
+                * Find the end of the type reference. If the type name contains
+                * spaces, it must be in single quotes.
+                */
+               if (str[end] == '\'') {
+                       marker = '\'';
+                       ++end;
+               }
+               while (str[end] && str[end] != marker)
+                       ++end;
+
+               /* Check that we have a non-empty type name */
+               if (marker == '\'') {
+                       if (str[end] != marker)
+                               error("incomplete %c# type reference for '%s' (string : '%s')",
+                                     str[pos], name, str);
+                       empty = end == pos + 3;
+                       ++end;
+               } else {
+                       empty = end == pos + 2;
+               }
+               if (empty)
+                       error("empty %c# type name for '%s' (string: '%s')",
+                             str[pos], name, str);
+
+               /* Append the part of the string before the type reference */
+               if (pos > start) {
+                       fragment = xstrndup(&str[start], pos - start);
+                       type_expansion_append(type, fragment, fragment);
+               }
+
+               /*
+                * Append the type reference -- note that if the reference
+                * is invalid, i.e. points to a non-existent type, we will
+                * print out an error when calculating versions.
+                */
+               fragment = xstrndup(&str[pos], end - pos);
+               type_expansion_append(type, fragment, fragment);
+
+               start = end;
+               pos = end - 1;
+       }
+
+       /* Append the rest of the type string, if there's any left */
+       if (str[start])
+               type_expansion_append(type, &str[start], NULL);
+}
+
 static void expand_type(struct die *cache, void *arg)
 {
        struct type_expansion type;
+       const char *override;
        char *name;
 
        if (cache->mapped)
                return;
 
        debug("%s", name);
-       type_expand(cache, &type, true);
-       type_map_add(name, &type);
 
+       if (stable && kabi_get_type_string(name, &override))
+               type_parse(name, override, &type);
+       else
+               type_expand(cache, &type, true);
+
+       type_map_add(name, &type);
        type_expansion_free(&type);
        free(name);
 }
 {
        struct type_expansion type;
        struct version version;
+       const char *override;
        struct die *cache;
 
        /*
        if (__die_map_get(sym->die_addr, DIE_SYMBOL, &cache))
                return; /* We'll warn about missing CRCs later. */
 
-       type_expand(cache, &type, false);
+       if (stable && kabi_get_type_string(sym->name, &override))
+               type_parse(sym->name, override, &type);
+       else
+               type_expand(cache, &type, false);
 
        /* If the symbol already has a version, don't calculate it again. */
        if (sym->state != SYMBOL_PROCESSED) {
-               calculate_version(&version, &type.expanded);
+               calculate_version(&version, &type);
                symbol_set_crc(sym, version.crc);
                debug("%s = %lx", sym->name, version.crc);