function_graph: Enable funcgraph-args and funcgraph-retaddr to work simultaneously

Currently, the funcgraph-args and funcgraph-retaddr features are
mutually exclusive. This patch resolves this limitation by allowing
funcgraph-retaddr to have an args array.

To verify the change, use perf to trace vfs_write with both options
enabled:

Before:
 # perf ftrace -G vfs_write --graph-opts args,retaddr
   ......
   down_read() { /* <-n_tty_write+0xa3/0x540 */
     __cond_resched(); /* <-down_read+0x12/0x160 */
     preempt_count_add(); /* <-down_read+0x3b/0x160 */
     preempt_count_sub(); /* <-down_read+0x8b/0x160 */
   }

After:
 # perf ftrace -G vfs_write --graph-opts args,retaddr
   ......
   down_read(sem=0xffff8880100bea78) { /* <-n_tty_write+0xa3/0x540 */
     __cond_resched(); /* <-down_read+0x12/0x160 */
     preempt_count_add(val=1); /* <-down_read+0x3b/0x160 */
     preempt_count_sub(val=1); /* <-down_read+0x8b/0x160 */
   }

Cc: Steven Rostedt (Google) <rostedt@goodmis.org>
Cc: Sven Schnelle <svens@linux.ibm.com>
Cc: Masami Hiramatsu <mhiramat@kernel.org>
Cc: Xiaoqin Zhang <zhangxiaoqin@xiaomi.com>
Link: https://patch.msgid.link/20251125093425.2563849-1-dolinux.peng@gmail.com
Signed-off-by: pengdonglin <pengdonglin@xiaomi.com>
Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
This commit is contained in:
pengdonglin 2025-11-25 17:34:25 +08:00 committed by Steven Rostedt (Google)
parent 20e7168326
commit f83ac7544f
4 changed files with 80 additions and 37 deletions

View File

@ -1126,17 +1126,14 @@ static inline void ftrace_init(void) { }
*/ */
struct ftrace_graph_ent { struct ftrace_graph_ent {
unsigned long func; /* Current function */ unsigned long func; /* Current function */
int depth; unsigned long depth;
} __packed; } __packed;
/* /*
* Structure that defines an entry function trace with retaddr. * 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 { struct fgraph_retaddr_ent {
unsigned long func; /* Current function */ struct ftrace_graph_ent ent;
int depth;
unsigned long retaddr; /* Return address */ unsigned long retaddr; /* Return address */
} __packed; } __packed;

View File

@ -964,7 +964,8 @@ extern int __trace_graph_entry(struct trace_array *tr,
extern int __trace_graph_retaddr_entry(struct trace_array *tr, extern int __trace_graph_retaddr_entry(struct trace_array *tr,
struct ftrace_graph_ent *trace, struct ftrace_graph_ent *trace,
unsigned int trace_ctx, unsigned int trace_ctx,
unsigned long retaddr); unsigned long retaddr,
struct ftrace_regs *fregs);
extern void __trace_graph_return(struct trace_array *tr, extern void __trace_graph_return(struct trace_array *tr,
struct ftrace_graph_ret *trace, struct ftrace_graph_ret *trace,
unsigned int trace_ctx, unsigned int trace_ctx,
@ -2276,4 +2277,25 @@ static inline int rv_init_interface(void)
*/ */
#define FTRACE_TRAMPOLINE_MARKER ((unsigned long) INT_MAX) #define FTRACE_TRAMPOLINE_MARKER ((unsigned long) INT_MAX)
/*
* This is used to get the address of the args array based on
* the type of the entry.
*/
#define FGRAPH_ENTRY_ARGS(e) \
({ \
unsigned long *_args; \
struct ftrace_graph_ent_entry *_e = e; \
\
if (IS_ENABLED(CONFIG_FUNCTION_GRAPH_RETADDR) && \
e->ent.type == TRACE_GRAPH_RETADDR_ENT) { \
struct fgraph_retaddr_ent_entry *_re; \
\
_re = (typeof(_re))_e; \
_args = _re->args; \
} else { \
_args = _e->args; \
} \
_args; \
})
#endif /* _LINUX_KERNEL_TRACE_H */ #endif /* _LINUX_KERNEL_TRACE_H */

View File

@ -80,11 +80,11 @@ FTRACE_ENTRY(funcgraph_entry, ftrace_graph_ent_entry,
F_STRUCT( F_STRUCT(
__field_struct( struct ftrace_graph_ent, graph_ent ) __field_struct( struct ftrace_graph_ent, graph_ent )
__field_packed( unsigned long, graph_ent, func ) __field_packed( unsigned long, graph_ent, func )
__field_packed( unsigned int, graph_ent, depth ) __field_packed( unsigned long, graph_ent, depth )
__dynamic_array(unsigned long, args ) __dynamic_array(unsigned long, args )
), ),
F_printk("--> %ps (%u)", (void *)__entry->func, __entry->depth) F_printk("--> %ps (%lu)", (void *)__entry->func, __entry->depth)
); );
#ifdef CONFIG_FUNCTION_GRAPH_RETADDR #ifdef CONFIG_FUNCTION_GRAPH_RETADDR
@ -95,13 +95,14 @@ FTRACE_ENTRY_PACKED(fgraph_retaddr_entry, fgraph_retaddr_ent_entry,
TRACE_GRAPH_RETADDR_ENT, TRACE_GRAPH_RETADDR_ENT,
F_STRUCT( F_STRUCT(
__field_struct( struct fgraph_retaddr_ent, graph_ent ) __field_struct( struct fgraph_retaddr_ent, graph_rent )
__field_packed( unsigned long, graph_ent, func ) __field_packed( unsigned long, graph_rent.ent, func )
__field_packed( unsigned int, graph_ent, depth ) __field_packed( unsigned long, graph_rent.ent, depth )
__field_packed( unsigned long, graph_ent, retaddr ) __field_packed( unsigned long, graph_rent, retaddr )
__dynamic_array(unsigned long, args )
), ),
F_printk("--> %ps (%u) <- %ps", (void *)__entry->func, __entry->depth, F_printk("--> %ps (%lu) <- %ps", (void *)__entry->func, __entry->depth,
(void *)__entry->retaddr) (void *)__entry->retaddr)
); );

View File

@ -36,14 +36,19 @@ struct fgraph_ent_args {
unsigned long args[FTRACE_REGS_MAX_ARGS]; unsigned long args[FTRACE_REGS_MAX_ARGS];
}; };
struct fgraph_retaddr_ent_args {
struct fgraph_retaddr_ent_entry ent;
/* Force the sizeof of args[] to have FTRACE_REGS_MAX_ARGS entries */
unsigned long args[FTRACE_REGS_MAX_ARGS];
};
struct fgraph_data { struct fgraph_data {
struct fgraph_cpu_data __percpu *cpu_data; struct fgraph_cpu_data __percpu *cpu_data;
/* Place to preserve last processed entry. */ /* Place to preserve last processed entry. */
union { union {
struct fgraph_ent_args ent; struct fgraph_ent_args ent;
/* TODO allow retaddr to have args */ struct fgraph_retaddr_ent_args rent;
struct fgraph_retaddr_ent_entry rent;
}; };
struct ftrace_graph_ret_entry ret; struct ftrace_graph_ret_entry ret;
int failed; int failed;
@ -160,20 +165,32 @@ int __trace_graph_entry(struct trace_array *tr,
int __trace_graph_retaddr_entry(struct trace_array *tr, int __trace_graph_retaddr_entry(struct trace_array *tr,
struct ftrace_graph_ent *trace, struct ftrace_graph_ent *trace,
unsigned int trace_ctx, unsigned int trace_ctx,
unsigned long retaddr) unsigned long retaddr,
struct ftrace_regs *fregs)
{ {
struct ring_buffer_event *event; struct ring_buffer_event *event;
struct trace_buffer *buffer = tr->array_buffer.buffer; struct trace_buffer *buffer = tr->array_buffer.buffer;
struct fgraph_retaddr_ent_entry *entry; struct fgraph_retaddr_ent_entry *entry;
int size;
/* If fregs is defined, add FTRACE_REGS_MAX_ARGS long size words */
size = sizeof(*entry) + (FTRACE_REGS_MAX_ARGS * !!fregs * sizeof(long));
event = trace_buffer_lock_reserve(buffer, TRACE_GRAPH_RETADDR_ENT, event = trace_buffer_lock_reserve(buffer, TRACE_GRAPH_RETADDR_ENT,
sizeof(*entry), trace_ctx); size, trace_ctx);
if (!event) if (!event)
return 0; return 0;
entry = ring_buffer_event_data(event); entry = ring_buffer_event_data(event);
entry->graph_ent.func = trace->func; entry->graph_rent.ent = *trace;
entry->graph_ent.depth = trace->depth; entry->graph_rent.retaddr = retaddr;
entry->graph_ent.retaddr = retaddr;
#ifdef CONFIG_HAVE_FUNCTION_ARG_ACCESS_API
if (fregs) {
for (int i = 0; i < FTRACE_REGS_MAX_ARGS; i++)
entry->args[i] = ftrace_regs_get_argument(fregs, i);
}
#endif
trace_buffer_unlock_commit_nostack(buffer, event); trace_buffer_unlock_commit_nostack(buffer, event);
return 1; return 1;
@ -182,7 +199,8 @@ int __trace_graph_retaddr_entry(struct trace_array *tr,
int __trace_graph_retaddr_entry(struct trace_array *tr, int __trace_graph_retaddr_entry(struct trace_array *tr,
struct ftrace_graph_ent *trace, struct ftrace_graph_ent *trace,
unsigned int trace_ctx, unsigned int trace_ctx,
unsigned long retaddr) unsigned long retaddr,
struct ftrace_regs *fregs)
{ {
return 1; return 1;
} }
@ -267,7 +285,8 @@ static int graph_entry(struct ftrace_graph_ent *trace,
if (IS_ENABLED(CONFIG_FUNCTION_GRAPH_RETADDR) && if (IS_ENABLED(CONFIG_FUNCTION_GRAPH_RETADDR) &&
tracer_flags_is_set(tr, TRACE_GRAPH_PRINT_RETADDR)) { tracer_flags_is_set(tr, TRACE_GRAPH_PRINT_RETADDR)) {
unsigned long retaddr = ftrace_graph_top_ret_addr(current); unsigned long retaddr = ftrace_graph_top_ret_addr(current);
ret = __trace_graph_retaddr_entry(tr, trace, trace_ctx, retaddr); ret = __trace_graph_retaddr_entry(tr, trace, trace_ctx,
retaddr, fregs);
} else { } else {
ret = __graph_entry(tr, trace, trace_ctx, fregs); ret = __graph_entry(tr, trace, trace_ctx, fregs);
} }
@ -654,13 +673,9 @@ get_return_for_leaf(struct trace_iterator *iter,
* Save current and next entries for later reference * Save current and next entries for later reference
* if the output fails. * if the output fails.
*/ */
if (unlikely(curr->ent.type == TRACE_GRAPH_RETADDR_ENT)) { int size = min_t(int, sizeof(data->rent), iter->ent_size);
data->rent = *(struct fgraph_retaddr_ent_entry *)curr;
} else {
int size = min((int)sizeof(data->ent), (int)iter->ent_size);
memcpy(&data->ent, curr, size); memcpy(&data->rent, curr, size);
}
/* /*
* If the next event is not a return type, then * If the next event is not a return type, then
* we only care about what type it is. Otherwise we can * we only care about what type it is. Otherwise we can
@ -838,7 +853,7 @@ static void print_graph_retaddr(struct trace_seq *s, struct fgraph_retaddr_ent_e
trace_seq_puts(s, " /*"); trace_seq_puts(s, " /*");
trace_seq_puts(s, " <-"); trace_seq_puts(s, " <-");
seq_print_ip_sym_offset(s, entry->graph_ent.retaddr, trace_flags); seq_print_ip_sym_offset(s, entry->graph_rent.retaddr, trace_flags);
if (comment) if (comment)
trace_seq_puts(s, " */"); trace_seq_puts(s, " */");
@ -984,7 +999,7 @@ print_graph_entry_leaf(struct trace_iterator *iter,
trace_seq_printf(s, "%ps", (void *)ret_func); trace_seq_printf(s, "%ps", (void *)ret_func);
if (args_size >= FTRACE_REGS_MAX_ARGS * sizeof(long)) { if (args_size >= FTRACE_REGS_MAX_ARGS * sizeof(long)) {
print_function_args(s, entry->args, ret_func); print_function_args(s, FGRAPH_ENTRY_ARGS(entry), ret_func);
trace_seq_putc(s, ';'); trace_seq_putc(s, ';');
} else } else
trace_seq_puts(s, "();"); trace_seq_puts(s, "();");
@ -1036,7 +1051,7 @@ print_graph_entry_nested(struct trace_iterator *iter,
args_size = iter->ent_size - offsetof(struct ftrace_graph_ent_entry, args); args_size = iter->ent_size - offsetof(struct ftrace_graph_ent_entry, args);
if (args_size >= FTRACE_REGS_MAX_ARGS * sizeof(long)) if (args_size >= FTRACE_REGS_MAX_ARGS * sizeof(long))
print_function_args(s, entry->args, func); print_function_args(s, FGRAPH_ENTRY_ARGS(entry), func);
else else
trace_seq_puts(s, "()"); trace_seq_puts(s, "()");
@ -1218,11 +1233,14 @@ print_graph_entry(struct ftrace_graph_ent_entry *field, struct trace_seq *s,
/* /*
* print_graph_entry() may consume the current event, * print_graph_entry() may consume the current event,
* thus @field may become invalid, so we need to save it. * thus @field may become invalid, so we need to save it.
* sizeof(struct ftrace_graph_ent_entry) is very small, * This function is shared by ftrace_graph_ent_entry and
* it can be safely saved at the stack. * fgraph_retaddr_ent_entry, the size of the latter one
* is larger, but it is very small and can be safely saved
* at the stack.
*/ */
struct ftrace_graph_ent_entry *entry; struct ftrace_graph_ent_entry *entry;
u8 save_buf[sizeof(*entry) + FTRACE_REGS_MAX_ARGS * sizeof(long)]; struct fgraph_retaddr_ent_entry *rentry;
u8 save_buf[sizeof(*rentry) + FTRACE_REGS_MAX_ARGS * sizeof(long)];
/* The ent_size is expected to be as big as the entry */ /* The ent_size is expected to be as big as the entry */
if (iter->ent_size > sizeof(save_buf)) if (iter->ent_size > sizeof(save_buf))
@ -1451,12 +1469,17 @@ print_graph_function_flags(struct trace_iterator *iter, u32 flags)
} }
#ifdef CONFIG_FUNCTION_GRAPH_RETADDR #ifdef CONFIG_FUNCTION_GRAPH_RETADDR
case TRACE_GRAPH_RETADDR_ENT: { case TRACE_GRAPH_RETADDR_ENT: {
struct fgraph_retaddr_ent_entry saved; /*
* ftrace_graph_ent_entry and fgraph_retaddr_ent_entry have
* similar functions and memory layouts. The only difference
* is that the latter one has an extra retaddr member, so
* they can share most of the logic.
*/
struct fgraph_retaddr_ent_entry *rfield; struct fgraph_retaddr_ent_entry *rfield;
trace_assign_type(rfield, entry); trace_assign_type(rfield, entry);
saved = *rfield; return print_graph_entry((struct ftrace_graph_ent_entry *)rfield,
return print_graph_entry((struct ftrace_graph_ent_entry *)&saved, s, iter, flags); s, iter, flags);
} }
#endif #endif
case TRACE_GRAPH_RET: { case TRACE_GRAPH_RET: {