• Andrii Nakryiko's avatar
    uprobes: revamp uprobe refcounting and lifetime management · 3f7f1a64
    Andrii Nakryiko authored
    Revamp how struct uprobe is refcounted, and thus how its lifetime is
    managed.
    
    Right now, there are a few possible "owners" of uprobe refcount:
      - uprobes_tree RB tree assumes one refcount when uprobe is registered
        and added to the lookup tree;
      - while uprobe is triggered and kernel is handling it in the breakpoint
        handler code, temporary refcount bump is done to keep uprobe from
        being freed;
      - if we have uretprobe requested on a given struct uprobe instance, we
        take another refcount to keep uprobe alive until user space code
        returns from the function and triggers return handler.
    
    The uprobe_tree's extra refcount of 1 is confusing and problematic. No
    matter how many actual consumers are attached, they all share the same
    refcount, and we have an extra logic to drop the "last" (which might not
    really be last) refcount once uprobe's consumer list becomes empty.
    
    This is unconventional and has to be kept in mind as a special case all
    the time. Further, because of this design we have the situations where
    find_uprobe() will find uprobe, bump refcount, return it to the caller,
    but that uprobe will still need uprobe_is_active() check, after which
    the caller is required to drop refcount and try again. This is just too
    many details leaking to the higher level logic.
    
    This patch changes refcounting scheme in such a way as to not have
    uprobes_tree keeping extra refcount for struct uprobe. Instead, each
    uprobe_consumer is assuming its own refcount, which will be dropped
    when consumer is unregistered. Other than that, all the active users of
    uprobe (entry and return uprobe handling code) keeps exactly the same
    refcounting approach.
    
    With the above setup, once uprobe's refcount drops to zero, we need to
    make sure that uprobe's "destructor" removes uprobe from uprobes_tree,
    of course. This, though, races with uprobe entry handling code in
    handle_swbp(), which, through find_active_uprobe()->find_uprobe() lookup,
    can race with uprobe being destroyed after refcount drops to zero (e.g.,
    due to uprobe_consumer unregistering). So we add try_get_uprobe(), which
    will attempt to bump refcount, unless it already is zero. Caller needs
    to guarantee that uprobe instance won't be freed in parallel, which is
    the case while we keep uprobes_treelock (for read or write, doesn't
    matter).
    
    Note also, we now don't leak the race between registration and
    unregistration, so we remove the retry logic completely. If
    find_uprobe() returns valid uprobe, it's guaranteed to remain in
    uprobes_tree with properly incremented refcount. The race is handled
    inside __insert_uprobe() and put_uprobe() working together:
    __insert_uprobe() will remove uprobe from RB-tree, if it can't bump
    refcount and will retry to insert the new uprobe instance. put_uprobe()
    won't attempt to remove uprobe from RB-tree, if it's already not there.
    All that is protected by uprobes_treelock, which keeps things simple.
    Signed-off-by: default avatarAndrii Nakryiko <andrii@kernel.org>
    Signed-off-by: default avatarPeter Zijlstra (Intel) <peterz@infradead.org>
    Reviewed-by: default avatarOleg Nesterov <oleg@redhat.com>
    Link: https://lore.kernel.org/r/20240903174603.3554182-2-andrii@kernel.org
    3f7f1a64
uprobes.c 58 KB