1488 lines · 34.6 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 <linux/kprobes.h>#include <linux/seq_file.h>#include <linux/slab.h>#include <linux/smp.h>#include <linux/debugfs.h>#include <linux/types.h>#include <linux/string.h>#include <linux/ctype.h>#include <linux/ptrace.h>#include <linux/perf_event.h> #include "trace.h"#include "trace_output.h" #define MAX_TRACE_ARGS 128#define MAX_ARGSTR_LEN 63#define MAX_EVENT_NAME_LEN 64#define KPROBE_EVENT_SYSTEM "kprobes" /* Reserved field names */#define FIELD_STRING_IP "__probe_ip"#define FIELD_STRING_NARGS "__probe_nargs"#define FIELD_STRING_RETIP "__probe_ret_ip"#define FIELD_STRING_FUNC "__probe_func" const char *reserved_field_names[] = {	"common_type",	"common_flags",	"common_preempt_count",	"common_pid",	"common_tgid",	"common_lock_depth",	FIELD_STRING_IP,	FIELD_STRING_NARGS,	FIELD_STRING_RETIP,	FIELD_STRING_FUNC,}; struct fetch_func {	unsigned long (*func)(struct pt_regs *, void *);	void *data;}; static __kprobes unsigned long call_fetch(struct fetch_func *f,					  struct pt_regs *regs){	return f->func(regs, f->data);} /* fetch handlers */static __kprobes unsigned long fetch_register(struct pt_regs *regs,					      void *offset){	return regs_get_register(regs, (unsigned int)((unsigned long)offset));} static __kprobes unsigned long fetch_stack(struct pt_regs *regs,					   void *num){	return regs_get_kernel_stack_nth(regs,					 (unsigned int)((unsigned long)num));} static __kprobes unsigned long fetch_memory(struct pt_regs *regs, void *addr){	unsigned long retval; 	if (probe_kernel_address(addr, retval))		return 0;	return retval;} static __kprobes unsigned long fetch_retvalue(struct pt_regs *regs,					      void *dummy){	return regs_return_value(regs);} static __kprobes unsigned long fetch_stack_address(struct pt_regs *regs,						   void *dummy){	return kernel_stack_pointer(regs);} /* Memory fetching by symbol */struct symbol_cache {	char *symbol;	long offset;	unsigned long addr;}; static 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;} static void free_symbol_cache(struct symbol_cache *sc){	kfree(sc->symbol);	kfree(sc);} static 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;} static __kprobes unsigned long fetch_symbol(struct pt_regs *regs, void *data){	struct symbol_cache *sc = data; 	if (sc->addr)		return fetch_memory(regs, (void *)sc->addr);	else		return 0;} /* Special indirect memory access interface */struct indirect_fetch_data {	struct fetch_func orig;	long offset;}; static __kprobes unsigned long fetch_indirect(struct pt_regs *regs, void *data){	struct indirect_fetch_data *ind = data;	unsigned long addr; 	addr = call_fetch(&ind->orig, regs);	if (addr) {		addr += ind->offset;		return fetch_memory(regs, (void *)addr);	} else		return 0;} static __kprobes void free_indirect_fetch_data(struct indirect_fetch_data *data){	if (data->orig.func == fetch_indirect)		free_indirect_fetch_data(data->orig.data);	else if (data->orig.func == fetch_symbol)		free_symbol_cache(data->orig.data);	kfree(data);} /** * Kprobe event core functions */ struct probe_arg {	struct fetch_func	fetch;	const char		*name;}; /* Flags for trace_probe */#define TP_FLAG_TRACE	1#define TP_FLAG_PROFILE	2 struct trace_probe {	struct list_head	list;	struct kretprobe	rp;	/* Use rp.kp for kprobe use */	unsigned long 		nhit;	unsigned int		flags;	/* For TP_FLAG_* */	const char		*symbol;	/* symbol name */	struct ftrace_event_call	call;	struct trace_event		event;	unsigned int		nr_args;	struct probe_arg	args[];}; #define SIZEOF_TRACE_PROBE(n)			\	(offsetof(struct trace_probe, args) +	\	(sizeof(struct probe_arg) * (n))) static __kprobes int probe_is_return(struct trace_probe *tp){	return tp->rp.handler != NULL;} static __kprobes const char *probe_symbol(struct trace_probe *tp){	return tp->symbol ? tp->symbol : "unknown";} static int probe_arg_string(char *buf, size_t n, struct fetch_func *ff){	int ret = -EINVAL; 	if (ff->func == fetch_register) {		const char *name;		name = regs_query_register_name((unsigned int)((long)ff->data));		ret = snprintf(buf, n, "%%%s", name);	} else if (ff->func == fetch_stack)		ret = snprintf(buf, n, "$stack%lu", (unsigned long)ff->data);	else if (ff->func == fetch_memory)		ret = snprintf(buf, n, "@0x%p", ff->data);	else if (ff->func == fetch_symbol) {		struct symbol_cache *sc = ff->data;		if (sc->offset)			ret = snprintf(buf, n, "@%s%+ld", sc->symbol,					sc->offset);		else			ret = snprintf(buf, n, "@%s", sc->symbol);	} else if (ff->func == fetch_retvalue)		ret = snprintf(buf, n, "$retval");	else if (ff->func == fetch_stack_address)		ret = snprintf(buf, n, "$stack");	else if (ff->func == fetch_indirect) {		struct indirect_fetch_data *id = ff->data;		size_t l = 0;		ret = snprintf(buf, n, "%+ld(", id->offset);		if (ret >= n)			goto end;		l += ret;		ret = probe_arg_string(buf + l, n - l, &id->orig);		if (ret < 0)			goto end;		l += ret;		ret = snprintf(buf + l, n - l, ")");		ret += l;	}end:	if (ret >= n)		return -ENOSPC;	return ret;} static int register_probe_event(struct trace_probe *tp);static void unregister_probe_event(struct trace_probe *tp); 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); /* Check the name is good for event/group */static int check_event_name(const char *name){	if (!isalpha(*name) && *name != '_')		return 0;	while (*++name != '\0') {		if (!isalpha(*name) && !isdigit(*name) && *name != '_')			return 0;	}	return 1;} /* * Allocate new trace_probe and initialize it (including kprobes). */static struct trace_probe *alloc_trace_probe(const char *group,					     const char *event,					     void *addr,					     const char *symbol,					     unsigned long offs,					     int nargs, int is_return){	struct trace_probe *tp;	int ret = -ENOMEM; 	tp = kzalloc(SIZEOF_TRACE_PROBE(nargs), GFP_KERNEL);	if (!tp)		return ERR_PTR(ret); 	if (symbol) {		tp->symbol = kstrdup(symbol, GFP_KERNEL);		if (!tp->symbol)			goto error;		tp->rp.kp.symbol_name = tp->symbol;		tp->rp.kp.offset = offs;	} else		tp->rp.kp.addr = addr; 	if (is_return)		tp->rp.handler = kretprobe_dispatcher;	else		tp->rp.kp.pre_handler = kprobe_dispatcher; 	if (!event || !check_event_name(event)) {		ret = -EINVAL;		goto error;	} 	tp->call.name = kstrdup(event, GFP_KERNEL);	if (!tp->call.name)		goto error; 	if (!group || !check_event_name(group)) {		ret = -EINVAL;		goto error;	} 	tp->call.system = kstrdup(group, GFP_KERNEL);	if (!tp->call.system)		goto error; 	INIT_LIST_HEAD(&tp->list);	return tp;error:	kfree(tp->call.name);	kfree(tp->symbol);	kfree(tp);	return ERR_PTR(ret);} static void free_probe_arg(struct probe_arg *arg){	if (arg->fetch.func == fetch_symbol)		free_symbol_cache(arg->fetch.data);	else if (arg->fetch.func == fetch_indirect)		free_indirect_fetch_data(arg->fetch.data);	kfree(arg->name);} static void free_trace_probe(struct trace_probe *tp){	int i; 	for (i = 0; i < tp->nr_args; i++)		free_probe_arg(&tp->args[i]); 	kfree(tp->call.system);	kfree(tp->call.name);	kfree(tp->symbol);	kfree(tp);} static struct trace_probe *find_probe_event(const char *event,					    const char *group){	struct trace_probe *tp; 	list_for_each_entry(tp, &probe_list, list)		if (strcmp(tp->call.name, event) == 0 &&		    strcmp(tp->call.system, group) == 0)			return tp;	return NULL;} /* Unregister a trace_probe and probe_event: call with locking probe_lock */static void unregister_trace_probe(struct trace_probe *tp){	if (probe_is_return(tp))		unregister_kretprobe(&tp->rp);	else		unregister_kprobe(&tp->rp.kp);	list_del(&tp->list);	unregister_probe_event(tp);} /* Register a trace_probe and probe_event */static int register_trace_probe(struct trace_probe *tp){	struct trace_probe *old_tp;	int ret; 	mutex_lock(&probe_lock); 	/* register as an event */	old_tp = find_probe_event(tp->call.name, tp->call.system);	if (old_tp) {		/* delete old event */		unregister_trace_probe(old_tp);		free_trace_probe(old_tp);	}	ret = register_probe_event(tp);	if (ret) {		pr_warning("Faild to register probe event(%d)\n", ret);		goto end;	} 	tp->rp.kp.flags |= KPROBE_FLAG_DISABLED;	if (probe_is_return(tp))		ret = register_kretprobe(&tp->rp);	else		ret = register_kprobe(&tp->rp.kp); 	if (ret) {		pr_warning("Could not insert probe(%d)\n", ret);		if (ret == -EILSEQ) {			pr_warning("Probing address(0x%p) is not an "				   "instruction boundary.\n",				   tp->rp.kp.addr);			ret = -EINVAL;		}		unregister_probe_event(tp);	} else		list_add_tail(&tp->list, &probe_list);end:	mutex_unlock(&probe_lock);	return ret;} /* Split symbol and offset. */static int split_symbol_offset(char *symbol, unsigned long *offset){	char *tmp;	int ret; 	if (!offset)		return -EINVAL; 	tmp = strchr(symbol, '+');	if (tmp) {		/* skip sign because strict_strtol doesn't accept '+' */		ret = strict_strtoul(tmp + 1, 0, offset);		if (ret)			return ret;		*tmp = '\0';	} else		*offset = 0;	return 0;} #define PARAM_MAX_ARGS 16#define PARAM_MAX_STACK (THREAD_SIZE / sizeof(unsigned long)) static int parse_probe_vars(char *arg, struct fetch_func *ff, int is_return){	int ret = 0;	unsigned long param; 	if (strcmp(arg, "retval") == 0) {		if (is_return) {			ff->func = fetch_retvalue;			ff->data = NULL;		} else			ret = -EINVAL;	} else if (strncmp(arg, "stack", 5) == 0) {		if (arg[5] == '\0') {			ff->func = fetch_stack_address;			ff->data = NULL;		} else if (isdigit(arg[5])) {			ret = strict_strtoul(arg + 5, 10, &param);			if (ret || param > PARAM_MAX_STACK)				ret = -EINVAL;			else {				ff->func = fetch_stack;				ff->data = (void *)param;			}		} else			ret = -EINVAL;	} else		ret = -EINVAL;	return ret;} /* Recursive argument parser */static int __parse_probe_arg(char *arg, struct fetch_func *ff, int is_return){	int ret = 0;	unsigned long param;	long offset;	char *tmp; 	switch (arg[0]) {	case '$':		ret = parse_probe_vars(arg + 1, ff, is_return);

Lines 1–500 of 1488.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t