libctf: core type lookup
authorNick Alcock <nick.alcock@oracle.com>
Wed, 24 Apr 2019 10:03:37 +0000 (11:03 +0100)
committerNick Alcock <nick.alcock@oracle.com>
Tue, 28 May 2019 16:08:14 +0000 (17:08 +0100)
Finally we get to the functions used to actually look up and enumerate
properties of types in a container (names, sizes, members, what type a
pointer or cv-qual references, determination of whether two types are
assignment-compatible, etc).

With a very few exceptions these do not work for types newly added via
ctf_add_*(): they only work on types in read-only containers, or types
added before the most recent call to ctf_update().

This also adds support for lookup of "variables" (string -> type ID
mappings) and for generation of C type names corresponding to a type ID.

libctf/
* ctf-decl.c: New file.
* ctf-types.c: Likewise.
* ctf-impl.h: New declarations.

include/
* ctf-api.h (ctf_visit_f): New definition.
(ctf_member_f): Likewise.
(ctf_enum_f): Likewise.
(ctf_variable_f): Likewise.
(ctf_type_f): Likewise.
(ctf_type_isparent): Likewise.
(ctf_type_ischild): Likewise.
(ctf_type_resolve): Likewise.
(ctf_type_aname): Likewise.
(ctf_type_lname): Likewise.
(ctf_type_name): Likewise.
(ctf_type_sizee): Likewise.
(ctf_type_align): Likewise.
(ctf_type_kind): Likewise.
(ctf_type_reference): Likewise.
(ctf_type_pointer): Likewise.
(ctf_type_encoding): Likewise.
(ctf_type_visit): Likewise.
(ctf_type_cmp): Likewise.
(ctf_type_compat): Likewise.
(ctf_member_info): Likewise.
(ctf_array_info): Likewise.
(ctf_enum_name): Likewise.
(ctf_enum_value): Likewise.
(ctf_member_iter): Likewise.
(ctf_enum_iter): Likewise.
(ctf_type_iter): Likewise.
(ctf_variable_iter): Likewise.

include/ChangeLog
include/ctf-api.h
libctf/ChangeLog
libctf/ctf-decl.c [new file with mode: 0644]
libctf/ctf-impl.h
libctf/ctf-types.c [new file with mode: 0644]

index e8e80f872fc668ad4e6f4290d552472302fb30cb..b935cb4e38f0e24b2498856d745e3f22106b8b54 100644 (file)
@@ -1,3 +1,34 @@
+2019-05-28  Nick Alcock  <nick.alcock@oracle.com>
+
+       * ctf-api.h (ctf_visit_f): New definition.
+       (ctf_member_f): Likewise.
+       (ctf_enum_f): Likewise.
+       (ctf_variable_f): Likewise.
+       (ctf_type_f): Likewise.
+       (ctf_type_isparent): Likewise.
+       (ctf_type_ischild): Likewise.
+       (ctf_type_resolve): Likewise.
+       (ctf_type_aname): Likewise.
+       (ctf_type_lname): Likewise.
+       (ctf_type_name): Likewise.
+       (ctf_type_sizee): Likewise.
+       (ctf_type_align): Likewise.
+       (ctf_type_kind): Likewise.
+       (ctf_type_reference): Likewise.
+       (ctf_type_pointer): Likewise.
+       (ctf_type_encoding): Likewise.
+       (ctf_type_visit): Likewise.
+       (ctf_type_cmp): Likewise.
+       (ctf_type_compat): Likewise.
+       (ctf_member_info): Likewise.
+       (ctf_array_info): Likewise.
+       (ctf_enum_name): Likewise.
+       (ctf_enum_value): Likewise.
+       (ctf_member_iter): Likewise.
+       (ctf_enum_iter): Likewise.
+       (ctf_type_iter): Likewise.
+       (ctf_variable_iter): Likewise.
+
 2019-05-28  Nick Alcock  <nick.alcock@oracle.com>
 
        * ctf-api.h (struct bfd): New forward.
index ee68efefe37b9541d617d84dbe5685659ad9b208..023c890d7d0e999f37c4dd323dcda284fd49860c 100644 (file)
@@ -206,6 +206,13 @@ enum
 /* These typedefs are used to define the signature for callback functions
    that can be used with the iteration and visit functions below.  */
 
+typedef int ctf_visit_f (const char *name, ctf_id_t type, unsigned long offset,
+                        int depth, void *arg);
+typedef int ctf_member_f (const char *name, ctf_id_t membtype,
+                         unsigned long offset, void *arg);
+typedef int ctf_enum_f (const char *name, int val, void *arg);
+typedef int ctf_variable_f (const char *name, ctf_id_t type, void *arg);
+typedef int ctf_type_f (ctf_id_t type, void *arg);
 typedef int ctf_archive_member_f (ctf_file_t *fp, const char *name, void *arg);
 typedef int ctf_archive_raw_member_f (const char *name, const void *content,
                                      size_t len, void *arg);
@@ -250,6 +257,8 @@ extern int ctf_arc_write (const char *, ctf_file_t **, size_t,
 extern ctf_file_t *ctf_parent_file (ctf_file_t *);
 extern const char *ctf_parent_name (ctf_file_t *);
 extern void ctf_parent_name_set (ctf_file_t *, const char *);
+extern int ctf_type_isparent (ctf_file_t *, ctf_id_t);
+extern int ctf_type_ischild (ctf_file_t *, ctf_id_t);
 
 extern int ctf_import (ctf_file_t *, ctf_file_t *);
 extern int ctf_setmodel (ctf_file_t *, int);
@@ -260,6 +269,32 @@ extern void *ctf_getspecific (ctf_file_t *);
 
 extern int ctf_errno (ctf_file_t *);
 extern const char *ctf_errmsg (int);
+
+extern ctf_id_t ctf_type_resolve (ctf_file_t *, ctf_id_t);
+extern char *ctf_type_aname (ctf_file_t *, ctf_id_t);
+extern ssize_t ctf_type_lname (ctf_file_t *, ctf_id_t, char *, size_t);
+extern char *ctf_type_name (ctf_file_t *, ctf_id_t, char *, size_t);
+extern ssize_t ctf_type_size (ctf_file_t *, ctf_id_t);
+extern ssize_t ctf_type_align (ctf_file_t *, ctf_id_t);
+extern int ctf_type_kind (ctf_file_t *, ctf_id_t);
+extern ctf_id_t ctf_type_reference (ctf_file_t *, ctf_id_t);
+extern ctf_id_t ctf_type_pointer (ctf_file_t *, ctf_id_t);
+extern int ctf_type_encoding (ctf_file_t *, ctf_id_t, ctf_encoding_t *);
+extern int ctf_type_visit (ctf_file_t *, ctf_id_t, ctf_visit_f *, void *);
+extern int ctf_type_cmp (ctf_file_t *, ctf_id_t, ctf_file_t *, ctf_id_t);
+extern int ctf_type_compat (ctf_file_t *, ctf_id_t, ctf_file_t *, ctf_id_t);
+
+extern int ctf_member_info (ctf_file_t *, ctf_id_t, const char *,
+                           ctf_membinfo_t *);
+extern int ctf_array_info (ctf_file_t *, ctf_id_t, ctf_arinfo_t *);
+
+extern const char *ctf_enum_name (ctf_file_t *, ctf_id_t, int);
+extern int ctf_enum_value (ctf_file_t *, ctf_id_t, const char *, int *);
+
+extern int ctf_member_iter (ctf_file_t *, ctf_id_t, ctf_member_f *, void *);
+extern int ctf_enum_iter (ctf_file_t *, ctf_id_t, ctf_enum_f *, void *);
+extern int ctf_type_iter (ctf_file_t *, ctf_type_f *, void *);
+extern int ctf_variable_iter (ctf_file_t *, ctf_variable_f *, void *);
 extern int ctf_archive_iter (const ctf_archive_t *, ctf_archive_member_f *,
                             void *);
 /* This function alone does not currently operate on CTF files masquerading
@@ -268,6 +303,7 @@ extern int ctf_archive_iter (const ctf_archive_t *, ctf_archive_member_f *,
    to deal with non-archives at all.  */
 extern int ctf_archive_raw_iter (const ctf_archive_t *,
                                 ctf_archive_raw_member_f *, void *);
+
 extern ctf_id_t ctf_add_array (ctf_file_t *, uint32_t,
                               const ctf_arinfo_t *);
 extern ctf_id_t ctf_add_const (ctf_file_t *, uint32_t, ctf_id_t);
index 23a8b893ebc06cd8b8e915b3a67645b7c967ca1a..56d6016c20045b4fe99c7e798e61d9325aa7192d 100644 (file)
@@ -1,3 +1,9 @@
+2019-05-28  Nick Alcock  <nick.alcock@oracle.com>
+
+       * ctf-decl.c: New file.
+       * ctf-types.c: Likewise.
+       * ctf-impl.h: New declarations.
+
 2019-05-28  Nick Alcock  <nick.alcock@oracle.com>
 
        * ctf-open-bfd.c: New file.
diff --git a/libctf/ctf-decl.c b/libctf/ctf-decl.c
new file mode 100644 (file)
index 0000000..c85982e
--- /dev/null
@@ -0,0 +1,195 @@
+/* C declarator syntax glue.
+   Copyright (C) 2019 Free Software Foundation, Inc.
+
+   This file is part of libctf.
+
+   libctf is free software; you can redistribute it and/or modify it under
+   the terms of the GNU General Public License as published by the Free
+   Software Foundation; either version 3, or (at your option) any later
+   version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+   See the GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; see the file COPYING.  If not see
+   <http://www.gnu.org/licenses/>.  */
+
+/* CTF Declaration Stack
+
+   In order to implement ctf_type_name(), we must convert a type graph back
+   into a C type declaration.  Unfortunately, a type graph represents a storage
+   class ordering of the type whereas a type declaration must obey the C rules
+   for operator precedence, and the two orderings are frequently in conflict.
+   For example, consider these CTF type graphs and their C declarations:
+
+   CTF_K_POINTER -> CTF_K_FUNCTION -> CTF_K_INTEGER  : int (*)()
+   CTF_K_POINTER -> CTF_K_ARRAY -> CTF_K_INTEGER     : int (*)[]
+
+   In each case, parentheses are used to raise operator * to higher lexical
+   precedence, so the string form of the C declaration cannot be constructed by
+   walking the type graph links and forming the string from left to right.
+
+   The functions in this file build a set of stacks from the type graph nodes
+   corresponding to the C operator precedence levels in the appropriate order.
+   The code in ctf_type_name() can then iterate over the levels and nodes in
+   lexical precedence order and construct the final C declaration string.  */
+
+#include <ctf-impl.h>
+#include <string.h>
+
+void
+ctf_decl_init (ctf_decl_t *cd)
+{
+  int i;
+
+  memset (cd, 0, sizeof (ctf_decl_t));
+
+  for (i = CTF_PREC_BASE; i < CTF_PREC_MAX; i++)
+    cd->cd_order[i] = CTF_PREC_BASE - 1;
+
+  cd->cd_qualp = CTF_PREC_BASE;
+  cd->cd_ordp = CTF_PREC_BASE;
+}
+
+void
+ctf_decl_fini (ctf_decl_t *cd)
+{
+  ctf_decl_node_t *cdp, *ndp;
+  int i;
+
+  for (i = CTF_PREC_BASE; i < CTF_PREC_MAX; i++)
+    {
+      for (cdp = ctf_list_next (&cd->cd_nodes[i]); cdp != NULL; cdp = ndp)
+       {
+         ndp = ctf_list_next (cdp);
+         ctf_free (cdp);
+       }
+    }
+}
+
+void
+ctf_decl_push (ctf_decl_t *cd, ctf_file_t *fp, ctf_id_t type)
+{
+  ctf_decl_node_t *cdp;
+  ctf_decl_prec_t prec;
+  uint32_t kind, n = 1;
+  int is_qual = 0;
+
+  const ctf_type_t *tp;
+  ctf_arinfo_t ar;
+
+  if ((tp = ctf_lookup_by_id (&fp, type)) == NULL)
+    {
+      cd->cd_err = fp->ctf_errno;
+      return;
+    }
+
+  switch (kind = LCTF_INFO_KIND (fp, tp->ctt_info))
+    {
+    case CTF_K_ARRAY:
+      (void) ctf_array_info (fp, type, &ar);
+      ctf_decl_push (cd, fp, ar.ctr_contents);
+      n = ar.ctr_nelems;
+      prec = CTF_PREC_ARRAY;
+      break;
+
+    case CTF_K_TYPEDEF:
+      if (ctf_strptr (fp, tp->ctt_name)[0] == '\0')
+       {
+         ctf_decl_push (cd, fp, tp->ctt_type);
+         return;
+       }
+      prec = CTF_PREC_BASE;
+      break;
+
+    case CTF_K_FUNCTION:
+      ctf_decl_push (cd, fp, tp->ctt_type);
+      prec = CTF_PREC_FUNCTION;
+      break;
+
+    case CTF_K_POINTER:
+      ctf_decl_push (cd, fp, tp->ctt_type);
+      prec = CTF_PREC_POINTER;
+      break;
+
+    case CTF_K_SLICE:
+      ctf_decl_push (cd, fp, ctf_type_reference (fp, type));
+      prec = CTF_PREC_BASE;
+      break;
+
+    case CTF_K_VOLATILE:
+    case CTF_K_CONST:
+    case CTF_K_RESTRICT:
+      ctf_decl_push (cd, fp, tp->ctt_type);
+      prec = cd->cd_qualp;
+      is_qual++;
+      break;
+
+    default:
+      prec = CTF_PREC_BASE;
+    }
+
+  if ((cdp = ctf_alloc (sizeof (ctf_decl_node_t))) == NULL)
+    {
+      cd->cd_err = EAGAIN;
+      return;
+    }
+
+  cdp->cd_type = type;
+  cdp->cd_kind = kind;
+  cdp->cd_n = n;
+
+  if (ctf_list_next (&cd->cd_nodes[prec]) == NULL)
+    cd->cd_order[prec] = cd->cd_ordp++;
+
+  /* Reset cd_qualp to the highest precedence level that we've seen so
+     far that can be qualified (CTF_PREC_BASE or CTF_PREC_POINTER).  */
+
+  if (prec > cd->cd_qualp && prec < CTF_PREC_ARRAY)
+    cd->cd_qualp = prec;
+
+  /* C array declarators are ordered inside out so prepend them.  Also by
+     convention qualifiers of base types precede the type specifier (e.g.
+     const int vs. int const) even though the two forms are equivalent.  */
+
+  if (kind == CTF_K_ARRAY || (is_qual && prec == CTF_PREC_BASE))
+    ctf_list_prepend (&cd->cd_nodes[prec], cdp);
+  else
+    ctf_list_append (&cd->cd_nodes[prec], cdp);
+}
+
+_libctf_printflike_ (2, 3)
+void ctf_decl_sprintf (ctf_decl_t *cd, const char *format, ...)
+{
+  va_list ap;
+  char *str;
+  int n;
+
+  if (cd->cd_enomem)
+    return;
+
+  va_start (ap, format);
+  n = vasprintf (&str, format, ap);
+  va_end (ap);
+
+  if (n > 0)
+      cd->cd_buf = ctf_str_append (cd->cd_buf, str);
+
+  /* Sticky error condition.  */
+  if (n < 0)
+    {
+      free (cd->cd_buf);
+      cd->cd_buf = NULL;
+      cd->cd_enomem = 1;
+    }
+
+  free (str);
+}
+
+char *ctf_decl_buf (ctf_decl_t *cd)
+{
+  return cd->cd_buf;
+}
index 8522a032dd806614479f63478ded1da1cafd1647..cd8450550eb0b9f8193b255966e3afc06966f3d9 100644 (file)
@@ -324,6 +324,14 @@ extern void ctf_dvd_insert (ctf_file_t *, ctf_dvdef_t *);
 extern void ctf_dvd_delete (ctf_file_t *, ctf_dvdef_t *);
 extern ctf_dvdef_t *ctf_dvd_lookup (const ctf_file_t *, const char *);
 
+extern void ctf_decl_init (ctf_decl_t *);
+extern void ctf_decl_fini (ctf_decl_t *);
+extern void ctf_decl_push (ctf_decl_t *, ctf_file_t *, ctf_id_t);
+
+_libctf_printflike_ (2, 3)
+extern void ctf_decl_sprintf (ctf_decl_t *, const char *, ...);
+extern char *ctf_decl_buf (ctf_decl_t *cd);
+
 extern const char *ctf_strraw (ctf_file_t *, uint32_t);
 extern const char *ctf_strptr (ctf_file_t *, uint32_t);
 
diff --git a/libctf/ctf-types.c b/libctf/ctf-types.c
new file mode 100644 (file)
index 0000000..a7fe5d0
--- /dev/null
@@ -0,0 +1,1023 @@
+/* Type handling functions.
+   Copyright (C) 2019 Free Software Foundation, Inc.
+
+   This file is part of libctf.
+
+   libctf is free software; you can redistribute it and/or modify it under
+   the terms of the GNU General Public License as published by the Free
+   Software Foundation; either version 3, or (at your option) any later
+   version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+   See the GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; see the file COPYING.  If not see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <ctf-impl.h>
+#include <string.h>
+
+/* Determine whether a type is a parent or a child.  */
+
+int
+ctf_type_isparent (ctf_file_t *fp, ctf_id_t id)
+{
+  return (LCTF_TYPE_ISPARENT (fp, id));
+}
+
+int
+ctf_type_ischild (ctf_file_t * fp, ctf_id_t id)
+{
+  return (LCTF_TYPE_ISCHILD (fp, id));
+}
+
+/* Iterate over the members of a STRUCT or UNION.  We pass the name, member
+   type, and offset of each member to the specified callback function.  */
+
+int
+ctf_member_iter (ctf_file_t *fp, ctf_id_t type, ctf_member_f *func, void *arg)
+{
+  ctf_file_t *ofp = fp;
+  const ctf_type_t *tp;
+  ssize_t size, increment;
+  uint32_t kind, n;
+  int rc;
+
+  if ((type = ctf_type_resolve (fp, type)) == CTF_ERR)
+    return CTF_ERR;            /* errno is set for us.  */
+
+  if ((tp = ctf_lookup_by_id (&fp, type)) == NULL)
+    return CTF_ERR;            /* errno is set for us.  */
+
+  (void) ctf_get_ctt_size (fp, tp, &size, &increment);
+  kind = LCTF_INFO_KIND (fp, tp->ctt_info);
+
+  if (kind != CTF_K_STRUCT && kind != CTF_K_UNION)
+    return (ctf_set_errno (ofp, ECTF_NOTSOU));
+
+  if (size < CTF_LSTRUCT_THRESH)
+    {
+      const ctf_member_t *mp = (const ctf_member_t *) ((uintptr_t) tp +
+                                                      increment);
+
+      for (n = LCTF_INFO_VLEN (fp, tp->ctt_info); n != 0; n--, mp++)
+       {
+         const char *name = ctf_strptr (fp, mp->ctm_name);
+         if ((rc = func (name, mp->ctm_type, mp->ctm_offset, arg)) != 0)
+           return rc;
+       }
+
+    }
+  else
+    {
+      const ctf_lmember_t *lmp = (const ctf_lmember_t *) ((uintptr_t) tp +
+                                                         increment);
+
+      for (n = LCTF_INFO_VLEN (fp, tp->ctt_info); n != 0; n--, lmp++)
+       {
+         const char *name = ctf_strptr (fp, lmp->ctlm_name);
+         if ((rc = func (name, lmp->ctlm_type,
+                         (unsigned long) CTF_LMEM_OFFSET (lmp), arg)) != 0)
+           return rc;
+       }
+    }
+
+  return 0;
+}
+
+/* Iterate over the members of an ENUM.  We pass the string name and associated
+   integer value of each enum element to the specified callback function.  */
+
+int
+ctf_enum_iter (ctf_file_t *fp, ctf_id_t type, ctf_enum_f *func, void *arg)
+{
+  ctf_file_t *ofp = fp;
+  const ctf_type_t *tp;
+  const ctf_enum_t *ep;
+  ssize_t increment;
+  uint32_t n;
+  int rc;
+
+  if ((type = ctf_type_resolve_unsliced (fp, type)) == CTF_ERR)
+    return CTF_ERR;            /* errno is set for us.  */
+
+  if ((tp = ctf_lookup_by_id (&fp, type)) == NULL)
+    return CTF_ERR;            /* errno is set for us.  */
+
+  if (LCTF_INFO_KIND (fp, tp->ctt_info) != CTF_K_ENUM)
+    return (ctf_set_errno (ofp, ECTF_NOTENUM));
+
+  (void) ctf_get_ctt_size (fp, tp, NULL, &increment);
+
+  ep = (const ctf_enum_t *) ((uintptr_t) tp + increment);
+
+  for (n = LCTF_INFO_VLEN (fp, tp->ctt_info); n != 0; n--, ep++)
+    {
+      const char *name = ctf_strptr (fp, ep->cte_name);
+      if ((rc = func (name, ep->cte_value, arg)) != 0)
+       return rc;
+    }
+
+  return 0;
+}
+
+/* Iterate over every root (user-visible) type in the given CTF container.
+   We pass the type ID of each type to the specified callback function.  */
+
+int
+ctf_type_iter (ctf_file_t *fp, ctf_type_f *func, void *arg)
+{
+  ctf_id_t id, max = fp->ctf_typemax;
+  int rc, child = (fp->ctf_flags & LCTF_CHILD);
+
+  for (id = 1; id <= max; id++)
+    {
+      const ctf_type_t *tp = LCTF_INDEX_TO_TYPEPTR (fp, id);
+      if (LCTF_INFO_ISROOT (fp, tp->ctt_info)
+         && (rc = func (LCTF_INDEX_TO_TYPE (fp, id, child), arg)) != 0)
+       return rc;
+    }
+
+  return 0;
+}
+
+/* Iterate over every variable in the given CTF container, in arbitrary order.
+   We pass the name of each variable to the specified callback function.  */
+
+int
+ctf_variable_iter (ctf_file_t *fp, ctf_variable_f *func, void *arg)
+{
+  unsigned long i;
+  int rc;
+
+  if ((fp->ctf_flags & LCTF_CHILD) && (fp->ctf_parent == NULL))
+    return ECTF_NOPARENT;
+
+  for (i = 0; i < fp->ctf_nvars; i++)
+    if ((rc = func (ctf_strptr (fp, fp->ctf_vars[i].ctv_name),
+                   fp->ctf_vars[i].ctv_type, arg)) != 0)
+      return rc;
+
+  return 0;
+}
+
+/* Follow a given type through the graph for TYPEDEF, VOLATILE, CONST, and
+   RESTRICT nodes until we reach a "base" type node.  This is useful when
+   we want to follow a type ID to a node that has members or a size.  To guard
+   against infinite loops, we implement simplified cycle detection and check
+   each link against itself, the previous node, and the topmost node.
+
+   Does not drill down through slices to their contained type.  */
+
+ctf_id_t
+ctf_type_resolve (ctf_file_t *fp, ctf_id_t type)
+{
+  ctf_id_t prev = type, otype = type;
+  ctf_file_t *ofp = fp;
+  const ctf_type_t *tp;
+
+  while ((tp = ctf_lookup_by_id (&fp, type)) != NULL)
+    {
+      switch (LCTF_INFO_KIND (fp, tp->ctt_info))
+       {
+       case CTF_K_TYPEDEF:
+       case CTF_K_VOLATILE:
+       case CTF_K_CONST:
+       case CTF_K_RESTRICT:
+         if (tp->ctt_type == type || tp->ctt_type == otype
+             || tp->ctt_type == prev)
+           {
+             ctf_dprintf ("type %ld cycle detected\n", otype);
+             return (ctf_set_errno (ofp, ECTF_CORRUPT));
+           }
+         prev = type;
+         type = tp->ctt_type;
+         break;
+       default:
+         return type;
+       }
+    }
+
+  return CTF_ERR;              /* errno is set for us.  */
+}
+
+/* Like ctf_type_resolve(), but traverse down through slices to their contained
+   type.  */
+
+ctf_id_t
+ctf_type_resolve_unsliced (ctf_file_t *fp, ctf_id_t type)
+{
+  const ctf_type_t *tp;
+
+  if ((type = ctf_type_resolve (fp, type)) == CTF_ERR)
+    return -1;
+
+  if ((tp = ctf_lookup_by_id (&fp, type)) == NULL)
+    return CTF_ERR;            /* errno is set for us.  */
+
+  if ((LCTF_INFO_KIND (fp, tp->ctt_info)) == CTF_K_SLICE)
+    return ctf_type_reference (fp, type);
+  return type;
+}
+
+/* Lookup the given type ID and return its name as a new dynamcally-allocated
+   string.  */
+
+char *
+ctf_type_aname (ctf_file_t *fp, ctf_id_t type)
+{
+  ctf_decl_t cd;
+  ctf_decl_node_t *cdp;
+  ctf_decl_prec_t prec, lp, rp;
+  int ptr, arr;
+  uint32_t k;
+  char *buf;
+
+  if (fp == NULL && type == CTF_ERR)
+    return NULL;       /* Simplify caller code by permitting CTF_ERR.  */
+
+  ctf_decl_init (&cd);
+  ctf_decl_push (&cd, fp, type);
+
+  if (cd.cd_err != 0)
+    {
+      ctf_decl_fini (&cd);
+      ctf_set_errno (fp, cd.cd_err);
+      return NULL;
+    }
+
+  /* If the type graph's order conflicts with lexical precedence order
+     for pointers or arrays, then we need to surround the declarations at
+     the corresponding lexical precedence with parentheses.  This can
+     result in either a parenthesized pointer (*) as in int (*)() or
+     int (*)[], or in a parenthesized pointer and array as in int (*[])().  */
+
+  ptr = cd.cd_order[CTF_PREC_POINTER] > CTF_PREC_POINTER;
+  arr = cd.cd_order[CTF_PREC_ARRAY] > CTF_PREC_ARRAY;
+
+  rp = arr ? CTF_PREC_ARRAY : ptr ? CTF_PREC_POINTER : -1;
+  lp = ptr ? CTF_PREC_POINTER : arr ? CTF_PREC_ARRAY : -1;
+
+  k = CTF_K_POINTER;           /* Avoid leading whitespace (see below).  */
+
+  for (prec = CTF_PREC_BASE; prec < CTF_PREC_MAX; prec++)
+    {
+      for (cdp = ctf_list_next (&cd.cd_nodes[prec]);
+          cdp != NULL; cdp = ctf_list_next (cdp))
+       {
+         ctf_file_t *rfp = fp;
+         const ctf_type_t *tp = ctf_lookup_by_id (&rfp, cdp->cd_type);
+         const char *name = ctf_strptr (rfp, tp->ctt_name);
+
+         if (k != CTF_K_POINTER && k != CTF_K_ARRAY)
+           ctf_decl_sprintf (&cd, " ");
+
+         if (lp == prec)
+           {
+             ctf_decl_sprintf (&cd, "(");
+             lp = -1;
+           }
+
+         switch (cdp->cd_kind)
+           {
+           case CTF_K_INTEGER:
+           case CTF_K_FLOAT:
+           case CTF_K_TYPEDEF:
+             ctf_decl_sprintf (&cd, "%s", name);
+             break;
+           case CTF_K_POINTER:
+             ctf_decl_sprintf (&cd, "*");
+             break;
+           case CTF_K_ARRAY:
+             ctf_decl_sprintf (&cd, "[%u]", cdp->cd_n);
+             break;
+           case CTF_K_FUNCTION:
+             ctf_decl_sprintf (&cd, "()");
+             break;
+           case CTF_K_STRUCT:
+           case CTF_K_FORWARD:
+             ctf_decl_sprintf (&cd, "struct %s", name);
+             break;
+           case CTF_K_UNION:
+             ctf_decl_sprintf (&cd, "union %s", name);
+             break;
+           case CTF_K_ENUM:
+             ctf_decl_sprintf (&cd, "enum %s", name);
+             break;
+           case CTF_K_VOLATILE:
+             ctf_decl_sprintf (&cd, "volatile");
+             break;
+           case CTF_K_CONST:
+             ctf_decl_sprintf (&cd, "const");
+             break;
+           case CTF_K_RESTRICT:
+             ctf_decl_sprintf (&cd, "restrict");
+             break;
+           case CTF_K_SLICE:
+             /* No representation: just changes encoding of contained type,
+                which is not in any case printed.  Skip it.  */
+             break;
+           }
+
+         k = cdp->cd_kind;
+       }
+
+      if (rp == prec)
+       ctf_decl_sprintf (&cd, ")");
+    }
+
+  if (cd.cd_enomem)
+    (void) ctf_set_errno (fp, ENOMEM);
+
+  buf = ctf_decl_buf (&cd);
+
+  ctf_decl_fini (&cd);
+  return buf;
+}
+
+/* Lookup the given type ID and print a string name for it into buf.  Return
+   the actual number of bytes (not including \0) needed to format the name.  */
+
+ssize_t
+ctf_type_lname (ctf_file_t *fp, ctf_id_t type, char *buf, size_t len)
+{
+  char *str = ctf_type_aname (fp, type);
+  size_t slen = strlen (str);
+
+  if (str == NULL)
+    return CTF_ERR;             /* errno is set for us */
+
+  snprintf (buf, len, "%s", str);
+  free (str);
+
+  if (slen >= len)
+    (void) ctf_set_errno (fp, ECTF_NAMELEN);
+
+  return slen;
+}
+
+/* Lookup the given type ID and print a string name for it into buf.  If buf
+   is too small, return NULL: the ECTF_NAMELEN error is set on 'fp' for us.  */
+
+char *
+ctf_type_name (ctf_file_t *fp, ctf_id_t type, char *buf, size_t len)
+{
+  ssize_t rv = ctf_type_lname (fp, type, buf, len);
+  return (rv >= 0 && (size_t) rv < len ? buf : NULL);
+}
+
+/* Resolve the type down to a base type node, and then return the size
+   of the type storage in bytes.  */
+
+ssize_t
+ctf_type_size (ctf_file_t *fp, ctf_id_t type)
+{
+  const ctf_type_t *tp;
+  ssize_t size;
+  ctf_arinfo_t ar;
+
+  if ((type = ctf_type_resolve (fp, type)) == CTF_ERR)
+    return -1;                 /* errno is set for us.  */
+
+  if ((tp = ctf_lookup_by_id (&fp, type)) == NULL)
+    return -1;                 /* errno is set for us.  */
+
+  switch (LCTF_INFO_KIND (fp, tp->ctt_info))
+    {
+    case CTF_K_POINTER:
+      return fp->ctf_dmodel->ctd_pointer;
+
+    case CTF_K_FUNCTION:
+      return 0;                /* Function size is only known by symtab.  */
+
+    case CTF_K_ENUM:
+      return fp->ctf_dmodel->ctd_int;
+
+    case CTF_K_ARRAY:
+      /* ctf_add_array() does not directly encode the element size, but
+        requires the user to multiply to determine the element size.
+
+        If ctf_get_ctt_size() returns nonzero, then use the recorded
+        size instead.  */
+
+      if ((size = ctf_get_ctt_size (fp, tp, NULL, NULL)) > 0)
+       return size;
+
+      if (ctf_array_info (fp, type, &ar) == CTF_ERR
+         || (size = ctf_type_size (fp, ar.ctr_contents)) == CTF_ERR)
+       return -1;              /* errno is set for us.  */
+
+      return size * ar.ctr_nelems;
+
+    default: /* including slices of enums, etc */
+      return (ctf_get_ctt_size (fp, tp, NULL, NULL));
+    }
+}
+
+/* Resolve the type down to a base type node, and then return the alignment
+   needed for the type storage in bytes.
+
+   XXX may need arch-dependent attention.  */
+
+ssize_t
+ctf_type_align (ctf_file_t *fp, ctf_id_t type)
+{
+  const ctf_type_t *tp;
+  ctf_file_t *ofp = fp;
+  int kind;
+
+  if ((type = ctf_type_resolve (fp, type)) == CTF_ERR)
+    return -1;                 /* errno is set for us.  */
+
+  if ((tp = ctf_lookup_by_id (&fp, type)) == NULL)
+    return -1;                 /* errno is set for us.  */
+
+  kind = LCTF_INFO_KIND (fp, tp->ctt_info);
+  switch (kind)
+    {
+    case CTF_K_POINTER:
+    case CTF_K_FUNCTION:
+      return fp->ctf_dmodel->ctd_pointer;
+
+    case CTF_K_ARRAY:
+      {
+       ctf_arinfo_t r;
+       if (ctf_array_info (fp, type, &r) == CTF_ERR)
+         return -1;            /* errno is set for us.  */
+       return (ctf_type_align (fp, r.ctr_contents));
+      }
+
+    case CTF_K_STRUCT:
+    case CTF_K_UNION:
+      {
+       size_t align = 0;
+       ctf_dtdef_t *dtd;
+
+       if ((dtd = ctf_dynamic_type (ofp, type)) == NULL)
+         {
+           uint32_t n = LCTF_INFO_VLEN (fp, tp->ctt_info);
+           ssize_t size, increment;
+           const void *vmp;
+
+           (void) ctf_get_ctt_size (fp, tp, &size, &increment);
+           vmp = (unsigned char *) tp + increment;
+
+           if (kind == CTF_K_STRUCT)
+             n = MIN (n, 1);   /* Only use first member for structs.  */
+
+           if (size < CTF_LSTRUCT_THRESH)
+             {
+               const ctf_member_t *mp = vmp;
+               for (; n != 0; n--, mp++)
+                 {
+                   ssize_t am = ctf_type_align (fp, mp->ctm_type);
+                   align = MAX (align, am);
+                 }
+             }
+           else
+             {
+               const ctf_lmember_t *lmp = vmp;
+               for (; n != 0; n--, lmp++)
+                 {
+                   ssize_t am = ctf_type_align (fp, lmp->ctlm_type);
+                   align = MAX (align, am);
+                 }
+             }
+         }
+       else
+         {
+             ctf_dmdef_t *dmd;
+
+             for (dmd = ctf_list_next (&dtd->dtd_u.dtu_members);
+                  dmd != NULL; dmd = ctf_list_next (dmd))
+               {
+                 ssize_t am = ctf_type_align (fp, dmd->dmd_type);
+                 align = MAX (align, am);
+                 if (kind == CTF_K_STRUCT)
+                   break;
+               }
+         }
+
+       return align;
+      }
+
+    case CTF_K_ENUM:
+      return fp->ctf_dmodel->ctd_int;
+
+    default:  /* including slices of enums, etc */
+      return (ctf_get_ctt_size (fp, tp, NULL, NULL));
+    }
+}
+
+/* Return the kind (CTF_K_* constant) for the specified type ID.  */
+
+int
+ctf_type_kind_unsliced (ctf_file_t *fp, ctf_id_t type)
+{
+  const ctf_type_t *tp;
+
+  if ((tp = ctf_lookup_by_id (&fp, type)) == NULL)
+    return CTF_ERR;            /* errno is set for us.  */
+
+  return (LCTF_INFO_KIND (fp, tp->ctt_info));
+}
+
+/* Return the kind (CTF_K_* constant) for the specified type ID.
+   Slices are considered to be of the same kind as the type sliced.  */
+
+int
+ctf_type_kind (ctf_file_t *fp, ctf_id_t type)
+{
+  int kind;
+
+  if ((kind = ctf_type_kind_unsliced (fp, type)) == CTF_ERR)
+    return CTF_ERR;
+
+  if (kind == CTF_K_SLICE)
+    {
+      if ((type = ctf_type_reference (fp, type)) == CTF_ERR)
+       return CTF_ERR;
+      kind = ctf_type_kind_unsliced (fp, type);
+    }
+
+  return kind;
+}
+
+/* If the type is one that directly references another type (such as POINTER),
+   then return the ID of the type to which it refers.  */
+
+ctf_id_t
+ctf_type_reference (ctf_file_t *fp, ctf_id_t type)
+{
+  ctf_file_t *ofp = fp;
+  const ctf_type_t *tp;
+
+  if ((tp = ctf_lookup_by_id (&fp, type)) == NULL)
+    return CTF_ERR;            /* errno is set for us.  */
+
+  switch (LCTF_INFO_KIND (fp, tp->ctt_info))
+    {
+    case CTF_K_POINTER:
+    case CTF_K_TYPEDEF:
+    case CTF_K_VOLATILE:
+    case CTF_K_CONST:
+    case CTF_K_RESTRICT:
+      return tp->ctt_type;
+      /* Slices store their type in an unusual place.  */
+    case CTF_K_SLICE:
+      {
+       const ctf_slice_t *sp;
+       ssize_t increment;
+       (void) ctf_get_ctt_size (fp, tp, NULL, &increment);
+       sp = (const ctf_slice_t *) ((uintptr_t) tp + increment);
+       return sp->cts_type;
+      }
+    default:
+      return (ctf_set_errno (ofp, ECTF_NOTREF));
+    }
+}
+
+/* Find a pointer to type by looking in fp->ctf_ptrtab.  If we can't find a
+   pointer to the given type, see if we can compute a pointer to the type
+   resulting from resolving the type down to its base type and use that
+   instead.  This helps with cases where the CTF data includes "struct foo *"
+   but not "foo_t *" and the user accesses "foo_t *" in the debugger.
+
+   XXX what about parent containers?  */
+
+ctf_id_t
+ctf_type_pointer (ctf_file_t *fp, ctf_id_t type)
+{
+  ctf_file_t *ofp = fp;
+  ctf_id_t ntype;
+
+  if (ctf_lookup_by_id (&fp, type) == NULL)
+    return CTF_ERR;            /* errno is set for us.  */
+
+  if ((ntype = fp->ctf_ptrtab[LCTF_TYPE_TO_INDEX (fp, type)]) != 0)
+    return (LCTF_INDEX_TO_TYPE (fp, ntype, (fp->ctf_flags & LCTF_CHILD)));
+
+  if ((type = ctf_type_resolve (fp, type)) == CTF_ERR)
+    return (ctf_set_errno (ofp, ECTF_NOTYPE));
+
+  if (ctf_lookup_by_id (&fp, type) == NULL)
+    return (ctf_set_errno (ofp, ECTF_NOTYPE));
+
+  if ((ntype = fp->ctf_ptrtab[LCTF_TYPE_TO_INDEX (fp, type)]) != 0)
+    return (LCTF_INDEX_TO_TYPE (fp, ntype, (fp->ctf_flags & LCTF_CHILD)));
+
+  return (ctf_set_errno (ofp, ECTF_NOTYPE));
+}
+
+/* Return the encoding for the specified INTEGER or FLOAT.  */
+
+int
+ctf_type_encoding (ctf_file_t *fp, ctf_id_t type, ctf_encoding_t *ep)
+{
+  ctf_file_t *ofp = fp;
+  ctf_dtdef_t *dtd;
+  const ctf_type_t *tp;
+  ssize_t increment;
+  uint32_t data;
+
+  if ((tp = ctf_lookup_by_id (&fp, type)) == NULL)
+    return CTF_ERR;            /* errno is set for us.  */
+
+  if ((dtd = ctf_dynamic_type (ofp, type)) != NULL)
+    {
+      *ep = dtd->dtd_u.dtu_enc;
+      return 0;
+    }
+
+  (void) ctf_get_ctt_size (fp, tp, NULL, &increment);
+
+  switch (LCTF_INFO_KIND (fp, tp->ctt_info))
+    {
+    case CTF_K_INTEGER:
+      data = *(const uint32_t *) ((uintptr_t) tp + increment);
+      ep->cte_format = CTF_INT_ENCODING (data);
+      ep->cte_offset = CTF_INT_OFFSET (data);
+      ep->cte_bits = CTF_INT_BITS (data);
+      break;
+    case CTF_K_FLOAT:
+      data = *(const uint32_t *) ((uintptr_t) tp + increment);
+      ep->cte_format = CTF_FP_ENCODING (data);
+      ep->cte_offset = CTF_FP_OFFSET (data);
+      ep->cte_bits = CTF_FP_BITS (data);
+      break;
+    case CTF_K_SLICE:
+      {
+       const ctf_slice_t *slice;
+       ctf_encoding_t underlying_en;
+
+       slice = (ctf_slice_t *) ((uintptr_t) tp + increment);
+       data = ctf_type_encoding (fp, slice->cts_type, &underlying_en);
+
+       ep->cte_format = underlying_en.cte_format;
+       ep->cte_offset = slice->cts_offset;
+       ep->cte_bits = slice->cts_bits;
+       break;
+      }
+    default:
+      return (ctf_set_errno (ofp, ECTF_NOTINTFP));
+    }
+
+  return 0;
+}
+
+int
+ctf_type_cmp (ctf_file_t *lfp, ctf_id_t ltype, ctf_file_t *rfp,
+             ctf_id_t rtype)
+{
+  int rval;
+
+  if (ltype < rtype)
+    rval = -1;
+  else if (ltype > rtype)
+    rval = 1;
+  else
+    rval = 0;
+
+  if (lfp == rfp)
+    return rval;
+
+  if (LCTF_TYPE_ISPARENT (lfp, ltype) && lfp->ctf_parent != NULL)
+    lfp = lfp->ctf_parent;
+
+  if (LCTF_TYPE_ISPARENT (rfp, rtype) && rfp->ctf_parent != NULL)
+    rfp = rfp->ctf_parent;
+
+  if (lfp < rfp)
+    return -1;
+
+  if (lfp > rfp)
+    return 1;
+
+  return rval;
+}
+
+/* Return a boolean value indicating if two types are compatible.  This function
+   returns true if the two types are the same, or if they (or their ultimate
+   base type) have the same encoding properties, or (for structs / unions /
+   enums / forward declarations) if they have the same name and (for structs /
+   unions) member count.  */
+
+int
+ctf_type_compat (ctf_file_t *lfp, ctf_id_t ltype,
+                ctf_file_t *rfp, ctf_id_t rtype)
+{
+  const ctf_type_t *ltp, *rtp;
+  ctf_encoding_t le, re;
+  ctf_arinfo_t la, ra;
+  uint32_t lkind, rkind;
+  int same_names = 0;
+
+  if (ctf_type_cmp (lfp, ltype, rfp, rtype) == 0)
+    return 1;
+
+  ltype = ctf_type_resolve (lfp, ltype);
+  lkind = ctf_type_kind (lfp, ltype);
+
+  rtype = ctf_type_resolve (rfp, rtype);
+  rkind = ctf_type_kind (rfp, rtype);
+
+  ltp = ctf_lookup_by_id (&lfp, ltype);
+  rtp = ctf_lookup_by_id (&rfp, rtype);
+
+  if (ltp != NULL && rtp != NULL)
+    same_names = (strcmp (ctf_strptr (lfp, ltp->ctt_name),
+                         ctf_strptr (rfp, rtp->ctt_name)) == 0);
+
+  if (((lkind == CTF_K_ENUM) && (rkind == CTF_K_INTEGER)) ||
+      ((rkind == CTF_K_ENUM) && (lkind == CTF_K_INTEGER)))
+    return 1;
+
+  if (lkind != rkind)
+    return 0;
+
+  switch (lkind)
+    {
+    case CTF_K_INTEGER:
+    case CTF_K_FLOAT:
+      memset (&le, 0, sizeof (le));
+      memset (&re, 0, sizeof (re));
+      return (ctf_type_encoding (lfp, ltype, &le) == 0
+             && ctf_type_encoding (rfp, rtype, &re) == 0
+             && memcmp (&le, &re, sizeof (ctf_encoding_t)) == 0);
+    case CTF_K_POINTER:
+      return (ctf_type_compat (lfp, ctf_type_reference (lfp, ltype),
+                              rfp, ctf_type_reference (rfp, rtype)));
+    case CTF_K_ARRAY:
+      return (ctf_array_info (lfp, ltype, &la) == 0
+             && ctf_array_info (rfp, rtype, &ra) == 0
+             && la.ctr_nelems == ra.ctr_nelems
+             && ctf_type_compat (lfp, la.ctr_contents, rfp, ra.ctr_contents)
+             && ctf_type_compat (lfp, la.ctr_index, rfp, ra.ctr_index));
+    case CTF_K_STRUCT:
+    case CTF_K_UNION:
+      return (same_names && (ctf_type_size (lfp, ltype)
+                            == ctf_type_size (rfp, rtype)));
+    case CTF_K_ENUM:
+      {
+       int lencoded, rencoded;
+       lencoded = ctf_type_encoding (lfp, ltype, &le);
+       rencoded = ctf_type_encoding (rfp, rtype, &re);
+
+       if ((lencoded != rencoded) ||
+           ((lencoded == 0) && memcmp (&le, &re, sizeof (ctf_encoding_t)) != 0))
+         return 0;
+      }
+      /* FALLTHRU */
+    case CTF_K_FORWARD:
+      return same_names;   /* No other checks required for these type kinds.  */
+    default:
+      return 0;                      /* Should not get here since we did a resolve.  */
+    }
+}
+
+/* Return the type and offset for a given member of a STRUCT or UNION.  */
+
+int
+ctf_member_info (ctf_file_t *fp, ctf_id_t type, const char *name,
+                ctf_membinfo_t *mip)
+{
+  ctf_file_t *ofp = fp;
+  const ctf_type_t *tp;
+  ssize_t size, increment;
+  uint32_t kind, n;
+
+  if ((type = ctf_type_resolve (fp, type)) == CTF_ERR)
+    return CTF_ERR;            /* errno is set for us.  */
+
+  if ((tp = ctf_lookup_by_id (&fp, type)) == NULL)
+    return CTF_ERR;            /* errno is set for us.  */
+
+  (void) ctf_get_ctt_size (fp, tp, &size, &increment);
+  kind = LCTF_INFO_KIND (fp, tp->ctt_info);
+
+  if (kind != CTF_K_STRUCT && kind != CTF_K_UNION)
+    return (ctf_set_errno (ofp, ECTF_NOTSOU));
+
+  if (size < CTF_LSTRUCT_THRESH)
+    {
+      const ctf_member_t *mp = (const ctf_member_t *) ((uintptr_t) tp +
+                                                      increment);
+
+      for (n = LCTF_INFO_VLEN (fp, tp->ctt_info); n != 0; n--, mp++)
+       {
+         if (strcmp (ctf_strptr (fp, mp->ctm_name), name) == 0)
+           {
+             mip->ctm_type = mp->ctm_type;
+             mip->ctm_offset = mp->ctm_offset;
+             return 0;
+           }
+       }
+    }
+  else
+    {
+      const ctf_lmember_t *lmp = (const ctf_lmember_t *) ((uintptr_t) tp +
+                                                         increment);
+
+      for (n = LCTF_INFO_VLEN (fp, tp->ctt_info); n != 0; n--, lmp++)
+       {
+         if (strcmp (ctf_strptr (fp, lmp->ctlm_name), name) == 0)
+           {
+             mip->ctm_type = lmp->ctlm_type;
+             mip->ctm_offset = (unsigned long) CTF_LMEM_OFFSET (lmp);
+             return 0;
+           }
+       }
+    }
+
+  return (ctf_set_errno (ofp, ECTF_NOMEMBNAM));
+}
+
+/* Return the array type, index, and size information for the specified ARRAY.  */
+
+int
+ctf_array_info (ctf_file_t *fp, ctf_id_t type, ctf_arinfo_t *arp)
+{
+  ctf_file_t *ofp = fp;
+  const ctf_type_t *tp;
+  const ctf_array_t *ap;
+  const ctf_dtdef_t *dtd;
+  ssize_t increment;
+
+  if ((tp = ctf_lookup_by_id (&fp, type)) == NULL)
+    return CTF_ERR;            /* errno is set for us.  */
+
+  if (LCTF_INFO_KIND (fp, tp->ctt_info) != CTF_K_ARRAY)
+    return (ctf_set_errno (ofp, ECTF_NOTARRAY));
+
+  if ((dtd = ctf_dynamic_type (ofp, type)) != NULL)
+    {
+      *arp = dtd->dtd_u.dtu_arr;
+      return 0;
+    }
+
+  (void) ctf_get_ctt_size (fp, tp, NULL, &increment);
+
+  ap = (const ctf_array_t *) ((uintptr_t) tp + increment);
+  arp->ctr_contents = ap->cta_contents;
+  arp->ctr_index = ap->cta_index;
+  arp->ctr_nelems = ap->cta_nelems;
+
+  return 0;
+}
+
+/* Convert the specified value to the corresponding enum tag name, if a
+   matching name can be found.  Otherwise NULL is returned.  */
+
+const char *
+ctf_enum_name (ctf_file_t *fp, ctf_id_t type, int value)
+{
+  ctf_file_t *ofp = fp;
+  const ctf_type_t *tp;
+  const ctf_enum_t *ep;
+  ssize_t increment;
+  uint32_t n;
+
+  if ((type = ctf_type_resolve_unsliced (fp, type)) == CTF_ERR)
+    return NULL;               /* errno is set for us.  */
+
+  if ((tp = ctf_lookup_by_id (&fp, type)) == NULL)
+    return NULL;               /* errno is set for us.  */
+
+  if (LCTF_INFO_KIND (fp, tp->ctt_info) != CTF_K_ENUM)
+    {
+      (void) ctf_set_errno (ofp, ECTF_NOTENUM);
+      return NULL;
+    }
+
+  (void) ctf_get_ctt_size (fp, tp, NULL, &increment);
+
+  ep = (const ctf_enum_t *) ((uintptr_t) tp + increment);
+
+  for (n = LCTF_INFO_VLEN (fp, tp->ctt_info); n != 0; n--, ep++)
+    {
+      if (ep->cte_value == value)
+       return (ctf_strptr (fp, ep->cte_name));
+    }
+
+  (void) ctf_set_errno (ofp, ECTF_NOENUMNAM);
+  return NULL;
+}
+
+/* Convert the specified enum tag name to the corresponding value, if a
+   matching name can be found.  Otherwise CTF_ERR is returned.  */
+
+int
+ctf_enum_value (ctf_file_t * fp, ctf_id_t type, const char *name, int *valp)
+{
+  ctf_file_t *ofp = fp;
+  const ctf_type_t *tp;
+  const ctf_enum_t *ep;
+  ssize_t increment;
+  uint32_t n;
+
+  if ((type = ctf_type_resolve_unsliced (fp, type)) == CTF_ERR)
+    return CTF_ERR;            /* errno is set for us.  */
+
+  if ((tp = ctf_lookup_by_id (&fp, type)) == NULL)
+    return CTF_ERR;            /* errno is set for us.  */
+
+  if (LCTF_INFO_KIND (fp, tp->ctt_info) != CTF_K_ENUM)
+    {
+      (void) ctf_set_errno (ofp, ECTF_NOTENUM);
+      return CTF_ERR;
+    }
+
+  (void) ctf_get_ctt_size (fp, tp, NULL, &increment);
+
+  ep = (const ctf_enum_t *) ((uintptr_t) tp + increment);
+
+  for (n = LCTF_INFO_VLEN (fp, tp->ctt_info); n != 0; n--, ep++)
+    {
+      if (strcmp (ctf_strptr (fp, ep->cte_name), name) == 0)
+       {
+         if (valp != NULL)
+           *valp = ep->cte_value;
+         return 0;
+       }
+    }
+
+  (void) ctf_set_errno (ofp, ECTF_NOENUMNAM);
+  return CTF_ERR;
+}
+
+/* Recursively visit the members of any type.  This function is used as the
+   engine for ctf_type_visit, below.  We resolve the input type, recursively
+   invoke ourself for each type member if the type is a struct or union, and
+   then invoke the callback function on the current type.  If any callback
+   returns non-zero, we abort and percolate the error code back up to the top.  */
+
+static int
+ctf_type_rvisit (ctf_file_t *fp, ctf_id_t type, ctf_visit_f *func,
+                void *arg, const char *name, unsigned long offset, int depth)
+{
+  ctf_id_t otype = type;
+  const ctf_type_t *tp;
+  ssize_t size, increment;
+  uint32_t kind, n;
+  int rc;
+
+  if ((type = ctf_type_resolve (fp, type)) == CTF_ERR)
+    return CTF_ERR;            /* errno is set for us.  */
+
+  if ((tp = ctf_lookup_by_id (&fp, type)) == NULL)
+    return CTF_ERR;            /* errno is set for us.  */
+
+  if ((rc = func (name, otype, offset, depth, arg)) != 0)
+    return rc;
+
+  kind = LCTF_INFO_KIND (fp, tp->ctt_info);
+
+  if (kind != CTF_K_STRUCT && kind != CTF_K_UNION)
+    return 0;
+
+  (void) ctf_get_ctt_size (fp, tp, &size, &increment);
+
+  if (size < CTF_LSTRUCT_THRESH)
+    {
+      const ctf_member_t *mp = (const ctf_member_t *) ((uintptr_t) tp +
+                                                      increment);
+
+      for (n = LCTF_INFO_VLEN (fp, tp->ctt_info); n != 0; n--, mp++)
+       {
+         if ((rc = ctf_type_rvisit (fp, mp->ctm_type,
+                                    func, arg, ctf_strptr (fp, mp->ctm_name),
+                                    offset + mp->ctm_offset,
+                                    depth + 1)) != 0)
+           return rc;
+       }
+
+    }
+  else
+    {
+      const ctf_lmember_t *lmp = (const ctf_lmember_t *) ((uintptr_t) tp +
+                                                         increment);
+
+      for (n = LCTF_INFO_VLEN (fp, tp->ctt_info); n != 0; n--, lmp++)
+       {
+         if ((rc = ctf_type_rvisit (fp, lmp->ctlm_type,
+                                    func, arg, ctf_strptr (fp,
+                                                           lmp->ctlm_name),
+                                    offset + (unsigned long) CTF_LMEM_OFFSET (lmp),
+                                    depth + 1)) != 0)
+           return rc;
+       }
+    }
+
+  return 0;
+}
+
+/* Recursively visit the members of any type.  We pass the name, member
+ type, and offset of each member to the specified callback function.  */
+int
+ctf_type_visit (ctf_file_t *fp, ctf_id_t type, ctf_visit_f *func, void *arg)
+{
+  return (ctf_type_rvisit (fp, type, func, arg, "", 0, 0));
+}