Commit a1be9ccc authored by Donglin Peng's avatar Donglin Peng Committed by Steven Rostedt (Google)
Browse files

function_graph: Support recording and printing the return value of function

Analyzing system call failures with the function_graph tracer can be a
time-consuming process, particularly when locating the kernel function
that first returns an error in the trace logs. This change aims to
simplify the process by recording the function return value to the
'retval' member of 'ftrace_graph_ret' and printing it when outputting
the trace log.

We have introduced new trace options: funcgraph-retval and
funcgraph-retval-hex. The former controls whether to display the return
value, while the latter controls the display format.

Please note that even if a function's return type is void, a return
value will still be printed. You can simply ignore it.

This patch only establishes the fundamental infrastructure. Subsequent
patches will make this feature available on some commonly used processor
architectures.

Here is an example:

I attempted to attach the demo process to a cpu cgroup, but it failed:

echo `pidof demo` > /sys/fs/cgroup/cpu/test/tasks
-bash: echo: write error: Invalid argument

The strace logs indicate that the write system call returned -EINVAL(-22):
...
write(1, "273\n", 4)                    = -1 EINVAL (Invalid argument)
...

To capture trace logs during a write system call, use the following
commands:

cd /sys/kernel/debug/tracing/
echo 0 > tracing_on
echo > trace
echo *sys_write > set_graph_function
echo *spin* > set_graph_notrace
echo *rcu* >> set_graph_notrace
echo *alloc* >> set_graph_notrace
echo preempt* >> set_graph_notrace
echo kfree* >> set_graph_notrace
echo $$ > set_ftrace_pid
echo function_graph > current_tracer
echo 1 > options/funcgraph-retval
echo 0 > options/funcgraph-retval-hex
echo 1 > tracing_on
echo `pidof demo` > /sys/fs/cgroup/cpu/test/tasks
echo 0 > tracing_on
cat trace > ~/trace.log

To locate the root cause, search for error code -22 directly in the file
trace.log and identify the first function that returned -22. Once you
have identified this function, examine its code to determine the root
cause.

For example, in the trace log below, cpu_cgroup_can_attach
returned -22 first, so we can focus our analysis on this function to
identify the root cause.

...

 1)          | cgroup_migrate() {
 1) 0.651 us |   cgroup_migrate_add_task(); /* = 0xffff93fcfd346c00 */
 1)          |   cgroup_migrate_execute() {
 1)          |     cpu_cgroup_can_attach() {
 1)          |       cgroup_taskset_first() {
 1) 0.732 us |         cgroup_taskset_next(); /* = 0xffff93fc8fb20000 */
 1) 1.232 us |       } /* cgroup_taskset_first = 0xffff93fc8fb20000 */
 1) 0.380 us |       sched_rt_can_attach(); /* = 0x0 */
 1) 2.335 us |     } /* cpu_cgroup_can_attach = -22 */
 1) 4.369 us |   } /* cgroup_migrate_execute = -22 */
 1) 7.143 us | } /* cgroup_migrate = -22 */

...

Link: https://lkml.kernel.org/r/1fc502712c981e0e6742185ba242992170ac9da8.1680954589.git.pengdonglin@sangfor.com.cn



Tested-by: default avatarFlorian Kauer <florian.kauer@linutronix.de>
Acked-by: default avatarMasami Hiramatsu (Google) <mhiramat@kernel.org>
Signed-off-by: default avatarDonglin Peng <pengdonglin@sangfor.com.cn>
Signed-off-by: default avatarSteven Rostedt (Google) <rostedt@goodmis.org>
parent f3d40e65
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -1018,6 +1018,9 @@ struct ftrace_graph_ent {
 */
struct ftrace_graph_ret {
	unsigned long func; /* Current function */
#ifdef CONFIG_FUNCTION_GRAPH_RETVAL
	unsigned long retval;
#endif
	int depth;
	/* Number of functions that overran the depth limit for current task */
	unsigned int overrun;
+15 −0
Original line number Diff line number Diff line
@@ -31,6 +31,9 @@ config HAVE_FUNCTION_GRAPH_TRACER
	help
	  See Documentation/trace/ftrace-design.rst

config HAVE_FUNCTION_GRAPH_RETVAL
	bool

config HAVE_DYNAMIC_FTRACE
	bool
	help
@@ -227,6 +230,18 @@ config FUNCTION_GRAPH_TRACER
	  the return value. This is done by setting the current return
	  address on the current task structure into a stack of calls.

config FUNCTION_GRAPH_RETVAL
	bool "Kernel Function Graph Return Value"
	depends on HAVE_FUNCTION_GRAPH_RETVAL
	depends on FUNCTION_GRAPH_TRACER
	default n
	help
	  Support recording and printing the function return value when
	  using function graph tracer. It can be helpful to locate functions
	  that return errors. This feature is off by default, and you can
	  enable it via the trace option funcgraph-retval.
	  See Documentation/trace/ftrace.rst

config DYNAMIC_FTRACE
	bool "enable/disable function tracing dynamically"
	depends on FUNCTION_TRACER
+22 −1
Original line number Diff line number Diff line
@@ -243,12 +243,16 @@ struct fgraph_ret_regs;
 * Send the trace to the ring-buffer.
 * @return the original return address.
 */
unsigned long ftrace_return_to_handler(unsigned long frame_pointer)
static unsigned long __ftrace_return_to_handler(struct fgraph_ret_regs *ret_regs,
						unsigned long frame_pointer)
{
	struct ftrace_graph_ret trace;
	unsigned long ret;

	ftrace_pop_return_trace(&trace, &ret, frame_pointer);
#ifdef CONFIG_FUNCTION_GRAPH_RETVAL
	trace.retval = fgraph_ret_regs_return_value(ret_regs);
#endif
	trace.rettime = trace_clock_local();
	ftrace_graph_return(&trace);
	/*
@@ -269,6 +273,23 @@ unsigned long ftrace_return_to_handler(unsigned long frame_pointer)
	return ret;
}

/*
 * After all architecures have selected HAVE_FUNCTION_GRAPH_RETVAL, we can
 * leave only ftrace_return_to_handler(ret_regs).
 */
#ifdef CONFIG_HAVE_FUNCTION_GRAPH_RETVAL
unsigned long ftrace_return_to_handler(struct fgraph_ret_regs *ret_regs)
{
	return __ftrace_return_to_handler(ret_regs,
				fgraph_ret_regs_frame_pointer(ret_regs));
}
#else
unsigned long ftrace_return_to_handler(unsigned long frame_pointer)
{
	return __ftrace_return_to_handler(NULL, frame_pointer);
}
#endif

/**
 * ftrace_graph_get_ret_stack - return the entry of the shadow stack
 * @task: The task to read the shadow stack from
+2 −0
Original line number Diff line number Diff line
@@ -832,6 +832,8 @@ static __always_inline bool ftrace_hash_empty(struct ftrace_hash *hash)
#define TRACE_GRAPH_PRINT_TAIL          0x100
#define TRACE_GRAPH_SLEEP_TIME          0x200
#define TRACE_GRAPH_GRAPH_TIME          0x400
#define TRACE_GRAPH_PRINT_RETVAL        0x800
#define TRACE_GRAPH_PRINT_RETVAL_HEX    0x1000
#define TRACE_GRAPH_PRINT_FILL_SHIFT	28
#define TRACE_GRAPH_PRINT_FILL_MASK	(0x3 << TRACE_GRAPH_PRINT_FILL_SHIFT)

+26 −0
Original line number Diff line number Diff line
@@ -86,6 +86,30 @@ FTRACE_ENTRY_PACKED(funcgraph_entry, ftrace_graph_ent_entry,
);

/* Function return entry */
#ifdef CONFIG_FUNCTION_GRAPH_RETVAL

FTRACE_ENTRY_PACKED(funcgraph_exit, ftrace_graph_ret_entry,

	TRACE_GRAPH_RET,

	F_STRUCT(
		__field_struct(	struct ftrace_graph_ret,	ret	)
		__field_packed(	unsigned long,	ret,		func	)
		__field_packed(	unsigned long,	ret,		retval	)
		__field_packed(	int,		ret,		depth	)
		__field_packed(	unsigned int,	ret,		overrun	)
		__field_packed(	unsigned long long, ret,	calltime)
		__field_packed(	unsigned long long, ret,	rettime	)
	),

	F_printk("<-- %ps (%d) (start: %llx  end: %llx) over: %d retval: %lx",
		 (void *)__entry->func, __entry->depth,
		 __entry->calltime, __entry->rettime,
		 __entry->depth, __entry->retval)
);

#else

FTRACE_ENTRY_PACKED(funcgraph_exit, ftrace_graph_ret_entry,

	TRACE_GRAPH_RET,
@@ -105,6 +129,8 @@ FTRACE_ENTRY_PACKED(funcgraph_exit, ftrace_graph_ret_entry,
		 __entry->depth)
);

#endif

/*
 * Context switch trace entry - which task (and prio) we switched from/to:
 *
Loading