printk内核调试

首先阐明一点,我调试的目的是为了学习,看看内核代码是如何运行,打印一些内核运行时候的信息帮助自己学习,不是为了调试出系统的BUG(原因很简单,我没那个水平^-^).

 

在编程时候,最简单的调试莫过于用打印语句打印出结果从而判断BUG出在哪儿,写JAVA的都写过System.out.println这样的语句来调试。后来高级点,用了DEBUG来调试。不过打印语句简单,对付简单的BUG和一些跟踪还是很好用。

 

内核调试相比于用户程序调试难度就要大很多。LINUX是C语言写的,但我们不能使用printf来打印,原因很容易解释,内核中不认识库文件中的printf函数。其次,内核程序运行在系统空间中,而用户程序运行在用户空间中。我们的打印语句也就是打印内存的内容到控制台,看到这里就是说我们必须想办法让自己的写有print的程序运行到系统空间下才可以跟踪内核中的代码。

 

对付第一个,内核已经提供了一个函数printk。他是可以在系统空间运行的打印语句,相当于我们写C语言中的printf。用法很简单,下面是个样例

printk(KERN_ALERT "hello world\n");

KERN_ALERT是这个信息的等级,低等级的不会被打印出来,这个取决于你的配置文件中配置的等级(具体信息大家可以查询 printk的内容,KERN_ALERT级别很高,不用担心这一点)中间有空格但是没有逗号。还有就是输出信息在哪儿,由于linux都带有syslog和klog进程,不清楚的一查看下linux系统管理中日志管理这块,如果你没有修改过日志配置文件,这些打印的信息会被输出到/var/log/messages中,配置的话。那就取决于你配后的日志文件位置。

 

对付第2点,让自己写有print的程序运行到系统空间下才可以看到内核代码执行过程中产生的信息。在linux中,内核是按模块划分的,一般包括进程管理,内存管理,文件系统,设备控制,网络这些。而linux有一个优良特性就是运行时候可以扩展这些模块包括用户自己定义的,而且不需要从启。也可以在运行时候卸载。

 

接下来思路就有了,我们可以自己写一个内核模块,里面附带打印语句printk打印出系统空间的内容,就可以来调试了。下面是具体步骤:(内核中有个全局变量current,熟悉的人都知道这是当前进程的task->struct指针,里面记载了大量当前进程的信息,接下来我们就把这些信息一部分输出来)

 

前提条件:首先确保你从新编译过一次内核(如果没有编译过的话,就继续编译吧,有问题可以留言),原因是我们新增的内核模块代码自然是要编译的(一句废话),我们采用GCC编译器,因为内核也是GCC编译器。编译过内核的人自然知道自己的内核是被什么版本的GCC编译,那么在编译模块时候采用相同的编译器就OK了,如果没有编译过,那么就可能会导致你编译的模块和内核采用的编译器不一致(不出问题说明你很幸运,出问题很正常)。

 

1.切换到root用户,只有root用户才有权限加载模块。(安全,加载到内核的模块运行在系统空间,这说明它的权限非常大,可以查看用户空间的内容。任何人若都能加载内核模块,麻烦就XXXX)。

 

2.新建个目录,名字随便,位置也随便,可以在你的源代码树下,也可以在其它位置。为了方便,我新建了/program/debug(不建也可以,你可以在任意位置来写,但随后生成的东西会让你觉得很乱,所以最好新建一个空的目录)。

 

3.在/program/debug目录下新建文件hello_world.c,文件内容是:

 

#include "linux/init.h"
#include "linux/module.h"
#include "linux/sched.h" //加载这个文件就是因为task_struct这个数据结构是在这个文件中定义的
MODULE_LICENSE("Dual BSD/GPL"); //不要遗漏,相当于模块许可证,不写的话内核会抱怨

static int hello_init(void) { //初始化函数
        printk(KERN_ALERT "%d\n", current->state);
        return 0;
}

static void hello_exit(void) { //退出函数
        printk(KERN_ALERT "Goodbye\n");
}

module_init(hello_init); //声明hello_init是初始化函数,不是有_init就能表示它是初始化函数
module_exit(hello_exit);//同样的道理

 

C语言代码就是这样子了,很明显直接用GCC是无法编译的,printk不是C库函数编译不通过,其次内核全局变量current,GCC也肯定不认识(错的地方太多)。他必须在内核态下才可以编译通过,那我们用make来编译他。

在/program/debug下新建一个文件,名字就叫Makefile(熟悉make的人应该知道,不过我不太懂)

内容如下

obj-m:=hello_world.o //你要编译的内核模块,hello_world
all:
        make -C /lib/modules/2.6.18/build M=/program/debug modules  #编译指令

clean:
        make -C /lib/modules/2.6.18/build M=/program/debug clean #清除指令

 

#注意下目录 /lib/modules/2.6.18/build这个目录是存在的用Tab可以补齐,2.6.18是你当前运行的内核版本号,而且对应目录是存在的,/program/debug是当前目录,其实可以用shell指令来写可以移植的文件,但不知道为什么我用$(pwd)总会出错,可能跟我不太熟悉make有关吧

 

接下来的操作用shell给出

[root@liumengli debug]# make all
XXXX (中文在我的远程连接上不支持,出现乱码,不过是输出信息不要紧)

[root@liumengli debug]# ls
hello_world.c  hello_world.ko  hello_world.mod.c  hello_world.mod.o  hello_world.o  Makefile  Module.symvers(编译结束后生成的一对编译后的文件)

[root@liumengli debug]# insmod hello_world.ko (加载编译好的模块到内核)
[root@liumengli debug]# rmmod hello_world.ko (从内核卸载模块)

[root@liumengli debug]# tail /var/log/messages

Oct 14 19:01:02 liumengli crond(pam_unix)[7301]: session opened for user root by (uid=0)
Oct 14 19:01:02 liumengli crond(pam_unix)[7301]: session closed for user root
Oct 14 19:48:11 liumengli iiimd[2011]: status has not been enabled yet. (1, 2)
Oct 14 19:48:11 liumengli iiimd[2011]: status has not been enabled yet. (1, 1)
Oct 14 19:51:13 liumengli kernel: [26844.370996] 0
Oct 14 19:51:22 liumengli kernel: [26853.341653] Goodbye

[root@liumengli debug]#


结束,前面日志信息不用管了。后面2条是比较感兴趣的。刚刚我们在看到我们声明了一个初始化函数,他是在被你加载的时候会运行,结束函数是在被你卸载的时候会运行。那么最后2行就好理解了,0是我们在加载时候运行 printk(KERN_ALERT "%d\n", current->state);这条语句写入到/var/log/messages中的,current->state表示当前进程的状态,0表示就绪态。

你可能感兴趣的:(C++,c,linux,gcc,配置管理)