1486 lines · 36.2 KB · C
RawDownload
/* * Kprobes-based tracing events * * Created by Masami Hiramatsu <mhiramat@redhat.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */ #include <linux/module.h>#include <linux/uaccess.h> #include "trace_probe.h" #define KPROBE_EVENT_SYSTEM "kprobes" /** * Kprobe event core functions */struct trace_kprobe {	struct list_head	list;	struct kretprobe	rp;	/* Use rp.kp for kprobe use */	unsigned long 		nhit;	const char		*symbol;	/* symbol name */	struct trace_probe	tp;}; #define SIZEOF_TRACE_KPROBE(n)				\	(offsetof(struct trace_kprobe, tp.args) +	\	(sizeof(struct probe_arg) * (n)))  static nokprobe_inline bool trace_kprobe_is_return(struct trace_kprobe *tk){	return tk->rp.handler != NULL;} static nokprobe_inline const char *trace_kprobe_symbol(struct trace_kprobe *tk){	return tk->symbol ? tk->symbol : "unknown";} static nokprobe_inline unsigned long trace_kprobe_offset(struct trace_kprobe *tk){	return tk->rp.kp.offset;} static nokprobe_inline bool trace_kprobe_has_gone(struct trace_kprobe *tk){	return !!(kprobe_gone(&tk->rp.kp));} static nokprobe_inline bool trace_kprobe_within_module(struct trace_kprobe *tk,						 struct module *mod){	int len = strlen(mod->name);	const char *name = trace_kprobe_symbol(tk);	return strncmp(mod->name, name, len) == 0 && name[len] == ':';} static nokprobe_inline bool trace_kprobe_is_on_module(struct trace_kprobe *tk){	return !!strchr(trace_kprobe_symbol(tk), ':');} static int register_kprobe_event(struct trace_kprobe *tk);static int unregister_kprobe_event(struct trace_kprobe *tk); static DEFINE_MUTEX(probe_lock);static LIST_HEAD(probe_list); static int kprobe_dispatcher(struct kprobe *kp, struct pt_regs *regs);static int kretprobe_dispatcher(struct kretprobe_instance *ri,				struct pt_regs *regs); /* Memory fetching by symbol */struct symbol_cache {	char		*symbol;	long		offset;	unsigned long	addr;}; unsigned long update_symbol_cache(struct symbol_cache *sc){	sc->addr = (unsigned long)kallsyms_lookup_name(sc->symbol); 	if (sc->addr)		sc->addr += sc->offset; 	return sc->addr;} void free_symbol_cache(struct symbol_cache *sc){	kfree(sc->symbol);	kfree(sc);} struct symbol_cache *alloc_symbol_cache(const char *sym, long offset){	struct symbol_cache *sc; 	if (!sym || strlen(sym) == 0)		return NULL; 	sc = kzalloc(sizeof(struct symbol_cache), GFP_KERNEL);	if (!sc)		return NULL; 	sc->symbol = kstrdup(sym, GFP_KERNEL);	if (!sc->symbol) {		kfree(sc);		return NULL;	}	sc->offset = offset;	update_symbol_cache(sc); 	return sc;} /* * Kprobes-specific fetch functions */#define DEFINE_FETCH_stack(type)					\static void FETCH_FUNC_NAME(stack, type)(struct pt_regs *regs,		\					  void *offset, void *dest)	\{									\	*(type *)dest = (type)regs_get_kernel_stack_nth(regs,		\				(unsigned int)((unsigned long)offset));	\}									\NOKPROBE_SYMBOL(FETCH_FUNC_NAME(stack, type)); DEFINE_BASIC_FETCH_FUNCS(stack)/* No string on the stack entry */#define fetch_stack_string	NULL#define fetch_stack_string_size	NULL #define DEFINE_FETCH_memory(type)					\static void FETCH_FUNC_NAME(memory, type)(struct pt_regs *regs,		\					  void *addr, void *dest)	\{									\	type retval;							\	if (probe_kernel_address(addr, retval))				\		*(type *)dest = 0;					\	else								\		*(type *)dest = retval;					\}									\NOKPROBE_SYMBOL(FETCH_FUNC_NAME(memory, type)); DEFINE_BASIC_FETCH_FUNCS(memory)/* * Fetch a null-terminated string. Caller MUST set *(u32 *)dest with max * length and relative data location. */static void FETCH_FUNC_NAME(memory, string)(struct pt_regs *regs,					    void *addr, void *dest){	long ret;	int maxlen = get_rloc_len(*(u32 *)dest);	u8 *dst = get_rloc_data(dest);	u8 *src = addr;	mm_segment_t old_fs = get_fs(); 	if (!maxlen)		return; 	/*	 * Try to get string again, since the string can be changed while	 * probing.	 */	set_fs(KERNEL_DS);	pagefault_disable(); 	do		ret = __copy_from_user_inatomic(dst++, src++, 1);	while (dst[-1] && ret == 0 && src - (u8 *)addr < maxlen); 	dst[-1] = '\0';	pagefault_enable();	set_fs(old_fs); 	if (ret < 0) {	/* Failed to fetch string */		((u8 *)get_rloc_data(dest))[0] = '\0';		*(u32 *)dest = make_data_rloc(0, get_rloc_offs(*(u32 *)dest));	} else {		*(u32 *)dest = make_data_rloc(src - (u8 *)addr,					      get_rloc_offs(*(u32 *)dest));	}}NOKPROBE_SYMBOL(FETCH_FUNC_NAME(memory, string)); /* Return the length of string -- including null terminal byte */static void FETCH_FUNC_NAME(memory, string_size)(struct pt_regs *regs,						 void *addr, void *dest){	mm_segment_t old_fs;	int ret, len = 0;	u8 c; 	old_fs = get_fs();	set_fs(KERNEL_DS);	pagefault_disable(); 	do {		ret = __copy_from_user_inatomic(&c, (u8 *)addr + len, 1);		len++;	} while (c && ret == 0 && len < MAX_STRING_SIZE); 	pagefault_enable();	set_fs(old_fs); 	if (ret < 0)	/* Failed to check the length */		*(u32 *)dest = 0;	else		*(u32 *)dest = len;}NOKPROBE_SYMBOL(FETCH_FUNC_NAME(memory, string_size)); #define DEFINE_FETCH_symbol(type)					\void FETCH_FUNC_NAME(symbol, type)(struct pt_regs *regs, void *data, void *dest)\{									\	struct symbol_cache *sc = data;					\	if (sc->addr)							\		fetch_memory_##type(regs, (void *)sc->addr, dest);	\	else								\		*(type *)dest = 0;					\}									\NOKPROBE_SYMBOL(FETCH_FUNC_NAME(symbol, type)); DEFINE_BASIC_FETCH_FUNCS(symbol)DEFINE_FETCH_symbol(string)DEFINE_FETCH_symbol(string_size) /* kprobes don't support file_offset fetch methods */#define fetch_file_offset_u8		NULL#define fetch_file_offset_u16		NULL#define fetch_file_offset_u32		NULL#define fetch_file_offset_u64		NULL#define fetch_file_offset_string	NULL#define fetch_file_offset_string_size	NULL /* Fetch type information table */const struct fetch_type kprobes_fetch_type_table[] = {	/* Special types */	[FETCH_TYPE_STRING] = __ASSIGN_FETCH_TYPE("string", string, string,					sizeof(u32), 1, "__data_loc char[]"),	[FETCH_TYPE_STRSIZE] = __ASSIGN_FETCH_TYPE("string_size", u32,					string_size, sizeof(u32), 0, "u32"),	/* Basic types */	ASSIGN_FETCH_TYPE(u8,  u8,  0),	ASSIGN_FETCH_TYPE(u16, u16, 0),	ASSIGN_FETCH_TYPE(u32, u32, 0),	ASSIGN_FETCH_TYPE(u64, u64, 0),	ASSIGN_FETCH_TYPE(s8,  u8,  1),	ASSIGN_FETCH_TYPE(s16, u16, 1),	ASSIGN_FETCH_TYPE(s32, u32, 1),	ASSIGN_FETCH_TYPE(s64, u64, 1), 	ASSIGN_FETCH_TYPE_END}; /* * Allocate new trace_probe and initialize it (including kprobes). */static struct trace_kprobe *alloc_trace_kprobe(const char *group,					     const char *event,					     void *addr,					     const char *symbol,					     unsigned long offs,					     int nargs, bool is_return){	struct trace_kprobe *tk;	int ret = -ENOMEM; 	tk = kzalloc(SIZEOF_TRACE_KPROBE(nargs), GFP_KERNEL);	if (!tk)		return ERR_PTR(ret); 	if (symbol) {		tk->symbol = kstrdup(symbol, GFP_KERNEL);		if (!tk->symbol)			goto error;		tk->rp.kp.symbol_name = tk->symbol;		tk->rp.kp.offset = offs;	} else		tk->rp.kp.addr = addr; 	if (is_return)		tk->rp.handler = kretprobe_dispatcher;	else		tk->rp.kp.pre_handler = kprobe_dispatcher; 	if (!event || !is_good_name(event)) {		ret = -EINVAL;		goto error;	} 	tk->tp.call.class = &tk->tp.class;	tk->tp.call.name = kstrdup(event, GFP_KERNEL);	if (!tk->tp.call.name)		goto error; 	if (!group || !is_good_name(group)) {		ret = -EINVAL;		goto error;	} 	tk->tp.class.system = kstrdup(group, GFP_KERNEL);	if (!tk->tp.class.system)		goto error; 	INIT_LIST_HEAD(&tk->list);	INIT_LIST_HEAD(&tk->tp.files);	return tk;error:	kfree(tk->tp.call.name);	kfree(tk->symbol);	kfree(tk);	return ERR_PTR(ret);} static void free_trace_kprobe(struct trace_kprobe *tk){	int i; 	for (i = 0; i < tk->tp.nr_args; i++)		traceprobe_free_probe_arg(&tk->tp.args[i]); 	kfree(tk->tp.call.class->system);	kfree(tk->tp.call.name);	kfree(tk->symbol);	kfree(tk);} static struct trace_kprobe *find_trace_kprobe(const char *event,					      const char *group){	struct trace_kprobe *tk; 	list_for_each_entry(tk, &probe_list, list)		if (strcmp(ftrace_event_name(&tk->tp.call), event) == 0 &&		    strcmp(tk->tp.call.class->system, group) == 0)			return tk;	return NULL;} /* * Enable trace_probe * if the file is NULL, enable "perf" handler, or enable "trace" handler. */static intenable_trace_kprobe(struct trace_kprobe *tk, struct ftrace_event_file *file){	int ret = 0; 	if (file) {		struct event_file_link *link; 		link = kmalloc(sizeof(*link), GFP_KERNEL);		if (!link) {			ret = -ENOMEM;			goto out;		} 		link->file = file;		list_add_tail_rcu(&link->list, &tk->tp.files); 		tk->tp.flags |= TP_FLAG_TRACE;	} else		tk->tp.flags |= TP_FLAG_PROFILE; 	if (trace_probe_is_registered(&tk->tp) && !trace_kprobe_has_gone(tk)) {		if (trace_kprobe_is_return(tk))			ret = enable_kretprobe(&tk->rp);		else			ret = enable_kprobe(&tk->rp.kp);	} out:	return ret;} /* * Disable trace_probe * if the file is NULL, disable "perf" handler, or disable "trace" handler. */static intdisable_trace_kprobe(struct trace_kprobe *tk, struct ftrace_event_file *file){	struct event_file_link *link = NULL;	int wait = 0;	int ret = 0; 	if (file) {		link = find_event_file_link(&tk->tp, file);		if (!link) {			ret = -EINVAL;			goto out;		} 		list_del_rcu(&link->list);		wait = 1;		if (!list_empty(&tk->tp.files))			goto out; 		tk->tp.flags &= ~TP_FLAG_TRACE;	} else		tk->tp.flags &= ~TP_FLAG_PROFILE; 	if (!trace_probe_is_enabled(&tk->tp) && trace_probe_is_registered(&tk->tp)) {		if (trace_kprobe_is_return(tk))			disable_kretprobe(&tk->rp);		else			disable_kprobe(&tk->rp.kp);		wait = 1;	} out:	if (wait) {		/*		 * Synchronize with kprobe_trace_func/kretprobe_trace_func		 * to ensure disabled (all running handlers are finished).		 * This is not only for kfree(), but also the caller,		 * trace_remove_event_call() supposes it for releasing		 * event_call related objects, which will be accessed in		 * the kprobe_trace_func/kretprobe_trace_func.		 */		synchronize_sched();		kfree(link);	/* Ignored if link == NULL */	} 	return ret;} /* Internal register function - just handle k*probes and flags */static int __register_trace_kprobe(struct trace_kprobe *tk){	int i, ret; 	if (trace_probe_is_registered(&tk->tp))		return -EINVAL; 	for (i = 0; i < tk->tp.nr_args; i++)		traceprobe_update_arg(&tk->tp.args[i]); 	/* Set/clear disabled flag according to tp->flag */	if (trace_probe_is_enabled(&tk->tp))		tk->rp.kp.flags &= ~KPROBE_FLAG_DISABLED;	else		tk->rp.kp.flags |= KPROBE_FLAG_DISABLED; 	if (trace_kprobe_is_return(tk))		ret = register_kretprobe(&tk->rp);	else		ret = register_kprobe(&tk->rp.kp); 	if (ret == 0)		tk->tp.flags |= TP_FLAG_REGISTERED;	else {		pr_warning("Could not insert probe at %s+%lu: %d\n",			   trace_kprobe_symbol(tk), trace_kprobe_offset(tk), ret);		if (ret == -ENOENT && trace_kprobe_is_on_module(tk)) {			pr_warning("This probe might be able to register after"				   "target module is loaded. Continue.\n");			ret = 0;		} else if (ret == -EILSEQ) {			pr_warning("Probing address(0x%p) is not an "				   "instruction boundary.\n",				   tk->rp.kp.addr);			ret = -EINVAL;		}	} 	return ret;} /* Internal unregister function - just handle k*probes and flags */static void __unregister_trace_kprobe(struct trace_kprobe *tk){	if (trace_probe_is_registered(&tk->tp)) {		if (trace_kprobe_is_return(tk))			unregister_kretprobe(&tk->rp);		else			unregister_kprobe(&tk->rp.kp);		tk->tp.flags &= ~TP_FLAG_REGISTERED;		/* Cleanup kprobe for reuse */		if (tk->rp.kp.symbol_name)			tk->rp.kp.addr = NULL;	}} /* Unregister a trace_probe and probe_event: call with locking probe_lock */

Lines 1–500 of 1486.