]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
libctf: tear opening and serialization in two
authorNick Alcock <nick.alcock@oracle.com>
Mon, 15 Jul 2024 20:11:40 +0000 (21:11 +0100)
committerNick Alcock <nick.alcock@oracle.com>
Fri, 28 Feb 2025 14:47:24 +0000 (14:47 +0000)
The next stage in sharing the strtab involves tearing two core parts
of libctf into two pieces.

Large parts of init_static_types, called at open time, involve traversing
the types table and initializing the hashtabs used by the type name lookup
functions and the enumerator conflicting checks.  If the string table is
partly located in the parent dict, this is obviously not going to work: so
split out that code into a new init_static_types_names function (which
also means moving the wrapper around init_static_types that was used
to simplify the enumerator code into being a wrapper around
init_static_types_names instead) and call that from init_static_types
(for parent dicts, and < v4 dicts), and from ctf_import (for v4 dicts).

At the same time as doing this we arrange to set LCTF_NO_STR (recently
introduced) iff this is a v4 child dict with a nonzero cth_parent_strlen:
this then blocks more or less everything that involves string operations
until a ctf_import has actually imported the strtab it depends on.  (No
string oeprations that actually use this have been introduced yet, but
since no string deduplication is happening yet either this is harmless.)

For v4 dicts, at import time we also validate that the cth_parent_strlen has
the same value as the parent's strlen (zero is also a valid value,
indicating a non-shared strtab, as is commonplace in older dicts, dicts
emitted by the compiler, parent dicts etc).  This makes ctf_import more
complex, so we simplify things again by dropping all the repeated code in
the obscure used-only-by-ctf_link ctf_import_unref and turning both into
wrappers around an internal function.  We prohibit repeated ctf_imports
(except of NULL or the same dict repeatedly), and set up some new fields
which will be used later to prevent people from adding strings to parent
dicts with pre-existing serialized strtabs once they have children imported
into them (which would change their string length and corrupt all those
strtabs).

Serialization also needs to be torn in two.  The problem here is that
currently serialization does too much: it emits everything including the
strtab, does things that depend on the strtab being finalized (notably
variable table sorting), and then writes it out.  Much of this emission
itself involves strtab writes, so the strtab is not actually complete until
halfway through ctf_serialize.  But when deduplicating, we want to use
machinery in ctf-link and ctf-dedup to deduplicate the strtab after it is
complete, and only then write it out.

We could do this via having ctf_serialize call some sort of horrible
callback, but it seems much simpler to just cut ctf_serialize in two,
and introduce a new ctf_preserialize which can optionally be called to do
all this "everything but the strtab" work.  (If it's not called,
ctf_serialize calls it itself.)

This means pulling some internal variables out of ctf_serialize into the
ctf_dict_t, and slightly abusing LCTF_NO_STR to mean (in addition to its
"no, you can't do much between opening a child dict and importing its
parent" semantics), "no, you can't do much between calling ctf_preserialize
and ctf_serialize". The requirements of both are not quite identical -- you
definitely can do things that involve string lookups after ctf_preserialize
-- but it serves to stop callers from accidentally adding more types after
the types table has been written out, and that's good enough.
ctf_preserialize isn't public API anyway.

libctf/
* ctf-impl.h (struct ctf_dict) [ctf_serializing_buf]: New.
        [ctf_serializing_buf_size]: Likewise.
        [ctf_serializing_vars]: Likewise.
        [ctf_serializing_nvars]: Likewise.
        [ctf_max_children]: Likewise.
(LCTF_PRESERIALIZED): New.
(ctf_preserialize): New.
(ctf_depreserialize): New.
* ctf-open.c (init_static_types): Rename to...
(init_static_types_names): ... this, wrapping a different
        function.
        (init_static_types_internal): Rename to...
        (init_static_types): ... this, and set LCTF_NO_STR if neecessary.
        Tear out the name-lookup guts into...
(init_static_types_names_internal): ... this new function. Fix a few
        comment typos.
(ctf_bufopen): Emphasise that you cannot rely on looking up strings
        at any point in ctf_bufopen any more.
(ctf_dict_close): Free ctf_serializing_buf.
(ctf_import): Turn into a wrapper, calling...
(ctf_import_internal): ... this.  Prohibit repeated ctf_imports of
        different parent dicts, or "unimporting" by setting it back to NULL
        again.  Validate the parent we do import using cth_parent_strlen.
        Call init_static_types_names if the strtab is shared with the
        parent.
(ctf_import_unref): Turn into a wrapper.
* ctf-serialize.c (ctf_serialize): Split out everything before
        strtab serialization into...
(ctf_preserialize): ... this new function.
(ctf_depreserialize): New, undo preserialization on error.

libctf/ctf-impl.h
libctf/ctf-open.c
libctf/ctf-serialize.c

index ce0e186e08a96ce69545587715980f8c3862290d..b8b2732739af74c8347f15539e4384f103bf690d 100644 (file)
@@ -399,6 +399,10 @@ struct ctf_dict
   unsigned char *ctf_dynbase;    /* Freeable CTF file pointer. */
   unsigned char *ctf_buf;        /* Uncompressed CTF data buffer.  */
   size_t ctf_size;               /* Size of CTF header + uncompressed data.  */
+  unsigned char *ctf_serializing_buf; /* CTF buffer in mid-serialization.  */
+  size_t ctf_serializing_buf_size; /* Length of that buffer.  */
+  ctf_varent_t *ctf_serializing_vars; /* Unsorted vars in mid-serialization.  */
+  size_t ctf_serializing_nvars;          /* Number of those vars.  */
   uint32_t *ctf_sxlate;                  /* Translation table for unindexed symtypetab
                                     entries.  */
   unsigned long ctf_nsyms;       /* Number of entries in symtab xlate table.  */
@@ -440,6 +444,7 @@ struct ctf_dict
   uint32_t ctf_parmax;           /* Highest type ID of a parent type.  */
   uint32_t ctf_refcnt;           /* Reference count (for parent links).  */
   uint32_t ctf_flags;            /* Libctf flags (see below).  */
+  uint32_t ctf_max_children;     /* Max number of child dicts.  */
   int ctf_errno;                 /* Error code for most recent error.  */
   int ctf_version;               /* CTF data version.  */
   ctf_dynhash_t *ctf_dthash;     /* Hash of dynamic type definitions.  */
@@ -606,6 +611,7 @@ struct ctf_next
 #define LCTF_STRICT_NO_DUP_ENUMERATORS 0x0004 /* Duplicate enums prohibited.  */
 #define LCTF_NO_STR            0x0008  /* No string lookup possible yet.  */
 #define LCTF_NO_SERIALIZE      0x0010  /* Serialization of this dict prohibited.  */
+#define LCTF_PRESERIALIZED     0x0020  /* Already serialized all but the strtab.  */
 
 extern ctf_dynhash_t *ctf_name_table (ctf_dict_t *, int);
 extern const ctf_type_t *ctf_lookup_by_id (ctf_dict_t **, ctf_id_t);
@@ -753,6 +759,9 @@ extern void ctf_str_remove_ref (ctf_dict_t *, const char *, uint32_t *ref);
 extern void ctf_str_rollback (ctf_dict_t *, ctf_snapshot_id_t);
 extern const ctf_strs_writable_t *ctf_str_write_strtab (ctf_dict_t *);
 
+extern int ctf_preserialize (ctf_dict_t *fp);
+extern void ctf_depreserialize (ctf_dict_t *fp);
+
 extern struct ctf_archive_internal *
 ctf_new_archive_internal (int is_archive, int unmap_on_close,
                          struct ctf_archive *, ctf_dict_t *,
index 9423ad462bce775b647b724fff0a8b9b30918439..3ba78c8942699e2fe45551e49f5e2db6479d132b 100644 (file)
@@ -463,7 +463,7 @@ upgrade_types_v1 (ctf_dict_t *fp, ctf_header_t *cth)
   tbuf = (ctf_type_v1_t *) (fp->ctf_buf + cth->cth_typeoff);
   tend = (ctf_type_v1_t *) (fp->ctf_buf + cth->cth_stroff);
 
-  /* Much like init_static_types(), this is a two-pass process.
+  /* This is a two-pass process.
 
      First, figure out the new type-section size needed.  (It is possible,
      in theory, for it to be less than the old size, but this is very
@@ -701,8 +701,7 @@ upgrade_types (ctf_dict_t *fp, ctf_header_t *cth)
 }
 
 static int
-init_static_types_internal (ctf_dict_t *fp, ctf_header_t *cth,
-                           ctf_dynset_t *all_enums);
+init_static_types_names (ctf_dict_t *fp, ctf_header_t *cth);
 
 /* Populate statically-defined types (those loaded from a saved buffer).
 
@@ -718,22 +717,6 @@ init_static_types_internal (ctf_dict_t *fp, ctf_header_t *cth,
 
 static int
 init_static_types (ctf_dict_t *fp, ctf_header_t *cth)
-{
-  ctf_dynset_t *all_enums;
-  int err;
-
-  if ((all_enums = ctf_dynset_create (htab_hash_pointer, htab_eq_pointer,
-                                     NULL)) == NULL)
-    return ENOMEM;
-
-  err = init_static_types_internal (fp, cth, all_enums);
-  ctf_dynset_destroy (all_enums);
-  return err;
-}
-
-static int
-init_static_types_internal (ctf_dict_t *fp, ctf_header_t *cth,
-                           ctf_dynset_t *all_enums)
 {
   const ctf_type_t *tbuf;
   const ctf_type_t *tend;
@@ -741,18 +724,12 @@ init_static_types_internal (ctf_dict_t *fp, ctf_header_t *cth,
   unsigned long pop[CTF_K_MAX + 1] = { 0 };
   int pop_enumerators = 0;
   const ctf_type_t *tp;
-  uint32_t id;
-  uint32_t *xp;
   unsigned long typemax = 0;
-  ctf_next_t *i = NULL;
-  void *k;
 
   /* We determine whether the dict is a child or a parent based on the value of
      cth_parname.  */
 
   int child = cth->cth_parname != 0;
-  int nlstructs = 0, nlunions = 0;
-  int err;
 
   if (fp->ctf_version < CTF_VERSION_4)
     {
@@ -765,9 +742,12 @@ init_static_types_internal (ctf_dict_t *fp, ctf_header_t *cth,
   tend = (ctf_type_t *) (fp->ctf_buf + cth->cth_stroff);
 
   /* We make two passes through the entire type section, and one third pass
-     through part of it.  In this first pass, we count the number of each type
-     and type-like identifier (like enumerators) and the total number of
-     types.  */
+     through part of it: but only the first is guaranteed to happen at this
+     stage.  The second and third passes require working string lookup, so in
+     child dicts can only happen at ctf_import time.
+
+     In this first pass, we count the number of each type and type-like
+     identifier (like enumerators) and the total number of types.  */
 
   for (tp = tbuf; tp < tend; typemax++)
     {
@@ -844,28 +824,96 @@ init_static_types_internal (ctf_dict_t *fp, ctf_header_t *cth,
      because later-added types will call grow_ptrtab() automatically, as
      needed.  */
 
-  fp->ctf_txlate = malloc (sizeof (uint32_t) * (typemax + 1));
+  fp->ctf_txlate = calloc (typemax + 1, sizeof (uint32_t));
+  fp->ctf_ptrtab = calloc (typemax + 1, sizeof (uint32_t));
   fp->ctf_ptrtab_len = typemax + 1;
-  fp->ctf_ptrtab = malloc (sizeof (uint32_t) * fp->ctf_ptrtab_len);
   fp->ctf_stypes = typemax;
+  fp->ctf_typemax = typemax;
 
   if (fp->ctf_txlate == NULL || fp->ctf_ptrtab == NULL)
     return ENOMEM;             /* Memory allocation failed.  */
 
+  /* UPTODO: BTF is like v4 here: no string lookups in children, which blocks
+     almost all operations until after ctf_import.  */
+  if (child && cth->cth_parent_strlen != 0)
+    {
+      fp->ctf_flags |= LCTF_NO_STR;
+      return 0;
+    }
+
+  ctf_dprintf ("%lu types initialized (other than names)\n", fp->ctf_typemax);
+
+  return init_static_types_names (fp, cth);
+}
+
+static int
+init_static_types_names_internal (ctf_dict_t *fp, ctf_header_t *cth,
+                                 ctf_dynset_t *all_enums);
+
+/* A wrapper to simplify memory allocation.  */
+
+static int
+init_static_types_names (ctf_dict_t *fp, ctf_header_t *cth)
+{
+  ctf_dynset_t *all_enums;
+  int err;
+
+  if ((all_enums = ctf_dynset_create (htab_hash_pointer, htab_eq_pointer,
+                                     NULL)) == NULL)
+    return ENOMEM;
+
+  err = init_static_types_names_internal (fp, cth, all_enums);
+  ctf_dynset_destroy (all_enums);
+  return err;
+}
+
+/* Initialize the parts of the CTF dict whose initialization depends on name
+   lookup.  This happens at open time except for child dicts, when (for CTFv4+
+   dicts) it happens at ctf_import time instead, because before then the strtab
+   is truncated at the start.
+
+   As a function largely called at open time, this function does not reliably
+   set the ctf_errno, but instead *returns* the error code.  */
+
+static int
+init_static_types_names_internal (ctf_dict_t *fp, ctf_header_t *cth,
+                                 ctf_dynset_t *all_enums)
+{
+  const ctf_type_t *tbuf;
+  const ctf_type_t *tend;
+
+  const ctf_type_t *tp;
+  uint32_t id;
+  uint32_t *xp;
+
+  unsigned long typemax = fp->ctf_typemax;
+
+  ctf_next_t *i = NULL;
+  void *k;
+  int err;
+
+  int child = cth->cth_parname != 0;
+  int nlstructs = 0, nlunions = 0;
+
+  tbuf = (ctf_type_t *) (fp->ctf_buf + cth->cth_typeoff);
+  tend = (ctf_type_t *) (fp->ctf_buf + cth->cth_stroff);
+
+  assert (!(fp->ctf_flags & LCTF_NO_STR));
+
   xp = fp->ctf_txlate;
   *xp++ = 0;                   /* Type id 0 is used as a sentinel value.  */
 
-  memset (fp->ctf_txlate, 0, sizeof (uint32_t) * (typemax + 1));
-  memset (fp->ctf_ptrtab, 0, sizeof (uint32_t) * (typemax + 1));
-
-  /* In the second pass through the types, we fill in each entry of the
-     type and pointer tables and add names to the appropriate hashes.
+  /* In this second pass through the types, we fill in each entry of the type
+     and pointer tables and add names to the appropriate hashes.
 
      (Not all names are added in this pass, only type names.  See below.)
 
-     Bump ctf_typemax as we go, but keep it one higher than normal, so that
-     the type being read in is considered a valid type and it is at least
-     barely possible to run simple lookups on it.  */
+     Reset ctf_typemax and bump it as we go, but keep it one higher than normal,
+     so that the type being read in is considered a valid type and it is at
+     least barely possible to run simple lookups on it: but higher types are
+     not, since their names are not yet known.  (It is kept at its standard
+     value before this function is called so that at least some type-related
+     operations work.  */
 
   for (id = 1, fp->ctf_typemax = 1, tp = tbuf; tp < tend; xp++, id++, fp->ctf_typemax++)
     {
@@ -1734,8 +1782,9 @@ ctf_bufopen (const ctf_sect_t *ctfsect, const ctf_sect_t *symsect,
 
   ctf_set_version (fp, hp, hp->cth_version);
 
-  /* Temporary assignment, just enough to be able to initialize
-     the atoms table.  */
+  /* Temporary assignment, just enough to be able to initialize the atoms table.
+     This does not guarantee that we can look up strings: in v4, child dicts
+     cannot reliably look up strings until after ctf_import.  */
 
   fp->ctf_str[CTF_STRTAB_0].cts_strs = (const char *) fp->ctf_buf
     + hp->cth_stroff;
@@ -1997,6 +2046,7 @@ ctf_dict_close (ctf_dict_t *fp)
   free (fp->ctf_txlate);
   free (fp->ctf_ptrtab);
   free (fp->ctf_pptrtab);
+  free (fp->ctf_serializing_buf);
 
   free (fp->ctf_header);
   free (fp);
@@ -2120,45 +2170,100 @@ ctf_cuname_set (ctf_dict_t *fp, const char *name)
   return 0;
 }
 
-/* Import the types from the specified parent dict by storing a pointer to it in
-   ctf_parent and incrementing its reference count.  Only one parent is allowed:
-   if a parent already exists, it is replaced by the new parent.  The pptrtab
-   is wiped, and will be refreshed by the next ctf_lookup_by_name call.  */
-int
-ctf_import (ctf_dict_t *fp, ctf_dict_t *pfp)
+static int
+ctf_import_internal (ctf_dict_t *fp, ctf_dict_t *pfp, int unreffed)
 {
-  if (fp == NULL || fp == pfp || (pfp != NULL && pfp->ctf_refcnt == 0))
+  int err;
+  int no_strings = fp->ctf_flags & LCTF_NO_STR;
+  int old_flags = fp->ctf_flags;
+  ctf_dict_t *old_parent = fp->ctf_parent;
+  const char *old_parname = fp->ctf_parname;
+  int old_unreffed = fp->ctf_parent_unreffed;
+
+  if (pfp == NULL || pfp == fp->ctf_parent)
+    return 0;
+
+  if (fp == NULL || fp == pfp || pfp->ctf_refcnt == 0)
     return (ctf_set_errno (fp, EINVAL));
 
-  if (pfp != NULL && pfp->ctf_dmodel != fp->ctf_dmodel)
+  if (pfp->ctf_dmodel != fp->ctf_dmodel)
     return (ctf_set_errno (fp, ECTF_DMODEL));
 
-  if (fp->ctf_parent && !fp->ctf_parent_unreffed)
-    ctf_dict_close (fp->ctf_parent);
-  fp->ctf_parent = NULL;
+  if (fp->ctf_parent && fp->ctf_header->cth_parent_strlen != 0)
+    return (ctf_set_errno (fp, ECTF_HASPARENT));
 
+  if (fp->ctf_header->cth_parent_strlen != 0 &&
+      pfp->ctf_header->cth_strlen != fp->ctf_header->cth_parent_strlen)
+    {
+      ctf_err_warn (fp, 0, ECTF_WRONGPARENT,
+                   _("ctf_import: incorrect parent dict: %u bytes of strings expected, %u found"),
+                   fp->ctf_header->cth_parent_strlen, pfp->ctf_header->cth_strlen);
+      return (ctf_set_errno (fp, ECTF_WRONGPARENT));
+    }
+
+  fp->ctf_parent = NULL;
   free (fp->ctf_pptrtab);
   fp->ctf_pptrtab = NULL;
   fp->ctf_pptrtab_len = 0;
   fp->ctf_pptrtab_typemax = 0;
 
-  if (pfp != NULL)
+  if (fp->ctf_parname == NULL)
+    if ((err = ctf_parent_name_set (fp, "PARENT")) < 0)
+      return err;                              /* errno is set for us.  */
+
+  if (!unreffed)
+    pfp->ctf_refcnt++;
+
+  fp->ctf_parent_unreffed = unreffed;
+  fp->ctf_parent = pfp;
+
+  /* If this is a dict that hasn't previously allowed string lookups,
+     we can allow them now, and finish initialization.  */
+
+  fp->ctf_flags |= LCTF_CHILD;
+  fp->ctf_flags &= ~LCTF_NO_STR;
+
+  if (no_strings && (err = init_static_types_names (fp, fp->ctf_header)) < 0)
     {
-      int err;
+      /* Undo everything other than cache flushing.  */
 
-      if (fp->ctf_parname == NULL)
-       if ((err = ctf_parent_name_set (fp, "PARENT")) < 0)
-         return err;
+      fp->ctf_flags = old_flags;
+      fp->ctf_parent_unreffed = old_unreffed;
+      fp->ctf_parent = old_parent;
+      fp->ctf_parname = old_parname;
 
-      fp->ctf_flags |= LCTF_CHILD;
-      pfp->ctf_refcnt++;
-      fp->ctf_parent_unreffed = 0;
+      if (fp->ctf_parent_unreffed)
+       old_parent->ctf_refcnt++;
+
+      return (ctf_set_errno (fp, err));                /* errno is set for us.  */
     }
 
-  fp->ctf_parent = pfp;
+  /* Failure can now no longer happen, so we can close the old parent (which may
+     deallocate it and is not easily reversible).  */
+
+  if (old_parent && !old_unreffed)
+    ctf_dict_close (old_parent);
+
+  fp->ctf_parent->ctf_max_children++;
   return 0;
 }
 
+/* Import the types from the specified parent dict by storing a pointer to it in
+   ctf_parent and incrementing its reference count.  You can only call this
+   function once on serialized dicts: the parent cannot be replaced once set.
+   (You can call it on unserialized dicts as often as you like.)
+
+   The pptrtab is wiped, and will be refreshed by the next ctf_lookup_by_name
+   call.
+
+   You can call this with a parent of NULL as many times as you like (but
+   it doesn't do much).  */
+int
+ctf_import (ctf_dict_t *fp, ctf_dict_t *pfp)
+{
+  return ctf_import_internal (fp, pfp, 0);
+}
+
 /* Like ctf_import, but does not increment the refcount on the imported parent
    or close it at any point: as a result it can go away at any time and the
    caller must do all freeing itself.  Used internally to avoid refcount
@@ -2166,34 +2271,7 @@ ctf_import (ctf_dict_t *fp, ctf_dict_t *pfp)
 int
 ctf_import_unref (ctf_dict_t *fp, ctf_dict_t *pfp)
 {
-  if (fp == NULL || fp == pfp || (pfp != NULL && pfp->ctf_refcnt == 0))
-    return (ctf_set_errno (fp, EINVAL));
-
-  if (pfp != NULL && pfp->ctf_dmodel != fp->ctf_dmodel)
-    return (ctf_set_errno (fp, ECTF_DMODEL));
-
-  if (fp->ctf_parent && !fp->ctf_parent_unreffed)
-    ctf_dict_close (fp->ctf_parent);
-  fp->ctf_parent = NULL;
-
-  free (fp->ctf_pptrtab);
-  fp->ctf_pptrtab = NULL;
-  fp->ctf_pptrtab_len = 0;
-  fp->ctf_pptrtab_typemax = 0;
-  if (pfp != NULL)
-    {
-      int err;
-
-      if (fp->ctf_parname == NULL)
-       if ((err = ctf_parent_name_set (fp, "PARENT")) < 0)
-         return err;
-
-      fp->ctf_flags |= LCTF_CHILD;
-      fp->ctf_parent_unreffed = 1;
-    }
-
-  fp->ctf_parent = pfp;
-  return 0;
+  return ctf_import_internal (fp, pfp, 1);
 }
 
 /* Set the data model constant for the CTF dict.  */
index ebd61457ccb02cb2f0a3d09f4fced06d721503f8..19ab8abf63e1a8c0b85f7ba5193015677d923b4a 100644 (file)
@@ -933,22 +933,16 @@ ctf_sort_var (const void *one_, const void *two_, void *arg_)
 
 /* Overall serialization.  */
 
-/* Emit a new CTF dict which is a serialized copy of this one: also reify
-   the string table and update all offsets in the current dict suitably.
-   (This simplifies ctf-string.c a little, at the cost of storing a second
-   copy of the strtab if this dict was originally read in via ctf_open.)
-
-   Other aspects of the existing dict are unchanged, although some
-   static entries may be duplicated in the dynamic state (which should
-   have no effect on visible operation).  */
+/* Do all aspects of serialization up to strtab writeout and variable table
+   sorting.  The resulting dict will have the LCTF_PRESERIALIZED flag on and
+   must not be modified in any way before serialization.  (This is not enforced,
+   as this feature is internal-only, employed by the linker machinery.)  */
 
-static unsigned char *
-ctf_serialize (ctf_dict_t *fp, size_t *bufsiz)
+int
+ctf_preserialize (ctf_dict_t *fp)
 {
   ctf_header_t hdr, *hdrp;
   ctf_dvdef_t *dvd;
-  ctf_varent_t *dvarents;
-  const ctf_strs_writable_t *strtab;
   int sym_functions = 0;
 
   unsigned char *t;
@@ -956,20 +950,13 @@ ctf_serialize (ctf_dict_t *fp, size_t *bufsiz)
   size_t buf_size, type_size, objt_size, func_size;
   size_t funcidx_size, objtidx_size;
   size_t nvars;
-  unsigned char *buf = NULL, *newbuf;
+  unsigned char *buf = NULL;
 
   emit_symtypetab_state_t symstate;
   memset (&symstate, 0, sizeof (emit_symtypetab_state_t));
 
-  /* Stop unstable file formats (subject to change) getting out into the
-     wild.  */
-#if CTF_VERSION != CTF_STABLE_VERSION
-  if (!getenv ("I_KNOW_LIBCTF_IS_UNSTABLE"))
-    {
-      ctf_set_errno (fp, ECTF_UNSTABLE);
-      return NULL;
-    }
-#endif
+  if (fp->ctf_flags & LCTF_NO_STR)
+    return (ctf_set_errno (fp, ECTF_NOPARENT));
 
   /* Prohibit reserialization of dicts for which we have dynamic state inherited
      from the upgrade process which we cannot record in the dict.  Right now,
@@ -977,10 +964,7 @@ ctf_serialize (ctf_dict_t *fp, size_t *bufsiz)
      offset to v2 and higher, and nowhere to record this in CTFv4.  */
 
   if (fp->ctf_flags & LCTF_NO_SERIALIZE)
-    {
-      ctf_set_errno (fp, ECTF_CTFVERS_NO_SERIALIZE);
-      return NULL;
-    }
+    return (ctf_set_errno (fp, ECTF_CTFVERS_NO_SERIALIZE));
 
   /* Fill in an initial CTF header.  The type section begins at a 4-byte aligned
      boundary past the CTF header itself (at relative offset zero).  The flag
@@ -1010,17 +994,17 @@ ctf_serialize (ctf_dict_t *fp, size_t *bufsiz)
                                            sym_functions)) != CTF_ERR)
        if ((ctf_add_funcobjt_sym_forced (fp, sym_functions, sym_name, sym)) < 0)
          if (ctf_errno (fp) != ECTF_DUPLICATE)
-           return NULL;                        /* errno is set for us.  */
+           return -1;                          /* errno is set for us.  */
 
       if (ctf_errno (fp) != ECTF_NEXT_END)
-       return NULL;                            /* errno is set for us.  */
+       return -1;                              /* errno is set for us.  */
     } while (sym_functions++ < 1);
 
   /* Figure out how big the symtypetabs are now.  */
 
   if (ctf_symtypetab_sect_sizes (fp, &symstate, &hdr, &objt_size, &func_size,
                                 &objtidx_size, &funcidx_size) < 0)
-    return NULL;                               /* errno is set for us.  */
+    return -1;                                 /* errno is set for us.  */
 
   /* Propagate all vars into the dynamic state, so we can put them back later.
      Variables already in the dynamic state, likely due to repeated
@@ -1032,7 +1016,7 @@ ctf_serialize (ctf_dict_t *fp, size_t *bufsiz)
 
       if (name != NULL && !ctf_dvd_lookup (fp, name))
        if (ctf_add_variable_forced (fp, name, fp->ctf_vars[i].ctv_type) < 0)
-         return NULL;                          /* errno is set for us.  */
+         return -1;                            /* errno is set for us.  */
     }
 
   for (nvars = 0, dvd = ctf_list_next (&fp->ctf_dvdefs);
@@ -1052,14 +1036,12 @@ ctf_serialize (ctf_dict_t *fp, size_t *bufsiz)
   hdr.cth_typeoff = hdr.cth_varoff + (nvars * sizeof (ctf_varent_t));
   hdr.cth_stroff = hdr.cth_typeoff + type_size;
   hdr.cth_strlen = 0;
+  hdr.cth_parent_strlen = 0;
 
   buf_size = sizeof (ctf_header_t) + hdr.cth_stroff + hdr.cth_strlen;
 
   if ((buf = malloc (buf_size)) == NULL)
-    {
-      ctf_set_errno (fp, EAGAIN);
-      return NULL;
-    }
+    return (ctf_set_errno (fp, EAGAIN));
 
   memcpy (buf, &hdr, sizeof (ctf_header_t));
   t = (unsigned char *) buf + sizeof (ctf_header_t) + hdr.cth_objtoff;
@@ -1072,18 +1054,21 @@ ctf_serialize (ctf_dict_t *fp, size_t *bufsiz)
 
   if (ctf_emit_symtypetab_sects (fp, &symstate, &t, objt_size, func_size,
                                 objtidx_size, funcidx_size) < 0)
-    goto err;
+    {
+      free (buf);
+      return -1;                               /* errno is set for us.  */
+    }
 
   assert (t == (unsigned char *) buf + sizeof (ctf_header_t) + hdr.cth_varoff);
 
   /* Work over the variable list, translating everything into ctf_varent_t's and
      prepping the string table.  */
 
-  dvarents = (ctf_varent_t *) t;
+  fp->ctf_serializing_vars = (ctf_varent_t *) t;
   for (i = 0, dvd = ctf_list_next (&fp->ctf_dvdefs); dvd != NULL;
        dvd = ctf_list_next (dvd), i++)
     {
-      ctf_varent_t *var = &dvarents[i];
+      ctf_varent_t *var = &fp->ctf_serializing_vars[i];
 
       ctf_str_add_ref (fp, dvd->dvd_name, &var->ctv_name);
       var->ctv_type = (uint32_t) dvd->dvd_type;
@@ -1091,6 +1076,7 @@ ctf_serialize (ctf_dict_t *fp, size_t *bufsiz)
   assert (i == nvars);
 
   t += sizeof (ctf_varent_t) * nvars;
+  fp->ctf_serializing_nvars = nvars;
 
   assert (t == (unsigned char *) buf + sizeof (ctf_header_t) + hdr.cth_typeoff);
 
@@ -1103,6 +1089,65 @@ ctf_serialize (ctf_dict_t *fp, size_t *bufsiz)
 
   assert (t == (unsigned char *) buf + sizeof (ctf_header_t) + hdr.cth_stroff);
 
+  fp->ctf_serializing_buf = buf;
+  fp->ctf_serializing_buf_size = buf_size;
+
+  /* Prohibit additions and the like from this point on.  */
+  fp->ctf_flags |= LCTF_NO_STR;
+
+  return 0;
+}
+
+/* Undo preserialization (called on error).  */
+void
+ctf_depreserialize (ctf_dict_t *fp)
+{
+  fp->ctf_flags &= ~LCTF_NO_STR;
+  free (fp->ctf_serializing_buf);
+  fp->ctf_serializing_buf = NULL;
+  fp->ctf_serializing_vars = NULL;
+  fp->ctf_serializing_buf_size = 0;
+  fp->ctf_serializing_nvars = 0;
+}
+
+/* Emit a new CTF dict which is a serialized copy of this one: also reify
+   the string table and update all offsets in the current dict suitably.
+   (This simplifies ctf-string.c a little, at the cost of storing a second
+   copy of the strtab if this dict was originally read in via ctf_open.)
+
+   Other aspects of the existing dict are unchanged, although some
+   static entries may be duplicated in the dynamic state (which should
+   have no effect on visible operation).  */
+
+static unsigned char *
+ctf_serialize (ctf_dict_t *fp, size_t *bufsiz)
+{
+  const ctf_strs_writable_t *strtab;
+  unsigned char *buf, *newbuf;
+  ctf_header_t *hdrp;
+
+  /* Stop unstable file formats (subject to change) getting out into the
+     wild.  */
+#if CTF_VERSION != CTF_STABLE_VERSION
+  if (!getenv ("I_KNOW_LIBCTF_IS_UNSTABLE"))
+    {
+      ctf_depreserialize (fp);
+      ctf_set_errno (fp, ECTF_UNSTABLE);
+      return NULL;
+    }
+#endif
+
+  /* Preserialize, if we need to.  */
+
+  if (!fp->ctf_serializing_buf)
+    if (ctf_preserialize (fp) < 0)
+      return NULL;                             /* errno is set for us.  */
+
+  /* UPTODO: prevent writing of BTF dicts when upgrading from CTFv3.  */
+
+  /* Allow string lookup again, now we need it to sort the vartab.  */
+  fp->ctf_flags &= ~LCTF_NO_STR;
+
   /* Construct the final string table and fill out all the string refs with the
      final offsets.  At link time, before the strtab can be constructed, child
      dicts also need their cth_parent_strlen header field updated to match the
@@ -1113,34 +1158,45 @@ ctf_serialize (ctf_dict_t *fp, size_t *bufsiz)
   if ((fp->ctf_flags & LCTF_LINKING) && fp->ctf_parent)
     fp->ctf_header->cth_parent_strlen = fp->ctf_parent->ctf_str[CTF_STRTAB_0].cts_len;
 
+  hdrp = (ctf_header_t *) fp->ctf_serializing_buf;
+
   strtab = ctf_str_write_strtab (fp);
 
   if (strtab == NULL)
-    goto oom;
+    goto err;
 
   /* Now the string table is constructed, we can sort the buffer of
      ctf_varent_t's.  */
   ctf_sort_var_arg_cb_t sort_var_arg = { fp, (ctf_strs_t *) strtab };
-  ctf_qsort_r (dvarents, nvars, sizeof (ctf_varent_t), ctf_sort_var,
-              &sort_var_arg);
+  ctf_qsort_r (fp->ctf_serializing_vars, fp->ctf_serializing_nvars,
+              sizeof (ctf_varent_t), ctf_sort_var, &sort_var_arg);
 
-  if ((newbuf = realloc (buf, buf_size + strtab->cts_len)) == NULL)
+  if ((newbuf = realloc (fp->ctf_serializing_buf, fp->ctf_serializing_buf_size
+                        + strtab->cts_len)) == NULL)
     goto oom;
 
-  buf = newbuf;
-  memcpy (buf + buf_size, strtab->cts_strs, strtab->cts_len);
-  hdrp = (ctf_header_t *) buf;
+  fp->ctf_serializing_buf = newbuf;
+  memcpy (fp->ctf_serializing_buf + fp->ctf_serializing_buf_size, strtab->cts_strs,
+         strtab->cts_len);
+  hdrp = (ctf_header_t *) fp->ctf_serializing_buf;
   hdrp->cth_strlen = strtab->cts_len;
-  buf_size += hdrp->cth_strlen;
-  *bufsiz = buf_size;
   hdrp->cth_parent_strlen = fp->ctf_header->cth_parent_strlen;
+  fp->ctf_serializing_buf_size += hdrp->cth_strlen;
+  *bufsiz = fp->ctf_serializing_buf_size;
+
+  buf = fp->ctf_serializing_buf;
+
+  fp->ctf_serializing_buf = NULL;
+  fp->ctf_serializing_vars = NULL;
+  fp->ctf_serializing_buf_size = 0;
+  fp->ctf_serializing_nvars = 0;
 
   return buf;
 
 oom:
   ctf_set_errno (fp, EAGAIN);
 err:
-  free (buf);
+  ctf_depreserialize (fp);
   return NULL;                                 /* errno is set for us.  */
 }