Skip to contentSkip to conversation

arm64: ftrace: fix incorrect output from stack tracer

PATCH Superseded AKASHI Takahiro 3 participants 23 messages Nov 18, 2015 – Dec 10, 2015

Subsystems (4)

v6 of 7 Superseded Latest is v7 →

lore
Share & tools
lore
#0 lore raw

v6 cover arm64: ftrace: fix incorrect output from stack tracer

This is the sixth patch series for fixing stack tracer on arm64. The original issue was reported by Jungseok[1], and then I found more issues[2].

patch1 is a proactive improvement of function_graph tracer. patch2 and 3 correspond to II-4(functions under function_graph tracer). patch4, 5 and 6 correspond to II-1(slurping stack) and II-2(differences between x86 and arm64).
patch6 is a function prologue analyzer test. This won't attest the correctness of the functionality, but it can suggest that all the traced functions are treated properly by this function.

We don't have to care about the original issue because the root cause (patch "ARM64: unwind: Fix PC calculation") has been reverted in v4.3.

Among the issues in [2], this patchset doesn't address
- II-3(interrupted frame):
Recent discussions[3] about introducing a dedicated interrupt stack suggests that we can avoid walking through from interrupt stack to process stack.
(Please note that, even on x86, interrupt stack is not supported by stack tracer.)

So recent interrupt-stack patch[4] is a prerequisite here.

- II-5(leaf function):
I don't remember why I thought this was a problem, but anyhow "-pg" seems to disable omit-leaf-stack-frame.

I tested the code with v4.4-rc1 + Jungseok's/James' patch v7[4].

Changes from v5:
- removed a patch ("ftrace: allow arch-specific stack tracer") which is already in v4.4-rc1
- handle a "return_to_handler" entry in call stack lists in more commonr way by fixing such entries in unwind_frame(). This will cover all the cases, a) stack tracer, b) perf call graph and c) dump_backtrace. (patch 2, 3)
- fixed aarch64_insn_is_eret(). Thanks to Jungseok. (patch 4)
- removed some hunks (offseting AARCH64_INSN_SIZE) due to having reverted a patch ("ARM64: unwind: Fix PC calculation") (patch 3)
- fixed function prologue analyzer on big-endian kernel. Thanks to Yalin. (patch 5)
- fixed a stack size of the top function in stack tracer's output (its size was reported 16 bytes bigger than actual size due to mishandled ftrace_caller.) (patch 3)

Changes from v4:
- removed a patch("arm64: ftrace: adjust callsite addresses examined by stack tracer")
- added a function prologue analyzer test(patch 6)

Changes from v3:
- fixed build errors/warnings reported by kbuild test robot
- addressed Steven's comments around check_stack()
- removed a patch ("arm64: ftrace: allow for tracing leaf functions") I don't remember why I thought this was necessary, but anyhow "-pg" seems to disable omit-leaf-stack-frame.

[1] http://lists.infradead.org/pipermail/linux-arm-kernel/2015-July/354126.html
[2] http://lists.infradead.org/pipermail/linux-arm-kernel/2015-September/369316.html
[3] http://lists.infradead.org/pipermail/linux-arm-kernel/2015-September/368003.html
[4] http://lists.infradead.org/pipermail/linux-arm-kernel/2015-November/385337.html

AKASHI Takahiro (6):
arm64: ftrace: modify a stack frame in a safe way
arm64: pass a task parameter to unwind_frame()
arm64: ftrace: fix a stack tracer's output under function graph
tracer
arm64: insn: add instruction decoders for ldp/stp and add/sub
arm64: ftrace: add arch-specific stack tracer
arm64: ftrace: add a test of function prologue analyzer

 arch/arm64/include/asm/ftrace.h     |    4 +-
 arch/arm64/include/asm/insn.h       |   18 +++
 arch/arm64/include/asm/stacktrace.h |   13 +-
 arch/arm64/kernel/ftrace.c          |   75 +++++++++-
 arch/arm64/kernel/insn.c            |  102 +++++++++++++
 arch/arm64/kernel/perf_callchain.c  |    5 +-
 arch/arm64/kernel/process.c         |    5 +-
 arch/arm64/kernel/return_address.c  |    5 +-
 arch/arm64/kernel/stacktrace.c      |  267 ++++++++++++++++++++++++++++++++++-
 arch/arm64/kernel/time.c            |    5 +-
 arch/arm64/kernel/traps.c           |    5 +-
 11 files changed, 482 insertions(+), 22 deletions(-)
Signature · 1 line
1.7.9.5
#1 lore raw

PATCH v6 1/6 arm64: ftrace: modify a stack frame in a safe way

Function graph tracer modifies a return address (LR) in a stack frame by calling ftrace_prepare_return() in a traced function's function prologue. The current code does this modification before preserving an original address at ftrace_push_return_trace() and there is always a small window of inconsistency when an interrupt occurs.

This doesn't matter, as far as an interrupt stack is introduced, because stack tracer won't be invoked in an interrupt context. But it would be better to proactively minimize such a window by moving the LR modification after ftrace_push_return_trace().

---
 arch/arm64/kernel/ftrace.c |   11 ++++-------
 1 file changed, 4 insertions(+), 7 deletions(-)
arch/arm64/kernel/ftrace.c +4 -7
index c851be7..314f82d 100644
@@ -125,23 +125,20 @@ void prepare_ftrace_return(unsigned long *parent, unsigned long self_addr,
 	 * on other archs. It's unlikely on AArch64.
 	 */
 	old = *parent;
-	*parent = return_hooker;
 
 	trace.func = self_addr;
 	trace.depth = current->curr_ret_stack + 1;
 
 	/* Only trace if the calling function expects to */
-	if (!ftrace_graph_entry(&trace)) {
-		*parent = old;
+	if (!ftrace_graph_entry(&trace))
 		return;
-	}
 
 	err = ftrace_push_return_trace(old, self_addr, &trace.depth,
 				       frame_pointer);
-	if (err == -EBUSY) {
-		*parent = old;
+	if (err == -EBUSY)
 		return;
-	}
+	else
+		*parent = return_hooker;
 }
 
 #ifdef CONFIG_DYNAMIC_FTRACE
Signature · 1 line
1.7.9.5
#2 lore raw

PATCH v6 1/6 arm64: ftrace: modify a stack frame in a safe way

Quoted 10 lines · AKASHI Takahiro

Function graph tracer modifies a return address (LR) in a stack frame by calling ftrace_prepare_return() in a traced function's function prologue. The current code does this modification before preserving an original address at ftrace_push_return_trace() and there is always a small window of inconsistency when an interrupt occurs.

This doesn't matter, as far as an interrupt stack is introduced, because stack tracer won't be invoked in an interrupt context. But it would be better to proactively minimize such a window by moving the LR modification after ftrace_push_return_trace().

There are two statements in my mind: 1)IRQ stack will be introduced on ARM64 in the future and 2)This change makes subtle variance compared to ARM and x86. I'm not sure about this patch from those perspectives.

However, I have no objection to the change. I will piggyback on other folks regarding this patch :)

Best Regards
Jungseok Lee

#3 lore raw

PATCH v6 2/6 arm64: pass a task parameter to unwind_frame()

In reply to AKASHI Takahiro

The change here doesn't make any difference in this patch, but is a preparation for later fixing a problem where stacktrace functions, unwind_frame() and walk_stackframe(), may return useless call stacks under function graph tracer.

---
 arch/arm64/include/asm/stacktrace.h |    6 ++++--
 arch/arm64/kernel/perf_callchain.c  |    2 +-
 arch/arm64/kernel/process.c         |    2 +-
 arch/arm64/kernel/return_address.c  |    2 +-
 arch/arm64/kernel/stacktrace.c      |    8 ++++----
 arch/arm64/kernel/time.c            |    2 +-
 arch/arm64/kernel/traps.c           |    2 +-
 7 files changed, 13 insertions(+), 11 deletions(-)
arch/arm64/include/asm/stacktrace.h +4 -2
index 7318f6d..6fb61c5 100644
@@ -16,14 +16,16 @@
 #ifndef __ASM_STACKTRACE_H
 #define __ASM_STACKTRACE_H
 
+struct task_struct;
+
 struct stackframe {
 	unsigned long fp;
 	unsigned long sp;
 	unsigned long pc;
 };
 
-extern int unwind_frame(struct stackframe *frame);
-extern void walk_stackframe(struct stackframe *frame,
+extern int unwind_frame(struct task_struct *tsk, struct stackframe *frame);
+extern void walk_stackframe(struct task_struct *tsk, struct stackframe *frame,
 			    int (*fn)(struct stackframe *, void *), void *data);
 
 #endif	/* __ASM_STACKTRACE_H */
arch/arm64/kernel/perf_callchain.c +1 -1
index 3aa7483..797220d 100644
@@ -165,7 +165,7 @@ void perf_callchain_kernel(struct perf_callchain_entry *entry,
 	frame.sp = regs->sp;
 	frame.pc = regs->pc;
 
-	walk_stackframe(&frame, callchain_trace, entry);
+	walk_stackframe(current, &frame, callchain_trace, entry);
 }
 
 unsigned long perf_instruction_pointer(struct pt_regs *regs)
arch/arm64/kernel/process.c +1 -1
index f75b540..98bf546 100644
@@ -348,7 +348,7 @@ unsigned long get_wchan(struct task_struct *p)
 	do {
 		if (frame.sp < stack_page ||
 		    frame.sp >= stack_page + THREAD_SIZE ||
-		    unwind_frame(&frame))
+		    unwind_frame(p, &frame))
 			return 0;
 		if (!in_sched_functions(frame.pc))
 			return frame.pc;
arch/arm64/kernel/return_address.c +1 -1
index 6c4fd28..07b37ac 100644
@@ -44,7 +44,7 @@ void *return_address(unsigned int level)
 	frame.sp = current_stack_pointer;
 	frame.pc = (unsigned long)return_address; /* dummy */
 
-	walk_stackframe(&frame, save_return_addr, &data);
+	walk_stackframe(current, &frame, save_return_addr, &data);
 
 	if (!data.level)
 		return data.addr;
arch/arm64/kernel/stacktrace.c +4 -4
index a159851..9c7acf8 100644
@@ -36,7 +36,7 @@
  *	ldp	x29, x30, [sp]
  *	add	sp, sp, #0x10
  */
-int notrace unwind_frame(struct stackframe *frame)
+int notrace unwind_frame(struct task_struct *tsk, struct stackframe *frame)
 {
 	unsigned long high, low;
 	unsigned long fp = frame->fp;
@@ -78,7 +78,7 @@ int notrace unwind_frame(struct stackframe *frame)
 	return 0;
 }
 
-void notrace walk_stackframe(struct stackframe *frame,
+void notrace walk_stackframe(struct task_struct *tsk, struct stackframe *frame,
 		     int (*fn)(struct stackframe *, void *), void *data)
 {
 	while (1) {
@@ -86,7 +86,7 @@ void notrace walk_stackframe(struct stackframe *frame,
 
 		if (fn(frame, data))
 			break;
-		ret = unwind_frame(frame);
+		ret = unwind_frame(tsk, frame);
 		if (ret < 0)
 			break;
 	}
@@ -138,7 +138,7 @@ void save_stack_trace_tsk(struct task_struct *tsk, struct stack_trace *trace)
 		frame.pc = (unsigned long)save_stack_trace_tsk;
 	}
 
-	walk_stackframe(&frame, save_trace, &data);
+	walk_stackframe(tsk, &frame, save_trace, &data);
 	if (trace->nr_entries < trace->max_entries)
 		trace->entries[trace->nr_entries++] = ULONG_MAX;
 }
arch/arm64/kernel/time.c +1 -1
index 13339b6..6e5c521 100644
@@ -53,7 +53,7 @@ unsigned long profile_pc(struct pt_regs *regs)
 	frame.sp = regs->sp;
 	frame.pc = regs->pc;
 	do {
-		int ret = unwind_frame(&frame);
+		int ret = unwind_frame(NULL, &frame);
 		if (ret < 0)
 			return 0;
 	} while (in_lock_functions(frame.pc));
arch/arm64/kernel/traps.c +1 -1
index cdfa2f9..da45224 100644
@@ -177,7 +177,7 @@ static void dump_backtrace(struct pt_regs *regs, struct task_struct *tsk)
 		int ret;
 
 		dump_backtrace_entry(where);
-		ret = unwind_frame(&frame);
+		ret = unwind_frame(tsk, &frame);
 		if (ret < 0)
 			break;
 		stack = frame.sp;
Signature · 1 line
1.7.9.5
#4 lore raw

PATCH v6 2/6 arm64: pass a task parameter to unwind_frame()

Quoted 4 lines · AKASHI Takahiro

The change here doesn't make any difference in this patch, but is a preparation for later fixing a problem where stacktrace functions, unwind_frame() and walk_stackframe(), may return useless call stacks under function graph tracer.

I'm aligned with the argument. The case cannot be handled correctly without ret_stack[] of struct task_struct.

Best Regards
Jungseok Lee

#5 lore raw

PATCH v6 2/6 arm64: pass a task parameter to unwind_frame()

Quoted 8 lines · Jungseok Lee
Quoted 4 lines · AKASHI Takahiro

The change here doesn't make any difference in this patch, but is a preparation for later fixing a problem where stacktrace functions, unwind_frame() and walk_stackframe(), may return useless call stacks under function graph tracer.

I'm aligned with the argument. The case cannot be handled correctly without ret_stack[] of struct task_struct.

Thanks. I will add some description about why we need 'tsk' in a commit message.

-Takahiro AKASHI

Quoted 3 lines

Best Regards
Jungseok Lee

#6 lore raw

PATCH v6 2/6 arm64: pass a task parameter to unwind_frame()

Quoted 7 lines · AKASHI Takahiro
Quoted 6 lines · Jungseok Lee
Quoted 4 lines · AKASHI Takahiro

The change here doesn't make any difference in this patch, but is a preparation for later fixing a problem where stacktrace functions, unwind_frame() and walk_stackframe(), may return useless call stacks under function graph tracer.

Quoted 5 lines
Quoted 2 lines

I'm aligned with the argument. The case cannot be handled correctly without ret_stack[] of struct task_struct.

Thanks. I will add some description about why we need 'tsk' in a commit message.

Ok, so are you planning to repost this series?

Will

#7 lore raw

PATCH v6 2/6 arm64: pass a task parameter to unwind_frame()

Quoted 8 lines · Will Deacon
Quoted 7 lines · AKASHI Takahiro
Quoted 6 lines · Jungseok Lee
Quoted 4 lines · AKASHI Takahiro

The change here doesn't make any difference in this patch, but is a preparation for later fixing a problem where stacktrace functions, unwind_frame() and walk_stackframe(), may return useless call stacks under function graph tracer.

Quoted 7 lines
Quoted 5 lines
Quoted 2 lines

I'm aligned with the argument. The case cannot be handled correctly without ret_stack[] of struct task_struct.

Thanks. I will add some description about why we need 'tsk' in a commit message.

Ok, so are you planning to repost this series?

I think this function_graph changes could be factored out from this series. It would be helpful for maintainers and reviewers to take a look at them.

Best Regards
Jungseok Lee

#8 lore raw

PATCH v6 2/6 arm64: pass a task parameter to unwind_frame()

Quoted 12 lines · Jungseok Lee
Quoted 11 lines · Will Deacon
Quoted 10 lines · AKASHI Takahiro
Quoted 8 lines · Jungseok Lee
Quoted 4 lines · AKASHI Takahiro

The change here doesn't make any difference in this patch, but is a preparation for later fixing a problem where stacktrace functions, unwind_frame() and walk_stackframe(), may return useless call stacks under function graph tracer.

I'm aligned with the argument. The case cannot be handled correctly without ret_stack[] of struct task_struct.

Quoted 7 lines
Quoted 4 lines
Quoted 2 lines

Thanks. I will add some description about why we need 'tsk' in a commit message.

Ok, so are you planning to repost this series?

I think this function_graph changes could be factored out from this series. It would be helpful for maintainers and reviewers to take a look at them.

Yeah, now patch1, 2 and 3 are almost independent from patch 4, 5 and 6. But I will submit them in one series, at least, for my next version. Will, if this is inconvenient to you, let me know.

Thanks,
-Takahiro AKASHI

Quoted 3 lines

Best Regards
Jungseok Lee

#9 lore raw

PATCH v6 3/6 arm64: ftrace: fix a stack tracer's output under function graph tracer

In reply to AKASHI Takahiro

Function graph tracer modifies a return address (LR) in a stack frame to hook a function return. This will result in many useless entries (return_to_handler) showing up in
a) a stack tracer's output
b) perf call graph (with perf record -g)
c) dump_backtrace (at panic et al.)

For example, in case of a),

  $ echo function_graph > /sys/kernel/debug/tracing/current_tracer
  $ echo 1 > /proc/sys/kernel/stack_trace_enabled
  $ cat /sys/kernel/debug/tracing/stack_trace
        Depth    Size   Location    (54 entries)
        -----    ----   --------
  0)     4504      16   gic_raise_softirq+0x28/0x150
  1)     4488      80   smp_cross_call+0x38/0xb8
  2)     4408      48   return_to_handler+0x0/0x40
  3)     4360      32   return_to_handler+0x0/0x40
  ...

In case of b),

  $ echo function_graph > /sys/kernel/debug/tracing/current_tracer
  $ perf record -e mem:XXX:x -ag -- sleep 10
  $ perf report
                  ...
                  |          |          |--0.22%-- 0x550f8
                  |          |          |          0x10888
                  |          |          |          el0_svc_naked
                  |          |          |          sys_openat
                  |          |          |          return_to_handler
                  |          |          |          return_to_handler
                  ...

In case of c),

  $ echo function_graph > /sys/kernel/debug/tracing/current_tracer
  $ echo c > /proc/sysrq-trigger
  ...
  Call trace:
  [<ffffffc00044d3ac>] sysrq_handle_crash+0x24/0x30
  [<ffffffc000092250>] return_to_handler+0x0/0x40
  [<ffffffc000092250>] return_to_handler+0x0/0x40
  ...

This patch replaces such entries with real addresses preserved in current->ret_stack[] at unwind_frame(). This way, we can cover all the cases.

---
 arch/arm64/include/asm/ftrace.h     |    2 ++
 arch/arm64/include/asm/stacktrace.h |    3 +++
 arch/arm64/kernel/perf_callchain.c  |    3 +++
 arch/arm64/kernel/process.c         |    3 +++
 arch/arm64/kernel/return_address.c  |    3 +++
 arch/arm64/kernel/stacktrace.c      |   17 +++++++++++++++++
 arch/arm64/kernel/time.c            |    3 +++
 arch/arm64/kernel/traps.c           |    3 +++
 8 files changed, 37 insertions(+)
arch/arm64/include/asm/ftrace.h +2 -0
index c5534fa..3c60f37 100644
@@ -28,6 +28,8 @@ struct dyn_arch_ftrace {
 
 extern unsigned long ftrace_graph_call;
 
+extern void return_to_handler(void);
+
 static inline unsigned long ftrace_call_adjust(unsigned long addr)
 {
 	/*
arch/arm64/include/asm/stacktrace.h +3 -0
index 6fb61c5..801a16db 100644
@@ -22,6 +22,9 @@ struct stackframe {
 	unsigned long fp;
 	unsigned long sp;
 	unsigned long pc;
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	unsigned int graph;
+#endif
 };
 
 extern int unwind_frame(struct task_struct *tsk, struct stackframe *frame);
arch/arm64/kernel/perf_callchain.c +3 -0
index 797220d..ff46654 100644
@@ -164,6 +164,9 @@ void perf_callchain_kernel(struct perf_callchain_entry *entry,
 	frame.fp = regs->regs[29];
 	frame.sp = regs->sp;
 	frame.pc = regs->pc;
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	frame.graph = current->curr_ret_stack;
+#endif
 
 	walk_stackframe(current, &frame, callchain_trace, entry);
 }
arch/arm64/kernel/process.c +3 -0
index 98bf546..88d742b 100644
@@ -344,6 +344,9 @@ unsigned long get_wchan(struct task_struct *p)
 	frame.fp = thread_saved_fp(p);
 	frame.sp = thread_saved_sp(p);
 	frame.pc = thread_saved_pc(p);
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	frame.graph = p->curr_ret_stack;
+#endif
 	stack_page = (unsigned long)task_stack_page(p);
 	do {
 		if (frame.sp < stack_page ||
arch/arm64/kernel/return_address.c +3 -0
index 07b37ac..1718706 100644
@@ -43,6 +43,9 @@ void *return_address(unsigned int level)
 	frame.fp = (unsigned long)__builtin_frame_address(0);
 	frame.sp = current_stack_pointer;
 	frame.pc = (unsigned long)return_address; /* dummy */
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	frame.graph = current->curr_ret_stack;
+#endif
 
 	walk_stackframe(current, &frame, save_return_addr, &data);
 
arch/arm64/kernel/stacktrace.c +17 -0
index 9c7acf8..0a39049 100644
@@ -17,6 +17,7 @@
  */
 #include <linux/kernel.h>
 #include <linux/export.h>
+#include <linux/ftrace.h>
 #include <linux/sched.h>
 #include <linux/stacktrace.h>
 
@@ -66,6 +67,19 @@ int notrace unwind_frame(struct task_struct *tsk, struct stackframe *frame)
 	frame->fp = *(unsigned long *)(fp);
 	frame->pc = *(unsigned long *)(fp + 8);
 
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	if (tsk && tsk->ret_stack &&
+			(frame->pc == (unsigned long)return_to_handler)) {
+		/*
+		 * This is a case where function graph tracer has
+		 * modified a return address (LR) in a stack frame
+		 * to hook a function return.
+		 * So replace it to an original value.
+		 */
+		frame->pc = tsk->ret_stack[frame->graph--].ret;
+	}
+#endif /* CONFIG_FUNCTION_GRAPH_TRACER */
+
 	/*
 	 * Check whether we are going to walk through from interrupt stack
 	 * to task stack.
@@ -137,6 +151,9 @@ void save_stack_trace_tsk(struct task_struct *tsk, struct stack_trace *trace)
 		frame.sp = current_stack_pointer;
 		frame.pc = (unsigned long)save_stack_trace_tsk;
 	}
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	frame.graph = tsk->curr_ret_stack;
+#endif
 
 	walk_stackframe(tsk, &frame, save_trace, &data);
 	if (trace->nr_entries < trace->max_entries)
arch/arm64/kernel/time.c +3 -0
index 6e5c521..5977969 100644
@@ -52,6 +52,9 @@ unsigned long profile_pc(struct pt_regs *regs)
 	frame.fp = regs->regs[29];
 	frame.sp = regs->sp;
 	frame.pc = regs->pc;
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	frame.graph = -1; /* no task info */
+#endif
 	do {
 		int ret = unwind_frame(NULL, &frame);
 		if (ret < 0)
arch/arm64/kernel/traps.c +3 -0
index da45224..46053c2 100644
@@ -169,6 +169,9 @@ static void dump_backtrace(struct pt_regs *regs, struct task_struct *tsk)
 		frame.sp = thread_saved_sp(tsk);
 		frame.pc = thread_saved_pc(tsk);
 	}
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	frame.graph = tsk->curr_ret_stack;
+#endif
 
 	pr_emerg("Call trace:\n");
 	while (1) {
Signature · 1 line
1.7.9.5
#10 lore raw

PATCH v6 3/6 arm64: ftrace: fix a stack tracer's output under function graph tracer

On Nov 18, 2015, at 3:43 PM, AKASHI Takahiro wrote:

Hi Akashi,

Quoted 42 lines

Function graph tracer modifies a return address (LR) in a stack frame to hook a function return. This will result in many useless entries (return_to_handler) showing up in
a) a stack tracer's output
b) perf call graph (with perf record -g)
c) dump_backtrace (at panic et al.)

For example, in case of a),

 $ echo function_graph > /sys/kernel/debug/tracing/current_tracer
 $ echo 1 > /proc/sys/kernel/stack_trace_enabled
 $ cat /sys/kernel/debug/tracing/stack_trace
       Depth    Size   Location    (54 entries)
       -----    ----   --------
 0)     4504      16   gic_raise_softirq+0x28/0x150
 1)     4488      80   smp_cross_call+0x38/0xb8
 2)     4408      48   return_to_handler+0x0/0x40
 3)     4360      32   return_to_handler+0x0/0x40
 ...

In case of b),

 $ echo function_graph > /sys/kernel/debug/tracing/current_tracer
 $ perf record -e mem:XXX:x -ag -- sleep 10
 $ perf report
                 ...
                 |          |          |--0.22%-- 0x550f8
                 |          |          |          0x10888
                 |          |          |          el0_svc_naked
                 |          |          |          sys_openat
                 |          |          |          return_to_handler
                 |          |          |          return_to_handler
                 ...

In case of c),

 $ echo function_graph > /sys/kernel/debug/tracing/current_tracer
 $ echo c > /proc/sysrq-trigger
 ...
 Call trace:
 [<ffffffc00044d3ac>] sysrq_handle_crash+0x24/0x30
 [<ffffffc000092250>] return_to_handler+0x0/0x40
 [<ffffffc000092250>] return_to_handler+0x0/0x40
 ...
Quoted 3 lines

This patch replaces such entries with real addresses preserved in current->ret_stack[] at unwind_frame(). This way, we can cover all the cases.

I've observed a strange behavior when playing with case c). Call trace is as follows when function_graph is not used.

Call trace:
 [<fffffe00003dc738>] sysrq_handle_crash+0x24/0x30          <- (1)
 [<fffffe00003dd2ac>] __handle_sysrq+0x128/0x19c            <- (2)
 [<fffffe00003dd730>] write_sysrq_trigger+0x60/0x74
 [<fffffe0000249fc4>] proc_reg_write+0x84/0xc0
 [<fffffe00001f2638>] __vfs_write+0x44/0x104                <- (3)
 [<fffffe00001f2e60>] vfs_write+0x98/0x1a8
 [<fffffe00001f3730>] SyS_write+0x50/0xb0
 [<fffffe00000939ec>] el0_svc_naked+0x20/0x28               <- (4)

When function_graph is set, some entries, such as do_mem_abort, are added between (1) and (2). In addition, entries from (3) to (4) are not printed out. As tracking down elements of ret_stack[], I realize dump_backtrace() has been terminated before reaching to ret_stack[0]. Have you seen this kind of behavior? I believe push & pop operations work correctly.

Please correct me if I'm wrong.

Quoted 30 lines

---

arch/arm64/include/asm/ftrace.h     |    2 ++
arch/arm64/include/asm/stacktrace.h |    3 +++
arch/arm64/kernel/perf_callchain.c  |    3 +++
arch/arm64/kernel/process.c         |    3 +++
arch/arm64/kernel/return_address.c  |    3 +++
arch/arm64/kernel/stacktrace.c      |   17 +++++++++++++++++
arch/arm64/kernel/time.c            |    3 +++
arch/arm64/kernel/traps.c           |    3 +++
8 files changed, 37 insertions(+)
arch/arm64/include/asm/ftrace.h +2 -0
index c5534fa..3c60f37 100644
@@ -28,6 +28,8 @@ struct dyn_arch_ftrace {
 
 extern unsigned long ftrace_graph_call;
 
+extern void return_to_handler(void);
+
 static inline unsigned long ftrace_call_adjust(unsigned long addr)
 {
 	/*
arch/arm64/include/asm/stacktrace.h +0 -0
index 6fb61c5..801a16db 100644

arch/arm64/include/asm/stacktrace.h struct stackframe { · line 28

Quoted 7 lines
arch/arm64/include/asm/stacktrace.h +3 -0
@@ -22,6 +22,9 @@ struct stackframe {
 	unsigned long fp;
 	unsigned long sp;
 	unsigned long pc;
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	unsigned int graph;
+#endif
 };

How about using int instead of unsigned int to align with cure_ret_stack of struct task_struct?

Quoted 61 lines
arch/arm64/include/asm/stacktrace.h +0 -0
 extern int unwind_frame(struct task_struct *tsk, struct stackframe *frame);
arch/arm64/kernel/perf_callchain.c +3 -0
index 797220d..ff46654 100644
@@ -164,6 +164,9 @@ void perf_callchain_kernel(struct perf_callchain_entry *entry,
 	frame.fp = regs->regs[29];
 	frame.sp = regs->sp;
 	frame.pc = regs->pc;
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	frame.graph = current->curr_ret_stack;
+#endif
 
 	walk_stackframe(current, &frame, callchain_trace, entry);
 }
arch/arm64/kernel/process.c +3 -0
index 98bf546..88d742b 100644
@@ -344,6 +344,9 @@ unsigned long get_wchan(struct task_struct *p)
 	frame.fp = thread_saved_fp(p);
 	frame.sp = thread_saved_sp(p);
 	frame.pc = thread_saved_pc(p);
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	frame.graph = p->curr_ret_stack;
+#endif
 	stack_page = (unsigned long)task_stack_page(p);
 	do {
 		if (frame.sp < stack_page ||
arch/arm64/kernel/return_address.c +3 -0
index 07b37ac..1718706 100644
@@ -43,6 +43,9 @@ void *return_address(unsigned int level)
 	frame.fp = (unsigned long)__builtin_frame_address(0);
 	frame.sp = current_stack_pointer;
 	frame.pc = (unsigned long)return_address; /* dummy */
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	frame.graph = current->curr_ret_stack;
+#endif
 
 	walk_stackframe(current, &frame, save_return_addr, &data);
 
arch/arm64/kernel/stacktrace.c +3 -0
index 9c7acf8..0a39049 100644
@@ -17,6 +17,7 @@
 */
 #include <linux/kernel.h>
 #include <linux/export.h>
+#include <linux/ftrace.h>
 #include <linux/sched.h>
 #include <linux/stacktrace.h>
 
@@ -66,6 +67,19 @@ int notrace unwind_frame(struct task_struct *tsk, struct stackframe *frame)
 	frame->fp = *(unsigned long *)(fp);
 	frame->pc = *(unsigned long *)(fp + 8);
 
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	if (tsk && tsk->ret_stack &&

arch/arm64/kernel/stacktrace.c int notrace unwind_frame(struct task_struct *tsk, struct stackframe *frame) · line 81

Quoted 10 lines
arch/arm64/kernel/stacktrace.c +10 -0
+			(frame->pc == (unsigned long)return_to_handler)) {
+		/*
+		 * This is a case where function graph tracer has
+		 * modified a return address (LR) in a stack frame
+		 * to hook a function return.
+		 * So replace it to an original value.
+		 */
+		frame->pc = tsk->ret_stack[frame->graph--].ret;
+	}
+#endif /* CONFIG_FUNCTION_GRAPH_TRACER */

There is an index check of ret_stack[] in case of x86 [1]. Even though graph is unsigned int, I think we need to check the value of frame->graph before accessing ret_stack[].

Quoted 19 lines
arch/arm64/kernel/stacktrace.c +4 -0
+
 	/*
 	 * Check whether we are going to walk through from interrupt stack
 	 * to task stack.
@@ -137,6 +151,9 @@ void save_stack_trace_tsk(struct task_struct *tsk, struct stack_trace *trace)
 		frame.sp = current_stack_pointer;
 		frame.pc = (unsigned long)save_stack_trace_tsk;
 	}
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	frame.graph = tsk->curr_ret_stack;
+#endif
 
 	walk_stackframe(tsk, &frame, save_trace, &data);
 	if (trace->nr_entries < trace->max_entries)
arch/arm64/kernel/time.c +0 -0
index 6e5c521..5977969 100644

arch/arm64/kernel/time.c unsigned long profile_pc(struct pt_regs *regs) · line 57

Quoted 6 lines
arch/arm64/kernel/time.c +3 -0
@@ -52,6 +52,9 @@ unsigned long profile_pc(struct pt_regs *regs)
 	frame.fp = regs->regs[29];
 	frame.sp = regs->sp;
 	frame.pc = regs->pc;
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	frame.graph = -1; /* no task info */
+#endif

graph is unsigned int type. Is this intentional?

Best Regards
Jungseok Lee

[1] arch/x86/kernel/dumpstack.c

#11 lore raw

PATCH v6 3/6 arm64: ftrace: fix a stack tracer's output under function graph tracer

Quoted 63 lines · Jungseok Lee

On Nov 18, 2015, at 3:43 PM, AKASHI Takahiro wrote:

Hi Akashi,

Quoted 42 lines

Function graph tracer modifies a return address (LR) in a stack frame to hook a function return. This will result in many useless entries (return_to_handler) showing up in
a) a stack tracer's output
b) perf call graph (with perf record -g)
c) dump_backtrace (at panic et al.)

For example, in case of a),

  $ echo function_graph > /sys/kernel/debug/tracing/current_tracer
  $ echo 1 > /proc/sys/kernel/stack_trace_enabled
  $ cat /sys/kernel/debug/tracing/stack_trace
        Depth    Size   Location    (54 entries)
        -----    ----   --------
  0)     4504      16   gic_raise_softirq+0x28/0x150
  1)     4488      80   smp_cross_call+0x38/0xb8
  2)     4408      48   return_to_handler+0x0/0x40
  3)     4360      32   return_to_handler+0x0/0x40
  ...

In case of b),

  $ echo function_graph > /sys/kernel/debug/tracing/current_tracer
  $ perf record -e mem:XXX:x -ag -- sleep 10
  $ perf report
                  ...
                  |          |          |--0.22%-- 0x550f8
                  |          |          |          0x10888
                  |          |          |          el0_svc_naked
                  |          |          |          sys_openat
                  |          |          |          return_to_handler
                  |          |          |          return_to_handler
                  ...

In case of c),

  $ echo function_graph > /sys/kernel/debug/tracing/current_tracer
  $ echo c > /proc/sysrq-trigger
  ...
  Call trace:
  [<ffffffc00044d3ac>] sysrq_handle_crash+0x24/0x30
  [<ffffffc000092250>] return_to_handler+0x0/0x40
  [<ffffffc000092250>] return_to_handler+0x0/0x40
  ...
Quoted 3 lines

This patch replaces such entries with real addresses preserved in current->ret_stack[] at unwind_frame(). This way, we can cover all the cases.

I've observed a strange behavior when playing with case c). Call trace is as follows when function_graph is not used.

Call trace:
  [<fffffe00003dc738>] sysrq_handle_crash+0x24/0x30          <- (1)
  [<fffffe00003dd2ac>] __handle_sysrq+0x128/0x19c            <- (2)
  [<fffffe00003dd730>] write_sysrq_trigger+0x60/0x74
  [<fffffe0000249fc4>] proc_reg_write+0x84/0xc0
  [<fffffe00001f2638>] __vfs_write+0x44/0x104                <- (3)
  [<fffffe00001f2e60>] vfs_write+0x98/0x1a8
  [<fffffe00001f3730>] SyS_write+0x50/0xb0
  [<fffffe00000939ec>] el0_svc_naked+0x20/0x28               <- (4)
Quoted 7 lines

When function_graph is set, some entries, such as do_mem_abort, are added between (1) and (2). In addition, entries from (3) to (4) are not printed out. As tracking down elements of ret_stack[], I realize dump_backtrace() has been terminated before reaching to ret_stack[0]. Have you seen this kind of behavior? I believe push & pop operations work correctly.

Please correct me if I'm wrong.

Oops, I mis-interpreted the result output.
You are right.
This can happen because the original dump_backtrace() wants to trace a stack from a function where an exception has taken place, sysrq_handle_crash(), while ret_stack[curr_ret_stack] doesn't point to that function, but the very top of traced functions in callchains, that is, __do_kernel_fault in your case, probably. So it results in replacing entries of return_to_handler to wrong function addresses.

A fixup! patch attached below (informative only) fixes this issue by once tracing all the functions on a stack, but preventing a top few ones from being printed. But there is a tricky thing here: we have to use 'regs->pc' instead of frame.pc as a trapped function because, as I've already mentioned before, we always miss the function when walking through a stack from an exception handler to functions in a thread context.
(Please note that we will introduce a dummy stack frame at interrupt, but not at exception.)

Quoted 100 lines
Quoted 30 lines

---

arch/arm64/include/asm/ftrace.h     |    2 ++
arch/arm64/include/asm/stacktrace.h |    3 +++
arch/arm64/kernel/perf_callchain.c  |    3 +++
arch/arm64/kernel/process.c         |    3 +++
arch/arm64/kernel/return_address.c  |    3 +++
arch/arm64/kernel/stacktrace.c      |   17 +++++++++++++++++
arch/arm64/kernel/time.c            |    3 +++
arch/arm64/kernel/traps.c           |    3 +++
8 files changed, 37 insertions(+)
arch/arm64/include/asm/ftrace.h +2 -0
index c5534fa..3c60f37 100644
@@ -28,6 +28,8 @@ struct dyn_arch_ftrace {
 
 extern unsigned long ftrace_graph_call;
 
+extern void return_to_handler(void);
+
 static inline unsigned long ftrace_call_adjust(unsigned long addr)
 {
 	/*
arch/arm64/include/asm/stacktrace.h +0 -0
index 6fb61c5..801a16db 100644

arch/arm64/include/asm/stacktrace.h struct stackframe { · line 28

Quoted 7 lines
arch/arm64/include/asm/stacktrace.h +3 -0
@@ -22,6 +22,9 @@ struct stackframe {
 	unsigned long fp;
 	unsigned long sp;
 	unsigned long pc;
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	unsigned int graph;
+#endif
 };

How about using int instead of unsigned int to align with cure_ret_stack of struct task_struct?

Quoted 59 lines
arch/arm64/include/asm/stacktrace.h +0 -0
 extern int unwind_frame(struct task_struct *tsk, struct stackframe *frame);
arch/arm64/kernel/perf_callchain.c +3 -0
index 797220d..ff46654 100644
@@ -164,6 +164,9 @@ void perf_callchain_kernel(struct perf_callchain_entry *entry,
 	frame.fp = regs->regs[29];
 	frame.sp = regs->sp;
 	frame.pc = regs->pc;
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	frame.graph = current->curr_ret_stack;
+#endif
 
 	walk_stackframe(current, &frame, callchain_trace, entry);
 }
arch/arm64/kernel/process.c +3 -0
index 98bf546..88d742b 100644
@@ -344,6 +344,9 @@ unsigned long get_wchan(struct task_struct *p)
 	frame.fp = thread_saved_fp(p);
 	frame.sp = thread_saved_sp(p);
 	frame.pc = thread_saved_pc(p);
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	frame.graph = p->curr_ret_stack;
+#endif
 	stack_page = (unsigned long)task_stack_page(p);
 	do {
 		if (frame.sp < stack_page ||
arch/arm64/kernel/return_address.c +3 -0
index 07b37ac..1718706 100644
@@ -43,6 +43,9 @@ void *return_address(unsigned int level)
 	frame.fp = (unsigned long)__builtin_frame_address(0);
 	frame.sp = current_stack_pointer;
 	frame.pc = (unsigned long)return_address; /* dummy */
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	frame.graph = current->curr_ret_stack;
+#endif
 
 	walk_stackframe(current, &frame, save_return_addr, &data);
 
arch/arm64/kernel/stacktrace.c +1 -0
index 9c7acf8..0a39049 100644
@@ -17,6 +17,7 @@
  */
 #include <linux/kernel.h>
 #include <linux/export.h>
+#include <linux/ftrace.h>
 #include <linux/sched.h>
 #include <linux/stacktrace.h>
 
@@ -66,6 +67,19 @@ int notrace unwind_frame(struct task_struct *tsk, struct stackframe *frame)
 	frame->fp = *(unsigned long *)(fp);
 	frame->pc = *(unsigned long *)(fp + 8);
 
Quoted 16 lines

arch/arm64/kernel/stacktrace.c int notrace unwind_frame(struct task_struct *tsk, struct stackframe *frame) · line 81

Quoted 12 lines
arch/arm64/kernel/stacktrace.c +12 -0
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	if (tsk && tsk->ret_stack &&
+			(frame->pc == (unsigned long)return_to_handler)) {
+		/*
+		 * This is a case where function graph tracer has
+		 * modified a return address (LR) in a stack frame
+		 * to hook a function return.
+		 * So replace it to an original value.
+		 */
+		frame->pc = tsk->ret_stack[frame->graph--].ret;
+	}
+#endif /* CONFIG_FUNCTION_GRAPH_TRACER */

There is an index check of ret_stack[] in case of x86 [1]. Even though graph is unsigned int, I think we need to check the value of frame->graph before accessing ret_stack[].

I'm not sure that the checking is very useful because, if it happens, it is a bug. It might make sense to avoid a possible panic though.

Quoted 12 lines
Quoted 12 lines
arch/arm64/kernel/stacktrace.c +4 -0
+
 	/*
 	 * Check whether we are going to walk through from interrupt stack
 	 * to task stack.
@@ -137,6 +151,9 @@ void save_stack_trace_tsk(struct task_struct *tsk, struct stack_trace *trace)
 		frame.sp = current_stack_pointer;
 		frame.pc = (unsigned long)save_stack_trace_tsk;
 	}
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	frame.graph = tsk->curr_ret_stack;
+#endif
 
Quoted 15 lines

arch/arm64/kernel/time.c unsigned long profile_pc(struct pt_regs *regs) · line 57

Quoted 13 lines
arch/arm64/kernel/stacktrace.c +0 -0
 	walk_stackframe(tsk, &frame, save_trace, &data);
 	if (trace->nr_entries < trace->max_entries)
arch/arm64/kernel/time.c +3 -0
index 6e5c521..5977969 100644
@@ -52,6 +52,9 @@ unsigned long profile_pc(struct pt_regs *regs)
 	frame.fp = regs->regs[29];
 	frame.sp = regs->sp;
 	frame.pc = regs->pc;
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	frame.graph = -1; /* no task info */
+#endif

graph is unsigned int type. Is this intentional?

No. This initialization is, I believe, redundant as it is not checked anywhere, but I will re-think of it along with the checking above.

Thanks,
-Takahiro AKASHI

Quoted 5 lines

Best Regards
Jungseok Lee

[1] arch/x86/kernel/dumpstack.c

----8<----
From 9ebba7167f7838daf68d8231f04141d2f4d4b7b5 Mon Sep 17 00:00:00 2001
From: AKASHI Takahiro <[email protected]>
Date: Wed, 25 Nov 2015 13:29:54 +0900
Subject: [PATCH] fixup! arm64: ftrace: fix a stack tracer's output under
function graph tracer

---
  arch/arm64/kernel/traps.c |   23 +++++++++++++++++------
  1 file changed, 17 insertions(+), 6 deletions(-)
arch/arm64/kernel/traps.c +17 -6
index 46053c2..f140029 100644
@@ -147,17 +147,14 @@ static void dump_backtrace(struct pt_regs *regs, struct task_struct *tsk)
  {
  	struct stackframe frame;
  	unsigned long _irq_stack_ptr = per_cpu(irq_stack_ptr, smp_processor_id());
+	int skip;
 
  	pr_debug("%s(regs = %p tsk = %p)\n", __func__, regs, tsk);
 
  	if (!tsk)
  		tsk = current;
 
-	if (regs) {
-		frame.fp = regs->regs[29];
-		frame.sp = regs->sp;
-		frame.pc = regs->pc;
-	} else if (tsk == current) {
+	if (tsk == current) {
  		frame.fp = (unsigned long)__builtin_frame_address(0);
  		frame.sp = current_stack_pointer;
  		frame.pc = (unsigned long)dump_backtrace;
@@ -173,13 +170,27 @@ static void dump_backtrace(struct pt_regs *regs, struct task_struct *tsk)
  	frame.graph = tsk->curr_ret_stack;
  #endif
 
+	skip = (regs ? 1 : 0);
  	pr_emerg("Call trace:\n");
  	while (1) {
  		unsigned long where = frame.pc;
  		unsigned long stack;
  		int ret;
 
-		dump_backtrace_entry(where);
+		/* skip until specified stack frame */
+		if (!skip)
+			dump_backtrace_entry(where);
+		else if (frame.fp == regs->regs[29]) {
+			skip = 0;
+			/*
+			 * Mostly, this is the case where this function is
+			 * called in panic/abort. As exception handler's
+			 * stack frame does not contain the corresponding pc
+			 * at which an exception has taken place, use regs->pc
+			 * instead.
+			 */
+			dump_backtrace_entry(regs->pc);
+		}
  		ret = unwind_frame(tsk, &frame);
  		if (ret < 0)
  			break;
Signature · 1 line
1.7.9.5
#12 lore raw

PATCH v6 3/6 arm64: ftrace: fix a stack tracer's output under function graph tracer

Quoted 70 lines · AKASHI Takahiro
Quoted 69 lines · Jungseok Lee

On Nov 18, 2015, at 3:43 PM, AKASHI Takahiro wrote:

Hi Akashi,

Quoted 42 lines

Function graph tracer modifies a return address (LR) in a stack frame to hook a function return. This will result in many useless entries (return_to_handler) showing up in
a) a stack tracer's output
b) perf call graph (with perf record -g)
c) dump_backtrace (at panic et al.)

For example, in case of a),

 $ echo function_graph > /sys/kernel/debug/tracing/current_tracer
 $ echo 1 > /proc/sys/kernel/stack_trace_enabled
 $ cat /sys/kernel/debug/tracing/stack_trace
       Depth    Size   Location    (54 entries)
       -----    ----   --------
 0)     4504      16   gic_raise_softirq+0x28/0x150
 1)     4488      80   smp_cross_call+0x38/0xb8
 2)     4408      48   return_to_handler+0x0/0x40
 3)     4360      32   return_to_handler+0x0/0x40
 ...

In case of b),

 $ echo function_graph > /sys/kernel/debug/tracing/current_tracer
 $ perf record -e mem:XXX:x -ag -- sleep 10
 $ perf report
                 ...
                 |          |          |--0.22%-- 0x550f8
                 |          |          |          0x10888
                 |          |          |          el0_svc_naked
                 |          |          |          sys_openat
                 |          |          |          return_to_handler
                 |          |          |          return_to_handler
                 ...

In case of c),

 $ echo function_graph > /sys/kernel/debug/tracing/current_tracer
 $ echo c > /proc/sysrq-trigger
 ...
 Call trace:
 [<ffffffc00044d3ac>] sysrq_handle_crash+0x24/0x30
 [<ffffffc000092250>] return_to_handler+0x0/0x40
 [<ffffffc000092250>] return_to_handler+0x0/0x40
 ...
Quoted 3 lines

This patch replaces such entries with real addresses preserved in current->ret_stack[] at unwind_frame(). This way, we can cover all the cases.

I've observed a strange behavior when playing with case c). Call trace is as follows when function_graph is not used.

Call trace:
 [<fffffe00003dc738>] sysrq_handle_crash+0x24/0x30          <- (1)
 [<fffffe00003dd2ac>] __handle_sysrq+0x128/0x19c            <- (2)
 [<fffffe00003dd730>] write_sysrq_trigger+0x60/0x74
 [<fffffe0000249fc4>] proc_reg_write+0x84/0xc0
 [<fffffe00001f2638>] __vfs_write+0x44/0x104                <- (3)
 [<fffffe00001f2e60>] vfs_write+0x98/0x1a8
 [<fffffe00001f3730>] SyS_write+0x50/0xb0
 [<fffffe00000939ec>] el0_svc_naked+0x20/0x28               <- (4)

When function_graph is set, some entries, such as do_mem_abort, are added between (1) and (2). In addition, entries from (3) to (4) are not printed out. As tracking down elements of ret_stack[], I realize dump_backtrace() has been terminated before reaching to ret_stack[0]. Have you seen this kind of behavior? I believe push & pop operations work correctly.

Quoted 8 lines
Quoted 1 line

Please correct me if I'm wrong.

Oops, I mis-interpreted the result output.
You are right.
This can happen because the original dump_backtrace() wants to trace a stack from a function where an exception has taken place, sysrq_handle_crash(), while ret_stack[curr_ret_stack] doesn't point to that function, but the very top of traced functions in callchains, that is, __do_kernel_fault in your case, probably.

Yes, __do_kernel_fault.

Quoted 10 lines

So it results in replacing entries of return_to_handler to wrong function addresses.

A fixup! patch attached below (informative only) fixes this issue by once tracing all the functions on a stack, but preventing a top few ones from being printed. But there is a tricky thing here: we have to use 'regs->pc' instead of frame.pc as a trapped function because, as I've already mentioned before, we always miss the function when walking through a stack from an exception handler to functions in a thread context.
(Please note that we will introduce a dummy stack frame at interrupt, but not at exception.)

Thanks for clear explanation!

Since I'm not the only person to experience the above case, I report one more and the last observation ;)

PC and LR information is printed out when function_graph is set.

PC is at sysrq_handle_crash+0x24/0x30
LR is at sysrq_handle_crash+0x10/0x30

The logs are as follows when function_graph turns off.

PC is at sysrq_handle_crash+0x24/0x30
LR is at __handle_sysrq+0x128/0x19c

I think __show_regs() might have a similar problem when retrieving LR according to the below informative patch. Thoughts?

Quoted 227 lines
Quoted 100 lines
Quoted 30 lines

---

arch/arm64/include/asm/ftrace.h     |    2 ++
arch/arm64/include/asm/stacktrace.h |    3 +++
arch/arm64/kernel/perf_callchain.c  |    3 +++
arch/arm64/kernel/process.c         |    3 +++
arch/arm64/kernel/return_address.c  |    3 +++
arch/arm64/kernel/stacktrace.c      |   17 +++++++++++++++++
arch/arm64/kernel/time.c            |    3 +++
arch/arm64/kernel/traps.c           |    3 +++
8 files changed, 37 insertions(+)
arch/arm64/include/asm/ftrace.h +2 -0
index c5534fa..3c60f37 100644
@@ -28,6 +28,8 @@ struct dyn_arch_ftrace {
 
 extern unsigned long ftrace_graph_call;
 
+extern void return_to_handler(void);
+
 static inline unsigned long ftrace_call_adjust(unsigned long addr)
 {
 	/*
arch/arm64/include/asm/stacktrace.h +0 -0
index 6fb61c5..801a16db 100644

arch/arm64/include/asm/stacktrace.h struct stackframe { · line 28

Quoted 7 lines
arch/arm64/include/asm/stacktrace.h +3 -0
@@ -22,6 +22,9 @@ struct stackframe {
 	unsigned long fp;
 	unsigned long sp;
 	unsigned long pc;
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	unsigned int graph;
+#endif
 };

How about using int instead of unsigned int to align with cure_ret_stack of struct task_struct?

Quoted 59 lines
arch/arm64/include/asm/stacktrace.h +0 -0
 extern int unwind_frame(struct task_struct *tsk, struct stackframe *frame);
arch/arm64/kernel/perf_callchain.c +3 -0
index 797220d..ff46654 100644
@@ -164,6 +164,9 @@ void perf_callchain_kernel(struct perf_callchain_entry *entry,
 	frame.fp = regs->regs[29];
 	frame.sp = regs->sp;
 	frame.pc = regs->pc;
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	frame.graph = current->curr_ret_stack;
+#endif
 
 	walk_stackframe(current, &frame, callchain_trace, entry);
 }
arch/arm64/kernel/process.c +3 -0
index 98bf546..88d742b 100644
@@ -344,6 +344,9 @@ unsigned long get_wchan(struct task_struct *p)
 	frame.fp = thread_saved_fp(p);
 	frame.sp = thread_saved_sp(p);
 	frame.pc = thread_saved_pc(p);
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	frame.graph = p->curr_ret_stack;
+#endif
 	stack_page = (unsigned long)task_stack_page(p);
 	do {
 		if (frame.sp < stack_page ||
arch/arm64/kernel/return_address.c +3 -0
index 07b37ac..1718706 100644
@@ -43,6 +43,9 @@ void *return_address(unsigned int level)
 	frame.fp = (unsigned long)__builtin_frame_address(0);
 	frame.sp = current_stack_pointer;
 	frame.pc = (unsigned long)return_address; /* dummy */
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	frame.graph = current->curr_ret_stack;
+#endif
 
 	walk_stackframe(current, &frame, save_return_addr, &data);
 
arch/arm64/kernel/stacktrace.c +1 -0
index 9c7acf8..0a39049 100644
@@ -17,6 +17,7 @@
 */
 #include <linux/kernel.h>
 #include <linux/export.h>
+#include <linux/ftrace.h>
 #include <linux/sched.h>
 #include <linux/stacktrace.h>
 
@@ -66,6 +67,19 @@ int notrace unwind_frame(struct task_struct *tsk, struct stackframe *frame)
 	frame->fp = *(unsigned long *)(fp);
 	frame->pc = *(unsigned long *)(fp + 8);
 
Quoted 16 lines

arch/arm64/kernel/stacktrace.c int notrace unwind_frame(struct task_struct *tsk, struct stackframe *frame) · line 81

Quoted 12 lines
arch/arm64/kernel/stacktrace.c +12 -0
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	if (tsk && tsk->ret_stack &&
+			(frame->pc == (unsigned long)return_to_handler)) {
+		/*
+		 * This is a case where function graph tracer has
+		 * modified a return address (LR) in a stack frame
+		 * to hook a function return.
+		 * So replace it to an original value.
+		 */
+		frame->pc = tsk->ret_stack[frame->graph--].ret;
+	}
+#endif /* CONFIG_FUNCTION_GRAPH_TRACER */

There is an index check of ret_stack[] in case of x86 [1]. Even though graph is unsigned int, I think we need to check the value of frame->graph before accessing ret_stack[].

I'm not sure that the checking is very useful because, if it happens, it is a bug. It might make sense to avoid a possible panic though.

Quoted 12 lines
Quoted 12 lines
arch/arm64/kernel/stacktrace.c +4 -0
+
 	/*
 	 * Check whether we are going to walk through from interrupt stack
 	 * to task stack.
@@ -137,6 +151,9 @@ void save_stack_trace_tsk(struct task_struct *tsk, struct stack_trace *trace)
 		frame.sp = current_stack_pointer;
 		frame.pc = (unsigned long)save_stack_trace_tsk;
 	}
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	frame.graph = tsk->curr_ret_stack;
+#endif
 
Quoted 15 lines

arch/arm64/kernel/time.c unsigned long profile_pc(struct pt_regs *regs) · line 57

Quoted 13 lines
arch/arm64/kernel/stacktrace.c +0 -0
 	walk_stackframe(tsk, &frame, save_trace, &data);
 	if (trace->nr_entries < trace->max_entries)
arch/arm64/kernel/time.c +3 -0
index 6e5c521..5977969 100644
@@ -52,6 +52,9 @@ unsigned long profile_pc(struct pt_regs *regs)
 	frame.fp = regs->regs[29];
 	frame.sp = regs->sp;
 	frame.pc = regs->pc;
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	frame.graph = -1; /* no task info */
+#endif

graph is unsigned int type. Is this intentional?

No. This initialization is, I believe, redundant as it is not checked anywhere, but I will re-think of it along with the checking above.

Thanks,
-Takahiro AKASHI

Quoted 5 lines

Best Regards
Jungseok Lee

[1] arch/x86/kernel/dumpstack.c

----8<----
From 9ebba7167f7838daf68d8231f04141d2f4d4b7b5 Mon Sep 17 00:00:00 2001
From: AKASHI Takahiro <[email protected]>
Date: Wed, 25 Nov 2015 13:29:54 +0900
Subject: [PATCH] fixup! arm64: ftrace: fix a stack tracer's output under function graph tracer

---
arch/arm64/kernel/traps.c | 23 +++++++++++++++++------

1 file changed, 17 insertions(+), 6 deletions(-)
arch/arm64/kernel/traps.c +17 -6
index 46053c2..f140029 100644
@@ -147,17 +147,14 @@ static void dump_backtrace(struct pt_regs *regs, struct task_struct *tsk)
 {
 	struct stackframe frame;
 	unsigned long _irq_stack_ptr = per_cpu(irq_stack_ptr, smp_processor_id());
+	int skip;
 
 	pr_debug("%s(regs = %p tsk = %p)\n", __func__, regs, tsk);
 
 	if (!tsk)
 		tsk = current;
 
-	if (regs) {
-		frame.fp = regs->regs[29];
-		frame.sp = regs->sp;
-		frame.pc = regs->pc;
-	} else if (tsk == current) {
+	if (tsk == current) {
 		frame.fp = (unsigned long)__builtin_frame_address(0);
 		frame.sp = current_stack_pointer;
 		frame.pc = (unsigned long)dump_backtrace;
@@ -173,13 +170,27 @@ static void dump_backtrace(struct pt_regs *regs, struct task_struct *tsk)
 	frame.graph = tsk->curr_ret_stack;
 #endif
 
+	skip = (regs ? 1 : 0);
 	pr_emerg("Call trace:\n");
 	while (1) {
 		unsigned long where = frame.pc;
 		unsigned long stack;
 		int ret;
 
-		dump_backtrace_entry(where);
+		/* skip until specified stack frame */
+		if (!skip)
+			dump_backtrace_entry(where);
+		else if (frame.fp == regs->regs[29]) {
+			skip = 0;
+			/*
+			 * Mostly, this is the case where this function is
+			 * called in panic/abort. As exception handler's
+			 * stack frame does not contain the corresponding pc
+			 * at which an exception has taken place, use regs->pc
+			 * instead.
+			 */
+			dump_backtrace_entry(regs->pc);
+		}
 		ret = unwind_frame(tsk, &frame);
 		if (ret < 0)
 			break;
Signature · 1 line
1.7.9.5

This fixes up the issue I mentioned.

Best Regards
Jungseok Lee

#13 lore raw

PATCH v6 3/6 arm64: ftrace: fix a stack tracer's output under function graph tracer

Jungseok,

Quoted 103 lines · Jungseok Lee
Quoted 70 lines · AKASHI Takahiro
Quoted 69 lines · Jungseok Lee

On Nov 18, 2015, at 3:43 PM, AKASHI Takahiro wrote:

Hi Akashi,

Quoted 42 lines

Function graph tracer modifies a return address (LR) in a stack frame to hook a function return. This will result in many useless entries (return_to_handler) showing up in
a) a stack tracer's output
b) perf call graph (with perf record -g)
c) dump_backtrace (at panic et al.)

For example, in case of a),

  $ echo function_graph > /sys/kernel/debug/tracing/current_tracer
  $ echo 1 > /proc/sys/kernel/stack_trace_enabled
  $ cat /sys/kernel/debug/tracing/stack_trace
        Depth    Size   Location    (54 entries)
        -----    ----   --------
  0)     4504      16   gic_raise_softirq+0x28/0x150
  1)     4488      80   smp_cross_call+0x38/0xb8
  2)     4408      48   return_to_handler+0x0/0x40
  3)     4360      32   return_to_handler+0x0/0x40
  ...

In case of b),

  $ echo function_graph > /sys/kernel/debug/tracing/current_tracer
  $ perf record -e mem:XXX:x -ag -- sleep 10
  $ perf report
                  ...
                  |          |          |--0.22%-- 0x550f8
                  |          |          |          0x10888
                  |          |          |          el0_svc_naked
                  |          |          |          sys_openat
                  |          |          |          return_to_handler
                  |          |          |          return_to_handler
                  ...

In case of c),

  $ echo function_graph > /sys/kernel/debug/tracing/current_tracer
  $ echo c > /proc/sysrq-trigger
  ...
  Call trace:
  [<ffffffc00044d3ac>] sysrq_handle_crash+0x24/0x30
  [<ffffffc000092250>] return_to_handler+0x0/0x40
  [<ffffffc000092250>] return_to_handler+0x0/0x40
  ...
Quoted 3 lines

This patch replaces such entries with real addresses preserved in current->ret_stack[] at unwind_frame(). This way, we can cover all the cases.

I've observed a strange behavior when playing with case c). Call trace is as follows when function_graph is not used.

Call trace:
  [<fffffe00003dc738>] sysrq_handle_crash+0x24/0x30          <- (1)
  [<fffffe00003dd2ac>] __handle_sysrq+0x128/0x19c            <- (2)
  [<fffffe00003dd730>] write_sysrq_trigger+0x60/0x74
  [<fffffe0000249fc4>] proc_reg_write+0x84/0xc0
  [<fffffe00001f2638>] __vfs_write+0x44/0x104                <- (3)
  [<fffffe00001f2e60>] vfs_write+0x98/0x1a8
  [<fffffe00001f3730>] SyS_write+0x50/0xb0
  [<fffffe00000939ec>] el0_svc_naked+0x20/0x28               <- (4)

When function_graph is set, some entries, such as do_mem_abort, are added between (1) and (2). In addition, entries from (3) to (4) are not printed out. As tracking down elements of ret_stack[], I realize dump_backtrace() has been terminated before reaching to ret_stack[0]. Have you seen this kind of behavior? I believe push & pop operations work correctly.

Quoted 8 lines
Quoted 1 line

Please correct me if I'm wrong.

Oops, I mis-interpreted the result output.
You are right.
This can happen because the original dump_backtrace() wants to trace a stack from a function where an exception has taken place, sysrq_handle_crash(), while ret_stack[curr_ret_stack] doesn't point to that function, but the very top of traced functions in callchains, that is, __do_kernel_fault in your case, probably.

Yes, __do_kernel_fault.

Quoted 10 lines

So it results in replacing entries of return_to_handler to wrong function addresses.

A fixup! patch attached below (informative only) fixes this issue by once tracing all the functions on a stack, but preventing a top few ones from being printed. But there is a tricky thing here: we have to use 'regs->pc' instead of frame.pc as a trapped function because, as I've already mentioned before, we always miss the function when walking through a stack from an exception handler to functions in a thread context.
(Please note that we will introduce a dummy stack frame at interrupt, but not at exception.)

Thanks for clear explanation!

Since I'm not the only person to experience the above case, I report one more and the last observation ;)

PC and LR information is printed out when function_graph is set.

PC is at sysrq_handle_crash+0x24/0x30
LR is at sysrq_handle_crash+0x10/0x30

Quoted 7 lines

The logs are as follows when function_graph turns off.

PC is at sysrq_handle_crash+0x24/0x30
LR is at __handle_sysrq+0x128/0x19c

I think __show_regs() might have a similar problem when retrieving LR according to the below informative patch. Thoughts?

Well, I believe that it is normal.
Sysrq_handle_crash() is basically a leaf function, but once function or function graph tracer is turned on, ftrace_caller() is called before its function body and so LR has been modified when panic/show_regs().

Thanks,
-Takahiro AKASHI

Quoted 233 lines
Quoted 226 lines
Quoted 100 lines
Quoted 30 lines

---

arch/arm64/include/asm/ftrace.h     |    2 ++
arch/arm64/include/asm/stacktrace.h |    3 +++
arch/arm64/kernel/perf_callchain.c  |    3 +++
arch/arm64/kernel/process.c         |    3 +++
arch/arm64/kernel/return_address.c  |    3 +++
arch/arm64/kernel/stacktrace.c      |   17 +++++++++++++++++
arch/arm64/kernel/time.c            |    3 +++
arch/arm64/kernel/traps.c           |    3 +++
8 files changed, 37 insertions(+)
arch/arm64/include/asm/ftrace.h +2 -0
index c5534fa..3c60f37 100644
@@ -28,6 +28,8 @@ struct dyn_arch_ftrace {
 
 extern unsigned long ftrace_graph_call;
 
+extern void return_to_handler(void);
+
 static inline unsigned long ftrace_call_adjust(unsigned long addr)
 {
 	/*
arch/arm64/include/asm/stacktrace.h +0 -0
index 6fb61c5..801a16db 100644

arch/arm64/include/asm/stacktrace.h struct stackframe { · line 28

Quoted 7 lines
arch/arm64/include/asm/stacktrace.h +3 -0
@@ -22,6 +22,9 @@ struct stackframe {
 	unsigned long fp;
 	unsigned long sp;
 	unsigned long pc;
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	unsigned int graph;
+#endif
 };

How about using int instead of unsigned int to align with cure_ret_stack of struct task_struct?

Quoted 59 lines
arch/arm64/include/asm/stacktrace.h +0 -0
 extern int unwind_frame(struct task_struct *tsk, struct stackframe *frame);
arch/arm64/kernel/perf_callchain.c +3 -0
index 797220d..ff46654 100644
@@ -164,6 +164,9 @@ void perf_callchain_kernel(struct perf_callchain_entry *entry,
 	frame.fp = regs->regs[29];
 	frame.sp = regs->sp;
 	frame.pc = regs->pc;
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	frame.graph = current->curr_ret_stack;
+#endif
 
 	walk_stackframe(current, &frame, callchain_trace, entry);
 }
arch/arm64/kernel/process.c +3 -0
index 98bf546..88d742b 100644
@@ -344,6 +344,9 @@ unsigned long get_wchan(struct task_struct *p)
 	frame.fp = thread_saved_fp(p);
 	frame.sp = thread_saved_sp(p);
 	frame.pc = thread_saved_pc(p);
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	frame.graph = p->curr_ret_stack;
+#endif
 	stack_page = (unsigned long)task_stack_page(p);
 	do {
 		if (frame.sp < stack_page ||
arch/arm64/kernel/return_address.c +3 -0
index 07b37ac..1718706 100644
@@ -43,6 +43,9 @@ void *return_address(unsigned int level)
 	frame.fp = (unsigned long)__builtin_frame_address(0);
 	frame.sp = current_stack_pointer;
 	frame.pc = (unsigned long)return_address; /* dummy */
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	frame.graph = current->curr_ret_stack;
+#endif
 
 	walk_stackframe(current, &frame, save_return_addr, &data);
 
arch/arm64/kernel/stacktrace.c +1 -0
index 9c7acf8..0a39049 100644
@@ -17,6 +17,7 @@
  */
 #include <linux/kernel.h>
 #include <linux/export.h>
+#include <linux/ftrace.h>
 #include <linux/sched.h>
 #include <linux/stacktrace.h>
 
@@ -66,6 +67,19 @@ int notrace unwind_frame(struct task_struct *tsk, struct stackframe *frame)
 	frame->fp = *(unsigned long *)(fp);
 	frame->pc = *(unsigned long *)(fp + 8);
 
Quoted 16 lines

arch/arm64/kernel/stacktrace.c int notrace unwind_frame(struct task_struct *tsk, struct stackframe *frame) · line 81

Quoted 12 lines
arch/arm64/kernel/stacktrace.c +12 -0
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	if (tsk && tsk->ret_stack &&
+			(frame->pc == (unsigned long)return_to_handler)) {
+		/*
+		 * This is a case where function graph tracer has
+		 * modified a return address (LR) in a stack frame
+		 * to hook a function return.
+		 * So replace it to an original value.
+		 */
+		frame->pc = tsk->ret_stack[frame->graph--].ret;
+	}
+#endif /* CONFIG_FUNCTION_GRAPH_TRACER */

There is an index check of ret_stack[] in case of x86 [1]. Even though graph is unsigned int, I think we need to check the value of frame->graph before accessing ret_stack[].

I'm not sure that the checking is very useful because, if it happens, it is a bug. It might make sense to avoid a possible panic though.

Quoted 12 lines
Quoted 12 lines
arch/arm64/kernel/stacktrace.c +4 -0
+
 	/*
 	 * Check whether we are going to walk through from interrupt stack
 	 * to task stack.
@@ -137,6 +151,9 @@ void save_stack_trace_tsk(struct task_struct *tsk, struct stack_trace *trace)
 		frame.sp = current_stack_pointer;
 		frame.pc = (unsigned long)save_stack_trace_tsk;
 	}
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	frame.graph = tsk->curr_ret_stack;
+#endif
 
Quoted 15 lines

arch/arm64/kernel/time.c unsigned long profile_pc(struct pt_regs *regs) · line 57

Quoted 13 lines
arch/arm64/kernel/stacktrace.c +0 -0
 	walk_stackframe(tsk, &frame, save_trace, &data);
 	if (trace->nr_entries < trace->max_entries)
arch/arm64/kernel/time.c +3 -0
index 6e5c521..5977969 100644
@@ -52,6 +52,9 @@ unsigned long profile_pc(struct pt_regs *regs)
 	frame.fp = regs->regs[29];
 	frame.sp = regs->sp;
 	frame.pc = regs->pc;
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+	frame.graph = -1; /* no task info */
+#endif

graph is unsigned int type. Is this intentional?

No. This initialization is, I believe, redundant as it is not checked anywhere, but I will re-think of it along with the checking above.

Thanks,
-Takahiro AKASHI

Quoted 5 lines

Best Regards
Jungseok Lee

[1] arch/x86/kernel/dumpstack.c

----8<----
From 9ebba7167f7838daf68d8231f04141d2f4d4b7b5 Mon Sep 17 00:00:00 2001
From: AKASHI Takahiro <[email protected]>
Date: Wed, 25 Nov 2015 13:29:54 +0900
Subject: [PATCH] fixup! arm64: ftrace: fix a stack tracer's output under function graph tracer

---
arch/arm64/kernel/traps.c | 23 +++++++++++++++++------

1 file changed, 17 insertions(+), 6 deletions(-)
arch/arm64/kernel/traps.c +17 -7
index 46053c2..f140029 100644
@@ -147,17 +147,14 @@ static void dump_backtrace(struct pt_regs *regs, struct task_struct *tsk)
 {
 	struct stackframe frame;
 	unsigned long _irq_stack_ptr = per_cpu(irq_stack_ptr, smp_processor_id());
+	int skip;
 
 	pr_debug("%s(regs = %p tsk = %p)\n", __func__, regs, tsk);
 
 	if (!tsk)
 		tsk = current;
 
-	if (regs) {
-		frame.fp = regs->regs[29];
-		frame.sp = regs->sp;
-		frame.pc = regs->pc;
-	} else if (tsk == current) {
+	if (tsk == current) {
 		frame.fp = (unsigned long)__builtin_frame_address(0);
 		frame.sp = current_stack_pointer;
 		frame.pc = (unsigned long)dump_backtrace;
@@ -173,13 +170,27 @@ static void dump_backtrace(struct pt_regs *regs, struct task_struct *tsk)
 	frame.graph = tsk->curr_ret_stack;
 #endif
 
+	skip = (regs ? 1 : 0);
 	pr_emerg("Call trace:\n");
 	while (1) {
 		unsigned long where = frame.pc;
 		unsigned long stack;
 		int ret;
 
-		dump_backtrace_entry(where);
+		/* skip until specified stack frame */
+		if (!skip)
+			dump_backtrace_entry(where);
+		else if (frame.fp == regs->regs[29]) {
+			skip = 0;
+			/*
+			 * Mostly, this is the case where this function is
+			 * called in panic/abort. As exception handler's
+			 * stack frame does not contain the corresponding pc
+			 * at which an exception has taken place, use regs->pc
+			 * instead.
+			 */
+			dump_backtrace_entry(regs->pc);
+		}
 		ret = unwind_frame(tsk, &frame);
 		if (ret < 0)
 			break;
--
Quoted 1 line

1.7.9.5

This fixes up the issue I mentioned.

Best Regards
Jungseok Lee

#14 lore raw

PATCH v6 3/6 arm64: ftrace: fix a stack tracer's output under function graph tracer

Quoted 1 line · AKASHI Takahiro

Jungseok,

Hi Akashi,

Quoted 109 lines · Jungseok Lee
Quoted 108 lines · AKASHI Takahiro
Quoted 70 lines · Jungseok Lee
Quoted 69 lines · AKASHI Takahiro

Hi Akashi,

Quoted 42 lines

Function graph tracer modifies a return address (LR) in a stack frame to hook a function return. This will result in many useless entries (return_to_handler) showing up in
a) a stack tracer's output
b) perf call graph (with perf record -g)
c) dump_backtrace (at panic et al.)

For example, in case of a),

 $ echo function_graph > /sys/kernel/debug/tracing/current_tracer
 $ echo 1 > /proc/sys/kernel/stack_trace_enabled
 $ cat /sys/kernel/debug/tracing/stack_trace
       Depth    Size   Location    (54 entries)
       -----    ----   --------
 0)     4504      16   gic_raise_softirq+0x28/0x150
 1)     4488      80   smp_cross_call+0x38/0xb8
 2)     4408      48   return_to_handler+0x0/0x40
 3)     4360      32   return_to_handler+0x0/0x40
 ...

In case of b),

 $ echo function_graph > /sys/kernel/debug/tracing/current_tracer
 $ perf record -e mem:XXX:x -ag -- sleep 10
 $ perf report
                 ...
                 |          |          |--0.22%-- 0x550f8
                 |          |          |          0x10888
                 |          |          |          el0_svc_naked
                 |          |          |          sys_openat
                 |          |          |          return_to_handler
                 |          |          |          return_to_handler
                 ...

In case of c),

 $ echo function_graph > /sys/kernel/debug/tracing/current_tracer
 $ echo c > /proc/sysrq-trigger
 ...
 Call trace:
 [<ffffffc00044d3ac>] sysrq_handle_crash+0x24/0x30
 [<ffffffc000092250>] return_to_handler+0x0/0x40
 [<ffffffc000092250>] return_to_handler+0x0/0x40
 ...
Quoted 3 lines

This patch replaces such entries with real addresses preserved in current->ret_stack[] at unwind_frame(). This way, we can cover all the cases.

I've observed a strange behavior when playing with case c). Call trace is as follows when function_graph is not used.

Call trace:
 [<fffffe00003dc738>] sysrq_handle_crash+0x24/0x30          <- (1)
 [<fffffe00003dd2ac>] __handle_sysrq+0x128/0x19c            <- (2)
 [<fffffe00003dd730>] write_sysrq_trigger+0x60/0x74
 [<fffffe0000249fc4>] proc_reg_write+0x84/0xc0
 [<fffffe00001f2638>] __vfs_write+0x44/0x104                <- (3)
 [<fffffe00001f2e60>] vfs_write+0x98/0x1a8
 [<fffffe00001f3730>] SyS_write+0x50/0xb0
 [<fffffe00000939ec>] el0_svc_naked+0x20/0x28               <- (4)

When function_graph is set, some entries, such as do_mem_abort, are added between (1) and (2). In addition, entries from (3) to (4) are not printed out. As tracking down elements of ret_stack[], I realize dump_backtrace() has been terminated before reaching to ret_stack[0]. Have you seen this kind of behavior? I believe push & pop operations work correctly.

Quoted 8 lines
Quoted 1 line

Please correct me if I'm wrong.

Oops, I mis-interpreted the result output.
You are right.
This can happen because the original dump_backtrace() wants to trace a stack from a function where an exception has taken place, sysrq_handle_crash(), while ret_stack[curr_ret_stack] doesn't point to that function, but the very top of traced functions in callchains, that is, __do_kernel_fault in your case, probably.

Yes, __do_kernel_fault.

Quoted 10 lines

So it results in replacing entries of return_to_handler to wrong function addresses.

A fixup! patch attached below (informative only) fixes this issue by once tracing all the functions on a stack, but preventing a top few ones from being printed. But there is a tricky thing here: we have to use 'regs->pc' instead of frame.pc as a trapped function because, as I've already mentioned before, we always miss the function when walking through a stack from an exception handler to functions in a thread context.
(Please note that we will introduce a dummy stack frame at interrupt, but not at exception.)

Thanks for clear explanation!

Since I'm not the only person to experience the above case, I report one more and the last observation ;)

PC and LR information is printed out when function_graph is set.

PC is at sysrq_handle_crash+0x24/0x30
LR is at sysrq_handle_crash+0x10/0x30

The logs are as follows when function_graph turns off.

PC is at sysrq_handle_crash+0x24/0x30
LR is at __handle_sysrq+0x128/0x19c

Quoted 7 lines
Quoted 2 lines

I think __show_regs() might have a similar problem when retrieving LR according to the below informative patch. Thoughts?

Well, I believe that it is normal.
Sysrq_handle_crash() is basically a leaf function, but once function or function graph tracer is turned on, ftrace_caller() is called before its function body and so LR has been modified when panic/show_regs().

Got it. All aspects of this patch is clear now. Thanks!

Best Regards
Jungseok Lee

#15 lore raw

PATCH v6 4/6 arm64: insn: add instruction decoders for ldp/stp and add/sub

In reply to AKASHI Takahiro

A function prologue analyzer is a requisite for implementing stack tracer and getting better views of stack usages on arm64.
To implement a function prologue analyzer, we have to be able to decode, at least, stp, add, sub and mov instructions.

This patch adds decoders for those instructions, that are used solely by stack tracer for now, but generic enough for other uses.

---
 arch/arm64/include/asm/insn.h |   18 ++++++++
 arch/arm64/kernel/insn.c      |  102 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 120 insertions(+)
arch/arm64/include/asm/insn.h +18 -0
index 30e50eb..8d5c538 100644
@@ -165,6 +165,8 @@ enum aarch64_insn_ldst_type {
 	AARCH64_INSN_LDST_STORE_PAIR_PRE_INDEX,
 	AARCH64_INSN_LDST_LOAD_PAIR_POST_INDEX,
 	AARCH64_INSN_LDST_STORE_PAIR_POST_INDEX,
+	AARCH64_INSN_LDST_LOAD_PAIR,
+	AARCH64_INSN_LDST_STORE_PAIR,
 };
 
 enum aarch64_insn_adsb_type {
@@ -225,6 +227,8 @@ static __always_inline u32 aarch64_insn_get_##abbr##_value(void) \
 
 __AARCH64_INSN_FUNCS(str_reg,	0x3FE0EC00, 0x38206800)
 __AARCH64_INSN_FUNCS(ldr_reg,	0x3FE0EC00, 0x38606800)
+__AARCH64_INSN_FUNCS(stp,	0x7FC00000, 0x29000000)
+__AARCH64_INSN_FUNCS(ldp,	0x7FC00000, 0x29400000)
 __AARCH64_INSN_FUNCS(stp_post,	0x7FC00000, 0x28800000)
 __AARCH64_INSN_FUNCS(ldp_post,	0x7FC00000, 0x28C00000)
 __AARCH64_INSN_FUNCS(stp_pre,	0x7FC00000, 0x29800000)
@@ -277,6 +281,7 @@ __AARCH64_INSN_FUNCS(hint,	0xFFFFF01F, 0xD503201F)
 __AARCH64_INSN_FUNCS(br,	0xFFFFFC1F, 0xD61F0000)
 __AARCH64_INSN_FUNCS(blr,	0xFFFFFC1F, 0xD63F0000)
 __AARCH64_INSN_FUNCS(ret,	0xFFFFFC1F, 0xD65F0000)
+__AARCH64_INSN_FUNCS(eret,	0xFFFFFFFF, 0xD69F00E0)
 
 #undef	__AARCH64_INSN_FUNCS
 
@@ -370,6 +375,19 @@ bool aarch32_insn_is_wide(u32 insn);
 u32 aarch32_insn_extract_reg_num(u32 insn, int offset);
 u32 aarch32_insn_mcr_extract_opc2(u32 insn);
 u32 aarch32_insn_mcr_extract_crm(u32 insn);
+int aarch64_insn_decode_add_sub_imm(u32 insn,
+				    enum aarch64_insn_register *dst,
+				    enum aarch64_insn_register *src,
+				    int *imm,
+				    enum aarch64_insn_variant *variant,
+				    enum aarch64_insn_adsb_type *type);
+int aarch64_insn_decode_load_store_pair(u32 insn,
+					enum aarch64_insn_register *reg1,
+					enum aarch64_insn_register *reg2,
+					enum aarch64_insn_register *base,
+					int *offset,
+					enum aarch64_insn_variant *variant,
+					enum aarch64_insn_ldst_type *type);
 #endif /* __ASSEMBLY__ */
 
 #endif	/* __ASM_INSN_H */
arch/arm64/kernel/insn.c +102 -0

111 lines not on this page · raw message

Signature · 1 line
1.7.9.5
#16 lore raw

PATCH v6 4/6 arm64: insn: add instruction decoders for ldp/stp and add/sub

Quoted 21 lines · AKASHI Takahiro

A function prologue analyzer is a requisite for implementing stack tracer and getting better views of stack usages on arm64.
To implement a function prologue analyzer, we have to be able to decode, at least, stp, add, sub and mov instructions.

This patch adds decoders for those instructions, that are used solely by stack tracer for now, but generic enough for other uses.

---
 arch/arm64/include/asm/insn.h |   18 ++++++++
 arch/arm64/kernel/insn.c      |  102 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 120 insertions(+)
arch/arm64/include/asm/insn.h +0 -0
index 30e50eb..8d5c538 100644

arch/arm64/include/asm/insn.h enum aarch64_insn_ldst_type { · line 169

Quoted 5 lines
arch/arm64/include/asm/insn.h +2 -0
@@ -165,6 +165,8 @@ enum aarch64_insn_ldst_type {
 	AARCH64_INSN_LDST_STORE_PAIR_PRE_INDEX,
 	AARCH64_INSN_LDST_LOAD_PAIR_POST_INDEX,
 	AARCH64_INSN_LDST_STORE_PAIR_POST_INDEX,
+	AARCH64_INSN_LDST_LOAD_PAIR,
+	AARCH64_INSN_LDST_STORE_PAIR,

For consistency with the rest of this header, we should be calling these AARCH64_INSN_LDST_{LOAD,STORE}_PAIR_REG_OFFSET...

arch/arm64/include/asm/insn.h static __always_inline u32 aarch64_insn_get_##abbr##_value(void) \ · line 231

Quoted 9 lines
arch/arm64/include/asm/insn.h +2 -0
 };
 
 enum aarch64_insn_adsb_type {
@@ -225,6 +227,8 @@ static __always_inline u32 aarch64_insn_get_##abbr##_value(void) \
 
 __AARCH64_INSN_FUNCS(str_reg,	0x3FE0EC00, 0x38206800)
 __AARCH64_INSN_FUNCS(ldr_reg,	0x3FE0EC00, 0x38606800)
+__AARCH64_INSN_FUNCS(stp,	0x7FC00000, 0x29000000)
+__AARCH64_INSN_FUNCS(ldp,	0x7FC00000, 0x29400000)

... and using stp_reg/ldp_reg here.

arch/arm64/include/asm/insn.h __AARCH64_INSN_FUNCS(hint, 0xFFFFF01F, 0xD503201F) · line 284

Quoted 8 lines
arch/arm64/include/asm/insn.h +1 -0
 __AARCH64_INSN_FUNCS(stp_post,	0x7FC00000, 0x28800000)
 __AARCH64_INSN_FUNCS(ldp_post,	0x7FC00000, 0x28C00000)
 __AARCH64_INSN_FUNCS(stp_pre,	0x7FC00000, 0x29800000)
@@ -277,6 +281,7 @@ __AARCH64_INSN_FUNCS(hint,	0xFFFFF01F, 0xD503201F)
 __AARCH64_INSN_FUNCS(br,	0xFFFFFC1F, 0xD61F0000)
 __AARCH64_INSN_FUNCS(blr,	0xFFFFFC1F, 0xD63F0000)
 __AARCH64_INSN_FUNCS(ret,	0xFFFFFC1F, 0xD65F0000)
+__AARCH64_INSN_FUNCS(eret,	0xFFFFFFFF, 0xD69F00E0)

Is this encoding correct? I ended up with 0xd69f03e0.

Quoted 35 lines
arch/arm64/include/asm/insn.h +13 -0
 #undef	__AARCH64_INSN_FUNCS
 
@@ -370,6 +375,19 @@ bool aarch32_insn_is_wide(u32 insn);
 u32 aarch32_insn_extract_reg_num(u32 insn, int offset);
 u32 aarch32_insn_mcr_extract_opc2(u32 insn);
 u32 aarch32_insn_mcr_extract_crm(u32 insn);
+int aarch64_insn_decode_add_sub_imm(u32 insn,
+				    enum aarch64_insn_register *dst,
+				    enum aarch64_insn_register *src,
+				    int *imm,
+				    enum aarch64_insn_variant *variant,
+				    enum aarch64_insn_adsb_type *type);
+int aarch64_insn_decode_load_store_pair(u32 insn,
+					enum aarch64_insn_register *reg1,
+					enum aarch64_insn_register *reg2,
+					enum aarch64_insn_register *base,
+					int *offset,
+					enum aarch64_insn_variant *variant,
+					enum aarch64_insn_ldst_type *type);
 #endif /* __ASSEMBLY__ */
 
 #endif	/* __ASM_INSN_H */
arch/arm64/kernel/insn.c +1 -0
index c08b9ad..b56a66c 100644
@@ -33,6 +33,7 @@
 #include <asm/insn.h>
 
 #define AARCH64_INSN_SF_BIT	BIT(31)
+#define AARCH64_INSN_S_BIT	BIT(29)
 #define AARCH64_INSN_N_BIT	BIT(22)
 
 static int aarch64_insn_encoding_class[] = {

arch/arm64/kernel/insn.c u32 aarch32_insn_mcr_extract_crm(u32 insn) · line 1147

Quoted 6 lines
arch/arm64/kernel/insn.c +3 -0
@@ -1141,3 +1142,104 @@ u32 aarch32_insn_mcr_extract_crm(u32 insn)
 {
 	return insn & CRM_MASK;
 }
+
+enum aarch64_insn_register aarch64_insn_extract_reg_num(u32 insn,
+				enum aarch64_insn_register_type type)

Maybe call this aarch64_insn_decode_register? You're basically building decoders for some of the *_encode_* functions we already have.

Quoted 13 lines
arch/arm64/kernel/insn.c +13 -0
+{
+	int shift;
+
+	switch (type) {
+	case AARCH64_INSN_REGTYPE_RT:
+	case AARCH64_INSN_REGTYPE_RD:
+		shift = 0;
+		break;
+	case AARCH64_INSN_REGTYPE_RN:
+		shift = 5;
+		break;
+	case AARCH64_INSN_REGTYPE_RT2:
+	case AARCH64_INSN_REGTYPE_RA:

arch/arm64/kernel/insn.c u32 aarch32_insn_mcr_extract_crm(u32 insn) · line 1170

Quoted 10 lines
arch/arm64/kernel/insn.c +10 -0
+		shift = 10;
+		break;
+	case AARCH64_INSN_REGTYPE_RM:
+		shift = 16;
+		break;
+	default:
+		pr_err("%s: unknown register type decoding %d\n", __func__,
+		       type);
+		return ~0L;
+	}

This is largely a copy-paste from aarch64_insn_encode_register. Can you either work with what we have or factor out the common parts, please?

arch/arm64/kernel/insn.c u32 aarch32_insn_mcr_extract_crm(u32 insn) · line 1175

Quoted 4 lines
arch/arm64/kernel/insn.c +4 -0
+	return (insn & (GENMASK(4, 0) << shift)) >> shift;
+}
+
+int aarch64_insn_decode_add_sub_imm(u32 insn,

This one could be aarch64_insn_extract_add_sub_imm, if you like. Then extract is the converse of gen and decode is the converse of encode.

Quoted 6 lines
arch/arm64/kernel/insn.c +6 -0
+				    enum aarch64_insn_register *dst,
+				    enum aarch64_insn_register *src,
+				    int *imm,
+				    enum aarch64_insn_variant *variant,
+				    enum aarch64_insn_adsb_type *type)
+{

arch/arm64/kernel/insn.c u32 aarch32_insn_mcr_extract_crm(u32 insn) · line 1191

Quoted 10 lines
arch/arm64/kernel/insn.c +10 -0
+	if (aarch64_insn_is_add_imm(insn))
+		*type =	((insn) & AARCH64_INSN_S_BIT) ?
+				AARCH64_INSN_ADSB_ADD_SETFLAGS :
+				AARCH64_INSN_ADSB_ADD;
+	else if (aarch64_insn_is_sub_imm(insn))
+		*type =	((insn) & AARCH64_INSN_S_BIT) ?
+				AARCH64_INSN_ADSB_SUB_SETFLAGS :
+				AARCH64_INSN_ADSB_SUB;
+	else
+		return 0;

Why do we need to return 0 on error? -EINVAL might make more sense.

arch/arm64/kernel/insn.c u32 aarch32_insn_mcr_extract_crm(u32 insn) · line 1200

Quoted 9 lines
arch/arm64/kernel/insn.c +9 -0
+
+	*variant = (insn & AARCH64_INSN_SF_BIT) ? AARCH64_INSN_VARIANT_64BIT :
+					AARCH64_INSN_VARIANT_32BIT;
+
+	*dst = aarch64_insn_extract_reg_num(insn, AARCH64_INSN_REGTYPE_RD);
+
+	*src = aarch64_insn_extract_reg_num(insn, AARCH64_INSN_REGTYPE_RN);
+
+	/* TODO: ignore shilft field[23:22] */

I don't understand the TODO.

arch/arm64/kernel/insn.c u32 aarch32_insn_mcr_extract_crm(u32 insn) · line 1201

Quoted 1 line
arch/arm64/kernel/insn.c +1 -0
+	*imm = (int)aarch64_insn_decode_immediate(AARCH64_INSN_IMM_12, insn);

We use u64 for imm everywhere else.

arch/arm64/kernel/insn.c u32 aarch32_insn_mcr_extract_crm(u32 insn) · line 1202

Quoted 2 lines
arch/arm64/kernel/insn.c +2 -0
+	return 1;
+}

Similarly here, you're trying to implement the converse of aarch64_insn_gen_add_sub_imm

Quoted 27 lines
arch/arm64/kernel/insn.c +27 -0
+
+int aarch64_insn_decode_load_store_pair(u32 insn,
+					enum aarch64_insn_register *reg1,
+					enum aarch64_insn_register *reg2,
+					enum aarch64_insn_register *base,
+					int *offset,
+					enum aarch64_insn_variant *variant,
+					enum aarch64_insn_ldst_type *type)
+{
+	int imm;
+
+	if (aarch64_insn_is_stp(insn))
+		*type = AARCH64_INSN_LDST_STORE_PAIR;
+	else if (aarch64_insn_is_stp_post(insn))
+		*type = AARCH64_INSN_LDST_STORE_PAIR_POST_INDEX;
+	else if (aarch64_insn_is_stp_pre(insn))
+		*type = AARCH64_INSN_LDST_STORE_PAIR_PRE_INDEX;
+	else if (aarch64_insn_is_ldp(insn))
+		*type = AARCH64_INSN_LDST_LOAD_PAIR;
+	else if (aarch64_insn_is_ldp_post(insn))
+		*type = AARCH64_INSN_LDST_LOAD_PAIR_POST_INDEX;
+	else if (aarch64_insn_is_ldp_pre(insn))
+		*type = AARCH64_INSN_LDST_LOAD_PAIR_PRE_INDEX;
+	else
+		return 0;
+
+	*variant = (insn & AARCH64_INSN_S_BIT) ? AARCH64_INSN_VARIANT_64BIT :

arch/arm64/kernel/insn.c u32 aarch32_insn_mcr_extract_crm(u32 insn) · line 1241

Quoted 10 lines
arch/arm64/kernel/insn.c +10 -0
+				AARCH64_INSN_VARIANT_32BIT;
+
+	*reg1 = aarch64_insn_extract_reg_num(insn, AARCH64_INSN_REGTYPE_RT);
+
+	*reg2 = aarch64_insn_extract_reg_num(insn, AARCH64_INSN_REGTYPE_RT2);
+
+	*base = aarch64_insn_extract_reg_num(insn, AARCH64_INSN_REGTYPE_RN);
+
+	imm = (int)aarch64_insn_decode_immediate(AARCH64_INSN_IMM_7, insn);
+	asm("sbfm %0, %0, 0, 6" : "+r" (imm));

What? Can't you write this in C?

Will

#17 lore raw

PATCH v6 4/6 arm64: insn: add instruction decoders for ldp/stp and add/sub

On Dec 9, 2015, at 3:15 AM, Will Deacon wrote:

Hi Will,

Quoted 43 lines · AKASHI Takahiro
Quoted 21 lines

A function prologue analyzer is a requisite for implementing stack tracer and getting better views of stack usages on arm64.
To implement a function prologue analyzer, we have to be able to decode, at least, stp, add, sub and mov instructions.

This patch adds decoders for those instructions, that are used solely by stack tracer for now, but generic enough for other uses.

---
arch/arm64/include/asm/insn.h | 18 ++++++++
arch/arm64/kernel/insn.c | 102 +++++++++++++++++++++++++++++++++++++++++

2 files changed, 120 insertions(+)
arch/arm64/include/asm/insn.h +0 -0
index 30e50eb..8d5c538 100644

arch/arm64/include/asm/insn.h enum aarch64_insn_ldst_type { · line 169

Quoted 5 lines
arch/arm64/include/asm/insn.h +2 -0
@@ -165,6 +165,8 @@ enum aarch64_insn_ldst_type {
 	AARCH64_INSN_LDST_STORE_PAIR_PRE_INDEX,
 	AARCH64_INSN_LDST_LOAD_PAIR_POST_INDEX,
 	AARCH64_INSN_LDST_STORE_PAIR_POST_INDEX,
+	AARCH64_INSN_LDST_LOAD_PAIR,
+	AARCH64_INSN_LDST_STORE_PAIR,

For consistency with the rest of this header, we should be calling these AARCH64_INSN_LDST_{LOAD,STORE}_PAIR_REG_OFFSET...

arch/arm64/include/asm/insn.h static __always_inline u32 aarch64_insn_get_##abbr##_value(void) \ · line 231

Quoted 9 lines
arch/arm64/include/asm/insn.h +2 -0
 };
 
 enum aarch64_insn_adsb_type {
@@ -225,6 +227,8 @@ static __always_inline u32 aarch64_insn_get_##abbr##_value(void) \
 
 __AARCH64_INSN_FUNCS(str_reg,	0x3FE0EC00, 0x38206800)
 __AARCH64_INSN_FUNCS(ldr_reg,	0x3FE0EC00, 0x38606800)
+__AARCH64_INSN_FUNCS(stp,	0x7FC00000, 0x29000000)
+__AARCH64_INSN_FUNCS(ldp,	0x7FC00000, 0x29400000)

... and using stp_reg/ldp_reg here.

Quoted 10 lines

arch/arm64/include/asm/insn.h __AARCH64_INSN_FUNCS(hint, 0xFFFFF01F, 0xD503201F) · line 284

Quoted 8 lines
arch/arm64/include/asm/insn.h +1 -0
 __AARCH64_INSN_FUNCS(stp_post,	0x7FC00000, 0x28800000)
 __AARCH64_INSN_FUNCS(ldp_post,	0x7FC00000, 0x28C00000)
 __AARCH64_INSN_FUNCS(stp_pre,	0x7FC00000, 0x29800000)
@@ -277,6 +281,7 @@ __AARCH64_INSN_FUNCS(hint,	0xFFFFF01F, 0xD503201F)
 __AARCH64_INSN_FUNCS(br,	0xFFFFFC1F, 0xD61F0000)
 __AARCH64_INSN_FUNCS(blr,	0xFFFFFC1F, 0xD63F0000)
 __AARCH64_INSN_FUNCS(ret,	0xFFFFFC1F, 0xD65F0000)
+__AARCH64_INSN_FUNCS(eret,	0xFFFFFFFF, 0xD69F00E0)

Is this encoding correct? I ended up with 0xd69f03e0.

You're right. This is updated in [PATCH v6 6/6]. Please refer to [1] for more details.

[1] http://www.spinics.net/lists/arm-kernel/msg462856.html

Best Regards
Jungseok Lee

#18 lore raw

PATCH v6 4/6 arm64: insn: add instruction decoders for ldp/stp and add/sub

In reply to Will Deacon

Will,

I was back from my vacation.

Quoted 17 lines · Will Deacon
Quoted 16 lines · AKASHI Takahiro

A function prologue analyzer is a requisite for implementing stack tracer and getting better views of stack usages on arm64.
To implement a function prologue analyzer, we have to be able to decode, at least, stp, add, sub and mov instructions.

This patch adds decoders for those instructions, that are used solely by stack tracer for now, but generic enough for other uses.

---
  arch/arm64/include/asm/insn.h |   18 ++++++++
  arch/arm64/kernel/insn.c      |  102 +++++++++++++++++++++++++++++++++++++++++
  2 files changed, 120 insertions(+)
Quoted 13 lines

arch/arm64/include/asm/insn.h enum aarch64_insn_ldst_type { · line 169

Quoted 10 lines
arch/arm64/include/asm/insn.h +2 -0
index 30e50eb..8d5c538 100644
@@ -165,6 +165,8 @@ enum aarch64_insn_ldst_type {
  	AARCH64_INSN_LDST_STORE_PAIR_PRE_INDEX,
  	AARCH64_INSN_LDST_LOAD_PAIR_POST_INDEX,
  	AARCH64_INSN_LDST_STORE_PAIR_POST_INDEX,
+	AARCH64_INSN_LDST_LOAD_PAIR,
+	AARCH64_INSN_LDST_STORE_PAIR,

For consistency with the rest of this header, we should be calling these AARCH64_INSN_LDST_{LOAD,STORE}_PAIR_REG_OFFSET...

OK.
(But taking arguments of registers and offset is also common to _PRE_INDEX and _POST_INDEX cases).

Quoted 11 lines

arch/arm64/include/asm/insn.h static __always_inline u32 aarch64_insn_get_##abbr##_value(void) \ · line 231

Quoted 9 lines
arch/arm64/include/asm/insn.h +2 -0
  };
 
  enum aarch64_insn_adsb_type {
@@ -225,6 +227,8 @@ static __always_inline u32 aarch64_insn_get_##abbr##_value(void) \
 
  __AARCH64_INSN_FUNCS(str_reg,	0x3FE0EC00, 0x38206800)
  __AARCH64_INSN_FUNCS(ldr_reg,	0x3FE0EC00, 0x38606800)
+__AARCH64_INSN_FUNCS(stp,	0x7FC00000, 0x29000000)
+__AARCH64_INSN_FUNCS(ldp,	0x7FC00000, 0x29400000)

... and using stp_reg/ldp_reg here.

Ditto.

Quoted 10 lines

arch/arm64/include/asm/insn.h __AARCH64_INSN_FUNCS(hint, 0xFFFFF01F, 0xD503201F) · line 284

Quoted 8 lines
arch/arm64/include/asm/insn.h +1 -0
  __AARCH64_INSN_FUNCS(stp_post,	0x7FC00000, 0x28800000)
  __AARCH64_INSN_FUNCS(ldp_post,	0x7FC00000, 0x28C00000)
  __AARCH64_INSN_FUNCS(stp_pre,	0x7FC00000, 0x29800000)
@@ -277,6 +281,7 @@ __AARCH64_INSN_FUNCS(hint,	0xFFFFF01F, 0xD503201F)
  __AARCH64_INSN_FUNCS(br,	0xFFFFFC1F, 0xD61F0000)
  __AARCH64_INSN_FUNCS(blr,	0xFFFFFC1F, 0xD63F0000)
  __AARCH64_INSN_FUNCS(ret,	0xFFFFFC1F, 0xD65F0000)
+__AARCH64_INSN_FUNCS(eret,	0xFFFFFFFF, 0xD69F00E0)

Is this encoding correct? I ended up with 0xd69f03e0.

Fixed it in v6.

Quoted 33 lines
Quoted 33 lines
arch/arm64/include/asm/insn.h +13 -0
  #undef	__AARCH64_INSN_FUNCS
 
@@ -370,6 +375,19 @@ bool aarch32_insn_is_wide(u32 insn);
  u32 aarch32_insn_extract_reg_num(u32 insn, int offset);
  u32 aarch32_insn_mcr_extract_opc2(u32 insn);
  u32 aarch32_insn_mcr_extract_crm(u32 insn);
+int aarch64_insn_decode_add_sub_imm(u32 insn,
+				    enum aarch64_insn_register *dst,
+				    enum aarch64_insn_register *src,
+				    int *imm,
+				    enum aarch64_insn_variant *variant,
+				    enum aarch64_insn_adsb_type *type);
+int aarch64_insn_decode_load_store_pair(u32 insn,
+					enum aarch64_insn_register *reg1,
+					enum aarch64_insn_register *reg2,
+					enum aarch64_insn_register *base,
+					int *offset,
+					enum aarch64_insn_variant *variant,
+					enum aarch64_insn_ldst_type *type);
  #endif /* __ASSEMBLY__ */
 
  #endif	/* __ASM_INSN_H */
arch/arm64/kernel/insn.c +1 -0
index c08b9ad..b56a66c 100644
@@ -33,6 +33,7 @@
  #include <asm/insn.h>
 
  #define AARCH64_INSN_SF_BIT	BIT(31)
+#define AARCH64_INSN_S_BIT	BIT(29)
  #define AARCH64_INSN_N_BIT	BIT(22)
 
Quoted 11 lines

arch/arm64/kernel/insn.c u32 aarch32_insn_mcr_extract_crm(u32 insn) · line 1147

Quoted 8 lines
arch/arm64/kernel/insn.c +3 -0
  static int aarch64_insn_encoding_class[] = {
@@ -1141,3 +1142,104 @@ u32 aarch32_insn_mcr_extract_crm(u32 insn)
  {
  	return insn & CRM_MASK;
  }
+
+enum aarch64_insn_register aarch64_insn_extract_reg_num(u32 insn,
+				enum aarch64_insn_register_type type)

Maybe call this aarch64_insn_decode_register? You're basically building decoders for some of the *_encode_* functions we already have.

OK, will fix it.
But please note that there are already some functions named aarch32_insn_extract_reg_num() et al.

Quoted 18 lines
Quoted 18 lines
arch/arm64/kernel/insn.c +18 -0
+{
+	int shift;
+
+	switch (type) {
+	case AARCH64_INSN_REGTYPE_RT:
+	case AARCH64_INSN_REGTYPE_RD:
+		shift = 0;
+		break;
+	case AARCH64_INSN_REGTYPE_RN:
+		shift = 5;
+		break;
+	case AARCH64_INSN_REGTYPE_RT2:
+	case AARCH64_INSN_REGTYPE_RA:
+		shift = 10;
+		break;
+	case AARCH64_INSN_REGTYPE_RM:
+		shift = 16;
+		break;
Quoted 8 lines

arch/arm64/kernel/insn.c u32 aarch32_insn_mcr_extract_crm(u32 insn) · line 1170

Quoted 5 lines
arch/arm64/kernel/insn.c +5 -0
+	default:
+		pr_err("%s: unknown register type decoding %d\n", __func__,
+		       type);
+		return ~0L;
+	}

This is largely a copy-paste from aarch64_insn_encode_register. Can you either work with what we have or factor out the common parts, please?

OK, will re-factor it.

Quoted 7 lines

arch/arm64/kernel/insn.c u32 aarch32_insn_mcr_extract_crm(u32 insn) · line 1175

Quoted 4 lines
arch/arm64/kernel/insn.c +4 -0
+	return (insn & (GENMASK(4, 0) << shift)) >> shift;
+}
+
+int aarch64_insn_decode_add_sub_imm(u32 insn,

This one could be aarch64_insn_extract_add_sub_imm, if you like. Then extract is the converse of gen and decode is the converse of encode.

I agree, will change the name as you suggest.
But please see the comment above about aarch32_insn_extract_*.

Quoted 10 lines
Quoted 10 lines
arch/arm64/kernel/insn.c +10 -0
+				    enum aarch64_insn_register *dst,
+				    enum aarch64_insn_register *src,
+				    int *imm,
+				    enum aarch64_insn_variant *variant,
+				    enum aarch64_insn_adsb_type *type)
+{
+	if (aarch64_insn_is_add_imm(insn))
+		*type =	((insn) & AARCH64_INSN_S_BIT) ?
+				AARCH64_INSN_ADSB_ADD_SETFLAGS :
+				AARCH64_INSN_ADSB_ADD;
Quoted 8 lines

arch/arm64/kernel/insn.c u32 aarch32_insn_mcr_extract_crm(u32 insn) · line 1191

Quoted 6 lines
arch/arm64/kernel/insn.c +6 -0
+	else if (aarch64_insn_is_sub_imm(insn))
+		*type =	((insn) & AARCH64_INSN_S_BIT) ?
+				AARCH64_INSN_ADSB_SUB_SETFLAGS :
+				AARCH64_INSN_ADSB_SUB;
+	else
+		return 0;

Why do we need to return 0 on error? -EINVAL might make more sense.

I think that, in most failure cases of *_gen_*, 0 will be returned. See aarch64_insn_encode_immediate() and aach64_insn_encode_register(). But returning -EINVAL is also understandable. Will change so.

Quoted 11 lines

arch/arm64/kernel/insn.c u32 aarch32_insn_mcr_extract_crm(u32 insn) · line 1200

Quoted 9 lines
arch/arm64/kernel/insn.c +9 -0
+
+	*variant = (insn & AARCH64_INSN_SF_BIT) ? AARCH64_INSN_VARIANT_64BIT :
+					AARCH64_INSN_VARIANT_32BIT;
+
+	*dst = aarch64_insn_extract_reg_num(insn, AARCH64_INSN_REGTYPE_RD);
+
+	*src = aarch64_insn_extract_reg_num(insn, AARCH64_INSN_REGTYPE_RN);
+
+	/* TODO: ignore shilft field[23:22] */

I don't understand the TODO.

Haha, we don't have to interpret 'shift' in a function prologue usage, but yes, will fix it for general cases.

Quoted 3 lines

arch/arm64/kernel/insn.c u32 aarch32_insn_mcr_extract_crm(u32 insn) · line 1201

Quoted 1 line
arch/arm64/kernel/insn.c +1 -0
+	*imm = (int)aarch64_insn_decode_immediate(AARCH64_INSN_IMM_12, insn);

We use u64 for imm everywhere else.

Not true.
See aarch64_insn_gen_add_sub_imm(), _bitfield() and _movewide().

Quoted 2 lines

arch/arm64/kernel/insn.c u32 aarch32_insn_mcr_extract_crm(u32 insn) · line 1202

Quoted 2 lines
arch/arm64/kernel/insn.c +2 -0
+	return 1;
+}

return 0;

Quoted 2 lines

Similarly here, you're trying to implement the converse of aarch64_insn_gen_add_sub_imm

Will change the name.

Quoted 17 lines
Quoted 17 lines
arch/arm64/kernel/insn.c +17 -0
+
+int aarch64_insn_decode_load_store_pair(u32 insn,
+					enum aarch64_insn_register *reg1,
+					enum aarch64_insn_register *reg2,
+					enum aarch64_insn_register *base,
+					int *offset,
+					enum aarch64_insn_variant *variant,
+					enum aarch64_insn_ldst_type *type)
+{
+	int imm;
+
+	if (aarch64_insn_is_stp(insn))
+		*type = AARCH64_INSN_LDST_STORE_PAIR;
+	else if (aarch64_insn_is_stp_post(insn))
+		*type = AARCH64_INSN_LDST_STORE_PAIR_POST_INDEX;
+	else if (aarch64_insn_is_stp_pre(insn))
+		*type = AARCH64_INSN_LDST_STORE_PAIR_PRE_INDEX;
Quoted 8 lines

arch/arm64/kernel/insn.c u32 aarch32_insn_mcr_extract_crm(u32 insn) · line 1229

Quoted 8 lines
arch/arm64/kernel/insn.c +8 -0
+	else if (aarch64_insn_is_ldp(insn))
+		*type = AARCH64_INSN_LDST_LOAD_PAIR;
+	else if (aarch64_insn_is_ldp_post(insn))
+		*type = AARCH64_INSN_LDST_LOAD_PAIR_POST_INDEX;
+	else if (aarch64_insn_is_ldp_pre(insn))
+		*type = AARCH64_INSN_LDST_LOAD_PAIR_PRE_INDEX;
+	else
+		return 0;

return -EINVAL;

Quoted 5 lines
Quoted 5 lines
arch/arm64/kernel/insn.c +5 -0
+	*variant = (insn & AARCH64_INSN_S_BIT) ? AARCH64_INSN_VARIANT_64BIT :
+				AARCH64_INSN_VARIANT_32BIT;
+
+	*reg1 = aarch64_insn_extract_reg_num(insn, AARCH64_INSN_REGTYPE_RT);
+
Quoted 8 lines

arch/arm64/kernel/insn.c u32 aarch32_insn_mcr_extract_crm(u32 insn) · line 1241

Quoted 6 lines
arch/arm64/kernel/insn.c +6 -0
+	*reg2 = aarch64_insn_extract_reg_num(insn, AARCH64_INSN_REGTYPE_RT2);
+
+	*base = aarch64_insn_extract_reg_num(insn, AARCH64_INSN_REGTYPE_RN);
+
+	imm = (int)aarch64_insn_decode_immediate(AARCH64_INSN_IMM_7, insn);
+	asm("sbfm %0, %0, 0, 6" : "+r" (imm));

What? Can't you write this in C?

OK, will use sign_extend64().

Thanks,
-Takahiro AKASHI

Quoted 2 lines

Will

#19 lore raw

PATCH v6 5/6 arm64: ftrace: add arch-specific stack tracer

In reply to AKASHI Takahiro

Background and issues on generic check_stack():
1) slurping stack

Assume that a given function A was invoked, and it was invoked again in another context, then it called another function B which allocated a large size of local variables on the stack, but it has not modified the variable(s) yet.
When stack tracer, check_stack(), examines the stack looking for B, then A, we may have a chance to accidentally find a stale, not current, stack frame for A because the old frame might reside on the memory for the variable which has not been overwritten.

(issue) The stack_trace output may have stale entries.

2) differences between x86 and arm64

On x86, "call" instruction automatically pushes a return address on the top of the stack and decrements a stack pointer. Then child function allocates its local variables on the stack.

On arm64, a child function is responsible for allocating memory for local variables as well as a stack frame, and explicitly pushes a return address (LR) and old frame pointer in its function prologue *after* decreasing a stack pointer.

Generic check_stack() recogizes 'idxB,' which is the next address of the location where 'fpB' is found, in the picture below as an estimated stack pointer. This seems to fine with x86, but on arm64, 'idxB' is not appropriate just because it contains child function's "local variables."
We should instead use spB, if possible, for better interpretation of func_B's stack usage.

LOW      |  ...   |
fpA      +--------+   func_A (pcA, fpA, spA)
         |  fpB   |
    idxB + - - - -+
         |  pcB   |
         |  ... <----------- static local variables in func_A
         |  ...   |             and extra function args to func_A
spB      + - - - -+
         |  ... <----------- dynamically allocated variables in func_B
fpB      +--------+   func_B (pcB, fpB, spB)
         |  fpC   |
    idxC + - - - -+
         |  pcC   |
         |  ... <----------- static local variables in func_B
         |  ...   |             and extra function args to func_B
spC      + - - - -+
         |  ...   |
fpC      +--------+   func_C (pcC, fpC, spC)
HIGH     |        |

(issue) Stack size for a function in stack_trace output is inaccurate,
or rather wrong. It looks as if <Size> field is one-line offset against <Location>.

Depth Size Location (49 entries)
----- ---- --------
40) 1416 64 path_openat+0x128/0xe00 -> 176
41) 1352 176 do_filp_open+0x74/0xf0 -> 256
42) 1176 256 do_open_execat+0x74/0x1c8 -> 80
43) 920 80 open_exec+0x3c/0x70 -> 32
44) 840 32 load_elf_binary+0x294/0x10c8

Stack tracer on arm64:
Our approach in check_stack() is uniqeue in the following points:
* analyze a function prologue of a traced function to estimate a more accurate stack pointer value, replacing naive '<child's fp> + 0x10.'
* use walk_stackframe(), instead of slurping stack contents as orignal check_stack() does, to identify a stack frame and a stack index (height) for every callsite.

Regarding a function prologue analyzer, there is no guarantee that we can handle all the possible patterns of function prologue as gcc does not use any fixed templates to generate them. 'Instruction scheduling' is another issue here.
Nevertheless, the current version will surely cover almost all the cases in the kernel image and give us useful information on stack pointers.

So this patch utilizes a function prologue only for stack tracer, and does not affect the behaviors of existing stacktrace functions.

---
 arch/arm64/include/asm/ftrace.h     |    2 +-
 arch/arm64/include/asm/stacktrace.h |    4 +
 arch/arm64/kernel/ftrace.c          |   64 ++++++++++++
 arch/arm64/kernel/stacktrace.c      |  190 ++++++++++++++++++++++++++++++++++-
 4 files changed, 256 insertions(+), 4 deletions(-)
arch/arm64/include/asm/ftrace.h +1 -1
index 3c60f37..6795219 100644
@@ -26,7 +26,7 @@ struct dyn_arch_ftrace {
 	/* No extra data needed for arm64 */
 };
 
-extern unsigned long ftrace_graph_call;
+extern u32 ftrace_graph_call;
 
 extern void return_to_handler(void);
 
arch/arm64/include/asm/stacktrace.h +4 -0
index 801a16db..0eee008 100644
@@ -30,5 +30,9 @@ struct stackframe {
 extern int unwind_frame(struct task_struct *tsk, struct stackframe *frame);
 extern void walk_stackframe(struct task_struct *tsk, struct stackframe *frame,
 			    int (*fn)(struct stackframe *, void *), void *data);
+#ifdef CONFIG_STACK_TRACER
+struct stack_trace;
+extern void save_stack_trace_sp(struct stack_trace *trace, unsigned long *sp);
+#endif
 
 #endif	/* __ASM_STACKTRACE_H */
arch/arm64/kernel/ftrace.c +64 -0

79 lines not on this page · raw message

arch/arm64/kernel/stacktrace.c +187 -3

233 lines not on this page · raw message

Signature · 1 line
1.7.9.5
#20 lore raw

PATCH v6 6/6 arm64: ftrace: add a test of function prologue analyzer

In reply to AKASHI Takahiro

The patch ("arm64: ftrace: add arch-specific stack tracer") introduced a function prologue analyzer.

Given that there is no fixed template for a function prologue, at least on gcc for aarch64, a function prologue analyzer may be rather heuristic. So this patch adds a kernel command line option, function_prologue_analyzer_test, in order to run a basic test at startup by executing an analyzer against all the *traceable* functions.

For function_prologue_analyzer_test=2, the output looks like:

       po sp    fp    symbol
       == ==    ==    ======
    0: 0  0x040 0x000 gic_handle_irq+0x20/0xa4
    1: 0  0x040 0x000 gic_handle_irq+0x34/0x114
    2: 0  0x030 0x000 run_init_process+0x14/0x48
    3: 0  0x020 0x000 try_to_run_init_process+0x14/0x58
    4: 0  0x080 0x000 do_one_initcall+0x1c/0x194
    ...

22959: 0 0x020 0x000 tty_lock_slave+0x14/0x3c
22960: 0 0x020 0x000 tty_unlock_slave+0x14/0x3c
function prologue analyzer test: 0 errors

"po" indicates a position of callsite of mcount(), and should be zero if an analyzer has parsed a function prologue successfully and reached a location where fp is properly updated.
"sp" is a final offset to a parent's fp at the exit of function prologue. "fp" is also an ofset against sp at the exit of function prologue. So normally,
<new sp> = <old fp> + <"sp">
<new fp> = <new sp> - <"fp">

And the last line shows the number of possible errors in the result.

For function_prologue_analyzer_test=1, only the last line will be shown.

---
 arch/arm64/include/asm/insn.h  |    2 +-
 arch/arm64/kernel/stacktrace.c |   52 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 53 insertions(+), 1 deletion(-)
arch/arm64/include/asm/insn.h +1 -1
index 8d5c538..1dfae16 100644
@@ -281,7 +281,7 @@ __AARCH64_INSN_FUNCS(hint,	0xFFFFF01F, 0xD503201F)
 __AARCH64_INSN_FUNCS(br,	0xFFFFFC1F, 0xD61F0000)
 __AARCH64_INSN_FUNCS(blr,	0xFFFFFC1F, 0xD63F0000)
 __AARCH64_INSN_FUNCS(ret,	0xFFFFFC1F, 0xD65F0000)
-__AARCH64_INSN_FUNCS(eret,	0xFFFFFFFF, 0xD69F00E0)
+__AARCH64_INSN_FUNCS(eret,	0xFFFFFFFF, 0xD69F03E0)
 
 #undef	__AARCH64_INSN_FUNCS
 
arch/arm64/kernel/stacktrace.c +52 -0

63 lines not on this page · raw message

Signature · 1 line
1.7.9.5
#21 lore raw

PATCH v6 6/6 arm64: ftrace: add a test of function prologue analyzer

Quoted 47 lines · AKASHI Takahiro

The patch ("arm64: ftrace: add arch-specific stack tracer") introduced a function prologue analyzer.

Given that there is no fixed template for a function prologue, at least on gcc for aarch64, a function prologue analyzer may be rather heuristic. So this patch adds a kernel command line option, function_prologue_analyzer_test, in order to run a basic test at startup by executing an analyzer against all the *traceable* functions.

For function_prologue_analyzer_test=2, the output looks like:

      po sp    fp    symbol
      == ==    ==    ======
   0: 0  0x040 0x000 gic_handle_irq+0x20/0xa4
   1: 0  0x040 0x000 gic_handle_irq+0x34/0x114
   2: 0  0x030 0x000 run_init_process+0x14/0x48
   3: 0  0x020 0x000 try_to_run_init_process+0x14/0x58
   4: 0  0x080 0x000 do_one_initcall+0x1c/0x194

...
22959: 0 0x020 0x000 tty_lock_slave+0x14/0x3c
22960: 0 0x020 0x000 tty_unlock_slave+0x14/0x3c
function prologue analyzer test: 0 errors

"po" indicates a position of callsite of mcount(), and should be zero if an analyzer has parsed a function prologue successfully and reached a location where fp is properly updated.
"sp" is a final offset to a parent's fp at the exit of function prologue. "fp" is also an ofset against sp at the exit of function prologue. So normally,
<new sp> = <old fp> + <"sp">
<new fp> = <new sp> - <"fp">

And the last line shows the number of possible errors in the result.

For function_prologue_analyzer_test=1, only the last line will be shown.

---
arch/arm64/include/asm/insn.h | 2 +-
arch/arm64/kernel/stacktrace.c | 52 ++++++++++++++++++++++++++++++++++++++++

2 files changed, 53 insertions(+), 1 deletion(-)
arch/arm64/include/asm/insn.h +0 -0
index 8d5c538..1dfae16 100644

arch/arm64/include/asm/insn.h __AARCH64_INSN_FUNCS(hint, 0xFFFFF01F, 0xD503201F) · line 284

Quoted 5 lines
arch/arm64/include/asm/insn.h +1 -1
@@ -281,7 +281,7 @@ __AARCH64_INSN_FUNCS(hint,	0xFFFFF01F, 0xD503201F)
 __AARCH64_INSN_FUNCS(br,	0xFFFFFC1F, 0xD61F0000)
 __AARCH64_INSN_FUNCS(blr,	0xFFFFFC1F, 0xD63F0000)
 __AARCH64_INSN_FUNCS(ret,	0xFFFFFC1F, 0xD65F0000)
-__AARCH64_INSN_FUNCS(eret,	0xFFFFFFFF, 0xD69F00E0)
+__AARCH64_INSN_FUNCS(eret,	0xFFFFFFFF, 0xD69F03E0)

How about folding(squashing) this hunk into [PATCH 4/6]?

Quoted 48 lines
arch/arm64/include/asm/insn.h +0 -0
 #undef	__AARCH64_INSN_FUNCS
 
arch/arm64/kernel/stacktrace.c +31 -0
index 1d18bc4..19dad62 100644
@@ -18,6 +18,7 @@
 #include <linux/kernel.h>
 #include <linux/export.h>
 #include <linux/ftrace.h>
+#include <linux/kallsyms.h>
 #include <linux/sched.h>
 #include <linux/stacktrace.h>
 
@@ -348,5 +349,56 @@ void save_stack_trace_sp(struct stack_trace *trace,
 {
 	__save_stack_trace_tsk(current, trace, stack_dump_sp);
 }
+
+static int start_analyzer_test __initdata;
+
+static int __init enable_analyzer_test(char *str)
+{
+	get_option(&str, &start_analyzer_test);
+	return 0;
+}
+early_param("function_prologue_analyzer_test", enable_analyzer_test);
+
+static void __init do_test_function_prologue_analyzer(void)
+{
+	extern unsigned long __start_mcount_loc[];
+	extern unsigned long __stop_mcount_loc[];
+	unsigned long count, i, errors;
+	int print_once;
+
+	count = __stop_mcount_loc - __start_mcount_loc;
+	errors = print_once = 0;
+	for (i = 0; i < count; i++) {
+		unsigned long addr, sp_off, fp_off;
+		int pos;
+		bool check;
+		char buf[60];
+
+		addr = __start_mcount_loc[i];
+		pos = analyze_function_prologue(addr, &sp_off, &fp_off);
+		check = ((pos != 0) || !sp_off || (sp_off <= fp_off));
+		if (check)
+			errors++;

arch/arm64/kernel/stacktrace.c void save_stack_trace_sp(struct stack_trace *trace, · line 391

Quoted 10 lines
arch/arm64/kernel/stacktrace.c +10 -0
+		if (check || (start_analyzer_test > 1)) {
+			if (!print_once) {
+				pr_debug("       po sp    fp    symbol\n");
+				pr_debug("       == ==    ==    ======\n");
+				print_once++;
+			}
+			sprint_symbol(buf, addr);
+			pr_debug("%5ld: %d  0x%03lx 0x%03lx %s\n",
+					i, pos, sp_off, fp_off, buf);
+		}

It would be hard to find out which entry has an error if start_analyer_test is set to 2. How about adding one more column to mark 'OK' or 'NG'?

BTW, as I mentioned in previous thread, I'm also waiting for feedbacks on the analyzer itself :)

Best Regards
Jungseok Lee

#22 lore raw

PATCH v6 6/6 arm64: ftrace: add a test of function prologue analyzer

Quoted 109 lines · Jungseok Lee
Quoted 47 lines · AKASHI Takahiro

The patch ("arm64: ftrace: add arch-specific stack tracer") introduced a function prologue analyzer.

Given that there is no fixed template for a function prologue, at least on gcc for aarch64, a function prologue analyzer may be rather heuristic. So this patch adds a kernel command line option, function_prologue_analyzer_test, in order to run a basic test at startup by executing an analyzer against all the *traceable* functions.

For function_prologue_analyzer_test=2, the output looks like:

       po sp    fp    symbol
       == ==    ==    ======
    0: 0  0x040 0x000 gic_handle_irq+0x20/0xa4
    1: 0  0x040 0x000 gic_handle_irq+0x34/0x114
    2: 0  0x030 0x000 run_init_process+0x14/0x48
    3: 0  0x020 0x000 try_to_run_init_process+0x14/0x58
    4: 0  0x080 0x000 do_one_initcall+0x1c/0x194
    ...

22959: 0 0x020 0x000 tty_lock_slave+0x14/0x3c
22960: 0 0x020 0x000 tty_unlock_slave+0x14/0x3c
function prologue analyzer test: 0 errors

"po" indicates a position of callsite of mcount(), and should be zero if an analyzer has parsed a function prologue successfully and reached a location where fp is properly updated.
"sp" is a final offset to a parent's fp at the exit of function prologue. "fp" is also an ofset against sp at the exit of function prologue. So normally,
<new sp> = <old fp> + <"sp">
<new fp> = <new sp> - <"fp">

And the last line shows the number of possible errors in the result.

For function_prologue_analyzer_test=1, only the last line will be shown.

---
arch/arm64/include/asm/insn.h | 2 +-
arch/arm64/kernel/stacktrace.c | 52 ++++++++++++++++++++++++++++++++++++++++

2 files changed, 53 insertions(+), 1 deletion(-)
arch/arm64/include/asm/insn.h +0 -0
index 8d5c538..1dfae16 100644

arch/arm64/include/asm/insn.h __AARCH64_INSN_FUNCS(hint, 0xFFFFF01F, 0xD503201F) · line 284

Quoted 5 lines
arch/arm64/include/asm/insn.h +1 -1
@@ -281,7 +281,7 @@ __AARCH64_INSN_FUNCS(hint,	0xFFFFF01F, 0xD503201F)
 __AARCH64_INSN_FUNCS(br,	0xFFFFFC1F, 0xD61F0000)
 __AARCH64_INSN_FUNCS(blr,	0xFFFFFC1F, 0xD63F0000)
 __AARCH64_INSN_FUNCS(ret,	0xFFFFFC1F, 0xD65F0000)
-__AARCH64_INSN_FUNCS(eret,	0xFFFFFFFF, 0xD69F00E0)
+__AARCH64_INSN_FUNCS(eret,	0xFFFFFFFF, 0xD69F03E0)

How about folding(squashing) this hunk into [PATCH 4/6]?

Quoted 53 lines
arch/arm64/include/asm/insn.h +0 -0
 #undef	__AARCH64_INSN_FUNCS
 
arch/arm64/kernel/stacktrace.c +36 -0
index 1d18bc4..19dad62 100644
@@ -18,6 +18,7 @@
 #include <linux/kernel.h>
 #include <linux/export.h>
 #include <linux/ftrace.h>
+#include <linux/kallsyms.h>
 #include <linux/sched.h>
 #include <linux/stacktrace.h>
 
@@ -348,5 +349,56 @@ void save_stack_trace_sp(struct stack_trace *trace,
 {
 	__save_stack_trace_tsk(current, trace, stack_dump_sp);
 }
+
+static int start_analyzer_test __initdata;
+
+static int __init enable_analyzer_test(char *str)
+{
+	get_option(&str, &start_analyzer_test);
+	return 0;
+}
+early_param("function_prologue_analyzer_test", enable_analyzer_test);
+
+static void __init do_test_function_prologue_analyzer(void)
+{
+	extern unsigned long __start_mcount_loc[];
+	extern unsigned long __stop_mcount_loc[];
+	unsigned long count, i, errors;
+	int print_once;
+
+	count = __stop_mcount_loc - __start_mcount_loc;
+	errors = print_once = 0;
+	for (i = 0; i < count; i++) {
+		unsigned long addr, sp_off, fp_off;
+		int pos;
+		bool check;
+		char buf[60];
+
+		addr = __start_mcount_loc[i];
+		pos = analyze_function_prologue(addr, &sp_off, &fp_off);
+		check = ((pos != 0) || !sp_off || (sp_off <= fp_off));
+		if (check)
+			errors++;
+		if (check || (start_analyzer_test > 1)) {
+			if (!print_once) {
+				pr_debug("       po sp    fp    symbol\n");
+				pr_debug("       == ==    ==    ======\n");
+				print_once++;
Quoted 8 lines

arch/arm64/kernel/stacktrace.c void save_stack_trace_sp(struct stack_trace *trace, · line 391

Quoted 5 lines
arch/arm64/kernel/stacktrace.c +5 -0
+			}
+			sprint_symbol(buf, addr);
+			pr_debug("%5ld: %d  0x%03lx 0x%03lx %s\n",
+					i, pos, sp_off, fp_off, buf);
+		}

It would be hard to find out which entry has an error if start_analyer_test is set to 2. How about adding one more column to mark 'OK' or 'NG'?

Thank you for the comment.

Quoted 2 lines

BTW, as I mentioned in previous thread, I'm also waiting for feedbacks on the analyzer itself :)

Me, too.

-Takahiro AKASHI

Quoted 3 lines

Best Regards
Jungseok Lee