Skip to content

Commit 682968e

Browse files
srikardIngo Molnar
authored andcommitted
uprobes/core: Optimize probe hits with the help of a counter
Maintain a per-mm counter: number of uprobes that are inserted on this process address space. This counter can be used at probe hit time to determine if we need a lookup in the uprobes rbtree. Everytime a probe gets inserted successfully, the probe count is incremented and everytime a probe gets removed, the probe count is decremented. The new uprobe_munmap hook ensures the count is correct on a unmap or remap of a region. We expect that once a uprobe_munmap() is called, the vma goes away. So uprobe_unregister() finding a probe to unregister would either mean unmap event hasnt occurred yet or a mmap event on the same executable file occured after a unmap event. Additionally, uprobe_mmap hook now also gets called: a. on every executable vma that is COWed at fork. b. a vma of interest is newly mapped; breakpoint insertion also happens at the required address. On process creation, make sure the probes count in the child is set correctly. Special cases that are taken care include: a. mremap b. VM_DONTCOPY vmas on fork() c. insertion/removal races in the parent during fork(). Signed-off-by: Srikar Dronamraju <[email protected]> Cc: Linus Torvalds <[email protected]> Cc: Ananth N Mavinakayanahalli <[email protected]> Cc: Jim Keniston <[email protected]> Cc: Linux-mm <[email protected]> Cc: Oleg Nesterov <[email protected]> Cc: Andi Kleen <[email protected]> Cc: Christoph Hellwig <[email protected]> Cc: Steven Rostedt <[email protected]> Cc: Arnaldo Carvalho de Melo <[email protected]> Cc: Masami Hiramatsu <[email protected]> Cc: Anton Arapov <[email protected]> Cc: Peter Zijlstra <[email protected]> Link: http://lkml.kernel.org/r/[email protected] Signed-off-by: Ingo Molnar <[email protected]>
1 parent d4b3b63 commit 682968e

File tree

4 files changed

+128
-9
lines changed

4 files changed

+128
-9
lines changed

include/linux/uprobes.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,15 @@ struct xol_area {
9999

100100
struct uprobes_state {
101101
struct xol_area *xol_area;
102+
atomic_t count;
102103
};
103104
extern int __weak set_swbp(struct arch_uprobe *aup, struct mm_struct *mm, unsigned long vaddr);
104105
extern int __weak set_orig_insn(struct arch_uprobe *aup, struct mm_struct *mm, unsigned long vaddr, bool verify);
105106
extern bool __weak is_swbp_insn(uprobe_opcode_t *insn);
106107
extern int uprobe_register(struct inode *inode, loff_t offset, struct uprobe_consumer *uc);
107108
extern void uprobe_unregister(struct inode *inode, loff_t offset, struct uprobe_consumer *uc);
108109
extern int uprobe_mmap(struct vm_area_struct *vma);
110+
extern void uprobe_munmap(struct vm_area_struct *vma);
109111
extern void uprobe_free_utask(struct task_struct *t);
110112
extern void uprobe_copy_process(struct task_struct *t);
111113
extern unsigned long __weak uprobe_get_swbp_addr(struct pt_regs *regs);
@@ -132,6 +134,9 @@ static inline int uprobe_mmap(struct vm_area_struct *vma)
132134
{
133135
return 0;
134136
}
137+
static inline void uprobe_munmap(struct vm_area_struct *vma)
138+
{
139+
}
135140
static inline void uprobe_notify_resume(struct pt_regs *regs)
136141
{
137142
}

kernel/events/uprobes.c

Lines changed: 111 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,29 @@ copy_insn(struct uprobe *uprobe, struct vm_area_struct *vma, unsigned long addr)
642642
return __copy_insn(mapping, vma, uprobe->arch.insn, bytes, uprobe->offset);
643643
}
644644

645+
/*
646+
* How mm->uprobes_state.count gets updated
647+
* uprobe_mmap() increments the count if
648+
* - it successfully adds a breakpoint.
649+
* - it cannot add a breakpoint, but sees that there is a underlying
650+
* breakpoint (via a is_swbp_at_addr()).
651+
*
652+
* uprobe_munmap() decrements the count if
653+
* - it sees a underlying breakpoint, (via is_swbp_at_addr)
654+
* (Subsequent uprobe_unregister wouldnt find the breakpoint
655+
* unless a uprobe_mmap kicks in, since the old vma would be
656+
* dropped just after uprobe_munmap.)
657+
*
658+
* uprobe_register increments the count if:
659+
* - it successfully adds a breakpoint.
660+
*
661+
* uprobe_unregister decrements the count if:
662+
* - it sees a underlying breakpoint and removes successfully.
663+
* (via is_swbp_at_addr)
664+
* (Subsequent uprobe_munmap wouldnt find the breakpoint
665+
* since there is no underlying breakpoint after the
666+
* breakpoint removal.)
667+
*/
645668
static int
646669
install_breakpoint(struct uprobe *uprobe, struct mm_struct *mm,
647670
struct vm_area_struct *vma, loff_t vaddr)
@@ -675,15 +698,28 @@ install_breakpoint(struct uprobe *uprobe, struct mm_struct *mm,
675698

676699
uprobe->flags |= UPROBE_COPY_INSN;
677700
}
701+
702+
/*
703+
* Ideally, should be updating the probe count after the breakpoint
704+
* has been successfully inserted. However a thread could hit the
705+
* breakpoint we just inserted even before the probe count is
706+
* incremented. If this is the first breakpoint placed, breakpoint
707+
* notifier might ignore uprobes and pass the trap to the thread.
708+
* Hence increment before and decrement on failure.
709+
*/
710+
atomic_inc(&mm->uprobes_state.count);
678711
ret = set_swbp(&uprobe->arch, mm, addr);
712+
if (ret)
713+
atomic_dec(&mm->uprobes_state.count);
679714

680715
return ret;
681716
}
682717

683718
static void
684719
remove_breakpoint(struct uprobe *uprobe, struct mm_struct *mm, loff_t vaddr)
685720
{
686-
set_orig_insn(&uprobe->arch, mm, (unsigned long)vaddr, true);
721+
if (!set_orig_insn(&uprobe->arch, mm, (unsigned long)vaddr, true))
722+
atomic_dec(&mm->uprobes_state.count);
687723
}
688724

689725
/*
@@ -1009,7 +1045,7 @@ int uprobe_mmap(struct vm_area_struct *vma)
10091045
struct list_head tmp_list;
10101046
struct uprobe *uprobe, *u;
10111047
struct inode *inode;
1012-
int ret;
1048+
int ret, count;
10131049

10141050
if (!atomic_read(&uprobe_events) || !valid_vma(vma, true))
10151051
return 0;
@@ -1023,28 +1059,93 @@ int uprobe_mmap(struct vm_area_struct *vma)
10231059
build_probe_list(inode, &tmp_list);
10241060

10251061
ret = 0;
1062+
count = 0;
10261063

10271064
list_for_each_entry_safe(uprobe, u, &tmp_list, pending_list) {
10281065
loff_t vaddr;
10291066

10301067
list_del(&uprobe->pending_list);
10311068
if (!ret) {
10321069
vaddr = vma_address(vma, uprobe->offset);
1033-
if (vaddr >= vma->vm_start && vaddr < vma->vm_end) {
1034-
ret = install_breakpoint(uprobe, vma->vm_mm, vma, vaddr);
1035-
/* Ignore double add: */
1036-
if (ret == -EEXIST)
1037-
ret = 0;
1070+
1071+
if (vaddr < vma->vm_start || vaddr >= vma->vm_end) {
1072+
put_uprobe(uprobe);
1073+
continue;
10381074
}
1075+
1076+
ret = install_breakpoint(uprobe, vma->vm_mm, vma, vaddr);
1077+
1078+
/* Ignore double add: */
1079+
if (ret == -EEXIST) {
1080+
ret = 0;
1081+
1082+
if (!is_swbp_at_addr(vma->vm_mm, vaddr))
1083+
continue;
1084+
1085+
/*
1086+
* Unable to insert a breakpoint, but
1087+
* breakpoint lies underneath. Increment the
1088+
* probe count.
1089+
*/
1090+
atomic_inc(&vma->vm_mm->uprobes_state.count);
1091+
}
1092+
1093+
if (!ret)
1094+
count++;
10391095
}
10401096
put_uprobe(uprobe);
10411097
}
10421098

10431099
mutex_unlock(uprobes_mmap_hash(inode));
10441100

1101+
if (ret)
1102+
atomic_sub(count, &vma->vm_mm->uprobes_state.count);
1103+
10451104
return ret;
10461105
}
10471106

1107+
/*
1108+
* Called in context of a munmap of a vma.
1109+
*/
1110+
void uprobe_munmap(struct vm_area_struct *vma)
1111+
{
1112+
struct list_head tmp_list;
1113+
struct uprobe *uprobe, *u;
1114+
struct inode *inode;
1115+
1116+
if (!atomic_read(&uprobe_events) || !valid_vma(vma, false))
1117+
return;
1118+
1119+
if (!atomic_read(&vma->vm_mm->uprobes_state.count))
1120+
return;
1121+
1122+
inode = vma->vm_file->f_mapping->host;
1123+
if (!inode)
1124+
return;
1125+
1126+
INIT_LIST_HEAD(&tmp_list);
1127+
mutex_lock(uprobes_mmap_hash(inode));
1128+
build_probe_list(inode, &tmp_list);
1129+
1130+
list_for_each_entry_safe(uprobe, u, &tmp_list, pending_list) {
1131+
loff_t vaddr;
1132+
1133+
list_del(&uprobe->pending_list);
1134+
vaddr = vma_address(vma, uprobe->offset);
1135+
1136+
if (vaddr >= vma->vm_start && vaddr < vma->vm_end) {
1137+
/*
1138+
* An unregister could have removed the probe before
1139+
* unmap. So check before we decrement the count.
1140+
*/
1141+
if (is_swbp_at_addr(vma->vm_mm, vaddr) == 1)
1142+
atomic_dec(&vma->vm_mm->uprobes_state.count);
1143+
}
1144+
put_uprobe(uprobe);
1145+
}
1146+
mutex_unlock(uprobes_mmap_hash(inode));
1147+
}
1148+
10481149
/* Slot allocation for XOL */
10491150
static int xol_add_vma(struct xol_area *area)
10501151
{
@@ -1150,6 +1251,7 @@ void uprobe_clear_state(struct mm_struct *mm)
11501251
void uprobe_reset_state(struct mm_struct *mm)
11511252
{
11521253
mm->uprobes_state.xol_area = NULL;
1254+
atomic_set(&mm->uprobes_state.count, 0);
11531255
}
11541256

11551257
/*
@@ -1504,7 +1606,8 @@ int uprobe_pre_sstep_notifier(struct pt_regs *regs)
15041606
{
15051607
struct uprobe_task *utask;
15061608

1507-
if (!current->mm)
1609+
if (!current->mm || !atomic_read(&current->mm->uprobes_state.count))
1610+
/* task is currently not uprobed */
15081611
return 0;
15091612

15101613
utask = current->utask;

kernel/fork.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,9 @@ static int dup_mmap(struct mm_struct *mm, struct mm_struct *oldmm)
421421

422422
if (retval)
423423
goto out;
424+
425+
if (file && uprobe_mmap(tmp))
426+
goto out;
424427
}
425428
/* a new mm has just been created */
426429
arch_dup_mmap(oldmm, mm);

mm/mmap.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ void unlink_file_vma(struct vm_area_struct *vma)
218218
mutex_lock(&mapping->i_mmap_mutex);
219219
__remove_shared_vm_struct(vma, file, mapping);
220220
mutex_unlock(&mapping->i_mmap_mutex);
221+
uprobe_munmap(vma);
221222
}
222223
}
223224

@@ -546,8 +547,14 @@ again: remove_next = 1 + (end > next->vm_end);
546547

547548
if (file) {
548549
mapping = file->f_mapping;
549-
if (!(vma->vm_flags & VM_NONLINEAR))
550+
if (!(vma->vm_flags & VM_NONLINEAR)) {
550551
root = &mapping->i_mmap;
552+
uprobe_munmap(vma);
553+
554+
if (adjust_next)
555+
uprobe_munmap(next);
556+
}
557+
551558
mutex_lock(&mapping->i_mmap_mutex);
552559
if (insert) {
553560
/*
@@ -626,6 +633,7 @@ again: remove_next = 1 + (end > next->vm_end);
626633

627634
if (remove_next) {
628635
if (file) {
636+
uprobe_munmap(next);
629637
fput(file);
630638
if (next->vm_flags & VM_EXECUTABLE)
631639
removed_exe_file_vma(mm);

0 commit comments

Comments
 (0)