Skip to content

Commit ea806eb

Browse files
committed
ftrace: Add a helper function to modify_ftrace_direct() to allow arch optimization
If a direct ftrace callback is at a location that does not have any other ftrace helpers attached to it, it is possible to simply just change the text to call the new caller (if the architecture supports it). But this requires special architecture code. Currently, modify_ftrace_direct() uses a trick to add a stub ftrace callback to the location forcing it to call the ftrace iterator. Then it can change the direct helper to call the new function in C, and then remove the stub. Removing the stub will have the location now call the new location that the direct helper is using. The new helper function does the registering the stub trick, but is a weak function, allowing an architecture to override it to do something a bit more direct. Link: https://lore.kernel.org/r/[email protected] Suggested-by: Alexei Starovoitov <[email protected]> Signed-off-by: Steven Rostedt (VMware) <[email protected]>
1 parent 128161f commit ea806eb

File tree

2 files changed

+107
-34
lines changed

2 files changed

+107
-34
lines changed

include/linux/ftrace.h

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,12 +246,24 @@ static inline void ftrace_free_init_mem(void) { }
246246
static inline void ftrace_free_mem(struct module *mod, void *start, void *end) { }
247247
#endif /* CONFIG_FUNCTION_TRACER */
248248

249+
struct ftrace_func_entry {
250+
struct hlist_node hlist;
251+
unsigned long ip;
252+
unsigned long direct; /* for direct lookup only */
253+
};
254+
255+
struct dyn_ftrace;
256+
249257
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
250258
extern int ftrace_direct_func_count;
251259
int register_ftrace_direct(unsigned long ip, unsigned long addr);
252260
int unregister_ftrace_direct(unsigned long ip, unsigned long addr);
253261
int modify_ftrace_direct(unsigned long ip, unsigned long old_addr, unsigned long new_addr);
254262
struct ftrace_direct_func *ftrace_find_direct_func(unsigned long addr);
263+
int ftrace_modify_direct_caller(struct ftrace_func_entry *entry,
264+
struct dyn_ftrace *rec,
265+
unsigned long old_addr,
266+
unsigned long new_addr);
255267
#else
256268
# define ftrace_direct_func_count 0
257269
static inline int register_ftrace_direct(unsigned long ip, unsigned long addr)
@@ -271,6 +283,13 @@ static inline struct ftrace_direct_func *ftrace_find_direct_func(unsigned long a
271283
{
272284
return NULL;
273285
}
286+
static inline int ftrace_modify_direct_caller(struct ftrace_func_entry *entry,
287+
struct dyn_ftrace *rec,
288+
unsigned long old_addr,
289+
unsigned long new_addr)
290+
{
291+
return -ENODEV;
292+
}
274293
#endif /* CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS */
275294

276295
#ifndef CONFIG_HAVE_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
@@ -343,8 +362,6 @@ static inline void stack_tracer_enable(void) { }
343362
int ftrace_arch_code_modify_prepare(void);
344363
int ftrace_arch_code_modify_post_process(void);
345364

346-
struct dyn_ftrace;
347-
348365
enum ftrace_bug_type {
349366
FTRACE_BUG_UNKNOWN,
350367
FTRACE_BUG_INIT,

kernel/trace/ftrace.c

Lines changed: 88 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1020,12 +1020,6 @@ static bool update_all_ops;
10201020
# error Dynamic ftrace depends on MCOUNT_RECORD
10211021
#endif
10221022

1023-
struct ftrace_func_entry {
1024-
struct hlist_node hlist;
1025-
unsigned long ip;
1026-
unsigned long direct; /* for direct lookup only */
1027-
};
1028-
10291023
struct ftrace_func_probe {
10301024
struct ftrace_probe_ops *probe_ops;
10311025
struct ftrace_ops ops;
@@ -5112,7 +5106,8 @@ int register_ftrace_direct(unsigned long ip, unsigned long addr)
51125106
}
51135107
EXPORT_SYMBOL_GPL(register_ftrace_direct);
51145108

5115-
static struct ftrace_func_entry *find_direct_entry(unsigned long *ip)
5109+
static struct ftrace_func_entry *find_direct_entry(unsigned long *ip,
5110+
struct dyn_ftrace **recp)
51165111
{
51175112
struct ftrace_func_entry *entry;
51185113
struct dyn_ftrace *rec;
@@ -5132,6 +5127,9 @@ static struct ftrace_func_entry *find_direct_entry(unsigned long *ip)
51325127
/* Passed in ip just needs to be on the call site */
51335128
*ip = rec->ip;
51345129

5130+
if (recp)
5131+
*recp = rec;
5132+
51355133
return entry;
51365134
}
51375135

@@ -5143,7 +5141,7 @@ int unregister_ftrace_direct(unsigned long ip, unsigned long addr)
51435141

51445142
mutex_lock(&direct_mutex);
51455143

5146-
entry = find_direct_entry(&ip);
5144+
entry = find_direct_entry(&ip, NULL);
51475145
if (!entry)
51485146
goto out_unlock;
51495147

@@ -5179,6 +5177,75 @@ static struct ftrace_ops stub_ops = {
51795177
.func = ftrace_stub,
51805178
};
51815179

5180+
/**
5181+
* ftrace_modify_direct_caller - modify ftrace nop directly
5182+
* @entry: The ftrace hash entry of the direct helper for @rec
5183+
* @rec: The record representing the function site to patch
5184+
* @old_addr: The location that the site at @rec->ip currently calls
5185+
* @new_addr: The location that the site at @rec->ip should call
5186+
*
5187+
* An architecture may overwrite this function to optimize the
5188+
* changing of the direct callback on an ftrace nop location.
5189+
* This is called with the ftrace_lock mutex held, and no other
5190+
* ftrace callbacks are on the associated record (@rec). Thus,
5191+
* it is safe to modify the ftrace record, where it should be
5192+
* currently calling @old_addr directly, to call @new_addr.
5193+
*
5194+
* Safety checks should be made to make sure that the code at
5195+
* @rec->ip is currently calling @old_addr. And this must
5196+
* also update entry->direct to @new_addr.
5197+
*/
5198+
int __weak ftrace_modify_direct_caller(struct ftrace_func_entry *entry,
5199+
struct dyn_ftrace *rec,
5200+
unsigned long old_addr,
5201+
unsigned long new_addr)
5202+
{
5203+
unsigned long ip = rec->ip;
5204+
int ret;
5205+
5206+
/*
5207+
* The ftrace_lock was used to determine if the record
5208+
* had more than one registered user to it. If it did,
5209+
* we needed to prevent that from changing to do the quick
5210+
* switch. But if it did not (only a direct caller was attached)
5211+
* then this function is called. But this function can deal
5212+
* with attached callers to the rec that we care about, and
5213+
* since this function uses standard ftrace calls that take
5214+
* the ftrace_lock mutex, we need to release it.
5215+
*/
5216+
mutex_unlock(&ftrace_lock);
5217+
5218+
/*
5219+
* By setting a stub function at the same address, we force
5220+
* the code to call the iterator and the direct_ops helper.
5221+
* This means that @ip does not call the direct call, and
5222+
* we can simply modify it.
5223+
*/
5224+
ret = ftrace_set_filter_ip(&stub_ops, ip, 0, 0);
5225+
if (ret)
5226+
goto out_lock;
5227+
5228+
ret = register_ftrace_function(&stub_ops);
5229+
if (ret) {
5230+
ftrace_set_filter_ip(&stub_ops, ip, 1, 0);
5231+
goto out_lock;
5232+
}
5233+
5234+
entry->direct = new_addr;
5235+
5236+
/*
5237+
* By removing the stub, we put back the direct call, calling
5238+
* the @new_addr.
5239+
*/
5240+
unregister_ftrace_function(&stub_ops);
5241+
ftrace_set_filter_ip(&stub_ops, ip, 1, 0);
5242+
5243+
out_lock:
5244+
mutex_lock(&ftrace_lock);
5245+
5246+
return ret;
5247+
}
5248+
51825249
/**
51835250
* modify_ftrace_direct - Modify an existing direct call to call something else
51845251
* @ip: The instruction pointer to modify
@@ -5197,11 +5264,13 @@ int modify_ftrace_direct(unsigned long ip,
51975264
unsigned long old_addr, unsigned long new_addr)
51985265
{
51995266
struct ftrace_func_entry *entry;
5267+
struct dyn_ftrace *rec;
52005268
int ret = -ENODEV;
52015269

52025270
mutex_lock(&direct_mutex);
52035271

5204-
entry = find_direct_entry(&ip);
5272+
mutex_lock(&ftrace_lock);
5273+
entry = find_direct_entry(&ip, &rec);
52055274
if (!entry)
52065275
goto out_unlock;
52075276

@@ -5210,33 +5279,20 @@ int modify_ftrace_direct(unsigned long ip,
52105279
goto out_unlock;
52115280

52125281
/*
5213-
* By setting a stub function at the same address, we force
5214-
* the code to call the iterator and the direct_ops helper.
5215-
* This means that @ip does not call the direct call, and
5216-
* we can simply modify it.
5282+
* If there's no other ftrace callback on the rec->ip location,
5283+
* then it can be changed directly by the architecture.
5284+
* If there is another caller, then we just need to change the
5285+
* direct caller helper to point to @new_addr.
52175286
*/
5218-
ret = ftrace_set_filter_ip(&stub_ops, ip, 0, 0);
5219-
if (ret)
5220-
goto out_unlock;
5221-
5222-
ret = register_ftrace_function(&stub_ops);
5223-
if (ret) {
5224-
ftrace_set_filter_ip(&stub_ops, ip, 1, 0);
5225-
goto out_unlock;
5287+
if (ftrace_rec_count(rec) == 1) {
5288+
ret = ftrace_modify_direct_caller(entry, rec, old_addr, new_addr);
5289+
} else {
5290+
entry->direct = new_addr;
5291+
ret = 0;
52265292
}
52275293

5228-
entry->direct = new_addr;
5229-
5230-
/*
5231-
* By removing the stub, we put back the direct call, calling
5232-
* the @new_addr.
5233-
*/
5234-
unregister_ftrace_function(&stub_ops);
5235-
ftrace_set_filter_ip(&stub_ops, ip, 1, 0);
5236-
5237-
ret = 0;
5238-
52395294
out_unlock:
5295+
mutex_unlock(&ftrace_lock);
52405296
mutex_unlock(&direct_mutex);
52415297
return ret;
52425298
}

0 commit comments

Comments
 (0)