[v3,12/19] libctf: core type lookup

Message ID 20190524201046.427681-13-nick.alcock@oracle.com
State New
Headers show
Series
  • libctf, and CTF support for objdump and readelf
Related show

Commit Message

Nick Alcock May 24, 2019, 8:10 p.m.
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.

Changes from v1:
 - Correct erroneous license (GPLv2+ -> v3+) and reset copyright years.
 - Adjust for ctf_free() prototype change.
 - Enums and integers are assignment-compatible with each other.

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/ctf-api.h  |   36 ++
 libctf/ctf-decl.c  |  195 +++++++++
 libctf/ctf-impl.h  |    8 +
 libctf/ctf-types.c | 1023 ++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 1262 insertions(+)
 create mode 100644 libctf/ctf-decl.c
 create mode 100644 libctf/ctf-types.c

-- 
2.21.0.237.gd0cfaa883d

Patch

diff --git a/include/ctf-api.h b/include/ctf-api.h
index ee68efefe3..023c890d7d 100644
--- a/include/ctf-api.h
+++ b/include/ctf-api.h
@@ -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);
diff --git a/libctf/ctf-decl.c b/libctf/ctf-decl.c
new file mode 100644
index 0000000000..c85982e4a3
--- /dev/null
+++ b/libctf/ctf-decl.c
@@ -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;
+}
diff --git a/libctf/ctf-impl.h b/libctf/ctf-impl.h
index 8522a032dd..cd8450550e 100644
--- a/libctf/ctf-impl.h
+++ b/libctf/ctf-impl.h
@@ -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
index 0000000000..a7fe5d0b18
--- /dev/null
+++ b/libctf/ctf-types.c
@@ -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));
+}