Make register_uprobe/ unregister_uprobe to defer if the task having an active probe is stopped or traced Signed-off-by: Srikar Dronamraju <sri...@linux.vnet.ibm.com> Reviewed-by: Jim Keniston<jkeni...@us.ibm.com> --- kernel/uprobes_core.c | 78 +++++++++++++++++++++++++++++++++++++++++++++---- 1 files changed, 72 insertions(+), 6 deletions(-)
diff --git a/kernel/uprobes_core.c b/kernel/uprobes_core.c index 74d568b..7cea30b 100644 --- a/kernel/uprobes_core.c +++ b/kernel/uprobes_core.c @@ -824,12 +824,35 @@ static void purge_uprobe(struct uprobe_kimg *uk) uprobe_free_probept(ppt); } -/* Runs with utask->uproc read-locked. Returns -EINPROGRESS on success. */ +/* + * Runs with utask->uproc locked. + * read lock if called from uprobe handler. + * else write lock. + * Returns -EINPROGRESS on success. + * Returns -EBUSY if a request for defer registration already exists. + * Returns 0 if we have deferred request for both register/unregister. + * + */ static int defer_registration(struct uprobe *u, int regflag, struct uprobe_task *utask) { - struct deferred_registration *dr; + struct deferred_registration *dr, *d; + /* Check if we already have such a defer request */ + list_for_each_entry_safe(dr, d, &utask->deferred_registrations, list) { + if (dr->uprobe == u) { + if (dr->regflag != regflag) { + /* same as successful register + unregister */ + list_del(&dr->list); + kfree(dr); + return 0; + } else + /* we already have identical request */ + return -EBUSY; + } + } + + /* We have a new unique request */ dr = kmalloc(sizeof(struct deferred_registration), GFP_USER); if (!dr) return -ENOMEM; @@ -885,7 +908,8 @@ int register_uprobe(struct uprobe *u) return -ESRCH; cur_utask = uprobe_find_utask(current); - if (cur_utask && cur_utask->active_probe) { + if (cur_utask && (cur_utask->state == UPTASK_TRAMPOLINE_HIT || + cur_utask->state == UPTASK_BP_HIT)) { /* * Called from handler; cur_utask->uproc is read-locked. * Do this registration later. @@ -898,9 +922,36 @@ int register_uprobe(struct uprobe *u) mutex_lock(&uproc_mutex); uproc = uprobe_find_process(p); - if (uproc) + if (uproc) { + struct uprobe_task *utask; + mutex_unlock(&uproc_mutex); - else { + list_for_each_entry(utask, &uproc->thread_list, list) { + if (!utask->active_probe) + continue; + /* + * utask is at a probepoint, but has dropped + * uproc->rwsem to single-step. If utask is + * stopped, then it's probably because some + * other engine has asserted UTRACE_STOP; + * that engine may not allow UTRACE_RESUME + * until register_uprobe() returns. But, for + * reasons we won't go into here, utask wants + * to finish with utask->active_probe before + * allowing handle_pending_uprobes() to run + * (via utask_fake_quiesce()). So we defer this + * registration operation; it will be run after + * utask->active_probe is taken care of. + */ + BUG_ON(utask->state != UPTASK_SSTEP); + if (task_is_stopped_or_traced(utask->tsk)) { + put_pid(p); + ret = defer_registration(u, 1, utask); + up_write(&uproc->rwsem); + return ret; + } + } + } else { uproc = uprobe_mk_process(p); if (IS_ERR(uproc)) { ret = (int) PTR_ERR(uproc); @@ -1041,6 +1092,7 @@ void unregister_uprobe(struct uprobe *u) struct uprobe_kimg *uk; struct uprobe_probept *ppt; struct uprobe_task *cur_utask, *cur_utask_quiescing = NULL; + struct uprobe_task *utask; if (!u) return; @@ -1049,7 +1101,8 @@ void unregister_uprobe(struct uprobe *u) return; cur_utask = uprobe_find_utask(current); - if (cur_utask && cur_utask->active_probe) { + if (cur_utask && (cur_utask->state == UPTASK_TRAMPOLINE_HIT || + cur_utask->state == UPTASK_BP_HIT)) { /* Called from handler; uproc is read-locked; do this later */ put_pid(p); (void) defer_registration(u, 0, cur_utask); @@ -1067,6 +1120,19 @@ void unregister_uprobe(struct uprobe *u) if (!uproc) return; + list_for_each_entry(utask, &uproc->thread_list, list) { + if (!utask->active_probe) + continue; + + /* See comment in register_uprobe(). */ + BUG_ON(utask->state != UPTASK_SSTEP); + if (task_is_stopped_or_traced(utask->tsk)) { + put_pid(p); + (void) defer_registration(u, 0, utask); + up_write(&uproc->rwsem); + return; + } + } uk = (struct uprobe_kimg *)u->kdata; if (!uk) /*