Blob Blame History Raw
From 553b6df07c9b7ab30ed468a6a4374cbdf73d1c0d Mon Sep 17 00:00:00 2001
From: Mark Wielaard <mark@klomp.org>
Date: Tue, 17 Apr 2018 14:36:13 +0200
Subject: [PATCH] linux runtime: Add support for new kernel unwind fallback.

In newer kernels dump_trace got replaced by a new unwind infrastructure.
Add a new autoconf-unwind-stack-trace.c to detect whether we can use it.
Extend the runtime/stack.c _stp_stack_print_fallback with a new pt_regs*
argument. Update all callers and add dbug_unwind output to show which
fallback unwinder we are selecting (or if we are just giving up).
Rename the struct unwind_state in unwind.c and unwind.h to uw_state
because the old name now conflicts with the one used in the kernel.
---
 buildrun.cxx                                |  2 ++
 runtime/linux/autoconf-unwind-stack-trace.c | 16 +++++++++
 runtime/stack.c                             | 50 ++++++++++++++++++++++++++---
 runtime/unwind.c                            | 14 ++++----
 runtime/unwind/unwind.h                     |  4 +--
 5 files changed, 72 insertions(+), 14 deletions(-)
 create mode 100644 runtime/linux/autoconf-unwind-stack-trace.c

diff --git a/buildrun.cxx b/buildrun.cxx
index 403fa71..59b9e88 100644
--- a/buildrun.cxx
+++ b/buildrun.cxx
@@ -365,6 +365,8 @@ compile_pass (systemtap_session& s)
                   "STAPCONF_KERNEL_STACKTRACE", NULL);
   output_autoconf(s, o, "autoconf-save-stack-trace-no-bp.c",
                   "STAPCONF_KERNEL_STACKTRACE_NO_BP", NULL);
+  output_autoconf(s, o, "autoconf-unwind-stack-trace.c",
+                  "STAPCONF_KERNEL_UNWIND_STACK", NULL);
   output_autoconf(s, o, "autoconf-asm-syscall.c",
 		  "STAPCONF_ASM_SYSCALL_H", NULL);
   output_autoconf(s, o, "autoconf-ring_buffer-flags.c", "STAPCONF_RING_BUFFER_FLAGS", NULL);
diff --git a/runtime/linux/autoconf-unwind-stack-trace.c b/runtime/linux/autoconf-unwind-stack-trace.c
new file mode 100644
index 0000000..2ec399e
--- /dev/null
+++ b/runtime/linux/autoconf-unwind-stack-trace.c
@@ -0,0 +1,16 @@
+#include <linux/sched.h>
+#include <asm/unwind.h>
+
+void unwind_stack_trace (void)
+{
+  struct unwind_state state;
+  unwind_start (&state, current, 0, 0);
+  while (! unwind_done (&state))
+    {
+      unsigned long addr = unwind_get_return_address (&state);
+      if (addr == 0)
+	break;
+      unwind_next_frame (&state);
+    }
+}
+
diff --git a/runtime/stack.c b/runtime/stack.c
index c9d2c0c..43f98ef 100644
--- a/runtime/stack.c
+++ b/runtime/stack.c
@@ -43,7 +43,11 @@
 #include <asm/stacktrace.h>
 #endif
 
-static void _stp_stack_print_fallback(unsigned long, int, int, int);
+#if defined(STAPCONF_KERNEL_UNWIND_STACK)
+#include <asm/unwind.h>
+#endif
+
+static void _stp_stack_print_fallback(unsigned long, struct pt_regs*, int, int, int);
 
 #ifdef STP_USE_DWARF_UNWINDER
 #ifdef STAPCONF_LINUX_UACCESS_H
@@ -128,7 +132,7 @@ static const struct stacktrace_ops print_stack_ops = {
 };
 
 /* Used for kernel backtrace printing when other mechanisms fail. */
-static void _stp_stack_print_fallback(unsigned long stack,
+static void _stp_stack_print_fallback(unsigned long stack, struct pt_regs *regs,
 				      int sym_flags, int levels, int skip)
 {
         struct print_stack_data print_data;
@@ -136,20 +140,55 @@ static void _stp_stack_print_fallback(unsigned long stack,
         print_data.levels = levels;
         print_data.skip = skip;
 #if defined(STAPCONF_KERNEL_STACKTRACE)
+        dbug_unwind(1, "fallback kernel stacktrace\n");
         dump_trace(current, NULL, (long *)stack, 0, &print_stack_ops,
                    &print_data);
 #else
 	/* STAPCONF_KERNEL_STACKTRACE_NO_BP */
+        dbug_unwind(1, "fallback kernel stacktrace (no bp)\n");
         dump_trace(current, NULL, (long *)stack, &print_stack_ops,
                    &print_data);
 #endif
 }
 #else
-static void _stp_stack_print_fallback(unsigned long s, int v, int l, int k) {
+#if defined(STAPCONF_KERNEL_UNWIND_STACK)
+static void _stp_stack_print_fallback(unsigned long sp, struct pt_regs *regs,
+				      int sym_flags,
+				      int levels, int skip) {
+	struct unwind_state state;
+	unwind_start (&state, current, regs, (unsigned long *) sp);
+	dbug_unwind(1, "fallback kernel stacktrace (unwind)\n");
+	while (levels > 0 && ! unwind_done (&state))
+	  {
+	    if (skip == 0)
+	      {
+		unsigned long addr = unwind_get_return_address (&state);
+		/* When we have frame pointers, the unwind addresses can be
+		   (mostly) trusted, otherwise it is all guesswork.  */
+#ifdef CONFIG_FRAME_POINTER
+		_stp_print_addr(addr, sym_flags, NULL);
+#else
+		_stp_print_addr(addr, sym_flags | _STP_SYM_INEXACT, NULL);
+#endif
+		if (addr == 0)
+		  break;
+		levels--;
+	      }
+	    else
+	      {
+		dbug_unwind(1, "skipping frame\n");
+	        skip--;
+	      }
+	    unwind_next_frame(&state);
+	  }
+}
+#else /* no new unwind */
+static void _stp_stack_print_fallback(unsigned long s, struct pt_regs *r, int v, int l, int k) {
 	/* Don't guess, just give up. */
+        dbug_unwind(1, "no fallback kernel stacktrace (giving up)\n");
 	_stp_print_addr(0, v | _STP_SYM_INEXACT, NULL);
 }
-
+#endif /* new unwind */
 #endif /* defined(STAPCONF_KERNEL_STACKTRACE) || defined(STAPCONF_KERNEL_STACKTRACE_NO_BP) */
 
 
@@ -382,6 +421,7 @@ static void _stp_stack_kernel_print(struct context *c, int sym_flags)
 		if (l == 0) {
 			remaining = MAXBACKTRACE - n;
 			_stp_stack_print_fallback(UNW_SP(&c->uwcontext_kernel.info),
+						  &c->uwcontext_kernel.info.regs,
 						  sym_flags, remaining, 0);
 			break;
 		} else {
@@ -408,7 +448,7 @@ static void _stp_stack_kernel_print(struct context *c, int sym_flags)
 		sp = 0;
 		skip = 5; /* yes, that many framework frames. */
 #endif
-		_stp_stack_print_fallback(sp, sym_flags,
+		_stp_stack_print_fallback(sp, NULL, sym_flags,
 					  MAXBACKTRACE, skip);
 #else
 		if (sym_flags & _STP_SYM_SYMBOL)
diff --git a/runtime/unwind.c b/runtime/unwind.c
index ec7cd58..3a2d991 100644
--- a/runtime/unwind.c
+++ b/runtime/unwind.c
@@ -235,7 +235,7 @@ static int parse_fde_cie(const u32 *fde, const u32 *cie,
 
 #define REG_STATE state->reg[state->stackDepth]
 
-static int advance_loc(unsigned long delta, struct unwind_state *state)
+static int advance_loc(unsigned long delta, struct uw_state *state)
 {
 	state->loc += delta * state->codeAlign;
 	dbug_unwind(1, "state->loc=%lx\n", state->loc);
@@ -244,7 +244,7 @@ static int advance_loc(unsigned long delta, struct unwind_state *state)
 
 /* Set Same or Nowhere rule for register. */
 static void set_no_state_rule(uleb128_t reg, enum item_location where,
-                              struct unwind_state *state)
+                              struct uw_state *state)
 {
 	dbug_unwind(1, "reg=%lx, where=%d\n", reg, where);
 	if (reg < ARRAY_SIZE(REG_STATE.regs)) {
@@ -254,7 +254,7 @@ static void set_no_state_rule(uleb128_t reg, enum item_location where,
 
 /* Memory or Value rule */
 static void set_offset_rule(uleb128_t reg, enum item_location where,
-                            sleb128_t svalue, struct unwind_state *state)
+                            sleb128_t svalue, struct uw_state *state)
 {
 	dbug_unwind(1, "reg=%lx, where=%d, svalue=%lx\n", reg, where, svalue);
 	if (reg < ARRAY_SIZE(REG_STATE.regs)) {
@@ -265,7 +265,7 @@ static void set_offset_rule(uleb128_t reg, enum item_location where,
 
 /* Register rule. */
 static void set_register_rule(uleb128_t reg, uleb128_t value,
-                              struct unwind_state *state)
+                              struct uw_state *state)
 {
 	dbug_unwind(1, "reg=%lx, value=%lx\n", reg, value);
 	if (reg < ARRAY_SIZE(REG_STATE.regs)) {
@@ -277,7 +277,7 @@ static void set_register_rule(uleb128_t reg, uleb128_t value,
 /* Expr or ValExpr rule. */
 static void set_expr_rule(uleb128_t reg, enum item_location where,
 			  const u8 **expr, const u8 *end,
-			  struct unwind_state *state)
+			  struct uw_state *state)
 {
 	const u8 *const start = *expr;
 	uleb128_t len = get_uleb128(expr, end);
@@ -296,7 +296,7 @@ static void set_expr_rule(uleb128_t reg, enum item_location where,
 #define MAX_CFI 512
 
 static int processCFI(const u8 *start, const u8 *end, unsigned long targetLoc,
-		      signed ptrType, int user, struct unwind_state *state, int compat_task)
+		      signed ptrType, int user, struct uw_state *state, int compat_task)
 {
 	union {
 		const u8 *p8;
@@ -1169,7 +1169,7 @@ static int unwind_frame(struct unwind_context *context,
 	unsigned i;
 	signed ptrType = -1, call_frame = 1;
 	uleb128_t retAddrReg = 0;
-	struct unwind_state *state = &context->state;
+	struct uw_state *state = &context->state;
 	unsigned long addr;
 
 	if (unlikely(table_len == 0)) {
diff --git a/runtime/unwind/unwind.h b/runtime/unwind/unwind.h
index 9d66732..b3ff786 100644
--- a/runtime/unwind/unwind.h
+++ b/runtime/unwind/unwind.h
@@ -492,7 +492,7 @@ struct unwind_reg_state {
 	unsigned cfa_is_expr:1;
 };
 
-struct unwind_state {
+struct uw_state {
 	uleb128_t loc;
 	uleb128_t codeAlign;
 	sleb128_t dataAlign;
@@ -503,7 +503,7 @@ struct unwind_state {
 
 struct unwind_context {
     struct unwind_frame_info info;
-    struct unwind_state state;
+    struct uw_state state;
 };
 
 static const struct cfa badCFA = { ARRAY_SIZE(reg_info), 1 };
-- 
1.8.3.1