mirror of
https://github.com/torvalds/linux.git
synced 2024-11-21 19:46:16 +00:00
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:
parent
f1f36e22be
commit
21e92806d3
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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 = {
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user