1. 程式人生 > 其它 >Linux 宕機驅動

Linux 宕機驅動

技術標籤:驅動linux核心

原理:通過建立proc虛擬檔案來與核心互動,互動中通過訪問空指標,只讀記憶體,重複釋放記憶體,死鎖等方式來達到系統宕機或卡死的狀態。

一般用來測試系統硬狗。

#include <linux/module.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>

#include <linux/sysctl.h>
#include <linux/list.h>
#include <linux/highmem.h>
#include <asm/uaccess.h>
#include <linux/vmalloc.h>
#include <linux/netdevice.h>
#include <asm/cacheflush.h>
#include <linux/spinlock_types.h>


#define DEAD_MAGIC		(0xadde78563412addeUL)
#define CMD_PARAM_LEN	(64)
#define MEM_UNIT		(1024 * 1024 * 10)
#define MAX_BLOCK_CNT	(3200)

//#define __RAW_SPIN_LOCK_UNLOCKED { }
#define SPIN_DEP_MAP_INIT(lockname)
#define __RAW_RW_LOCK_UNLOCKED		{ 0 }
#define RW_DEP_MAP_INIT(lockname)
static void *s_pointer[MAX_BLOCK_CNT] = {NULL};
static int s_index = 0;

typedef void (*crash_func_t)(unsigned long data);
struct cmd_pair_st
{
	const char *cmd_name;
	crash_func_t func;
	const char *cmd_desc;
	int is_need_cpuid;
};

struct cmd_param_st
{
	char param1[CMD_PARAM_LEN];
	char param2[CMD_PARAM_LEN];
};

/*
#ifdef CONFIG_DEBUG_SPINLOCK
#define __SPIN_LOCK_UNLOCKED(lockname)					\
	(spinlock_t)	{	.raw_lock = __RAW_SPIN_LOCK_UNLOCKED,	\
				.magic = SPINLOCK_MAGIC,		\
				.owner = SPINLOCK_OWNER_INIT,		\
				.owner_cpu = -1,			\
				SPIN_DEP_MAP_INIT(lockname) }
#define __RW_LOCK_UNLOCKED(lockname)					\
	(rwlock_t)	{	.raw_lock = __RAW_RW_LOCK_UNLOCKED,	\
				.magic = RWLOCK_MAGIC,			\
				.owner = SPINLOCK_OWNER_INIT,		\
				.owner_cpu = -1,			\
				RW_DEP_MAP_INIT(lockname) }
#else

#define __SPIN_LOCK_UNLOCKED(lockname) \
	(spinlock_t)	{	.raw_lock = __RAW_SPIN_LOCK_UNLOCKED,	\
				SPIN_DEP_MAP_INIT(lockname) }
#define __RW_LOCK_UNLOCKED(lockname) \
	(rwlock_t)	{	.raw_lock = __RAW_RW_LOCK_UNLOCKED,	\
				RW_DEP_MAP_INIT(lockname) }
#endif
*/

//#define SPIN_LOCK_UNLOCKED	__SPIN_LOCK_UNLOCKED(old_style_spin_init)
#define DEFINE_SPINLOCK(x)	spinlock_t x = __SPIN_LOCK_UNLOCKED(x)

#define dbg(text, par...) \
	do { \
		printk(KERN_EMERG " [%s:%d] "text, __FILE__, __LINE__, ##par); \
	} while(0)


static void null_ptr_func(unsigned long data)
{
	*(unsigned long*)0 = DEAD_MAGIC;
}

static struct timer_list null_ptr_timer;
static void null_ptr_timer_func(unsigned long data)
{
    dbg("null_ptr_timer_func in\n");
	*(unsigned long*)0 = DEAD_MAGIC;
}

static void timer_null_ptr_func(unsigned long data)
{
    setup_timer(&null_ptr_timer, null_ptr_timer_func, 0);
    null_ptr_timer.expires = jiffies + HZ;
    add_timer(&null_ptr_timer);
    dbg("null_ptr timer started\n");
}

static void write_readonly_func(unsigned long data)
{
	void *addr = (void*)__get_free_page(GFP_KERNEL);
	if (!addr)
	{
		dbg("vmalloc failed\n");
		return;
	}
	dbg("page addr:%p\n", addr);
	free_page((unsigned long)addr);
	
	/*需要開啟核心空閒頁只讀檢測功能*/
	*(unsigned long*)addr = DEAD_MAGIC;
}

static void recursive_overflow(int level, void *data)
{
	unsigned long local_data = DEAD_MAGIC;
    dbg("recursive depth=%d\n", level);	
	if (level > 1024)
		return;
		
	recursive_overflow(level + 1, &local_data);
	*(unsigned long*)data = local_data;
}


static void stack_overflow_func(unsigned long data)
{
	unsigned long local_data;
	recursive_overflow(0, &local_data);
}

static void read_overflow_func(unsigned long data)
{
	dbg("do nothing!\n");
}

static void double_kfree_func(unsigned long data)
{
	void *addr = kmalloc(PAGE_SIZE, GFP_KERNEL);
	if (!addr)
	{
		dbg("kmalloc failed\n");
		return;
	}
	dbg("addr=%p\n", addr);
	kfree(addr);
	kfree(addr);
}

static void double_sfree_func(unsigned long data)
{
	void *addr;
	struct kmem_cache *test_cache;
	
	test_cache = kmem_cache_create("double_sfree_test", 128, 0, SLAB_HWCACHE_ALIGN, NULL);
	if (!test_cache)
	{
		dbg("kmem_cache_create failed\n");
		return;
	}
	addr = kmem_cache_alloc(test_cache, GFP_KERNEL);
	if (!addr)
	{
		dbg("kmem_cache_alloc failed\n");
		return;
	}
	dbg("addr=%p\n", addr);
	kmem_cache_free(test_cache, addr);
	kmem_cache_free(test_cache, addr);
	kmem_cache_destroy(test_cache);
}

static void double_vfree_func(unsigned long data)
{
	void *addr = vmalloc(PAGE_SIZE);
	if (!addr)
	{
		dbg("vmalloc failed\n");
		return;
	}
	dbg("addr=%p\n", addr);
	vfree(addr);
	vfree(addr);
}

static void double_pfree_func(unsigned long data)
{
	void *addr = (void*)__get_free_page(GFP_KERNEL);
	if (!addr)
	{
		dbg("vmalloc failed\n");
		return;
	}
	dbg("addr=%p\n", addr);
	free_page((unsigned long)addr);
	free_page((unsigned long)addr);
}

static DEFINE_SPINLOCK(sleeplock);
static void lock_sleep_func(unsigned long data)
{
    dbg("spin lock");
	spin_lock(&sleeplock);
    msleep(HZ);
    spin_unlock(&sleeplock);
    dbg("spin unlock");
}

static void bh_sleep_func(unsigned long data)
{
    dbg("local_bh_disable");
	local_bh_disable();
    msleep(HZ);
    local_bh_enable();
    dbg("local_bh_enable");
}

static struct timer_list sleep_timer;
static void sleep_timer_func(unsigned long data)
{
    dbg("sleep_timer_func in\n");
    msleep(HZ);
}

static void timer_sleep_func(unsigned long data)
{
    setup_timer(&sleep_timer, sleep_timer_func, 0);
    sleep_timer.expires = jiffies + HZ;
    add_timer(&sleep_timer);
    dbg("sleep timer started\n");
}

static void irq_sleep_func(unsigned long data)
{
    dbg("local_irq_disable");
	local_irq_disable();
    msleep(HZ);
    local_irq_enable();
    dbg("local_irq_enable");
}

static DEFINE_SPINLOCK(deadlock);
static void kernel_deadlock_func(unsigned long data)
{
    dbg("spin_lock");
	spin_lock(&deadlock);
	dbg("spin_lock again");
    spin_lock(&deadlock);
}

static void bh_deadlock_func(unsigned long data)
{
    dbg("spin_lock_bh");
	spin_lock_bh(&deadlock);
	dbg("spin_lock_bh again");
    spin_lock_bh(&deadlock); 
}

static struct timer_list dead_timer;
static void dead_timer_func(unsigned long data)
{
    dbg("dead_timer_func in\n");
	spin_lock(&deadlock);
    spin_lock(&deadlock);
}

static void timer_deadlock_func(unsigned long data)
{
    setup_timer(&dead_timer, dead_timer_func, 0);
    dead_timer.expires = jiffies + HZ;
    add_timer(&dead_timer);
    dbg("dead timer started\n");
}

static void irq_deadlock_func(unsigned long data)
{
    dbg("spin_lock_irq\n");
	spin_lock_irq(&deadlock);
	dbg("spin_lock_irq again");
    spin_lock_irq(&deadlock); 
}

static void bh_kmalloc_func(unsigned long data)
{
	void *mem;
	local_bh_disable();
    mem = kmalloc(PAGE_SIZE, GFP_KERNEL);
	if (mem)
		kfree(mem);
	else
		printk("kmalloc failed\n");
    local_bh_enable();
}

static void bh_kmem_func(unsigned long data)
{
	void *mem;
	struct kmem_cache *test_cache;
	
	test_cache = kmem_cache_create("test_cache", 100, 0, 0, NULL);
	if (!test_cache)
	{
		printk("kmem_cache_create failed\n");
		return;
	}
	local_bh_disable();
    mem = kmem_cache_alloc(test_cache, GFP_KERNEL);
	if (mem)
		kmem_cache_free(test_cache, mem);
	else
		printk("kmem_cache_alloc failed\n");
    local_bh_enable();
	kmem_cache_destroy(test_cache);
}

static void eat_mem_func(unsigned long data)
{
	int i = 0, count = 0;
	struct cmd_param_st *param = (struct cmd_param_st *)data;
	if (param && param->param1[0] != '0')
		count = simple_strtol(param->param1, NULL, 0);
	for (; i < count && i < MAX_BLOCK_CNT; i++) {
		if (s_pointer[s_index] == NULL)
			s_pointer[s_index++] = vmalloc(MEM_UNIT);
	}
}

static void free_mem_func(unsigned long data)
{
	int i = 0;
	for (; i < MAX_BLOCK_CNT; i++) {
		if (s_pointer[i] != NULL) {
			vfree(s_pointer[i]);
			s_pointer[i] = NULL;
		}
	}
}

static void bh_vmalloc_func(unsigned long data)
{
	void *mem;
	dbg("local_bh_disable");
	local_bh_disable();
    mem = vmalloc(PAGE_SIZE);
    dbg("mem=%p", mem);
	if (mem)
		vfree(mem);
	else
		dbg("vmalloc failed\n");
		
    dbg("local_bh_enable");
    local_bh_enable();
}


struct cmd_pair_st cmd_suit[] = 
{
	{"null_ptr", null_ptr_func, "[need cpu id]", 1},
    {"timer_null_ptr", timer_null_ptr_func, "[need cpu id]", 1},
	{"write_readonly", write_readonly_func, "[need cpu id]", 1},
	{"stack_overflow", stack_overflow_func, "[need cpu id]", 1},
	{"read_overflow", read_overflow_func, "[need cpu id]", 1},
	{"double_kfree", double_kfree_func, "[need cpu id]", 1},
	{"double_sfree", double_sfree_func, "[need cpu id]", 1},
	{"double_vfree", double_vfree_func, "[need cpu id]", 1},
	{"double_pfree", double_pfree_func, "[need cpu id]", 1},
	{"lock_sleep", lock_sleep_func, "[need cpu id]", 1},
	{"bh_sleep", bh_sleep_func, "[need cpu id]", 1},
	{"timer_sleep", timer_sleep_func, "[need cpu id]", 1},
	{"irq_sleep", irq_sleep_func, "[need cpu id]", 1},
	{"kernel_deadlock", kernel_deadlock_func, "[need cpu id]", 1},
	{"bh_deadlock", bh_deadlock_func, "[need cpu id]", 1},
	{"timer_deadlock", timer_deadlock_func, "[need cpu id]", 1},
	{"irq_deadlock", irq_deadlock_func, "[need cpu id]", 1},
	{"bh_vmalloc", bh_vmalloc_func, "[need cpu id]", 1},
	{"bh_kmalloc_kernel", bh_kmalloc_func, "[need cpu id]", 1},
	{"bh_kmem_kernel", bh_kmem_func, "[need cpu id]", 1},
	{"eat_mem", eat_mem_func, "need param:count(unit:10M)", 0},
	{"free_mem", free_mem_func, "no need param", 0}
};


static ssize_t test_crash_read(struct file * file, char * buf, size_t count, loff_t *ppos)
{
	int i;
	int len = 0;
	static char tmp_buf[PAGE_SIZE];
	int mylen = sizeof(tmp_buf);
	
	if (!buf || count < 512)
		return -1;
	
	if (*ppos != 0)
		return 0;
		
	len += snprintf(tmp_buf + len, mylen - len, "cmd: echo cmd > /proc/test_crash\n");
	len += snprintf(tmp_buf + len, mylen - len, "available cmds:\n");
	
	for (i = 0; i < ARRAY_SIZE(cmd_suit); i++)
	{
		if (cmd_suit[i].cmd_desc)
		{
			len += snprintf(tmp_buf + len, mylen - len, "    echo %-20s >/proc/test_crash # %-40s\n",
				cmd_suit[i].cmd_name, cmd_suit[i].cmd_desc);
		}
		else
		{
			len += snprintf(tmp_buf + len, mylen - len, "    echo %-20s >/proc/test_crash\n", 
				cmd_suit[i].cmd_name);
		}
	}
	
	len = min(len, (int)count);
	if (copy_to_user(buf, tmp_buf, len))
	{
		dbg("test_crash_read, copy_from_user failed\n");
	}
	*ppos += len;
	return len;
}

static struct cmd_pair_st *find_cmd_pair(const char *cmd_name)
{
	int i;
	
	for (i = 0; i < ARRAY_SIZE(cmd_suit); i++)
	{
		if (strcmp(cmd_name, cmd_suit[i].cmd_name) == 0)
		{
			return &cmd_suit[i];
		}
	}

	return NULL;
}

static void bind_cpu_crash(crash_func_t func, struct cmd_param_st *param)
{
	int cpuid = 0;
	if (param->param1[0] != '0')
		cpuid = simple_strtol(param->param1, NULL, 0);
	dbg("current cpu id:%d\n", cpuid);
	setup_timer(&dead_timer, func, 0);
    dead_timer.expires = jiffies + HZ;
    add_timer_on(&dead_timer, cpuid);
    dbg("dead timer started\n");
}

static ssize_t test_crash_write(struct file * file, const char * buf, size_t count, loff_t *ppos)
{
#define CMD_BUFF_SIZE (256)
	int len;
	int ret;
	char cmd_buf[CMD_BUFF_SIZE+1];
	char cmd_name[64] = {0};
	struct cmd_pair_st *cmd;
	static struct cmd_param_st param = {{0},{0}};
	
	len = min((int)count, CMD_BUFF_SIZE);
	if (copy_from_user(cmd_buf, buf, len))
	{
		dbg("copy_from_user failed, len=%d\n", len);
		return -1;
	}
	cmd_buf[len] = 0;
	
	ret = sscanf(cmd_buf, "%63s %63s", cmd_name, param.param1);
	if (ret != 2)
	{
		dbg("now cmd: %s\n", cmd_buf);
	}
	
	cmd = find_cmd_pair(cmd_name);
	if (cmd && cmd->func)
	{
	    dbg("start call crash. name:%s, param:%s\n", cmd->cmd_name, cmd_buf);
		if (ret == 1 || cmd->is_need_cpuid == 0)
		{    
		    cmd->func((unsigned long)&param);
		}
		else
		{
			bind_cpu_crash(cmd->func, &param);
		}
	}
	
	return count;
}


static struct file_operations test_crash_ops =
{
	.read = test_crash_read,
	.write = test_crash_write,
};


static int __init test_crash_init(void)
{
	struct proc_dir_entry* file;
 
	//建立proc檔案並關聯file_operations
	file = proc_create("test_crash", S_IRUGO|S_IWUGO/*0644*/, NULL , &test_crash_ops);
	if (!file)
	    return -ENOMEM;
    dbg("test_crash_init ok\n");
    dbg("use \'cat /proc/test_crash' to show usage!\n");
	return 0;
}
 
static void __exit test_crash_fini(void)
{
	remove_proc_entry("test_crash", NULL);
	dbg("test_crash_fini ok\n");
}
 
module_init(test_crash_init);
module_exit(test_crash_fini);
 
MODULE_AUTHOR("flz");
MODULE_LICENSE("GPL");

Makefile:

TARGET                  = test_crash.ko
KERNEL_SRC_DIR=/home/flz/openwrt/build_dir/target-x86_64_musl/linux-x86_64/linux-4.14.180
TOOLCHAIN=x86_64-openwrt-linux-musl-
STAGING_DIR=/home/flz/openwrt/staging_dir/target-x86_64_musl

obj-m += test_crash.o

all:
        make -C $(KERNEL_SRC_DIR) M=$(PWD) ARCH=x86 CROSS_COMPILE=$(TOOLCHAIN) modules
clean:
        rm -f *.o Module.symvers modules.order *.mod*
distclean:clean
        rm *.ko

通過echo 0 > /sys/proc/kernel/panic 來關閉系統panic

使用方法:

裝載驅動:insmod test_crash.ko

檢視可用的宕機方法:

cat /prce/test_crash

例如:

echo null_ptr 0 > /proc/test_crash

echo lock_sleep 1 > /proc/test_crash

上上面的0,1指的是cpu id