绪论
这篇文章我说明在openbsd上如何进行内核编程,以下句子来自lkm手册页: "可加载内核模块可以允许系统管理员在一台运行着的系统上动态的增加或删除功能模块,它同时可以帮助软件工程师们为内核增加新的功能而根本就不需要重起计算机就可以测试他们开发的程序."
当然,像众多系统的lkm一样,它存在一定的安全隐患,哈哈,其实这也是我写这篇文章给大家的原因:)它提供了更广泛的空间给恶意的superroot,其实也就是已经得到系统管理员权限的我们。我们利用lkm可以驾驭整个系统而不会轻易被发现. 同样的, 如果你系统的securelevel在0级一行的话就不能加载或卸载模块了,如果你要使系统在进入securemode之前可以加载模块,可以编辑/etc/rc.securelevel文件,添加相应的入口.
总览
/dev/lkm设备与用户的交互通过ioctl(2)系列系统调用来进行. 主要是一些工具如modload,modunload和modstat等来控制模块的加载
和卸载以及模块的状态.
lkm接口定义了五种不同的模块类型:
系统调用模块
虚拟文件系统模块
设备驱动模块
可执行程序解释器模块
其它模块
一个普通的模块包括三个主要部分:
1) 内核入口和出口的处理(也就是当模块被加载,被卸载时的动作).
2) 一个外部入口点, 当模块用modload程序被加载的时候需要用到
3) 模块的主体, 包含函数代码等.
对于其他类型的模块来说,它需要开发人员提供严格的控制和当内核模块卸载的时候对内核原有的状态的保存.
对于模块的支持必须用'option LKM'编译进内核的配置文件.模块需要支持默认的openBSD 2.9的内核.通常,内核空间的数据接口都被提供
给了模块来操作.后面
就有一个lkm设备的例子.
每个类型的模块的内部数据结构里面都存在一个宏用来加载自己.也就类似模块本身模块名的东东,它被指定在内核数据结构中,和模块的一些
特殊数据如sysent这样
的针对系统调用模块的结构在一起.
让我们看看一些例子吧.
★系统调用模块.
这里我们将增加一个新的系统调用printf()的整型和字符串参数.它的原型如下:
int syscall(int, char *)
内核内部定义的一个lkm的syscall结构如下:
strUCt lkm_syscall {
MODTYPE lkm_type;
int lkm_ver;
char *lkm_name;
u_long lkm_offset; /* 保存/分配 内存空间 */
struct sysent *lkm_sysent;
struct sysent lkm_oldent; /*保存原调用,用于lkm的卸载 */
};
现在我们已经有了一个简单的模块框架了(应该叫LM_SYSCALL),lkm的版本,模块名,都在系统调用表里存在一个相应的入口.这样我们有
了一个指向结构sysent的模块框架
我们将用MOD_SYSCALL宏来安装它:
MOD_SYSCALL("ourcall", -1, &newcallent)
我们来分析一下上面的宏,很明显,模块名为"ourcall",用来标示模块,还有一个作用就是我们利用modstat命令时会显示出来.-1代表我们
的syscall该插入的位置,在这个
宏当中的-1的意思是我们不用关心位置具体在什么地方,它会被分配到一个空的位置.最后一个字段newcallent是一个指向sysent的结构,
它包含了我们系统调用的相应的数
据.
除此之外我们还需要一个句柄用来加载和卸载内核模块,好,在这个例子中我用'hi'来加载,用'bye'来卸载.这对我们调试程序很有帮助.句柄可
以是相同的函数或者单个函数,
如果没有定义句柄,那么lkm_nofunc()会简单的返回0,这个模块是没有加载卸载的,也就失去了作用.
我们模块的外部入口点是ourcall():
int
ourcall(lkmtp, cmd, ver)
struct lkm_table *lkmtp;
int cmd;
int ver;
{
DISPATCH(lkmtp, cmd, ver, ourcall_handler, ourcall_handler, lkm_nofunc)
}
这个句柄可以用来加载,卸载模块.第四个参数我们用作加载操作,第五个参数用作卸载操作,第六个参数是状态函数(在此例中没有用到).
ok!完整的系统调用模块代码如下(syscall.c):
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/ioctl.h>
#include <sys/cdefs.h>
#include <sys/conf.h>
#include <sys/mount.h>
#include <sys/exec.h>
#include <sys/lkm.h>
#include <sys/proc.h>
#include <sys/syscallargs.h>
/* 定义我们自己的系统调用原型 */
int newcall __P((struct proc *p, void *uap, int *retval));
/*
* 所有的系统调用都有三个参数: 一个指向proc结构的结构指针,一个空指针指向参
* 数本身和一个返回指针.下面,我们定义这些参数的结构.如果你只有一个参数,则
* 只需要一个入口就可以了.
*/
struct newcall_args{
syscallarg(int) value;
syscallarg(char *) msg;