tracing: fprobe: function_graph: Multi-function graph and fprobe on fgraph
Subsystems (15)
Masami Hiramatsu (Google)
maintainerv3 cover tracing: fprobe: function_graph: Multi-function graph and fprobe on fgraph
Hi,
Here is the 3rd version of the series to re-implement the fprobe on function-graph tracer. The previous version is;
https://lore.kernel.org/all/169945345785.55307.5003201137843449313.stgit@devnote2/
In this version, I fixed the implementation issue in the multiple function-graph tracer[12/33]. The previous version calls the handlers of the function graph tracers multiple times if function tracer is used for function entry callback. Instead, the function graph tracer handler will be called once per each function-tracer callback. To avoid "push" on the shadow ret-stack, the "push" operation checks if the previous one can be reused and returns it.
Currently, x86-64 and arm64 support this new feature.
This also fixes reserve_data/retrieve_data functions[17/33] to allocate data with correct size with corresponding index number. And fixes fprobe patch to handle it correctly[28/33].
I added some arm64 supports[22/33][23/33], but this still seems to cause some backtrace issue (maybe wrong fp saved?).
Overview
--------
This series does major 2 changes, enable multiple function-graphs on the ftrace (e.g. allow function-graph on sub instances) and rewrite the fprobe on this function-graph.
The former changes had been sent from Steven Rostedt 4 years ago (*), which allows users to set different setting function-graph tracer (and other tracers based on function-graph) in each trace-instances at the same time.
(*) https://lore.kernel.org/all/[email protected]/
The purpose of latter change are;
1) Remove dependency of the rethook from fprobe so that we can reduce the return hook code and shadow stack.
2) Make 'ftrace_regs' the common trace interface for the function boundary.
1) Currently we have 2(or 3) different function return hook codes, the function-graph tracer and rethook (and legacy kretprobe). But since this is redundant and needs double maintenance cost, I would like to unify those. From the user's viewpoint, function- graph tracer is very useful to grasp the execution path. For this purpose, it is hard to use the rethook in the function-graph tracer, but the opposite is possible. (Strictly speaking, kretprobe can not use it because it requires 'pt_regs' for historical reasons.)
2) Now the fprobe provides the 'pt_regs' for its handler, but that is wrong for the function entry and exit. Moreover, depending on the architecture, there is no way to accurately reproduce 'pt_regs' outside of interrupt or exception handlers. This means fprobe should not use 'pt_regs' because it does not use such exceptions. (Conversely, kprobe should use 'pt_regs' because it is an abstract interface of the software breakpoint exception.)
This series changes fprobe to use function-graph tracer for tracing function entry and exit, instead of mixture of ftrace and rethook. Unlike the rethook which is a per-task list of system-wide allocated nodes, the function graph's ret_stack is a per-task shadow stack. Thus it does not need to set 'nr_maxactive' (which is the number of pre-allocated nodes).
Also the handlers will get the 'ftrace_regs' instead of 'pt_regs'. Since eBPF mulit_kprobe/multi_kretprobe events still use 'pt_regs' as their register interface, this changes it to convert 'ftrace_regs' to 'pt_regs'. Of course this conversion makes an incomplete 'pt_regs', so users must access only registers for function parameters or return value.
Design
------
Instead of using ftrace's function entry hook directly, the new fprobe is built on top of the function-graph's entry and return callbacks with 'ftrace_regs'.
Since the fprobe requires access to 'ftrace_regs', the architecture must support CONFIG_HAVE_DYNAMIC_FTRACE_WITH_ARGS, which enables to call function-graph entry callback with 'ftrace_regs', and also CONFIG_HAVE_FUNCTION_GRAPH_FREGS, which passes the ftrace_regs to return_to_handler.
All fprobes share a single function-graph ops (means shares a common ftrace filter) similar to the kprobe-on-ftrace. This needs another layer to find corresponding fprobe in the common function-graph callbacks, but has much better scalability, since the number of registered function-graph ops is limited.
In the entry callback, the fprobe runs its entry_handler and saves the address of 'fprobe' on the function-graph's shadow stack as data. The return callback decodes the data to get the 'fprobe' address, and runs the exit_handler.
The fprobe introduces two hash-tables, one is for entry callback which searches fprobes related to the given function address passed by entry callback. The other is for a return callback which checks if the given 'fprobe' data structure pointer is still valid. Note that it is possible to unregister fprobe before the return callback runs. Thus the address validation must be done before using it in the return callback.
Series
------
- Patch [1/33] and [2/33] are adding a comment for ftrace_regs.
- Patch [3/33] to [18/33] are the multiple function-graph support.
- Patch [19/33] and [20/33] adds new function-graph callbacks with ftrace_regs and x86-64 implementation.
- Patch [21/33] to [27/33] are preparation (adding util functions) of the new fprobe and its user.
- Patch [28/33] to [32/33] rewrites fprobes and updates its users.
- Patch [33/33] is a documentation update.
This series can be applied against the probes-fixes-v6.6-rc7 on linux-trace tree.
This series can also be found below branch.
https://git.kernel.org/pub/scm/linux/kernel/git/mhiramat/linux.git/log/?h=topic/fprobe-on-fgraph
Thank you,
---
Christophe JAILLET (1):
seq_buf: Export seq_buf_puts()
Masami Hiramatsu (Google) (17):
tracing: Add a comment about ftrace_regs definition
x86: tracing: Add ftrace_regs definition in the header
function_graph: Add a new entry handler with parent_ip and ftrace_regs
function_graph: Add a new exit handler with parent_ip and ftrace_regs x86/ftrace: Enable HAVE_FUNCTION_GRAPH_FREGS
tracing: Rename ftrace_regs_return_value to ftrace_regs_get_return_value
arm64: ftrace: Enable HAVE_FUNCTION_GRAPH_FREGS
fprobe: Use ftrace_regs in fprobe entry handler
fprobe: Use ftrace_regs in fprobe exit handler
tracing: Add ftrace_partial_regs() for converting ftrace_regs to pt_regs
tracing: Add ftrace_fill_perf_regs() for perf event
fprobe: Rewrite fprobe on function-graph tracer tracing/fprobe: Remove nr_maxactive from fprobe tracing/fprobe: Enable fprobe events with CONFIG_DYNAMIC_FTRACE_WITH_ARGS
bpf: Enable kprobe_multi feature if CONFIG_FPROBE is enabled
selftests: ftrace: Remove obsolate maxactive syntax check
Documentation: probes: Update fprobe on function-graph tracer
Steven Rostedt (VMware) (15):
function_graph: Convert ret_stack to a series of longs
fgraph: Use BUILD_BUG_ON() to make sure we have structures divisible by long
function_graph: Add an array structure that will allow multiple callbacks
function_graph: Allow multiple users to attach to function graph
function_graph: Remove logic around ftrace_graph_entry and return ftrace/function_graph: Pass fgraph_ops to function graph callbacks
ftrace: Allow function_graph tracer to be enabled in instances
ftrace: Allow ftrace startup flags exist without dynamic ftrace
function_graph: Have the instances use their own ftrace_ops for filtering
function_graph: Add "task variables" per task for fgraph_ops
function_graph: Move set_graph_function tests to shadow stack global var
function_graph: Move graph depth stored data to shadow stack global var
function_graph: Move graph notrace bit to shadow stack global var
function_graph: Implement fgraph_reserve_data() and fgraph_retrieve_data()
function_graph: Add selftest for passing local variables
Documentation/trace/fprobe.rst | 42 + arch/arm64/Kconfig | 2 arch/arm64/include/asm/ftrace.h | 24 arch/arm64/kernel/entry-ftrace.S | 28 + arch/arm64/kernel/ftrace.c | 19 arch/loongarch/Kconfig | 1 arch/loongarch/include/asm/ftrace.h | 2 arch/loongarch/kernel/ftrace_dyn.c | 6 arch/powerpc/include/asm/ftrace.h | 9 arch/powerpc/kernel/trace/ftrace.c | 2 arch/powerpc/kernel/trace/ftrace_64_pg.c | 10 arch/s390/Kconfig | 1 arch/s390/include/asm/ftrace.h | 7 arch/x86/Kconfig | 4 arch/x86/include/asm/ftrace.h | 17 arch/x86/kernel/ftrace.c | 51 + arch/x86/kernel/ftrace_64.S | 37 + include/linux/fprobe.h | 58 + include/linux/ftrace.h | 167 +++ include/linux/sched.h | 2 include/linux/trace_recursion.h | 39 - kernel/trace/Kconfig | 19 kernel/trace/bpf_trace.c | 14 kernel/trace/fgraph.c | 981 ++++++++++++++++---- kernel/trace/fprobe.c | 637 +++++++++---- kernel/trace/ftrace.c | 13 kernel/trace/ftrace_internal.h | 2 kernel/trace/trace.h | 94 ++ kernel/trace/trace_fprobe.c | 114 +- kernel/trace/trace_functions.c | 8 kernel/trace/trace_functions_graph.c | 96 +- kernel/trace/trace_irqsoff.c | 10 kernel/trace/trace_probe_tmpl.h | 2 kernel/trace/trace_sched_wakeup.c | 10 kernel/trace/trace_selftest.c | 178 ++++ lib/seq_buf.c | 1 lib/test_fprobe.c | 51 - samples/fprobe/fprobe_example.c | 4 .../ftrace/test.d/dynevent/fprobe_syntax_errors.tc | 4 39 files changed, 2060 insertions(+), 706 deletions(-)
--
Masami Hiramatsu (Google) <[email protected]>
Masami Hiramatsu (Google)
maintainerPATCH v3 1/33 tracing: Add a comment about ftrace_regs definition
From: Masami Hiramatsu (Google) <[email protected]>
To clarify what will be expected on ftrace_regs, add a comment to the architecture independent definition of the ftrace_regs.
---
Changes in v3:
- Add instruction pointer
Changes in v2:
- newly added.
--- include/linux/ftrace.h | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+)
include/linux/ftrace.h +26 -0
@@ -118,6 +118,32 @@ extern int ftrace_enabled;#ifndef CONFIG_HAVE_DYNAMIC_FTRACE_WITH_ARGS +/** +* ftrace_regs - ftrace partial/optimal register set +* +* ftrace_regs represents a group of registers which is used at the +* function entry and exit. There are three types of registers. +* +* - Registers for passing the parameters to callee, including the stack +* pointer. (e.g. rcx, rdx, rdi, rsi, r8, r9 and rsp on x86_64) +* - Registers for passing the return values to caller. +* (e.g. rax and rdx on x86_64) +* - Registers for hooking the function call and return including the +* frame pointer (the frame pointer is architecture/config dependent) +* (e.g. rip, rbp and rsp for x86_64) +* +* Also, architecture dependent fields can be used for internal process. +* (e.g. orig_ax on x86_64) +* +* On the function entry, those registers will be restored except for +* the stack pointer, so that user can change the function parameters +* and instruction pointer (e.g. live patching.) +* On the function exit, only registers which is used for return values +* are restored. +* +* NOTE: user *must not* access regs directly, only do it via APIs, because +* the member can be changed according to the architecture. +*/ struct ftrace_regs {struct pt_regs regs; };
Masami Hiramatsu (Google)
maintainerPATCH v3 2/33 x86: tracing: Add ftrace_regs definition in the header
In reply to Masami Hiramatsu (Google)From: Masami Hiramatsu (Google) <[email protected]>
Add ftrace_regs definition for x86_64 in the ftrace header to clarify what register will be accessible from ftrace_regs.
---
Changes in v3:
- Add rip to be saved.
Changes in v2:
- Newly added.
--- arch/x86/include/asm/ftrace.h | 6 ++++++ 1 file changed, 6 insertions(+)
arch/x86/include/asm/ftrace.h +6 -0
@@ -36,6 +36,12 @@ static inline unsigned long ftrace_call_adjust(unsigned long addr)#ifdef CONFIG_HAVE_DYNAMIC_FTRACE_WITH_ARGS struct ftrace_regs { +/* +* On the x86_64, the ftrace_regs saves; +* rax, rcx, rdx, rdi, rsi, r8, r9, rbp, rip and rsp. +* Also orig_ax is used for passing direct trampoline address. +* x86_32 doesn't support ftrace_regs. +*/ struct pt_regs regs; };
Masami Hiramatsu (Google)
maintainerPATCH v3 3/33 seq_buf: Export seq_buf_puts()
In reply to Masami Hiramatsu (Google)From: Christophe JAILLET <[email protected]>
Mark seq_buf_puts() which is part of the seq_buf API to be exported to kernel loadable GPL modules.
--- lib/seq_buf.c | 1 + 1 file changed, 1 insertion(+)
lib/seq_buf.c +1 -0
@@ -189,6 +189,7 @@ int seq_buf_puts(struct seq_buf *s, const char *str)seq_buf_set_overflow (s);return -1 ; } +EXPORT_SYMBOL_GPL (seq_buf_puts);/** * seq_buf_putc - sequence printing of simple character
Masami Hiramatsu (Google)
maintainerPATCH v3 4/33 function_graph: Convert ret_stack to a series of longs
In reply to Masami Hiramatsu (Google)From: Steven Rostedt (VMware) <[email protected]>
In order to make it possible to have multiple callbacks registered with the function_graph tracer, the retstack needs to be converted from an array of ftrace_ret_stack structures to an array of longs. This will allow to store the list of callbacks on the stack for the return side of the functions.
--- include/linux/sched.h | 2 - kernel/trace/fgraph.c | 124 ++++++++++++++++++++++++++++--------------------- 2 files changed, 71 insertions(+), 55 deletions(-)
include/linux/sched.h +1 -1
@@ -1386,7 +1386,7 @@ struct task_struct {int curr_ret_depth;/* Stack of return addresses for return function tracing: */ -struct ftrace_ret_stack *ret_stack; +unsigned long *ret_stack;/* Timestamp for last schedule: */ unsigned long long ftrace_timestamp;
kernel/trace/fgraph.c +70 -54
307 lines not on this page · raw message
Masami Hiramatsu (Google)
maintainerPATCH v3 5/33 fgraph: Use BUILD_BUG_ON() to make sure we have structures divisible by long
In reply to Masami Hiramatsu (Google)From: Steven Rostedt (VMware) <[email protected]>
Instead of using "ALIGN()", use BUILD_BUG_ON() as the structures should always be divisible by sizeof(long).
--- kernel/trace/fgraph.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-)
kernel/trace/fgraph.c +6 -3
@@ -26,10 +26,9 @@#endif #define FGRAPH_RET_SIZE sizeof(struct ftrace_ret_stack) -#define FGRAPH_RET_INDEX (ALIGN(FGRAPH_RET_SIZE, sizeof(long)) / sizeof(long)) +#define FGRAPH_RET_INDEX (FGRAPH_RET_SIZE / sizeof(long))#define SHADOW_STACK_SIZE (PAGE_SIZE) -#define SHADOW_STACK_INDEX \ - (ALIGN(SHADOW_STACK_SIZE, sizeof(long)) / sizeof(long)) +#define SHADOW_STACK_INDEX (SHADOW_STACK_SIZE / sizeof(long))/* Leave on a buffer at the end */ #define SHADOW_STACK_MAX_INDEX (SHADOW_STACK_INDEX - FGRAPH_RET_INDEX)
@@ -91,6 +90,8 @@ ftrace_push_return_trace(unsigned long ret, unsigned long func,if (!current->ret_stack)return -EBUSY ; +BUILD_BUG_ON (SHADOW_STACK_SIZE %sizeof (long )); +/* * We must make sure the ret_stack is tested before we read * anything else.
@@ -325,6 +326,8 @@ ftrace_graph_get_ret_stack(struct task_struct *task, int idx) {int index = task->curr_ret_stack; +BUILD_BUG_ON (FGRAPH_RET_SIZE %sizeof (long )); + index -=FGRAPH_RET_INDEX * (idx +1 );if (index <0 )return NULL ;
Masami Hiramatsu (Google)
maintainerPATCH v3 6/33 function_graph: Add an array structure that will allow multiple callbacks
In reply to Masami Hiramatsu (Google)From: Steven Rostedt (VMware) <[email protected]>
Add an array structure that will eventually allow the function graph tracer to have up to 16 simultaneous callbacks attached. It's an array of 16 fgraph_ops pointers, that is assigned when one is registered. On entry of a function the entry of the first item in the array is called, and if it returns zero, then the callback returns non zero if it wants the return callback to be called on exit of the function.
The array will simplify the process of having more than one callback attached to the same function, as its index into the array can be stored on the shadow stack. We need to only save the index, because this will allow the fgraph_ops to be freed before the function returns (which may happen if the function call schedule for a long time).
---
Changes in v2:
- Remove unneeded brace.
--- kernel/trace/fgraph.c | 114 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 81 insertions(+), 33 deletions(-)
kernel/trace/fgraph.c +81 -33
@@ -39,6 +39,11 @@DEFINE_STATIC_KEY_FALSE (kill_ftrace_graph);int ftrace_graph_active; +static int fgraph_array_cnt; +#define FGRAPH_ARRAY_SIZE 16 + +static struct fgraph_ops *fgraph_array[FGRAPH_ARRAY_SIZE ]; +/* Both enabled by default (can be cleared by function_graph tracer flags */ static bool fgraph_sleep_time = true;
@@ -62,6 +67,20 @@ int __weak ftrace_disable_ftrace_graph_caller(void) }#endif +int ftrace_graph_entry_stub (struct ftrace_graph_ent *trace) +{ +return 0 ; +} + +static void ftrace_graph_ret_stub (struct ftrace_graph_ret *trace) +{ +} + +static struct fgraph_ops fgraph_stub = { + .entryfunc = ftrace_graph_entry_stub, + .retfunc = ftrace_graph_ret_stub, +}; +/** * ftrace_graph_stop - set to permanently disable function graph tracing *
@@ -159,7 +178,7 @@ int function_graph_enter(unsigned long ret, unsigned long func, goto out;/* Only trace if the calling function expects to */ -if (!ftrace_graph_entry (&trace)) +if (!fgraph_array[0 ]->entryfunc (&trace)) goto out_ret;return 0 ;
@@ -274,7 +293,7 @@ static unsigned long __ftrace_return_to_handler(struct fgraph_ret_regs *ret_regs trace.retval =fgraph_ret_regs_return_value (ret_regs);#endif trace.rettime =trace_clock_local (); -ftrace_graph_return (&trace); + fgraph_array[0 ]->retfunc (&trace);/* * The ftrace_graph_return() may still access the current * ret_stack structure, we need to make sure the update of
@@ -410,11 +429,6 @@ void ftrace_graph_sleep_time_control(bool enable) fgraph_sleep_time = enable; } -int ftrace_graph_entry_stub (struct ftrace_graph_ent *trace) -{ -return 0 ; -} -/* * Simply points to ftrace_stub, but with the proper protocol. * Defined by the linker script in linux/vmlinux.lds.h
@@ -652,37 +666,54 @@ static int start_graph_tracing(void)int register_ftrace_graph (struct fgraph_ops *gops) {int ret =0 ; +int i;mutex_lock (&ftrace_lock); -/* we currently allow only one tracer registered at a time */ -if (ftrace_graph_active) { +if (!fgraph_array[0 ]) { +/* The array must always have real data on it */ +for (i =0 ; i <FGRAPH_ARRAY_SIZE ; i++) + fgraph_array[i] = &fgraph_stub; + } + +/* Look for an available spot */ +for (i =0 ; i <FGRAPH_ARRAY_SIZE ; i++) { +if (fgraph_array[i] == &fgraph_stub) +break ; + } +if (i >=FGRAPH_ARRAY_SIZE ) { ret = -EBUSY ; goto out; } -register_pm_notifier (&ftrace_suspend_notifier); + fgraph_array[i] = gops; +if (i +1 > fgraph_array_cnt) + fgraph_array_cnt = i +1 ; ftrace_graph_active++; - ret =start_graph_tracing (); -if (ret) { - ftrace_graph_active--; - goto out; - } - ftrace_graph_return = gops->retfunc; +if (ftrace_graph_active ==1 ) { +register_pm_notifier (&ftrace_suspend_notifier); + ret =start_graph_tracing (); +if (ret) { + ftrace_graph_active--; + goto out; + } + + ftrace_graph_return = gops->retfunc; -/* -* Update the indirect function to the entryfunc, and the -* function that gets called to the entry_test first. Then -* call the update fgraph entry function to determine if -* the entryfunc should be called directly or not. -*/ - __ftrace_graph_entry = gops->entryfunc; - ftrace_graph_entry = ftrace_graph_entry_test; -update_function_graph_func (); +/* +* Update the indirect function to the entryfunc, and the +* function that gets called to the entry_test first. Then +* call the update fgraph entry function to determine if +* the entryfunc should be called directly or not. +*/ + __ftrace_graph_entry = gops->entryfunc; + ftrace_graph_entry = ftrace_graph_entry_test; +update_function_graph_func (); - ret =ftrace_startup (&graph_ops,FTRACE_START_FUNC_RET ); + ret =ftrace_startup (&graph_ops,FTRACE_START_FUNC_RET ); + } out:mutex_unlock (&ftrace_lock);return ret;
@@ -690,19 +721,36 @@ int register_ftrace_graph(struct fgraph_ops *gops)void unregister_ftrace_graph (struct fgraph_ops *gops) { +int i; +mutex_lock (&ftrace_lock);if (unlikely (!ftrace_graph_active)) goto out; - ftrace_graph_active--; - ftrace_graph_return = ftrace_stub_graph; - ftrace_graph_entry = ftrace_graph_entry_stub; - __ftrace_graph_entry = ftrace_graph_entry_stub; -ftrace_shutdown (&graph_ops,FTRACE_STOP_FUNC_RET ); -unregister_pm_notifier (&ftrace_suspend_notifier); -unregister_trace_sched_switch (ftrace_graph_probe_sched_switch,NULL ); +for (i =0 ; i < fgraph_array_cnt; i++) +if (gops == fgraph_array[i]) +break ; +if (i >= fgraph_array_cnt) + goto out; + fgraph_array[i] = &fgraph_stub; +if (i +1 == fgraph_array_cnt) { +for (; i >=0 ; i--) +if (fgraph_array[i] != &fgraph_stub) +break ; + fgraph_array_cnt = i +1 ; + } + + ftrace_graph_active--; +if (!ftrace_graph_active) { + ftrace_graph_return = ftrace_stub_graph; + ftrace_graph_entry = ftrace_graph_entry_stub; + __ftrace_graph_entry = ftrace_graph_entry_stub; +ftrace_shutdown (&graph_ops,FTRACE_STOP_FUNC_RET ); +unregister_pm_notifier (&ftrace_suspend_notifier); +unregister_trace_sched_switch (ftrace_graph_probe_sched_switch,NULL ); + } out:mutex_unlock (&ftrace_lock); }
Masami Hiramatsu (Google)
maintainerPATCH v3 7/33 function_graph: Allow multiple users to attach to function graph
In reply to Masami Hiramatsu (Google)From: Steven Rostedt (VMware) <[email protected]>
Allow for multiple users to attach to function graph tracer at the same time. Only 16 simultaneous users can attach to the tracer. This is because there's an array that stores the pointers to the attached fgraph_ops. When a function being traced is entered, each of the ftrace_ops entryfunc is called and if it returns non zero, its index into the array will be added to the shadow stack.
On exit of the function being traced, the shadow stack will contain the indexes of the ftrace_ops on the array that want their retfunc to be called.
Because a function may sleep for a long time (if a task sleeps itself), the return of the function may be literally days later. If the ftrace_ops is removed, its place on the array is replaced with a ftrace_ops that contains the stub functions and that will be called when the function finally returns.
If another ftrace_ops is added that happens to get the same index into the array, its return function may be called. But that's actually the way things current work with the old function graph tracer. If one tracer is removed and another is added, the new one will get the return calls of the function traced by the previous one, thus this is not a regression. This can be fixed by adding a counter to each time the array item is updated and save that on the shadow stack as well, such that it won't be called if the index saved does not match the index on the array.
Note, being able to filter functions when both are called is not completely handled yet, but that shouldn't be too hard to manage.
---
Changes in v2:
- Check return value of the ftrace_pop_return_trace() instead of 'ret' since 'ret' is set to the address of panic().
- Fix typo and make lines shorter than 76 chars in description.
--- kernel/trace/fgraph.c | 332 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 280 insertions(+), 52 deletions(-)
kernel/trace/fgraph.c +280 -52
@@ -27,23 +27,144 @@#define FGRAPH_RET_SIZE sizeof(struct ftrace_ret_stack)#define FGRAPH_RET_INDEX (FGRAPH_RET_SIZE / sizeof(long)) + +/* +* On entry to a function (via function_graph_enter()), a new ftrace_ret_stack +* is allocated on the task's ret_stack, then each fgraph_ops on the +* fgraph_array[]'s entryfunc is called and if that returns non-zero, the +* index into the fgraph_array[] for that fgraph_ops is added to the ret_stack. +* As the associated ftrace_ret_stack saved for those fgraph_ops needs to +* be found, the index to it is also added to the ret_stack along with the +* index of the fgraph_array[] to each fgraph_ops that needs their retfunc +* called. +* +* The top of the ret_stack (when not empty) will always have a reference +* to the last ftrace_ret_stack saved. All references to the +* ftrace_ret_stack has the format of: +* +* bits: 0 - 13 Index in words from the previous ftrace_ret_stack +* bits: 14 - 15 Type of storage +* 0 - reserved +* 1 - fgraph_array index +* For fgraph_array_index: +* bits: 16 - 23 The fgraph_ops fgraph_array index +* +* That is, at the end of function_graph_enter, if the first and forth +* fgraph_ops on the fgraph_array[] (index 0 and 3) needs their retfunc called +* on the return of the function being traced, this is what will be on the +* task's shadow ret_stack: (the stack grows upward) +* +* | | <- task->curr_ret_stack +* +----------------------------------+ +* | (3 << FGRAPH_ARRAY_SHIFT)|(2) | ( 3 for index of fourth fgraph_ops) +* +----------------------------------+ +* | (0 << FGRAPH_ARRAY_SHIFT)|(1) | ( 0 for index of first fgraph_ops) +* +----------------------------------+ +* | struct ftrace_ret_stack | +* | (stores the saved ret pointer) | +* +----------------------------------+ +* | (X) | (N) | ( N words away from previous ret_stack) +* | | +* +* If a backtrace is required, and the real return pointer needs to be +* fetched, then it looks at the task's curr_ret_stack index, if it +* is greater than zero, it would subtact one, and then mask the value +* on the ret_stack by FGRAPH_RET_INDEX_MASK and subtract FGRAPH_RET_INDEX +* from that, to get the index of the ftrace_ret_stack structure stored +* on the shadow stack. +*/ + +#define FGRAPH_RET_INDEX_SIZE 14 +#define FGRAPH_RET_INDEX_MASK ((1 << FGRAPH_RET_INDEX_SIZE) - 1) + + +#define FGRAPH_TYPE_SIZE 2 +#define FGRAPH_TYPE_MASK ((1 << FGRAPH_TYPE_SIZE) - 1) +#define FGRAPH_TYPE_SHIFT FGRAPH_RET_INDEX_SIZE + +enum { +FGRAPH_TYPE_RESERVED =0 , +FGRAPH_TYPE_ARRAY =1 , +}; + +#define FGRAPH_ARRAY_SIZE 16 +#define FGRAPH_ARRAY_MASK ((1 << FGRAPH_ARRAY_SIZE) - 1) +#define FGRAPH_ARRAY_SHIFT (FGRAPH_TYPE_SHIFT + FGRAPH_TYPE_SIZE) + +/* Currently the max stack index can't be more than register callers */ +#define FGRAPH_MAX_INDEX FGRAPH_ARRAY_SIZE + +#define FGRAPH_FRAME_SIZE (FGRAPH_RET_SIZE + FGRAPH_ARRAY_SIZE * (sizeof(long))) +#define FGRAPH_FRAME_INDEX (ALIGN(FGRAPH_FRAME_SIZE, \ + sizeof(long)) / sizeof(long))#define SHADOW_STACK_SIZE (PAGE_SIZE)#define SHADOW_STACK_INDEX (SHADOW_STACK_SIZE / sizeof(long))/* Leave on a buffer at the end */ -#define SHADOW_STACK_MAX_INDEX (SHADOW_STACK_INDEX - FGRAPH_RET_INDEX) +#define SHADOW_STACK_MAX_INDEX (SHADOW_STACK_INDEX - (FGRAPH_RET_INDEX + 1))#define RET_STACK (t, index) ((struct ftrace_ret_stack *)(&(t)->ret_stack[index])) -#define RET_STACK_INC (c) ({ c += FGRAPH_RET_INDEX; }) -#define RET_STACK_DEC (c) ({ c -= FGRAPH_RET_INDEX; })DEFINE_STATIC_KEY_FALSE (kill_ftrace_graph);int ftrace_graph_active;static int fgraph_array_cnt; -#define FGRAPH_ARRAY_SIZE 16static struct fgraph_ops *fgraph_array[FGRAPH_ARRAY_SIZE ]; +static inline int get_ret_stack_index (struct task_struct *t,int offset) +{ +return current->ret_stack[offset] &FGRAPH_RET_INDEX_MASK ; +} + +static inline int get_fgraph_type (struct task_struct *t,int offset) +{ +return (current->ret_stack[offset] >>FGRAPH_TYPE_SHIFT ) & +FGRAPH_TYPE_MASK ; +} + +static inline int get_fgraph_array (struct task_struct *t,int offset) +{ +return (current->ret_stack[offset] >>FGRAPH_ARRAY_SHIFT ) & +FGRAPH_ARRAY_MASK ; +} + +/* +* @offset: The index into @t->ret_stack to find the ret_stack entry +* @index: Where to place the index into @t->ret_stack of that entry +* +* Calling this with: +* +* offset = task->curr_ret_stack; +* do { +* ret_stack = get_ret_stack(task, offset, &offset); +* } while (ret_stack); +* +* Will iterate through all the ret_stack entries from curr_ret_stack +* down to the first one. +*/ +static inline struct ftrace_ret_stack * +get_ret_stack (struct task_struct *t,int offset,int *index) +{ +int idx; + +BUILD_BUG_ON (FGRAPH_RET_SIZE %sizeof (long )); + +if (offset <=0 ) +return NULL ; + + idx =get_ret_stack_index (t, offset -1 ); + +if (idx <=0 || idx >FGRAPH_MAX_INDEX ) +return NULL ; + + offset -= idx +FGRAPH_RET_INDEX ; +if (offset <0 ) +return NULL ; + + *index = offset; +return RET_STACK (t, offset); +} +/* Both enabled by default (can be cleared by function_graph tracer flags */ static bool fgraph_sleep_time = true;
@@ -126,9 +247,34 @@ ftrace_push_return_trace(unsigned long ret, unsigned long func, calltime =trace_clock_local (); index = current->curr_ret_stack; -RET_STACK_INC (current->curr_ret_stack); +/* ret offset = 1 ; type = reserved */ + current->ret_stack[index +FGRAPH_RET_INDEX ] =1 ; ret_stack =RET_STACK (current, index); + ret_stack->ret = ret; +/* +* The unwinders expect curr_ret_stack to point to either zero +* or an index where to find the next ret_stack. Even though the +* ret stack might be bogus, we want to write the ret and the +* index to find the ret_stack before we increment the stack point. +* If an interrupt comes in now before we increment the curr_ret_stack +* it may blow away what we wrote. But that's fine, because the +* index will still be correct (even though the 'ret' won't be). +* What we worry about is the index being correct after we increment +* the curr_ret_stack and before we update that index, as if an +* interrupt comes in and does an unwind stack dump, it will need +* at least a correct index! +*/ barrier (); + current->curr_ret_stack +=FGRAPH_RET_INDEX +1 ; +/* +* This next barrier is to ensure that an interrupt coming in +* will not corrupt what we are about to write. +*/ +barrier (); + +/* Still keep it reserved even if an interrupt came in */ + current->ret_stack[index +FGRAPH_RET_INDEX ] =1 ; + ret_stack->ret = ret; ret_stack->func = func; ret_stack->calltime = calltime;
@@ -159,6 +305,12 @@ int function_graph_enter(unsigned long ret, unsigned long func, unsigned long frame_pointer, unsigned long *retp) { struct ftrace_graph_ent trace; +int offset; +int start; +int type; +int val; +int cnt =0 ; +int i;#ifndef CONFIG_HAVE_DYNAMIC_FTRACE_WITH_ARGS /*
@@ -177,38 +329,87 @@ int function_graph_enter(unsigned long ret, unsigned long func,if (ftrace_push_return_trace (ret, func, frame_pointer, retp)) goto out; -/* Only trace if the calling function expects to */ -if (!fgraph_array[0 ]->entryfunc (&trace)) +/* Use start for the distance to ret_stack (skipping over reserve) */ + start = offset = current->curr_ret_stack -2 ; + +for (i =0 ; i < fgraph_array_cnt; i++) { +struct fgraph_ops *gops = fgraph_array[i]; + +if (gops == &fgraph_stub) +continue ; + +if ((offset == start) && + (current->curr_ret_stack >=SHADOW_STACK_INDEX -1 )) { +atomic_inc (¤t->trace_overrun); +break ; + } +if (fgraph_array[i]->entryfunc (&trace)) { + offset = current->curr_ret_stack; +/* Check the top level stored word */ + type =get_fgraph_type (current, offset -1 ); + + val = (i <<FGRAPH_ARRAY_SHIFT ) | + (FGRAPH_TYPE_ARRAY <<FGRAPH_TYPE_SHIFT ) | + ((offset - start) -1 ); + +/* We can reuse the top word if it is reserved */ +if (type ==FGRAPH_TYPE_RESERVED ) { + current->ret_stack[offset -1 ] = val; + cnt++; +continue ; + } + val++; + + current->ret_stack[offset] = val; +/* +* Write the value before we increment, so that +* if an interrupt comes in after we increment +* it will still see the value and skip over +* this. +*/ +barrier (); + current->curr_ret_stack++; +/* +* Have to write again, in case an interrupt +* came in before the increment and after we +* wrote the value. +*/ +barrier (); + current->ret_stack[offset] = val; + cnt++; + } + } + +if (!cnt) goto out_ret;return 0 ; out_ret: -RET_STACK_DEC (current->curr_ret_stack); + current->curr_ret_stack -=FGRAPH_RET_INDEX +1 ; out: current->curr_ret_depth--;return -EBUSY ; }/* Retrieve a function return address to the trace stack on thread info.*/ -static void +static struct ftrace_ret_stack *ftrace_pop_return_trace (struct ftrace_graph_ret *trace,unsigned long *ret,unsigned long frame_pointer) {struct ftrace_ret_stack *ret_stack;int index; - index = current->curr_ret_stack; -RET_STACK_DEC (index); + ret_stack =get_ret_stack (current, current->curr_ret_stack, &index); -if (unlikely (index <0 || index >SHADOW_STACK_MAX_INDEX )) { +if (unlikely (!ret_stack)) {ftrace_graph_stop (); -WARN_ON (1 ); +WARN (1 ,"Bad function graph ret_stack pointer: %d" , + current->curr_ret_stack);/* Might as well panic, otherwise we have no where to go */ *ret = (unsigned long )panic; -return ; +return NULL ; } - ret_stack =RET_STACK (current, index);#ifdef HAVE_FUNCTION_GRAPH_FP_TEST /* * The arch may choose to record the frame pointer used
@@ -228,12 +429,12 @@ ftrace_pop_return_trace(struct ftrace_graph_ret *trace, unsigned long *ret,ftrace_graph_stop ();WARN (1 ,"Bad frame pointer: expected %lx, received %lx\n" " from func %ps return to %lx\n" , - current->ret_stack[index].fp, + ret_stack->fp, frame_pointer, (void *)ret_stack->func, ret_stack->ret); *ret = (unsigned long )panic; - return; + return NULL; }#endif
@@ -241,13 +442,15 @@ ftrace_pop_return_trace(struct ftrace_graph_ret *trace, unsigned long *ret, trace->func = ret_stack->func; trace->calltime = ret_stack->calltime; trace->overrun =atomic_read (¤t->trace_overrun); - trace->depth = current->curr_ret_depth--; + trace->depth = current->curr_ret_depth;/* * We still want to trace interrupts coming in if * max_depth is set to 1. Make sure the decrement is * seen before ftrace_graph_return. */ barrier (); + +return ret_stack; }/*
@@ -285,30 +488,47 @@ struct fgraph_ret_regs;static unsigned long __ftrace_return_to_handler (struct fgraph_ret_regs *ret_regs,unsigned long frame_pointer) { +struct ftrace_ret_stack *ret_stack;struct ftrace_graph_ret trace;unsigned long ret; +int offset; +int index; +int idx; +int i; + + ret_stack =ftrace_pop_return_trace (&trace, &ret, frame_pointer); + +if (unlikely (!ret_stack)) { +ftrace_graph_stop (); +WARN_ON (1 ); +/* Might as well panic. What else to do? */ +return (unsigned long )panic; + } -ftrace_pop_return_trace (&trace, &ret, frame_pointer); + trace.rettime =trace_clock_local ();#ifdef CONFIG_FUNCTION_GRAPH_RETVAL trace.retval =fgraph_ret_regs_return_value (ret_regs);#endif - trace.rettime =trace_clock_local (); - fgraph_array[0 ]->retfunc (&trace); + + offset = current->curr_ret_stack -1 ; + index =get_ret_stack_index (current, offset); + +/* index has to be at least one! Optimize for it */ + i =0 ; +do { + idx =get_fgraph_array (current, offset - i); + fgraph_array[idx]->retfunc (&trace); + i++; + }while (i < index); +/* * The ftrace_graph_return() may still access the current * ret_stack structure, we need to make sure the update of * curr_ret_stack is after that. */ barrier (); -RET_STACK_DEC (current->curr_ret_stack); - -if (unlikely (!ret)) { -ftrace_graph_stop (); -WARN_ON (1 ); -/* Might as well panic. What else to do? */ - ret = (unsigned long )panic; - } - + current->curr_ret_stack -= index +FGRAPH_RET_INDEX ; + current->curr_ret_depth--;return ret; }
@@ -343,15 +563,17 @@ unsigned long ftrace_return_to_handler(unsigned long frame_pointer)struct ftrace_ret_stack *ftrace_graph_get_ret_stack (struct task_struct *task,int idx) { +struct ftrace_ret_stack *ret_stack =NULL ;int index = task->curr_ret_stack; -BUILD_BUG_ON (FGRAPH_RET_SIZE %sizeof (long )); - - index -=FGRAPH_RET_INDEX * (idx +1 );if (index <0 )return NULL ; -return RET_STACK (task, index); +do { + ret_stack =get_ret_stack (task, index, &index); + }while (ret_stack && --idx >=0 ); + +return ret_stack; }/**
@@ -374,16 +596,15 @@ unsigned long ftrace_graph_ret_addr(struct task_struct *task, int *idx, unsigned long ret, unsigned long *retp) { struct ftrace_ret_stack *ret_stack; -int index = task->curr_ret_stack; -int i; +int i = task->curr_ret_stack;if (ret != (unsigned long )dereference_kernel_function_descriptor (return_to_handler))return ret; -RET_STACK_DEC (index); - -for (i = index; i >=0 ;RET_STACK_DEC (i)) { - ret_stack =RET_STACK (task, i); +while (i >0 ) { + ret_stack =get_ret_stack (current, i, &i); +if (!ret_stack) +break ;if (ret_stack->retp == retp)return ret_stack->ret; }
@@ -394,21 +615,26 @@ unsigned long ftrace_graph_ret_addr(struct task_struct *task, int *idx,unsigned long ftrace_graph_ret_addr (struct task_struct *task,int *idx,unsigned long ret,unsigned long *retp) { -int task_idx; +struct ftrace_ret_stack *ret_stack; +int task_idx = task->curr_ret_stack; +int i;if (ret != (unsigned long )dereference_kernel_function_descriptor (return_to_handler))return ret; - task_idx = task->curr_ret_stack; -RET_STACK_DEC (task_idx); - -if (!task->ret_stack || task_idx < *idx) +if (!idx)return ret; - task_idx -= *idx; -RET_STACK_INC (*idx); + i = *idx; +do { + ret_stack =get_ret_stack (task, task_idx, &task_idx); + i--; + }while (i >=0 && ret_stack); + +if (ret_stack) +return ret_stack->ret; -return RET_STACK (task, task_idx); +return ret; }#endif /* HAVE_FUNCTION_GRAPH_RET_ADDR_PTR */
@@ -514,10 +740,10 @@ ftrace_graph_probe_sched_switch(void *ignore, bool preempt,*/ timestamp -= next->ftrace_timestamp; -for (index = next->curr_ret_stack -FGRAPH_RET_INDEX ; index >=0 ; ) { - ret_stack =RET_STACK (next, index); - ret_stack->calltime += timestamp; - index -=FGRAPH_RET_INDEX ; +for (index = next->curr_ret_stack; index >0 ; ) { + ret_stack =get_ret_stack (next, index, &index); +if (ret_stack) + ret_stack->calltime += timestamp; } }
@@ -568,6 +794,8 @@ graph_init_task(struct task_struct *t, unsigned long *ret_stack) {atomic_set (&t->trace_overrun,0 ); t->ftrace_timestamp =0 ; + t->curr_ret_stack =0 ; + t->curr_ret_depth =-1 ;/* make curr_ret_stack visible before we add the ret_stack */ smp_wmb (); t->ret_stack = ret_stack;
Masami Hiramatsu (Google)
maintainerPATCH v3 8/33 function_graph: Remove logic around ftrace_graph_entry and return
In reply to Masami Hiramatsu (Google)From: Steven Rostedt (VMware) <[email protected]>
The function pointers ftrace_graph_entry and ftrace_graph_return are no longer called via the function_graph tracer. Instead, an array structure is now used that will allow for multiple users of the function_graph infrastructure. The variables are still used by the architecture code for non dynamic ftrace configs, where a test is made against them to see if they point to the default stub function or not. This is how the static function tracing knows to call into the function graph tracer infrastructure or not.
Two new stub functions are made. entry_run() and return_run(). The ftrace_graph_entry and ftrace_graph_return are set to them respectively when the function graph tracer is enabled, and this will trigger the architecture specific function graph code to be executed.
This also requires checking the global_ops hash for all calls into the function_graph tracer.
---
Changes in v2:
- Fix typo and make lines shorter than 76 chars in the description.
- Remove unneeded return from return_run() function.
--- kernel/trace/fgraph.c | 71 +++++++++++----------------------------- kernel/trace/ftrace.c | 2 - kernel/trace/ftrace_internal.h | 2 - 3 files changed, 19 insertions(+), 56 deletions(-)
kernel/trace/fgraph.c +19 -52
@@ -128,6 +128,17 @@ static inline int get_fgraph_array(struct task_struct *t, int offset)FGRAPH_ARRAY_MASK ; } +/* ftrace_graph_entry set to this to tell some archs to run function graph */ +static int entry_run (struct ftrace_graph_ent *trace) +{ +return 0 ; +} + +/* ftrace_graph_return set to this to tell some archs to run function graph */ +static void return_run (struct ftrace_graph_ret *trace) +{ +} +/* * @offset: The index into @t->ret_stack to find the ret_stack entry * @index: Where to place the index into @t->ret_stack of that entry
@@ -323,6 +334,10 @@ int function_graph_enter(unsigned long ret, unsigned long func, ftrace_find_rec_direct(ret - MCOUNT_INSN_SIZE)) return -EBUSY;#endif + +if (!ftrace_ops_test (&global_ops, func,NULL )) +return -EBUSY ; + trace.func = func; trace.depth = ++current->curr_ret_depth;
@@ -664,7 +679,6 @@ extern void ftrace_stub_graph(struct ftrace_graph_ret *);/* The callbacks that hook a function */ trace_func_graph_ret_t ftrace_graph_return = ftrace_stub_graph;trace_func_graph_ent_t ftrace_graph_entry = ftrace_graph_entry_stub; -static trace_func_graph_ent_t __ftrace_graph_entry = ftrace_graph_entry_stub;/* Try to assign a return stack array on FTRACE_RETSTACK_ALLOC_SIZE tasks. */ static int alloc_retstack_tasklist (unsigned long **ret_stack_list)
@@ -747,46 +761,6 @@ ftrace_graph_probe_sched_switch(void *ignore, bool preempt, } } -static int ftrace_graph_entry_test (struct ftrace_graph_ent *trace) -{ -if (!ftrace_ops_test (&global_ops, trace->func,NULL )) -return 0 ; -return __ftrace_graph_entry (trace); -} - -/* -* The function graph tracer should only trace the functions defined -* by set_ftrace_filter and set_ftrace_notrace. If another function -* tracer ops is registered, the graph tracer requires testing the -* function against the global ops, and not just trace any function -* that any ftrace_ops registered. -*/ -void update_function_graph_func (void ) -{ -struct ftrace_ops *op; -bool do_test = false; - -/* -* The graph and global ops share the same set of functions -* to test. If any other ops is on the list, then -* the graph tracing needs to test if its the function -* it should call. -*/ -do_for_each_ftrace_op (op, ftrace_ops_list) { -if (op != &global_ops && op != &graph_ops && - op != &ftrace_list_end) { - do_test = true; -/* in double loop, break out with goto */ - goto out; - } - }while_for_each_ftrace_op (op); - out: -if (do_test) - ftrace_graph_entry = ftrace_graph_entry_test; -else - ftrace_graph_entry = __ftrace_graph_entry; -} - static DEFINE_PER_CPU(unsigned long *, idle_ret_stack); static void
@@ -927,18 +901,12 @@ int register_ftrace_graph(struct fgraph_ops *gops) ftrace_graph_active--; goto out; } - - ftrace_graph_return = gops->retfunc; -/* -* Update the indirect function to the entryfunc, and the -* function that gets called to the entry_test first. Then -* call the update fgraph entry function to determine if -* the entryfunc should be called directly or not. +* Some archs just test to see if these are not +* the default function */ - __ftrace_graph_entry = gops->entryfunc; - ftrace_graph_entry = ftrace_graph_entry_test; -update_function_graph_func (); + ftrace_graph_return = return_run; + ftrace_graph_entry = entry_run; ret =ftrace_startup (&graph_ops,FTRACE_START_FUNC_RET ); }
@@ -974,7 +942,6 @@ void unregister_ftrace_graph(struct fgraph_ops *gops)if (!ftrace_graph_active) { ftrace_graph_return = ftrace_stub_graph; ftrace_graph_entry = ftrace_graph_entry_stub; - __ftrace_graph_entry = ftrace_graph_entry_stub;ftrace_shutdown (&graph_ops,FTRACE_STOP_FUNC_RET );unregister_pm_notifier (&ftrace_suspend_notifier);unregister_trace_sched_switch (ftrace_graph_probe_sched_switch,NULL );
kernel/trace/ftrace.c +0 -2
@@ -235,8 +235,6 @@ static void update_ftrace_function(void) func = ftrace_ops_list_func; } -update_function_graph_func (); -/* If there's no change, then do nothing more here */ if (ftrace_trace_function == func)return ;
kernel/trace/ftrace_internal.h +0 -2
@@ -42,10 +42,8 @@ ftrace_ops_test(struct ftrace_ops *ops, unsigned long ip, void *regs)#ifdef CONFIG_FUNCTION_GRAPH_TRACER extern int ftrace_graph_active; -void update_function_graph_func (void );#else /* !CONFIG_FUNCTION_GRAPH_TRACER */ # define ftrace_graph_active 0 -static inline void update_function_graph_func (void ) { }#endif /* CONFIG_FUNCTION_GRAPH_TRACER */ #else /* !CONFIG_FUNCTION_TRACER */
Masami Hiramatsu (Google)
maintainerPATCH v3 9/33 ftrace/function_graph: Pass fgraph_ops to function graph callbacks
In reply to Masami Hiramatsu (Google)From: Steven Rostedt (VMware) <[email protected]>
Pass the fgraph_ops structure to the function graph callbacks. This will allow callbacks to add a descriptor to a fgraph_ops private field that wil be added in the future and use it for the callbacks. This will be useful when more than one callback can be registered to the function graph tracer.
---
Changes in v2:
- cleanup to set argument name on function prototype.
--- include/linux/ftrace.h | 10 +++++++--- kernel/trace/fgraph.c | 16 +++++++++------- kernel/trace/ftrace.c | 6 ++++-- kernel/trace/trace.h | 4 ++-- kernel/trace/trace_functions_graph.c | 11 +++++++---- kernel/trace/trace_irqsoff.c | 6 ++++-- kernel/trace/trace_sched_wakeup.c | 6 ++++-- kernel/trace/trace_selftest.c | 5 +++-- 8 files changed, 40 insertions(+), 24 deletions(-)
include/linux/ftrace.h +7 -3
@@ -1055,11 +1055,15 @@ struct ftrace_graph_ret {unsigned long long rettime; } __packed; +struct fgraph_ops ; +/* Type of the callback handlers for tracing function graph*/ -typedef void (*trace_func_graph_ret_t )(struct ftrace_graph_ret *);/* return */ -typedef int (*trace_func_graph_ent_t )(struct ftrace_graph_ent *);/* entry */ +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 */ -extern int ftrace_graph_entry_stub (struct ftrace_graph_ent *trace); +extern int ftrace_graph_entry_stub (struct ftrace_graph_ent *trace,struct fgraph_ops *gops);#ifdef CONFIG_FUNCTION_GRAPH_TRACER
kernel/trace/fgraph.c +9 -7
@@ -129,13 +129,13 @@ static inline int get_fgraph_array(struct task_struct *t, 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) +static int entry_run (struct ftrace_graph_ent *trace,struct fgraph_ops *ops) {return 0 ; }/* ftrace_graph_return set to this to tell some archs to run function graph */ -static void return_run (struct ftrace_graph_ret *trace) +static void return_run (struct ftrace_graph_ret *trace,struct fgraph_ops *ops) { }
@@ -199,12 +199,14 @@ int __weak ftrace_disable_ftrace_graph_caller(void) }#endif -int ftrace_graph_entry_stub (struct ftrace_graph_ent *trace) +int ftrace_graph_entry_stub (struct ftrace_graph_ent *trace, +struct fgraph_ops *gops) {return 0 ; } -static void ftrace_graph_ret_stub (struct ftrace_graph_ret *trace) +static void ftrace_graph_ret_stub (struct ftrace_graph_ret *trace, +struct fgraph_ops *gops) { }
@@ -358,7 +360,7 @@ int function_graph_enter(unsigned long ret, unsigned long func,atomic_inc (¤t->trace_overrun);break ; } -if (fgraph_array[i]->entryfunc (&trace)) { +if (fgraph_array[i]->entryfunc (&trace, fgraph_array[i])) { offset = current->curr_ret_stack;/* Check the top level stored word */ type =get_fgraph_type (current, offset -1 );
@@ -532,7 +534,7 @@ static unsigned long __ftrace_return_to_handler(struct fgraph_ret_regs *ret_regs i =0 ;do { idx =get_fgraph_array (current, offset - i); - fgraph_array[idx]->retfunc (&trace); + fgraph_array[idx]->retfunc (&trace, fgraph_array[idx]); i++; }while (i < index);
@@ -674,7 +676,7 @@ void ftrace_graph_sleep_time_control(bool enable)* Simply points to ftrace_stub, but with the proper protocol. * Defined by the linker script in linux/vmlinux.lds.h */ -extern void ftrace_stub_graph (struct ftrace_graph_ret *); +void ftrace_stub_graph (struct ftrace_graph_ret *trace,struct fgraph_ops *gops);/* The callbacks that hook a function */ trace_func_graph_ret_t ftrace_graph_return = ftrace_stub_graph;
kernel/trace/ftrace.c +4 -2
@@ -815,7 +815,8 @@ void ftrace_graph_graph_time_control(bool enable) fgraph_graph_time = enable; } -static int profile_graph_entry (struct ftrace_graph_ent *trace) +static int profile_graph_entry (struct ftrace_graph_ent *trace, +struct fgraph_ops *gops) {struct ftrace_ret_stack *ret_stack;
@@ -832,7 +833,8 @@ static int profile_graph_entry(struct ftrace_graph_ent *trace)return 1 ; } -static void profile_graph_return (struct ftrace_graph_ret *trace) +static void profile_graph_return (struct ftrace_graph_ret *trace, +struct fgraph_ops *gops) {struct ftrace_ret_stack *ret_stack;struct ftrace_profile_stat *stat;
kernel/trace/trace.h +2 -2
@@ -670,8 +670,8 @@ void trace_latency_header(struct seq_file *m);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); -int trace_graph_entry (struct ftrace_graph_ent *trace); +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);void set_graph_array (struct trace_array *tr);void tracing_start_cmdline_record (void );
kernel/trace/trace_functions_graph.c +7 -4
@@ -129,7 +129,8 @@ static inline int ftrace_graph_ignore_irqs(void)return in_hardirq (); } -int trace_graph_entry (struct ftrace_graph_ent *trace) +int trace_graph_entry (struct ftrace_graph_ent *trace, +struct fgraph_ops *gops) {struct trace_array *tr = graph_array;struct trace_array_cpu *data;
@@ -238,7 +239,8 @@ void __trace_graph_return(struct trace_array *tr,trace_buffer_unlock_commit_nostack (buffer, event); } -void trace_graph_return (struct ftrace_graph_ret *trace) +void trace_graph_return (struct ftrace_graph_ret *trace, +struct fgraph_ops *gops) {struct trace_array *tr = graph_array;struct trace_array_cpu *data;
@@ -275,7 +277,8 @@ void set_graph_array(struct trace_array *tr)smp_mb (); } -static void trace_graph_thresh_return (struct ftrace_graph_ret *trace) +static void trace_graph_thresh_return (struct ftrace_graph_ret *trace, +struct fgraph_ops *gops) {ftrace_graph_addr_finish (trace);
@@ -288,7 +291,7 @@ static void trace_graph_thresh_return(struct ftrace_graph_ret *trace) (trace->rettime - trace->calltime < tracing_thresh)) return; else -trace_graph_return (trace ); +trace_graph_return (trace ,gops ); }static struct fgraph_ops funcgraph_thresh_ops = {
kernel/trace/trace_irqsoff.c +4 -2
@@ -175,7 +175,8 @@ static int irqsoff_display_graph(struct trace_array *tr, int set)return start_irqsoff_tracer (irqsoff_trace, set); } -static int irqsoff_graph_entry (struct ftrace_graph_ent *trace) +static int irqsoff_graph_entry (struct ftrace_graph_ent *trace, +struct fgraph_ops *gops) {struct trace_array *tr = irqsoff_trace;struct trace_array_cpu *data;
@@ -205,7 +206,8 @@ static int irqsoff_graph_entry(struct ftrace_graph_ent *trace)return ret; } -static void irqsoff_graph_return (struct ftrace_graph_ret *trace) +static void irqsoff_graph_return (struct ftrace_graph_ret *trace, +struct fgraph_ops *gops) {struct trace_array *tr = irqsoff_trace;struct trace_array_cpu *data;
kernel/trace/trace_sched_wakeup.c +4 -2
@@ -112,7 +112,8 @@ static int wakeup_display_graph(struct trace_array *tr, int set)return start_func_tracer (tr, set); } -static int wakeup_graph_entry (struct ftrace_graph_ent *trace) +static int wakeup_graph_entry (struct ftrace_graph_ent *trace, +struct fgraph_ops *gops) {struct trace_array *tr = wakeup_trace;struct trace_array_cpu *data;
@@ -141,7 +142,8 @@ static int wakeup_graph_entry(struct ftrace_graph_ent *trace)return ret; } -static void wakeup_graph_return (struct ftrace_graph_ret *trace) +static void wakeup_graph_return (struct ftrace_graph_ret *trace, +struct fgraph_ops *gops) {struct trace_array *tr = wakeup_trace;struct trace_array_cpu *data;
kernel/trace/trace_selftest.c +3 -2
@@ -762,7 +762,8 @@ trace_selftest_startup_function(struct tracer *trace, struct trace_array *tr)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) +static int trace_graph_entry_watchdog (struct ftrace_graph_ent *trace, +struct fgraph_ops *gops) {/* This is harmlessly racy, we want to approximately detect a hang */ if (unlikely (++graph_hang_thresh >GRAPH_MAX_FUNC_TEST )) {
@@ -776,7 +777,7 @@ static int trace_graph_entry_watchdog(struct ftrace_graph_ent *trace)return 0 ; } -return trace_graph_entry (trace); +return trace_graph_entry (trace, gops); }static struct fgraph_ops fgraph_ops__initdata = {
Masami Hiramatsu (Google)
maintainerPATCH v3 10/33 ftrace: Allow function_graph tracer to be enabled in instances
In reply to Masami Hiramatsu (Google)From: Steven Rostedt (VMware) <[email protected]>
Now that function graph tracing can handle more than one user, allow it to be enabled in the ftrace instances. Note, the filtering of the functions is still joined by the top level set_ftrace_filter and friends, as well as the graph and nograph files.
---
Changes in v2:
- Fix to remove set_graph_array() completely.
--- include/linux/ftrace.h | 1 + kernel/trace/ftrace.c | 1 + kernel/trace/trace.h | 13 ++++++- kernel/trace/trace_functions.c | 8 ++++ kernel/trace/trace_functions_graph.c | 65 +++++++++++++++++++++------------- kernel/trace/trace_selftest.c | 4 +- 6 files changed, 64 insertions(+), 28 deletions(-)
include/linux/ftrace.h +1 -0
@@ -1070,6 +1070,7 @@ extern int ftrace_graph_entry_stub(struct ftrace_graph_ent *trace, struct fgraphstruct fgraph_ops {trace_func_graph_ent_t entryfunc;trace_func_graph_ret_t retfunc; +void *private; };/*
kernel/trace/ftrace.c +1 -0
@@ -7319,6 +7319,7 @@ __init void ftrace_init_global_array_ops(struct trace_array *tr) tr->ops = &global_ops; tr->ops->private = tr;ftrace_init_trace_array (tr); +init_array_fgraph_ops (tr); }void ftrace_init_array_ops (struct trace_array *tr,ftrace_func_t func)
kernel/trace/trace.h +12 -1
@@ -395,6 +395,9 @@ struct trace_array {struct ftrace_ops *ops;struct trace_pid_list __rcu *function_pids;struct trace_pid_list __rcu *function_no_pids; +#ifdef CONFIG_FUNCTION_GRAPH_TRACER +struct fgraph_ops *gops; +#endif #ifdef CONFIG_DYNAMIC_FTRACE /* All of these are protected by the ftrace_lock */ struct list_head func_probes;
@@ -672,7 +675,6 @@ 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); -void set_graph_array (struct trace_array *tr);void tracing_start_cmdline_record (void );void tracing_stop_cmdline_record (void );
@@ -883,6 +885,9 @@ extern int __trace_graph_entry(struct trace_array *tr,extern void __trace_graph_return (struct trace_array *tr,struct ftrace_graph_ret *trace,unsigned int trace_ctx); +extern void init_array_fgraph_ops (struct trace_array *tr); +extern int allocate_fgraph_ops (struct trace_array *tr); +extern void free_fgraph_ops (struct trace_array *tr);#ifdef CONFIG_DYNAMIC_FTRACE extern struct ftrace_hash __rcu *ftrace_graph_hash;
@@ -995,6 +1000,12 @@ print_graph_function_flags(struct trace_iterator *iter, u32 flags) {return TRACE_TYPE_UNHANDLED ; } +static inline void init_array_fgraph_ops (struct trace_array *tr) { } +static inline int allocate_fgraph_ops (struct trace_array *tr) +{ +return 0 ; +} +static inline void free_fgraph_ops (struct trace_array *tr) { }#endif /* CONFIG_FUNCTION_GRAPH_TRACER */ extern struct list_head ftrace_pids;
kernel/trace/trace_functions.c +8 -0
@@ -80,6 +80,7 @@ void ftrace_free_ftrace_ops(struct trace_array *tr)int ftrace_create_function_files (struct trace_array *tr,struct dentry *parent) { +int ret;/* * The top level array uses the "global_ops", and the files are * created on boot up.
@@ -90,6 +91,12 @@ int ftrace_create_function_files(struct trace_array *tr,if (!tr->ops)return -EINVAL ; + ret =allocate_fgraph_ops (tr); +if (ret) { +kfree (tr->ops); +return ret; + } +ftrace_create_filter_files (tr->ops, parent);return 0 ;
@@ -99,6 +106,7 @@ void ftrace_destroy_function_files(struct trace_array *tr) {ftrace_destroy_filter_files (tr->ops);ftrace_free_ftrace_ops (tr); +free_fgraph_ops (tr); }static ftrace_func_t select_trace_function (u32 flags_val)
kernel/trace/trace_functions_graph.c +40 -25
118 lines not on this page · raw message
kernel/trace/trace_selftest.c +2 -2
@@ -813,7 +813,7 @@ trace_selftest_startup_function_graph(struct tracer *trace,* to detect and recover from possible hangs */ tracing_reset_online_cpus (&tr->array_buffer); -set_graph_array (tr); + fgraph_ops.private = tr; ret =register_ftrace_graph (&fgraph_ops);if (ret) {warn_failed_init_tracer (trace, ret);
@@ -856,7 +856,7 @@ trace_selftest_startup_function_graph(struct tracer *trace,cond_resched ();tracing_reset_online_cpus (&tr->array_buffer); -set_graph_array (tr); + fgraph_ops.private = tr;/* * Some archs *cough*PowerPC*cough* add characters to the
Masami Hiramatsu (Google)
maintainerPATCH v3 11/33 ftrace: Allow ftrace startup flags exist without dynamic ftrace
In reply to Masami Hiramatsu (Google)From: Steven Rostedt (VMware) <[email protected]>
Some of the flags for ftrace_startup() may be exposed even when CONFIG_DYNAMIC_FTRACE is not configured in. This is fine as the difference between dynamic ftrace and static ftrace is done within the internals of ftrace itself. No need to have use cases fail to compile because dynamic ftrace is disabled.
This change is needed to move some of the logic of what is passed to ftrace_startup() out of the parameters of ftrace_startup().
--- include/linux/ftrace.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-)
include/linux/ftrace.h +9 -9
@@ -538,6 +538,15 @@ static inline void stack_tracer_disable(void) { }static inline void stack_tracer_enable (void ) { }#endif +enum { +FTRACE_UPDATE_CALLS = (1 <<0 ), +FTRACE_DISABLE_CALLS = (1 <<1 ), +FTRACE_UPDATE_TRACE_FUNC = (1 <<2 ), +FTRACE_START_FUNC_RET = (1 <<3 ), +FTRACE_STOP_FUNC_RET = (1 <<4 ), +FTRACE_MAY_SLEEP = (1 <<5 ), +}; +#ifdef CONFIG_DYNAMIC_FTRACE void ftrace_arch_code_modify_prepare (void );
@@ -632,15 +641,6 @@ void ftrace_set_global_notrace(unsigned char *buf, int len, int reset);void ftrace_free_filter (struct ftrace_ops *ops);void ftrace_ops_set_global_filter (struct ftrace_ops *ops); -enum { -FTRACE_UPDATE_CALLS = (1 <<0 ), -FTRACE_DISABLE_CALLS = (1 <<1 ), -FTRACE_UPDATE_TRACE_FUNC = (1 <<2 ), -FTRACE_START_FUNC_RET = (1 <<3 ), -FTRACE_STOP_FUNC_RET = (1 <<4 ), -FTRACE_MAY_SLEEP = (1 <<5 ), -}; -/* * The FTRACE_UPDATE_* enum is used to pass information back * from the ftrace_update_record() and ftrace_test_record()
Masami Hiramatsu (Google)
maintainerPATCH v3 12/33 function_graph: Have the instances use their own ftrace_ops for filtering
In reply to Masami Hiramatsu (Google)From: Steven Rostedt (VMware) <[email protected]>
Allow for instances to have their own ftrace_ops part of the fgraph_ops that makes the funtion_graph tracer filter on the set_ftrace_filter file of the instance and not the top instance.
This also change how the function_graph handles multiple instances on the shadow stack. Previously we use ARRAY type entries to record which one is enabled, and this makes it a bitmap of the fgraph_array's indexes. Previous function_graph_enter() expects calling back from prepare_ftrace_return() function which is called back only once if it is enabled. But this introduces different ftrace_ops for each fgraph instance and those are called from ftrace_graph_func() one by one. Thus we can not loop on the fgraph_array(), and need to reuse the ret_stack pushed by the previous instance. Finding the ret_stack is easy because we can check the ret_stack->func. But that is not enough for the self- recursive tail-call case. Thus fgraph uses the bitmap entry to find it is already set (this means that entry is for previous tail call).
---
Changes in v3:
- Pass current fgraph_ops to the new entry handler (function_graph_enter_ops) if fgraph use ftrace.
- Add fgraph_ops::idx in this patch.
- Replace the array type with the bitmap type so that it can record which fgraph is called.
- Fix some helper function to use passed task_struct instead of current.
- Reduce the ret-index size to 1024 words.
- Make the ret-index directly points the ret_stack.
- Fix ftrace_graph_ret_addr() to handle tail-call case correctly.
Changes in v2:
- Use ftrace_graph_func and FTRACE_OPS_GRAPH_STUB instead of ftrace_stub and FTRACE_OPS_FL_STUB for new ftrace based fgraph.
--- arch/arm64/kernel/ftrace.c | 19 ++ arch/x86/kernel/ftrace.c | 19 ++ include/linux/ftrace.h | 7 + kernel/trace/fgraph.c | 364 ++++++++++++++++++++-------------- kernel/trace/ftrace.c | 6 - kernel/trace/trace.h | 16 + kernel/trace/trace_functions.c | 2 kernel/trace/trace_functions_graph.c | 8 + 8 files changed, 277 insertions(+), 164 deletions(-)
arch/arm64/kernel/ftrace.c +18 -1
@@ -481,7 +481,24 @@ void prepare_ftrace_return(unsigned long self_addr, unsigned long *parent,void ftrace_graph_func (unsigned long ip,unsigned long parent_ip,struct ftrace_ops *op,struct ftrace_regs *fregs) { -prepare_ftrace_return (ip, &fregs->lr, fregs->fp); +unsigned long *parent = &fregs->lr; + struct fgraph_ops *gops = container_of(op, struct fgraph_ops, ops); +int bit; + +if (unlikely (ftrace_graph_is_dead ())) +return ; + +if (unlikely (atomic_read (¤t->tracing_graph_pause))) +return ; + + bit =ftrace_test_recursion_trylock (ip, *parent); +if (bit <0 ) +return ; + +if (!function_graph_enter_ops (*parent, ip, fregs->fp, parent, gops)) + *parent = (unsigned long )&return_to_handler; + +ftrace_test_recursion_unlock (bit); }#else /*
arch/x86/kernel/ftrace.c +17 -2
@@ -657,9 +657,24 @@ void ftrace_graph_func(unsigned long ip, unsigned long parent_ip, struct ftrace_ops *op, struct ftrace_regs *fregs) { struct pt_regs *regs = &fregs->regs; -unsigned long *stack = (unsigned long *)kernel_stack_pointer (regs); +unsigned long *parent = (unsigned long *)kernel_stack_pointer (regs); + struct fgraph_ops *gops = container_of(op, struct fgraph_ops, ops); +int bit; + +if (unlikely (ftrace_graph_is_dead ())) +return ; + +if (unlikely (atomic_read (¤t->tracing_graph_pause))) +return ; -prepare_ftrace_return (ip, (unsigned long *)stack,0 ); + bit =ftrace_test_recursion_trylock (ip, *parent); +if (bit <0 ) +return ; + +if (!function_graph_enter_ops (*parent, ip,0 , parent, gops)) + *parent = (unsigned long )&return_to_handler; + +ftrace_test_recursion_unlock (bit); }#endif
include/linux/ftrace.h +7 -0
@@ -1070,7 +1070,9 @@ extern int ftrace_graph_entry_stub(struct ftrace_graph_ent *trace, struct fgraphstruct fgraph_ops {trace_func_graph_ent_t entryfunc;trace_func_graph_ret_t retfunc; +struct ftrace_ops ops;/* for the hash lists */ void *private; +int idx; };/*
@@ -1104,6 +1106,11 @@ extern int function_graph_enter(unsigned long ret, unsigned long func, unsigned long frame_pointer, unsigned long *retp); +extern int +function_graph_enter_ops (unsigned long ret,unsigned long func, +unsigned long frame_pointer,unsigned long *retp, +struct fgraph_ops *gops); +struct ftrace_ret_stack *ftrace_graph_get_ret_stack (struct task_struct *task,int idx);
kernel/trace/fgraph.c +215 -149
617 lines not on this page · raw message
kernel/trace/ftrace.c +4 -2
@@ -3050,6 +3050,8 @@ int ftrace_startup(struct ftrace_ops *ops, int command)if (unlikely (ftrace_disabled))return -ENODEV ; +ftrace_ops_init (ops); + ret =__register_ftrace_function (ops);if (ret)return ret;
@@ -7319,7 +7321,7 @@ __init void ftrace_init_global_array_ops(struct trace_array *tr) tr->ops = &global_ops; tr->ops->private = tr;ftrace_init_trace_array (tr); -init_array_fgraph_ops (tr); +init_array_fgraph_ops (tr, tr->ops); }void ftrace_init_array_ops (struct trace_array *tr,ftrace_func_t func)
@@ -8051,7 +8053,7 @@ static int register_ftrace_function_nolock(struct ftrace_ops *ops)*/ int register_ftrace_function (struct ftrace_ops *ops) { -int ret; +int ret =-1 ;lock_direct_mutex (); ret =prepare_direct_functions_for_ipmodify (ops);
kernel/trace/trace.h +9 -7
@@ -885,8 +885,8 @@ extern int __trace_graph_entry(struct trace_array *tr,extern void __trace_graph_return (struct trace_array *tr,struct ftrace_graph_ret *trace,unsigned int trace_ctx); -extern void init_array_fgraph_ops (struct trace_array *tr); -extern int allocate_fgraph_ops (struct trace_array *tr); +extern void init_array_fgraph_ops (struct trace_array *tr,struct ftrace_ops *ops); +extern int allocate_fgraph_ops (struct trace_array *tr,struct ftrace_ops *ops);extern void free_fgraph_ops (struct trace_array *tr);#ifdef CONFIG_DYNAMIC_FTRACE
@@ -969,6 +969,7 @@ static inline int ftrace_graph_notrace_addr(unsigned long addr)preempt_enable_notrace (); return ret; } +#else static inline int ftrace_graph_addr (struct ftrace_graph_ent *trace) {
@@ -994,18 +995,19 @@ static inline bool ftrace_graph_ignore_func(struct ftrace_graph_ent *trace) (fgraph_max_depth && trace->depth >= fgraph_max_depth); } +void fgraph_init_ops (struct ftrace_ops *dst_ops, +struct ftrace_ops *src_ops); +#else /* CONFIG_FUNCTION_GRAPH_TRACER */ static inline enum print_line_t print_graph_function_flags (struct trace_iterator *iter,u32 flags) {return TRACE_TYPE_UNHANDLED ; } -static inline void init_array_fgraph_ops (struct trace_array *tr) { } -static inline int allocate_fgraph_ops (struct trace_array *tr) -{ -return 0 ; -}static inline void free_fgraph_ops (struct trace_array *tr) { } +/* ftrace_ops may not be defined */ +#define init_array_fgraph_ops (tr, ops) do { } while (0) +#define allocate_fgraph_ops (tr, ops) ({ 0; })#endif /* CONFIG_FUNCTION_GRAPH_TRACER */ extern struct list_head ftrace_pids;
kernel/trace/trace_functions.c +1 -1
@@ -91,7 +91,7 @@ int ftrace_create_function_files(struct trace_array *tr,if (!tr->ops)return -EINVAL ; - ret =allocate_fgraph_ops (tr); + ret =allocate_fgraph_ops (tr, tr->ops);if (ret) {kfree (tr->ops);return ret;
kernel/trace/trace_functions_graph.c +6 -2
@@ -288,7 +288,7 @@ static struct fgraph_ops funcgraph_ops = { .retfunc = &trace_graph_return, }; -int allocate_fgraph_ops (struct trace_array *tr) +int allocate_fgraph_ops (struct trace_array *tr,struct ftrace_ops *ops) {struct fgraph_ops *gops;
@@ -301,6 +301,9 @@ int allocate_fgraph_ops(struct trace_array *tr) tr->gops = gops; gops->private = tr; + +fgraph_init_ops (&gops->ops, ops); +return 0 ; }
@@ -309,10 +312,11 @@ void free_fgraph_ops(struct trace_array *tr)kfree (tr->gops); } -__init void init_array_fgraph_ops (struct trace_array *tr) +__init void init_array_fgraph_ops (struct trace_array *tr,struct ftrace_ops *ops) { tr->gops = &funcgraph_ops; funcgraph_ops.private = tr; +fgraph_init_ops (&tr->gops->ops, ops); }static int graph_trace_init (struct trace_array *tr)
Masami Hiramatsu (Google)
maintainerRe: [PATCH v3 12/33] function_graph: Have the instances use their own ftrace_ops for filtering
Hi,
kernel/trace/fgraph.c ftrace_push_return_trace(unsigned long ret, unsigned long func, · line 257
Quoted 5 lines · On Mon, 27 Nov 2023 22:55:22 +0900 "Masami Hiramatsu (Google)"…
kernel/trace/fgraph.c +1
-0@@ -243,6 +254,27 @@ ftrace_push_return_trace(unsigned long ret, unsigned long func,if (!current->ret_stack)return -EBUSY ; +if (ret == (unsigned long )dereference_kernel_function_descriptor (return_to_handler)) {
I found this condition is not always needed. Actually, this is only needed for the first one. The second or later entry handlers or tail-call case will pass this condition.
kernel/trace/fgraph.c ftrace_push_return_trace(unsigned long ret, unsigned long func, · line 269
Quoted 12 lines
kernel/trace/fgraph.c +12
-0+/* +* In this case, the previous fgraph callback already pushed the +* ret_stack, or @func is called by tail-call. Usual tail-call can +* be detected if ret_stack::func is not @func, but for the self- +* recursive tail-call case needs to check whether the @fgraph_idx +* is already recorded or not. +*/ + ret_stack =get_ret_stack (current, current->curr_ret_stack, &index); +if ((ret_stack && ret_stack->func == func) && + !is_fgraph_index_set (current, index +FGRAPH_RET_INDEX , fgraph_idx)) { +return index +FGRAPH_RET_INDEX ; + }
But without that, I found this part caused a kernel panic while the ftrace_shutdown() when unregistering a current fgraph (which is the last one, see below). But it starts lockdep warning... (BTW, lockdep lock acquire location is replaced by return_to_ftrace and not resolved by ftrace, that is not good, a kind of bug.)
I need to check the bitmap check is really works or not.
/ # cd /sys/kernel/tracing//sys/kernel/tracing # echo function_graph > current_tracer /sys/kernel/tracing # echo nop > current_tracer
[ 21.882512] ------------[ cut here ]------------ [ 21.884837] DEBUG_LOCKS_WARN_ON(1) [ 21.884885] WARNING: CPU: 18 PID: 477 at kernel/locking/lockdep.c:232 __lock_acquire+0x9a4/0xbc0 [ 21.891152] Modules linked in: [ 21.892865] CPU: 18 PID: 477 Comm: sh Tainted: G N 6.6.0+ #27 [ 21.896383] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.2-debian-1.16.2-1 04/01/2014 [ 21.901092] RIP: 0010:__lock_acquire+0x9a4/0xbc0 [ 21.903540] Code: c8 85 c0 0f 84 14 fd ff ff 8b 35 4b 47 b5 01 85 f6 0f 85 06 fd ff ff 48 c7 c6 10 ac 39 82 48 c7 c7 2e b6 36 82 e8 4c 15 f7 ff <0f> 0b 31 c0 4c 8b 5d c8 44 8b 55 d0 e9 ff f7 ff ff e8 c6 86 56 00 [ 21.913175] RSP: 0018:ffffc900005b4ee0 EFLAGS: 00010086 [ 21.915850] RAX: 0000000000000000 RBX: ffff888005843cd8 RCX: 000000053b6d0030 [ 21.919327] RDX: 0000000000000000 RSI: ffffffff82b800c0 RDI: ffffffff8108e4d3 [ 21.922767] RBP: ffffc900005b4f30 R08: 0000000000000001 R09: ffffc900005b4d60 [ 21.926247] R10: 0000000000000018 R11: ffff88807cbc0000 R12: ffff888005843200 [ 21.929714] R13: 7627623276c27547 R14: 0000000000000000 R15: 0000000000000005 [ 21.933174] FS: 0000000000fea3c0(0000) GS:ffff88807d280000(0000) knlGS:0000000000000000 [ 21.937093] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 21.939959] CR2: 000000000059ada5 CR3: 0000000007156000 CR4: 00000000000006a0 [ 21.943423] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000 [ 21.946863] DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400 [ 21.950329] Call Trace: [ 21.951725] <IRQ> [ 21.952924] ? show_regs+0x69/0x80 [ 21.954754] ? __warn+0x8d/0x190 [ 21.956503] ? __lock_acquire+0x9a4/0xbc0 [ 21.958604] ? report_bug+0x171/0x1a0 [ 21.960528] ? sched_clock_noinstr+0xd/0x20 [ 21.962747] ? handle_bug+0x42/0x80 [ 21.964578] ? exc_invalid_op+0x1c/0x70 [ 21.966634] ? asm_exc_invalid_op+0x1f/0x30 [ 21.968844] ? __warn_printk+0x143/0x160 [ 21.970891] ? __lock_acquire+0x9a4/0xbc0 [ 21.973034] lock_acquire+0xb5/0x2a0 [ 21.974887] ? sysvec_apic_timer_interrupt+0x6b/0xa0 [ 21.977505] _raw_spin_lock+0x36/0x50 [ 21.979411] ? sysvec_apic_timer_interrupt+0x6b/0xa0 [ 21.981926] sysvec_apic_timer_interrupt+0x6b/0xa0 [ 21.984364] </IRQ> [ 21.985585] <TASK> [ 21.986832] asm_sysvec_apic_timer_interrupt+0x1f/0x30 [ 21.989389] RIP: 0010:trace_graph_entry+0x1cf/0x210 [ 21.991879] Code: 57 cb fe ff 48 89 de 4c 89 e7 89 c2 e8 aa fd ff ff f0 41 ff 0e 4d 85 ed 0f 84 6d fe ff ff 89 45 d4 e8 45 b6 ff ff fb 8b 45 d4 <e9> 5c fe ff ff 48 89 df e8 34 f3 ff ff 85 c0 0f 84 4a fe ff ff e9 [ 22.000539] RSP: 0018:ffffc90001247908 EFLAGS: 00000206 [ 22.002962] RAX: 0000000000000001 RBX: ffffc9000124794c RCX: 0000000000000040 [ 22.005452] RDX: 0000000000000000 RSI: 0000000000000000 RDI: ffffffff8120f7db [ 22.007936] RBP: ffffc90001247938 R08: 0000000000000001 R09: 00000000004393b5 [ 22.010426] R10: 0000000000000018 R11: ffff888005843c10 R12: ffffffff82b7ca20 [ 22.012961] R13: 0000000000000200 R14: ffff88807d2b2f10 R15: 0000000000000100 [ 22.015474] ? trace_graph_entry+0x1cb/0x210 [ 22.017052] ? trace_graph_entry+0x1cb/0x210 [ 22.018622] ? preempt_count_add+0x4/0x80 [ 22.020106] function_graph_enter_ops+0xa1/0x160 [ 22.021782] ? preempt_count_add+0x4/0x80 [ 22.023265] ftrace_graph_func+0xc4/0x170 [ 22.024786] ? __pte_offset_map+0x2f/0x1c0 [ 22.026312] ? preempt_count_add+0x9/0x80 [ 22.027795] ? preempt_count_add+0x9/0x80 [ 22.029265] ? _raw_spin_lock+0x1b/0x50 [ 22.030686] ? preempt_count_add+0x9/0x80 [ 22.032154] ? _raw_spin_lock+0x1b/0x50 [ 22.033568] ? ftrace_stub_direct_tramp+0x20/0x20 [ 22.035257] ? __pte_offset_map_lock+0x72/0x160 [ 22.036917] ? ftrace_stub_direct_tramp+0x20/0x20 [ 22.041935] ? __get_locked_pte+0x43/0x80 [ 22.043411] ? __get_locked_pte+0x9/0x80 [ 22.044849] ? __ia32_sys_waitid+0x5/0x30 [ 22.046333] ? ftrace_stub_direct_tramp+0x20/0x20 [ 22.048024] ? __text_poke+0x14d/0x510 [ 22.049413] ? perf_event_text_poke+0x4/0xc0 [ 22.050982] ? __pfx_text_poke_memcpy+0x10/0x10 [ 22.052643] ? text_poke_bp_batch+0x1c1/0x3b0 [ 22.054222] ? __ia32_sys_waitid+0x5/0x30 [ 22.055714] ? __pfx_ptrace_get_syscall_info+0x10/0x10 [ 22.057545] ? text_poke_flush+0x4c/0x60 [ 22.058993] ? text_poke_queue+0x25/0x60 [ 22.060451] ? ftrace_stub_direct_tramp+0x20/0x20 [ 22.062146] ? ftrace_replace_code+0x188/0x200 [ 22.063787] ? ftrace_modify_all_code+0x154/0x190 [ 22.065489] ? arch_ftrace_update_code+0xd/0x20 [ 22.067130] ? ftrace_shutdown.part.0+0x119/0x250 [ 22.068822] ? ftrace_shutdown+0x2f/0x70 [ 22.070270] ? unregister_ftrace_graph+0x79/0x110 [ 22.071989] ? graph_trace_reset+0x1d/0x30 [ 22.073479] ? tracing_set_tracer+0x12f/0x290 [ 22.075075] ? tracing_set_trace_write+0x9c/0xe0 [ 22.076802] ? vfs_write+0xd5/0x560 [ 22.078129] ? vfs_write+0x9/0x560 [ 22.079434] ? ftrace_stub_direct_tramp+0x20/0x20 [ 22.081114] ? ksys_write+0x7d/0x100 [ 22.082474] ? ftrace_stub_direct_tramp+0x20/0x20 [ 22.084155] ? __x64_sys_write+0x1d/0x30 [ 22.085615] ? ftrace_stub_direct_tramp+0x20/0x20 [ 22.087321] ? do_syscall_64+0x3f/0x90 [ 22.088713] ? entry_SYSCALL_64_after_hwframe+0x6e/0xd8 [ 22.090624] </TASK> [ 22.091509] irq event stamp: 5147790 [ 22.092837] hardirqs last enabled at (5147789): [<ffffffff8120f7db>] trace_graph_entry+0x1cb/0x210 [ 22.095952] hardirqs last disabled at (5147790): [<ffffffff81c26683>] sysvec_apic_timer_interrupt+0x13/0xa0 [ 22.099286] softirqs last enabled at (5068304): [<ffffffff81066580>] return_to_handler+0x0/0x40 [ 22.102320] softirqs last disabled at (5068283): [<ffffffff81066580>] return_to_handler+0x0/0x40 [ 22.105357] ---[ end trace 0000000000000000 ]--- [ 22.107041] BUG: kernel NULL pointer dereference, address: 00000000000000c8 [ 22.109364] #PF: supervisor read access in kernel mode [ 22.111117] #PF: error_code(0x0000) - not-present page [ 22.112871] PGD 800000000735a067 P4D 800000000735a067 PUD 7359067 PMD 0 [ 22.115149] Oops: 0000 [#1] PREEMPT SMP PTI [ 22.116631] CPU: 18 PID: 477 Comm: sh Tainted: G W N 6.6.0+ #27 [ 22.119013] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.2-debian-1.16.2-1 04/01/2014 [ 22.122196] RIP: 0010:__lock_acquire+0x1b4/0xbc0 [ 22.123802] Code: 43 24 4c 89 e8 25 ff 1f 00 00 48 0f a3 05 44 65 13 02 0f 83 e7 04 00 00 48 8d 14 40 48 8d 04 90 48 c1 e0 04 48 05 80 2f 25 83 <0f> b6 90 c8 00 00 00 0f b7 43 20 66 25 ff 1f 0f b7 c0 48 0f a3 05 [ 22.129876] RSP: 0018:ffffc900005b4ee0 EFLAGS: 00010046 [ 22.131669] RAX: 0000000000000000 RBX: ffff888005843cd8 RCX: 000000053b6d0030 [ 22.134050] RDX: 0000000000000000 RSI: ffffffff82b800c0 RDI: ffffffff8108e4d3 [ 22.136433] RBP: ffffc900005b4f30 R08: 0000000000000001 R09: ffffc900005b4d60 [ 22.138830] R10: 0000000000000001 R11: ffff888005843c10 R12: ffff888005843200 [ 22.141217] R13: 7627623276c27547 R14: 0000000000000000 R15: 0000000000000005 [ 22.143606] FS: 0000000000fea3c0(0000) GS:ffff88807d280000(0000) knlGS:0000000000000000 [ 22.146346] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 22.148307] CR2: 00000000000000c8 CR3: 0000000007156000 CR4: 00000000000006a0 [ 22.150702] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000 [ 22.153091] DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400 [ 22.155483] Call Trace: [ 22.156415] <IRQ> [ 22.157214] ? show_regs+0x69/0x80 [ 22.158450] ? __die+0x28/0x70 [ 22.159577] ? page_fault_oops+0xa5/0x170 [ 22.161013] ? kernelmode_fixup_or_oops.constprop.0+0x96/0x100 [ 22.163016] ? __bad_area_nosemaphore.constprop.0+0x17e/0x230 [ 22.164984] ? __warn+0xd8/0x190 [ 22.166214] ? bad_area_nosemaphore+0x13/0x20 [ 22.167756] ? do_user_addr_fault+0x252/0x860 [ 22.169299] ? exc_page_fault+0x7f/0x1f0 [ 22.170712] ? asm_exc_page_fault+0x2b/0x30 [ 22.172205] ? __warn_printk+0x143/0x160 [ 22.173599] ? __lock_acquire+0x1b4/0xbc0 [ 22.175047] lock_acquire+0xb5/0x2a0 [ 22.176328] ? sysvec_apic_timer_interrupt+0x6b/0xa0 [ 22.178075] _raw_spin_lock+0x36/0x50 [ 22.179392] ? sysvec_apic_timer_interrupt+0x6b/0xa0 [ 22.181107] sysvec_apic_timer_interrupt+0x6b/0xa0 [ 22.182775] </IRQ> [ 22.183600] <TASK> [ 22.184428] asm_sysvec_apic_timer_interrupt+0x1f/0x30 [ 22.186197] RIP: 0010:trace_graph_entry+0x1cf/0x210 [ 22.187893] Code: 57 cb fe ff 48 89 de 4c 89 e7 89 c2 e8 aa fd ff ff f0 41 ff 0e 4d 85 ed 0f 84 6d fe ff ff 89 45 d4 e8 45 b6 ff ff fb 8b 45 d4 <e9> 5c fe ff ff 48 89 df e8 34 f3 ff ff 85 c0 0f 84 4a fe ff ff e9 [ 22.194033] RSP: 0018:ffffc90001247908 EFLAGS: 00000206 [ 22.195836] RAX: 0000000000000001 RBX: ffffc9000124794c RCX: 0000000000000040 [ 22.198226] RDX: 0000000000000000 RSI: 0000000000000000 RDI: ffffffff8120f7db [ 22.200609] RBP: ffffc90001247938 R08: 0000000000000001 R09: 00000000004393b5 [ 22.202998] R10: 0000000000000018 R11: ffff888005843c10 R12: ffffffff82b7ca20 [ 22.205386] R13: 0000000000000200 R14: ffff88807d2b2f10 R15: 0000000000000100 [ 22.207798] ? trace_graph_entry+0x1cb/0x210 [ 22.209313] ? trace_graph_entry+0x1cb/0x210 [ 22.210836] ? preempt_count_add+0x4/0x80 [ 22.212259] function_graph_enter_ops+0xa1/0x160 [ 22.213872] ? preempt_count_add+0x4/0x80 [ 22.215304] ftrace_graph_func+0xc4/0x170 [ 22.216742] ? __pte_offset_map+0x2f/0x1c0 [ 22.218186] ? preempt_count_add+0x9/0x80 [ 22.219606] ? preempt_count_add+0x9/0x80 [ 22.221027] ? _raw_spin_lock+0x1b/0x50 [ 22.222394] ? preempt_count_add+0x9/0x80 [ 22.223805] ? _raw_spin_lock+0x1b/0x50 [ 22.225164] ? ftrace_stub_direct_tramp+0x20/0x20 [ 22.226801] ? __pte_offset_map_lock+0x72/0x160 [ 22.228389] ? ftrace_stub_direct_tramp+0x20/0x20 [ 22.230016] ? __get_locked_pte+0x43/0x80 [ 22.231427] ? __get_locked_pte+0x9/0x80 [ 22.232815] ? __ia32_sys_waitid+0x5/0x30 [ 22.234228] ? ftrace_stub_direct_tramp+0x20/0x20 [ 22.235855] ? __text_poke+0x14d/0x510 [ 22.237184] ? perf_event_text_poke+0x4/0xc0 [ 22.238686] ? __pfx_text_poke_memcpy+0x10/0x10 [ 22.240270] ? text_poke_bp_batch+0x1c1/0x3b0 [ 22.241787] ? __ia32_sys_waitid+0x5/0x30 [ 22.243218] ? __pfx_ptrace_get_syscall_info+0x10/0x10 [ 22.244987] ? text_poke_flush+0x4c/0x60 [ 22.246379] ? text_poke_queue+0x25/0x60 [ 22.247764] ? ftrace_stub_direct_tramp+0x20/0x20 [ 22.249396] ? ftrace_replace_code+0x188/0x200 [ 22.250961] ? ftrace_modify_all_code+0x154/0x190 [ 22.252601] ? arch_ftrace_update_code+0xd/0x20 [ 22.254175] ? ftrace_shutdown.part.0+0x119/0x250 [ 22.255811] ? ftrace_shutdown+0x2f/0x70 [ 22.257202] ? unregister_ftrace_graph+0x79/0x110 [ 22.258834] ? graph_trace_reset+0x1d/0x30 [ 22.260274] ? tracing_set_tracer+0x12f/0x290 [ 22.261797] ? tracing_set_trace_write+0x9c/0xe0 [ 22.263431] ? vfs_write+0xd5/0x560 [ 22.264692] ? vfs_write+0x9/0x560 [ 22.265956] ? ftrace_stub_direct_tramp+0x20/0x20 [ 22.267596] ? ksys_write+0x7d/0x100 [ 22.268887] ? ftrace_stub_direct_tramp+0x20/0x20 [ 22.270525] ? __x64_sys_write+0x1d/0x30 [ 22.271910] ? ftrace_stub_direct_tramp+0x20/0x20 [ 22.273548] ? do_syscall_64+0x3f/0x90 [ 22.274890] ? entry_SYSCALL_64_after_hwframe+0x6e/0xd8 [ 22.276693] </TASK> [ 22.277542] Modules linked in: [ 22.278672] CR2: 00000000000000c8 [ 22.279865] ---[ end trace 0000000000000000 ]--- [ 22.281460] RIP: 0010:__lock_acquire+0x1b4/0xbc0 [ 22.283072] Code: 43 24 4c 89 e8 25 ff 1f 00 00 48 0f a3 05 44 65 13 02 0f 83 e7 04 00 00 48 8d 14 40 48 8d 04 90 48 c1 e0 04 48 05 80 2f 25 83 <0f> b6 90 c8 00 00 00 0f b7 43 20 66 25 ff 1f 0f b7 c0 48 0f a3 05 [ 22.289153] RSP: 0018:ffffc900005b4ee0 EFLAGS: 00010046 [ 22.290936] RAX: 0000000000000000 RBX: ffff888005843cd8 RCX: 000000053b6d0030 [ 22.293304] RDX: 0000000000000000 RSI: ffffffff82b800c0 RDI: ffffffff8108e4d3 [ 22.295673] RBP: ffffc900005b4f30 R08: 0000000000000001 R09: ffffc900005b4d60 [ 22.298038] R10: 0000000000000001 R11: ffff888005843c10 R12: ffff888005843200 [ 22.300414] R13: 7627623276c27547 R14: 0000000000000000 R15: 0000000000000005 [ 22.302803] FS: 0000000000fea3c0(0000) GS:ffff88807d280000(0000) knlGS:0000000000000000 [ 22.305523] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 22.307472] CR2: 00000000000000c8 CR3: 0000000007156000 CR4: 00000000000006a0 [ 22.309846] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000 [ 22.312224] DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400 [ 22.314601] Kernel panic - not syncing: Fatal exception in interrupt [ 22.320291] Kernel Offset: disabled [ 22.321563] ---[ end Kernel panic - not syncing: Fatal exception in interrupt ]---
Thanks,
Signature · 1 line
Masami Hiramatsu (Google) <[email protected]>
Masami Hiramatsu (Google)
maintainerPATCH v3 13/33 function_graph: Add "task variables" per task for fgraph_ops
In reply to Masami Hiramatsu (Google)From: Steven Rostedt (VMware) <[email protected]>
Add a "task variables" array on the tasks shadow ret_stack that is the size of longs for each possible registered fgraph_ops. That's a total of 16, taking up 8 * 16 = 128 bytes (out of a page size 4k).
This will allow for fgraph_ops to do specific features on a per task basis having a way to maintain state for each task.
---
Changes in v3:
- Move fgraph_ops::idx to previous patch in the series.
Changes in v2:
- Make description lines shorter than 76 chars.
--- include/linux/ftrace.h | 1 + kernel/trace/fgraph.c | 70 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 70 insertions(+), 1 deletion(-)
include/linux/ftrace.h +1 -0
@@ -1116,6 +1116,7 @@ ftrace_graph_get_ret_stack(struct task_struct *task, int idx);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);/* * Sometimes we don't want to trace a function with the function
kernel/trace/fgraph.c +69 -1
109 lines not on this page · raw message
Masami Hiramatsu (Google)
maintainerPATCH v3 14/33 function_graph: Move set_graph_function tests to shadow stack global var
In reply to Masami Hiramatsu (Google)From: Steven Rostedt (VMware) <[email protected]>
The use of the task->trace_recursion for the logic used for the set_graph_funnction was a bit of an abuse of that variable. Now that there exists global vars that are per stack for registered graph traces, use that instead.
--- include/linux/trace_recursion.h | 5 +---- kernel/trace/trace.h | 32 +++++++++++++++++++++----------- kernel/trace/trace_functions_graph.c | 6 +++--- kernel/trace/trace_irqsoff.c | 4 ++-- kernel/trace/trace_sched_wakeup.c | 4 ++-- 5 files changed, 29 insertions(+), 22 deletions(-)
include/linux/trace_recursion.h +1 -4
@@ -44,9 +44,6 @@ enum {*/ TRACE_IRQ_BIT, -/* Set if the function is in the set_graph_function file */ - TRACE_GRAPH_BIT, -/* * In the very unlikely case that an interrupt came in * at a start of graph tracing, and we want to trace
@@ -60,7 +57,7 @@ enum {* that preempted a softirq start of a function that * preempted normal context!!!! Luckily, it can't be * greater than 3, so the next two bits are a mask -* of what the depth is when we set TRACE_GRAPH_BIT +* of what the depth is when we set TRACE_GRAPH_FL */ TRACE_GRAPH_DEPTH_START_BIT,
kernel/trace/trace.h +21 -11
@@ -889,11 +889,16 @@ extern void init_array_fgraph_ops(struct trace_array *tr, struct ftrace_ops *opsextern int allocate_fgraph_ops (struct trace_array *tr,struct ftrace_ops *ops);extern void free_fgraph_ops (struct trace_array *tr); +enum { +TRACE_GRAPH_FL =1 , +}; +#ifdef CONFIG_DYNAMIC_FTRACE extern struct ftrace_hash __rcu *ftrace_graph_hash;extern struct ftrace_hash __rcu *ftrace_graph_notrace_hash; -static inline int ftrace_graph_addr (struct ftrace_graph_ent *trace) +static inline int +ftrace_graph_addr (unsigned long *task_var,struct ftrace_graph_ent *trace) {unsigned long addr = trace->func;int ret =0 ;
@@ -915,12 +920,11 @@ static inline int ftrace_graph_addr(struct ftrace_graph_ent *trace) }if (ftrace_lookup_ip (hash, addr)) { -/* * This needs to be cleared on the return functions * when the depth is zero. */ -trace_recursion_set (TRACE_GRAPH_BIT ); + *task_var |=TRACE_GRAPH_FL ;trace_recursion_set_depth (trace->depth);/*
@@ -940,11 +944,14 @@ static inline int ftrace_graph_addr(struct ftrace_graph_ent *trace)return ret; } -static inline void ftrace_graph_addr_finish (struct ftrace_graph_ret *trace) +static inline void +ftrace_graph_addr_finish (struct fgraph_ops *gops,struct ftrace_graph_ret *trace) { -if (trace_recursion_test (TRACE_GRAPH_BIT ) && +unsigned long *task_var =fgraph_get_task_var (gops); + +if ((*task_var &TRACE_GRAPH_FL ) && trace->depth ==trace_recursion_depth ()) -trace_recursion_clear (TRACE_GRAPH_BIT ); + *task_var &= ~TRACE_GRAPH_FL ; }static inline int ftrace_graph_notrace_addr (unsigned long addr)
@@ -971,7 +978,7 @@ static inline int ftrace_graph_notrace_addr(unsigned long addr) }#else -static inline int ftrace_graph_addr (struct ftrace_graph_ent *trace) +static inline int ftrace_graph_addr (unsigned long *task_var,struct ftrace_graph_ent *trace) {return 1 ; }
@@ -980,17 +987,20 @@ static inline int ftrace_graph_notrace_addr(unsigned long addr) {return 0 ; } -static inline void ftrace_graph_addr_finish (struct ftrace_graph_ret *trace) +static inline void ftrace_graph_addr_finish (struct fgraph_ops *gops,struct ftrace_graph_ret *trace) { }#endif /* CONFIG_DYNAMIC_FTRACE */ extern unsigned int fgraph_max_depth; -static inline bool ftrace_graph_ignore_func (struct ftrace_graph_ent *trace) +static inline bool +ftrace_graph_ignore_func (struct fgraph_ops *gops,struct ftrace_graph_ent *trace) { +unsigned long *task_var =fgraph_get_task_var (gops); +/* trace it when it is-nested-in or is a function enabled. */ -return !(trace_recursion_test (TRACE_GRAPH_BIT ) || -ftrace_graph_addr (trace)) || +return !((*task_var &TRACE_GRAPH_FL ) || +ftrace_graph_addr (task_var, trace)) || (trace->depth <0 ) || (fgraph_max_depth && trace->depth >= fgraph_max_depth); }
kernel/trace/trace_functions_graph.c +3 -3
@@ -160,7 +160,7 @@ int trace_graph_entry(struct ftrace_graph_ent *trace,if (!ftrace_trace_task (tr))return 0 ; -if (ftrace_graph_ignore_func (trace)) +if (ftrace_graph_ignore_func (gops, trace))return 0 ;if (ftrace_graph_ignore_irqs ())
@@ -247,7 +247,7 @@ void trace_graph_return(struct ftrace_graph_ret *trace,long disabled;int cpu; -ftrace_graph_addr_finish (trace); +ftrace_graph_addr_finish (gops, trace);if (trace_recursion_test (TRACE_GRAPH_NOTRACE_BIT )) {trace_recursion_clear (TRACE_GRAPH_NOTRACE_BIT );
@@ -269,7 +269,7 @@ void trace_graph_return(struct ftrace_graph_ret *trace,static void trace_graph_thresh_return (struct ftrace_graph_ret *trace,struct fgraph_ops *gops) { -ftrace_graph_addr_finish (trace); +ftrace_graph_addr_finish (gops, trace);if (trace_recursion_test (TRACE_GRAPH_NOTRACE_BIT )) {trace_recursion_clear (TRACE_GRAPH_NOTRACE_BIT );
kernel/trace/trace_irqsoff.c +2 -2
@@ -184,7 +184,7 @@ static int irqsoff_graph_entry(struct ftrace_graph_ent *trace,unsigned int trace_ctx;int ret; -if (ftrace_graph_ignore_func (trace)) +if (ftrace_graph_ignore_func (gops, trace))return 0 ;/* * Do not trace a function if it's filtered by set_graph_notrace.
@@ -214,7 +214,7 @@ static void irqsoff_graph_return(struct ftrace_graph_ret *trace,unsigned long flags;unsigned int trace_ctx; -ftrace_graph_addr_finish (trace); +ftrace_graph_addr_finish (gops, trace);if (!func_prolog_dec (tr, &data, &flags))return ;
kernel/trace/trace_sched_wakeup.c +2 -2
@@ -120,7 +120,7 @@ static int wakeup_graph_entry(struct ftrace_graph_ent *trace,unsigned int trace_ctx;int ret =0 ; -if (ftrace_graph_ignore_func (trace)) +if (ftrace_graph_ignore_func (gops, trace))return 0 ;/* * Do not trace a function if it's filtered by set_graph_notrace.
@@ -149,7 +149,7 @@ static void wakeup_graph_return(struct ftrace_graph_ret *trace,struct trace_array_cpu *data;unsigned int trace_ctx; -ftrace_graph_addr_finish (trace); +ftrace_graph_addr_finish (gops, trace);if (!func_prolog_preempt_disable (tr, &data, &trace_ctx))return ;
Masami Hiramatsu (Google)
maintainerPATCH v3 15/33 function_graph: Move graph depth stored data to shadow stack global var
In reply to Masami Hiramatsu (Google)From: Steven Rostedt (VMware) <[email protected]>
The use of the task->trace_recursion for the logic used for the function graph depth was a bit of an abuse of that variable. Now that there exists global vars that are per stack for registered graph traces, use that instead.
--- include/linux/trace_recursion.h | 29 ----------------------------- kernel/trace/trace.h | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 31 deletions(-)
include/linux/trace_recursion.h +0 -29
@@ -44,25 +44,6 @@ enum {*/ TRACE_IRQ_BIT, -/* -* In the very unlikely case that an interrupt came in -* at a start of graph tracing, and we want to trace -* the function in that interrupt, the depth can be greater -* than zero, because of the preempted start of a previous -* trace. In an even more unlikely case, depth could be 2 -* if a softirq interrupted the start of graph tracing, -* followed by an interrupt preempting a start of graph -* tracing in the softirq, and depth can even be 3 -* if an NMI came in at the start of an interrupt function -* that preempted a softirq start of a function that -* preempted normal context!!!! Luckily, it can't be -* greater than 3, so the next two bits are a mask -* of what the depth is when we set TRACE_GRAPH_FL -*/ - -TRACE_GRAPH_DEPTH_START_BIT , -TRACE_GRAPH_DEPTH_END_BIT , -/* * To implement set_graph_notrace, if this bit is set, we ignore * function graph tracing of called functions, until the return
@@ -78,16 +59,6 @@ enum {#define trace_recursion_clear (bit) do { (current)->trace_recursion &= ~(1<<(bit)); } while (0)#define trace_recursion_test (bit) ((current)->trace_recursion & (1<<(bit))) -#define trace_recursion_depth () \ - (((current)->trace_recursion >> TRACE_GRAPH_DEPTH_START_BIT) & 3) -#define trace_recursion_set_depth (depth) \ - do { \ - current->trace_recursion &= \ - ~(3 << TRACE_GRAPH_DEPTH_START_BIT); \ - current->trace_recursion |= \ - ((depth) & 3) << TRACE_GRAPH_DEPTH_START_BIT; \ - } while (0) -#define TRACE_CONTEXT_BITS 4#define TRACE_FTRACE_START TRACE_FTRACE_BIT
kernel/trace/trace.h +32 -2
@@ -891,8 +891,38 @@ extern void free_fgraph_ops(struct trace_array *tr);enum {TRACE_GRAPH_FL =1 , + +/* +* In the very unlikely case that an interrupt came in +* at a start of graph tracing, and we want to trace +* the function in that interrupt, the depth can be greater +* than zero, because of the preempted start of a previous +* trace. In an even more unlikely case, depth could be 2 +* if a softirq interrupted the start of graph tracing, +* followed by an interrupt preempting a start of graph +* tracing in the softirq, and depth can even be 3 +* if an NMI came in at the start of an interrupt function +* that preempted a softirq start of a function that +* preempted normal context!!!! Luckily, it can't be +* greater than 3, so the next two bits are a mask +* of what the depth is when we set TRACE_GRAPH_FL +*/ + +TRACE_GRAPH_DEPTH_START_BIT , +TRACE_GRAPH_DEPTH_END_BIT , }; +static inline unsigned long ftrace_graph_depth (unsigned long *task_var) +{ +return (*task_var >>TRACE_GRAPH_DEPTH_START_BIT ) &3 ; +} + +static inline void ftrace_graph_set_depth (unsigned long *task_var,int depth) +{ + *task_var &= ~(3 <<TRACE_GRAPH_DEPTH_START_BIT ); + *task_var |= (depth &3 ) <<TRACE_GRAPH_DEPTH_START_BIT ; +} +#ifdef CONFIG_DYNAMIC_FTRACE extern struct ftrace_hash __rcu *ftrace_graph_hash;extern struct ftrace_hash __rcu *ftrace_graph_notrace_hash;
@@ -925,7 +955,7 @@ ftrace_graph_addr(unsigned long *task_var, struct ftrace_graph_ent *trace)* when the depth is zero. */ *task_var |=TRACE_GRAPH_FL ; -trace_recursion_set_depth (trace->depth); +ftrace_graph_set_depth (task_var, trace->depth);/* * If no irqs are to be traced, but a set_graph_function
@@ -950,7 +980,7 @@ ftrace_graph_addr_finish(struct fgraph_ops *gops, struct ftrace_graph_ret *traceunsigned long *task_var =fgraph_get_task_var (gops);if ((*task_var &TRACE_GRAPH_FL ) && - trace->depth ==trace_recursion_depth ()) + trace->depth ==ftrace_graph_depth (task_var)) *task_var &= ~TRACE_GRAPH_FL ; }
Masami Hiramatsu (Google)
maintainerPATCH v3 16/33 function_graph: Move graph notrace bit to shadow stack global var
In reply to Masami Hiramatsu (Google)From: Steven Rostedt (VMware) <[email protected]>
The use of the task->trace_recursion for the logic used for the function graph no-trace was a bit of an abuse of that variable. Now that there exists global vars that are per stack for registered graph traces, use that instead.
---
Changes in v2:
- Make description lines shorter than 76 chars.
--- include/linux/trace_recursion.h | 7 ------- kernel/trace/trace.h | 9 +++++++++ kernel/trace/trace_functions_graph.c | 10 ++++++---- 3 files changed, 15 insertions(+), 11 deletions(-)
include/linux/trace_recursion.h +0 -7
@@ -44,13 +44,6 @@ enum {*/ TRACE_IRQ_BIT , -/* -* To implement set_graph_notrace, if this bit is set, we ignore -* function graph tracing of called functions, until the return -* function is called to clear it. -*/ -TRACE_GRAPH_NOTRACE_BIT , -/* Used to prevent recursion recording from recursing. */ TRACE_RECORD_RECURSION_BIT, };
kernel/trace/trace.h +9 -0
@@ -910,8 +910,17 @@ enum {TRACE_GRAPH_DEPTH_START_BIT ,TRACE_GRAPH_DEPTH_END_BIT , + +/* +* To implement set_graph_notrace, if this bit is set, we ignore +* function graph tracing of called functions, until the return +* function is called to clear it. +*/ + TRACE_GRAPH_NOTRACE_BIT, }; +#define TRACE_GRAPH_NOTRACE (1 << TRACE_GRAPH_NOTRACE_BIT) +static inline unsigned long ftrace_graph_depth (unsigned long *task_var) {return (*task_var >>TRACE_GRAPH_DEPTH_START_BIT ) &3 ;
kernel/trace/trace_functions_graph.c +6 -4
@@ -130,6 +130,7 @@ static inline int ftrace_graph_ignore_irqs(void)int trace_graph_entry (struct ftrace_graph_ent *trace,struct fgraph_ops *gops) { +unsigned long *task_var =fgraph_get_task_var (gops);struct trace_array *tr = gops->private;struct trace_array_cpu *data;unsigned long flags;
@@ -138,7 +139,7 @@ int trace_graph_entry(struct ftrace_graph_ent *trace,int ret;int cpu; -if (trace_recursion_test (TRACE_GRAPH_NOTRACE_BIT )) +if (*task_var &TRACE_GRAPH_NOTRACE )return 0 ;/*
@@ -149,7 +150,7 @@ int trace_graph_entry(struct ftrace_graph_ent *trace,* returning from the function. */ if (ftrace_graph_notrace_addr (trace->func)) { -trace_recursion_set (TRACE_GRAPH_NOTRACE_BIT ); + *task_var |=TRACE_GRAPH_NOTRACE_BIT ;/* * Need to return 1 to have the return called * that will clear the NOTRACE bit.
@@ -240,6 +241,7 @@ void __trace_graph_return(struct trace_array *tr,void trace_graph_return (struct ftrace_graph_ret *trace,struct fgraph_ops *gops) { +unsigned long *task_var =fgraph_get_task_var (gops);struct trace_array *tr = gops->private;struct trace_array_cpu *data;unsigned long flags;
@@ -249,8 +251,8 @@ void trace_graph_return(struct ftrace_graph_ret *trace,ftrace_graph_addr_finish (gops, trace); -if (trace_recursion_test (TRACE_GRAPH_NOTRACE_BIT )) { -trace_recursion_clear (TRACE_GRAPH_NOTRACE_BIT ); +if (*task_var &TRACE_GRAPH_NOTRACE ) { + *task_var &= ~TRACE_GRAPH_NOTRACE ;return ; }
Masami Hiramatsu (Google)
maintainerPATCH v3 17/33 function_graph: Implement fgraph_reserve_data() and fgraph_retrieve_data()
In reply to Masami Hiramatsu (Google)From: Steven Rostedt (VMware) <[email protected]>
Added functions that can be called by a fgraph_ops entryfunc and retfunc to store state between the entry of the function being traced to the exit of the same function. The fgraph_ops entryfunc() may call fgraph_reserve_data() to store up to 32 words onto the task's shadow ret_stack and this then can be retrieved by fgraph_retrieve_data() called by the corresponding retfunc().
---
Changes in v3:
- Store fgraph_array index to the data entry.
- Both function requires fgraph_array index to store/retrieve data.
- Reserve correct size of the data.
- Return correct data area.
Changes in v2:
- Retrieve the reserved size by fgraph_retrieve_data().
- Expand the maximum data size to 32 words.
- Update stack index with __get_index(val) if FGRAPH_TYPE_ARRAY entry.
- fix typos and make description lines shorter than 76 chars.
--- include/linux/ftrace.h | 3 + kernel/trace/fgraph.c | 175 ++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 170 insertions(+), 8 deletions(-)
include/linux/ftrace.h +3 -0
@@ -1075,6 +1075,9 @@ struct fgraph_ops {int idx; }; +void *fgraph_reserve_data (int idx,int size_bytes); +void *fgraph_retrieve_data (int idx,int *size_bytes); +/* * Stack of return addresses for functions * of a thread.
kernel/trace/fgraph.c +167 -8
269 lines not on this page · raw message
Masami Hiramatsu (Google)
maintainerPATCH v3 18/33 function_graph: Add selftest for passing local variables
In reply to Masami Hiramatsu (Google)From: Steven Rostedt (VMware) <[email protected]>
Add boot up selftest that passes variables from a function entry to a function exit, and make sure that they do get passed around.
---
Changes in v2:
- Add reserved size test.
- Use pr_*() instead of printk(KERN_*).
--- kernel/trace/trace_selftest.c | 169 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+)
kernel/trace/trace_selftest.c +169 -0
@@ -756,6 +756,173 @@ trace_selftest_startup_function(struct tracer *trace, struct trace_array *tr)#ifdef CONFIG_FUNCTION_GRAPH_TRACER +#ifdef CONFIG_DYNAMIC_FTRACE + +#define BYTE_NUMBER 123 +#define SHORT_NUMBER 12345 +#define WORD_NUMBER 1234567890 +#define LONG_NUMBER 1234567890123456789LL + +static int fgraph_store_size__initdata ; +static const char *fgraph_store_type_name__initdata ; +static char *fgraph_error_str__initdata ; +static char fgraph_error_str_buf[128 ]__initdata ; + +static __init int store_entry (struct ftrace_graph_ent *trace, +struct fgraph_ops *gops) +{ +const char *type = fgraph_store_type_name; +int size = fgraph_store_size; +void *p; + + p =fgraph_reserve_data (gops->idx, size); +if (!p) { +snprintf (fgraph_error_str_buf,sizeof (fgraph_error_str_buf), +"Failed to reserve %s\n" , type); + fgraph_error_str = fgraph_error_str_buf; +return 0 ; + } + +switch (fgraph_store_size) { +case 1 : + *(char *)p =BYTE_NUMBER ; +break ; +case 2 : + *(short *)p =SHORT_NUMBER ; +break ; +case 4 : + *(int *)p =WORD_NUMBER ; +break ; +case 8 : + *(long long *)p =LONG_NUMBER ; +break ; + } + +return 1 ; +} + +static __init void store_return (struct ftrace_graph_ret *trace, +struct fgraph_ops *gops) +{ +const char *type = fgraph_store_type_name; +long long expect =0 ; +long long found =-1 ; +int size; +char *p; + + p =fgraph_retrieve_data (gops->idx, &size); +if (!p) { +snprintf (fgraph_error_str_buf,sizeof (fgraph_error_str_buf), +"Failed to retrieve %s\n" , type); + fgraph_error_str = fgraph_error_str_buf; +return ; + } +if (fgraph_store_size > size) { +snprintf (fgraph_error_str_buf,sizeof (fgraph_error_str_buf), +"Retrieved size %d is smaller than expected %d\n" , + size, (int )fgraph_store_size); + fgraph_error_str = fgraph_error_str_buf; +return ; + } + +switch (fgraph_store_size) { +case 1 : + expect =BYTE_NUMBER ; + found = *(char *)p; +break ; +case 2 : + expect =SHORT_NUMBER ; + found = *(short *)p; +break ; +case 4 : + expect =WORD_NUMBER ; + found = *(int *)p; +break ; +case 8 : + expect =LONG_NUMBER ; + found = *(long long *)p; +break ; + } + +if (found != expect) { +snprintf (fgraph_error_str_buf,sizeof (fgraph_error_str_buf), +"%s returned not %lld but %lld\n" , type, expect, found); + fgraph_error_str = fgraph_error_str_buf; +return ; + } + fgraph_error_str =NULL ; +} + +static struct fgraph_ops store_bytes__initdata = { + .entryfunc = store_entry, + .retfunc = store_return, +}; + +static int __init test_graph_storage_type (const char *name,int size) +{ +char *func_name; +int len; +int ret; + + fgraph_store_type_name = name; + fgraph_store_size = size; + +snprintf (fgraph_error_str_buf,sizeof (fgraph_error_str_buf), +"Failed to execute storage %s\n" , name); + fgraph_error_str = fgraph_error_str_buf; + +pr_cont ("PASSED\n" ); +pr_info ("Testing fgraph storage of %d byte%s: " , size, size >1 ?"s" :"" ); + + func_name ="*" __stringify(DYN_FTRACE_TEST_NAME ); + len =strlen (func_name); + + ret =ftrace_set_filter (&store_bytes.ops, func_name, len,1 ); +if (ret && ret != -ENODEV ) { +pr_cont ("*Could not set filter* " ); +return -1 ; + } + + ret =register_ftrace_graph (&store_bytes); +if (ret) { +pr_warn ("Failed to init store_bytes fgraph tracing\n" ); +return -1 ; + } + +DYN_FTRACE_TEST_NAME (); + +unregister_ftrace_graph (&store_bytes); + +if (fgraph_error_str) { +pr_cont ("*** %s ***" , fgraph_error_str); +return -1 ; + } + +return 0 ; +} +/* Test the storage passed across function_graph entry and return */ +static __init int test_graph_storage (void ) +{ +int ret; + + ret =test_graph_storage_type ("byte" ,1 ); +if (ret) +return ret; + ret =test_graph_storage_type ("short" ,2 ); +if (ret) +return ret; + ret =test_graph_storage_type ("word" ,4 ); +if (ret) +return ret; + ret =test_graph_storage_type ("long long" ,8 ); +if (ret) +return ret; +return 0 ; +} +#else +static inline int test_graph_storage (void ) {return 0 ; } +#endif /* CONFIG_DYNAMIC_FTRACE */ +/* Maximum number of functions to trace before diagnosing a hang */ #define GRAPH_MAX_FUNC_TEST 100000000
@@ -913,6 +1080,8 @@ trace_selftest_startup_function_graph(struct tracer *trace,ftrace_set_global_filter (NULL ,0 ,1 );#endif + ret =test_graph_storage (); +/* Don't test dynamic tracing, the function tracer already did */ out:/* Stop it if we failed */
Masami Hiramatsu (Google)
maintainerPATCH v3 19/33 function_graph: Add a new entry handler with parent_ip and ftrace_regs
In reply to Masami Hiramatsu (Google)From: Masami Hiramatsu (Google) <[email protected]>
Add a new entry handler to fgraph_ops as 'entryregfunc' which takes parent_ip and ftrace_regs. Note that the 'entryfunc' and 'entryregfunc' are mutual exclusive. You can set only one of them.
---
Changes in v3:
- Update for new multiple fgraph.
--- arch/arm64/kernel/ftrace.c | 2 + arch/loongarch/kernel/ftrace_dyn.c | 6 ++++ arch/powerpc/kernel/trace/ftrace.c | 2 + arch/powerpc/kernel/trace/ftrace_64_pg.c | 10 ++++--- arch/x86/kernel/ftrace.c | 42 ++++++++++++++++-------------- include/linux/ftrace.h | 19 +++++++++++--- kernel/trace/fgraph.c | 30 +++++++++++++++++---- 7 files changed, 76 insertions(+), 35 deletions(-)
arch/arm64/kernel/ftrace.c +1 -1
@@ -495,7 +495,7 @@ void ftrace_graph_func(unsigned long ip, unsigned long parent_ip,if (bit <0 )return ; -if (!function_graph_enter_ops (*parent, ip, fregs->fp, parent, gops)) +if (!function_graph_enter_ops (*parent, ip, fregs->fp, parent, fregs, gops)) *parent = (unsigned long )&return_to_handler;ftrace_test_recursion_unlock (bit);
arch/loongarch/kernel/ftrace_dyn.c +5 -1
@@ -244,7 +244,11 @@ void ftrace_graph_func(unsigned long ip, unsigned long parent_ip,struct pt_regs *regs = &fregs->regs;unsigned long *parent = (unsigned long *)®s->regs[1 ]; - prepare_ftrace_return(ip, (unsigned long *)parent); +if (unlikely (atomic_read (¤t->tracing_graph_pause))) +return ; + +if (!function_graph_enter_regs (regs->regs[1 ], ip,0 , parent, fregs)) + regs->regs[1] = (unsigned long)&return_to_handler; }#else static int ftrace_modify_graph_caller (bool enable)
arch/powerpc/kernel/trace/ftrace.c +1 -1
@@ -422,7 +422,7 @@ void ftrace_graph_func(unsigned long ip, unsigned long parent_ip,if (bit <0 ) goto out; -if (!function_graph_enter (parent_ip, ip,0 , (unsigned long *)sp)) +if (!function_graph_enter_regs (parent_ip, ip,0 , (unsigned long *)sp, fregs)) parent_ip =ppc_function_entry (return_to_handler);ftrace_test_recursion_unlock (bit);
arch/powerpc/kernel/trace/ftrace_64_pg.c +6 -4
@@ -795,7 +795,8 @@ int ftrace_disable_ftrace_graph_caller(void)* in current thread info. Return the address we want to divert to. */ static unsigned long -__prepare_ftrace_return (unsigned long parent,unsigned long ip,unsigned long sp) +__prepare_ftrace_return (unsigned long parent,unsigned long ip,unsigned long sp, +struct ftrace_regs *fregs) {unsigned long return_hooker;int bit;
@@ -812,7 +813,7 @@ __prepare_ftrace_return(unsigned long parent, unsigned long ip, unsigned long sp return_hooker =ppc_function_entry (return_to_handler); -if (!function_graph_enter (parent, ip,0 , (unsigned long *)sp)) +if (!function_graph_enter_regs (parent, ip,0 , (unsigned long *)sp, fregs)) parent = return_hooker;ftrace_test_recursion_unlock (bit);
@@ -824,13 +825,14 @@ __prepare_ftrace_return(unsigned long parent, unsigned long ip, unsigned long spvoid ftrace_graph_func (unsigned long ip,unsigned long parent_ip,struct ftrace_ops *op,struct ftrace_regs *fregs) { - fregs->regs.link =__prepare_ftrace_return (parent_ip, ip, fregs->regs.gpr[1 ]); + fregs->regs.link =__prepare_ftrace_return (parent_ip, ip, + fregs->regs.gpr[1 ], fregs); }#else unsigned long prepare_ftrace_return (unsigned long parent,unsigned long ip,unsigned long sp) { -return __prepare_ftrace_return (parent, ip, sp); +return __prepare_ftrace_return (parent, ip, sp,NULL ); }#endif #endif /* CONFIG_FUNCTION_GRAPH_TRACER */
arch/x86/kernel/ftrace.c +23 -19
69 lines not on this page · raw message
include/linux/ftrace.h +16 -3
@@ -1063,6 +1063,11 @@ typedef void (*trace_func_graph_ret_t)(struct ftrace_graph_ret *,typedef int (*trace_func_graph_ent_t )(struct ftrace_graph_ent *,struct fgraph_ops *);/* entry */ +typedef int (*trace_func_graph_regs_ent_t )(unsigned long func, +unsigned long parent_ip, +struct ftrace_regs *fregs, +struct fgraph_ops *);/* entry w/ regs */ +extern int ftrace_graph_entry_stub (struct ftrace_graph_ent *trace,struct fgraph_ops *gops);#ifdef CONFIG_FUNCTION_GRAPH_TRACER
@@ -1070,6 +1075,7 @@ extern int ftrace_graph_entry_stub(struct ftrace_graph_ent *trace, struct fgraphstruct fgraph_ops {trace_func_graph_ent_t entryfunc;trace_func_graph_ret_t retfunc; +trace_func_graph_regs_ent_t entryregfunc;struct ftrace_ops ops;/* for the hash lists */ void *private;int idx;
@@ -1106,13 +1112,20 @@ struct ftrace_ret_stack {extern void return_to_handler (void );extern int -function_graph_enter (unsigned long ret,unsigned long func, -unsigned long frame_pointer,unsigned long *retp); +function_graph_enter_regs (unsigned long ret,unsigned long func, +unsigned long frame_pointer,unsigned long *retp, +struct ftrace_regs *fregs); + +static inline int function_graph_enter (unsigned long ret,unsigned long func, +unsigned long fp,unsigned long *retp) +{ +return function_graph_enter_regs (ret, func, fp, retp,NULL ); +}extern int function_graph_enter_ops (unsigned long ret,unsigned long func,unsigned long frame_pointer,unsigned long *retp, -struct fgraph_ops *gops); +struct ftrace_regs *fregs,struct fgraph_ops *gops);struct ftrace_ret_stack *ftrace_graph_get_ret_stack (struct task_struct *task,int idx);
kernel/trace/fgraph.c +24 -6
@@ -544,9 +544,21 @@ ftrace_push_return_trace(unsigned long ret, unsigned long func,# define MCOUNT_INSN_SIZE 0#endif +static inline int call_entry_func (struct ftrace_graph_ent *trace, +unsigned long func,unsigned long ret, +struct ftrace_regs *fregs, +struct fgraph_ops *gops) +{ +if (gops->entryregfunc) +return gops->entryregfunc (func, ret, fregs, gops); + +return gops->entryfunc (trace, gops); +} +/* If the caller does not use ftrace, call this function. */ -int function_graph_enter (unsigned long ret,unsigned long func, -unsigned long frame_pointer,unsigned long *retp) +int function_graph_enter_regs (unsigned long ret,unsigned long func, +unsigned long frame_pointer,unsigned long *retp, +struct ftrace_regs *fregs) {struct ftrace_graph_ent trace;unsigned long bitmap =0 ;
@@ -581,7 +593,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)) +call_entry_func (&trace, func, ret, fregs, gops)) bitmap |=BIT (i);else /* Clear out any saved storage */
@@ -604,6 +616,7 @@ int function_graph_enter(unsigned long ret, unsigned long func,/* This is called from ftrace_graph_func() via ftrace */ int function_graph_enter_ops (unsigned long ret,unsigned long func,unsigned long frame_pointer,unsigned long *retp, +struct ftrace_regs *fregs,struct fgraph_ops *gops) {struct ftrace_graph_ent trace;
@@ -625,7 +638,7 @@ int function_graph_enter_ops(unsigned long ret, unsigned long func, trace.func = func; trace.depth = current->curr_ret_depth; save_curr_ret_stack = current->curr_ret_stack; -if (gops->entryfunc (&trace, gops)) { +if (call_entry_func (&trace, func, ret, fregs, gops)) {if (type ==FGRAPH_TYPE_RESERVED )set_fgraph_index_bitmap (current, index,BIT (gops->idx));else
@@ -906,7 +919,8 @@ void fgraph_init_ops(struct ftrace_ops *dst_ops, struct ftrace_ops *src_ops) { dst_ops->func = ftrace_graph_func; - dst_ops->flags =FTRACE_OPS_FL_PID |FTRACE_OPS_GRAPH_STUB ; + dst_ops->flags =FTRACE_OPS_FL_PID |FTRACE_OPS_GRAPH_STUB | +FTRACE_OPS_FL_SAVE_ARGS ;#ifdef FTRACE_GRAPH_TRAMP_ADDR dst_ops->trampoline =FTRACE_GRAPH_TRAMP_ADDR ;
@@ -1148,10 +1162,14 @@ int register_ftrace_graph(struct fgraph_ops *gops)int ret =0 ;int i; +if (gops->entryfunc && gops->entryregfunc) +return -EINVAL ; +mutex_lock (&ftrace_lock);if (!gops->ops.func) { - gops->ops.flags |=FTRACE_OPS_GRAPH_STUB ; + gops->ops.flags |=FTRACE_OPS_GRAPH_STUB | +FTRACE_OPS_FL_SAVE_ARGS ; gops->ops.func = ftrace_graph_func;#ifdef FTRACE_GRAPH_TRAMP_ADDR gops->ops.trampoline =FTRACE_GRAPH_TRAMP_ADDR ;
Masami Hiramatsu (Google)
maintainerPATCH v3 20/33 function_graph: Add a new exit handler with parent_ip and ftrace_regs
In reply to Masami Hiramatsu (Google)From: Masami Hiramatsu (Google) <[email protected]>
Add a new return handler to fgraph_ops as 'retregfunc' which takes parent_ip and ftrace_regs instead of ftrace_graph_ret. This handler is available only if the arch support CONFIG_HAVE_FUNCTION_GRAPH_FREGS. Note that the 'retfunc' and 'reregfunc' are mutual exclusive. You can set only one of them.
---
Changes in v3:
- Update for new multiple fgraph.
- Save the return address to instruction pointer in ftrace_regs.
--- arch/x86/include/asm/ftrace.h | 2 + include/linux/ftrace.h | 10 +++++- kernel/trace/Kconfig | 5 ++- kernel/trace/fgraph.c | 70 ++++++++++++++++++++++++++++------------- 4 files changed, 63 insertions(+), 24 deletions(-)
arch/x86/include/asm/ftrace.h +2 -0
@@ -72,6 +72,8 @@ arch_ftrace_get_regs(struct ftrace_regs *fregs) override_function_with_return(&(fregs)->regs)#define ftrace_regs_query_register_offset (name) \ regs_query_register_offset(name) +#define ftrace_regs_get_frame_pointer (fregs) \ + frame_pointer(&(fregs)->regs)struct ftrace_ops ;#define ftrace_graph_func ftrace_graph_func
include/linux/ftrace.h +9 -1
@@ -43,7 +43,9 @@ struct dyn_ftrace;char *arch_ftrace_match_adjust (char *str,const char *search); -#ifdef CONFIG_HAVE_FUNCTION_GRAPH_RETVAL +#ifdef CONFIG_HAVE_FUNCTION_GRAPH_FREGS +unsigned long ftrace_return_to_handler (struct ftrace_regs *fregs); +#elif defined(CONFIG_HAVE_FUNCTION_GRAPH_RETVAL )struct fgraph_ret_regs ;unsigned long ftrace_return_to_handler (struct fgraph_ret_regs *ret_regs);#else
@@ -157,6 +159,7 @@ struct ftrace_regs {#define ftrace_regs_set_instruction_pointer (fregs, ip) do { } while (0)#endif /* CONFIG_HAVE_DYNAMIC_FTRACE_WITH_ARGS */ + static __always_inline struct pt_regs *ftrace_get_regs(struct ftrace_regs *fregs) {if (!fregs)
@@ -1067,6 +1070,10 @@ typedef int (*trace_func_graph_regs_ent_t)(unsigned long func,unsigned long parent_ip, struct ftrace_regs *fregs, struct fgraph_ops *); /* entry w/ regs */ +typedef void (*trace_func_graph_regs_ret_t )(unsigned long func, +unsigned long parent_ip, +struct ftrace_regs *, +struct fgraph_ops *);/* return w/ regs */ extern int ftrace_graph_entry_stub (struct ftrace_graph_ent *trace,struct fgraph_ops *gops);
@@ -1076,6 +1083,7 @@ struct fgraph_ops {trace_func_graph_ent_t entryfunc;trace_func_graph_ret_t retfunc;trace_func_graph_regs_ent_t entryregfunc; +trace_func_graph_regs_ret_t retregfunc;struct ftrace_ops ops;/* for the hash lists */ void *private;int idx;
kernel/trace/Kconfig +4 -1
@@ -34,6 +34,9 @@ config HAVE_FUNCTION_GRAPH_TRACER config HAVE_FUNCTION_GRAPH_RETVAL bool +config HAVE_FUNCTION_GRAPH_FREGS + bool + config HAVE_DYNAMIC_FTRACE bool help
@@ -232,7 +235,7 @@ config FUNCTION_GRAPH_TRACER config FUNCTION_GRAPH_RETVAL bool "Kernel Function Graph Return Value" - depends on HAVE_FUNCTION_GRAPH_RETVAL + depends on HAVE_FUNCTION_GRAPH_RETVAL || HAVE_FUNCTION_GRAPH_FREGS depends on FUNCTION_GRAPH_TRACER default n help
kernel/trace/fgraph.c +48 -22
133 lines not on this page · raw message
Masami Hiramatsu (Google)
maintainerPATCH v3 21/33 x86/ftrace: Enable HAVE_FUNCTION_GRAPH_FREGS
In reply to Masami Hiramatsu (Google)From: Masami Hiramatsu (Google) <[email protected]>
Support HAVE_FUNCTION_GRAPH_FREGS on x86-64, which saves ftrace_regs on the stack in ftrace_graph return trampoline so that the callbacks can access registers via ftrace_regs APIs.
Note that this only recovers 'rax' and 'rdx' registers because other registers are not used anymore and recovered by caller. 'rax' and 'rdx' will be used for passing the return value.
---
Changes in v3:
- Add a comment about rip.
Changes in v2:
- Save rsp register and drop clearing orig_ax.
--- arch/x86/Kconfig | 3 ++- arch/x86/kernel/ftrace_64.S | 37 +++++++++++++++++++++++++++++-------- 2 files changed, 31 insertions(+), 9 deletions(-)
arch/x86/Kconfig +2 -1
@@ -219,7 +219,8 @@ config X86 select HAVE_FAST_GUP select HAVE_FENTRY if X86_64 || DYNAMIC_FTRACE select HAVE_FTRACE_MCOUNT_RECORD - select HAVE_FUNCTION_GRAPH_RETVAL if HAVE_FUNCTION_GRAPH_TRACER + select HAVE_FUNCTION_GRAPH_FREGS if HAVE_DYNAMIC_FTRACE_WITH_ARGS + select HAVE_FUNCTION_GRAPH_RETVAL if !HAVE_DYNAMIC_FTRACE_WITH_ARGS select HAVE_FUNCTION_GRAPH_TRACER if X86_32 || (X86_64 && DYNAMIC_FTRACE) select HAVE_FUNCTION_TRACER select HAVE_GCC_PLUGINS
arch/x86/kernel/ftrace_64.S +29 -8
@@ -348,21 +348,42 @@ STACK_FRAME_NON_STANDARD_FP(__fentry__) SYM_CODE_START(return_to_handler) UNWIND_HINT_UNDEFINED ANNOTATE_NOENDBR - subq $24, %rsp + /* + * Save the registers requires for ftrace_regs; + * rax, rcx, rdx, rdi, rsi, r8, r9 and rbp + */ + subq $(FRAME_SIZE), %rsp + movq %rax, RAX(%rsp) + movq %rcx, RCX(%rsp) + movq %rdx, RDX(%rsp) + movq %rsi, RSI(%rsp) + movq %rdi, RDI(%rsp) + movq %r8, R8(%rsp) + movq %r9, R9(%rsp) + movq %rbp, RBP(%rsp) + /* + * orig_ax is not cleared because it is used for indicating the direct + * trampoline in the fentry. And rip is not set because we don't know + * the correct return address here. + */ + + leaq FRAME_SIZE(%rsp), %rcx + movq %rcx, RSP(%rsp) - /* Save the return values */ - movq %rax, (%rsp) - movq %rdx, 8(%rsp) - movq %rbp, 16(%rsp) movq %rsp, %rdi call ftrace_return_to_handler movq %rax, %rdi - movq 8(%rsp), %rdx - movq (%rsp), %rax - addq $24, %rsp + /* + * Restore only rax and rdx because other registers are not used + * for return value nor callee saved. Caller will reuse/recover it. + */ + movq RDX(%rsp), %rdx + movq RAX(%rsp), %rax + + addq $(FRAME_SIZE), %rsp /* * Jump back to the old return address. This cannot be JMP_NOSPEC rdi * since IBT would demand that contain ENDBR, which simply isn't so for
Masami Hiramatsu (Google)
maintainerPATCH v3 22/33 tracing: Rename ftrace_regs_return_value to ftrace_regs_get_return_value
In reply to Masami Hiramatsu (Google)From: Masami Hiramatsu (Google) <[email protected]>
Rename ftrace_regs_return_value to ftrace_regs_get_return_value as same as other ftrace_regs_get/set_* APIs.
---
Changes in v3:
- Newly added.
--- arch/loongarch/include/asm/ftrace.h | 2 +- arch/powerpc/include/asm/ftrace.h | 2 +- arch/s390/include/asm/ftrace.h | 2 +- arch/x86/include/asm/ftrace.h | 2 +- include/linux/ftrace.h | 2 +- kernel/trace/fgraph.c | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-)
arch/loongarch/include/asm/ftrace.h +1 -1
@@ -70,7 +70,7 @@ ftrace_regs_set_instruction_pointer(struct ftrace_regs *fregs, unsigned long ip) regs_get_kernel_argument(&(fregs)->regs, n)#define ftrace_regs_get_stack_pointer (fregs) \ kernel_stack_pointer(&(fregs)->regs) -#define ftrace_regs_return_value (fregs) \ +#define ftrace_regs_get_return_value (fregs) \ regs_return_value(&(fregs)->regs)#define ftrace_regs_set_return_value (fregs, ret) \ regs_set_return_value(&(fregs)->regs, ret)
arch/powerpc/include/asm/ftrace.h +1 -1
@@ -69,7 +69,7 @@ ftrace_regs_get_instruction_pointer(struct ftrace_regs *fregs) regs_get_kernel_argument(&(fregs)->regs, n)#define ftrace_regs_get_stack_pointer (fregs) \ kernel_stack_pointer(&(fregs)->regs) -#define ftrace_regs_return_value (fregs) \ +#define ftrace_regs_get_return_value (fregs) \ regs_return_value(&(fregs)->regs)#define ftrace_regs_set_return_value (fregs, ret) \ regs_set_return_value(&(fregs)->regs, ret)
arch/s390/include/asm/ftrace.h +1 -1
@@ -88,7 +88,7 @@ ftrace_regs_set_instruction_pointer(struct ftrace_regs *fregs, regs_get_kernel_argument(&(fregs)->regs, n)#define ftrace_regs_get_stack_pointer (fregs) \ kernel_stack_pointer(&(fregs)->regs) -#define ftrace_regs_return_value (fregs) \ +#define ftrace_regs_get_return_value (fregs) \ regs_return_value(&(fregs)->regs)#define ftrace_regs_set_return_value (fregs, ret) \ regs_set_return_value(&(fregs)->regs, ret)
arch/x86/include/asm/ftrace.h +1 -1
@@ -64,7 +64,7 @@ arch_ftrace_get_regs(struct ftrace_regs *fregs) regs_get_kernel_argument(&(fregs)->regs, n)#define ftrace_regs_get_stack_pointer (fregs) \ kernel_stack_pointer(&(fregs)->regs) -#define ftrace_regs_return_value (fregs) \ +#define ftrace_regs_get_return_value (fregs) \ regs_return_value(&(fregs)->regs)#define ftrace_regs_set_return_value (fregs, ret) \ regs_set_return_value(&(fregs)->regs, ret)
include/linux/ftrace.h +1 -1
@@ -187,7 +187,7 @@ static __always_inline bool ftrace_regs_has_args(struct ftrace_regs *fregs) regs_get_kernel_argument(ftrace_get_regs(fregs), n)#define ftrace_regs_get_stack_pointer (fregs) \ kernel_stack_pointer(ftrace_get_regs(fregs)) -#define ftrace_regs_return_value (fregs) \ +#define ftrace_regs_get_return_value (fregs) \ regs_return_value(ftrace_get_regs(fregs))#define ftrace_regs_set_return_value (fregs, ret) \ regs_set_return_value(ftrace_get_regs(fregs), ret)
kernel/trace/fgraph.c +1 -1
@@ -754,7 +754,7 @@ static void fgraph_call_retfunc(struct ftrace_regs *fregs, trace.rettime =trace_clock_local ();#ifdef CONFIG_FUNCTION_GRAPH_RETVAL if (fregs) - trace.retval =ftrace_regs_return_value (fregs); + trace.retval =ftrace_regs_get_return_value (fregs);else trace.retval =fgraph_ret_regs_return_value (ret_regs);#endif
Masami Hiramatsu (Google)
maintainerPATCH v3 23/33 arm64: ftrace: Enable HAVE_FUNCTION_GRAPH_FREGS
In reply to Masami Hiramatsu (Google)From: Masami Hiramatsu (Google) <[email protected]>
Enable CONFIG_HAVE_FUNCTION_GRAPH_FREGS on arm64. Note that this depends on HAVE_DYNAMIC_FTRACE_WITH_ARGS which is enabled if the compiler supports "-fpatchable-function-entry=2". If not, it continue to use ftrace_ret_regs.
---
Changes in v3:
- Newly added.
--- arch/arm64/Kconfig | 2 ++ arch/arm64/include/asm/ftrace.h | 6 ++++++ arch/arm64/kernel/entry-ftrace.S | 28 ++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+)
arch/arm64/Kconfig +2 -0
@@ -192,6 +192,8 @@ config ARM64 select HAVE_DYNAMIC_FTRACE select HAVE_DYNAMIC_FTRACE_WITH_ARGS \ if $(cc-option,-fpatchable-function-entry=2) + select HAVE_FUNCTION_GRAPH_FREGS \ + if HAVE_DYNAMIC_FTRACE_WITH_ARGS select HAVE_DYNAMIC_FTRACE_WITH_DIRECT_CALLS \ if DYNAMIC_FTRACE_WITH_ARGS && DYNAMIC_FTRACE_WITH_CALL_OPS select HAVE_DYNAMIC_FTRACE_WITH_CALL_OPS \
arch/arm64/include/asm/ftrace.h +6 -0
@@ -131,6 +131,12 @@ ftrace_regs_set_return_value(struct ftrace_regs *fregs, fregs->regs[0 ] = ret; } +static __always_inline unsigned long +ftrace_regs_get_frame_pointer (struct ftrace_regs *fregs) +{ +return fregs->fp; +} + static __always_inline voidftrace_override_function_with_return (struct ftrace_regs *fregs) {
arch/arm64/kernel/entry-ftrace.S +28 -0
@@ -328,6 +328,33 @@ SYM_FUNC_END(ftrace_stub_graph) * Run ftrace_return_to_handler() before going back to parent. * @fp is checked against the value passed by ftrace_graph_caller(). */ +#ifdef CONFIG_HAVE_FUNCTION_GRAPH_FREGS +SYM_CODE_START(return_to_handler) + /* save ftrace_regs except for PC */ + sub sp, sp, #FREGS_SIZE + stp x0, x1, [sp, #FREGS_X0] + stp x2, x3, [sp, #FREGS_X2] + stp x4, x5, [sp, #FREGS_X4] + stp x6, x7, [sp, #FREGS_X6] + str x8, [sp, #FREGS_X8] + str x29, [sp, #FREGS_FP] + str x9, [sp, #FREGS_LR] + str x10, [sp, #FREGS_SP] + + mov x0, sp + bl ftrace_return_to_handler // addr = ftrace_return_to_hander(fregs); + mov x30, x0 // restore the original return address + + /* restore return value regs */ + ldp x0, x1, [sp, #FREGS_X0] + ldp x2, x3, [sp, #FREGS_X2] + ldp x4, x5, [sp, #FREGS_X4] + ldp x6, x7, [sp, #FREGS_X6] + add sp, sp, #FREGS_SIZE + + ret +SYM_CODE_END(return_to_handler) +#else /* !CONFIG_HAVE_FUNCTION_GRAPH_FREGS */ SYM_CODE_START(return_to_handler) /* save return value regs */ sub sp, sp, #FGRET_REGS_SIZE
@@ -350,4 +377,5 @@ SYM_CODE_START(return_to_handler) ret SYM_CODE_END(return_to_handler) +#endif /* CONFIG_HAVE_FUNCTION_GRAPH_FREGS */ #endif /* CONFIG_FUNCTION_GRAPH_TRACER */
Masami Hiramatsu (Google)
maintainerPATCH v3 24/33 fprobe: Use ftrace_regs in fprobe entry handler
In reply to Masami Hiramatsu (Google)From: Masami Hiramatsu (Google) <[email protected]>
This allows fprobes to be available with CONFIG_DYNAMIC_FTRACE_WITH_ARGS instead of CONFIG_DYNAMIC_FTRACE_WITH_REGS, then we can enable fprobe on arm64.
---
Changes from previous series: NOTHING, just forward ported.
--- include/linux/fprobe.h | 2 +- kernel/trace/Kconfig | 3 ++- kernel/trace/bpf_trace.c | 10 +++++++--- kernel/trace/fprobe.c | 4 ++-- kernel/trace/trace_fprobe.c | 6 +++++- lib/test_fprobe.c | 4 ++-- samples/fprobe/fprobe_example.c | 2 +- 7 files changed, 20 insertions(+), 11 deletions(-)
include/linux/fprobe.h +1 -1
@@ -35,7 +35,7 @@ struct fprobe {int nr_maxactive;int (*entry_handler)(struct fprobe *fp,unsigned long entry_ip, -unsigned long ret_ip,struct pt_regs *regs, +unsigned long ret_ip,struct ftrace_regs *regs,void *entry_data); void (*exit_handler)(struct fprobe *fp, unsigned long entry_ip, unsigned long ret_ip, struct pt_regs *regs,
kernel/trace/Kconfig +2 -1
@@ -290,7 +290,7 @@ config DYNAMIC_FTRACE_WITH_ARGS config FPROBE bool "Kernel Function Probe (fprobe)" depends on FUNCTION_TRACER - depends on DYNAMIC_FTRACE_WITH_REGS + depends on DYNAMIC_FTRACE_WITH_REGS || DYNAMIC_FTRACE_WITH_ARGS depends on HAVE_RETHOOK select RETHOOK default n
@@ -675,6 +675,7 @@ config FPROBE_EVENTS select TRACING select PROBE_EVENTS select DYNAMIC_EVENTS + depends on DYNAMIC_FTRACE_WITH_REGS default y help This allows user to add tracing events on the function entry and
kernel/trace/bpf_trace.c +7 -3
@@ -2501,7 +2501,7 @@ static int __init bpf_event_init(void)fs_initcall (bpf_event_init);#endif /* CONFIG_MODULES */ -#ifdef CONFIG_FPROBE +#if defined(CONFIG_FPROBE) && defined(CONFIG_DYNAMIC_FTRACE_WITH_REGS) struct bpf_kprobe_multi_link { struct bpf_link link; struct fprobe fp;
@@ -2729,10 +2729,14 @@ kprobe_multi_link_prog_run(struct bpf_kprobe_multi_link *link,static int kprobe_multi_link_handler (struct fprobe *fp,unsigned long fentry_ip, -unsigned long ret_ip,struct pt_regs *regs, +unsigned long ret_ip,struct ftrace_regs *fregs,void *data) {struct bpf_kprobe_multi_link *link; +struct pt_regs *regs =ftrace_get_regs (fregs); + +if (!regs) +return 0 ; link = container_of(fp, struct bpf_kprobe_multi_link, fp);kprobe_multi_link_prog_run (link,get_entry_ip (fentry_ip), regs);
@@ -3004,7 +3008,7 @@ int bpf_kprobe_multi_link_attach(const union bpf_attr *attr, struct bpf_prog *prkvfree (cookies); return err; } -#else /* !CONFIG_FPROBE */ +#else /* !CONFIG_FPROBE || !CONFIG_DYNAMIC_FTRACE_WITH_REGS */ int bpf_kprobe_multi_link_attach (const union bpf_attr *attr,struct bpf_prog *prog) {return -EOPNOTSUPP ;
kernel/trace/fprobe.c +2 -2
@@ -46,7 +46,7 @@ static inline void __fprobe_handler(unsigned long ip, unsigned long parent_ip, }if (fp->entry_handler) - ret = fp->entry_handler (fp, ip, parent_ip,ftrace_get_regs (fregs), entry_data); + ret = fp->entry_handler (fp, ip, parent_ip, fregs, entry_data);/* If entry_handler returns !0, nmissed is not counted. */ if (rh) {
@@ -182,7 +182,7 @@ static void fprobe_init(struct fprobe *fp) fp->ops.func = fprobe_kprobe_handler; else fp->ops.func = fprobe_handler; - fp->ops.flags |=FTRACE_OPS_FL_SAVE_REGS ; + fp->ops.flags |=FTRACE_OPS_FL_SAVE_ARGS ; }static int fprobe_init_rethook (struct fprobe *fp,int num)
kernel/trace/trace_fprobe.c +5 -1
@@ -320,12 +320,16 @@ NOKPROBE_SYMBOL(fexit_perf_func);#endif /* CONFIG_PERF_EVENTS */ static int fentry_dispatcher (struct fprobe *fp,unsigned long entry_ip, -unsigned long ret_ip,struct pt_regs *regs, +unsigned long ret_ip,struct ftrace_regs *fregs,void *entry_data) { struct trace_fprobe *tf = container_of(fp, struct trace_fprobe, fp); +struct pt_regs *regs =ftrace_get_regs (fregs);int ret =0 ; +if (!regs) +return 0 ; +if (trace_probe_test_flag (&tf->tp,TP_FLAG_TRACE ))fentry_trace_func (tf, entry_ip, regs);#ifdef CONFIG_PERF_EVENTS
lib/test_fprobe.c +2 -2
@@ -40,7 +40,7 @@ static noinline u32 fprobe_selftest_nest_target(u32 value, u32 (*nest)(u32)) static notrace int fp_entry_handler(struct fprobe *fp, unsigned long ip,unsigned long ret_ip, -struct pt_regs *regs,void *data) +struct ftrace_regs *fregs,void *data) {KUNIT_EXPECT_FALSE (current_test,preemptible ());/* This can be called on the fprobe_selftest_target and the fprobe_selftest_target2 */
@@ -81,7 +81,7 @@ static notrace void fp_exit_handler(struct fprobe *fp, unsigned long ip, static notrace int nest_entry_handler(struct fprobe *fp, unsigned long ip,unsigned long ret_ip, -struct pt_regs *regs,void *data) +struct ftrace_regs *fregs,void *data) {KUNIT_EXPECT_FALSE (current_test,preemptible ());return 0 ;
samples/fprobe/fprobe_example.c +1 -1
@@ -50,7 +50,7 @@ static void show_backtrace(void)static int sample_entry_handler (struct fprobe *fp,unsigned long ip,unsigned long ret_ip, -struct pt_regs *regs,void *data) +struct ftrace_regs *fregs,void *data) {if (use_trace)/*
Masami Hiramatsu (Google)
maintainerPATCH v3 25/33 fprobe: Use ftrace_regs in fprobe exit handler
In reply to Masami Hiramatsu (Google)From: Masami Hiramatsu (Google) <[email protected]>
Change the fprobe exit handler to use ftrace_regs structure instead of pt_regs. This also introduce HAVE_PT_REGS_TO_FTRACE_REGS_CAST which means the ftrace_regs's memory layout is equal to the pt_regs so that those are able to cast. Fprobe introduces a new dependency with that.
---
Changes in v3:
- Use ftrace_regs_get_return_value()
Changes from previous series: NOTHING, just forward ported.
--- arch/loongarch/Kconfig | 1 + arch/s390/Kconfig | 1 + arch/x86/Kconfig | 1 + include/linux/fprobe.h | 2 +- include/linux/ftrace.h | 5 +++++ kernel/trace/Kconfig | 8 ++++++++ kernel/trace/bpf_trace.c | 6 +++++- kernel/trace/fprobe.c | 3 ++- kernel/trace/trace_fprobe.c | 6 +++++- lib/test_fprobe.c | 6 +++--- samples/fprobe/fprobe_example.c | 2 +- 11 files changed, 33 insertions(+), 8 deletions(-)
arch/loongarch/Kconfig +1 -0
@@ -108,6 +108,7 @@ config LOONGARCH select HAVE_DMA_CONTIGUOUS select HAVE_DYNAMIC_FTRACE select HAVE_DYNAMIC_FTRACE_WITH_ARGS + select HAVE_PT_REGS_TO_FTRACE_REGS_CAST select HAVE_DYNAMIC_FTRACE_WITH_DIRECT_CALLS select HAVE_DYNAMIC_FTRACE_WITH_REGS select HAVE_EBPF_JIT
arch/s390/Kconfig +1 -0
@@ -167,6 +167,7 @@ config S390 select HAVE_DMA_CONTIGUOUS select HAVE_DYNAMIC_FTRACE select HAVE_DYNAMIC_FTRACE_WITH_ARGS + select HAVE_PT_REGS_TO_FTRACE_REGS_CAST select HAVE_DYNAMIC_FTRACE_WITH_DIRECT_CALLS select HAVE_DYNAMIC_FTRACE_WITH_REGS select HAVE_EBPF_JIT if HAVE_MARCH_Z196_FEATURES
arch/x86/Kconfig +1 -0
@@ -209,6 +209,7 @@ config X86 select HAVE_DYNAMIC_FTRACE select HAVE_DYNAMIC_FTRACE_WITH_REGS select HAVE_DYNAMIC_FTRACE_WITH_ARGS if X86_64 + select HAVE_PT_REGS_TO_FTRACE_REGS_CAST if X86_64 select HAVE_DYNAMIC_FTRACE_WITH_DIRECT_CALLS select HAVE_SAMPLE_FTRACE_DIRECT if X86_64 select HAVE_SAMPLE_FTRACE_DIRECT_MULTI if X86_64
include/linux/fprobe.h +1 -1
@@ -38,7 +38,7 @@ struct fprobe { unsigned long ret_ip, struct ftrace_regs *regs, void *entry_data);void (*exit_handler)(struct fprobe *fp,unsigned long entry_ip, -unsigned long ret_ip,struct pt_regs *regs, +unsigned long ret_ip,struct ftrace_regs *fregs,void *entry_data); };
include/linux/ftrace.h +5 -0
@@ -159,6 +159,11 @@ struct ftrace_regs {#define ftrace_regs_set_instruction_pointer (fregs, ip) do { } while (0)#endif /* CONFIG_HAVE_DYNAMIC_FTRACE_WITH_ARGS */ +#ifdef CONFIG_HAVE_PT_REGS_TO_FTRACE_REGS_CAST + +static_assert (sizeof (struct pt_regs ) ==sizeof (struct ftrace_regs )); + +#endif /* CONFIG_HAVE_PT_REGS_TO_FTRACE_REGS_CAST */ static __always_inline struct pt_regs *ftrace_get_regs(struct ftrace_regs *fregs) {
kernel/trace/Kconfig +8 -0
@@ -60,6 +60,13 @@ config HAVE_DYNAMIC_FTRACE_WITH_ARGS This allows for use of ftrace_regs_get_argument() and ftrace_regs_get_stack_pointer(). +config HAVE_PT_REGS_TO_FTRACE_REGS_CAST + bool + help + If this is set, the memory layout of the ftrace_regs data structure + is the same as the pt_regs. So the pt_regs is possible to be casted + to ftrace_regs. + config HAVE_DYNAMIC_FTRACE_NO_PATCHABLE bool help
@@ -291,6 +298,7 @@ config FPROBE bool "Kernel Function Probe (fprobe)" depends on FUNCTION_TRACER depends on DYNAMIC_FTRACE_WITH_REGS || DYNAMIC_FTRACE_WITH_ARGS + depends on HAVE_PT_REGS_TO_FTRACE_REGS_CAST || !HAVE_DYNAMIC_FTRACE_WITH_ARGS depends on HAVE_RETHOOK select RETHOOK default n
kernel/trace/bpf_trace.c +5 -1
@@ -2745,10 +2745,14 @@ kprobe_multi_link_handler(struct fprobe *fp, unsigned long fentry_ip,static void kprobe_multi_link_exit_handler (struct fprobe *fp,unsigned long fentry_ip, -unsigned long ret_ip,struct pt_regs *regs, +unsigned long ret_ip,struct ftrace_regs *fregs,void *data) {struct bpf_kprobe_multi_link *link; +struct pt_regs *regs =ftrace_get_regs (fregs); + +if (!regs) +return ; link = container_of(fp, struct bpf_kprobe_multi_link, fp);kprobe_multi_link_prog_run (link,get_entry_ip (fentry_ip), regs);
kernel/trace/fprobe.c +2 -1
@@ -124,6 +124,7 @@ static void fprobe_exit_handler(struct rethook_node *rh, void *data, {struct fprobe *fp = (struct fprobe *)data;struct fprobe_rethook_node *fpr; +struct ftrace_regs *fregs = (struct ftrace_regs *)regs;int bit;if (!fp ||fprobe_disabled (fp))
@@ -141,7 +142,7 @@ static void fprobe_exit_handler(struct rethook_node *rh, void *data,return ; } - fp->exit_handler (fp, fpr->entry_ip, ret_ip, regs, + fp->exit_handler (fp, fpr->entry_ip, ret_ip, fregs, fp->entry_data_size ? (void *)fpr->data :NULL );ftrace_test_recursion_unlock (bit); }
kernel/trace/trace_fprobe.c +5 -1
@@ -341,10 +341,14 @@ static int fentry_dispatcher(struct fprobe *fp, unsigned long entry_ip,NOKPROBE_SYMBOL (fentry_dispatcher);static void fexit_dispatcher (struct fprobe *fp,unsigned long entry_ip, -unsigned long ret_ip,struct pt_regs *regs, +unsigned long ret_ip,struct ftrace_regs *fregs,void *entry_data) { struct trace_fprobe *tf = container_of(fp, struct trace_fprobe, fp); +struct pt_regs *regs =ftrace_get_regs (fregs); + +if (!regs) +return ;if (trace_probe_test_flag (&tf->tp,TP_FLAG_TRACE ))fexit_trace_func (tf, entry_ip, ret_ip, regs);
lib/test_fprobe.c +3 -3
@@ -59,9 +59,9 @@ static notrace int fp_entry_handler(struct fprobe *fp, unsigned long ip, static notrace void fp_exit_handler(struct fprobe *fp, unsigned long ip,unsigned long ret_ip, -struct pt_regs *regs,void *data) +struct ftrace_regs *fregs,void *data) { -unsigned long ret =regs_return_value (regs); +unsigned long ret =ftrace_regs_get_return_value (fregs);KUNIT_EXPECT_FALSE (current_test,preemptible ());if (ip != target_ip) {
@@ -89,7 +89,7 @@ static notrace int nest_entry_handler(struct fprobe *fp, unsigned long ip, static notrace void nest_exit_handler(struct fprobe *fp, unsigned long ip,unsigned long ret_ip, -struct pt_regs *regs,void *data) +struct ftrace_regs *fregs,void *data) {KUNIT_EXPECT_FALSE (current_test,preemptible ());KUNIT_EXPECT_EQ (current_test, ip, target_nest_ip);
samples/fprobe/fprobe_example.c +1 -1
@@ -67,7 +67,7 @@ static int sample_entry_handler(struct fprobe *fp, unsigned long ip, }static void sample_exit_handler (struct fprobe *fp,unsigned long ip, -unsigned long ret_ip,struct pt_regs *regs, +unsigned long ret_ip,struct ftrace_regs *regs,void *data) {unsigned long rip = ret_ip;
Masami Hiramatsu (Google)
maintainerPATCH v3 26/33 tracing: Add ftrace_partial_regs() for converting ftrace_regs to pt_regs
In reply to Masami Hiramatsu (Google)From: Masami Hiramatsu (Google) <[email protected]>
Add ftrace_partial_regs() which converts the ftrace_regs to pt_regs. If the architecture defines its own ftrace_regs, this copies partial registers to pt_regs and returns it. If not, ftrace_regs is the same as pt_regs and ftrace_partial_regs() will return ftrace_regs::regs.
---
Changes from previous series: NOTHING, just forward ported.
--- arch/arm64/include/asm/ftrace.h | 11 +++++++++++ include/linux/ftrace.h | 17 +++++++++++++++++ 2 files changed, 28 insertions(+)
arch/arm64/include/asm/ftrace.h +11 -0
@@ -143,6 +143,17 @@ ftrace_override_function_with_return(struct ftrace_regs *fregs) fregs->pc = fregs->lr; } +static __always_inline struct pt_regs * +ftrace_partial_regs (const struct ftrace_regs *fregs,struct pt_regs *regs) +{ +memcpy (regs->regs, fregs->regs,sizeof (u64) *9 ); + regs->sp = fregs->sp; + regs->pc = fregs->pc; + regs->regs[29 ] = fregs->fp; + regs->regs[30 ] = fregs->lr; +return regs; +} +int ftrace_regs_query_register_offset (const char *name);int ftrace_init_nop (struct module *mod,struct dyn_ftrace *rec);
include/linux/ftrace.h +17 -0
@@ -173,6 +173,23 @@ static __always_inline struct pt_regs *ftrace_get_regs(struct ftrace_regs *fregsreturn arch_ftrace_get_regs (fregs); } +#if !defined(CONFIG_HAVE_DYNAMIC_FTRACE_WITH_ARGS ) || \ + defined(CONFIG_HAVE_PT_REGS_TO_FTRACE_REGS_CAST ) + +static __always_inline struct pt_regs * +ftrace_partial_regs (struct ftrace_regs *fregs,struct pt_regs *regs) +{ +/* +* If CONFIG_HAVE_PT_REGS_TO_FTRACE_REGS_CAST=y, ftrace_regs memory +* layout is the same as pt_regs. So always returns that address. +* Since arch_ftrace_get_regs() will check some members and may return +* NULL, we can not use it. +*/ +return &fregs->regs; +} + +#endif /* !CONFIG_HAVE_DYNAMIC_FTRACE_WITH_ARGS || CONFIG_HAVE_PT_REGS_TO_FTRACE_REGS_CAST */ +/* * When true, the ftrace_regs_{get,set}_*() functions may be used on fregs. * Note: this can be true even when ftrace_get_regs() cannot provide a pt_regs.
Masami Hiramatsu (Google)
maintainerPATCH v3 27/33 tracing: Add ftrace_fill_perf_regs() for perf event
In reply to Masami Hiramatsu (Google)From: Masami Hiramatsu (Google) <[email protected]>
Add ftrace_fill_perf_regs() which should be compatible with the perf_fetch_caller_regs(). In other words, the pt_regs returned from the ftrace_fill_perf_regs() must satisfy 'user_mode(regs) == false' and can be used for stack tracing.
---
Changes from previous series: NOTHING, just forward ported.
--- arch/arm64/include/asm/ftrace.h | 7 +++++++ arch/powerpc/include/asm/ftrace.h | 7 +++++++ arch/s390/include/asm/ftrace.h | 5 +++++ arch/x86/include/asm/ftrace.h | 7 +++++++ include/linux/ftrace.h | 31 +++++++++++++++++++++++++++++++ 5 files changed, 57 insertions(+)
arch/arm64/include/asm/ftrace.h +7 -0
@@ -154,6 +154,13 @@ ftrace_partial_regs(const struct ftrace_regs *fregs, struct pt_regs *regs)return regs; } +#define arch_ftrace_fill_perf_regs (fregs, _regs) do { \ + (_regs)->pc = (fregs)->pc; \ + (_regs)->regs[29] = (fregs)->fp; \ + (_regs)->sp = (fregs)->sp; \ + (_regs)->pstate = PSR_MODE_EL1h; \ + } while (0) +int ftrace_regs_query_register_offset (const char *name);int ftrace_init_nop (struct module *mod,struct dyn_ftrace *rec);
arch/powerpc/include/asm/ftrace.h +7 -0
@@ -52,6 +52,13 @@ static __always_inline struct pt_regs *arch_ftrace_get_regs(struct ftrace_regs *return fregs->regs.msr ? &fregs->regs :NULL ; } +#define arch_ftrace_fill_perf_regs (fregs, _regs) do { \ + (_regs)->result = 0; \ + (_regs)->nip = (fregs)->regs.nip; \ + (_regs)->gpr[1] = (fregs)->regs.gpr[1]; \ + asm volatile("mfmsr %0" : "=r" ((_regs)->msr)); \ + } while (0) + static __always_inline voidftrace_regs_set_instruction_pointer (struct ftrace_regs *fregs,unsigned long ip)
arch/s390/include/asm/ftrace.h +5 -0
@@ -97,6 +97,11 @@ ftrace_regs_set_instruction_pointer(struct ftrace_regs *fregs,#define ftrace_regs_query_register_offset (name) \ regs_query_register_offset(name) +#define arch_ftrace_fill_perf_regs (fregs, _regs) do { \ + (_regs)->psw.addr = (fregs)->regs.psw.addr; \ + (_regs)->gprs[15] = (fregs)->regs.gprs[15]; \ + } while (0) +#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS /* * When an ftrace registered caller is tracing a function that is
arch/x86/include/asm/ftrace.h +7 -0
@@ -54,6 +54,13 @@ arch_ftrace_get_regs(struct ftrace_regs *fregs)return &fregs->regs; } +#define arch_ftrace_fill_perf_regs (fregs, _regs) do { \ + (_regs)->ip = (fregs)->regs.ip; \ + (_regs)->sp = (fregs)->regs.sp; \ + (_regs)->cs = __KERNEL_CS; \ + (_regs)->flags = 0; \ + } while (0) +#define ftrace_regs_set_instruction_pointer (fregs, _ip) \ do { (fregs)->regs.ip = (_ip); } while (0)
include/linux/ftrace.h +31 -0
@@ -190,6 +190,37 @@ ftrace_partial_regs(struct ftrace_regs *fregs, struct pt_regs *regs)#endif /* !CONFIG_HAVE_DYNAMIC_FTRACE_WITH_ARGS || CONFIG_HAVE_PT_REGS_TO_FTRACE_REGS_CAST */ +#ifdef CONFIG_HAVE_DYNAMIC_FTRACE_WITH_ARGS + +/* +* Please define arch dependent pt_regs which compatible to the +* perf_arch_fetch_caller_regs() but based on ftrace_regs. +* This requires +* - user_mode(_regs) returns false (always kernel mode). +* - able to use the _regs for stack trace. +*/ +#ifndef arch_ftrace_fill_perf_regs +/* As same as perf_arch_fetch_caller_regs(), do nothing by default */ +#define arch_ftrace_fill_perf_regs (fregs, _regs) do {} while (0) +#endif + +static __always_inline struct pt_regs * +ftrace_fill_perf_regs (struct ftrace_regs *fregs,struct pt_regs *regs) +{ +arch_ftrace_fill_perf_regs (fregs, regs); +return regs; +} + +#else /* !CONFIG_HAVE_DYNAMIC_FTRACE_WITH_ARGS */ + +static __always_inline struct pt_regs * +ftrace_fill_perf_regs (struct ftrace_regs *fregs,struct pt_regs *regs) +{ +return &fregs->regs; +} + +#endif +/* * When true, the ftrace_regs_{get,set}_*() functions may be used on fregs. * Note: this can be true even when ftrace_get_regs() cannot provide a pt_regs.
Masami Hiramatsu (Google)
maintainerPATCH v3 28/33 fprobe: Rewrite fprobe on function-graph tracer
In reply to Masami Hiramatsu (Google)From: Masami Hiramatsu (Google) <[email protected]>
Rewrite fprobe implementation on function-graph tracer. Major API changes are:
- 'nr_maxactive' field is deprecated.
- This depends on CONFIG_DYNAMIC_FTRACE_WITH_ARGS or !CONFIG_HAVE_DYNAMIC_FTRACE_WITH_ARGS, and CONFIG_HAVE_FUNCTION_GRAPH_FREGS. So currently works only on x86_64.
- Currently the entry size is limited in 15 * sizeof(long).
- If there is too many fprobe exit handler set on the same function, it will fail to probe.
---
Changes in v3:
- Update for new reserve_data/retrieve_data API.
- Fix internal push/pop on fgraph data logic so that it can correctly save/restore the returning fprobes.
Changes in v2:
- Add more lockdep_assert_held(fprobe_mutex)
- Use READ_ONCE() and WRITE_ONCE() for fprobe_hlist_node::fp.
- Add NOKPROBE_SYMBOL() for the functions which is called from entry/exit callback.
--- include/linux/fprobe.h | 54 +++- kernel/trace/Kconfig | 8 - kernel/trace/fprobe.c | 638 ++++++++++++++++++++++++++++++++++-------------- lib/test_fprobe.c | 45 --- 4 files changed, 493 insertions(+), 252 deletions(-)
include/linux/fprobe.h +40 -14
@@ -5,32 +5,56 @@#include <linux/compiler.h> #include <linux/ftrace.h> -#include <linux/rethook.h> +#include <linux/rcupdate.h> +#include <linux/refcount.h> +#include <linux/slab.h> + +struct fprobe ; + +/** +* strcut fprobe_hlist_node - address based hash list node for fprobe. +* +* @hlist: The hlist node for address search hash table. +* @addr: The address represented by this. +* @fp: The fprobe which owns this. +*/ +struct fprobe_hlist_node { +struct hlist_node hlist; +unsigned long addr; +struct fprobe *fp; +}; + +/** +* struct fprobe_hlist - hash list nodes for fprobe. +* +* @hlist: The hlist node for existence checking hash table. +* @rcu: rcu_head for RCU deferred release. +* @fp: The fprobe which owns this fprobe_hlist. +* @size: The size of @array. +* @array: The fprobe_hlist_node for each address to probe. +*/ +struct fprobe_hlist { +struct hlist_node hlist; +struct rcu_head rcu; +struct fprobe *fp; +int size; +struct fprobe_hlist_node array[]; +};/** * struct fprobe - ftrace based probe. -* @ops: The ftrace_ops. +* * @nmissed: The counter for missing events. * @flags: The status flag. -* @rethook: The rethook data structure. (internal data) * @entry_data_size: The private data storage size. -* @nr_maxactive: The max number of active functions. +* @nr_maxactive: The max number of active functions. (*deprecated) * @entry_handler: The callback function for function entry. * @exit_handler: The callback function for function exit. +* @hlist_array: The fprobe_hlist for fprobe search from IP hash table. */ struct fprobe { -#ifdef CONFIG_FUNCTION_TRACER -/* -* If CONFIG_FUNCTION_TRACER is not set, CONFIG_FPROBE is disabled too. -* But user of fprobe may keep embedding the struct fprobe on their own -* code. To avoid build error, this will keep the fprobe data structure -* defined here, but remove ftrace_ops data structure. -*/ -struct ftrace_ops ops; -#endif unsigned long nmissed;unsigned int flags; -struct rethook *rethook;size_t entry_data_size;int nr_maxactive;
@@ -40,6 +64,8 @@ struct fprobe {void (*exit_handler)(struct fprobe *fp,unsigned long entry_ip,unsigned long ret_ip,struct ftrace_regs *fregs,void *entry_data); + +struct fprobe_hlist *hlist_array; };/* This fprobe is soft-disabled. */
kernel/trace/Kconfig +3 -5
@@ -296,11 +296,9 @@ config DYNAMIC_FTRACE_WITH_ARGS config FPROBE bool "Kernel Function Probe (fprobe)" - depends on FUNCTION_TRACER - depends on DYNAMIC_FTRACE_WITH_REGS || DYNAMIC_FTRACE_WITH_ARGS - depends on HAVE_PT_REGS_TO_FTRACE_REGS_CAST || !HAVE_DYNAMIC_FTRACE_WITH_ARGS - depends on HAVE_RETHOOK - select RETHOOK + depends on FUNCTION_GRAPH_TRACER + depends on HAVE_FUNCTION_GRAPH_FREGS + depends on DYNAMIC_FTRACE_WITH_ARGS || !HAVE_DYNAMIC_FTRACE_WITH_ARGS default n help This option enables kernel function probe (fprobe) based on ftrace.
kernel/trace/fprobe.c +450 -188
768 lines not on this page · raw message
lib/test_fprobe.c +0 -45
85 lines not on this page · raw message
Re: [PATCH v3 28/33] fprobe: Rewrite fprobe on function-graph tracer
Quoted 13 lines · Masami Hiramatsu (Google)
From: Masami Hiramatsu (Google) <[email protected]>
Rewrite fprobe implementation on function-graph tracer. Major API changes are:
- 'nr_maxactive' field is deprecated.
- This depends onCONFIG_DYNAMIC_FTRACE_WITH_ARGSor !CONFIG_HAVE_DYNAMIC_FTRACE_WITH_ARGS, andCONFIG_HAVE_FUNCTION_GRAPH_FREGS. So currently works only onx86_64.
- Currently the entry size is limited in 15 * sizeof(long).
- If there is too many fprobe exit handler set on the same function, it will fail to probe.
Quoted 6 lines
Signed-off-by Masami Hiramatsu (Google) <[email protected]>---
Changes in v3:
- Update for new reserve_data/retrieve_data API.
- Fix internal push/pop on fgraph data logic so that it can correctly save/restore the returning fprobes.
hi,
looks like this one conflicts with recent:
4bbd93455659 kprobes: kretprobe scalability improvement
jirka
Masami Hiramatsu (Google)
maintainerRe: [PATCH v3 28/33] fprobe: Rewrite fprobe on function-graph tracer
Quoted 21 lines · On Tue, 28 Nov 2023 11:53:19 +0100 Jiri Olsa <[email protected]> wrote:
Quoted 13 lines · Masami Hiramatsu (Google)
From: Masami Hiramatsu (Google) <[email protected]>
Rewrite fprobe implementation on function-graph tracer. Major API changes are:
- 'nr_maxactive' field is deprecated.
- This depends onCONFIG_DYNAMIC_FTRACE_WITH_ARGSor !CONFIG_HAVE_DYNAMIC_FTRACE_WITH_ARGS, andCONFIG_HAVE_FUNCTION_GRAPH_FREGS. So currently works only onx86_64.
- Currently the entry size is limited in 15 * sizeof(long).
- If there is too many fprobe exit handler set on the same function, it will fail to probe.Quoted 6 lines
Signed-off-by Masami Hiramatsu (Google) <[email protected]>---
Changes in v3:
- Update for new reserve_data/retrieve_data API.
- Fix internal push/pop on fgraph data logic so that it can correctly save/restore the returning fprobes.
Quoted 4 lines
hi,
looks like this one conflicts with recent:
4bbd93455659kprobes: kretprobe scalability improvement
Thanks for reporting!
I also found some other patches conflicts with recent commits. Let me rebase it on the recent branch.
Thank!
Quoted 2 lines
jirka
Signature · 1 line
Masami Hiramatsu (Google) <[email protected]>
Masami Hiramatsu (Google)
maintainerPATCH v3 29/33 tracing/fprobe: Remove nr_maxactive from fprobe
In reply to Masami Hiramatsu (Google)From: Masami Hiramatsu (Google) <[email protected]>
Remove depercated fprobe::nr_maxactive. This involves fprobe events to rejects the maxactive number.
---
Changes in v2:
- Newly added.
--- include/linux/fprobe.h | 2 -- kernel/trace/trace_fprobe.c | 44 ++++++------------------------------------- 2 files changed, 6 insertions(+), 40 deletions(-)
include/linux/fprobe.h +0 -2
@@ -47,7 +47,6 @@ struct fprobe_hlist {* @nmissed: The counter for missing events. * @flags: The status flag. * @entry_data_size: The private data storage size. -* @nr_maxactive: The max number of active functions. (*deprecated) * @entry_handler: The callback function for function entry. * @exit_handler: The callback function for function exit. * @hlist_array: The fprobe_hlist for fprobe search from IP hash table.
@@ -56,7 +55,6 @@ struct fprobe {unsigned long nmissed;unsigned int flags;size_t entry_data_size; -int nr_maxactive; int (*entry_handler)(struct fprobe *fp, unsigned long entry_ip, unsigned long ret_ip, struct ftrace_regs *regs,
kernel/trace/trace_fprobe.c +6 -38
93 lines not on this page · raw message
Masami Hiramatsu (Google)
maintainerPATCH v3 30/33 tracing/fprobe: Enable fprobe events with CONFIG_DYNAMIC_FTRACE_WITH_ARGS
In reply to Masami Hiramatsu (Google)From: Masami Hiramatsu (Google) <[email protected]>
Allow fprobe events to be enabled with CONFIG_DYNAMIC_FTRACE_WITH_ARGS. With this change, fprobe events mostly use ftrace_regs instead of pt_regs. Note that if the arch doesn't enable HAVE_PT_REGS_COMPAT_FTRACE_REGS, fprobe events will not be able to be used from perf.
---
Chagnes in v3:
- Use ftrace_regs_get_return_value().
Changes in v2:
- Define ftrace_regs_get_kernel_stack_nth() for !CONFIG_HAVE_REGS_AND_STACK_ACCESS_API.
Changes from previous series: Update against the new series.
--- include/linux/ftrace.h | 17 +++++++++ kernel/trace/Kconfig | 1 - kernel/trace/trace_fprobe.c | 74 ++++++++++++++++++++------------------- kernel/trace/trace_probe_tmpl.h | 2 + 4 files changed, 55 insertions(+), 39 deletions(-)
include/linux/ftrace.h +17 -0
@@ -250,6 +250,23 @@ static __always_inline bool ftrace_regs_has_args(struct ftrace_regs *fregs) regs_query_register_offset(name)#endif +#ifdef CONFIG_HAVE_REGS_AND_STACK_ACCESS_API +static __always_inline unsigned long +ftrace_regs_get_kernel_stack_nth (struct ftrace_regs *fregs,unsigned int nth) +{ +unsigned long *stackp; + + stackp = (unsigned long *)ftrace_regs_get_stack_pointer (fregs); +if (((unsigned long )(stackp + nth) & ~(THREAD_SIZE -1 )) == + ((unsigned long )stackp & ~(THREAD_SIZE -1 ))) +return *(stackp + nth); + +return 0 ; +} +#else /* !CONFIG_HAVE_REGS_AND_STACK_ACCESS_API */ +#define ftrace_regs_get_kernel_stack_nth (fregs, nth) (0L) +#endif /* CONFIG_HAVE_REGS_AND_STACK_ACCESS_API */ +typedef void (*ftrace_func_t )(unsigned long ip,unsigned long parent_ip,struct ftrace_ops *op,struct ftrace_regs *fregs);
kernel/trace/Kconfig +0 -1
@@ -681,7 +681,6 @@ config FPROBE_EVENTS select TRACING select PROBE_EVENTS select DYNAMIC_EVENTS - depends on DYNAMIC_FTRACE_WITH_REGS default y help This allows user to add tracing events on the function entry and
kernel/trace/trace_fprobe.c +37 -37
227 lines not on this page · raw message
kernel/trace/trace_probe_tmpl.h +1 -1
@@ -232,7 +232,7 @@ process_fetch_insn_bottom(struct fetch_insn *code, unsigned long val,/* Sum up total data length for dynamic arrays (strings) */ static nokprobe_inline int -__get_data_size (struct trace_probe *tp,struct pt_regs *regs) +__get_data_size (struct trace_probe *tp,void *regs) {struct probe_arg *arg;int i, len, ret =0 ;
Masami Hiramatsu (Google)
maintainerPATCH v3 31/33 bpf: Enable kprobe_multi feature if CONFIG_FPROBE is enabled
In reply to Masami Hiramatsu (Google)From: Masami Hiramatsu (Google) <[email protected]>
Enable kprobe_multi feature if CONFIG_FPROBE is enabled. The pt_regs is converted from ftrace_regs by ftrace_partial_regs(), thus some registers may always returns 0. But it should be enough for function entry (access arguments) and exit (access return value).
---
Changes from previous series: NOTHING, Update against the new series.
--- kernel/trace/bpf_trace.c | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-)
kernel/trace/bpf_trace.c +9 -13
@@ -2501,7 +2501,7 @@ static int __init bpf_event_init(void)fs_initcall (bpf_event_init);#endif /* CONFIG_MODULES */ -#if defined(CONFIG_FPROBE) && defined(CONFIG_DYNAMIC_FTRACE_WITH_REGS) +#ifdef CONFIG_FPROBE struct bpf_kprobe_multi_link { struct bpf_link link; struct fprobe fp;
@@ -2524,6 +2524,8 @@ struct user_syms {char *buf; }; +static DEFINE_PER_CPU(struct pt_regs, bpf_kprobe_multi_pt_regs); +static int copy_user_syms (struct user_syms *us,unsigned long __user *usyms,u32 cnt) {unsigned long __user usymbol;
@@ -2700,13 +2702,14 @@ static u64 bpf_kprobe_multi_entry_ip(struct bpf_run_ctx *ctx)static int kprobe_multi_link_prog_run (struct bpf_kprobe_multi_link *link, -unsigned long entry_ip,struct pt_regs *regs) +unsigned long entry_ip,struct ftrace_regs *fregs) {struct bpf_kprobe_multi_run_ctx run_ctx = { .link = link, .entry_ip = entry_ip, };struct bpf_run_ctx *old_run_ctx; +struct pt_regs *regs;int err;if (unlikely (__this_cpu_inc_return (bpf_prog_active) !=1 )) {
@@ -2716,6 +2719,7 @@ kprobe_multi_link_prog_run(struct bpf_kprobe_multi_link *link,migrate_disable ();rcu_read_lock (); + regs =ftrace_partial_regs (fregs,this_cpu_ptr (&bpf_kprobe_multi_pt_regs)); old_run_ctx =bpf_set_run_ctx (&run_ctx.run_ctx); err =bpf_prog_run (link->link.prog, regs);bpf_reset_run_ctx (old_run_ctx);
@@ -2733,13 +2737,9 @@ kprobe_multi_link_handler(struct fprobe *fp, unsigned long fentry_ip, void *data) {struct bpf_kprobe_multi_link *link; -struct pt_regs *regs =ftrace_get_regs (fregs); - -if (!regs) -return 0 ; link = container_of(fp, struct bpf_kprobe_multi_link, fp); -kprobe_multi_link_prog_run (link,get_entry_ip (fentry_ip), regs); +kprobe_multi_link_prog_run (link,get_entry_ip (fentry_ip), fregs);return 0 ; }
@@ -2749,13 +2749,9 @@ kprobe_multi_link_exit_handler(struct fprobe *fp, unsigned long fentry_ip, void *data) {struct bpf_kprobe_multi_link *link; -struct pt_regs *regs =ftrace_get_regs (fregs); - -if (!regs) -return ; link = container_of(fp, struct bpf_kprobe_multi_link, fp); -kprobe_multi_link_prog_run (link,get_entry_ip (fentry_ip), regs); +kprobe_multi_link_prog_run (link,get_entry_ip (fentry_ip), fregs); }static int symbols_cmp_r (const void *a,const void *b,const void *priv)
@@ -3012,7 +3008,7 @@ int bpf_kprobe_multi_link_attach(const union bpf_attr *attr, struct bpf_prog *prkvfree (cookies); return err; } -#else /* !CONFIG_FPROBE || !CONFIG_DYNAMIC_FTRACE_WITH_REGS */ +#else /* !CONFIG_FPROBE */ int bpf_kprobe_multi_link_attach (const union bpf_attr *attr,struct bpf_prog *prog) {return -EOPNOTSUPP ;
Masami Hiramatsu (Google)
maintainerPATCH v3 32/33 selftests: ftrace: Remove obsolate maxactive syntax check
In reply to Masami Hiramatsu (Google)From: Masami Hiramatsu (Google) <[email protected]>
Since the fprobe event does not support maxactive anymore, stop testing the maxactive syntax error checking.
--- .../ftrace/test.d/dynevent/fprobe_syntax_errors.tc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-)
tools/testing/selftests/ftrace/test.d/dynevent/fprobe_syntax_errors.tc +1 -3
@@ -16,9 +16,7 @@ aarch64) REG=%r0 ;; esac -check_error 'f^100 vfs_read' # MAXACT_NO_KPROBE -check_error 'f^1a111 vfs_read' # BAD_MAXACT -check_error 'f^100000 vfs_read' # MAXACT_TOO_BIG +check_error 'f^100 vfs_read' # BAD_MAXACT check_error 'f ^non_exist_func' # BAD_PROBE_ADDR (enoent) check_error 'f ^vfs_read+10' # BAD_PROBE_ADDR
Masami Hiramatsu (Google)
maintainerPATCH v3 33/33 Documentation: probes: Update fprobe on function-graph tracer
In reply to Masami Hiramatsu (Google)From: Masami Hiramatsu (Google) <[email protected]>
Update fprobe documentation for the new fprobe on function-graph tracer. This includes some bahvior changes and pt_regs to ftrace_regs interface change.
---
Changes in v2:
- Update @fregs parameter explanation.
--- Documentation/trace/fprobe.rst | 42 ++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 15 deletions(-)
Documentation/trace/fprobe.rst +27 -15
@@ -9,9 +9,10 @@ Fprobe - Function entry/exit probe Introduction ============ -Fprobe is a function entry/exit probe mechanism based on ftrace. -Instead of using ftrace full feature, if you only want to attach callbacks -on function entry and exit, similar to the kprobes and kretprobes, you can +Fprobe is a function entry/exit probe mechanism based on the function-graph +tracer. +Instead of tracing all functions, if you want to attach callbacks on specific +function entry and exit, similar to the kprobes and kretprobes, you can use fprobe. Compared with kprobes and kretprobes, fprobe gives faster instrumentation for multiple functions with single handler. This document describes how to use fprobe.
@@ -91,12 +92,14 @@ The prototype of the entry/exit callback function are as follows: .. code-block:: c - int entry_callback(struct fprobe *fp, unsigned long entry_ip, unsigned long ret_ip, struct pt_regs *regs, void *entry_data); + int entry_callback(struct fprobe *fp, unsigned long entry_ip, unsigned long ret_ip, struct ftrace_regs *fregs, void *entry_data); - void exit_callback(struct fprobe *fp, unsigned long entry_ip, unsigned long ret_ip, struct pt_regs *regs, void *entry_data); + void exit_callback(struct fprobe *fp, unsigned long entry_ip, unsigned long ret_ip, struct ftrace_regs *fregs, void *entry_data); -Note that the @entry_ip is saved at function entry and passed to exit handler. -If the entry callback function returns !0, the corresponding exit callback will be cancelled. +Note that the @entry_ip is saved at function entry and passed to exit +handler. +If the entry callback function returns !0, the corresponding exit callback +will be cancelled. @fp This is the address of `fprobe` data structure related to this handler.
@@ -112,12 +115,10 @@ If the entry callback function returns !0, the corresponding exit callback will This is the return address that the traced function will return to, somewhere in the caller. This can be used at both entry and exit. -@regs - This is the `pt_regs` data structure at the entry and exit. Note that - the instruction pointer of @regs may be different from the @entry_ip - in the entry_handler. If you need traced instruction pointer, you need - to use @entry_ip. On the other hand, in the exit_handler, the instruction - pointer of @regs is set to the current return address. +@fregs + This is the `ftrace_regs` data structure at the entry and exit. This + includes the function parameters, or the return values. So user can + access thos values via appropriate `ftrace_regs_*` APIs. @entry_data This is a local storage to share the data between entry and exit handlers.
@@ -125,6 +126,17 @@ If the entry callback function returns !0, the corresponding exit callback will and `entry_data_size` field when registering the fprobe, the storage is allocated and passed to both `entry_handler` and `exit_handler`. +Entry data size and exit handlers on the same function +====================================================== + +Since the entry data is passed via per-task stack and it is has limited size, +the entry data size per probe is limited to `15 * sizeof(long)`. You also need +to take care that the different fprobes are probing on the same function, this +limit becomes smaller. The entry data size is aligned to `sizeof(long)` and +each fprobe which has exit handler uses a `sizeof(long)` space on the stack, +you should keep the number of fprobes on the same function as small as +possible. + Share the callbacks with kprobes ================================
@@ -165,8 +177,8 @@ This counter counts up when; - fprobe fails to take ftrace_recursion lock. This usually means that a function which is traced by other ftrace users is called from the entry_handler. - - fprobe fails to setup the function exit because of the shortage of rethook - (the shadow stack for hooking the function return.) + - fprobe fails to setup the function exit because of failing to allocate the + data buffer from the per-task shadow stack. The `fprobe::nmissed` field counts up in both cases. Therefore, the former skips both of entry and exit callback and the latter skips the exit