[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