function_graph: Support recording and printing the function return address

When using function_graph tracer to analyze the flow of kernel function
execution, it is often necessary to quickly locate the exact line of code
where the call occurs. While this may be easy at times, it can be more
time-consuming when some functions are inlined or the flow is too long.

This feature aims to simplify the process by recording the return address
of traced funcions and printing it when outputing trace logs.

To enhance human readability, the prefix 'ret=' is used for the kernel return
value, while '<-' serves as the prefix for the return address in trace logs to
make it look more like the function tracer.

A new trace option named 'funcgraph-retaddr' has been introduced, and the
existing option 'sym-addr' can be used to control the format of the return
address.

See below logs with both funcgraph-retval and funcgraph-retaddr enabled.

0)             | load_elf_binary() { /* <-bprm_execve+0x249/0x600 */
0)             |   load_elf_phdrs() { /* <-load_elf_binary+0x84/0x1730 */
0)             |     __kmalloc_noprof() { /* <-load_elf_phdrs+0x4a/0xb0 */
0)   3.657 us  |       __cond_resched(); /* <-__kmalloc_noprof+0x28c/0x390 ret=0x0 */
0) + 24.335 us |     } /* __kmalloc_noprof ret=0xffff8882007f3000 */
0)             |     kernel_read() { /* <-load_elf_phdrs+0x6c/0xb0 */
0)             |       rw_verify_area() { /* <-kernel_read+0x2b/0x50 */
0)             |         security_file_permission() { /* <-kernel_read+0x2b/0x50 */
0)             |           selinux_file_permission() { /* <-security_file_permission+0x26/0x40 */
0)             |             __inode_security_revalidate() { /* <-selinux_file_permission+0x6d/0x140 */
0)   2.034 us  |               __cond_resched(); /* <-__inode_security_revalidate+0x5f/0x80 ret=0x0 */
0)   6.602 us  |             } /* __inode_security_revalidate ret=0x0 */
0)   2.214 us  |             avc_policy_seqno(); /* <-selinux_file_permission+0x107/0x140 ret=0x0 */
0) + 16.670 us |           } /* selinux_file_permission ret=0x0 */
0) + 20.809 us |         } /* security_file_permission ret=0x0 */
0) + 25.217 us |       } /* rw_verify_area ret=0x0 */
0)             |       __kernel_read() { /* <-load_elf_phdrs+0x6c/0xb0 */
0)             |         ext4_file_read_iter() { /* <-__kernel_read+0x160/0x2e0 */

Then, we can use the faddr2line to locate the source code, for example:

$ ./scripts/faddr2line ./vmlinux load_elf_phdrs+0x6c/0xb0
load_elf_phdrs+0x6c/0xb0:
elf_read at fs/binfmt_elf.c:471
(inlined by) load_elf_phdrs at fs/binfmt_elf.c:531

Link: https://lore.kernel.org/20240915032912.1118397-1-dolinux.peng@gmail.com
Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/oe-kbuild-all/202409150605.HgUmU8ea-lkp@intel.com/
Signed-off-by: Donglin Peng <dolinux.peng@gmail.com>
[ Rebased to handle text_delta offsets ]
Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
This commit is contained in:
Donglin Peng 2024-09-14 20:29:12 -07:00 committed by Steven Rostedt (Google)
parent f1f36e22be
commit 21e92806d3
11 changed files with 276 additions and 63 deletions

View File

@ -1040,6 +1040,17 @@ struct ftrace_graph_ent {
int depth;
} __packed;
/*
* Structure that defines an entry function trace with retaddr.
* It's already packed but the attribute "packed" is needed
* to remove extra padding at the end.
*/
struct fgraph_retaddr_ent {
unsigned long func; /* Current function */
int depth;
unsigned long retaddr; /* Return address */
} __packed;
/*
* Structure that defines a return function trace.
* It's already packed but the attribute "packed" is needed
@ -1057,19 +1068,29 @@ struct ftrace_graph_ret {
unsigned long long rettime;
} __packed;
struct fgraph_extras;
struct fgraph_ops;
/* Type of the callback handlers for tracing function graph*/
typedef void (*trace_func_graph_ret_t)(struct ftrace_graph_ret *,
struct fgraph_ops *); /* return */
typedef int (*trace_func_graph_ent_t)(struct ftrace_graph_ent *,
struct fgraph_ops *); /* entry */
struct fgraph_ops *,
struct fgraph_extras *); /* entry */
extern int ftrace_graph_entry_stub(struct ftrace_graph_ent *trace, struct fgraph_ops *gops);
extern int ftrace_graph_entry_stub(struct ftrace_graph_ent *trace,
struct fgraph_ops *gops,
struct fgraph_extras *extras);
bool ftrace_pids_enabled(struct ftrace_ops *ops);
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
/* Used to convey some extra datas when creating a graph entry */
struct fgraph_extras {
u32 flags;
unsigned long retaddr;
};
struct fgraph_ops {
trace_func_graph_ent_t entryfunc;
trace_func_graph_ret_t retfunc;
@ -1115,6 +1136,8 @@ unsigned long ftrace_graph_ret_addr(struct task_struct *task, int *idx,
unsigned long ret, unsigned long *retp);
unsigned long *fgraph_get_task_var(struct fgraph_ops *gops);
u32 graph_tracer_flags_get(u32 flags);
/*
* Sometimes we don't want to trace a function with the function
* graph tracer but we want them to keep traced by the usual function

View File

@ -242,6 +242,16 @@ config FUNCTION_GRAPH_RETVAL
enable it via the trace option funcgraph-retval.
See Documentation/trace/ftrace.rst
config FUNCTION_GRAPH_RETADDR
bool "Kernel Function Graph Return Address"
depends on FUNCTION_GRAPH_TRACER
default n
help
Support recording and printing the function return address when
using function graph tracer. It can be helpful to locate code line that
the function is called. This feature is off by default, and you can
enable it via the trace option funcgraph-retaddr.
config DYNAMIC_FTRACE
bool "enable/disable function tracing dynamically"
depends on FUNCTION_TRACER

View File

@ -290,7 +290,8 @@ static inline unsigned long make_data_type_val(int idx, int size, int offset)
}
/* ftrace_graph_entry set to this to tell some archs to run function graph */
static int entry_run(struct ftrace_graph_ent *trace, struct fgraph_ops *ops)
static int entry_run(struct ftrace_graph_ent *trace, struct fgraph_ops *ops,
struct fgraph_extras *extras)
{
return 0;
}
@ -518,7 +519,8 @@ int __weak ftrace_disable_ftrace_graph_caller(void)
#endif
int ftrace_graph_entry_stub(struct ftrace_graph_ent *trace,
struct fgraph_ops *gops)
struct fgraph_ops *gops,
struct fgraph_extras *extras)
{
return 0;
}
@ -646,13 +648,20 @@ int function_graph_enter(unsigned long ret, unsigned long func,
unsigned long frame_pointer, unsigned long *retp)
{
struct ftrace_graph_ent trace;
struct fgraph_extras extras;
unsigned long bitmap = 0;
int offset;
int i;
int idx = 0;
trace.func = func;
trace.depth = ++current->curr_ret_depth;
extras.flags = graph_tracer_flags_get(TRACE_GRAPH_PRINT_RETADDR);
if (IS_ENABLED(CONFIG_FUNCTION_GRAPH_RETADDR)
&& extras.flags & TRACE_GRAPH_PRINT_RETADDR)
extras.retaddr = ftrace_graph_ret_addr(current, &idx, ret, retp);
offset = ftrace_push_return_trace(ret, func, frame_pointer, retp, 0);
if (offset < 0)
goto out;
@ -661,7 +670,7 @@ int function_graph_enter(unsigned long ret, unsigned long func,
if (static_branch_likely(&fgraph_do_direct)) {
int save_curr_ret_stack = current->curr_ret_stack;
if (static_call(fgraph_func)(&trace, fgraph_direct_gops))
if (static_call(fgraph_func)(&trace, fgraph_direct_gops, &extras))
bitmap |= BIT(fgraph_direct_gops->idx);
else
/* Clear out any saved storage */
@ -679,7 +688,7 @@ int function_graph_enter(unsigned long ret, unsigned long func,
save_curr_ret_stack = current->curr_ret_stack;
if (ftrace_ops_test(&gops->ops, func, NULL) &&
gops->entryfunc(&trace, gops))
gops->entryfunc(&trace, gops, &extras))
bitmap |= BIT(i);
else
/* Clear out any saved storage */
@ -1136,7 +1145,8 @@ void ftrace_graph_exit_task(struct task_struct *t)
#ifdef CONFIG_DYNAMIC_FTRACE
static int fgraph_pid_func(struct ftrace_graph_ent *trace,
struct fgraph_ops *gops)
struct fgraph_ops *gops,
struct fgraph_extras *extras)
{
struct trace_array *tr = gops->ops.private;
int pid;
@ -1150,7 +1160,7 @@ static int fgraph_pid_func(struct ftrace_graph_ent *trace,
return 0;
}
return gops->saved_func(trace, gops);
return gops->saved_func(trace, gops, NULL);
}
void fgraph_update_pid_func(void)

View File

@ -827,7 +827,8 @@ struct profile_fgraph_data {
};
static int profile_graph_entry(struct ftrace_graph_ent *trace,
struct fgraph_ops *gops)
struct fgraph_ops *gops,
struct fgraph_extras *extras)
{
struct profile_fgraph_data *profile_data;

View File

@ -46,6 +46,7 @@ enum trace_type {
TRACE_BRANCH,
TRACE_GRAPH_RET,
TRACE_GRAPH_ENT,
TRACE_GRAPH_RETADDR_ENT,
TRACE_USER_STACK,
TRACE_BLK,
TRACE_BPUTS,
@ -512,6 +513,8 @@ extern void __ftrace_bad_type(void);
IF_ASSIGN(var, ent, struct trace_branch, TRACE_BRANCH); \
IF_ASSIGN(var, ent, struct ftrace_graph_ent_entry, \
TRACE_GRAPH_ENT); \
IF_ASSIGN(var, ent, struct fgraph_retaddr_ent_entry,\
TRACE_GRAPH_RETADDR_ENT); \
IF_ASSIGN(var, ent, struct ftrace_graph_ret_entry, \
TRACE_GRAPH_RET); \
IF_ASSIGN(var, ent, struct func_repeats_entry, \
@ -692,7 +695,8 @@ void trace_default_header(struct seq_file *m);
void print_trace_header(struct seq_file *m, struct trace_iterator *iter);
void trace_graph_return(struct ftrace_graph_ret *trace, struct fgraph_ops *gops);
int trace_graph_entry(struct ftrace_graph_ent *trace, struct fgraph_ops *gops);
int trace_graph_entry(struct ftrace_graph_ent *trace, struct fgraph_ops *gops,
struct fgraph_extras *extras);
void tracing_start_cmdline_record(void);
void tracing_stop_cmdline_record(void);
@ -879,6 +883,7 @@ static __always_inline bool ftrace_hash_empty(struct ftrace_hash *hash)
#define TRACE_GRAPH_GRAPH_TIME 0x400
#define TRACE_GRAPH_PRINT_RETVAL 0x800
#define TRACE_GRAPH_PRINT_RETVAL_HEX 0x1000
#define TRACE_GRAPH_PRINT_RETADDR 0x2000
#define TRACE_GRAPH_PRINT_FILL_SHIFT 28
#define TRACE_GRAPH_PRINT_FILL_MASK (0x3 << TRACE_GRAPH_PRINT_FILL_SHIFT)
@ -900,6 +905,10 @@ extern void graph_trace_close(struct trace_iterator *iter);
extern int __trace_graph_entry(struct trace_array *tr,
struct ftrace_graph_ent *trace,
unsigned int trace_ctx);
extern int __trace_graph_retaddr_entry(struct trace_array *tr,
struct ftrace_graph_ent *trace,
unsigned int trace_ctx,
unsigned long retaddr);
extern void __trace_graph_return(struct trace_array *tr,
struct ftrace_graph_ret *trace,
unsigned int trace_ctx);

View File

@ -85,9 +85,35 @@ FTRACE_ENTRY_PACKED(funcgraph_entry, ftrace_graph_ent_entry,
F_printk("--> %ps (%d)", (void *)__entry->func, __entry->depth)
);
/* Function return entry */
#ifdef CONFIG_FUNCTION_GRAPH_RETADDR
/* Function call entry with a return address */
FTRACE_ENTRY_PACKED(fgraph_retaddr_entry, fgraph_retaddr_ent_entry,
TRACE_GRAPH_RETADDR_ENT,
F_STRUCT(
__field_struct( struct fgraph_retaddr_ent, graph_ent )
__field_packed( unsigned long, graph_ent, func )
__field_packed( int, graph_ent, depth )
__field_packed( unsigned long, graph_ent, retaddr )
),
F_printk("--> %ps (%d) <- %ps", (void *)__entry->func, __entry->depth,
(void *)__entry->retaddr)
);
#else
#ifndef fgraph_retaddr_ent_entry
#define fgraph_retaddr_ent_entry ftrace_graph_ent_entry
#endif
#endif
#ifdef CONFIG_FUNCTION_GRAPH_RETVAL
/* Function return entry */
FTRACE_ENTRY_PACKED(funcgraph_exit, ftrace_graph_ret_entry,
TRACE_GRAPH_RET,
@ -110,6 +136,7 @@ FTRACE_ENTRY_PACKED(funcgraph_exit, ftrace_graph_ret_entry,
#else
/* Function return entry */
FTRACE_ENTRY_PACKED(funcgraph_exit, ftrace_graph_ret_entry,
TRACE_GRAPH_RET,

View File

@ -31,7 +31,10 @@ struct fgraph_data {
struct fgraph_cpu_data __percpu *cpu_data;
/* Place to preserve last processed entry. */
struct ftrace_graph_ent_entry ent;
union {
struct ftrace_graph_ent_entry ent;
struct fgraph_retaddr_ent_entry rent;
} ent;
struct ftrace_graph_ret_entry ret;
int failed;
int cpu;
@ -63,6 +66,10 @@ static struct tracer_opt trace_opts[] = {
{ TRACER_OPT(funcgraph-retval, TRACE_GRAPH_PRINT_RETVAL) },
/* Display function return value in hexadecimal format ? */
{ TRACER_OPT(funcgraph-retval-hex, TRACE_GRAPH_PRINT_RETVAL_HEX) },
#endif
#ifdef CONFIG_FUNCTION_GRAPH_RETADDR
/* Display function return address ? */
{ TRACER_OPT(funcgraph-retaddr, TRACE_GRAPH_PRINT_RETADDR) },
#endif
/* Include sleep time (scheduled out) between entry and return */
{ TRACER_OPT(sleep-time, TRACE_GRAPH_SLEEP_TIME) },
@ -83,6 +90,11 @@ static struct tracer_flags tracer_flags = {
.opts = trace_opts
};
u32 graph_tracer_flags_get(u32 flags)
{
return tracer_flags.val & flags;
}
/*
* DURATION column is being also used to display IRQ signs,
* following values are used by print_graph_irq and others
@ -119,6 +131,40 @@ int __trace_graph_entry(struct trace_array *tr,
return 1;
}
#ifdef CONFIG_FUNCTION_GRAPH_RETADDR
int __trace_graph_retaddr_entry(struct trace_array *tr,
struct ftrace_graph_ent *trace,
unsigned int trace_ctx,
unsigned long retaddr)
{
struct trace_event_call *call = &event_fgraph_retaddr_entry;
struct ring_buffer_event *event;
struct trace_buffer *buffer = tr->array_buffer.buffer;
struct fgraph_retaddr_ent_entry *entry;
event = trace_buffer_lock_reserve(buffer, TRACE_GRAPH_RETADDR_ENT,
sizeof(*entry), trace_ctx);
if (!event)
return 0;
entry = ring_buffer_event_data(event);
entry->graph_ent.func = trace->func;
entry->graph_ent.depth = trace->depth;
entry->graph_ent.retaddr = retaddr;
if (!call_filter_check_discard(call, entry, buffer, event))
trace_buffer_unlock_commit_nostack(buffer, event);
return 1;
}
#else
int __trace_graph_retaddr_entry(struct trace_array *tr,
struct ftrace_graph_ent *trace,
unsigned int trace_ctx,
unsigned long retaddr)
{
return 1;
}
#endif
static inline int ftrace_graph_ignore_irqs(void)
{
if (!ftrace_graph_skip_irqs || trace_recursion_test(TRACE_IRQ_BIT))
@ -133,7 +179,8 @@ struct fgraph_times {
};
int trace_graph_entry(struct ftrace_graph_ent *trace,
struct fgraph_ops *gops)
struct fgraph_ops *gops,
struct fgraph_extras *extras)
{
unsigned long *task_var = fgraph_get_task_var(gops);
struct trace_array *tr = gops->private;
@ -199,7 +246,12 @@ int trace_graph_entry(struct ftrace_graph_ent *trace,
disabled = atomic_inc_return(&data->disabled);
if (likely(disabled == 1)) {
trace_ctx = tracing_gen_ctx_flags(flags);
ret = __trace_graph_entry(tr, trace, trace_ctx);
if (unlikely(IS_ENABLED(CONFIG_FUNCTION_GRAPH_RETADDR) && extras
&& (extras->flags & TRACE_GRAPH_PRINT_RETADDR)))
ret = __trace_graph_retaddr_entry(tr, trace, trace_ctx,
extras->retaddr);
else
ret = __trace_graph_entry(tr, trace, trace_ctx);
} else {
ret = 0;
}
@ -507,7 +559,7 @@ get_return_for_leaf(struct trace_iterator *iter,
* then we just reuse the data from before.
*/
if (data && data->failed) {
curr = &data->ent;
curr = &data->ent.ent;
next = &data->ret;
} else {
@ -537,7 +589,10 @@ get_return_for_leaf(struct trace_iterator *iter,
* Save current and next entries for later reference
* if the output fails.
*/
data->ent = *curr;
if (unlikely(curr->ent.type == TRACE_GRAPH_RETADDR_ENT))
data->ent.rent = *(struct fgraph_retaddr_ent_entry *)curr;
else
data->ent.ent = *curr;
/*
* If the next event is not a return type, then
* we only care about what type it is. Otherwise we can
@ -701,52 +756,96 @@ print_graph_duration(struct trace_array *tr, unsigned long long duration,
}
#ifdef CONFIG_FUNCTION_GRAPH_RETVAL
#define __TRACE_GRAPH_PRINT_RETVAL TRACE_GRAPH_PRINT_RETVAL
#else
#define __TRACE_GRAPH_PRINT_RETVAL 0
#endif
static void print_graph_retval(struct trace_seq *s, unsigned long retval,
bool leaf, void *func, bool hex_format)
#ifdef CONFIG_FUNCTION_GRAPH_RETADDR
#define __TRACE_GRAPH_PRINT_RETADDR TRACE_GRAPH_PRINT_RETADDR
static void print_graph_retaddr(struct trace_seq *s, struct fgraph_retaddr_ent_entry *entry,
u32 trace_flags, bool comment)
{
if (comment)
trace_seq_puts(s, " /*");
trace_seq_puts(s, " <-");
seq_print_ip_sym(s, entry->graph_ent.retaddr, trace_flags | TRACE_ITER_SYM_OFFSET);
if (comment)
trace_seq_puts(s, " */");
}
#else
#define __TRACE_GRAPH_PRINT_RETADDR 0
#define print_graph_retaddr(_seq, _entry, _tflags, _comment) do { } while (0)
#endif
#if defined(CONFIG_FUNCTION_GRAPH_RETVAL) || defined(CONFIG_FUNCTION_GRAPH_RETADDR)
static void print_graph_retval(struct trace_seq *s, struct ftrace_graph_ent_entry *entry,
struct ftrace_graph_ret *graph_ret, void *func,
u32 opt_flags, u32 trace_flags)
{
unsigned long err_code = 0;
unsigned long retval = 0;
bool print_retaddr = false;
bool print_retval = false;
bool hex_format = !!(opt_flags & TRACE_GRAPH_PRINT_RETVAL_HEX);
if (retval == 0 || hex_format)
goto done;
#ifdef CONFIG_FUNCTION_GRAPH_RETVAL
retval = graph_ret->retval;
print_retval = !!(opt_flags & TRACE_GRAPH_PRINT_RETVAL);
#endif
/* Check if the return value matches the negative format */
if (IS_ENABLED(CONFIG_64BIT) && (retval & BIT(31)) &&
(((u64)retval) >> 32) == 0) {
/* sign extension */
err_code = (unsigned long)(s32)retval;
} else {
err_code = retval;
#ifdef CONFIG_FUNCTION_GRAPH_RETADDR
print_retaddr = !!(opt_flags & TRACE_GRAPH_PRINT_RETADDR);
#endif
if (print_retval && retval && !hex_format) {
/* Check if the return value matches the negative format */
if (IS_ENABLED(CONFIG_64BIT) && (retval & BIT(31)) &&
(((u64)retval) >> 32) == 0) {
err_code = sign_extend64(retval, 31);
} else {
err_code = retval;
}
if (!IS_ERR_VALUE(err_code))
err_code = 0;
}
if (!IS_ERR_VALUE(err_code))
err_code = 0;
if (entry) {
if (entry->ent.type != TRACE_GRAPH_RETADDR_ENT)
print_retaddr = false;
done:
if (leaf) {
if (hex_format || (err_code == 0))
trace_seq_printf(s, "%ps(); /* = 0x%lx */\n",
func, retval);
trace_seq_printf(s, "%ps();", func);
if (print_retval || print_retaddr)
trace_seq_puts(s, " /*");
else
trace_seq_printf(s, "%ps(); /* = %ld */\n",
func, err_code);
trace_seq_putc(s, '\n');
} else {
if (hex_format || (err_code == 0))
trace_seq_printf(s, "} /* %ps = 0x%lx */\n",
func, retval);
else
trace_seq_printf(s, "} /* %ps = %ld */\n",
func, err_code);
print_retaddr = false;
trace_seq_printf(s, "} /* %ps", func);
}
if (print_retaddr)
print_graph_retaddr(s, (struct fgraph_retaddr_ent_entry *)entry,
trace_flags, false);
if (print_retval) {
if (hex_format || (err_code == 0))
trace_seq_printf(s, " ret=0x%lx", retval);
else
trace_seq_printf(s, " ret=%ld", err_code);
}
if (!entry || print_retval || print_retaddr)
trace_seq_puts(s, " */\n");
}
#else
#define __TRACE_GRAPH_PRINT_RETVAL 0
#define print_graph_retval(_seq, _retval, _leaf, _func, _format) do {} while (0)
#define print_graph_retval(_seq, _ent, _ret, _func, _opt_flags, _trace_flags) do {} while (0)
#endif
@ -798,14 +897,15 @@ print_graph_entry_leaf(struct trace_iterator *iter,
trace_seq_putc(s, ' ');
/*
* Write out the function return value if the option function-retval is
* enabled.
* Write out the function return value or return address
*/
if (flags & __TRACE_GRAPH_PRINT_RETVAL)
print_graph_retval(s, graph_ret->retval, true, (void *)func,
!!(flags & TRACE_GRAPH_PRINT_RETVAL_HEX));
else
if (flags & (__TRACE_GRAPH_PRINT_RETVAL | __TRACE_GRAPH_PRINT_RETADDR)) {
print_graph_retval(s, entry, graph_ret,
(void *)graph_ret->func + iter->tr->text_delta,
flags, tr->trace_flags);
} else {
trace_seq_printf(s, "%ps();\n", (void *)func);
}
print_graph_irq(iter, graph_ret->func, TRACE_GRAPH_RET,
cpu, iter->ent->pid, flags);
@ -846,7 +946,12 @@ print_graph_entry_nested(struct trace_iterator *iter,
func = call->func + iter->tr->text_delta;
trace_seq_printf(s, "%ps() {\n", (void *)func);
trace_seq_printf(s, "%ps() {", (void *)func);
if (flags & __TRACE_GRAPH_PRINT_RETADDR &&
entry->ent.type == TRACE_GRAPH_RETADDR_ENT)
print_graph_retaddr(s, (struct fgraph_retaddr_ent_entry *)entry,
tr->trace_flags, true);
trace_seq_putc(s, '\n');
if (trace_seq_has_overflowed(s))
return TRACE_TYPE_PARTIAL_LINE;
@ -1093,11 +1198,10 @@ print_graph_return(struct ftrace_graph_ret *trace, struct trace_seq *s,
/*
* Always write out the function name and its return value if the
* function-retval option is enabled.
* funcgraph-retval option is enabled.
*/
if (flags & __TRACE_GRAPH_PRINT_RETVAL) {
print_graph_retval(s, trace->retval, false, (void *)func,
!!(flags & TRACE_GRAPH_PRINT_RETVAL_HEX));
print_graph_retval(s, NULL, trace, (void *)func, flags, tr->trace_flags);
} else {
/*
* If the return function does not have a matching entry,
@ -1212,7 +1316,7 @@ print_graph_function_flags(struct trace_iterator *iter, u32 flags)
* to print out the missing entry which would never go out.
*/
if (data && data->failed) {
field = &data->ent;
field = &data->ent.ent;
iter->cpu = data->cpu;
ret = print_graph_entry(field, s, iter, flags);
if (ret == TRACE_TYPE_HANDLED && iter->cpu != cpu) {
@ -1236,6 +1340,16 @@ print_graph_function_flags(struct trace_iterator *iter, u32 flags)
saved = *field;
return print_graph_entry(&saved, s, iter, flags);
}
#ifdef CONFIG_FUNCTION_GRAPH_RETADDR
case TRACE_GRAPH_RETADDR_ENT: {
struct fgraph_retaddr_ent_entry saved;
struct fgraph_retaddr_ent_entry *rfield;
trace_assign_type(rfield, entry);
saved = *rfield;
return print_graph_entry((struct ftrace_graph_ent_entry *)&saved, s, iter, flags);
}
#endif
case TRACE_GRAPH_RET: {
struct ftrace_graph_ret_entry *field;
trace_assign_type(field, entry);
@ -1430,6 +1544,13 @@ static struct trace_event graph_trace_entry_event = {
.funcs = &graph_functions,
};
#ifdef CONFIG_FUNCTION_GRAPH_RETADDR
static struct trace_event graph_trace_retaddr_entry_event = {
.type = TRACE_GRAPH_RETADDR_ENT,
.funcs = &graph_functions,
};
#endif
static struct trace_event graph_trace_ret_event = {
.type = TRACE_GRAPH_RET,
.funcs = &graph_functions
@ -1516,6 +1637,13 @@ static __init int init_graph_trace(void)
return 1;
}
#ifdef CONFIG_FUNCTION_GRAPH_RETADDR
if (!register_trace_event(&graph_trace_retaddr_entry_event)) {
pr_warn("Warning: could not register graph trace retaddr events\n");
return 1;
}
#endif
if (!register_trace_event(&graph_trace_ret_event)) {
pr_warn("Warning: could not register graph trace events\n");
return 1;

View File

@ -176,7 +176,8 @@ static int irqsoff_display_graph(struct trace_array *tr, int set)
}
static int irqsoff_graph_entry(struct ftrace_graph_ent *trace,
struct fgraph_ops *gops)
struct fgraph_ops *gops,
struct fgraph_extras *extras)
{
struct trace_array *tr = irqsoff_trace;
struct trace_array_cpu *data;

View File

@ -113,7 +113,8 @@ static int wakeup_display_graph(struct trace_array *tr, int set)
}
static int wakeup_graph_entry(struct ftrace_graph_ent *trace,
struct fgraph_ops *gops)
struct fgraph_ops *gops,
struct fgraph_extras *extras)
{
struct trace_array *tr = wakeup_trace;
struct trace_array_cpu *data;

View File

@ -17,6 +17,7 @@ static inline int trace_valid_entry(struct trace_entry *entry)
case TRACE_PRINT:
case TRACE_BRANCH:
case TRACE_GRAPH_ENT:
case TRACE_GRAPH_RETADDR_ENT:
case TRACE_GRAPH_RET:
return 1;
}
@ -773,7 +774,8 @@ struct fgraph_fixture {
};
static __init int store_entry(struct ftrace_graph_ent *trace,
struct fgraph_ops *gops)
struct fgraph_ops *gops,
struct fgraph_extras *extras)
{
struct fgraph_fixture *fixture = container_of(gops, struct fgraph_fixture, gops);
const char *type = fixture->store_type_name;
@ -1024,7 +1026,8 @@ static unsigned int graph_hang_thresh;
/* Wrap the real function entry probe to avoid possible hanging */
static int trace_graph_entry_watchdog(struct ftrace_graph_ent *trace,
struct fgraph_ops *gops)
struct fgraph_ops *gops,
struct fgraph_extras *extras)
{
/* This is harmlessly racy, we want to approximately detect a hang */
if (unlikely(++graph_hang_thresh > GRAPH_MAX_FUNC_TEST)) {
@ -1038,7 +1041,7 @@ static int trace_graph_entry_watchdog(struct ftrace_graph_ent *trace,
return 0;
}
return trace_graph_entry(trace, gops);
return trace_graph_entry(trace, gops, NULL);
}
static struct fgraph_ops fgraph_ops __initdata = {

View File

@ -29,7 +29,7 @@ set -e
: "Test printing the error code in signed decimal format"
echo 0 > options/funcgraph-retval-hex
count=`cat trace | grep 'proc_reg_write' | grep '= -5' | wc -l`
count=`cat trace | grep 'proc_reg_write' | grep '=-5' | wc -l`
if [ $count -eq 0 ]; then
fail "Return value can not be printed in signed decimal format"
fi