ftrace: host klp and bpf trampoline together
Song Liu
maintainerv5 cover ftrace: host klp and bpf trampoline together
Changes v4 => v5:
1. Cleanup direct_mutex handling in register_ftrace_function. (Steven Rostedt, Petr Mladek).
2. Various smallish fixes. (Steven Rostedt, Petr Mladek).
Changes v3 => v4:
1. Fix build errors for different config. (kernel test robot)
Changes v2 => v3:
1. Major rewrite after discussions with Steven Rostedt. [1]
2. Remove SHARE_IPMODIFY flag from ftrace code. Instead use the callback function to communicate this information. (Steven)
3. Add cleanup_direct_functions_after_ipmodify() to clear SHARE_IPMODIFY on the DIRECT ops when the IPMODIFY ops is removed.
Changes v1 => v2:
1. Fix build errors for different config. (kernel test robot)
Kernel Live Patch (livepatch, or klp) and bpf trampoline are important features for modern systems. This set allows the two to work on the same kernel function as the same time.
live patch uses ftrace with IPMODIFY, while bpf trampoline use direct ftrace. Existing policy does not allow the two to attach to the same kernel function. This is changed by fine tuning ftrace IPMODIFY policy, and allows one IPMODIFY ftrace_ops and one DIRECT ftrace_ops on the same kernel function at the same time. Please see patch 2 and 4 for more details.
Note that, one of the constraint here is to let bpf trampoline use direct call when it is not working on the same function as live patch. This is achieved by allowing ftrace code to ask bpf trampoline to make changes.
[1] https://lore.kernel.org/all/[email protected]/
Jiri Olsa (1):
bpf, x64: Allow to use caller address from stack
Song Liu (3):
ftrace: Add modify_ftrace_direct_multi_nolock
ftrace: Allow IPMODIFY and DIRECT ops on the same function
bpf: Support bpf_trampoline on functions with IPMODIFY (e.g.
livepatch)
arch/x86/net/bpf_jit_comp.c | 13 +- include/linux/bpf.h | 13 ++ include/linux/ftrace.h | 43 +++++ kernel/bpf/trampoline.c | 158 +++++++++++++++-- kernel/trace/ftrace.c | 328 ++++++++++++++++++++++++++++++------ 5 files changed, 484 insertions(+), 71 deletions(-)
--
2.30.2
Song Liu
maintainerPATCH v5 1/4 ftrace: Add modify_ftrace_direct_multi_nolock
This is similar to modify_ftrace_direct_multi, but does not acquire direct_mutex. This is useful when direct_mutex is already locked by the user.
--- include/linux/ftrace.h | 5 +++ kernel/trace/ftrace.c | 86 ++++++++++++++++++++++++++++++------------ 2 files changed, 67 insertions(+), 24 deletions(-)
include/linux/ftrace.h +5 -0
@@ -340,6 +340,7 @@ unsigned long ftrace_find_rec_direct(unsigned long ip);int register_ftrace_direct_multi (struct ftrace_ops *ops,unsigned long addr);int unregister_ftrace_direct_multi (struct ftrace_ops *ops,unsigned long addr);int modify_ftrace_direct_multi (struct ftrace_ops *ops,unsigned long addr); +int modify_ftrace_direct_multi_nolock (struct ftrace_ops *ops,unsigned long addr);#else struct ftrace_ops ;
@@ -384,6 +385,10 @@ static inline int modify_ftrace_direct_multi(struct ftrace_ops *ops, unsigned lo {return -ENODEV ; } +static inline int modify_ftrace_direct_multi_nolock (struct ftrace_ops *ops,unsigned long addr) +{ +return -ENODEV ; +}#endif /* CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS */ #ifndef CONFIG_HAVE_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
kernel/trace/ftrace.c +62 -24
110 lines not on this page · raw message
Signature · 1 line
2.30.2
Song Liu
maintainerPATCH v5 2/4 ftrace: Allow IPMODIFY and DIRECT ops on the same function
In reply to Song LiuIPMODIFY (livepatch) and DIRECT (bpf trampoline) ops are both important users of ftrace. It is necessary to allow them work on the same function at the same time.
First, DIRECT ops no longer specify IPMODIFY flag. Instead, DIRECT flag is handled together with IPMODIFY flag in __ftrace_hash_update_ipmodify().
Then, a callback function, ops_func, is added to ftrace_ops. This is used by ftrace core code to understand whether the DIRECT ops can share with an IPMODIFY ops. To share with IPMODIFY ops, the DIRECT ops need to implement the callback function and adjust the direct trampoline accordingly.
If DIRECT ops is attached before the IPMODIFY ops, ftrace core code calls ENABLE_SHARE_IPMODIFY_PEER on the DIRECT ops before registering the IPMODIFY ops.
If IPMODIFY ops is attached before the DIRECT ops, ftrace core code calls ENABLE_SHARE_IPMODIFY_SELF in __ftrace_hash_update_ipmodify. Owner of the DIRECT ops may return 0 if the DIRECT trampoline can share with IPMODIFY, so error code otherwise. The error code is propagated to register_ftrace_direct_multi so that onwer of the DIRECT trampoline can handle it properly.
For more details, please refer to comment before enum ftrace_ops_cmd.
--- include/linux/ftrace.h | 38 +++++++ kernel/trace/ftrace.c | 242 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 254 insertions(+), 26 deletions(-)
include/linux/ftrace.h +38 -0
@@ -208,6 +208,43 @@ enum { FTRACE_OPS_FL_DIRECT = BIT(17), }; +/* +* FTRACE_OPS_CMD_* commands allow the ftrace core logic to request changes +* to a ftrace_ops. Note, the requests may fail. +* +* ENABLE_SHARE_IPMODIFY_SELF - enable a DIRECT ops to work on the same +* function as an ops with IPMODIFY. Called +* when the DIRECT ops is being registered. +* This is called with both direct_mutex and +* ftrace_lock are locked. +* +* ENABLE_SHARE_IPMODIFY_PEER - enable a DIRECT ops to work on the same +* function as an ops with IPMODIFY. Called +* when the other ops (the one with IPMODIFY) +* is being registered. +* This is called with direct_mutex locked. +* +* DISABLE_SHARE_IPMODIFY_PEER - disable a DIRECT ops to work on the same +* function as an ops with IPMODIFY. Called +* when the other ops (the one with IPMODIFY) +* is being unregistered. +* This is called with direct_mutex locked. +*/ +enum ftrace_ops_cmd { +FTRACE_OPS_CMD_ENABLE_SHARE_IPMODIFY_SELF , +FTRACE_OPS_CMD_ENABLE_SHARE_IPMODIFY_PEER , +FTRACE_OPS_CMD_DISABLE_SHARE_IPMODIFY_PEER , +}; + +/* +* For most ftrace_ops_cmd, +* Returns: +* 0 - Success. +* Negative on failure. The return value is dependent on the +* callback. +*/ +typedef int (*ftrace_ops_func_t )(struct ftrace_ops *op,enum ftrace_ops_cmd cmd); +#ifdef CONFIG_DYNAMIC_FTRACE /* The hash used to know what functions callbacks trace */ struct ftrace_ops_hash {
@@ -250,6 +287,7 @@ struct ftrace_ops {unsigned long trampoline;unsigned long trampoline_size;struct list_head list; +ftrace_ops_func_t ops_func;#endif };
kernel/trace/ftrace.c +216 -26
356 lines not on this page · raw message
Signature · 1 line
2.30.2
Re: [PATCH v5 bpf-next 2/4] ftrace: Allow IPMODIFY and DIRECT ops on the same function
Quoted 435 lines · Song Liu
IPMODIFY (livepatch) and DIRECT (bpf trampoline) ops are both important users of ftrace. It is necessary to allow them work on the same function at the same time.
First, DIRECT ops no longer specify IPMODIFY flag. Instead, DIRECT flag is handled together with IPMODIFY flag in
__ftrace_hash_update_ipmodify().Then, a callback function,
ops_func, is added toftrace_ops. This is used by ftrace core code to understand whether the DIRECT ops can share with an IPMODIFY ops. To share with IPMODIFY ops, the DIRECT ops need to implement the callback function and adjust the direct trampoline accordingly.If DIRECT ops is attached before the IPMODIFY ops, ftrace core code calls
ENABLE_SHARE_IPMODIFY_PEERon the DIRECT ops before registering the IPMODIFY ops.If IPMODIFY ops is attached before the DIRECT ops, ftrace core code calls
ENABLE_SHARE_IPMODIFY_SELFin__ftrace_hash_update_ipmodify. Owner of the DIRECT ops may return 0 if the DIRECT trampoline can share with IPMODIFY, so error code otherwise. The error code is propagated toregister_ftrace_direct_multiso that onwer of the DIRECT trampoline can handle it properly.For more details, please refer to comment before enum
ftrace_ops_cmd.Signed-off-by Song Liu <[email protected]>--- include/linux/ftrace.h | 38 +++++++ kernel/trace/ftrace.c | 242 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 254 insertions(+), 26 deletions(-)include/linux/ftrace.h +38
-0@@ -208,6 +208,43 @@ enum { FTRACE_OPS_FL_DIRECT = BIT(17), }; +/* +* FTRACE_OPS_CMD_* commands allow the ftrace core logic to request changes +* to a ftrace_ops. Note, the requests may fail. +* +* ENABLE_SHARE_IPMODIFY_SELF - enable a DIRECT ops to work on the same +* function as an ops with IPMODIFY. Called +* when the DIRECT ops is being registered. +* This is called with both direct_mutex and +* ftrace_lock are locked. +* +* ENABLE_SHARE_IPMODIFY_PEER - enable a DIRECT ops to work on the same +* function as an ops with IPMODIFY. Called +* when the other ops (the one with IPMODIFY) +* is being registered. +* This is called with direct_mutex locked. +* +* DISABLE_SHARE_IPMODIFY_PEER - disable a DIRECT ops to work on the same +* function as an ops with IPMODIFY. Called +* when the other ops (the one with IPMODIFY) +* is being unregistered. +* This is called with direct_mutex locked. +*/ +enum ftrace_ops_cmd { +FTRACE_OPS_CMD_ENABLE_SHARE_IPMODIFY_SELF , +FTRACE_OPS_CMD_ENABLE_SHARE_IPMODIFY_PEER , +FTRACE_OPS_CMD_DISABLE_SHARE_IPMODIFY_PEER , +}; + +/* +* For most ftrace_ops_cmd, +* Returns: +* 0 - Success. +* Negative on failure. The return value is dependent on the +* callback. +*/ +typedef int (*ftrace_ops_func_t )(struct ftrace_ops *op,enum ftrace_ops_cmd cmd); +#ifdef CONFIG_DYNAMIC_FTRACE /* The hash used to know what functions callbacks trace */ struct ftrace_ops_hash {@@ -250,6 +287,7 @@ struct ftrace_ops {unsigned long trampoline;unsigned long trampoline_size;struct list_head list; +ftrace_ops_func_t ops_func;#endif };kernel/trace/ftrace.c +208
-20@@ -1861,6 +1861,8 @@ static void ftrace_hash_rec_enable_modify(struct ftrace_ops *ops,ftrace_hash_rec_update_modify (ops, filter_hash,1 ); } +static bool ops_references_ip (struct ftrace_ops *ops,unsigned long ip); +/* * Try to update IPMODIFY flag on each ftrace_rec. Return 0 if it is OK * or no-needed to update, -EBUSY if it detects a conflict of the flag @@ -1869,6 +1871,13 @@ static void ftrace_hash_rec_enable_modify(struct ftrace_ops *ops,* - If the hash is NULL, it hits all recs (if IPMODIFY is set, this is rejected) * - If the hash is EMPTY_HASH, it hits nothing * - Anything else hits the recs which match the hash entries. +* +* DIRECT ops does not have IPMODIFY flag, but we still need to check it +* against functions with FTRACE_FL_IPMODIFY. If there is any overlap, call +* ops_func(SHARE_IPMODIFY_SELF) to make sure current ops can share with +* IPMODIFY. If ops_func(SHARE_IPMODIFY_SELF) returns non-zero, propagate +* the return value to the caller and eventually to the owner of the DIRECT +* ops. */ static int __ftrace_hash_update_ipmodify(struct ftrace_ops *ops, struct ftrace_hash *old_hash,@@ -1877,17 +1886,26 @@ static int __ftrace_hash_update_ipmodify(struct ftrace_ops *ops,struct ftrace_page *pg;struct dyn_ftrace *rec, *end =NULL ;int in_old, in_new; +bool is_ipmodify, is_direct;/* Only update if the ops has been registered */ if (!(ops->flags &FTRACE_OPS_FL_ENABLED ))return 0 ; -if (!(ops->flags &FTRACE_OPS_FL_IPMODIFY )) + is_ipmodify = ops->flags &FTRACE_OPS_FL_IPMODIFY ; + is_direct = ops->flags &FTRACE_OPS_FL_DIRECT ; + +/* neither IPMODIFY nor DIRECT, skip */ +if (!is_ipmodify && !is_direct) +return 0 ; + +if (WARN_ON_ONCE (is_ipmodify && is_direct))return 0 ;/* -* Since the IPMODIFY is a very address sensitive action, we do not -* allow ftrace_ops to set all functions to new hash. +* Since the IPMODIFY and DIRECT are very address sensitive +* actions, we do not allow ftrace_ops to set all functions to new +* hash. */ if (!new_hash || !old_hash)return -EINVAL ;@@ -1905,12 +1923,32 @@ static int __ftrace_hash_update_ipmodify(struct ftrace_ops *ops,continue ;if (in_new) { -/* New entries must ensure no others are using it */ -if (rec->flags &FTRACE_FL_IPMODIFY ) - goto rollback; - rec->flags |=FTRACE_FL_IPMODIFY ; - }else /* Removed entry */ +if (rec->flags &FTRACE_FL_IPMODIFY ) { +int ret; + +/* Cannot have two ipmodify on same rec */ +if (is_ipmodify) + goto rollback; + +FTRACE_WARN_ON (rec->flags &FTRACE_FL_DIRECT ); + +/* +* Another ops with IPMODIFY is already +* attached. We are now attaching a direct +* ops. Run SHARE_IPMODIFY_SELF, to check +* whether sharing is supported. +*/ +if (!ops->ops_func) +return -EBUSY ; + ret = ops->ops_func (ops,FTRACE_OPS_CMD_ENABLE_SHARE_IPMODIFY_SELF ); +if (ret) +return ret; + }else if (is_ipmodify) { + rec->flags |=FTRACE_FL_IPMODIFY ; + } + }else if (is_ipmodify) { rec->flags &= ~FTRACE_FL_IPMODIFY ; + } }while_for_each_ftrace_rec ();return 0 ;@@ -2454,8 +2492,7 @@ static void call_direct_funcs(unsigned long ip, unsigned long pip,struct ftrace_ops direct_ops = { .func = call_direct_funcs, - .flags =FTRACE_OPS_FL_IPMODIFY - |FTRACE_OPS_FL_DIRECT |FTRACE_OPS_FL_SAVE_REGS + .flags =FTRACE_OPS_FL_DIRECT |FTRACE_OPS_FL_SAVE_REGS |FTRACE_OPS_FL_PERMANENT ,/* * By declaring the main trampoline as this trampoline @@ -3072,14 +3109,14 @@ static inline int ops_traces_mod(struct ftrace_ops *ops) }/* -* Check if the current ops references the record. +* Check if the current ops references the given ip. * * If the ops traces all functions, then it was already accounted for. * If the ops does not trace the current record function, skip it. * If the ops ignores the function via notrace filter, skip it. */ -static inline bool -ops_references_rec (struct ftrace_ops *ops,struct dyn_ftrace *rec) +static bool +ops_references_ip (struct ftrace_ops *ops,unsigned long ip) {/* If ops isn't enabled, ignore it */ if (!(ops->flags &FTRACE_OPS_FL_ENABLED ))@@ -3091,16 +3128,29 @@ ops_references_rec(struct ftrace_ops *ops, struct dyn_ftrace *rec)/* The function must be in the filter */ if (!ftrace_hash_empty (ops->func_hash->filter_hash) && - !__ftrace_lookup_ip (ops->func_hash->filter_hash, rec->ip)) + !__ftrace_lookup_ip (ops->func_hash->filter_hash, ip))return false;/* If in notrace hash, we ignore it too */ -if (ftrace_lookup_ip (ops->func_hash->notrace_hash, rec->ip)) +if (ftrace_lookup_ip (ops->func_hash->notrace_hash, ip))return false;return true; } +/* +* Check if the current ops references the record. +* +* If the ops traces all functions, then it was already accounted for. +* If the ops does not trace the current record function, skip it. +* If the ops ignores the function via notrace filter, skip it. +*/ +static bool +ops_references_rec (struct ftrace_ops *ops,struct dyn_ftrace *rec) +{ +return ops_references_ip (ops, rec->ip); +} +static int ftrace_update_code (struct module *mod,struct ftrace_page *new_pgs) {bool init_nop =ftrace_need_init_nop ();@@ -5215,6 +5265,8 @@ static struct ftrace_direct_func *ftrace_alloc_direct_func(unsigned long addr)return direct; } +static int register_ftrace_function_nolock (struct ftrace_ops *ops); +/** * register_ftrace_direct - Call a custom trampoline directly * @ip: The address of the nop at the beginning of a function @@ -5286,7 +5338,7 @@ int register_ftrace_direct(unsigned long ip, unsigned long addr) ret =ftrace_set_filter_ip (&direct_ops, ip,0 ,0 );if (!ret && !(direct_ops.flags &FTRACE_OPS_FL_ENABLED )) { - ret =register_ftrace_function (&direct_ops); + ret =register_ftrace_function_nolock (&direct_ops);if (ret)ftrace_set_filter_ip (&direct_ops, ip,1 ,0 ); }@@ -5545,8 +5597,7 @@ int modify_ftrace_direct(unsigned long ip, }EXPORT_SYMBOL_GPL (modify_ftrace_direct); -#define MULTI_FLAGS (FTRACE_OPS_FL_IPMODIFY | FTRACE_OPS_FL_DIRECT | \ - FTRACE_OPS_FL_SAVE_REGS) +#define MULTI_FLAGS (FTRACE_OPS_FL_DIRECT | FTRACE_OPS_FL_SAVE_REGS)static int check_direct_multi (struct ftrace_ops *ops) {@@ -5639,7 +5690,7 @@ int register_ftrace_direct_multi(struct ftrace_ops *ops, unsigned long addr) ops->flags =MULTI_FLAGS ; ops->trampoline =FTRACE_REGS_ADDR ; - err =register_ftrace_function (ops); + err =register_ftrace_function_nolock (ops); out_remove:if (err)@@ -5709,7 +5760,7 @@ __modify_ftrace_direct_multi(struct ftrace_ops *ops, unsigned long addr)ftrace_ops_init (&tmp_ops); tmp_ops.func_hash = ops->func_hash; - err =register_ftrace_function (&tmp_ops); + err =register_ftrace_function_nolock (&tmp_ops);if (err)return err;@@ -8003,6 +8054,143 @@ int ftrace_is_dead(void)return ftrace_disabled; } +#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS +/* +* When registering ftrace_ops with IPMODIFY, it is necessary to make sure +* it doesn't conflict with any direct ftrace_ops. If there is existing +* direct ftrace_ops on a kernel function being patched, call +* FTRACE_OPS_CMD_ENABLE_SHARE_IPMODIFY_PEER on it to enable sharing. +* +* @ops: ftrace_ops being registered. +* +* Returns: +* 0 on success; +* Negative on failure. +*/ +static int prepare_direct_functions_for_ipmodify (struct ftrace_ops *ops) +{ +struct ftrace_func_entry *entry; +struct ftrace_hash *hash; +struct ftrace_ops *op; +int size, i, ret; + +lockdep_assert_held_once (&direct_mutex); + +if (!(ops->flags &FTRACE_OPS_FL_IPMODIFY )) +return 0 ; + + hash = ops->func_hash->filter_hash; + size =1 << hash->size_bits; +for (i =0 ; i < size; i++) { +hlist_for_each_entry (entry, &hash->buckets[i], hlist) { +unsigned long ip = entry->ip; +bool found_op = false; + +mutex_lock (&ftrace_lock); +do_for_each_ftrace_op (op, ftrace_ops_list) { +if (!(op->flags &FTRACE_OPS_FL_DIRECT )) +continue ; +if (ops_references_ip (op, ip)) { + found_op = true; +break ; + } + }while_for_each_ftrace_op (op); +mutex_unlock (&ftrace_lock); + +if (found_op) { +if (!op->ops_func) +return -EBUSY ; + + ret = op->ops_func (op,FTRACE_OPS_CMD_ENABLE_SHARE_IPMODIFY_PEER ); +if (ret) +return ret; + } + } + } + +return 0 ; +} + +/* +* Similar to prepare_direct_functions_for_ipmodify, clean up after ops +* with IPMODIFY is unregistered. The cleanup is optional for most DIRECT +* ops. +*/ +static void cleanup_direct_functions_after_ipmodify (struct ftrace_ops *ops) +{ +struct ftrace_func_entry *entry; +struct ftrace_hash *hash; +struct ftrace_ops *op; +int size, i; + +if (!(ops->flags &FTRACE_OPS_FL_IPMODIFY )) +return ; + +mutex_lock (&direct_mutex); + + hash = ops->func_hash->filter_hash; + size =1 << hash->size_bits; +for (i =0 ; i < size; i++) { +hlist_for_each_entry (entry, &hash->buckets[i], hlist) { +unsigned long ip = entry->ip; +bool found_op = false; + +mutex_lock (&ftrace_lock); +do_for_each_ftrace_op (op, ftrace_ops_list) { +if (!(op->flags &FTRACE_OPS_FL_DIRECT )) +continue ; +if (ops_references_ip (op, ip)) { + found_op = true; +break ; + } + }while_for_each_ftrace_op (op); +mutex_unlock (&ftrace_lock); + +/* The cleanup is optional, ignore any errors */ +if (found_op && op->ops_func) + op->ops_func (op,FTRACE_OPS_CMD_DISABLE_SHARE_IPMODIFY_PEER ); + } + } +mutex_unlock (&direct_mutex); +} + +#define lock_direct_mutex () mutex_lock(&direct_mutex) +#define unlock_direct_mutex () mutex_unlock(&direct_mutex) + +#else /* CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS */ + +static int prepare_direct_functions_for_ipmodify (struct ftrace_ops *ops) +{ +return 0 ; +} + +static void cleanup_direct_functions_after_ipmodify (struct ftrace_ops *ops) +{ +} + +#define lock_direct_mutex () do { } while (0) +#define unlock_direct_mutex () do { } while (0) + +#endif /* CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS */ + +/* +* Similar to register_ftrace_function, except we don't lock direct_mutex. +*/ +static int register_ftrace_function_nolock (struct ftrace_ops *ops) +{ +int ret; + +ftrace_ops_init (ops); + +mutex_lock (&ftrace_lock); + + ret =ftrace_startup (ops,0 ); + +mutex_unlock (&ftrace_lock); + +return ret; +} +/** * register_ftrace_function - register a function for profiling * @ops: ops structure that holds the function for profiling.
kernel/trace/ftrace.c int register_ftrace_function(struct ftrace_ops *ops) · line 8209
Quoted 9 lines
kernel/trace/ftrace.c +1
-5@@ -8018,14 +8206,15 @@ int register_ftrace_function(struct ftrace_ops *ops) {int ret; -ftrace_ops_init (ops); - -mutex_lock (&ftrace_lock); - - ret =ftrace_startup (ops,0 ); +lock_direct_mutex ();
Trying to enable ftrace direct on powerpc, this is resulting in a hung task when testing samples/ftrace/ftrace-direct-modify.c
Essentially, the sample calls modify_ftrace_direct(), which grabs direct_mutex before calling ftrace_modify_direct_caller()->register_ftrace_function().
- Naveen
Quoted 25 lines
kernel/trace/ftrace.c +7
-1+ ret =prepare_direct_functions_for_ipmodify (ops); +if (ret <0 ) + goto out_unlock; -mutex_unlock (&ftrace_lock); + ret =register_ftrace_function_nolock (ops); +out_unlock: +unlock_direct_mutex ();return ret; }EXPORT_SYMBOL_GPL (register_ftrace_function);@@ -8044,6 +8233,7 @@ int unregister_ftrace_function(struct ftrace_ops *ops) ret =ftrace_shutdown (ops,0 );mutex_unlock (&ftrace_lock); +cleanup_direct_functions_after_ipmodify (ops);return ret; }EXPORT_SYMBOL_GPL (unregister_ftrace_function);Signature · 1 line
2.30.2
Re: [PATCH v5 bpf-next 2/4] ftrace: Allow IPMODIFY and DIRECT ops on the same function
Quoted 5 lines · Naveen N. Rao
Song Liu wrote:
[...]
Quoted 16 lines
Review int register_ftrace_function(struct ftrace_ops *ops) · line 8208
Quoted 13 lines
+
/**
*register_ftrace_function- register a function for profiling
* @ops: ops structure that holds the function for profiling.(quoted diff) +1
-5@@ -8018,14 +8206,15 @@ int register_ftrace_function(struct ftrace_ops *ops) { int ret; - ftrace_ops_init(ops); - - mutex_lock(&ftrace_lock); - - ret = ftrace_startup(ops, 0); + lock_direct_mutex();Trying to enable ftrace direct on powerpc, this is resulting in a hung task when testing
samples/ftrace/ftrace-direct-modify.c
Quoted 2 lines
Essentially, the sample calls
modify_ftrace_direct(), which grabsdirect_mutexbefore calling ftrace_modify_direct_caller()->register_ftrace_function().
Thanks for the report. Would the following change fix the issue?
Song
kernel/trace/ftrace.c +1 -1
@@ -5496,7 +5496,7 @@ int __weak ftrace_modify_direct_caller(struct ftrace_func_entry *entry,if (ret) goto out_lock; - ret =register_ftrace_function (&stub_ops); + ret =register_ftrace_function_nolock (&stub_ops);if (ret) {ftrace_set_filter_ip (&stub_ops, ip,1 ,0 ); goto out_lock;
Quoted 23 lines
- Naveen
Quoted 19 lines
(quoted diff) +7
-1+ ret = prepare_direct_functions_for_ipmodify(ops); + if (ret < 0) + goto out_unlock; - mutex_unlock(&ftrace_lock); + ret = register_ftrace_function_nolock(ops); +out_unlock: + unlock_direct_mutex(); return ret; } EXPORT_SYMBOL_GPL(register_ftrace_function);@@ -8044,6 +8233,7 @@ int unregister_ftrace_function(struct ftrace_ops *ops) ret = ftrace_shutdown(ops, 0); mutex_unlock(&ftrace_lock); + cleanup_direct_functions_after_ipmodify(ops); return ret; } EXPORT_SYMBOL_GPL(unregister_ftrace_function);Signature · 1 line
2.30.2
Re: [PATCH v5 bpf-next 2/4] ftrace: Allow IPMODIFY and DIRECT ops on the same function
Hi Song,
Quoted 37 lines · Song Liu
Quoted 5 lines · Naveen N. Rao
Song Liu wrote:
[...]
Quoted 16 lines
Review int register_ftrace_function(struct ftrace_ops *ops) · line 8208
Quoted 13 lines
+
/**
*register_ftrace_function- register a function for profiling
* @ops: ops structure that holds the function for profiling.(quoted diff) +1
-5@@ -8018,14 +8206,15 @@ int register_ftrace_function(struct ftrace_ops *ops) { int ret; - ftrace_ops_init(ops); - - mutex_lock(&ftrace_lock); - - ret = ftrace_startup(ops, 0); + lock_direct_mutex();Trying to enable ftrace direct on powerpc, this is resulting in a hung task when testing
samples/ftrace/ftrace-direct-modify.cQuoted 2 lines
Essentially, the sample calls
modify_ftrace_direct(), which grabsdirect_mutexbefore calling ftrace_modify_direct_caller()->register_ftrace_function().Thanks for the report. Would the following change fix the issue?
Song
kernel/trace/ftrace.c +0
-0
kernel/trace/ftrace.c int __weak ftrace_modify_direct_caller(struct ftrace_func_entry *entry, · line 5502
Quoted 8 lines
kernel/trace/ftrace.c +1
-1@@ -5496,7 +5496,7 @@ int __weak ftrace_modify_direct_caller(struct ftrace_func_entry *entry,if (ret) goto out_lock; - ret =register_ftrace_function (&stub_ops); + ret =register_ftrace_function_nolock (&stub_ops);if (ret) {ftrace_set_filter_ip (&stub_ops, ip,1 ,0 ); goto out_lock;
That fixes it for me.
Thanks!
- Naveen
Song Liu
maintainerPATCH v5 3/4 bpf, x64: Allow to use caller address from stack
In reply to Song LiuFrom: Jiri Olsa <[email protected]>
Currently we call the original function by using the absolute address given at the JIT generation. That's not usable when having trampoline attached to multiple functions, or the target address changes dynamically (in case of live patch). In such cases we need to take the return address from the stack.
Adding support to retrieve the original function address from the stack by adding new BPF_TRAMP_F_ORIG_STACK flag for arch_prepare_bpf_trampoline function.
Basically we take the return address of the 'fentry' call:
function + 0: call fentry # stores 'function + 5' address on stack
function + 5: ...
The 'function + 5' address will be used as the address for the original function to call.
--- arch/x86/net/bpf_jit_comp.c | 13 +++++++++---- include/linux/bpf.h | 5 +++++ 2 files changed, 14 insertions(+), 4 deletions(-)
arch/x86/net/bpf_jit_comp.c +9 -4
@@ -2119,10 +2119,15 @@ int arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *image, void *iif (flags &BPF_TRAMP_F_CALL_ORIG ) {restore_regs (m, &prog, nr_args, regs_off); -/* call original function */ -if (emit_call (&prog, orig_call, prog)) { - ret = -EINVAL ; - goto cleanup; +if (flags &BPF_TRAMP_F_ORIG_STACK ) { +emit_ldx (&prog,BPF_DW ,BPF_REG_0 ,BPF_REG_FP ,8 ); +EMIT2 (0xff ,0xd0 );/* call *rax */ + }else { +/* call original function */ +if (emit_call (&prog, orig_call, prog)) { + ret = -EINVAL ; + goto cleanup; + } }/* remember return value in a stack for bpf prog to access */ emit_stx (&prog,BPF_DW ,BPF_REG_FP ,BPF_REG_0 ,-8 );
include/linux/bpf.h +5 -0
@@ -751,6 +751,11 @@ struct btf_func_model {/* Return the return value of fentry prog. Only used by bpf_struct_ops. */ #define BPF_TRAMP_F_RET_FENTRY_RET BIT(4) +/* Get original function from stack instead of from provided direct address. +* Makes sense for trampolines with fexit or fmod_ret programs. +*/ +#define BPF_TRAMP_F_ORIG_STACK BIT(5) +/* Each call __bpf_prog_enter + call bpf_func + call __bpf_prog_exit is ~50 * bytes on x86. */
Signature · 1 line
2.30.2
Song Liu
maintainerPATCH v5 4/4 bpf: Support bpf_trampoline on functions with IPMODIFY (e.g. livepatch)
In reply to Song LiuWhen tracing a function with IPMODIFY ftrace_ops (livepatch), the bpf trampoline must follow the instruction pointer saved on stack. This needs extra handling for bpf trampolines with BPF_TRAMP_F_CALL_ORIG flag.
Implement bpf_tramp_ftrace_ops_func and use it for the ftrace_ops used by BPF trampoline. This enables tracing functions with livepatch.
This also requires moving bpf trampoline to *_ftrace_direct_mult APIs.
--- include/linux/bpf.h | 8 ++ kernel/bpf/trampoline.c | 158 +++++++++++++++++++++++++++++++++++----- 2 files changed, 149 insertions(+), 17 deletions(-)
include/linux/bpf.h +8 -0
@@ -47,6 +47,7 @@ struct kobject;struct mem_cgroup ;struct module ;struct bpf_func_state ; +struct ftrace_ops ;extern struct idr btf_idr;extern spinlock_t btf_idr_lock;
@@ -756,6 +757,11 @@ struct btf_func_model {*/ #define BPF_TRAMP_F_ORIG_STACK BIT(5) +/* This trampoline is on a function with another ftrace_ops with IPMODIFY, +* e.g., a live patch. This flag is set and cleared by ftrace call backs, +*/ +#define BPF_TRAMP_F_SHARE_IPMODIFY BIT(6) +/* Each call __bpf_prog_enter + call bpf_func + call __bpf_prog_exit is ~50 * bytes on x86. */
@@ -838,9 +844,11 @@ struct bpf_tramp_image {struct bpf_trampoline {/* hlist for trampoline_table */ struct hlist_node hlist; +struct ftrace_ops *fops;/* serializes access to fields of this trampoline */ struct mutex mutex;refcount_t refcnt; +u32 flags;u64 key;struct {struct btf_func_model model;
kernel/bpf/trampoline.c +141 -17
257 lines not on this page · raw message
Signature · 1 line
2.30.2
Re: [PATCH v5 bpf-next 4/4] bpf: Support bpf_trampoline on functions with IPMODIFY (e.g. livepatch)
On Tue, Jul 19, 2022 at 05:21:26PM -0700, Song Liu wrote:
SNIP
Quoted 26 lines
kernel/bpf/trampoline.c +22
-0+ tr->flags |=BPF_TRAMP_F_SHARE_IPMODIFY ; + +if ((tr->flags &BPF_TRAMP_F_CALL_ORIG ) && + !(tr->flags &BPF_TRAMP_F_ORIG_STACK )) + ret =bpf_trampoline_update (tr, false/* lock_direct_mutex */ ); +break ; +case FTRACE_OPS_CMD_DISABLE_SHARE_IPMODIFY_PEER : + tr->flags &= ~BPF_TRAMP_F_SHARE_IPMODIFY ; + +if (tr->flags &BPF_TRAMP_F_ORIG_STACK ) + ret =bpf_trampoline_update (tr, false/* lock_direct_mutex */ ); +break ; +default : + ret = -EINVAL ; + break; + }; + +mutex_unlock (&tr->mutex); +return ret; +} +#endif +bool bpf_prog_has_trampoline (const struct bpf_prog *prog) {enum bpf_attach_type eatype = prog->expected_attach_type;
kernel/bpf/trampoline.c static struct bpf_trampoline *bpf_trampoline_lookup(u64 key) · line 174
Quoted 10 lines
kernel/bpf/trampoline.c +7
-0@@ -89,6 +165,16 @@ static struct bpf_trampoline *bpf_trampoline_lookup(u64 key) tr =kzalloc (sizeof (*tr),GFP_KERNEL );if (!tr) goto out; +#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS + tr->fops =kzalloc (sizeof (struct ftrace_ops ),GFP_KERNEL ); +if (!tr->fops) { +kfree (tr); + tr =NULL ; + goto out; + }
would it be easier to put ftrace_ops directly to bpf_trampoline, not just pointer.. it's allocated and freed at the same point
I recall there were some include issues when I tried that long time ago [1], but could make the change bit simpler
jirka
Quoted 171 lines
kernel/bpf/trampoline.c +58
-17+ tr->fops->private = tr; + tr->fops->ops_func = bpf_tramp_ftrace_ops_func; +#endif tr->key = key;INIT_HLIST_NODE (&tr->hlist);@@ -128,7 +214,7 @@ static int unregister_fentry(struct bpf_trampoline *tr, void *old_addr)int ret;if (tr->func.ftrace_managed) - ret =unregister_ftrace_direct ((long )ip, (long )old_addr); + ret =unregister_ftrace_direct_multi (tr->fops, (long )old_addr);else ret =bpf_arch_text_poke (ip,BPF_MOD_CALL , old_addr,NULL );@@ -137,15 +223,20 @@ static int unregister_fentry(struct bpf_trampoline *tr, void *old_addr)return ret; } -static int modify_fentry (struct bpf_trampoline *tr,void *old_addr,void *new_addr) +static int modify_fentry (struct bpf_trampoline *tr,void *old_addr,void *new_addr, +bool lock_direct_mutex) {void *ip = tr->func.addr;int ret; -if (tr->func.ftrace_managed) - ret =modify_ftrace_direct ((long )ip, (long )old_addr, (long )new_addr); -else +if (tr->func.ftrace_managed) { +if (lock_direct_mutex) + ret =modify_ftrace_direct_multi (tr->fops, (long )new_addr); +else + ret =modify_ftrace_direct_multi_nolock (tr->fops, (long )new_addr); + }else { ret =bpf_arch_text_poke (ip,BPF_MOD_CALL , old_addr, new_addr); + }return ret; }@@ -163,10 +254,12 @@ static int register_fentry(struct bpf_trampoline *tr, void *new_addr)if (bpf_trampoline_module_get (tr))return -ENOENT ; -if (tr->func.ftrace_managed) - ret =register_ftrace_direct ((long )ip, (long )new_addr); -else +if (tr->func.ftrace_managed) { +ftrace_set_filter_ip (tr->fops, (unsigned long )ip,0 ,0 ); + ret =register_ftrace_direct_multi (tr->fops, (long )new_addr); + }else { ret =bpf_arch_text_poke (ip,BPF_MOD_CALL ,NULL , new_addr); + }if (ret)bpf_trampoline_module_put (tr);@@ -332,11 +425,11 @@ static struct bpf_tramp_image *bpf_tramp_image_alloc(u64 key, u32 idx)return ERR_PTR (err); } -static int bpf_trampoline_update (struct bpf_trampoline *tr) +static int bpf_trampoline_update (struct bpf_trampoline *tr,bool lock_direct_mutex) {struct bpf_tramp_image *im;struct bpf_tramp_links *tlinks; -u32 flags =BPF_TRAMP_F_RESTORE_REGS ; +u32 orig_flags = tr->flags;bool ip_arg = false;int err, total;@@ -358,18 +451,31 @@ static int bpf_trampoline_update(struct bpf_trampoline *tr) goto out; } +/* clear all bits except SHARE_IPMODIFY */ + tr->flags &=BPF_TRAMP_F_SHARE_IPMODIFY ; +if (tlinks[BPF_TRAMP_FEXIT ].nr_links || - tlinks[BPF_TRAMP_MODIFY_RETURN ].nr_links) + tlinks[BPF_TRAMP_MODIFY_RETURN ].nr_links) {/* NOTE: BPF_TRAMP_F_RESTORE_REGS and BPF_TRAMP_F_SKIP_FRAME * should not be set together. */ - flags =BPF_TRAMP_F_CALL_ORIG |BPF_TRAMP_F_SKIP_FRAME ; + tr->flags |=BPF_TRAMP_F_CALL_ORIG |BPF_TRAMP_F_SKIP_FRAME ; + }else { + tr->flags |=BPF_TRAMP_F_RESTORE_REGS ; + }if (ip_arg) - flags |=BPF_TRAMP_F_IP_ARG ; + tr->flags |=BPF_TRAMP_F_IP_ARG ; + +#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS +again: +if ((tr->flags &BPF_TRAMP_F_SHARE_IPMODIFY ) && + (tr->flags &BPF_TRAMP_F_CALL_ORIG )) + tr->flags |=BPF_TRAMP_F_ORIG_STACK ; +#endif err =arch_prepare_bpf_trampoline (im, im->image, im->image +PAGE_SIZE , - &tr->func.model, flags, tlinks, + &tr->func.model, tr->flags, tlinks, tr->func.addr);if (err <0 ) goto out;@@ -378,17 +484,34 @@ static int bpf_trampoline_update(struct bpf_trampoline *tr)WARN_ON (!tr->cur_image && tr->selector);if (tr->cur_image)/* progs already running at this address */ - err =modify_fentry (tr, tr->cur_image->image, im->image); + err =modify_fentry (tr, tr->cur_image->image, im->image, lock_direct_mutex);else /* first time registering */ err =register_fentry (tr, im->image); + +#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS +if (err == -EAGAIN ) { +/* -EAGAIN from bpf_tramp_ftrace_ops_func. Now +* BPF_TRAMP_F_SHARE_IPMODIFY is set, we can generate the +* trampoline again, and retry register. +*/ +/* reset fops->func and fops->trampoline for re-register */ + tr->fops->func =NULL ; + tr->fops->trampoline =0 ; + goto again; + } +#endif if (err) goto out; +if (tr->cur_image)bpf_tramp_image_put (tr->cur_image); tr->cur_image = im; tr->selector++; out: +/* If any error happens, restore previous flags */ +if (err) + tr->flags = orig_flags;kfree (tlinks);return err; }@@ -454,7 +577,7 @@ static int __bpf_trampoline_link_prog(struct bpf_tramp_link *link, struct bpf_trhlist_add_head (&link->tramp_hlist, &tr->progs_hlist[kind]); tr->progs_cnt[kind]++; - err =bpf_trampoline_update (tr); + err =bpf_trampoline_update (tr, true/* lock_direct_mutex */ );if (err) {hlist_del_init (&link->tramp_hlist); tr->progs_cnt[kind]--;@@ -487,7 +610,7 @@ static int __bpf_trampoline_unlink_prog(struct bpf_tramp_link *link, struct bpf_ }hlist_del_init (&link->tramp_hlist); tr->progs_cnt[kind]--; -return bpf_trampoline_update (tr); +return bpf_trampoline_update (tr, true/* lock_direct_mutex */ ); }/* bpf_trampoline_unlink_prog() should never fail. */ @@ -715,6 +838,7 @@ void bpf_trampoline_put(struct bpf_trampoline *tr)* multiple rcu callbacks. */ hlist_del (&tr->hlist); +kfree (tr->fops);kfree (tr); out:mutex_unlock (&trampoline_mutex);Signature · 1 line
2.30.2
Re: [PATCH v5 bpf-next 4/4] bpf: Support bpf_trampoline on functions with IPMODIFY (e.g. livepatch)
Quoted 48 lines · Jiri Olsa
On Tue, Jul 19, 2022 at 05:21:26PM -0700, Song Liu wrote:
SNIP
Quoted 26 lines
kernel/bpf/trampoline.c +22
-0+ tr->flags |=BPF_TRAMP_F_SHARE_IPMODIFY ; + +if ((tr->flags &BPF_TRAMP_F_CALL_ORIG ) && + !(tr->flags &BPF_TRAMP_F_ORIG_STACK )) + ret =bpf_trampoline_update (tr, false/* lock_direct_mutex */ ); +break ; +case FTRACE_OPS_CMD_DISABLE_SHARE_IPMODIFY_PEER : + tr->flags &= ~BPF_TRAMP_F_SHARE_IPMODIFY ; + +if (tr->flags &BPF_TRAMP_F_ORIG_STACK ) + ret =bpf_trampoline_update (tr, false/* lock_direct_mutex */ ); +break ; +default : + ret = -EINVAL ; + break; + }; + +mutex_unlock (&tr->mutex); +return ret; +} +#endif +bool
bpf_prog_has_trampoline(const struct bpf_prog *prog){ enum bpf_attach_type eatype = prog->expected_attach_type;kernel/bpf/trampoline.c static struct bpf_trampoline *bpf_trampoline_lookup(u64 key) · line 174
Quoted 10 lines
kernel/bpf/trampoline.c +7
-0@@ -89,6 +165,16 @@ static struct bpf_trampoline *bpf_trampoline_lookup(u64 key) tr =kzalloc (sizeof (*tr),GFP_KERNEL );if (!tr) goto out; +#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS + tr->fops =kzalloc (sizeof (struct ftrace_ops ),GFP_KERNEL ); +if (!tr->fops) { +kfree (tr); + tr =NULL ; + goto out; + }would it be easier to put
ftrace_opsdirectly tobpf_trampoline, not just pointer.. it's allocated and freed at the same point
Quoted 4 lines
I recall there were some include issues when I tried that long time ago [1], but could make the change bit simpler
I was not aware the CC_USING_FENTRY issue. However, I think we would like to avoid including ftrace.h in bpf.h, which will slow down the build.
Thanks,
Song
Hi Steven,
Quoted 30 lines · Song Liu
Changes v4 => v5:
1. Cleanupdirect_mutexhandling inregister_ftrace_function. (Steven Rostedt, Petr Mladek).
2. Various smallish fixes. (Steven Rostedt, Petr Mladek).Changes v3 => v4:
1. Fix build errors for different config. (kernel test robot)Changes v2 => v3:
1. Major rewrite after discussions with Steven Rostedt. [1]
2. RemoveSHARE_IPMODIFYflag from ftrace code. Instead use the callback function to communicate this information. (Steven)
3. Addcleanup_direct_functions_after_ipmodify()to clearSHARE_IPMODIFYon the DIRECT ops when the IPMODIFY ops is removed.Changes v1 => v2:
1. Fix build errors for different config. (kernel test robot)Kernel Live Patch (livepatch, or klp) and bpf trampoline are important features for modern systems. This set allows the two to work on the same kernel function as the same time.
live patch uses ftrace with IPMODIFY, while bpf trampoline use direct ftrace. Existing policy does not allow the two to attach to the same kernel function. This is changed by fine tuning ftrace IPMODIFY policy, and allows one IPMODIFY
ftrace_opsand one DIRECTftrace_opson the same kernel function at the same time. Please see patch 2 and 4 for more details.
Quoted 5 lines
Note that, one of the constraint here is to let bpf trampoline use direct call when it is not working on the same function as live patch. This is achieved by allowing ftrace code to ask bpf trampoline to make changes.
How does this version look to you?
Thanks,
Song
Quoted 1 line · On Thu, 21 Jul 2022 22:59:30 +0000 Song Liu <[email protected]> wrote:
How does this version look to you?
Looks good. Thanks Song!
For the first two patches:
-- Steve
Quoted 7 lines · Steven Rostedt
On Thu, 21 Jul 2022 22:59:30 +0000
Song Liu <[email protected]> wrote:
Quoted 7 lines
Quoted 1 line
How does this version look to you?
Looks good. Thanks Song!
For the first two patches:
Reviewed-by Steven Rostedt (Google) <[email protected]>
Thanks!
Song
This series was applied to bpf/bpf-next.git (master) by Daniel Borkmann <[email protected]>:
Hello:
This series was applied to bpf/bpf-next.git (master) by Daniel Borkmann <[email protected]>:
Quoted 9 lines · On Tue, 19 Jul 2022 17:21:22 -0700 you wrote:
Changes v4 => v5:
1. Cleanupdirect_mutexhandling inregister_ftrace_function. (Steven Rostedt, Petr Mladek).
2. Various smallish fixes. (Steven Rostedt, Petr Mladek).Changes v3 => v4:
1. Fix build errors for different config. (kernel test robot)[...]
Here is the summary with links:
- [v5,bpf-next,1/4] ftrace: Add modify_ftrace_direct_multi_nolock
https://git.kernel.org/bpf/bpf-next/c/f96f644ab97a - [v5,bpf-next,2/4] ftrace: Allow IPMODIFY and DIRECT ops on the same function https://git.kernel.org/bpf/bpf-next/c/53cd885bc5c3 - [v5,bpf-next,3/4] bpf, x64: Allow to use caller address from stack https://git.kernel.org/bpf/bpf-next/c/316cba62dfb7 - [v5,bpf-next,4/4] bpf: Support bpf_trampoline on functions with IPMODIFY (e.g. livepatch) https://git.kernel.org/bpf/bpf-next/c/00963a2e75a8
You are awesome, thank you!
Signature · 2 lines
Deet-doot-dot, I am a bot. https://korg.docs.kernel.org/patchwork/pwbot.html