Re: [PATCH] ftrace: fix race between ftrace and insmod
From: Steven Rostedt
Date: Mon Dec 14 2015 - 10:51:36 EST
On Mon, 14 Dec 2015 11:16:18 +0800
"Qiu, PeiyangX" <peiyangx.qiu@xxxxxxxxx> wrote:
> We hit ftrace_bug report when booting Android on a 64bit ATOM SOC chip.
> Basically, there is a race between insmod and ftrace_run_update_code.
>
> After load_module=>ftrace_module_init, another thread jumps in to call
> ftrace_run_update_code=>ftrace_arch_code_modify_prepare
> =>set_all_modules_text_rw, to change all modules
> as RW. Since the new module is at MODULE_STATE_UNFORMED, the text attribute
> is not changed. Then, the 2nd thread goes ahead to change codes.
> However, load_module continues to call
> complete_formation=>set_section_ro_nx,
> then 2nd thread would fail when probing the module's TEXT.
>
> Below patch tries to resolve it.
>
> commit a949ae560a511fe4e3adf48fa44fefded93e5c2b
> Author: Steven Rostedt (Red Hat) <rostedt@xxxxxxxxxxx>
> Date: Thu Apr 24 10:40:12 2014 -0400
>
> ftrace/module: Hardcode ftrace_module_init() call into load_module()
>
> But it can't fully resolve the issue.
>
> THis patch holds ftrace_mutex across ftrace_module_init and
> complete_formation.
>
> Signed-off-by: Qiu Peiyang <peiyangx.qiu@xxxxxxxxx>
> Signed-off-by: Zhang Yanmin <yanmin.zhang@xxxxxxxxx>
First, this patch has major whitespace damage. All tabs are now spaces!
> ---
> include/linux/ftrace.h | 6 ++++--
> kernel/module.c | 3 ++-
> kernel/trace/ftrace.c | 33 ++++++++++++++++++++++-----------
> 3 files changed, 28 insertions(+), 14 deletions(-)
>
> diff --git a/include/linux/ftrace.h b/include/linux/ftrace.h
> index eae6548..4adc279 100644
> --- a/include/linux/ftrace.h
> +++ b/include/linux/ftrace.h
> @@ -585,7 +585,8 @@ static inline int ftrace_modify_call(struct
> dyn_ftrace *rec, unsigned long old_a
> extern int ftrace_arch_read_dyn_info(char *buf, int size);
>
> extern int skip_trace(unsigned long ip);
> -extern void ftrace_module_init(struct module *mod);
> +extern void ftrace_module_init_start(struct module *mod);
> +extern void ftrace_module_init_end(struct module *mod);
>
> extern void ftrace_disable_daemon(void);
> extern void ftrace_enable_daemon(void);
> @@ -595,7 +596,8 @@ static inline int ftrace_force_update(void) { return
> 0; }
> static inline void ftrace_disable_daemon(void) { }
> static inline void ftrace_enable_daemon(void) { }
> static inline void ftrace_release_mod(struct module *mod) {}
> -static inline void ftrace_module_init(struct module *mod) {}
> +static inline void ftrace_module_init_start(struct module *mod) {}
> +static inline void ftrace_module_init_end(struct module *mod) {}
> static inline __init int register_ftrace_command(struct
> ftrace_func_command *cmd)
> {
> return -EINVAL;
> diff --git a/kernel/module.c b/kernel/module.c
> index 8f051a1..324c5c6 100644
> --- a/kernel/module.c
> +++ b/kernel/module.c
> @@ -3506,10 +3506,11 @@ static int load_module(struct load_info *info,
> const char __user *uargs,
> dynamic_debug_setup(info->debug, info->num_debug);
>
> /* Ftrace init must be called in the MODULE_STATE_UNFORMED state */
> - ftrace_module_init(mod);
> + ftrace_module_init_start(mod);
>
> /* Finally it's fully formed, ready to start executing. */
> err = complete_formation(mod, info);
> + ftrace_module_init_end(mod);
Instead of modifying the module code even more, why not just put this
into the module notifier when done?
Then this would only need to change the ftrace code. Although, holding
a lock throughout seems rather hacky.
> if (err)
> goto ddebug_cleanup;
>
> diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c
> index 3f743b1..436c199 100644
> --- a/kernel/trace/ftrace.c
> +++ b/kernel/trace/ftrace.c
> @@ -4795,7 +4795,7 @@ static int ftrace_cmp_ips(const void *a, const
> void *b)
> return 0;
> }
>
> -static int ftrace_process_locs(struct module *mod,
> +static int ftrace_process_locs_nolock(struct module *mod,
Note, the usually method for functions in the tracing code that do not
take locks is to simply prepend a double underscore (__) in front of
the function name: __ftrace_process_locs()
> unsigned long *start,
> unsigned long *end)
> {
> @@ -4820,8 +4820,6 @@ static int ftrace_process_locs(struct module *mod,
> if (!start_pg)
> return -ENOMEM;
>
> - mutex_lock(&ftrace_lock);
> -
> /*
> * Core and each module needs their own pages, as
> * modules will free them when they are removed.
> @@ -4889,8 +4887,18 @@ static int ftrace_process_locs(struct module *mod,
> local_irq_restore(flags);
> ret = 0;
> out:
> - mutex_unlock(&ftrace_lock);
> + return ret;
> +}
We can remove the out, and all the "goto out" can be changed to return.
>
> +static int ftrace_process_locs(struct module *mod,
> + unsigned long *start,
> + unsigned long *end)
> +{
> + int ret;
> +
> + mutex_lock(&ftrace_lock);
> + ret = ftrace_process_locs_nolock(mod, start, end);
> + mutex_unlock(&ftrace_lock);
> return ret;
> }
>
> @@ -4940,19 +4948,22 @@ void ftrace_release_mod(struct module *mod)
> mutex_unlock(&ftrace_lock);
> }
>
> -static void ftrace_init_module(struct module *mod,
> - unsigned long *start, unsigned long *end)
> +void ftrace_module_init_start(struct module *mod)
> {
> + unsigned long *start = mod->ftrace_callsites;
> + unsigned long *end = mod->ftrace_callsites + mod->num_ftrace_callsites;
> +
> + mutex_lock(&ftrace_lock);
> +
> if (ftrace_disabled || start == end)
> return;
> - ftrace_process_locs(mod, start, end);
> +
> + ftrace_process_locs_nolock(mod, start, end);
> }
>
> -void ftrace_module_init(struct module *mod)
> +void ftrace_module_init_end(struct module *mod)
> {
> - ftrace_init_module(mod, mod->ftrace_callsites,
> - mod->ftrace_callsites +
> - mod->num_ftrace_callsites);
> + mutex_unlock(&ftrace_lock);
> }
>
> static int ftrace_module_notify_exit(struct notifier_block *self,
This seems very hackish, although I can't think of a better way at the
moment. But I would like not to add more code into module.c if
possible, and just use a notifier unless there's a real reason we can't
(as there was when we added the hook in the first place).
-- Steve
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/