uprobes: pre-filtering
Subsystems (2)
Oleg Nesterov
maintainercover uprobes: pre-filtering
Hello.
On top of preparations I sent a month ago. Srikar, you still didn't review the last 2 patches. I assume you do not care so I am going to add them without your ack.
Initially I was going to send more patches in this series. We can add some optimizations to avoid the unnecessary register_for_each_vma() or even consumer->filter() if there is a consumer with ->filter == NULL. But lets do this later, lets discuss the functional change first.
Next steps:
- teach handler_chain() to remove the unwanted breakpoints
- if the above is not enough, implement UPROBE_FILTER_FORK
- implement uprobe_apply(consumer, task, is_register) ?
Ole.
Oleg Nesterov
maintainerPATCH 1/2 uprobes: Rationalize the usage of filter_chain()
filter_chain() was added into install_breakpoint/remove_breakpoint to simplify the initial changes but this is sub-optimal.
This patch shifts the callsite to the callers, register_for_each_vma() and uprobe_mmap(). This way:
- It will be easier to add the new arguments. This is the main reason, we can do more optimizations later.
- register_for_each_vma(is_register => true) can be optimized, we only need to consult the new consumer. The previous consumers were already asked when they called uprobe_register().
This patch also moves the MMF_HAS_UPROBES check from remove_breakpoint(), this allows to avoid the potentionally costly filter_chain(). Note that register_for_each_vma(is_register => false) doesn't really need to take
Quoted 1 line
consumer_rwsem, but I don't think it makes sense to optimize this and
introduce filter_chain_lockless().
--- kernel/events/uprobes.c | 44 +++++++++++++++++++++----------------------- 1 files changed, 21 insertions(+), 23 deletions(-)
kernel/events/uprobes.c +21 -23
@@ -579,6 +579,11 @@ static int prepare_uprobe(struct uprobe *uprobe, struct file *file,return ret; } +static inline bool consumer_filter (struct uprobe_consumer *uc) +{ +return true;/* TODO: !uc->filter || uc->filter(...) */ +} +static bool filter_chain (struct uprobe *uprobe) {struct uprobe_consumer *uc;
@@ -586,8 +591,7 @@ static bool filter_chain(struct uprobe *uprobe)down_read (&uprobe->consumer_rwsem);for (uc = uprobe->consumers; uc; uc = uc->next) { -/* TODO: ret = uc->filter(...) */ - ret = true; + ret =consumer_filter (uc);if (ret)break ; }
@@ -603,15 +607,6 @@ install_breakpoint(struct uprobe *uprobe, struct mm_struct *mm,bool first_uprobe;int ret; -/* -* If probe is being deleted, unregister thread could be done with -* the vma-rmap-walk through. Adding a probe now can be fatal since -* nobody will be able to cleanup. But in this case filter_chain() -* must return false, all consumers have gone away. -*/ -if (!filter_chain (uprobe)) -return 0 ; - ret =prepare_uprobe (uprobe, vma->vm_file, mm, vaddr);if (ret)return ret;
@@ -636,12 +631,6 @@ install_breakpoint(struct uprobe *uprobe, struct mm_struct *mm,static int remove_breakpoint (struct uprobe *uprobe,struct mm_struct *mm,unsigned long vaddr) { -if (!test_bit (MMF_HAS_UPROBES , &mm->flags)) -return 0 ; - -if (filter_chain (uprobe)) -return 0 ; -set_bit (MMF_RECALC_UPROBES , &mm->flags);return set_orig_insn (&uprobe->arch, mm, vaddr); }
@@ -781,10 +770,14 @@ static int register_for_each_vma(struct uprobe *uprobe, bool is_register) vaddr_to_offset(vma, info->vaddr) != uprobe->offset) goto unlock; -if (is_register) - err =install_breakpoint (uprobe, mm, vma, info->vaddr); -else - err |=remove_breakpoint (uprobe, mm, info->vaddr); +if (is_register) { +/* consult only the "caller", new consumer. */ +if (consumer_filter (uprobe->consumers)) + err =install_breakpoint (uprobe, mm, vma, info->vaddr); + }else if (test_bit (MMF_HAS_UPROBES , &mm->flags)) { +if (!filter_chain (uprobe)) + err |=remove_breakpoint (uprobe, mm, info->vaddr); + } unlock:up_write (&mm->mmap_sem);
@@ -968,9 +961,14 @@ int uprobe_mmap(struct vm_area_struct *vma)mutex_lock (uprobes_mmap_hash (inode));build_probe_list (inode, vma, vma->vm_start, vma->vm_end, &tmp_list); - +/* +* We can race with uprobe_unregister(), this uprobe can be already +* removed. But in this case filter_chain() must return false, all +* consumers have gone away. +*/ list_for_each_entry_safe (uprobe, u, &tmp_list, pending_list) { -if (!fatal_signal_pending (current)) { +if (!fatal_signal_pending (current) && +filter_chain (uprobe)) {unsigned long vaddr =offset_to_vaddr (vma, uprobe->offset);install_breakpoint (uprobe, vma->vm_mm, vma, vaddr); }
Signature · 1 line
1.5.5.1
Re: [PATCH 1/2] uprobes: Rationalize the usage of filter_chain()
Quoted 13 lines · Oleg Nesterov
filter_chain()was added into install_breakpoint/remove_breakpoint to simplify the initial changes but this is sub-optimal.This patch shifts the callsite to the callers,
register_for_each_vma()anduprobe_mmap(). This way:- It will be easier to add the new arguments. This is the main reason, we can do more optimizations later.
-
register_for_each_vma(is_register => true)can be optimized, we only need to consult the new consumer. The previous consumers were already asked when they calleduprobe_register().
Quoted 7 lines
This patch also moves the
MMF_HAS_UPROBEScheck fromremove_breakpoint(), this allows to avoid the potentionally costlyfilter_chain(). Note thatregister_for_each_vma(is_register => false)doesn't really need to takeQuoted 1 line
consumer_rwsem, but I don't think it makes sense to optimize this andintroduce
filter_chain_lockless().Signed-off-by Oleg Nesterov <[email protected]>
Quoted 98 lines
--- kernel/events/uprobes.c | 44 +++++++++++++++++++++----------------------- 1 files changed, 21 insertions(+), 23 deletions(-)kernel/events/uprobes.c +21
-23@@ -579,6 +579,11 @@ static int prepare_uprobe(struct uprobe *uprobe, struct file *file,return ret; } +static inline bool consumer_filter (struct uprobe_consumer *uc) +{ +return true;/* TODO: !uc->filter || uc->filter(...) */ +} +static bool filter_chain (struct uprobe *uprobe) {struct uprobe_consumer *uc;@@ -586,8 +591,7 @@ static bool filter_chain(struct uprobe *uprobe)down_read (&uprobe->consumer_rwsem);for (uc = uprobe->consumers; uc; uc = uc->next) { -/* TODO: ret = uc->filter(...) */ - ret = true; + ret =consumer_filter (uc);if (ret)break ; }@@ -603,15 +607,6 @@ install_breakpoint(struct uprobe *uprobe, struct mm_struct *mm,bool first_uprobe;int ret; -/* -* If probe is being deleted, unregister thread could be done with -* the vma-rmap-walk through. Adding a probe now can be fatal since -* nobody will be able to cleanup. But in this case filter_chain() -* must return false, all consumers have gone away. -*/ -if (!filter_chain (uprobe)) -return 0 ; - ret =prepare_uprobe (uprobe, vma->vm_file, mm, vaddr);if (ret)return ret;@@ -636,12 +631,6 @@ install_breakpoint(struct uprobe *uprobe, struct mm_struct *mm,static int remove_breakpoint (struct uprobe *uprobe,struct mm_struct *mm,unsigned long vaddr) { -if (!test_bit (MMF_HAS_UPROBES , &mm->flags)) -return 0 ; - -if (filter_chain (uprobe)) -return 0 ; -set_bit (MMF_RECALC_UPROBES , &mm->flags);return set_orig_insn (&uprobe->arch, mm, vaddr); }@@ -781,10 +770,14 @@ static int register_for_each_vma(struct uprobe *uprobe, bool is_register) vaddr_to_offset(vma, info->vaddr) != uprobe->offset) goto unlock; -if (is_register) - err =install_breakpoint (uprobe, mm, vma, info->vaddr); -else - err |=remove_breakpoint (uprobe, mm, info->vaddr); +if (is_register) { +/* consult only the "caller", new consumer. */ +if (consumer_filter (uprobe->consumers)) + err =install_breakpoint (uprobe, mm, vma, info->vaddr); + }else if (test_bit (MMF_HAS_UPROBES , &mm->flags)) { +if (!filter_chain (uprobe)) + err |=remove_breakpoint (uprobe, mm, info->vaddr); + } unlock:up_write (&mm->mmap_sem);@@ -968,9 +961,14 @@ int uprobe_mmap(struct vm_area_struct *vma)mutex_lock (uprobes_mmap_hash (inode));build_probe_list (inode, vma, vma->vm_start, vma->vm_end, &tmp_list); - +/* +* We can race with uprobe_unregister(), this uprobe can be already +* removed. But in this case filter_chain() must return false, all +* consumers have gone away. +*/ list_for_each_entry_safe (uprobe, u, &tmp_list, pending_list) { -if (!fatal_signal_pending (current)) { +if (!fatal_signal_pending (current) && +filter_chain (uprobe)) {unsigned long vaddr =offset_to_vaddr (vma, uprobe->offset);install_breakpoint (uprobe, vma->vm_mm, vma, vaddr); }Signature · 1 line
1.5.5.1
Oleg Nesterov
maintainerPATCH 2/2 uprobes: Reintroduce uprobe_consumer->filter()
In reply to Oleg NesterovFinally add uprobe_consumer->filter() and change consumer_filter() to actually call this method.
Note that ->filter() accepts mm_struct, not task_struct. Because:
1. We do not have for_each_mm_user(mm, task).
2. Even if we implement for_each_mm_user(), ->filter() can use it itself.
3. It is not clear who will actually need this interface to do the "nontrivial" filtering.
Another argument is "enum uprobe_filter_ctx", consumer->filter() can use it to figure out why/where it was called. For example, perhaps we can add UPROBE_FILTER_PRE_REGISTER used by build_map_info() to quickly "nack" the unwanted mm's. In this case consumer should know that it is called under ->i_mmap_mutex.
See the previous discussion at http://marc.info/?t=135214229700002
Perhaps we should pass more arguments, vma/vaddr?
Note: this patch obviously can't help to filter out the child created by fork(), this will be addressed later.
--- include/linux/uprobes.h | 9 +++++++++ kernel/events/uprobes.c | 18 +++++++++++------- 2 files changed, 20 insertions(+), 7 deletions(-)
include/linux/uprobes.h +9 -0
@@ -35,8 +35,17 @@ struct inode;# include <asm/uprobes.h> #endif +enum uprobe_filter_ctx { +UPROBE_FILTER_REGISTER , +UPROBE_FILTER_UNREGISTER , +UPROBE_FILTER_MMAP , +}; +struct uprobe_consumer {int (*handler)(struct uprobe_consumer *self,struct pt_regs *regs); +bool (*filter)(struct uprobe_consumer *self, +enum uprobe_filter_ctx ctx, +struct mm_struct *mm);struct uprobe_consumer *next; };
kernel/events/uprobes.c +11 -7
@@ -579,19 +579,21 @@ static int prepare_uprobe(struct uprobe *uprobe, struct file *file,return ret; } -static inline bool consumer_filter (struct uprobe_consumer *uc) +static inline bool consumer_filter (struct uprobe_consumer *uc, +enum uprobe_filter_ctx ctx,struct mm_struct *mm) { -return true;/* TODO: !uc->filter || uc->filter(...) */ +return !uc->filter || uc->filter (uc, ctx, mm); } -static bool filter_chain (struct uprobe *uprobe) +static bool filter_chain (struct uprobe *uprobe, +enum uprobe_filter_ctx ctx,struct mm_struct *mm) {struct uprobe_consumer *uc;bool ret = false;down_read (&uprobe->consumer_rwsem);for (uc = uprobe->consumers; uc; uc = uc->next) { - ret =consumer_filter (uc); + ret =consumer_filter (uc, ctx, mm);if (ret)break ; }
@@ -772,10 +774,12 @@ static int register_for_each_vma(struct uprobe *uprobe, bool is_register)if (is_register) {/* consult only the "caller", new consumer. */ -if (consumer_filter (uprobe->consumers)) +if (consumer_filter (uprobe->consumers, +UPROBE_FILTER_REGISTER , mm)) err =install_breakpoint (uprobe, mm, vma, info->vaddr); }else if (test_bit (MMF_HAS_UPROBES , &mm->flags)) { -if (!filter_chain (uprobe)) +if (!filter_chain (uprobe, +UPROBE_FILTER_UNREGISTER , mm)) err |=remove_breakpoint (uprobe, mm, info->vaddr); }
@@ -968,7 +972,7 @@ int uprobe_mmap(struct vm_area_struct *vma)*/ list_for_each_entry_safe (uprobe, u, &tmp_list, pending_list) {if (!fatal_signal_pending (current) && -filter_chain (uprobe)) { +filter_chain (uprobe,UPROBE_FILTER_MMAP , vma->vm_mm)) {unsigned long vaddr =offset_to_vaddr (vma, uprobe->offset);install_breakpoint (uprobe, vma->vm_mm, vma, vaddr); }
Signature · 1 line
1.5.5.1
Re: [PATCH 2/2] uprobes: Reintroduce uprobe_consumer->filter()
Quoted 19 lines · Oleg Nesterov
Finally add
uprobe_consumer->filter()and changeconsumer_filter()to actually call this method.Note that ->filter() accepts
mm_struct, nottask_struct. Because:1. We do not have
for_each_mm_user(mm, task).2. Even if we implement
for_each_mm_user(), ->filter() can use it itself.3. It is not clear who will actually need this interface to do the "nontrivial" filtering.
Another argument is "enum
uprobe_filter_ctx",consumer->filter()can use it to figure out why/where it was called. For example, perhaps we can addUPROBE_FILTER_PRE_REGISTERused bybuild_map_info()to quickly "nack" the unwanted mm's. In this case consumer should know that it is called under ->i_mmap_mutex.
Quoted 7 lines
See the previous discussion at http://marc.info/?t=135214229700002
Perhaps we should pass more arguments, vma/vaddr?Note: this patch obviously can't help to filter out the child created by
fork(), this will be addressed later.Signed-off-by Oleg Nesterov <[email protected]>
Quoted 84 lines
--- include/linux/uprobes.h | 9 +++++++++ kernel/events/uprobes.c | 18 +++++++++++------- 2 files changed, 20 insertions(+), 7 deletions(-)include/linux/uprobes.h +9
-0@@ -35,8 +35,17 @@ struct inode;# include <asm/uprobes.h> #endif +enum uprobe_filter_ctx { +UPROBE_FILTER_REGISTER , +UPROBE_FILTER_UNREGISTER , +UPROBE_FILTER_MMAP , +}; +struct uprobe_consumer {int (*handler)(struct uprobe_consumer *self,struct pt_regs *regs); +bool (*filter)(struct uprobe_consumer *self, +enum uprobe_filter_ctx ctx, +struct mm_struct *mm);struct uprobe_consumer *next; };kernel/events/uprobes.c +11
-7@@ -579,19 +579,21 @@ static int prepare_uprobe(struct uprobe *uprobe, struct file *file,return ret; } -static inline bool consumer_filter (struct uprobe_consumer *uc) +static inline bool consumer_filter (struct uprobe_consumer *uc, +enum uprobe_filter_ctx ctx,struct mm_struct *mm) { -return true;/* TODO: !uc->filter || uc->filter(...) */ +return !uc->filter || uc->filter (uc, ctx, mm); } -static bool filter_chain (struct uprobe *uprobe) +static bool filter_chain (struct uprobe *uprobe, +enum uprobe_filter_ctx ctx,struct mm_struct *mm) {struct uprobe_consumer *uc;bool ret = false;down_read (&uprobe->consumer_rwsem);for (uc = uprobe->consumers; uc; uc = uc->next) { - ret =consumer_filter (uc); + ret =consumer_filter (uc, ctx, mm);if (ret)break ; }@@ -772,10 +774,12 @@ static int register_for_each_vma(struct uprobe *uprobe, bool is_register)if (is_register) {/* consult only the "caller", new consumer. */ -if (consumer_filter (uprobe->consumers)) +if (consumer_filter (uprobe->consumers, +UPROBE_FILTER_REGISTER , mm)) err =install_breakpoint (uprobe, mm, vma, info->vaddr); }else if (test_bit (MMF_HAS_UPROBES , &mm->flags)) { -if (!filter_chain (uprobe)) +if (!filter_chain (uprobe, +UPROBE_FILTER_UNREGISTER , mm)) err |=remove_breakpoint (uprobe, mm, info->vaddr); }@@ -968,7 +972,7 @@ int uprobe_mmap(struct vm_area_struct *vma)*/ list_for_each_entry_safe (uprobe, u, &tmp_list, pending_list) {if (!fatal_signal_pending (current) && -filter_chain (uprobe)) { +filter_chain (uprobe,UPROBE_FILTER_MMAP , vma->vm_mm)) {unsigned long vaddr =offset_to_vaddr (vma, uprobe->offset);install_breakpoint (uprobe, vma->vm_mm, vma, vaddr); }Signature · 1 line
1.5.5.1