[PATCH 2/2] stack: Add -i, --inlines. Show inlined call frames using DWARF debuginfo.

Mark Wielaard mjw at redhat.com
Tue Jan 21 15:20:06 UTC 2014


Using dwarf_getscopes_die we can get all scopes that make up the current
subprogram representing an address. Using the call_file/line/column
attributes we can also show the source locations of these "inlined" calls.
Includes a test that shows that when DWARF debuginfo is available all
inlined function call frames and their source location can be shown.

Signed-off-by: Mark Wielaard <mjw at redhat.com>
---
 src/ChangeLog             |   11 ++
 src/stack.c               |  256 +++++++++++++++++++++++++++++++--------------
 tests/ChangeLog           |    6 +
 tests/Makefile.am         |    4 +-
 tests/run-stack-i-test.sh |   70 ++++++++++++
 5 files changed, 267 insertions(+), 80 deletions(-)
 create mode 100755 tests/run-stack-i-test.sh

diff --git a/src/ChangeLog b/src/ChangeLog
index 143d73c..8bbcff4 100644
--- a/src/ChangeLog
+++ b/src/ChangeLog
@@ -1,3 +1,14 @@
+2014-01-21  Mark Wielaard  <mjw at redhat.com>
+
+	* stack.c (show_inlines): New static boolean.
+	(print_frame): New function split out from...
+	(print_frames): ..here. If show_inlines is true and we found a
+	DIE for the frame address, call print_inline_frames otherwise
+	call print_frame. Keep track of and track frame_nr.
+	(print_inline_frames): New function.
+	(parse_opt): Handle '-i'.
+	(main): Add 'i' to options.
+
 2014-01-20  Mark Wielaard  <mjw at redhat.com>
 
 	* stack.c (show_debugname): New static boolean.
diff --git a/src/stack.c b/src/stack.c
index ae0ef12..0ae4c12 100644
--- a/src/stack.c
+++ b/src/stack.c
@@ -51,8 +51,9 @@ static bool show_quiet = false;
 static bool show_raw = false;
 static bool show_modules = false;
 static bool show_debugname = false;
+static bool show_inlines = false;
 
-static int maxframes = 2048;
+static int maxframes = 256;
 
 struct frame
 {
@@ -212,13 +213,159 @@ die_name (Dwarf_Die *die)
 }
 
 static void
+print_frame (int nr, Dwarf_Addr pc, bool isactivation,
+	     Dwarf_Addr pc_adjusted, Dwfl_Module *mod,
+	     const char *symname, Dwarf_Die *cudie,
+	     Dwarf_Die *die)
+{
+  int width = get_addr_width (mod);
+  printf ("#%-2u 0x%0*" PRIx64, nr, width, (uint64_t) pc);
+
+  if (show_activation)
+    printf ("%4s", ! isactivation ? "- 1" : "");
+
+  if (symname != NULL)
+    {
+#ifdef USE_DEMANGLE
+      // Require GNU v3 ABI by the "_Z" prefix.
+      if (! show_raw && symname[0] == '_' && symname[1] == 'Z')
+	{
+	  int status = -1;
+	  char *dsymname = __cxa_demangle (symname, demangle_buffer,
+					   &demangle_buffer_len, &status);
+	  if (status == 0)
+	    symname = demangle_buffer = dsymname;
+	}
+#endif
+      printf (" %s", symname);
+    }
+
+  const char* fname;
+  Dwarf_Addr start;
+  fname = dwfl_module_info(mod, NULL, &start,
+			   NULL, NULL, NULL, NULL, NULL);
+  if (show_module)
+    {
+      if (fname != NULL)
+	printf (" - %s", fname);
+    }
+
+  if (show_build_id)
+    {
+      const unsigned char *id;
+      GElf_Addr id_vaddr;
+      int id_len = dwfl_module_build_id (mod, &id, &id_vaddr);
+      if (id_len > 0)
+	{
+	  printf ("\n    [");
+	  do
+	    printf ("%02" PRIx8, *id++);
+	  while (--id_len > 0);
+	  printf ("]@0x%0" PRIx64 "+0x%" PRIx64,
+		  start, pc_adjusted - start);
+	}
+    }
+
+  if (show_source)
+    {
+      int line, col;
+      const char* sname;
+      line = col = -1;
+      sname = NULL;
+      if (die != NULL)
+	{
+	  Dwarf_Files *files;
+	  if (dwarf_getsrcfiles (cudie, &files, NULL) == 0)
+	    {
+	      Dwarf_Attribute attr;
+	      Dwarf_Word val;
+	      if (dwarf_formudata (dwarf_attr (die, DW_AT_call_file, &attr),
+				   &val) == 0)
+		{
+		  sname = dwarf_filesrc (files, val, NULL, NULL);
+		  if (dwarf_formudata (dwarf_attr (die, DW_AT_call_line,
+						   &attr), &val) == 0)
+		    {
+		      line = val;
+		      if (dwarf_formudata (dwarf_attr (die, DW_AT_call_column,
+						       &attr), &val) == 0)
+			col = val;
+		    }
+		}
+	    }
+	}
+      else
+	{
+	  Dwfl_Line *lineobj = dwfl_module_getsrc(mod, pc_adjusted);
+	  if (lineobj)
+	    sname = dwfl_lineinfo (lineobj, NULL, &line, &col, NULL, NULL);
+	}
+
+      if (sname != NULL)
+	{
+	  printf ("\n    %s", sname);
+	  if (line > 0)
+	    {
+	      printf (":%d", line);
+	      if (col > 0)
+		printf (":%d", col);
+	    }
+	}
+    }
+  printf ("\n");
+}
+
+static void
+print_inline_frames (int *nr, Dwarf_Addr pc, bool isactivation,
+		     Dwarf_Addr pc_adjusted, Dwfl_Module *mod,
+		     const char *symname, Dwarf_Die *cudie, Dwarf_Die *die)
+{
+  Dwarf_Die *scopes = NULL;
+  int nscopes = dwarf_getscopes_die (die, &scopes);
+  if (nscopes > 0)
+    {
+      /* scopes[0] == die, the lowest level, for which we already have
+	 the name.  This is the actual source location where it
+	 happened.  */
+      print_frame ((*nr)++, pc, isactivation, pc_adjusted, mod, symname,
+		   NULL, NULL);
+
+      /* last_scope is the source location where the next frame/function
+	 call was done. */
+      Dwarf_Die *last_scope = &scopes[0];
+      for (int i = 1; i < nscopes && (maxframes == 0 || *nr < maxframes); i++)
+	{
+	  Dwarf_Die *scope = &scopes[i];
+	  int tag = dwarf_tag (scope);
+	  if (tag != DW_TAG_inlined_subroutine
+	      && tag != DW_TAG_entry_point
+	      && tag != DW_TAG_subprogram)
+	    continue;
+
+	  symname = die_name (scope);
+	  print_frame ((*nr)++, pc, isactivation, pc_adjusted, mod, symname,
+		       cudie, last_scope);
+
+	  /* Found the "top-level" in which everything was inlined?  */
+	  if (tag == DW_TAG_subprogram)
+	    break;
+
+	  last_scope = scope;
+	}
+    }
+  free (scopes);
+}
+
+static void
 print_frames (struct frames *frames, pid_t tid, int dwflerr, const char *what)
 {
   if (frames->frames > 0)
     frames_shown = true;
 
   printf ("TID %d:\n", tid);
-  for (int nr = 0; nr < frames->frames; nr++)
+  int frame_nr = 0;
+  for (int nr = 0; nr < frames->frames && (maxframes == 0
+					   || frame_nr < maxframes); nr++)
     {
       Dwarf_Addr pc = frames->frame[nr].pc;
       bool isactivation = frames->frame[nr].isactivation;
@@ -227,13 +374,16 @@ print_frames (struct frames *frames, pid_t tid, int dwflerr, const char *what)
       /* Get PC->SYMNAME.  */
       Dwfl_Module *mod = dwfl_addrmodule (dwfl, pc_adjusted);
       const char *symname = NULL;
+      Dwarf_Die die_mem;
+      Dwarf_Die *die = NULL;
+      Dwarf_Die *cudie = NULL;
       if (mod && ! show_quiet)
 	{
 	  if (show_debugname)
 	    {
 	      Dwarf_Addr bias = 0;
-	      Dwarf_Die *cudie = dwfl_module_addrdie (mod, pc_adjusted, &bias);
 	      Dwarf_Die *scopes = NULL;
+	      cudie = dwfl_module_addrdie (mod, pc_adjusted, &bias);
 	      int nscopes = dwarf_getscopes (cudie, pc_adjusted - bias,
 					     &scopes);
 
@@ -246,6 +396,12 @@ print_frames (struct frames *frames, pid_t tid, int dwflerr, const char *what)
 		      || tag == DW_TAG_inlined_subroutine
 		      || tag == DW_TAG_entry_point)
 		    symname = die_name (scope);
+
+		  if (symname != NULL)
+		    {
+		      die_mem = *scope;
+		      die = &die_mem;
+		    }
 		}
 	      free (scopes);
 	    }
@@ -254,78 +410,18 @@ print_frames (struct frames *frames, pid_t tid, int dwflerr, const char *what)
 	    symname = dwfl_module_addrname (mod, pc_adjusted);
 	}
 
-      int width = get_addr_width (mod);
-      printf ("#%-2u 0x%0*" PRIx64, nr, width, (uint64_t) pc);
-
-      if (show_activation)
-	printf ("%4s", ! isactivation ? "- 1" : "");
-
-      if (symname != NULL)
-	{
-#ifdef USE_DEMANGLE
-	  // Require GNU v3 ABI by the "_Z" prefix.
-	  if (! show_raw && symname[0] == '_' && symname[1] == 'Z')
-	    {
-	      int status = -1;
-	      char *dsymname = __cxa_demangle (symname, demangle_buffer,
-					       &demangle_buffer_len, &status);
-	      if (status == 0)
-		symname = demangle_buffer = dsymname;
-	    }
-#endif
-	  printf (" %s", symname);
-	}
-
-      const char* fname;
-      Dwarf_Addr start;
-      fname = dwfl_module_info(mod, NULL, &start,
-			       NULL, NULL, NULL, NULL, NULL);
-      if (show_module)
-	{
-	  if (fname != NULL)
-	    printf (" - %s", fname);
-	}
-
-      if (show_build_id)
-	{
-	  const unsigned char *id;
-	  GElf_Addr id_vaddr;
-	  int id_len = dwfl_module_build_id (mod, &id, &id_vaddr);
-	  if (id_len > 0)
-	    {
-	      printf ("\n    [");
-	      do
-		printf ("%02" PRIx8, *id++);
-	      while (--id_len > 0);
-	      printf ("]@0x%0" PRIx64 "+0x%" PRIx64,
-		      start, pc_adjusted - start);
-	    }
-	}
-
-      if (show_source)
-	{
-	  Dwfl_Line *lineobj = dwfl_module_getsrc(mod, pc_adjusted);
-	  if (lineobj)
-	    {
-	      int line, col;
-	      const char* sname;
-	      line = col = -1;
-	      sname = dwfl_lineinfo (lineobj, NULL, &line, &col, NULL, NULL);
-	      if (sname != NULL)
-		{
-		  printf ("\n    %s", sname);
-		  if (line > 0)
-		    {
-		      printf (":%d", line);
-		      if (col > 0)
-			printf (":%d", col);
-		    }
-		}
-	    }
-	}
-      printf ("\n");
+      if (show_inlines && die != NULL)
+	print_inline_frames (&frame_nr, pc, isactivation, pc_adjusted, mod,
+			     symname, cudie, die);
+      else
+	print_frame (frame_nr++, pc, isactivation, pc_adjusted, mod, symname,
+		     NULL, NULL);
     }
-  if (dwflerr != 0)
+
+  if (frames->frames > 0 && frame_nr == maxframes)
+    error (0, 0, "tid %d: shown max number of frames "
+	   "(%d, use -n 0 for unlimited)", tid, maxframes);
+  else if (dwflerr != 0)
     {
       if (frames->frames > 0)
 	{
@@ -350,9 +446,6 @@ print_frames (struct frames *frames, pid_t tid, int dwflerr, const char *what)
       else
 	error (0, 0, "%s tid %d: %s", what, tid, dwfl_errmsg (dwflerr));
     }
-  else if (frames->frames > 0 && frames->frames == maxframes)
-    error (0, 0, "tid %d: shown max number of frames "
-	   "(%d, use -n 0 for unlimited)", tid, maxframes);
 }
 
 static int
@@ -429,8 +522,13 @@ parse_opt (int key, char *arg __attribute__ ((unused)),
       show_debugname = true;
       break;
 
+    case 'i':
+      show_inlines = show_debugname = true;
+      break;
+
     case 'v':
       show_activation = show_source = show_module = show_debugname = true;
+      show_inlines = true;
       break;
 
     case 'b':
@@ -556,12 +654,14 @@ main (int argc, char **argv)
       { "debugname",  'd', NULL, 0,
 	N_("Additionally try to lookup DWARF debuginfo name for frame address"),
 	0 },
+      { "inlines",  'i', NULL, 0,
+	N_("Additionally show inlined function frames using DWARF debuginfo if available (implies -d)"), 0 },
       { "module",  'm', NULL, 0,
 	N_("Additionally show module file information"), 0 },
       { "source",  's', NULL, 0,
 	N_("Additionally show source file information"), 0 },
       { "verbose", 'v', NULL, 0,
-	N_("Show all additional information (activation, debugname, module and source)"), 0 },
+	N_("Show all additional information (activation, debugname, inlines, module and source)"), 0 },
       { "quiet", 'q', NULL, 0,
 	N_("Do not resolve address to function symbol name"), 0 },
       { "raw", 'r', NULL, 0,
diff --git a/tests/ChangeLog b/tests/ChangeLog
index 70b0551..aa468c4 100644
--- a/tests/ChangeLog
+++ b/tests/ChangeLog
@@ -1,3 +1,9 @@
+2014-01-21  Mark Wielaard  <mjw at redhat.com>
+
+	* Makefile.am (TESTS): Add run-stack-i-test.sh.
+	(EXTRA_DIST): Likewise.
+	* run-stack-i-test.sh: New test.
+
 2014-01-20  Mark Wielaard  <mjw at redhat.com>
 
 	* Makefile.am (TESTS): Add run-stack-d-test.sh.
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 35f6e87..aa13cfa 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -108,7 +108,7 @@ TESTS = run-arextract.sh run-arsymtest.sh newfile test-nlist \
 	run-backtrace-native-core-biarch.sh run-backtrace-core-x86_64.sh \
 	run-backtrace-core-i386.sh run-backtrace-core-ppc.sh \
 	run-backtrace-core-s390x.sh run-backtrace-core-s390.sh \
-	run-backtrace-demangle.sh run-stack-d-test.sh
+	run-backtrace-demangle.sh run-stack-d-test.sh run-stack-i-test.sh
 
 if !BIARCH
 export ELFUTILS_DISABLE_BIARCH = 1
@@ -265,7 +265,7 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh \
 	     run-backtrace-demangle.sh testfile-backtrace-demangle.bz2 \
 	     testfile-backtrace-demangle.cc \
 	     testfile-backtrace-demangle.core.bz2 \
-	     run-stack-d-test.sh \
+	     run-stack-d-test.sh run-stack-i-test.sh \
 	     testfiledwarfinlines.bz2 testfiledwarfinlines.core.bz2
 
 if USE_VALGRIND
diff --git a/tests/run-stack-i-test.sh b/tests/run-stack-i-test.sh
new file mode 100755
index 0000000..2d09ec0
--- /dev/null
+++ b/tests/run-stack-i-test.sh
@@ -0,0 +1,70 @@
+#! /bin/sh
+# Copyright (C) 2014 Red Hat, Inc.
+# This file is part of elfutils.
+#
+# This file 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 of the License, or
+# (at your option) any later version.
+#
+# elfutils 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.  If not, see <http://www.gnu.org/licenses/>.
+
+. $srcdir/test-subr.sh
+
+# See run-stack-d-test.sh for dwarfinlines.cpp source.
+testfiles testfiledwarfinlines testfiledwarfinlines.core
+
+# Depending on whether we are running make check or make installcheck
+# the actual binary name under test might be different. It is used in
+# the error message, which we also try to match.
+if test "$elfutils_testrun" = "installed"; then
+STACKCMD=${bindir}/`program_transform stack`
+else
+STACKCMD=${abs_top_builddir}/src/stack
+fi
+
+# Compare with run-stack-d-test.sh to see the output without --inlines.
+# Only two call frames are visible (there is a jump from main to fu or
+# fubar).
+
+# With --inlines we get all inlined calls. Note they share the same
+# address.
+testrun_compare ${abs_top_builddir}/src/stack -n 6 -i -e testfiledwarfinlines --core testfiledwarfinlines.core<<EOF
+PID 13654 - core
+TID 13654:
+#0  0x00000000004006c8 fubar
+#1  0x00000000004006c8 foobar
+#2  0x00000000004006c8 bar
+#3  0x00000000004006c8 foo
+#4  0x00000000004006c8 fu(int)
+#5  0x00000000004004c5 main
+$STACKCMD: tid 13654: shown max number of frames (6, use -n 0 for unlimited)
+EOF
+
+# With --source we can also see where in the source the inlined frames
+# where originally called from.
+testrun_compare ${abs_top_builddir}/src/stack -n 6 -s -i -e testfiledwarfinlines --core testfiledwarfinlines.core<<EOF
+PID 13654 - core
+TID 13654:
+#0  0x00000000004006c8 fubar
+    /home/mark/src/tests/dwarfinlines.cpp:6
+#1  0x00000000004006c8 foobar
+    /home/mark/src/tests/dwarfinlines.cpp:14
+#2  0x00000000004006c8 bar
+    /home/mark/src/tests/dwarfinlines.cpp:21
+#3  0x00000000004006c8 foo
+    /home/mark/src/tests/dwarfinlines.cpp:27
+#4  0x00000000004006c8 fu(int)
+    /home/mark/src/tests/dwarfinlines.cpp:33
+#5  0x00000000004004c5 main
+    /home/mark/src/tests/dwarfinlines.cpp:39
+$STACKCMD: tid 13654: shown max number of frames (6, use -n 0 for unlimited)
+EOF
+
+exit 0
-- 
1.7.1



More information about the elfutils-devel mailing list