/*
* Noisy Interface for Linux
*
* This driver allows arbitrary printk's to be inserted into
* executing kernel code by using the new kprobes interface.
* A message is attached to an address, and when that address
* is reached, the message is printed.
*
* This uses a sysfs control file to manage a list of probes.
* The sysfs directory is at
*
* /sys/noisy/
*
* and the control is named 'ctl'.
*
* A Noisy Probe can be added by echoing into the file, like:
*
* $ echo "add
" > /sys/noisy/ctl
*
* where is the address to break on, and
* is the message to print when the address is reached.
*
* Probes can be removed by doing:
*
* $ echo "del " > /sys/noisy/ctl
*
* where is the address of the probe.
*
* The probes themselves get a directory under /sys/noisy/, and
* the name of the directory is the address of the probe. Each
* probe directory contains one file ('message') that contains
* the message to be printed. (More may be added later).
*
* Copyright (C) 2002 Rusty Lynch
*/
#include
#include
#include
#include
#include
/* exported by arch/YOURARCH/kernel/traps.c */
extern int valid_kernel_address(unsigned long);
#define MAX_MSG_SIZE 128
#define to_nprobe(entry) container_of(entry,struct nprobe,kobj.entry);
static DECLARE_MUTEX(noisy_sem);
static struct subsystem noisy_subsys;
/*
* struct nrpobe: data structure for managing list of probe points
*/
struct nprobe {
struct kprobe probe;
char message[MAX_MSG_SIZE + 1];
struct kobject kobj;
};
/*
* Probe handlers.
* Only one is used (pre) to print the message out.
*/
static void noisy_pre_handler(struct kprobe *p, struct pt_regs *r)
{
struct nprobe *c = container_of(p, struct nprobe, probe);
printk(KERN_CRIT "%s: %s\n", __FUNCTION__, c->message);
}
static void noisy_post_handler(struct kprobe *p, struct pt_regs *r,
unsigned long flags)
{ }
static int noisy_fault_handler(struct kprobe *p, struct pt_regs *r, int trapnr)
{
/* Let the kernel handle this fault */
return 0;
}
/*
* struct noisy_attribute - used for defining probe attributes, with a
* typesafe show method.
*/
struct noisy_attribute {
struct attribute attr;
ssize_t (*show)(struct nprobe *,char *,size_t,loff_t);
};
/**
* noisy_attr_show - forward sysfs read call to proper handler.
* @kobj: kobject of probe being acessed.
* @attr: generic attribute portion of struct noisy_attribute.
* @page: buffer to write into.
* @count: number of bytes requested.
* @off: offset into buffer.
*
* This is called from sysfs and is necessary to convert the generic
* kobject into the right type, and to convert the attribute into the
* right attribute type.
*/
static ssize_t noisy_attr_show(struct kobject * kobj, struct attribute * attr,
char * page, size_t count, loff_t off)
{
struct nprobe * n = container_of(kobj,struct nprobe,kobj);
struct noisy_attribute * noisy_attr = container_of(attr,struct noisy_attribute,attr);
return noisy_attr->show ? noisy_attr->show(n,page,count,off) : 0;
}
/*
* noisy_sysfs_ops - sysfs operations for struct nprobes.
*/
static struct sysfs_ops noisy_sysfs_ops = {
.show = noisy_attr_show,
};
/* Default Attribute - the message to print out. */
static ssize_t noisy_message_read(struct nprobe * n, char * page, size_t count, loff_t off)
{
return off ? 0: snprintf(page,MAX_MSG_SIZE,"%s\n",n->message);
}
static struct noisy_attribute attr_message = {
.attr = { .name = "message", .mode = S_IRUGO },
.show = noisy_message_read,
};
/* Declare array of default attributes to be added when an nprobe is added */
static struct attribute * default_attrs[] = {
&attr_message.attr,
NULL,
};
/* Declare noisy_subsys for addition to sysfs */
static struct subsystem noisy_subsys = {
.kobj = { .name = "noisy" },
.default_attrs = default_attrs,
.sysfs_ops = &noisy_sysfs_ops,
};
/*
* noisy ctl attribute.
* This is declared as an attribute of the subsystem, and added in
* noisy_init().
*
* Reading this attribute dumps all the registered noisy probes.
* Writing to it either adds or deletes a noisy probe, as described at
* the beginning of the file.
*/
static ssize_t ctl_show(struct subsystem * s, char * page, size_t count, loff_t off)
{
char * str = page;
int ret = 0;
down(&noisy_sem);
if (!off) {
struct list_head * entry, * next;
list_for_each_safe(entry,next,&noisy_subsys.list) {
struct nprobe * n = to_nprobe(entry);
if ((ret + MAX_MSG_SIZE) > PAGE_SIZE)
break;
str += snprintf(str,PAGE_SIZE - ret,"%p: %s\n",
n->probe.addr,n->message);
ret = str - page;
}
}
up(&noisy_sem);
return ret;
}
static int add(unsigned long addr, char * message)
{
struct nprobe * n;
int error = 0;
if (!valid_kernel_address(addr))
return -EFAULT;
n = kmalloc(sizeof(struct nprobe),GFP_KERNEL);
if (!n)
return -ENOMEM;
memset(n,0,sizeof(struct nprobe));
n->probe.addr = (kprobe_opcode_t *)addr;
strncpy(n->message,message,MAX_MSG_SIZE);
n->probe.pre_handler = noisy_pre_handler;
n->probe.post_handler = noisy_post_handler;
n->probe.fault_handler = noisy_fault_handler;
/* Doing this manually will be unnecessary soon. */
kobject_init(&n->kobj);
n->kobj.subsys = &noisy_subsys;
snprintf(n->kobj.name, KOBJ_NAME_LEN, "%p", n->probe.addr);
if ((error = register_kprobe(&n->probe))) {
printk(KERN_ERR "Unable to register probe at %p\n",
(n->probe).addr);
goto Error;
}
if ((error = kobject_register(&n->kobj))) {
unregister_kprobe(&n->probe);
goto Error;
}
goto Done;
Error:
kfree(n);
Done:
return error;
}
static int del(unsigned long addr)
{
struct list_head * entry;
struct nprobe * n;
list_for_each(entry,&noisy_subsys.list) {
n = to_nprobe(entry);
if ((unsigned long)(n->probe.addr) == addr) {
kobject_unregister(&n->kobj);
unregister_kprobe(&n->probe);
return 0;
}
}
return -EFAULT;
}
static ssize_t ctl_store(struct subsystem * s, const char * page, size_t count, loff_t off)
{
char message[MAX_MSG_SIZE];
char ctl[16];
unsigned long addr;
int num;
int error;
int ret = 0;
down(&noisy_sem);
if (off)
goto Done;
num = sscanf(page,"%15s 0x%lx %128s",ctl,&addr,message);
if (!num) {
error = -EINVAL;
goto Done;
}
if (!strcmp(ctl,"add") && num == 3)
error = add(addr,message);
else if (!strcmp(ctl,"del") && num == 2)
error = del(addr);
else
error = -EINVAL;
ret = error ? error : count;
Done:
up(&noisy_sem);
return ret;
}
static struct subsys_attribute subsys_attr_ctl = {
.attr = { .name = "ctl", .mode = 0644 },
.show = ctl_show,
.store = ctl_store,
};
static int __init noisy_init(void)
{
subsystem_register(&noisy_subsys);
subsys_create_file(&noisy_subsys,&subsys_attr_ctl);
return 0;
}
static void __exit noisy_exit (void)
{
subsys_remove_file(&noisy_subsys,&subsys_attr_ctl);
subsystem_unregister(&noisy_subsys);
}
module_init(noisy_init);
module_exit(noisy_exit);
MODULE_AUTHOR("Rusty Lynch");
MODULE_LICENSE("GPL");