[05/22] libctf: support addition of types to dicts read via ctf_open()

Message ID 20240417202018.34966-6-nick.alcock@oracle.com
State New
Headers
Series more modifiable CTF dicts (and a few bugfixes) |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_binutils_build--master-arm fail Patch failed to apply
linaro-tcwg-bot/tcwg_binutils_build--master-aarch64 fail Patch failed to apply

Commit Message

Nick Alcock April 17, 2024, 8:20 p.m. UTC
  libctf has long declared deserialized dictionaries (out of files or ELF
sections or memory buffers or whatever) to be read-only: back in the
furthest prehistory this was not the case, in that you could add a
few sorts of type to such dicts, but attempting to do so often caused
horrible memory corruption, so I banned the lot.

But it turns out real consumers want it (notably DTrace, which
synthesises pointers to types that don't have them and adds them to the
ctf_open()ed dicts if it needs them). Let's bring it back again, but
without the memory corruption and without the massive code duplication
required in days of yore to distinguish between static and dynamic
types: the representation of both types has been identical for a few
years, with the only difference being that types as a whole are stored in
a big buffer for types read in via ctf_open and per-type hashtables for
newly-added types.

So we discard the internally-visible concept of "readonly dictionaries"
in favour of declaring the *range of types* that were already present
when the dict was read in to be read-only: you can't modify them (say,
by adding members to them if they're structs, or calling ctf_set_array
on them), but you can add more types and point to them.  (The API
remains the same, with calls sometimes returning ECTF_RDONLY, but now
they do so less often.)

This is a fairly invasive change, mostly because code written since the
ban was introduced didn't take the possibility of a static/dynamic split
into account.  Some of these irregularities were hard to define as
anything but bugs.

Notably:

 - The symbol handling was assuming that symbols only needed to be
   looked for in dynamic hashtabs or static linker-laid-out indexed/
   nonindexed layouts, but now we want to check both in case people
   added more symbols to a dict they opened.

 - The code that handles type additions wasn't checking to see if types
   with the same name existed *at all* (so you could do
   ctf_add_typedef (fp, "foo", bar) repeatedly without error).  This
   seems reasonable for types you just added, but we probably *do* want
   to ban addition of types with names that override names we already
   used in the ctf_open()ed portion, since that would probably corrupt
   existing type relationships.  (Doing things this way also avoids
   causing new errors for any existing code that was doing this sort of
   thing.)

 - ctf_lookup_variable entirely failed to work for variables just added
   by ctf_add_variable: you had to write the dict out and read it back
   in again before they appeared.

 - The symbol handling remembered what symbols you looked up but didn't
   remember their types, so you could look up an object symbol and then
   find it popping up when you asked for function symbols, which seems
   less than ideal.  Since we had to rejig things enough to be able to
   distinguish function and object symbols internally anyway (in order
   to give suitable errors if you try to add a symbol with a name that
   already existed in the ctf_open()ed dict), this bug suddenly became
   more visible and was easily fixed.

We do not (yet) support writing out dicts that have been previously read
in via ctf_open() or other deserializer (you can look things up in them,
but not write them out a second time).  This never worked, so there is
no incompatibility; if it is needed at a later date, the serializer is a
little bit closer to having it work now (the only table we don't deal
with is the types table, and that's because the upcoming CTFv4 changes
are likely to make major changes to the way that table is represented
internally, so adding more code that depends on its current form seems
like a bad idea).

There is a new testcase that tests much of this, in particular that
modification of existing types is still banned and that you can add new
ones and chase them without error.

libctf/

	* ctf-impl.h (struct ctf_dict.ctf_symhash): Split into...
	(ctf_dict.ctf_symhash_func): ... this and...
	(ctf_dict.ctf_symhash_objt): ... this.
	(ctf_dict.ctf_stypes): New, counts static types.
	(LCTF_INDEX_TO_TYPEPTR): Use it instead of CTF_RDWR.
	(LCTF_RDWR): Deleted.
	(LCTF_DIRTY): Renumbered.
	(LCTF_LINKING): Likewise.
	(ctf_lookup_variable_here): New.
	(ctf_lookup_by_sym_or_name): Likewise.
	(ctf_symbol_next_static): Likewise.
	(ctf_add_variable_forced): Likewise.
	(ctf_add_funcobjt_sym_forced): Likewise.
	(ctf_simple_open_internal): Adjust.
	(ctf_bufopen_internal): Likewise.
	* ctf-create.c (ctf_grow_ptrtab): Adjust a lot to start with.
	(ctf_create): Migrate a bunch of initializations into bufopen.
	Force recreation of name tables.  Do not forcibly override the
	model, let ctf_bufopen do it.
	(ctf_static_type): New.
	(ctf_update): Drop LCTF_RDWR check.
	(ctf_dynamic_type): Likewise.
	(ctf_add_function): Likewise.
	(ctf_add_type_internal): Likewise.
	(ctf_rollback): Check ctf_stypes, not LCTF_RDWR.
	(ctf_set_array): Likewise.
	(ctf_add_struct_sized): Likewise.
	(ctf_add_union_sized): Likewise.
	(ctf_add_enum): Likewise.
	(ctf_add_enumerator): Likewise (only on the target dict).
	(ctf_add_member_offset): Likewise.
	(ctf_add_generic): Drop LCTF_RDWR check.  Ban addition of types
	with colliding names.
	(ctf_add_forward): Note safety under the new rules.
	(ctf_add_variable): Split all but the existence check into...
	(ctf_add_variable_forced): ... this new function.
	(ctf_add_funcobjt_sym): Likewise...
	(ctf_add_funcobjt_sym_forced): ... for this new function.
	* ctf-link.c (ctf_link_add_linker_symbol): Ban calling on dicts
	with any stypes.
	(ctf_link_add_strtab): Likewise.
	(ctf_link_shuffle_syms): Likewise.
	(ctf_link_intern_extern_string): Note pre-existing prohibition.
	* ctf-lookup.c (ctf_lookup_by_id): Drop LCTF_RDWR check.
	(ctf_lookup_variable): Split out looking in a dict but not
	its parent into...
	(ctf_lookup_variable_here): ... this new function.
	(ctf_lookup_symbol_idx): Track whether looking up a function or
	object: cache them separately.
	(ctf_symbol_next): Split out looking in non-dynamic symtypetab
	entries to...
	(ctf_symbol_next_static): ... this new function.  Don't get confused
	by the simultaneous presence of static and dynamic symtypetab entries.
	(ctf_try_lookup_indexed):  Don't waste time looking up symbols by
	index before there can be any idea how symbols are numbered.
	(ctf_lookup_by_sym_or_name): Distinguish between function and
	data object lookups.  Drop LCTF_RDWR.
	(ctf_lookup_by_symbol): Adjust.
	(ctf_lookup_by_symbol_name): Likewise.
	* ctf-open.c (init_types): Rename to...
	(init_static_types): ... this.  Drop LCTF_RDWR.  Populate ctf_stypes.
	(ctf_simple_open): Drop writable arg.
	(ctf_simple_open_internal): Likewise.
	(ctf_bufopen): Likewise.
	(ctf_bufopen_internal): Populate fields only used for writable dicts.
	Drop LCTF_RDWR.
	(ctf_dict_close): Cater for symhash cache split.
	* ctf-serialize.c (ctf_serialize): Use ctf_stypes, not LCTF_RDWR.
	* ctf-types.c (ctf_variable_next): Drop LCTF_RDWR.
	* testsuite/libctf-lookup/add-to-opened*: New test.
---
 libctf/ctf-create.c                           | 184 ++++-----
 libctf/ctf-impl.h                             |  28 +-
 libctf/ctf-link.c                             |  14 +-
 libctf/ctf-lookup.c                           | 351 +++++++++++-------
 libctf/ctf-open.c                             |  66 ++--
 libctf/ctf-serialize.c                        |  51 ++-
 libctf/ctf-types.c                            |  28 +-
 .../libctf-lookup/add-to-opened-ctf.c         |  19 +
 .../testsuite/libctf-lookup/add-to-opened.c   | 147 ++++++++
 .../testsuite/libctf-lookup/add-to-opened.lk  |   3 +
 10 files changed, 621 insertions(+), 270 deletions(-)
 create mode 100644 libctf/testsuite/libctf-lookup/add-to-opened-ctf.c
 create mode 100644 libctf/testsuite/libctf-lookup/add-to-opened.c
 create mode 100644 libctf/testsuite/libctf-lookup/add-to-opened.lk
  

Patch

diff --git a/libctf/ctf-create.c b/libctf/ctf-create.c
index 240f3dad9ff..7aa244e5ec7 100644
--- a/libctf/ctf-create.c
+++ b/libctf/ctf-create.c
@@ -47,9 +47,13 @@  ctf_grow_ptrtab (ctf_dict_t *fp)
   size_t new_ptrtab_len = fp->ctf_ptrtab_len;
 
   /* We allocate one more ptrtab entry than we need, for the initial zero,
-     plus one because the caller will probably allocate a new type.  */
+     plus one because the caller will probably allocate a new type.
 
-  if (fp->ctf_ptrtab == NULL)
+     Equally, if the ptrtab is small -- perhaps due to ctf_open of a small
+     dict -- boost it by quite a lot at first, so we don't need to keep
+     realloc()ing.  */
+
+  if (fp->ctf_ptrtab == NULL || fp->ctf_ptrtab_len < 1024)
     new_ptrtab_len = 1024;
   else if ((fp->ctf_typemax + 2) > fp->ctf_ptrtab_len)
     new_ptrtab_len = fp->ctf_ptrtab_len * 1.25;
@@ -104,29 +108,11 @@  ctf_create (int *errp)
 {
   static const ctf_header_t hdr = { .cth_preamble = { CTF_MAGIC, CTF_VERSION, 0 } };
 
-  ctf_dynhash_t *dthash;
-  ctf_dynhash_t *dvhash;
   ctf_dynhash_t *structs = NULL, *unions = NULL, *enums = NULL, *names = NULL;
-  ctf_dynhash_t *objthash = NULL, *funchash = NULL;
   ctf_sect_t cts;
   ctf_dict_t *fp;
 
   libctf_init_debug();
-  dthash = ctf_dynhash_create (ctf_hash_integer, ctf_hash_eq_integer,
-			       NULL, NULL);
-  if (dthash == NULL)
-    {
-      ctf_set_open_errno (errp, EAGAIN);
-      goto err;
-    }
-
-  dvhash = ctf_dynhash_create (ctf_hash_string, ctf_hash_eq_string,
-			       NULL, NULL);
-  if (dvhash == NULL)
-    {
-      ctf_set_open_errno (errp, EAGAIN);
-      goto err_dt;
-    }
 
   structs = ctf_dynhash_create (ctf_hash_string, ctf_hash_eq_string,
 				NULL, NULL);
@@ -136,14 +122,10 @@  ctf_create (int *errp)
 			      NULL, NULL);
   names = ctf_dynhash_create (ctf_hash_string, ctf_hash_eq_string,
 			      NULL, NULL);
-  objthash = ctf_dynhash_create (ctf_hash_string, ctf_hash_eq_string,
-				 free, NULL);
-  funchash = ctf_dynhash_create (ctf_hash_string, ctf_hash_eq_string,
-				 free, NULL);
   if (!structs || !unions || !enums || !names)
     {
       ctf_set_open_errno (errp, EAGAIN);
-      goto err_dv;
+      goto err;
     }
 
   cts.cts_name = _CTF_SECTION;
@@ -151,24 +133,26 @@  ctf_create (int *errp)
   cts.cts_size = sizeof (hdr);
   cts.cts_entsize = 1;
 
-  if ((fp = ctf_bufopen_internal (&cts, NULL, NULL, NULL, 1, errp)) == NULL)
-    goto err_dv;
+  if ((fp = ctf_bufopen_internal (&cts, NULL, NULL, NULL, errp)) == NULL)
+    goto err;
 
+  /* These hashes will have been initialized with a starting size of zero,
+     which is surely wrong.  Use ones with slightly larger sizes.  */
+  ctf_dynhash_destroy (fp->ctf_structs);
+  ctf_dynhash_destroy (fp->ctf_unions);
+  ctf_dynhash_destroy (fp->ctf_enums);
+  ctf_dynhash_destroy (fp->ctf_names);
   fp->ctf_structs = structs;
   fp->ctf_unions = unions;
   fp->ctf_enums = enums;
   fp->ctf_names = names;
-  fp->ctf_objthash = objthash;
-  fp->ctf_funchash = funchash;
-  fp->ctf_dthash = dthash;
-  fp->ctf_dvhash = dvhash;
   fp->ctf_dtoldid = 0;
-  fp->ctf_snapshots = 1;
   fp->ctf_snapshot_lu = 0;
   fp->ctf_flags |= LCTF_DIRTY;
 
+  /* Make sure the ptrtab starts out at a reasonable size.  */
+
   ctf_set_ctl_hashes (fp);
-  ctf_setmodel (fp, CTF_MODEL_NATIVE);
   if (ctf_grow_ptrtab (fp) < 0)
     {
       ctf_set_open_errno (errp, ctf_errno (fp));
@@ -178,17 +162,11 @@  ctf_create (int *errp)
 
   return fp;
 
- err_dv:
+ err:
   ctf_dynhash_destroy (structs);
   ctf_dynhash_destroy (unions);
   ctf_dynhash_destroy (enums);
   ctf_dynhash_destroy (names);
-  ctf_dynhash_destroy (objthash);
-  ctf_dynhash_destroy (funchash);
-  ctf_dynhash_destroy (dvhash);
- err_dt:
-  ctf_dynhash_destroy (dthash);
- err:
   return NULL;
 }
 
@@ -196,9 +174,6 @@  ctf_create (int *errp)
 int
 ctf_update (ctf_dict_t *fp)
 {
-  if (!(fp->ctf_flags & LCTF_RDWR))
-    return (ctf_set_errno (fp, ECTF_RDONLY));
-
   fp->ctf_dtoldid = fp->ctf_typemax;
   return 0;
 }
@@ -310,9 +285,6 @@  ctf_dynamic_type (const ctf_dict_t *fp, ctf_id_t id)
 {
   ctf_id_t idx;
 
-  if (!(fp->ctf_flags & LCTF_RDWR))
-    return NULL;
-
   if ((fp->ctf_flags & LCTF_CHILD) && LCTF_TYPE_ISPARENT (fp, id))
     fp = fp->ctf_parent;
 
@@ -323,6 +295,19 @@  ctf_dynamic_type (const ctf_dict_t *fp, ctf_id_t id)
   return NULL;
 }
 
+static int
+ctf_static_type (const ctf_dict_t *fp, ctf_id_t id)
+{
+  ctf_id_t idx;
+
+  if ((fp->ctf_flags & LCTF_CHILD) && LCTF_TYPE_ISPARENT (fp, id))
+    fp = fp->ctf_parent;
+
+  idx = LCTF_TYPE_TO_INDEX(fp, id);
+
+  return ((unsigned long) idx <= fp->ctf_stypes);
+}
+
 int
 ctf_dvd_insert (ctf_dict_t *fp, ctf_dvdef_t *dvd)
 {
@@ -385,7 +370,7 @@  ctf_rollback (ctf_dict_t *fp, ctf_snapshot_id_t id)
   ctf_dtdef_t *dtd, *ntd;
   ctf_dvdef_t *dvd, *nvd;
 
-  if (!(fp->ctf_flags & LCTF_RDWR))
+  if (id.snapshot_id < fp->ctf_stypes)
     return (ctf_set_errno (fp, ECTF_RDONLY));
 
   if (fp->ctf_snapshot_lu >= id.snapshot_id)
@@ -449,15 +434,25 @@  ctf_add_generic (ctf_dict_t *fp, uint32_t flag, const char *name, int kind,
   if (flag != CTF_ADD_NONROOT && flag != CTF_ADD_ROOT)
     return (ctf_set_typed_errno (fp, EINVAL));
 
-  if (!(fp->ctf_flags & LCTF_RDWR))
-    return (ctf_set_typed_errno (fp, ECTF_RDONLY));
-
   if (LCTF_INDEX_TO_TYPE (fp, fp->ctf_typemax, 1) >= CTF_MAX_TYPE)
     return (ctf_set_typed_errno (fp, ECTF_FULL));
 
   if (LCTF_INDEX_TO_TYPE (fp, fp->ctf_typemax, 1) == (CTF_MAX_PTYPE - 1))
     return (ctf_set_typed_errno (fp, ECTF_FULL));
 
+  /* Prohibit addition of a root-visible type that is already present
+     in the non-dynamic portion. */
+
+  if (flag == CTF_ADD_ROOT && name != NULL && name[0] != '\0')
+    {
+      ctf_id_t existing;
+
+      if (((existing = ctf_dynhash_lookup_type (ctf_name_table (fp, kind),
+						name)) > 0)
+	  && ctf_static_type (fp, existing))
+	return (ctf_set_typed_errno (fp, ECTF_RDONLY));
+    }
+
   /* Make sure ptrtab always grows to be big enough for all types.  */
   if (ctf_grow_ptrtab (fp) < 0)
       return CTF_ERR;				/* errno is set for us. */
@@ -724,10 +719,9 @@  ctf_set_array (ctf_dict_t *fp, ctf_id_t type, const ctf_arinfo_t *arp)
   if ((fp->ctf_flags & LCTF_CHILD) && LCTF_TYPE_ISPARENT (fp, type))
     fp = fp->ctf_parent;
 
-  if (!(ofp->ctf_flags & LCTF_RDWR))
-    return (ctf_set_errno (ofp, ECTF_RDONLY));
-
-  if (!(fp->ctf_flags & LCTF_RDWR))
+  /* You can only call ctf_set_array on a type you have added, not a
+     type that was read in via ctf_open().  */
+  if (type < fp->ctf_stypes)
     return (ctf_set_errno (ofp, ECTF_RDONLY));
 
   if (dtd == NULL
@@ -755,9 +749,6 @@  ctf_add_function (ctf_dict_t *fp, uint32_t flag,
   size_t initial_vlen;
   size_t i;
 
-  if (!(fp->ctf_flags & LCTF_RDWR))
-    return (ctf_set_typed_errno (fp, ECTF_RDONLY));
-
   if (ctc == NULL || (ctc->ctc_flags & ~CTF_FUNC_VARARG) != 0
       || (ctc->ctc_argc != 0 && argv == NULL))
     return (ctf_set_typed_errno (fp, EINVAL));
@@ -813,6 +804,10 @@  ctf_add_struct_sized (ctf_dict_t *fp, uint32_t flag, const char *name,
   if (name != NULL)
     type = ctf_lookup_by_rawname (fp, CTF_K_STRUCT, name);
 
+  /* Prohibit promotion if this type was ctf_open()ed.  */
+  if (type > 0 && type < fp->ctf_stypes)
+    return (ctf_set_errno (fp, ECTF_RDONLY));
+
   if (type != 0 && ctf_type_kind (fp, type) == CTF_K_FORWARD)
     dtd = ctf_dtd_lookup (fp, type);
   else if ((type = ctf_add_generic (fp, flag, name, CTF_K_STRUCT,
@@ -853,11 +848,15 @@  ctf_add_union_sized (ctf_dict_t *fp, uint32_t flag, const char *name,
   if (name != NULL)
     type = ctf_lookup_by_rawname (fp, CTF_K_UNION, name);
 
+  /* Prohibit promotion if this type was ctf_open()ed.  */
+  if (type > 0 && type < fp->ctf_stypes)
+    return (ctf_set_errno (fp, ECTF_RDONLY));
+
   if (type != 0 && ctf_type_kind (fp, type) == CTF_K_FORWARD)
     dtd = ctf_dtd_lookup (fp, type);
   else if ((type = ctf_add_generic (fp, flag, name, CTF_K_UNION,
 				    initial_vlen, &dtd)) == CTF_ERR)
-    return CTF_ERR;		/* errno is set for us */
+    return CTF_ERR;		/* errno is set for us.  */
 
   /* Forwards won't have any vlen yet.  */
   if (dtd->dtd_vlen_alloc == 0)
@@ -892,6 +891,10 @@  ctf_add_enum (ctf_dict_t *fp, uint32_t flag, const char *name)
   if (name != NULL)
     type = ctf_lookup_by_rawname (fp, CTF_K_ENUM, name);
 
+  /* Prohibit promotion if this type was ctf_open()ed.  */
+  if (type > 0 && type < fp->ctf_stypes)
+    return (ctf_set_errno (fp, ECTF_RDONLY));
+
   if (type != 0 && ctf_type_kind (fp, type) == CTF_K_FORWARD)
     dtd = ctf_dtd_lookup (fp, type);
   else if ((type = ctf_add_generic (fp, flag, name, CTF_K_ENUM,
@@ -953,8 +956,9 @@  ctf_add_forward (ctf_dict_t *fp, uint32_t flag, const char *name,
   if (name == NULL || name[0] == '\0')
     return (ctf_set_typed_errno (fp, ECTF_NONAME));
 
-  /* If the type is already defined or exists as a forward tag, just
-     return the ctf_id_t of the existing definition.  */
+  /* If the type is already defined or exists as a forward tag, just return
+     the ctf_id_t of the existing definition.  Since this changes nothing,
+     it's safe to do even on the read-only portion of the dict.  */
 
   type = ctf_lookup_by_rawname (fp, kind, name);
 
@@ -1066,10 +1070,7 @@  ctf_add_enumerator (ctf_dict_t *fp, ctf_id_t enid, const char *name,
   if ((fp->ctf_flags & LCTF_CHILD) && LCTF_TYPE_ISPARENT (fp, enid))
     fp = fp->ctf_parent;
 
-  if (!(ofp->ctf_flags & LCTF_RDWR))
-    return (ctf_set_errno (ofp, ECTF_RDONLY));
-
-  if (!(fp->ctf_flags & LCTF_RDWR))
+  if (enid < fp->ctf_stypes)
     return (ctf_set_errno (ofp, ECTF_RDONLY));
 
   if (dtd == NULL)
@@ -1142,10 +1143,7 @@  ctf_add_member_offset (ctf_dict_t *fp, ctf_id_t souid, const char *name,
       fp = fp->ctf_parent;
     }
 
-  if (!(ofp->ctf_flags & LCTF_RDWR))
-    return (ctf_set_errno (ofp, ECTF_RDONLY));
-
-  if (!(fp->ctf_flags & LCTF_RDWR))
+  if (souid < fp->ctf_stypes)
     return (ctf_set_errno (ofp, ECTF_RDONLY));
 
   if (dtd == NULL)
@@ -1332,18 +1330,15 @@  ctf_add_member (ctf_dict_t *fp, ctf_id_t souid, const char *name,
   return ctf_add_member_offset (fp, souid, name, type, (unsigned long) - 1);
 }
 
+/* Add a variable regardless of whether or not it is already present.
+
+   Internal use only.  */
 int
-ctf_add_variable (ctf_dict_t *fp, const char *name, ctf_id_t ref)
+ctf_add_variable_forced (ctf_dict_t *fp, const char *name, ctf_id_t ref)
 {
   ctf_dvdef_t *dvd;
   ctf_dict_t *tmp = fp;
 
-  if (!(fp->ctf_flags & LCTF_RDWR))
-    return (ctf_set_errno (fp, ECTF_RDONLY));
-
-  if (ctf_dvd_lookup (fp, name) != NULL)
-    return (ctf_set_errno (fp, ECTF_DUPLICATE));
-
   if (ctf_lookup_by_id (&tmp, ref) == NULL)
     return -1;			/* errno is set for us.  */
 
@@ -1375,21 +1370,30 @@  ctf_add_variable (ctf_dict_t *fp, const char *name, ctf_id_t ref)
 }
 
 int
-ctf_add_funcobjt_sym (ctf_dict_t *fp, int is_function, const char *name, ctf_id_t id)
+ctf_add_variable (ctf_dict_t *fp, const char *name, ctf_id_t ref)
+{
+  if (ctf_lookup_variable_here (fp, name) != CTF_ERR)
+    return (ctf_set_errno (fp, ECTF_DUPLICATE));
+
+  if (ctf_errno (fp) != ECTF_NOTYPEDAT)
+    return -1;				/* errno is set for us.  */
+
+  return ctf_add_variable_forced (fp, name, ref);
+}
+
+/* Add a function or object symbol regardless of whether or not it is already
+   present (already existing symbols are silently overwritten).
+
+   Internal use only.  */
+int
+ctf_add_funcobjt_sym_forced (ctf_dict_t *fp, int is_function, const char *name, ctf_id_t id)
 {
   ctf_dict_t *tmp = fp;
   char *dupname;
   ctf_dynhash_t *h = is_function ? fp->ctf_funchash : fp->ctf_objthash;
 
-  if (!(fp->ctf_flags & LCTF_RDWR))
-    return (ctf_set_errno (fp, ECTF_RDONLY));
-
-  if (ctf_dynhash_lookup (fp->ctf_objthash, name) != NULL ||
-      ctf_dynhash_lookup (fp->ctf_funchash, name) != NULL)
-    return (ctf_set_errno (fp, ECTF_DUPLICATE));
-
   if (ctf_lookup_by_id (&tmp, id) == NULL)
-    return -1;                                  /* errno is set for us.  */
+    return -1;				/* errno is set for us.  */
 
   if (is_function && ctf_type_kind (fp, id) != CTF_K_FUNCTION)
     return (ctf_set_errno (fp, ECTF_NOTFUNC));
@@ -1405,6 +1409,15 @@  ctf_add_funcobjt_sym (ctf_dict_t *fp, int is_function, const char *name, ctf_id_
   return 0;
 }
 
+int
+ctf_add_funcobjt_sym (ctf_dict_t *fp, int is_function, const char *name, ctf_id_t id)
+{
+  if (ctf_lookup_by_sym_or_name (fp, 0, name, 0, is_function) != CTF_ERR)
+    return (ctf_set_errno (fp, ECTF_DUPLICATE));
+
+  return ctf_add_funcobjt_sym_forced (fp, is_function, name, id);
+}
+
 int
 ctf_add_objt_sym (ctf_dict_t *fp, const char *name, ctf_id_t id)
 {
@@ -1606,9 +1619,6 @@  ctf_add_type_internal (ctf_dict_t *dst_fp, ctf_dict_t *src_fp, ctf_id_t src_type
 
   ctf_id_t orig_src_type = src_type;
 
-  if (!(dst_fp->ctf_flags & LCTF_RDWR))
-    return (ctf_set_typed_errno (dst_fp, ECTF_RDONLY));
-
   if ((src_tp = ctf_lookup_by_id (&src_fp, src_type)) == NULL)
     return (ctf_set_typed_errno (dst_fp, ctf_errno (src_fp)));
 
diff --git a/libctf/ctf-impl.h b/libctf/ctf-impl.h
index 8cbb2ae8242..f4fa3234681 100644
--- a/libctf/ctf-impl.h
+++ b/libctf/ctf-impl.h
@@ -369,7 +369,8 @@  struct ctf_dict
   ctf_sect_t ctf_symtab;	    /* Symbol table from object file.  */
   ctf_sect_t ctf_strtab;	    /* String table from object file.  */
   int ctf_symsect_little_endian;    /* Endianness of the ctf_symtab.  */
-  ctf_dynhash_t *ctf_symhash;       /* (partial) hash, symsect name -> idx. */
+  ctf_dynhash_t *ctf_symhash_func;  /* (partial) hash, symsect name -> idx. */
+  ctf_dynhash_t *ctf_symhash_objt;  /* ditto, for object symbols.  */
   size_t ctf_symhash_latest;	    /* Amount of symsect scanned so far.  */
   ctf_dynhash_t *ctf_prov_strtab;   /* Maps provisional-strtab offsets
 				       to names.  */
@@ -406,8 +407,8 @@  struct ctf_dict
   uint32_t *ctf_funcidx_sxlate;	  /* Offsets into funcinfo for a given funcidx.  */
   uint32_t *ctf_objtidx_sxlate;	  /* Likewise, for ctf_objtidx.  */
   size_t ctf_nobjtidx;		  /* Number of objtidx entries.  */
-  ctf_dynhash_t *ctf_objthash;	  /* name -> type ID.  */
-  ctf_dynhash_t *ctf_funchash;	  /* name -> CTF_K_FUNCTION type ID.  */
+  ctf_dynhash_t *ctf_objthash;	  /* Dynamic: name -> type ID.  */
+  ctf_dynhash_t *ctf_funchash;	  /* Dynamic: name -> CTF_K_FUNCTION type ID.  */
 
   /* The next three are linker-derived state found in ctf_link targets only.  */
 
@@ -418,6 +419,7 @@  struct ctf_dict
   struct ctf_varent *ctf_vars;	  /* Sorted variable->type mapping.  */
   unsigned long ctf_nvars;	  /* Number of variables in ctf_vars.  */
   unsigned long ctf_typemax;	  /* Maximum valid type ID number.  */
+  unsigned long ctf_stypes;	  /* Number of static (non-dynamic) types.  */
   const ctf_dmodel_t *ctf_dmodel; /* Data model pointer (see above).  */
   const char *ctf_cuname;	  /* Compilation unit name (if any).  */
   char *ctf_dyncuname;		  /* Dynamically allocated name of CU.  */
@@ -575,7 +577,7 @@  struct ctf_next
 					   (id))
 
 #define LCTF_INDEX_TO_TYPEPTR(fp, i) \
-    ((fp->ctf_flags & LCTF_RDWR) ?					\
+  ((i > fp->ctf_stypes) ?							\
      &(ctf_dtd_lookup (fp, LCTF_INDEX_TO_TYPE				\
 		       (fp, i, fp->ctf_flags & LCTF_CHILD))->dtd_data) : \
      (ctf_type_t *)((uintptr_t)(fp)->ctf_buf + (fp)->ctf_txlate[(i)]))
@@ -587,14 +589,19 @@  struct ctf_next
   ((fp)->ctf_dictops->ctfo_get_vbytes(fp, kind, size, vlen))
 
 #define LCTF_CHILD	0x0001	/* CTF dict is a child.  */
-#define LCTF_RDWR	0x0002	/* CTF dict is writable.  */
-#define LCTF_DIRTY	0x0004	/* CTF dict has been modified.  */
-#define LCTF_LINKING	0x0008  /* CTF link is underway: respect ctf_link_flags.  */
+#define LCTF_DIRTY	0x0002	/* CTF dict has been modified.  */
+#define LCTF_LINKING	0x0004  /* CTF link is underway: respect ctf_link_flags.  */
 
 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);
+extern ctf_id_t ctf_lookup_variable_here (ctf_dict_t *fp, const char *name);
+extern ctf_id_t ctf_lookup_by_sym_or_name (ctf_dict_t *, unsigned long symidx,
+					   const char *symname, int try_parent,
+					   int is_function);
 extern ctf_id_t ctf_lookup_by_rawname (ctf_dict_t *, int, const char *);
 extern void ctf_set_ctl_hashes (ctf_dict_t *);
+extern ctf_id_t ctf_symbol_next_static (ctf_dict_t *, ctf_next_t **,
+					const char **, int);
 
 extern int ctf_symtab_skippable (ctf_link_sym_t *sym);
 extern int ctf_add_funcobjt_sym (ctf_dict_t *, int is_function,
@@ -690,6 +697,9 @@  extern ctf_id_t ctf_add_encoded (ctf_dict_t *, uint32_t, const char *,
 				 const ctf_encoding_t *, uint32_t kind);
 extern ctf_id_t ctf_add_reftype (ctf_dict_t *, uint32_t, ctf_id_t,
 				 uint32_t kind);
+extern int ctf_add_variable_forced (ctf_dict_t *, const char *, ctf_id_t);
+extern int ctf_add_funcobjt_sym_forced (ctf_dict_t *, int is_function,
+					const char *, ctf_id_t);
 
 extern int ctf_dedup_atoms_init (ctf_dict_t *);
 extern int ctf_dedup (ctf_dict_t *, ctf_dict_t **, uint32_t ninputs,
@@ -741,10 +751,10 @@  extern int ctf_flip (ctf_dict_t *, ctf_header_t *, unsigned char *, int);
 extern ctf_dict_t *ctf_simple_open_internal (const char *, size_t, const char *,
 					     size_t, size_t,
 					     const char *, size_t,
-					     ctf_dynhash_t *, int, int *);
+					     ctf_dynhash_t *, int *);
 extern ctf_dict_t *ctf_bufopen_internal (const ctf_sect_t *, const ctf_sect_t *,
 					 const ctf_sect_t *, ctf_dynhash_t *,
-					 int, int *);
+					 int *);
 extern int ctf_import_unref (ctf_dict_t *fp, ctf_dict_t *pfp);
 extern int ctf_serialize (ctf_dict_t *);
 
diff --git a/libctf/ctf-link.c b/libctf/ctf-link.c
index 360bc1a0e63..9d2d29416d3 100644
--- a/libctf/ctf-link.c
+++ b/libctf/ctf-link.c
@@ -1576,6 +1576,8 @@  ctf_link_intern_extern_string (void *key _libctf_unused_, void *value,
 /* Repeatedly call ADD_STRING to acquire strings from the external string table,
    adding them to the atoms table for this CU and all subsidiary CUs.
 
+   Must be called on a dict that has not yet been serialized.
+
    If ctf_link is also called, it must be called first if you want the new CTF
    files ctf_link can create to get their strings dedupped against the ELF
    strtab properly.  */
@@ -1587,6 +1589,9 @@  ctf_link_add_strtab (ctf_dict_t *fp, ctf_link_strtab_string_f *add_string,
   uint32_t offset;
   int err = 0;
 
+  if (fp->ctf_stypes > 0)
+    return ctf_set_errno (fp, ECTF_RDONLY);
+
   while ((str = add_string (&offset, arg)) != NULL)
     {
       ctf_link_out_string_cb_arg_t iter_arg = { str, offset, 0 };
@@ -1610,7 +1615,8 @@  ctf_link_add_strtab (ctf_dict_t *fp, ctf_link_strtab_string_f *add_string,
 /* Inform the ctf-link machinery of a new symbol in the target symbol table
    (which must be some symtab that is not usually stripped, and which
    is in agreement with ctf_bfdopen_ctfsect).  May be called either before or
-   after ctf_link_add_strtab.  */
+   after ctf_link_add_strtab.  As with that function, must be called on a dict which
+   has not yet been serialized.  */
 int
 ctf_link_add_linker_symbol (ctf_dict_t *fp, ctf_link_sym_t *sym)
 {
@@ -1625,6 +1631,9 @@  ctf_link_add_linker_symbol (ctf_dict_t *fp, ctf_link_sym_t *sym)
   if (ctf_errno (fp) == ENOMEM)
     return -ENOMEM;				/* errno is set for us.  */
 
+  if (fp->ctf_stypes > 0)
+    return ctf_set_errno (fp, ECTF_RDONLY);
+
   if (ctf_symtab_skippable (sym))
     return 0;
 
@@ -1660,6 +1669,9 @@  ctf_link_shuffle_syms (ctf_dict_t *fp)
   int err = ENOMEM;
   void *name_, *sym_;
 
+  if (fp->ctf_stypes > 0)
+    return ctf_set_errno (fp, ECTF_RDONLY);
+
   if (!fp->ctf_dynsyms)
     {
       fp->ctf_dynsyms = ctf_dynhash_create (ctf_hash_string,
diff --git a/libctf/ctf-lookup.c b/libctf/ctf-lookup.c
index b5d2637fe01..1fcbebee2d1 100644
--- a/libctf/ctf-lookup.c
+++ b/libctf/ctf-lookup.c
@@ -329,7 +329,7 @@  ctf_lookup_by_name (ctf_dict_t *fp, const char *name)
 const ctf_type_t *
 ctf_lookup_by_id (ctf_dict_t **fpp, ctf_id_t type)
 {
-  ctf_dict_t *fp = *fpp;	/* Caller passes in starting CTF dict.  */
+  ctf_dict_t *fp = *fpp;
   ctf_id_t idx;
 
   if ((fp = ctf_get_dict (fp, type)) == NULL)
@@ -338,27 +338,10 @@  ctf_lookup_by_id (ctf_dict_t **fpp, ctf_id_t type)
       return NULL;
     }
 
-  /* If this dict is writable, check for a dynamic type.  */
-
-  if (fp->ctf_flags & LCTF_RDWR)
-    {
-      ctf_dtdef_t *dtd;
-
-      if ((dtd = ctf_dynamic_type (fp, type)) != NULL)
-	{
-	  *fpp = fp;
-	  return &dtd->dtd_data;
-	}
-      (void) ctf_set_errno (*fpp, ECTF_BADID);
-      return NULL;
-    }
-
-  /* Check for a type in the static portion.  */
-
   idx = LCTF_TYPE_TO_INDEX (fp, type);
   if (idx > 0 && (unsigned long) idx <= fp->ctf_typemax)
     {
-      *fpp = fp;		/* Function returns ending CTF dict.  */
+      *fpp = fp;		/* Possibly the parent CTF dict.  */
       return (LCTF_INDEX_TO_TYPEPTR (fp, idx));
     }
 
@@ -384,36 +367,52 @@  ctf_lookup_var (const void *key_, const void *lookup_)
   return (strcmp (key->clik_name, ctf_strptr (key->clik_fp, lookup->ctv_name)));
 }
 
-/* Given a variable name, return the type of the variable with that name.  */
+/* Given a variable name, return the type of the variable with that name.
+   Look only in this dict, not in the parent. */
 
 ctf_id_t
-ctf_lookup_variable (ctf_dict_t *fp, const char *name)
+ctf_lookup_variable_here (ctf_dict_t *fp, const char *name)
 {
+  ctf_dvdef_t *dvd = ctf_dvd_lookup (fp, name);
   ctf_varent_t *ent;
   ctf_lookup_idx_key_t key = { fp, name, NULL };
 
+  if (dvd != NULL)
+    return dvd->dvd_type;
+
   /* This array is sorted, so we can bsearch for it.  */
 
   ent = bsearch (&key, fp->ctf_vars, fp->ctf_nvars, sizeof (ctf_varent_t),
 		 ctf_lookup_var);
 
   if (ent == NULL)
-    {
-      if (fp->ctf_parent != NULL)
-        {
-          ctf_id_t ptype;
-
-          if ((ptype = ctf_lookup_variable (fp->ctf_parent, name)) != CTF_ERR)
-            return ptype;
-          return (ctf_set_typed_errno (fp, ctf_errno (fp->ctf_parent)));
-        }
-
       return (ctf_set_typed_errno (fp, ECTF_NOTYPEDAT));
-    }
 
   return ent->ctv_type;
 }
 
+/* As above, but look in the parent too.  */
+
+ctf_id_t
+ctf_lookup_variable (ctf_dict_t *fp, const char *name)
+{
+  ctf_id_t type;
+
+  if ((type = ctf_lookup_variable_here (fp, name)) == CTF_ERR)
+    {
+      if (ctf_errno (fp) == ECTF_NOTYPEDAT && fp->ctf_parent != NULL)
+	{
+	  if ((type = ctf_lookup_variable_here (fp->ctf_parent, name)) != CTF_ERR)
+	    return type;
+	  return (ctf_set_typed_errno (fp, ctf_errno (fp->ctf_parent)));
+	}
+
+      return -1;				/* errno is set for us.  */
+    }
+
+  return type;
+}
+
 typedef struct ctf_symidx_sort_arg_cb
 {
   ctf_dict_t *fp;
@@ -535,9 +534,11 @@  ctf_lookup_symbol_name (ctf_dict_t *fp, unsigned long symidx)
 }
 
 /* Given a symbol name, return the index of that symbol, or -1 on error or if
-   not found.  */
+   not found.  If is_function is >= 0, return only function or data object
+   symbols, respectively.  */
 static unsigned long
-ctf_lookup_symbol_idx (ctf_dict_t *fp, const char *symname)
+ctf_lookup_symbol_idx (ctf_dict_t *fp, const char *symname, int try_parent,
+		       int is_function)
 {
   const ctf_sect_t *sp = &fp->ctf_symtab;
   ctf_link_sym_t sym;
@@ -551,7 +552,9 @@  ctf_lookup_symbol_idx (ctf_dict_t *fp, const char *symname)
 
       ctf_link_sym_t *symp;
 
-      if ((symp = ctf_dynhash_lookup (fp->ctf_dynsyms, symname)) == NULL)
+      if (((symp = ctf_dynhash_lookup (fp->ctf_dynsyms, symname)) == NULL)
+	  || (symp->st_type != STT_OBJECT && is_function == 0)
+	  || (symp->st_type != STT_FUNC && is_function == 1))
 	goto try_parent;
 
       return symp->st_symidx;
@@ -562,22 +565,33 @@  ctf_lookup_symbol_idx (ctf_dict_t *fp, const char *symname)
     goto try_parent;
 
   /* First, try a hash lookup to see if we have already spotted this symbol
-     during a past iteration: create the hash first if need be.  The lifespan
-     of the strings is equal to the lifespan of the cts_data, so we don't
-     need to strdup them.  If this dict was opened as part of an archive,
-     and this archive has designed a crossdict_cache to cache results that
+     during a past iteration: create the hash first if need be.  The
+     lifespan of the strings is equal to the lifespan of the cts_data, so we
+     don't need to strdup them.  If this dict was opened as part of an
+     archive, and this archive has a crossdict_cache to cache results that
      are the same across all dicts in an archive, use it.  */
 
   if (fp->ctf_archive && fp->ctf_archive->ctfi_crossdict_cache)
     cache = fp->ctf_archive->ctfi_crossdict_cache;
 
-  if (!cache->ctf_symhash)
-    if ((cache->ctf_symhash = ctf_dynhash_create (ctf_hash_string,
-						  ctf_hash_eq_string,
-						  NULL, NULL)) == NULL)
+  if (!cache->ctf_symhash_func)
+    if ((cache->ctf_symhash_func = ctf_dynhash_create (ctf_hash_string,
+						       ctf_hash_eq_string,
+						       NULL, NULL)) == NULL)
       goto oom;
 
-  if (ctf_dynhash_lookup_kv (cache->ctf_symhash, symname, NULL, &known_idx))
+  if (!cache->ctf_symhash_objt)
+    if ((cache->ctf_symhash_objt = ctf_dynhash_create (ctf_hash_string,
+						       ctf_hash_eq_string,
+						       NULL, NULL)) == NULL)
+      goto oom;
+
+  if (is_function != 0 &&
+      ctf_dynhash_lookup_kv (cache->ctf_symhash_func, symname, NULL, &known_idx))
+    return (unsigned long) (uintptr_t) known_idx;
+
+  if (is_function != 1 &&
+      ctf_dynhash_lookup_kv (cache->ctf_symhash_objt, symname, NULL, &known_idx))
     return (unsigned long) (uintptr_t) known_idx;
 
   /* Hash lookup unsuccessful: linear search, populating the hashtab for later
@@ -586,21 +600,16 @@  ctf_lookup_symbol_idx (ctf_dict_t *fp, const char *symname)
   for (; cache->ctf_symhash_latest < sp->cts_size / sp->cts_entsize;
        cache->ctf_symhash_latest++)
     {
+      ctf_dynhash_t *h;
+
       switch (sp->cts_entsize)
 	{
 	case sizeof (Elf64_Sym):
 	  {
 	    Elf64_Sym *symp = (Elf64_Sym *) sp->cts_data;
+
 	    ctf_elf64_to_link_sym (fp, &sym, &symp[cache->ctf_symhash_latest],
 				   cache->ctf_symhash_latest);
-	    if (!ctf_dynhash_lookup_kv (cache->ctf_symhash, sym.st_name,
-					NULL, NULL))
-	      if (ctf_dynhash_cinsert (cache->ctf_symhash, sym.st_name,
-				       (const void *) (uintptr_t)
-				       cache->ctf_symhash_latest) < 0)
-		goto oom;
-	    if (strcmp (sym.st_name, symname) == 0)
-	      return cache->ctf_symhash_latest++;
 	  }
 	  break;
 	case sizeof (Elf32_Sym):
@@ -608,20 +617,28 @@  ctf_lookup_symbol_idx (ctf_dict_t *fp, const char *symname)
 	    Elf32_Sym *symp = (Elf32_Sym *) sp->cts_data;
 	    ctf_elf32_to_link_sym (fp, &sym, &symp[cache->ctf_symhash_latest],
 				   cache->ctf_symhash_latest);
-	    if (!ctf_dynhash_lookup_kv (cache->ctf_symhash, sym.st_name,
-					NULL, NULL))
-	      if (ctf_dynhash_cinsert (cache->ctf_symhash, sym.st_name,
-				       (const void *) (uintptr_t)
-				       cache->ctf_symhash_latest) < 0)
-		goto oom;
-	    if (strcmp (sym.st_name, symname) == 0)
-	      return cache->ctf_symhash_latest++;
+	    break;
 	  }
-	  break;
 	default:
 	  ctf_set_errno (fp, ECTF_SYMTAB);
 	  return (unsigned long) -1;
 	}
+
+      if (sym.st_type == STT_FUNC)
+	h = cache->ctf_symhash_func;
+      else if (sym.st_type == STT_OBJECT)
+	h = cache->ctf_symhash_objt;
+      else
+	continue;					/* Not of interest.  */
+
+      if (!ctf_dynhash_lookup_kv (h, sym.st_name,
+				  NULL, NULL))
+	if (ctf_dynhash_cinsert (h, sym.st_name,
+				 (const void *) (uintptr_t)
+				 cache->ctf_symhash_latest) < 0)
+	  goto oom;
+      if (strcmp (sym.st_name, symname) == 0)
+	return cache->ctf_symhash_latest++;
     }
 
   /* Searched everything, still not found.  */
@@ -629,11 +646,12 @@  ctf_lookup_symbol_idx (ctf_dict_t *fp, const char *symname)
   return (unsigned long) -1;
 
  try_parent:
-  if (fp->ctf_parent)
+  if (fp->ctf_parent && try_parent)
     {
       unsigned long psym;
 
-      if ((psym = ctf_lookup_symbol_idx (fp->ctf_parent, symname))
+      if ((psym = ctf_lookup_symbol_idx (fp->ctf_parent, symname, try_parent,
+					 is_function))
           != (unsigned long) -1)
         return psym;
 
@@ -653,12 +671,17 @@  oom:
 
 }
 
-/* Iterate over all symbols with types: if FUNC, function symbols, otherwise,
-   data symbols.  The name argument is not optional.  The return order is
-   arbitrary, though is likely to be in symbol index or name order.  You can
-   change the value of 'functions' in the middle of iteration over non-dynamic
-   dicts, but doing so on dynamic dicts will fail.  (This is probably not very
-   useful, but there is no reason to prohibit it.)  */
+ctf_id_t
+ctf_symbol_next_static (ctf_dict_t *fp, ctf_next_t **it, const char **name,
+			int functions);
+
+/* Iterate over all symbols with types: if FUNC, function symbols,
+   otherwise, data symbols.  The name argument is not optional.  The return
+   order is arbitrary, though is likely to be in symbol index or name order.
+   Changing the value of 'functions' in the middle of iteration has
+   unpredictable effects (probably skipping symbols, etc) and is not
+   recommended.  Adding symbols while iteration is underway may also lead
+   to other symbols being skipped.  */
 
 ctf_id_t
 ctf_symbol_next (ctf_dict_t *fp, ctf_next_t **it, const char **name,
@@ -685,24 +708,24 @@  ctf_symbol_next (ctf_dict_t *fp, ctf_next_t **it, const char **name,
   if (fp != i->cu.ctn_fp)
     return (ctf_set_typed_errno (fp, ECTF_NEXT_WRONGFP));
 
-  /* We intentionally use raw access, not ctf_lookup_by_symbol, to avoid
+  /* Check the dynamic set of names first, to allow previously-written names
+     to be replaced with dynamic ones (there is still no way to remove them,
+     though).
+
+     We intentionally use raw access, not ctf_lookup_by_symbol, to avoid
      incurring additional sorting cost for unsorted symtypetabs coming from the
      compiler, to allow ctf_symbol_next to work in the absence of a symtab, and
      finally because it's easier to work out what the name of each symbol is if
      we do that.  */
 
-  if (fp->ctf_flags & LCTF_RDWR)
+  ctf_dynhash_t *dynh = functions ? fp->ctf_funchash : fp->ctf_objthash;
+  void *dyn_name = NULL, *dyn_value = NULL;
+  size_t dyn_els = dynh ? ctf_dynhash_elements (dynh) : 0;
+
+  if (i->ctn_n < dyn_els)
     {
-      ctf_dynhash_t *dynh = functions ? fp->ctf_funchash : fp->ctf_objthash;
-      void *dyn_name = NULL, *dyn_value = NULL;
-
-      if (!dynh)
-	{
-	  ctf_next_destroy (i);
-	  return (ctf_set_typed_errno (fp, ECTF_NEXT_END));
-	}
-
       err = ctf_dynhash_next (dynh, &i->ctn_next, &dyn_name, &dyn_value);
+
       /* This covers errors and also end-of-iteration.  */
       if (err != 0)
 	{
@@ -713,9 +736,50 @@  ctf_symbol_next (ctf_dict_t *fp, ctf_next_t **it, const char **name,
 
       *name = dyn_name;
       sym = (ctf_id_t) (uintptr_t) dyn_value;
+      i->ctn_n++;
+
+      return sym;
     }
-  else if ((!functions && fp->ctf_objtidx_names) ||
-	   (functions && fp->ctf_funcidx_names))
+
+  return ctf_symbol_next_static (fp, it, name, functions);
+}
+
+/* ctf_symbol_next, but only for static symbols.  Mostly an internal
+   implementation detail of ctf_symbol_next, but also used to simplify
+   serialization.  */
+ctf_id_t
+ctf_symbol_next_static (ctf_dict_t *fp, ctf_next_t **it, const char **name,
+			int functions)
+{
+  ctf_id_t sym = CTF_ERR;
+  ctf_next_t *i = *it;
+  ctf_dynhash_t *dynh = functions ? fp->ctf_funchash : fp->ctf_objthash;
+  size_t dyn_els = dynh ? ctf_dynhash_elements (dynh) : 0;
+
+  /* Only relevant for direct internal-to-library calls, not via
+     ctf_symbol_next (but important then).  */
+
+  if (!i)
+    {
+      if ((i = ctf_next_create ()) == NULL)
+	return ctf_set_typed_errno (fp, ENOMEM);
+
+      i->cu.ctn_fp = fp;
+      i->ctn_iter_fun = (void (*) (void)) ctf_symbol_next;
+      i->ctn_n = dyn_els;
+      *it = i;
+    }
+
+  if ((void (*) (void)) ctf_symbol_next != i->ctn_iter_fun)
+    return (ctf_set_typed_errno (fp, ECTF_NEXT_WRONGFUN));
+
+  if (fp != i->cu.ctn_fp)
+    return (ctf_set_typed_errno (fp, ECTF_NEXT_WRONGFP));
+
+  /* TODO-v4: Indexed after non-indexed portions?  */
+
+  if ((!functions && fp->ctf_objtidx_names) ||
+      (functions && fp->ctf_funcidx_names))
     {
       ctf_header_t *hp = fp->ctf_header;
       uint32_t *idx = functions ? fp->ctf_funcidx_names : fp->ctf_objtidx_names;
@@ -735,48 +799,51 @@  ctf_symbol_next (ctf_dict_t *fp, ctf_next_t **it, const char **name,
 
       do
 	{
-	  if (i->ctn_n >= len)
+	  if (i->ctn_n - dyn_els >= len)
 	    goto end;
 
-	  *name = ctf_strptr (fp, idx[i->ctn_n]);
-	  sym = tab[i->ctn_n++];
+	  *name = ctf_strptr (fp, idx[i->ctn_n - dyn_els]);
+	  sym = tab[i->ctn_n - dyn_els];
+	  i->ctn_n++;
 	}
       while (sym == -1u || sym == 0);
     }
   else
     {
-      /* Skip over pads in ctf_xslate, padding for typeless symbols in the
+      /* Skip over pads in ctf_sxlate, padding for typeless symbols in the
 	 symtypetab itself, and symbols in the wrong table.  */
-      for (; i->ctn_n < fp->ctf_nsyms; i->ctn_n++)
+      for (; i->ctn_n - dyn_els < fp->ctf_nsyms; i->ctn_n++)
 	{
 	  ctf_header_t *hp = fp->ctf_header;
+	  size_t n = i->ctn_n - dyn_els;
 
-	  if (fp->ctf_sxlate[i->ctn_n] == -1u)
+	  if (fp->ctf_sxlate[n] == -1u)
 	    continue;
 
-	  sym = *(uint32_t *) ((uintptr_t) fp->ctf_buf + fp->ctf_sxlate[i->ctn_n]);
+	  sym = *(uint32_t *) ((uintptr_t) fp->ctf_buf + fp->ctf_sxlate[n]);
 
 	  if (sym == 0)
 	    continue;
 
 	  if (functions)
 	    {
-	      if (fp->ctf_sxlate[i->ctn_n] >= hp->cth_funcoff
-		  && fp->ctf_sxlate[i->ctn_n] < hp->cth_objtidxoff)
+	      if (fp->ctf_sxlate[n] >= hp->cth_funcoff
+		  && fp->ctf_sxlate[n] < hp->cth_objtidxoff)
 		break;
 	    }
 	  else
 	    {
-	      if (fp->ctf_sxlate[i->ctn_n] >= hp->cth_objtoff
-		  && fp->ctf_sxlate[i->ctn_n] < hp->cth_funcoff)
+	      if (fp->ctf_sxlate[n] >= hp->cth_objtoff
+		  && fp->ctf_sxlate[n] < hp->cth_funcoff)
 		break;
 	    }
 	}
 
-      if (i->ctn_n >= fp->ctf_nsyms)
+      if (i->ctn_n - dyn_els >= fp->ctf_nsyms)
 	goto end;
 
-      *name = ctf_lookup_symbol_name (fp, i->ctn_n++);
+      *name = ctf_lookup_symbol_name (fp, i->ctn_n - dyn_els);
+      i->ctn_n++;
     }
 
   return sym;
@@ -815,6 +882,13 @@  ctf_try_lookup_indexed (ctf_dict_t *fp, unsigned long symidx,
   if (symname == NULL)
     symname = ctf_lookup_symbol_name (fp, symidx);
 
+  /* Dynamic dict with no static portion: just return.  */
+  if (!hp)
+    {
+      ctf_dprintf ("%s not found in idx: dict is dynamic\n", symname);
+      return 0;
+    }
+
   ctf_dprintf ("Looking up type of object with symtab idx %lx or name %s in "
 	       "indexed symtypetab\n", symidx, symname);
 
@@ -887,17 +961,27 @@  ctf_try_lookup_indexed (ctf_dict_t *fp, unsigned long symidx,
    function or data object described by the corresponding entry in the symbol
    table.  We can only return symbols in read-only dicts and in dicts for which
    ctf_link_shuffle_syms has been called to assign symbol indexes to symbol
-   names.  */
+   names.
 
-static ctf_id_t
+   If try_parent is false, do not check the parent dict too.
+
+   If is_function is > -1, only look for data objects or functions in
+   particular.  */
+
+ctf_id_t
 ctf_lookup_by_sym_or_name (ctf_dict_t *fp, unsigned long symidx,
-			   const char *symname)
+			   const char *symname, int try_parent,
+			   int is_function)
 {
   const ctf_sect_t *sp = &fp->ctf_symtab;
   ctf_id_t type = 0;
   int err = 0;
 
-  /* Shuffled dynsymidx present?  Use that.  */
+  /* Shuffled dynsymidx present?  Use that.  For now, the dynsymidx and
+     shuffled-symbol lookup only support dynamically-added symbols, because
+     this interface is meant for use by linkers, and linkers are only going
+     to report symbols against newly-created, freshly-ctf_link'ed dicts: so
+     there will be no static component in any case.  */
   if (fp->ctf_dynsymidx)
     {
       const ctf_link_sym_t *sym;
@@ -909,10 +993,6 @@  ctf_lookup_by_sym_or_name (ctf_dict_t *fp, unsigned long symidx,
 	ctf_dprintf ("Looking up type of object with symtab idx %lx in "
 		     "writable dict symtypetab\n", symidx);
 
-      /* The dict must be dynamic.  */
-      if (!ctf_assert (fp, fp->ctf_flags & LCTF_RDWR))
-	return CTF_ERR;
-
       /* No name? Need to look it up.  */
       if (!symname)
 	{
@@ -922,7 +1002,9 @@  ctf_lookup_by_sym_or_name (ctf_dict_t *fp, unsigned long symidx,
 
 	  sym = fp->ctf_dynsymidx[symidx];
 	  err = ECTF_NOTYPEDAT;
-	  if (!sym || (sym->st_shndx != STT_OBJECT && sym->st_shndx != STT_FUNC))
+	  if (!sym || (sym->st_type != STT_OBJECT && sym->st_type != STT_FUNC)
+	      || (sym->st_type != STT_OBJECT && is_function == 0)
+	      || (sym->st_type != STT_FUNC && is_function == 1))
 	    goto try_parent;
 
 	  if (!ctf_assert (fp, !sym->st_nameidx_set))
@@ -931,49 +1013,55 @@  ctf_lookup_by_sym_or_name (ctf_dict_t *fp, unsigned long symidx,
      }
 
       if (fp->ctf_objthash == NULL
-	  || ((type = (ctf_id_t) (uintptr_t)
-	       ctf_dynhash_lookup (fp->ctf_objthash, symname)) == 0))
+	  || is_function == 1
+	  || (type = (ctf_id_t) (uintptr_t)
+	      ctf_dynhash_lookup (fp->ctf_objthash, symname)) == 0)
 	{
 	  if (fp->ctf_funchash == NULL
-	      || ((type = (ctf_id_t) (uintptr_t)
-		   ctf_dynhash_lookup (fp->ctf_funchash, symname)) == 0))
+	      || is_function == 0
+	      || (type = (ctf_id_t) (uintptr_t)
+		  ctf_dynhash_lookup (fp->ctf_funchash, symname)) == 0)
 	    goto try_parent;
 	}
 
       return type;
     }
 
-  /* Lookup by name in a dynamic dict: just do it directly.  */
-  if (symname && fp->ctf_flags & LCTF_RDWR)
+  /* Dict not shuffled: look for a dynamic sym first, and look it up
+     directly.  */
+  if (symname)
     {
-      if (fp->ctf_objthash == NULL
-	  || ((type = (ctf_id_t) (uintptr_t)
-	       ctf_dynhash_lookup (fp->ctf_objthash, symname)) == 0))
-	{
-	  if (fp->ctf_funchash == NULL
-	      || ((type = (ctf_id_t) (uintptr_t)
-		   ctf_dynhash_lookup (fp->ctf_funchash, symname)) == 0))
-	    goto try_parent;
-	}
-      return type;
+      if (fp->ctf_objthash != NULL
+	  && is_function != 1
+	  && ((type = (ctf_id_t) (uintptr_t)
+	       ctf_dynhash_lookup (fp->ctf_objthash, symname)) != 0))
+	return type;
+
+      if (fp->ctf_funchash != NULL
+	  && is_function != 0
+	  && ((type = (ctf_id_t) (uintptr_t)
+	       ctf_dynhash_lookup (fp->ctf_funchash, symname)) != 0))
+	return type;
     }
 
   err = ECTF_NOSYMTAB;
   if (sp->cts_data == NULL)
     goto try_parent;
 
-  /* This covers both out-of-range lookups and a dynamic dict which hasn't been
-     shuffled yet.  */
+  /* This covers both out-of-range lookups by index and a dynamic dict which
+     hasn't been shuffled yet.  */
   err = EINVAL;
   if (symname == NULL && symidx >= fp->ctf_nsyms)
     goto try_parent;
 
-  if (fp->ctf_objtidx_names)
+  /* Try an indexed lookup.  */
+
+  if (fp->ctf_objtidx_names && is_function != 1)
     {
       if ((type = ctf_try_lookup_indexed (fp, symidx, symname, 0)) == CTF_ERR)
 	return CTF_ERR;				/* errno is set for us.  */
     }
-  if (type == 0 && fp->ctf_funcidx_names)
+  if (type == 0 && fp->ctf_funcidx_names && is_function != 0)
     {
       if ((type = ctf_try_lookup_indexed (fp, symidx, symname, 1)) == CTF_ERR)
 	return CTF_ERR;				/* errno is set for us.  */
@@ -981,6 +1069,7 @@  ctf_lookup_by_sym_or_name (ctf_dict_t *fp, unsigned long symidx,
   if (type != 0)
     return type;
 
+  /* Indexed but no symbol found -> not present, try the parent.  */
   err = ECTF_NOTYPEDAT;
   if (fp->ctf_objtidx_names && fp->ctf_funcidx_names)
     goto try_parent;
@@ -990,7 +1079,8 @@  ctf_lookup_by_sym_or_name (ctf_dict_t *fp, unsigned long symidx,
   ctf_dprintf ("Looking up object type %lx in 1:1 dict symtypetab\n", symidx);
 
   if (symname != NULL)
-    if ((symidx = ctf_lookup_symbol_idx (fp, symname)) == (unsigned long) -1)
+    if ((symidx = ctf_lookup_symbol_idx (fp, symname, try_parent, is_function))
+	== (unsigned long) -1)
       goto try_parent;
 
   if (fp->ctf_sxlate[symidx] == -1u)
@@ -1002,11 +1092,16 @@  ctf_lookup_by_sym_or_name (ctf_dict_t *fp, unsigned long symidx,
     goto try_parent;
 
   return type;
+
  try_parent:
+  if (!try_parent)
+    return ctf_set_errno (fp, err);
+
   if (fp->ctf_parent)
     {
       ctf_id_t ret = ctf_lookup_by_sym_or_name (fp->ctf_parent, symidx,
-						symname);
+						symname, try_parent,
+						is_function);
       if (ret == CTF_ERR)
 	ctf_set_errno (fp, ctf_errno (fp->ctf_parent));
       return ret;
@@ -1020,7 +1115,7 @@  ctf_lookup_by_sym_or_name (ctf_dict_t *fp, unsigned long symidx,
 ctf_id_t
 ctf_lookup_by_symbol (ctf_dict_t *fp, unsigned long symidx)
 {
-  return ctf_lookup_by_sym_or_name (fp, symidx, NULL);
+  return ctf_lookup_by_sym_or_name (fp, symidx, NULL, 1, -1);
 }
 
 /* Given a symbol name, return the type of the function or data object described
@@ -1028,7 +1123,7 @@  ctf_lookup_by_symbol (ctf_dict_t *fp, unsigned long symidx)
 ctf_id_t
 ctf_lookup_by_symbol_name (ctf_dict_t *fp, const char *symname)
 {
-  return ctf_lookup_by_sym_or_name (fp, 0, symname);
+  return ctf_lookup_by_sym_or_name (fp, 0, symname, 1, -1);
 }
 
 /* Given a symbol table index, return the info for the function described
diff --git a/libctf/ctf-open.c b/libctf/ctf-open.c
index 87b0f74367a..f80bf5476a7 100644
--- a/libctf/ctf-open.c
+++ b/libctf/ctf-open.c
@@ -670,13 +670,15 @@  upgrade_types (ctf_dict_t *fp, ctf_header_t *cth)
   return 0;
 }
 
-/* Initialize the type ID translation table with the byte offset of each type,
+/* Populate statically-defined types (those loaded from a saved buffer).
+
+   Initialize the type ID translation table with the byte offset of each type,
    and initialize the hash tables of each named type.  Upgrade the type table to
    the latest supported representation in the process, if needed, and if this
    recension of libctf supports upgrading.  */
 
 static int
-init_types (ctf_dict_t *fp, ctf_header_t *cth)
+init_static_types (ctf_dict_t *fp, ctf_header_t *cth)
 {
   const ctf_type_t *tbuf;
   const ctf_type_t *tend;
@@ -694,8 +696,6 @@  init_types (ctf_dict_t *fp, ctf_header_t *cth)
   int nlstructs = 0, nlunions = 0;
   int err;
 
-  assert (!(fp->ctf_flags & LCTF_RDWR));
-
   if (_libctf_unlikely_ (fp->ctf_version == CTF_VERSION_1))
     {
       int err;
@@ -770,9 +770,16 @@  init_types (ctf_dict_t *fp, ctf_header_t *cth)
 				   ctf_hash_eq_string, NULL, NULL)) == NULL)
     return ENOMEM;
 
+  /* The ptrtab and txlate can be appropriately sized for precisely this set
+     of types: the txlate because it is only used to look up static types,
+     so dynamic types added later will never go through it, and the ptrtab
+     because later-added types will call grow_ptrtab() automatically, as
+     needed.  */
+
   fp->ctf_txlate = malloc (sizeof (uint32_t) * (typemax + 1));
   fp->ctf_ptrtab_len = typemax + 1;
   fp->ctf_ptrtab = malloc (sizeof (uint32_t) * fp->ctf_ptrtab_len);
+  fp->ctf_stypes = typemax;
 
   if (fp->ctf_txlate == NULL || fp->ctf_ptrtab == NULL)
     return ENOMEM;		/* Memory allocation failed.  */
@@ -1283,7 +1290,7 @@  ctf_dict_t *ctf_simple_open (const char *ctfsect, size_t ctfsect_size,
 {
   return ctf_simple_open_internal (ctfsect, ctfsect_size, symsect, symsect_size,
 				   symsect_entsize, strsect, strsect_size, NULL,
-				   0, errp);
+				   errp);
 }
 
 /* Open a CTF file, mocking up a suitable ctf_sect and overriding the external
@@ -1293,8 +1300,7 @@  ctf_dict_t *ctf_simple_open_internal (const char *ctfsect, size_t ctfsect_size,
 				      const char *symsect, size_t symsect_size,
 				      size_t symsect_entsize,
 				      const char *strsect, size_t strsect_size,
-				      ctf_dynhash_t *syn_strtab, int writable,
-				      int *errp)
+				      ctf_dynhash_t *syn_strtab, int *errp)
 {
   ctf_sect_t skeleton;
 
@@ -1332,7 +1338,7 @@  ctf_dict_t *ctf_simple_open_internal (const char *ctfsect, size_t ctfsect_size,
     }
 
   return ctf_bufopen_internal (ctfsectp, symsectp, strsectp, syn_strtab,
-			       writable, errp);
+			       errp);
 }
 
 /* Decode the specified CTF buffer and optional symbol table, and create a new
@@ -1344,7 +1350,7 @@  ctf_dict_t *
 ctf_bufopen (const ctf_sect_t *ctfsect, const ctf_sect_t *symsect,
 	     const ctf_sect_t *strsect, int *errp)
 {
-  return ctf_bufopen_internal (ctfsect, symsect, strsect, NULL, 0, errp);
+  return ctf_bufopen_internal (ctfsect, symsect, strsect, NULL, errp);
 }
 
 /* Like ctf_bufopen, but overriding the external strtab with a synthetic one.  */
@@ -1352,7 +1358,7 @@  ctf_bufopen (const ctf_sect_t *ctfsect, const ctf_sect_t *symsect,
 ctf_dict_t *
 ctf_bufopen_internal (const ctf_sect_t *ctfsect, const ctf_sect_t *symsect,
 		      const ctf_sect_t *strsect, ctf_dynhash_t *syn_strtab,
-		      int writable, int *errp)
+		      int *errp)
 {
   const ctf_preamble_t *pp;
   size_t hdrsz = sizeof (ctf_header_t);
@@ -1441,9 +1447,6 @@  ctf_bufopen_internal (const ctf_sect_t *ctfsect, const ctf_sect_t *symsect,
 
   memset (fp, 0, sizeof (ctf_dict_t));
 
-  if (writable)
-    fp->ctf_flags |= LCTF_RDWR;
-
   if ((fp->ctf_header = malloc (sizeof (struct ctf_header))) == NULL)
     {
       free (fp);
@@ -1526,7 +1529,7 @@  ctf_bufopen_internal (const ctf_sect_t *ctfsect, const ctf_sect_t *symsect,
      section's buffer pointer into ctf_buf, below.  */
 
   /* Note: if this is a v1 buffer, it will be reallocated and expanded by
-     init_types().  */
+     init_static_types().  */
 
   if (hp->cth_flags & CTF_F_COMPRESS)
     {
@@ -1607,7 +1610,7 @@  ctf_bufopen_internal (const ctf_sect_t *ctfsect, const ctf_sect_t *symsect,
      proceed with initializing the ctf_dict_t we allocated above.
 
      Nothing that depends on buf or base should be set directly in this function
-     before the init_types() call, because it may be reallocated during
+     before the init_static_types() call, because it may be reallocated during
      transparent upgrade if this recension of libctf is so configured: see
      ctf_set_base().  */
 
@@ -1660,6 +1663,26 @@  ctf_bufopen_internal (const ctf_sect_t *ctfsect, const ctf_sect_t *symsect,
     }
   fp->ctf_syn_ext_strtab = syn_strtab;
 
+  /* Dynamic state, for dynamic addition to this dict after loading.  */
+
+  fp->ctf_dthash = ctf_dynhash_create (ctf_hash_integer, ctf_hash_eq_integer,
+				       NULL, NULL);
+  fp->ctf_dvhash = ctf_dynhash_create (ctf_hash_string, ctf_hash_eq_string,
+				       NULL, NULL);
+  fp->ctf_snapshots = 1;
+
+  fp->ctf_objthash = ctf_dynhash_create (ctf_hash_string, ctf_hash_eq_string,
+					   free, NULL);
+  fp->ctf_funchash = ctf_dynhash_create (ctf_hash_string, ctf_hash_eq_string,
+					 free, NULL);
+
+  if (!fp->ctf_dthash || !fp->ctf_dvhash || !fp->ctf_snapshots ||
+      !fp->ctf_objthash || !fp->ctf_funchash)
+    {
+      err = ENOMEM;
+      goto bad;
+    }
+
   if (foreign_endian &&
       (err = ctf_flip (fp, hp, fp->ctf_buf, 0)) != 0)
     {
@@ -1673,15 +1696,7 @@  ctf_bufopen_internal (const ctf_sect_t *ctfsect, const ctf_sect_t *symsect,
 
   ctf_set_base (fp, hp, fp->ctf_base);
 
-  /* No need to do anything else for dynamic dicts: they do not support symbol
-     lookups, and the type table is maintained in the dthashes.  */
-  if (fp->ctf_flags & LCTF_RDWR)
-    {
-      fp->ctf_refcnt = 1;
-      return fp;
-    }
-
-  if ((err = init_types (fp, hp)) != 0)
+  if ((err = init_static_types (fp, hp)) != 0)
     goto bad;
 
   /* Allocate and initialize the symtab translation table, pointed to by
@@ -1800,7 +1815,8 @@  ctf_dict_close (ctf_dict_t *fp)
     }
   ctf_dynhash_destroy (fp->ctf_dvhash);
 
-  ctf_dynhash_destroy (fp->ctf_symhash);
+  ctf_dynhash_destroy (fp->ctf_symhash_func);
+  ctf_dynhash_destroy (fp->ctf_symhash_objt);
   free (fp->ctf_funcidx_sxlate);
   free (fp->ctf_objtidx_sxlate);
   ctf_dynhash_destroy (fp->ctf_objthash);
diff --git a/libctf/ctf-serialize.c b/libctf/ctf-serialize.c
index 511c5116140..7092264f446 100644
--- a/libctf/ctf-serialize.c
+++ b/libctf/ctf-serialize.c
@@ -945,7 +945,12 @@  ctf_sort_var (const void *one_, const void *two_, void *arg_)
    code simple: ctf_simple_open_internal() will return a new ctf_dict_t, but we
    want to keep the fp constant for the caller, so after
    ctf_simple_open_internal() returns, we use memcpy to swap the interior of the
-   old and new ctf_dict_t's, and then free the old.  */
+   old and new ctf_dict_t's, and then free the old.
+
+   We do not currently support serializing a dict that has already been
+   serialized in the past: but all the tables support it except for the types
+   table.  */
+
 int
 ctf_serialize (ctf_dict_t *fp)
 {
@@ -956,6 +961,7 @@  ctf_serialize (ctf_dict_t *fp)
   ctf_strs_writable_t strtab;
   int err;
   int num_missed_str_refs;
+  int sym_functions = 0;
 
   unsigned char *t;
   unsigned long i;
@@ -967,7 +973,11 @@  ctf_serialize (ctf_dict_t *fp)
   emit_symtypetab_state_t symstate;
   memset (&symstate, 0, sizeof (emit_symtypetab_state_t));
 
-  if (!(fp->ctf_flags & LCTF_RDWR))
+  /* This isn't a very nice error code, but it's close enough: it's what you
+     get if you try to modify a type loaded out of a serialized dict, so
+     it makes at least a little sense that it's what you get if you try to
+     reserialize the dict again.  */
+  if (fp->ctf_stypes > 0)
     return (ctf_set_errno (fp, ECTF_RDONLY));
 
   /* Update required?  */
@@ -999,10 +1009,44 @@  ctf_serialize (ctf_dict_t *fp)
      of the dynsym and dynstr these days.  */
   hdr.cth_flags = (CTF_F_NEWFUNCINFO | CTF_F_DYNSTR);
 
+  /* Propagate all symbols in the symtypetabs into the dynamic state, so that
+     we can put them back in the right order.  Symbols already in the dynamic
+     state are left as they are.  */
+  do
+    {
+      ctf_next_t *it = NULL;
+      const char *sym_name;
+      ctf_id_t sym;
+
+      while ((sym = ctf_symbol_next_static (fp, &it, &sym_name,
+					    sym_functions)) != CTF_ERR)
+	if ((ctf_add_funcobjt_sym_forced (fp, sym_functions, sym_name, sym)) < 0)
+	  if (ctf_errno (fp) != ECTF_DUPLICATE)
+	    return -1;				/* errno is set for us.  */
+
+      if (ctf_errno (fp) != ECTF_NEXT_END)
+	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 -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
+     serialization, are left unchanged.  */
+
+  for (i = 0; i < fp->ctf_nvars; i++)
+    {
+      const char *name = ctf_strptr (fp, fp->ctf_vars[i].ctv_name);
+
+      if (name != NULL && !ctf_dvd_lookup (fp, name))
+	if (ctf_add_variable_forced (fp, name, fp->ctf_vars[i].ctv_type) < 0)
+	  return -1;				/* errno is set for us.  */
+    }
+
   for (nvars = 0, dvd = ctf_list_next (&fp->ctf_dvdefs);
        dvd != NULL; dvd = ctf_list_next (dvd), nvars++);
 
@@ -1101,7 +1145,7 @@  ctf_serialize (ctf_dict_t *fp)
 
   if ((nfp = ctf_simple_open_internal ((char *) buf, buf_size, NULL, 0,
 				       0, NULL, 0, fp->ctf_syn_ext_strtab,
-				       1, &err)) == NULL)
+				       &err)) == NULL)
     {
       free (buf);
       return (ctf_set_errno (fp, err));
@@ -1131,6 +1175,7 @@  ctf_serialize (ctf_dict_t *fp)
   nfp->ctf_ptrtab = fp->ctf_ptrtab;
   nfp->ctf_pptrtab = fp->ctf_pptrtab;
   nfp->ctf_typemax = fp->ctf_typemax;
+  nfp->ctf_stypes = fp->ctf_stypes;
   nfp->ctf_dynsymidx = fp->ctf_dynsymidx;
   nfp->ctf_dynsymmax = fp->ctf_dynsymmax;
   nfp->ctf_ptrtab_len = fp->ctf_ptrtab_len;
diff --git a/libctf/ctf-types.c b/libctf/ctf-types.c
index 10bb6d1596a..ff12d51941d 100644
--- a/libctf/ctf-types.c
+++ b/libctf/ctf-types.c
@@ -492,6 +492,7 @@  ctf_id_t
 ctf_variable_next (ctf_dict_t *fp, ctf_next_t **it, const char **name)
 {
   ctf_next_t *i = *it;
+  ctf_id_t id;
 
   if ((fp->ctf_flags & LCTF_CHILD) && (fp->ctf_parent == NULL))
     return (ctf_set_typed_errno (fp, ECTF_NOPARENT));
@@ -503,8 +504,7 @@  ctf_variable_next (ctf_dict_t *fp, ctf_next_t **it, const char **name)
 
       i->cu.ctn_fp = fp;
       i->ctn_iter_fun = (void (*) (void)) ctf_variable_next;
-      if (fp->ctf_flags & LCTF_RDWR)
-	i->u.ctn_dvd = ctf_list_next (&fp->ctf_dvdefs);
+      i->u.ctn_dvd = ctf_list_next (&fp->ctf_dvdefs);
       *it = i;
     }
 
@@ -514,27 +514,21 @@  ctf_variable_next (ctf_dict_t *fp, ctf_next_t **it, const char **name)
   if (fp != i->cu.ctn_fp)
     return (ctf_set_typed_errno (fp, ECTF_NEXT_WRONGFP));
 
-  if (!(fp->ctf_flags & LCTF_RDWR))
+  if (i->ctn_n < fp->ctf_nvars)
     {
-      if (i->ctn_n >= fp->ctf_nvars)
-	goto end_iter;
-
       *name = ctf_strptr (fp, fp->ctf_vars[i->ctn_n].ctv_name);
       return fp->ctf_vars[i->ctn_n++].ctv_type;
-    }
-  else
-    {
-      ctf_id_t id;
 
-      if (i->u.ctn_dvd == NULL)
-	goto end_iter;
-
-      *name = i->u.ctn_dvd->dvd_name;
-      id = i->u.ctn_dvd->dvd_type;
-      i->u.ctn_dvd = ctf_list_next (i->u.ctn_dvd);
-      return id;
     }
 
+  if (i->u.ctn_dvd == NULL)
+    goto end_iter;
+
+  *name = i->u.ctn_dvd->dvd_name;
+  id = i->u.ctn_dvd->dvd_type;
+  i->u.ctn_dvd = ctf_list_next (i->u.ctn_dvd);
+  return id;
+
  end_iter:
   ctf_next_destroy (i);
   *it = NULL;
diff --git a/libctf/testsuite/libctf-lookup/add-to-opened-ctf.c b/libctf/testsuite/libctf-lookup/add-to-opened-ctf.c
new file mode 100644
index 00000000000..b5d483ea1cb
--- /dev/null
+++ b/libctf/testsuite/libctf-lookup/add-to-opened-ctf.c
@@ -0,0 +1,19 @@ 
+int an_int;
+char *a_char_ptr;
+typedef int (*a_typedef) (int main);
+struct struct_forward;
+enum enum_forward;
+union union_forward;
+typedef int an_array[50];
+struct a_struct { int foo; };
+union a_union { int bar; };
+enum an_enum { FOO };
+
+a_typedef a;
+struct struct_forward *x;
+union union_forward *y;
+enum enum_forward *z;
+struct a_struct *xx;
+union a_union *yy;
+enum an_enum *zz;
+an_array ar;
diff --git a/libctf/testsuite/libctf-lookup/add-to-opened.c b/libctf/testsuite/libctf-lookup/add-to-opened.c
new file mode 100644
index 00000000000..dc2e1f55b99
--- /dev/null
+++ b/libctf/testsuite/libctf-lookup/add-to-opened.c
@@ -0,0 +1,147 @@ 
+/* Make sure you can add to ctf_open()ed CTF dicts, and that you
+   cannot make changes to existing types.  */
+
+#include <ctf-api.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+int
+main (int argc, char *argv[])
+{
+  ctf_dict_t *fp;
+  ctf_archive_t *ctf;
+  ctf_id_t type, ptrtype;
+  ctf_arinfo_t ar = {0, 0, 0};
+  ctf_encoding_t en = { CTF_INT_SIGNED, 0, sizeof (int) };
+  unsigned char *ctf_written;
+  size_t size;
+  int err;
+
+  if (argc != 2)
+    {
+      fprintf (stderr, "Syntax: %s PROGRAM\n", argv[0]);
+      exit(1);
+    }
+
+  if ((ctf = ctf_open (argv[1], NULL, &err)) == NULL)
+    goto open_err;
+  if ((fp = ctf_dict_open (ctf, NULL, &err)) == NULL)
+    goto open_err;
+
+  /* Check that various modifications to already-written types
+     are prohibited.  */
+
+  if (ctf_add_integer (fp, CTF_ADD_ROOT, "int", &en) == 0)
+    fprintf (stderr, "allowed to add integer existing in readonly portion\n");
+
+  if (ctf_errno (fp) != ECTF_RDONLY)
+    fprintf (stderr, "unexpected error %s attempting to add integer in readonly portion\n", ctf_errmsg (ctf_errno (fp)));
+
+  if (ctf_add_typedef (fp, CTF_ADD_ROOT, "a_typedef", 0) == 0)
+    fprintf (stderr, "allowed to add typedef existing in readonly portion\n");
+
+  if (ctf_errno (fp) != ECTF_RDONLY)
+    fprintf (stderr, "unexpected error %s attempting to add typedef in readonly portion\n", ctf_errmsg (ctf_errno (fp)));
+
+  if (ctf_add_struct (fp, CTF_ADD_ROOT, "a_struct") == 0)
+    fprintf (stderr, "allowed to add struct existing in readonly portion\n");
+
+  if (ctf_errno (fp) != ECTF_RDONLY)
+    fprintf (stderr, "unexpected error %s attempting to add struct in readonly portion\n", ctf_errmsg (ctf_errno (fp)));
+
+  if (ctf_add_union (fp, CTF_ADD_ROOT, "a_union") == 0)
+    fprintf (stderr, "allowed to add union existing in readonly portion\n");
+
+  if (ctf_errno (fp) != ECTF_RDONLY)
+    fprintf (stderr, "unexpected error %s attempting to add union in readonly portion\n", ctf_errmsg (ctf_errno (fp)));
+
+  if (ctf_add_enum (fp, CTF_ADD_ROOT, "an_enum") == 0)
+    fprintf (stderr, "allowed to add enum existing in readonly portion\n");
+
+  if (ctf_errno (fp) != ECTF_RDONLY)
+    fprintf (stderr, "unexpected error %s attempting to add enum in readonly portion\n", ctf_errmsg (ctf_errno (fp)));
+
+  if (ctf_add_struct (fp, CTF_ADD_ROOT, "struct_forward") == 0)
+    fprintf (stderr, "allowed to promote struct forward existing in readonly portion\n");
+
+  if (ctf_errno (fp) != ECTF_RDONLY)
+    fprintf (stderr, "unexpected error %s attempting to promote struct forward in readonly portion\n", ctf_errmsg (ctf_errno (fp)));
+
+  if (ctf_add_union (fp, CTF_ADD_ROOT, "union_forward") == 0)
+    fprintf (stderr, "allowed to promote union forward existing in readonly portion\n");
+
+  if (ctf_errno (fp) != ECTF_RDONLY)
+    fprintf (stderr, "unexpected error %s attempting to promote union forward in readonly portion\n", ctf_errmsg (ctf_errno (fp)));
+
+  if (ctf_add_enum (fp, CTF_ADD_ROOT, "enum_forward") == 0)
+    fprintf (stderr, "allowed to promote enum forward existing in readonly portion\n");
+
+  if (ctf_errno (fp) != ECTF_RDONLY)
+    fprintf (stderr, "unexpected error %s attempting to promote enum forward in readonly portion\n", ctf_errmsg (ctf_errno (fp)));
+
+  if ((type = ctf_lookup_by_name (fp, "struct a_struct")) == CTF_ERR)
+    fprintf (stderr, "Lookup of struct a_struct failed: %s\n", ctf_errmsg (ctf_errno (fp)));
+
+  if (ctf_add_member (fp, type, "wombat", 0) == 0)
+    fprintf (stderr, "allowed to add member to struct existing in readonly portion\n");
+
+  if (ctf_errno (fp) != ECTF_RDONLY)
+    fprintf (stderr, "unexpected error %s attempting to add member to struct in readonly portion\n", ctf_errmsg (ctf_errno (fp)));
+
+  if ((type = ctf_lookup_by_name (fp, "union a_union")) == CTF_ERR)
+    fprintf (stderr, "Lookup of union a_union failed: %s\n", ctf_errmsg (ctf_errno (fp)));
+
+  if (ctf_add_member (fp, type, "wombat", 0) == 0)
+    fprintf (stderr, "allowed to add member to union existing in readonly portion\n");
+
+  if (ctf_errno (fp) != ECTF_RDONLY)
+    fprintf (stderr, "unexpected error %s attempting to add member to union in readonly portion\n", ctf_errmsg (ctf_errno (fp)));
+
+  if ((type = ctf_lookup_by_name (fp, "enum an_enum")) == CTF_ERR)
+    fprintf (stderr, "Lookup of enum an_enum failed: %s\n", ctf_errmsg (ctf_errno (fp)));
+
+  if (ctf_add_enumerator (fp, type, "wombat", 0) == 0)
+    fprintf (stderr, "allowed to add enumerator to enum existing in readonly portion\n");
+
+  if (ctf_errno (fp) != ECTF_RDONLY)
+    fprintf (stderr, "unexpected error %s attempting to add enumerator to enum in readonly portion\n", ctf_errmsg (ctf_errno (fp)));
+
+  if ((type = ctf_lookup_by_name (fp, "an_array")) == CTF_ERR)
+    fprintf (stderr, "Lookup of an_array failed: %s\n", ctf_errmsg (ctf_errno (fp)));
+
+  if ((type = ctf_type_reference (fp, type)) == CTF_ERR)
+    fprintf (stderr, "Lookup of type reffed by an_array failed: %s\n", ctf_errmsg (ctf_errno (fp)));
+
+  if (ctf_set_array (fp, type, &ar) == 0)
+    fprintf (stderr, "allowed to set array in readonly portion\n");
+
+  if (ctf_errno (fp) != ECTF_RDONLY)
+    fprintf (stderr, "unexpected error %s attempting to set array in readonly portion\n", ctf_errmsg (ctf_errno (fp)));
+
+  if ((ctf_written = ctf_write_mem (fp, &size, 4096)) != NULL)
+    fprintf (stderr, "Writeout unexpectedly succeeded: %s\n", ctf_errmsg (ctf_errno (fp)));
+
+  if (ctf_errno (fp) != ECTF_RDONLY)
+    fprintf (stderr, "unexpected error %s trying to write out previously serialized dict\n", ctf_errmsg (ctf_errno (fp)));
+
+  /* Finally, make sure we can add new types, and look them up again.  */
+
+  if ((type = ctf_lookup_by_name (fp, "struct a_struct")) == CTF_ERR)
+    fprintf (stderr, "Lookup of struct a_struct failed: %s\n", ctf_errmsg (ctf_errno (fp)));
+
+  if ((ptrtype = ctf_add_pointer (fp, CTF_ADD_ROOT, type)) == CTF_ERR)
+    fprintf (stderr, "Cannot add pointer to ctf_opened dict: %s\n", ctf_errmsg (ctf_errno (fp)));
+
+  if (ctf_type_reference (fp, ptrtype) == CTF_ERR)
+    fprintf (stderr, "Lookup of pointer preserved across writeout failed: %s\n", ctf_errmsg (ctf_errno (fp)));
+
+  if (ctf_type_reference (fp, ptrtype) != type)
+    fprintf (stderr, "Look up of newly-added type in serialized dict yields ID %lx, expected %lx\n", ctf_type_reference (fp, ptrtype), type);
+
+  printf ("All done.\n");
+  return 0;
+ 
+ open_err:
+  fprintf (stderr, "%s: cannot open: %s\n", argv[0], ctf_errmsg (err));
+  return 1;
+}    
diff --git a/libctf/testsuite/libctf-lookup/add-to-opened.lk b/libctf/testsuite/libctf-lookup/add-to-opened.lk
new file mode 100644
index 00000000000..af842597363
--- /dev/null
+++ b/libctf/testsuite/libctf-lookup/add-to-opened.lk
@@ -0,0 +1,3 @@ 
+# source: add-to-opened-ctf.c
+# lookup: add-to-opened.c
+All done.