2022-03-15 17:00:38 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* fprobe - Simple ftrace probe wrapper for function entry .
*/
# define pr_fmt(fmt) "fprobe: " fmt
# include <linux/err.h>
# include <linux/fprobe.h>
# include <linux/kallsyms.h>
# include <linux/kprobes.h>
2022-03-15 17:01:48 +03:00
# include <linux/rethook.h>
2022-03-15 17:00:38 +03:00
# include <linux/slab.h>
# include <linux/sort.h>
2022-03-15 17:01:48 +03:00
# include "trace.h"
struct fprobe_rethook_node {
struct rethook_node node ;
unsigned long entry_ip ;
} ;
2022-03-15 17:00:38 +03:00
static void fprobe_handler ( unsigned long ip , unsigned long parent_ip ,
struct ftrace_ops * ops , struct ftrace_regs * fregs )
{
2022-03-15 17:01:48 +03:00
struct fprobe_rethook_node * fpr ;
struct rethook_node * rh ;
2022-03-15 17:00:38 +03:00
struct fprobe * fp ;
int bit ;
fp = container_of ( ops , struct fprobe , ops ) ;
if ( fprobe_disabled ( fp ) )
return ;
bit = ftrace_test_recursion_trylock ( ip , parent_ip ) ;
if ( bit < 0 ) {
fp - > nmissed + + ;
return ;
}
if ( fp - > entry_handler )
fp - > entry_handler ( fp , ip , ftrace_get_regs ( fregs ) ) ;
2022-03-15 17:01:48 +03:00
if ( fp - > exit_handler ) {
rh = rethook_try_get ( fp - > rethook ) ;
if ( ! rh ) {
fp - > nmissed + + ;
goto out ;
}
fpr = container_of ( rh , struct fprobe_rethook_node , node ) ;
fpr - > entry_ip = ip ;
rethook_hook ( rh , ftrace_get_regs ( fregs ) , true ) ;
}
out :
2022-03-15 17:00:38 +03:00
ftrace_test_recursion_unlock ( bit ) ;
}
NOKPROBE_SYMBOL ( fprobe_handler ) ;
2022-03-15 17:02:11 +03:00
static void fprobe_kprobe_handler ( unsigned long ip , unsigned long parent_ip ,
struct ftrace_ops * ops , struct ftrace_regs * fregs )
{
struct fprobe * fp = container_of ( ops , struct fprobe , ops ) ;
if ( unlikely ( kprobe_running ( ) ) ) {
fp - > nmissed + + ;
return ;
}
kprobe_busy_begin ( ) ;
fprobe_handler ( ip , parent_ip , ops , fregs ) ;
kprobe_busy_end ( ) ;
}
2022-03-15 17:01:48 +03:00
static void fprobe_exit_handler ( struct rethook_node * rh , void * data ,
struct pt_regs * regs )
{
struct fprobe * fp = ( struct fprobe * ) data ;
struct fprobe_rethook_node * fpr ;
if ( ! fp | | fprobe_disabled ( fp ) )
return ;
fpr = container_of ( rh , struct fprobe_rethook_node , node ) ;
fp - > exit_handler ( fp , fpr - > entry_ip , regs ) ;
}
NOKPROBE_SYMBOL ( fprobe_exit_handler ) ;
2022-05-10 15:26:14 +03:00
static int symbols_cmp ( const void * a , const void * b )
{
const char * * str_a = ( const char * * ) a ;
const char * * str_b = ( const char * * ) b ;
return strcmp ( * str_a , * str_b ) ;
}
2022-03-15 17:00:38 +03:00
/* Convert ftrace location address from symbols */
static unsigned long * get_ftrace_locations ( const char * * syms , int num )
{
unsigned long * addrs ;
/* Convert symbols to symbol address */
addrs = kcalloc ( num , sizeof ( * addrs ) , GFP_KERNEL ) ;
if ( ! addrs )
return ERR_PTR ( - ENOMEM ) ;
2022-05-10 15:26:14 +03:00
/* ftrace_lookup_symbols expects sorted symbols */
sort ( syms , num , sizeof ( * syms ) , symbols_cmp , NULL ) ;
2022-03-15 17:00:38 +03:00
2022-05-10 15:26:14 +03:00
if ( ! ftrace_lookup_symbols ( syms , num , addrs ) )
return addrs ;
2022-03-15 17:00:38 +03:00
kfree ( addrs ) ;
return ERR_PTR ( - ENOENT ) ;
}
static void fprobe_init ( struct fprobe * fp )
{
fp - > nmissed = 0 ;
2022-03-15 17:02:11 +03:00
if ( fprobe_shared_with_kprobes ( fp ) )
fp - > ops . func = fprobe_kprobe_handler ;
else
fp - > ops . func = fprobe_handler ;
2022-03-15 17:00:38 +03:00
fp - > ops . flags | = FTRACE_OPS_FL_SAVE_REGS ;
}
2022-03-15 17:01:48 +03:00
static int fprobe_init_rethook ( struct fprobe * fp , int num )
{
int i , size ;
if ( num < 0 )
return - EINVAL ;
if ( ! fp - > exit_handler ) {
fp - > rethook = NULL ;
return 0 ;
}
/* Initialize rethook if needed */
size = num * num_possible_cpus ( ) * 2 ;
if ( size < 0 )
return - E2BIG ;
fp - > rethook = rethook_alloc ( ( void * ) fp , fprobe_exit_handler ) ;
2022-10-25 06:12:08 +03:00
if ( ! fp - > rethook )
return - ENOMEM ;
2022-03-15 17:01:48 +03:00
for ( i = 0 ; i < size ; i + + ) {
2022-03-23 10:35:26 +03:00
struct fprobe_rethook_node * node ;
2022-03-15 17:01:48 +03:00
2022-03-23 10:35:26 +03:00
node = kzalloc ( sizeof ( * node ) , GFP_KERNEL ) ;
2022-03-15 17:01:48 +03:00
if ( ! node ) {
rethook_free ( fp - > rethook ) ;
fp - > rethook = NULL ;
return - ENOMEM ;
}
2022-03-23 10:35:26 +03:00
rethook_add_node ( fp - > rethook , & node - > node ) ;
2022-03-15 17:01:48 +03:00
}
return 0 ;
}
static void fprobe_fail_cleanup ( struct fprobe * fp )
{
if ( fp - > rethook ) {
/* Don't need to cleanup rethook->handler because this is not used. */
rethook_free ( fp - > rethook ) ;
fp - > rethook = NULL ;
}
ftrace_free_filter ( & fp - > ops ) ;
}
2022-03-15 17:00:38 +03:00
/**
* register_fprobe ( ) - Register fprobe to ftrace by pattern .
* @ fp : A fprobe data structure to be registered .
* @ filter : A wildcard pattern of probed symbols .
* @ notfilter : A wildcard pattern of NOT probed symbols .
*
* Register @ fp to ftrace for enabling the probe on the symbols matched to @ filter .
* If @ notfilter is not NULL , the symbols matched the @ notfilter are not probed .
*
* Return 0 if @ fp is registered successfully , - errno if not .
*/
int register_fprobe ( struct fprobe * fp , const char * filter , const char * notfilter )
{
2022-03-15 17:01:48 +03:00
struct ftrace_hash * hash ;
2022-03-15 17:00:38 +03:00
unsigned char * str ;
int ret , len ;
if ( ! fp | | ! filter )
return - EINVAL ;
fprobe_init ( fp ) ;
len = strlen ( filter ) ;
str = kstrdup ( filter , GFP_KERNEL ) ;
ret = ftrace_set_filter ( & fp - > ops , str , len , 0 ) ;
kfree ( str ) ;
if ( ret )
return ret ;
if ( notfilter ) {
len = strlen ( notfilter ) ;
str = kstrdup ( notfilter , GFP_KERNEL ) ;
ret = ftrace_set_notrace ( & fp - > ops , str , len , 0 ) ;
kfree ( str ) ;
if ( ret )
goto out ;
}
2022-03-15 17:01:48 +03:00
/* TODO:
* correctly calculate the total number of filtered symbols
* from both filter and notfilter .
*/
2022-03-23 10:35:36 +03:00
hash = rcu_access_pointer ( fp - > ops . local_hash . filter_hash ) ;
2022-03-15 17:01:48 +03:00
if ( WARN_ON_ONCE ( ! hash ) )
goto out ;
ret = fprobe_init_rethook ( fp , ( int ) hash - > count ) ;
if ( ! ret )
ret = register_ftrace_function ( & fp - > ops ) ;
2022-03-15 17:00:38 +03:00
out :
if ( ret )
2022-03-15 17:01:48 +03:00
fprobe_fail_cleanup ( fp ) ;
2022-03-15 17:00:38 +03:00
return ret ;
}
EXPORT_SYMBOL_GPL ( register_fprobe ) ;
/**
* register_fprobe_ips ( ) - Register fprobe to ftrace by address .
* @ fp : A fprobe data structure to be registered .
* @ addrs : An array of target ftrace location addresses .
* @ num : The number of entries of @ addrs .
*
* Register @ fp to ftrace for enabling the probe on the address given by @ addrs .
* The @ addrs must be the addresses of ftrace location address , which may be
* the symbol address + arch - dependent offset .
* If you unsure what this mean , please use other registration functions .
*
* Return 0 if @ fp is registered successfully , - errno if not .
*/
int register_fprobe_ips ( struct fprobe * fp , unsigned long * addrs , int num )
{
int ret ;
if ( ! fp | | ! addrs | | num < = 0 )
return - EINVAL ;
fprobe_init ( fp ) ;
ret = ftrace_set_filter_ips ( & fp - > ops , addrs , num , 0 , 0 ) ;
2022-03-15 17:01:48 +03:00
if ( ret )
return ret ;
ret = fprobe_init_rethook ( fp , num ) ;
2022-03-15 17:00:38 +03:00
if ( ! ret )
ret = register_ftrace_function ( & fp - > ops ) ;
if ( ret )
2022-03-15 17:01:48 +03:00
fprobe_fail_cleanup ( fp ) ;
2022-03-15 17:00:38 +03:00
return ret ;
}
EXPORT_SYMBOL_GPL ( register_fprobe_ips ) ;
/**
* register_fprobe_syms ( ) - Register fprobe to ftrace by symbols .
* @ fp : A fprobe data structure to be registered .
* @ syms : An array of target symbols .
* @ num : The number of entries of @ syms .
*
* Register @ fp to the symbols given by @ syms array . This will be useful if
* you are sure the symbols exist in the kernel .
*
* Return 0 if @ fp is registered successfully , - errno if not .
*/
int register_fprobe_syms ( struct fprobe * fp , const char * * syms , int num )
{
unsigned long * addrs ;
int ret ;
if ( ! fp | | ! syms | | num < = 0 )
return - EINVAL ;
addrs = get_ftrace_locations ( syms , num ) ;
if ( IS_ERR ( addrs ) )
return PTR_ERR ( addrs ) ;
ret = register_fprobe_ips ( fp , addrs , num ) ;
kfree ( addrs ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( register_fprobe_syms ) ;
/**
* unregister_fprobe ( ) - Unregister fprobe from ftrace
* @ fp : A fprobe data structure to be unregistered .
*
* Unregister fprobe ( and remove ftrace hooks from the function entries ) .
*
* Return 0 if @ fp is unregistered successfully , - errno if not .
*/
int unregister_fprobe ( struct fprobe * fp )
{
int ret ;
2022-10-23 05:11:43 +03:00
if ( ! fp | | ( fp - > ops . saved_func ! = fprobe_handler & &
fp - > ops . saved_func ! = fprobe_kprobe_handler ) )
2022-03-15 17:00:38 +03:00
return - EINVAL ;
2022-03-15 17:01:48 +03:00
/*
* rethook_free ( ) starts disabling the rethook , but the rethook handlers
* may be running on other processors at this point . To make sure that all
* current running handlers are finished , call unregister_ftrace_function ( )
* after this .
*/
if ( fp - > rethook )
rethook_free ( fp - > rethook ) ;
2022-03-15 17:00:38 +03:00
ret = unregister_ftrace_function ( & fp - > ops ) ;
2022-03-15 17:01:48 +03:00
if ( ret < 0 )
return ret ;
2022-03-15 17:00:38 +03:00
2022-03-15 17:01:48 +03:00
ftrace_free_filter ( & fp - > ops ) ;
2022-03-15 17:00:38 +03:00
return ret ;
}
EXPORT_SYMBOL_GPL ( unregister_fprobe ) ;