编写自己的驱动
一些Kernel 知识
内核的各个模块
LKM
LKM(loadable kernel module),内核作为一个那么复杂,庞大的系统,如果每次产生新的设备,更新新的驱动都要把源码加到内核再重新编译,明显是不太现实。所以呢,LKM也就千呼万唤始出来了。设备分类
按照LDD3的说法,分为三种设备Character devices
block device
network device
。这只是一些比较抽象的分类,各个设备在编写驱动时肯定在逻辑设计上有交叉。Character devices
A character (char) device is one that can be accessed as a stream of bytes (like a file); a char driver is in charge of implementing this behavior. Such a driver usually implements at least the open, close, read, and write system calls. The text console (/dev/console) and the serial ports (/dev/ttyS0 and friends) are examples of char devices, as they are well represented by the stream abstraction.
本着一切皆文件的思想,字符设备被映射到文件系统的文件上。可以像操作文件一样操作一个设备。open,read,write之类的方法也可被用于字符设备。
1 | $ ls -alh /dev |
关注第一列,c表示字符设备(char device),b表示块设备(block device),-表示文件(file),l表示链接(link),d表示文件夹(directory)。
块设备与字符设备很相似,不做提及。
- Network interfaces
Network interface is in charge of sending and receiving data packets, driven by the network subsystem of the kernel, without knowing how individual transactions map to the actual packets being transmitted. Many network connections (especially those using TCP) are stream-oriented, but network devices are,usually, designed around the transmission and receipt of packets.A network driver knows nothing about individual connections; it only handles packets.
比较特殊的是网络设备作为stream-oriented device
,并不会像前边字符设备,块设备一样以文件的形式存在于文件系统中。
Hello World
首先先写一个hello world试试看
- hello.c
1 |
|
- Makefile
1 | obj-m += hello.o |
- 编译出的hello.ko也是elf,没有main函数,入口在哪?
module_init(init_function);
当insmod时,init_function会被执行
module_exit(exit_function);
当rmmod时,exit_function会被执行
1 | // include/linux/module.h |
- printk?,它的参数是怎么回事?
user_mode时可以动态链接到libc的printf来打印信息,显然LKM工作在kernel_mode,不可能再使用libc的函数。printk时内核中的打印函数,采用类似于什么符号定位的方式来链接。
int printk(const char *fmt, ...);
这应该是它的函数声明,在使用时会加入一个loglevels,printk(KERN_DEBUG "Here I am: %s:%i\n", __FILE__, __LINE__);
。内核作为一个庞大的系统,日志分优先级确实是应该的。另外它当然是支持格式化。
1 | //include/linux/kern_levels.h |
- static int ?为什么要如此声明成静态的?
Initialization functions should be declared static, since they are not meant to be visible outside the specific file; there is no hard rule about this, though, as no function is exported to the rest of the kernel unless explicitly requested.
init_function和exit_function,就像构造和析构,不应该为外部所引用,是由系统所调用。声明称静态更好,当然并不是硬性要求的。
- __init,__exit是什么?
The __init token in the definition may look a little strange; it is a hint to the kernel that the given function is used only at initialization time. The module loader drops the initialization function after the module is loaded, making its memory available for other uses.
据说 具有 __exit __init标识的函数会被放入elf的特殊段,执行之后会释放
1 | // include/linux/init.h |
- 那些标明证书和作者之类的宏是什么?
有些宏并非必须的,他们最终会出现elf的某一个数据段内。
1 | // 一些说明性,标识性的,非必需的宏 |
走向字符设备
我参考了几个kernel pwn的题目源码,简化成了下边的模板,以方便实验,调试使用。
- Makefile
1 | obj-m += template.o # <= module.o id from module.c |
- template.c
1 |
|
- test.c
1 | // gcc test.c -g |
- 测试驱动
1 | $ ls |
一些说明
- 为什么是那些头文件?
1 |
|
- 其他注册设备的方式
- 可以使用misc_register来注册misc设备。关于misc设备,他是绑定在10号(major num)驱动上的字符设备。它的minor num既可以指定,但更应该动态获取。在进行注册时,应该向misc_register函数传入已经赋值minor,name,fops成员的
struct miscdevice
结构体指针。
1 |
|
如此修改便可以。
- 也可以注册/proc目录下的设备
1 | static struct proc_dir_entry *the_dev; |
- 抱怨
愚花了一个星期左右的时间在研究LDD3的前几章(英文版(找不到中文版)看的很煎熬),我的目的是为了自己编写驱动从而模拟kernel pwn的些许题目,方便带符号调试,但是大多是在学习整个驱动开发的框架,关注太多旁系的知识。实际不如直接去研究代码,看书的效率确实有限。。。。