1. Wez Furlong
  2. gimli

Commits

Wez Furlong  committed d4034c0

Add a configure option to link against libunwind

This is wired up for linux only at the moment; Darwin has a mach specific
implementation that we could probably adapt to in the future, and I just haven't
gotten around to trying this with Illumos. Also: Solaris systems tend to be
very good about keeping frame pointers and don't typically need DWARF unwinding.

If you request libunwind and it is not present, configure will not fail; you'll
just end up using the built-in unwinder.

Why? I've seen some issues with the built-in unwinder and haven't had
time to delve in and resolve them. They manifest as apparent "invalid read"
attempts when dumping out the full stack trace. What is actually happening
is that the unwind has lost track of the registers and cannot properly
locate the variables in the frames.

  • Participants
  • Parent commits 1db7ec1
  • Branches default

Comments (0)

Files changed (8)

File Makefile.am

View file
 	trace.c linux.c elf.c hash.c elf-read.c dwarf-read.c dwarf-unwind.c \
 	dwarf-expr.c darwin.c solaris.c demangle.c freebsd.c proc.c \
 	proc_service.c symbols.c types.c maps.c apiv2.c print.c slab.c \
-	apiv3.c module.c
+	apiv3.c module.c unwind-unwind.c
 
 libgimli_la_SOURCES = \
   heartbeat.c

File configure.ac

View file
 esac
 AC_DEFINE_UNQUOTED([MODULE_SUFFIX], "$MODULE_SUFFIX", [platform module suffix])
 
-CFLAGS="$CFLAGS $DWARF_DEBUG_FLAGS -DGIMLI_GLIDER_PATH=\\\"\$(bindir)/glider\\\""
+CFLAGS="$CFLAGS $DWARF_DEBUG_FLAGS -DGIMLI_GLIDER_PATH=\"\\\"\$(bindir)/glider\\\"\""
 LDFLAGS="$LDFLAGS $DWARF_DEBUG_FLAGS"
 AC_SUBST(CORONER_LDFLAGS)
 AC_SUBST(DARWIN_DSYMUTIL)
 AC_CHECK_LIB(crypt, crypt)
 AC_CHECK_FUNCS(clock_gettime)
 
+AC_ARG_WITH(libunwind,
+[  --with-libunwind    Use libunwind instead of the built-in dwarf unwinder],
+[
+  dnl Can't use CHECK_LIB because the header files do symbol munging
+  AC_MSG_CHECKING([for libunwind])
+  libs="$LIBS"
+  LIBS="$LIBS -lunwind-ptrace -lunwind-generic"
+  AC_TRY_LINK([
+  #include <libunwind.h>
+  #include <libunwind-ptrace.h>
+  ],[
+  unw_accessors_t accessors;
+  accessors.find_proc_info = _UPT_find_proc_info;
+  unw_create_addr_space(0,0);
+  ],[
+    AC_DEFINE(HAVE_LIBUNWIND, 1, [Have libunwind])
+    AC_MSG_RESULT([yes])
+    CORONER_LDFLAGS="$CORONER_LDFLAGS -lunwind-ptrace -lunwind-generic"
+  ],[
+    AC_MSG_RESULT([no])
+  ])
+  LIBS="$libs"
+])
 AC_CHECK_SIZEOF(void*)
 
 AC_DEFINE(LUA_USE_POSIX, 1, [Use POSIX functions])

File impl.h

View file
 #endif
 #include "libgimli.h"
 #include "libgimli_ana.h"
+#ifdef HAVE_LIBUNWIND
+# include <libunwind.h>
+# include <libunwind-ptrace.h>
+#endif
 
 #ifdef __cplusplus
 extern "C" {
   gimli_proc_t proc;
   struct gimli_thread_state st;
   struct gimli_dwarf_unwind_state dw;
+#ifdef HAVE_LIBUNWIND
+  unw_cursor_t unw_cursor;
+#endif
   /* if a signal frame, the signal that triggered it */
   siginfo_t si;
   int frameno;
    * for /proc/pid/mem. */
   int proc_mem;
 #endif
+#if HAVE_LIBUNWIND
+  unw_addr_space_t unw_addrspace;
+  void *unw_upt;
+#endif
 
   /** list of threads */
   STAILQ_HEAD(threadlist, gimli_thread_state) threads;
 int gimli_thread_regs_to_dwarf(struct gimli_unwind_cursor *cur);
 void *gimli_reg_addr(struct gimli_unwind_cursor *cur, int col);
 
+#ifdef HAVE_LIBUNWIND
+int gimli_unw_proc_init(gimli_proc_t proc);
+int gimli_unw_unwind_next(struct gimli_unwind_cursor *cur);
+int gimli_unw_unwind_init(struct gimli_unwind_cursor *cur);
+#endif
+
 static inline int gimli_reg_get(struct gimli_unwind_cursor *cur,
     int col, gimli_addr_t *val)
 {

File linux.c

View file
   struct gimli_thread_state *st)
 {
   memcpy(&cur->st, st, sizeof(*st));
+#ifdef HAVE_LIBUNWIND
+  gimli_unw_unwind_init(cur);
+#endif
   return 1;
 }
 
 
 int gimli_unwind_next(struct gimli_unwind_cursor *cur)
 {
+#ifdef HAVE_LIBUNWIND
+  return gimli_unw_unwind_next(cur);
+#else
   /* generic x86 backtrace */
   struct x86_frame {
     struct x86_frame *next;
   }
 
   return 0;
+#endif
 }
 
 static int child_stopped = 0;

File proc.c

View file
   }
 #endif
   proc->proc_stat.pid = proc->pid;
+
+#ifdef HAVE_LIBUNWIND
+  gimli_unw_proc_init(proc);
+#endif
 }
 
 
   if (create) {
     thr = calloc(1, sizeof(*thr));
     thr->lwpid = lwpid;
+    thr->proc = proc;
 
     STAILQ_INSERT_TAIL(&proc->threads, thr, threadlist);
     return thr;

File proc_service.c

View file
     return 0;
   }
 
-  if (info.ti_lid != proc->pid && 
+  if (info.ti_lid != proc->pid &&
       gimli_ptrace(PTRACE_DETACH, info.ti_lid, NULL, (void*)SIGCONT)) {
     fprintf(stderr, "resume_threads: failed to detach from thread %d %s\n",
         info.ti_lid, strerror(errno));
 {
   int i, done = 0, tries = 20;
   td_err_e te;
-  int nthreads;
+  int nthreads = 0;
   struct gimli_thread_state *thr;
 
 #ifdef sun
     td_ta_thr_iter(proc->ta, enum_threads1, proc, TD_THR_ANY_STATE,
       TD_THR_LOWEST_PRIORITY, TD_SIGNO_MASK, TD_THR_ANY_USER_FLAGS);
 
-    nthreads = 0;
     STAILQ_FOREACH(thr, &proc->threads, threadlist) {
       nthreads++;
     }

File trace.c

View file
     { "main", 0 },
 #ifdef __linux__
     { "start_thread", 1 },
+    { "__libc_start_main", 1 },
 #endif
 #ifdef sun
     { "_thr_setup", 1 },

File unwind-unwind.c

View file
+/* Copyright (c) 2013-present Facebook. All Rights Reserved
+ * For licensing information, see:
+ * https://bitbucket.org/wez/gimli/src/tip/LICENSE
+ */
+
+/* This uses libunwind to unwind stack frames.
+ * It does this by driving the libunwind-ptrace implementation.  Since we want
+ * to manage the actual ptrace() calls (we've already stopped the process, so
+ * don't want unwind stopping/starting on top of our management of the target),
+ * we wrap around libunwind rather than just calling the ptrace implementation
+ * directly.
+ */
+
+#include "impl.h"
+
+#ifdef HAVE_LIBUNWIND
+
+#define FETCH_CUR struct gimli_unwind_cursor *cur = arg
+
+static int find_proc_info(unw_addr_space_t as, unw_word_t ip,
+  unw_proc_info_t *pip, int need_unwind_info, void *arg)
+{
+  FETCH_CUR;
+
+  return _UPT_find_proc_info(as, ip, pip, need_unwind_info,
+      cur->proc->unw_upt);
+}
+
+static void put_unwind_info(unw_addr_space_t as, unw_proc_info_t *pip,
+    void *arg)
+{
+  FETCH_CUR;
+
+  _UPT_put_unwind_info(as, pip, cur->proc->unw_upt);
+}
+
+static int get_dyn_info_list_addr(unw_addr_space_t as, unw_word_t *dilap,
+    void *arg)
+{
+  FETCH_CUR;
+
+  return _UPT_get_dyn_info_list_addr(as, dilap, cur->proc->unw_upt);
+}
+
+static int access_mem(unw_addr_space_t as, unw_word_t addr,
+    unw_word_t *valp, int write, void *arg)
+{
+  if (write) {
+    fprintf(stderr, "libunwind wants to write to memory at " PTRFMT "\n",
+        addr);
+    abort();
+  }
+
+  // We use `the_proc` directly here, because the unwind-ptrace implementation
+  // calls into access_mem but sets arg to unw_upt and not our unwind
+  // cursor pointer
+  if (gimli_read_mem(the_proc, addr, valp, sizeof(*valp)) == sizeof(*valp)) {
+    return 0;
+  }
+  return -UNW_EINVAL;
+}
+
+/* Access register number REG at address ADDR.  */
+static int access_reg(unw_addr_space_t as, unw_regnum_t regnum,
+    unw_word_t *valp, int write, void *arg)
+{
+  FETCH_CUR;
+
+  void **regaddr = gimli_reg_addr(cur, regnum);
+  if (write) {
+    fprintf(stderr, "libunwind wants to store " PTRFMT " to reg %d\n",
+        (PTRFMT_T)*valp, regnum);
+    abort();
+  }
+  *valp = (intptr_t)*regaddr;
+  return 0;
+}
+
+static int access_fpreg(unw_addr_space_t as, unw_regnum_t regnum,
+    unw_fpreg_t *valp, int write , void *arg)
+{
+  FETCH_CUR;
+  fprintf(stderr, "access_fpreg: regno=%d\n", regnum);
+  return -UNW_EUNSPEC;
+}
+
+static int resume(unw_addr_space_t as, unw_cursor_t *cp, void *arg)
+{
+  FETCH_CUR;
+  fprintf(stderr, "unw_resume called, but shouldn't have been\n");
+  abort();
+}
+
+static unw_accessors_t accessors = {
+  find_proc_info,
+  put_unwind_info,
+  get_dyn_info_list_addr,
+  access_mem,
+  access_reg,
+  access_fpreg,
+  resume,
+  NULL, // get_proc_name: not used here
+};
+
+int gimli_unw_proc_init(gimli_proc_t proc)
+{
+  // These are both leaked.  We're short lived so this doesn't matter.
+  proc->unw_addrspace = unw_create_addr_space(&accessors, 0);
+  proc->unw_upt = _UPT_create(proc->pid);
+
+  return 1;
+}
+
+int gimli_unw_unwind_init(struct gimli_unwind_cursor *cur)
+{
+  int err = unw_init_remote(&cur->unw_cursor, cur->proc->unw_addrspace, cur);
+
+  if (err != 0) {
+    fprintf(stderr, "unw_init_remote: %d %s\n",
+        err, unw_strerror(err));
+    return 0;
+  }
+
+  return 1;
+}
+
+int gimli_unw_unwind_next(struct gimli_unwind_cursor *cur)
+{
+  if (unw_step(&cur->unw_cursor)) {
+    int i = 0;
+
+    unw_get_reg(&cur->unw_cursor, UNW_REG_IP, &cur->st.pc);
+    unw_get_reg(&cur->unw_cursor, UNW_REG_SP, &cur->st.fp);
+    cur->st.sp = cur->st.fp;
+
+    // Ask unwind to fill out information on all of the registers,
+    // then update our cursor with them.  Failure to do this results
+    // in being unable to read the correct location of variables
+    // when we print out the full stack trace
+    while (1) {
+      // gimli_reg_addr returns NULL for invalid register offsets;
+      // leverage that to avoid replicating machdep logic in here
+      unw_word_t *valp = (unw_word_t*)gimli_reg_addr(cur, i);
+      if (!valp) {
+        break;
+      }
+
+      unw_get_reg(&cur->unw_cursor, i, valp);
+      i++;
+    }
+
+    return 1;
+  }
+  return 0;
+}
+
+#endif /* HAVE_LIBUNWIND */
+
+/* vim:ts=2:sw=2:et:
+ */
+