kk Blog —— 通用基础


date [-d @int|str] [+%s|"+%F %T"]
netstat -ltunp
sar -n DEV 1

linux内核文件读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// test_file.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

#include <linux/types.h>

#include <linux/fs.h>
#include <linux/string.h>
#include <asm/uaccess.h> /* get_fs(),set_fs(),get_ds() */


static int __init file_test_init(void)
{
	char *FILE_DIR = "/root/test.txt";
	char *buff = "module read/write test";
	char tmp[100];
	struct file *filp = NULL;
	mm_segment_t old_fs;
	ssize_t ret;
   
	filp = filp_open(FILE_DIR, O_RDWR | O_CREAT, 0644);
   
	if(IS_ERR(filp)) {
		printk("open error...\n");
		return -2;
	}
   
	old_fs = get_fs();
	set_fs(get_ds());

	filp->f_op->write(filp, buff, strlen(buff), &filp->f_pos);
	filp->f_op->llseek(filp, 0, 0);
	ret = filp->f_op->read(filp, tmp, strlen(buff), &filp->f_pos);

	set_fs(old_fs);
	   
	if(ret > 0)
		printk("%s\n", tmp);
	else if(ret == 0)
		printk("read nothing.............\n");
	else {
		printk("read error\n");
		return -1;
	}

	filp_close(filp, NULL);
	return 0;
}

static void __exit file_test_exit(void)
{
	printk("file test exit\n");
}

module_init(file_test_init);
module_exit(file_test_exit);

MODULE_LICENSE("GPL");
1
2
3
4
5
6
7
8
9
10
11
// Makefile

obj-m := test_file.o

KDIR := /lib/modules/$(uname -r)/build/
PWD := $(shellpwd)

all:
      make -C $(KDIR) M=$(PWD) modules
clean:
      make -C $(KDIR) M=$(PWD) clean

注意:

在调用filp->f_op->read和filp->f_op->write等对文件的操作之前,应该先设置FS。
默认情况下,filp->f_op->read或者filp->f_op->write会对传进来的参数buff进行指针检查。如果不是在用户空间会拒绝访问。因为是在内核模块中,所以buff肯定不在用户空间,所以要增大其寻址范围。

拿filp->f_op->write为例来说明:
filp->f_op->write最终会调用access_ok ==> range_ok.
而range_ok会判断访问的地址是否在0 ~ addr_limit之间。如果在,则ok,继续。如果不在,则禁止访问。而内核空间传过来的buff肯定大于addr_limit。所以要set_fs(get_ds())。
这些函数在asm/uaccess.h中定义。以下是这个头文件中的部分内容:

1
2
3
4
5
6
7
8
9
10
#define MAKE_MM_SEG(s)   ((mm_segment_t) { (s) })

#define KERNEL_DS MAKE_MM_SEG(-1UL)
#define USER_DS       MAKE_MM_SEG(PAGE_OFFSET)

#define get_ds()  (KERNEL_DS)
#define get_fs()  (current_thread_info()->addr_limit)
#define set_fs(x) (current_thread_info()->addr_limit = (x))

#define segment_eq(a, b)  ((a).seg == (b).seg)

可以看到set_fs(get_ds())改变了addr_limit的值。这样就使得从模块中传递进去的参数也可以正常使用了。

在写测试模块的时候,要实现的功能是写进去什么,然后读出来放在tmp数组中。但写完了以后filp->f_ops已经在末尾了,这个时候读是什么也 读不到的,如果想要读到数据,则应该改变filp->f-ops的值,这就要用到filp->f_op->llseek函数了。其中的参数需要记下笔记:
系统调用:
off_t sys_lseek(unsigned int fd, off_t offset, unsigned int origin)
offset是偏移量。
若origin是SEEK_SET(0),则将该文件的位移量设置为距文件开始处offset 个字节。
若origin是SEEK_CUR(1),则将该文件的位移量设置为其当前值加offset, offset可为正或负。
若origin是SEEK_END(2),则将该文件的位移量设置为文件长度加offset, offset可为正或负。

http://blog.csdn.net/yf210yf/article/details/8997007

RedHat/CentOS发行版本号及内核版本号对照表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Redhat 9.0———————————————2.4.20-8
RHEL 3 Update 8————————————2.4.21-47
RHEL 4 ————————————————2.6.9-5
RHEL 4 Update 1————————————2.6.9-11
RHEL 4 Update 2————————————2.6.9-22
RHEL 4 Update 3————————————2.6.9-34
RHEL 4 Update 4————————————2.6.9-42
RHEL 4 Update 5————————————2.6.9-55
RHEL 4 Update 6————————————2.6.9-67
RHEL 4 Update 7————————————2.6.9-78

CENTOS 5/RHEL 5 ———————————2.6.18-8
CENTOS 5.1/RHEL 5 Update 1——————2.6.18-53
CENTOS 5.2/RHEL 5 Update 2——————2.6.18-92
CENTOS 5.3/RHEL 5 Update 3——————2.6.18-128
CENTOS 5.4/RHEL 5 Update 4——————2.6.18-164
CENTOS 5.5/RHEL 5 Update 5——————2.6.18-194
CENTOS 5.6/RHEL 5 Update 6——————2.6.18-238
CENTOS 5.7/RHEL 5 Update 7——————2.6.18-274
CENTOS 5.8/RHEL 5 Update 8——————2.6.18-308

CENTOS 6.0/RHEL 6 Update 0——————2.6.32-71
CENTOS 6.1/RHEL 6 Update 1——————2.6.32-131
CENTOS 6.2/RHEL 6 Update 2——————2.6.32-220
CENTOS 6.3/RHEL 6 Update 3——————2.6.32-279
CENTOS 6.4/RHEL 6 Update 4——————2.6.32-358
CENTOS 6.5/RHEL 6 Update 5——————2.6.32-431
CENTOS 6.6/RHEL 6 Update 6——————2.6.32-504

CENTOS 7.0/RHEL 7 Update 0——————3.10.0-123

Linux内核kretprobe机制

http://www.ibm.com/developerworks/cn/linux/l-cn-systemtap1/index.html

kretprobe也使用了kprobes来实现,当用户调用register_kretprobe()时,kprobe在被探测函数的入口建立 了一个探测点,当执行到探测点时,kprobe保存了被探测函数的返回地址并取代返回地址为一个trampoline的地址,kprobe在初始化时定义 了该trampoline并且为该trampoline注册了一个kprobe,当被探测函数执行它的返回指令时,控制传递到该trampoline,因 此kprobe已经注册的对应于trampoline的处理函数将被执行,而该处理函数会调用用户关联到该kretprobe上的处理函数,处理完毕后, 设置指令寄存器指向已经备份的函数返回地址,因而原来的函数返回被正常执行。

被探测函数的返回地址保存在类型为 kretprobe_instance的变量中,结构kretprobe的maxactive字段指定了被探测函数可以被同时探测的实例数,函数 register_kretprobe()将预分配指定数量的kretprobe_instance。如果被探测函数是非递归的并且调用时已经保持了自旋 锁(spinlock),那么maxactive为1就足够了; 如果被探测函数是非递归的且运行时是抢占失效的,那么maxactive为NR_CPUS就可以了;如果maxactive被设置为小于等于0, 它被设置到缺省值(如果抢占使能, 即配置了 CONFIG_PREEMPT,缺省值为10和2*NR_CPUS中的最大值,否则缺省值为NR_CPUS)。

如果 maxactive被设置的太小了,一些探测点的执行可能被丢失,但是不影响系统的正常运行,在结构kretprobe中nmissed字段将记录被丢失 的探测点执行数,它在返回探测点被注册时设置为0,每次当执行探测函数而没有kretprobe_instance可用时,它就加1。

http://hi.baidu.com/lixiang1988/item/8884bc286c9920ceddf69acd

kretprobe的实现

相关数据结构与函数分析

1) struct kretprobe结构

该结构是kretprobe实现的基础数据结构,以下是该结构的成员:

1
2
3
4
5
6
7
struct kprobe kp; //该成员是kretprobe内嵌的struct kprobe结构。
kretprobe_handler_t handler;//该成员是调试者定义的回调函数。
int maxactive;//该成员是最多支持的返回地址实例数。
int nmissed;//该成员记录有多少次该函数返回没有被回调函数处理。
struct hlist_head free_instances;
用于链接未使用的返回地址实例,在注册时初始化。
struct hlist_head used_instances;//该成员是正在被使用的返回地址实例链表。
2) struct kretprobe_instance结构

该结构表示一个返回地址实例。因为函数每次被调用的地方不同,这造成了返回地址不同,因此需要为每一次发生的调用记录在这样一个结构里面。以下是该结构的成员:

1
2
3
4
5
struct hlist_node uflist;
该成员被链接入kretprobe的used_instances或是free_instances链表。
struct kretprobe *rp;//该成员指向所属的kretprobe结构。
kprobe_opcode_t *ret_addr;//该成员用于记录被探测函数正真的返回地址。
struct task_struct *task;//该成员记录当时运行的进程。
3) pre_handler_kretprobe()函数

该函数在kretprobe探测点被执行到后,用于修改被探测函数的返回地址。

4) trampoline_handler()函数

该函数用于执行调试者定义的回调函数以及把被探测函数的返回地址修改回原来的返回地址。

kretprobe处理流程分析

kretprobe探测方式是基于kprobe实现的又一种内核探测方式,该探测方式主要用于在函数返回时进行探测,获得内核函数的返回值,还可以用于计算函数执行时间等方面。

1) kretprobe的注册过程

调试者要进行kretprobe调试首先要注册处理,这需要在调试模块中调用register_kretprobe(),下文中称该函数为 kretprobe 注册器。kretprobe注册器对传入的kretprobe结构的中kprobe.pre_handler赋值为 pre_handler_kretprobe()函数,用于在探测点被触发时被调用。接着,kretprobe注册器还会初始化kretprobe的一些 成员,比如分配返回地址实例的空间等操作。最后,kretprobe注册器会利用 kretprobe内嵌的structkprobe结构进行kropbe的注册。自此,kretprobe注册过程就完成了。

2) kretprobe探测点的触发过程

kretprobe触发是在刚进入被探测函数的第一条汇编指令时发生的,因为 kretprobe注册时把该地址修改位int3指令。
此时发生了一次CPU异常处理,这与kprobe探测点被触发相同。但与kprobe处理不同的是,这里并不是运行用户定义的pre_handler函 数,而是执行pre_handler_kretprobe()函数,该函数又会调用arch_prepare_kretprobe()函数。 arch_prepare_kretprobe()函数的主要功能是把被探测函数的返回地址变换为&kretprobe_trampoline所 在的地址,这是一个汇编地址标签。这个标签的地址在kretprobe_trampoline_holder()中用汇编伪指令定义。替换函数返回地址是 kretprobe实现的关键。当被探测函数返回时,返回到&kretprobe_trampoline地址处开始运行。

接着,在 一些保护现场的处理后,又去调用trampoline_handler()函数。该函数的主要有两个作用,一是根据当前的实例去运行用户定义的调试函数, 也就是kretprobe结构中的handler所指向的函数,二是把返回值设成被探测函数正真的返回地址。最后,在进行一些堆栈的处理后,被探测函数又 返回到了正常执行流程中去。

以上讨论的就是kretprobe的执行过程,可以看出,该调试方式的关键点在于修改被探测函数的返回地址到kprobes的控制流程中,之后再把返回地址修改到原来的返回地址并使得该函数继续正常执行。