396 lines · 9.3 KB · C
RawDownload
// 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>#include <linux/rethook.h>#include <linux/slab.h>#include <linux/sort.h> #include "trace.h" struct fprobe_rethook_node {	struct rethook_node node;	unsigned long entry_ip;	unsigned long entry_parent_ip;	char data[];}; static inline void __fprobe_handler(unsigned long ip, unsigned long parent_ip,			struct ftrace_ops *ops, struct ftrace_regs *fregs){	struct fprobe_rethook_node *fpr;	struct rethook_node *rh = NULL;	struct fprobe *fp;	void *entry_data = NULL;	int ret = 0; 	fp = container_of(ops, struct fprobe, ops); 	if (fp->exit_handler) {		rh = rethook_try_get(fp->rethook);		if (!rh) {			fp->nmissed++;			return;		}		fpr = container_of(rh, struct fprobe_rethook_node, node);		fpr->entry_ip = ip;		fpr->entry_parent_ip = parent_ip;		if (fp->entry_data_size)			entry_data = fpr->data;	} 	if (fp->entry_handler)		ret = fp->entry_handler(fp, ip, parent_ip, ftrace_get_regs(fregs), entry_data); 	/* If entry_handler returns !0, nmissed is not counted. */	if (rh) {		if (ret)			rethook_recycle(rh);		else			rethook_hook(rh, ftrace_get_regs(fregs), true);	}} static void fprobe_handler(unsigned long ip, unsigned long parent_ip,		struct ftrace_ops *ops, struct ftrace_regs *fregs){	struct fprobe *fp;	int bit; 	fp = container_of(ops, struct fprobe, ops);	if (fprobe_disabled(fp))		return; 	/* recursion detection has to go before any traceable function and	 * all functions before this point should be marked as notrace	 */	bit = ftrace_test_recursion_trylock(ip, parent_ip);	if (bit < 0) {		fp->nmissed++;		return;	}	__fprobe_handler(ip, parent_ip, ops, fregs);	ftrace_test_recursion_unlock(bit); }NOKPROBE_SYMBOL(fprobe_handler); static void fprobe_kprobe_handler(unsigned long ip, unsigned long parent_ip,				  struct ftrace_ops *ops, struct ftrace_regs *fregs){	struct fprobe *fp;	int bit; 	fp = container_of(ops, struct fprobe, ops);	if (fprobe_disabled(fp))		return; 	/* recursion detection has to go before any traceable function and	 * all functions called before this point should be marked as notrace	 */	bit = ftrace_test_recursion_trylock(ip, parent_ip);	if (bit < 0) {		fp->nmissed++;		return;	} 	/*	 * This user handler is shared with other kprobes and is not expected to be	 * called recursively. So if any other kprobe handler is running, this will	 * exit as kprobe does. See the section 'Share the callbacks with kprobes'	 * in Documentation/trace/fprobe.rst for more information.	 */	if (unlikely(kprobe_running())) {		fp->nmissed++;		goto recursion_unlock;	} 	kprobe_busy_begin();	__fprobe_handler(ip, parent_ip, ops, fregs);	kprobe_busy_end(); recursion_unlock:	ftrace_test_recursion_unlock(bit);} static void fprobe_exit_handler(struct rethook_node *rh, void *data,				unsigned long ret_ip, struct pt_regs *regs){	struct fprobe *fp = (struct fprobe *)data;	struct fprobe_rethook_node *fpr;	int bit; 	if (!fp || fprobe_disabled(fp))		return; 	fpr = container_of(rh, struct fprobe_rethook_node, node); 	/*	 * we need to assure no calls to traceable functions in-between the	 * end of fprobe_handler and the beginning of fprobe_exit_handler.	 */	bit = ftrace_test_recursion_trylock(fpr->entry_ip, fpr->entry_parent_ip);	if (bit < 0) {		fp->nmissed++;		return;	} 	fp->exit_handler(fp, fpr->entry_ip, ret_ip, regs,			 fp->entry_data_size ? (void *)fpr->data : NULL);	ftrace_test_recursion_unlock(bit);}NOKPROBE_SYMBOL(fprobe_exit_handler); 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);} /* 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); 	/* ftrace_lookup_symbols expects sorted symbols */	sort(syms, num, sizeof(*syms), symbols_cmp, NULL); 	if (!ftrace_lookup_symbols(syms, num, addrs))		return addrs; 	kfree(addrs);	return ERR_PTR(-ENOENT);} static void fprobe_init(struct fprobe *fp){	fp->nmissed = 0;	if (fprobe_shared_with_kprobes(fp))		fp->ops.func = fprobe_kprobe_handler;	else		fp->ops.func = fprobe_handler;	fp->ops.flags |= FTRACE_OPS_FL_SAVE_REGS;} 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 */	if (fp->nr_maxactive)		size = fp->nr_maxactive;	else		size = num * num_possible_cpus() * 2;	if (size < 0)		return -E2BIG; 	fp->rethook = rethook_alloc((void *)fp, fprobe_exit_handler);	if (!fp->rethook)		return -ENOMEM;	for (i = 0; i < size; i++) {		struct fprobe_rethook_node *node; 		node = kzalloc(sizeof(*node) + fp->entry_data_size, GFP_KERNEL);		if (!node) {			rethook_free(fp->rethook);			fp->rethook = NULL;			return -ENOMEM;		}		rethook_add_node(fp->rethook, &node->node);	}	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);} /** * 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){	struct ftrace_hash *hash;	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;	} 	/* TODO:	 * correctly calculate the total number of filtered symbols	 * from both filter and notfilter.	 */	hash = rcu_access_pointer(fp->ops.local_hash.filter_hash);	if (WARN_ON_ONCE(!hash))		goto out; 	ret = fprobe_init_rethook(fp, (int)hash->count);	if (!ret)		ret = register_ftrace_function(&fp->ops); out:	if (ret)		fprobe_fail_cleanup(fp);	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);	if (ret)		return ret; 	ret = fprobe_init_rethook(fp, num);	if (!ret)		ret = register_ftrace_function(&fp->ops); 	if (ret)		fprobe_fail_cleanup(fp);	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); bool fprobe_is_registered(struct fprobe *fp){	if (!fp || (fp->ops.saved_func != fprobe_handler &&		    fp->ops.saved_func != fprobe_kprobe_handler))		return false;	return true;} /** * 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; 	if (!fprobe_is_registered(fp))		return -EINVAL; 	if (fp->rethook)		rethook_stop(fp->rethook); 	ret = unregister_ftrace_function(&fp->ops);	if (ret < 0)		return ret; 	if (fp->rethook)		rethook_free(fp->rethook); 	ftrace_free_filter(&fp->ops); 	return ret;}EXPORT_SYMBOL_GPL(unregister_fprobe);