STEP 1——编写一个最简单的系统调用
最简单的系统调用当然是调了它之后什么都不干了,但是我还是决定让它干一件很是经典的事情,想调试器输出一条语句“Hello World!”。几乎每种编程技术的开始都有由一个名为“HelloWorld”的程序开始,我不想破坏这一传统。
下面我们就来一步步完成这个系统调用。完成这个系统掉用共需要修改2个内核文件,并要编写一个用户程序来调用这个系统调用。
首先要修改的就是系统服务表。系统服务表位于base/ntos/ke/i386/中,名为systable.asm。
打开这个文件,可以很容易的找到一大长串以TABLE_ENTRY开头的项目,这里每一个TABLE_ENTRY就代表一个系统调用,最后还有一个项是 TABLE_END 295,这表示一共有296个系统调用(从0开始计算)。我们呢,就在列表的最后加上一项,
TABLE_ENTRY HelloWorld, 0, 0
然后把TABLE_END数字更新为296,这样我们就在系统服务表中加上了自己的一条系统调用的记录了。需要说明一下的是HelloWorld后面的第一个0表示没有参数,如果为1表示有参数,后面的数字就表示参数的个数。
另一个需要修改的文件当然是我们那个系统调用函数的实体了,但是我们的系统调用函数还没有呢啊,要写在哪儿呢?这个问题很值得研究,据我的同学实验后说,可以在base文件夹下任意文件夹随便添加个.c文件就行,但是我觉得不太靠谱。因为编译的时候会涉及好多设置,还有源文件里还要有相关的宏,来控制编译结果,这么复杂我当然不懂了。我用个投机取巧的办法就是找个相关的文件,把自己的函数写在后面。但是什么叫相关的文件呢,既然我要做的是关于进程的,那我就在base/ntos/ps文件夹找了个文件psctx.c。ps文件夹是关于进程的源文件集合。psctx.c是关于线程上下文的几个函数的集合。我们的这个系统调用的名字也有要求,首先必须符合内核函数的命名习惯,返回值必须是NTSTATUS,对于我们这系统调用,函数名应该是NTSTATUS NtHelloWorld()这样子的。注意前面的前缀Nt,好像这个是必须的,这关系的是不是能根据系统服务表正确的找到系统服务函数。至于为什么回事这种对应规则,我也不知道,人家都这么做的我也这么做吧。
系统服务很简单,就两句话
DbgPrint(“Hello World!/n”);
return STATUS_SUCCESS;
这个DbgPrint函数和printf函数用法基本一样。只不过是在WinDbg中输出。
好了,到此为止最简单的系统调用已经完成了,重新编译一下内核,替换掉虚拟机中的内核就算完成了第一步。
下面我们还要编写一个用户程序看看我们的系统调用是不是能够成功执行。我是在VC6.0的环境下编的。是个控制台程序,程序清单如下:
#include<stdio.h>
#include<windows.h>
int main(int argc, char* argv[])
{
__asm {
mov eax, 296
int 0x2E
}
system("pause");
return 0;
}
最核心的就是哪两句汇编代码,第一句是将系统服务号放到eax寄存器里,然后一个INT指令,这样就陷入到内核了。这里插一下,据说以INT 0x2E方式调用系统服务是奔腾2代以前的方式,后来的CPU都通过sysenter指令完成。本人用的是Intel Core2 Duo T7600,显然是奔2之后的。但是我在VC上尝试用sysenter指令,编译通过不了。只好用这种古老的形式了。还好是向下兼容的。
把这个程序编译连接后生成的.exe文件拷贝的虚拟机中,双击运行一下,这时在WinDbg的Command窗口中就会看到我们期待的“Hello World!”字样(当然前提是把WinDbg以内核调试的模式连接上了虚拟机)。
好了,我们的STEP 1至此已经成功完成了,也从此迈出了第一步。