`
memorymyann
  • 浏览: 266228 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

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表示就绪态。

分享到:
评论
2 楼 AndMacLinuXp 2011-11-15  
试了下,不错!
1 楼 lefish 2008-10-16  
写的很牛的,条理比较清楚,顶你一下.

相关推荐

    linux内核调试方法总结

    三 内核调试配置选项 1 内核配置 2 调试原子操作 四 引发bug并打印信息 1 BUG()和BUG_ON() 2 dump_stack() 五 printk() 1 printk函数的健壮性 2 printk函数脆弱之处 3 LOG等级 4 记录缓冲区 5 syslogd/klogd 6 dmesg...

    Linux内核调试技术之Printk

     在内核调试技术之中,简单的是printk的使用了,它的用法和C语言应用程序中的printf使用类似,在应用程序中依靠的是stdio.h中的库,而在linux内核中没有这个库,所以在linux内核中,使用这个printk要对内核的实现...

    Debugging kernel and modules via GDB.pdf

    在 Linux 内核开发过程中不...但是很多情况下,我们对问题产生的原因毫无头绪,也就不可能在茫茫的代码中添加printk,这时候我们就依赖于GDB构建Linux内核调试环境。本文以 Linux 4.1.15为例,介绍调试环境的搭建过程。

    Linux内核设备驱动之内核的调试技术笔记整理

    内核的配置选项中包含了一些与内核调试相关的选项,都集中在”kernel hacking”菜单中。包括: CONFIG_DEBUG_KERNEL 使其他的调试选项可用,应该选中,其本身不会打开所有的调试功能。 具体的调试选项说明可参见驱动...

    嵌入式Linux的调试技术

    打印内核调试信息:printk  Printk函数运行在内核空间,printf函数运行在用户空间。Linux内核程序只能使用printk函数输出调试信息。  防止printk函数降低linux驱动性能  Linux驱动只在开发阶段使用printk函数...

    嵌入式软件调试技术专题(3):Linux内核日志与信息打印

    Linux内核、驱动开发中的printk打印技巧、日志系统、函数调用栈、动态调试、strace命令、内核转储、使用proc文件系统查看内核信息等查看Linux内核日志及打印信息的各种工具和方法。

    嵌入式Linux调试技术

     大多数开发人员认为kgdb是好的内核调试工具。Kgdb除了提供类似的printk函数的日志输出功能,还允许开发人员直接在pc上通过gdb链接目标设备。Kgdb包含了两部分:kgdb内核和一套连接接口。这些接口目前支持串口tty...

    ecryptfs-creat调试1

    调试open系统调用时需要记住,如果直接加入printk会导致开机刷屏有大量的printk输出甚至机器无法正常开启和正常工作,这里就需要重新加入一个内核中的系统

    pt7c4307 驱动

    //printk("set_time Date(y/m/d):%d/%d/%d Time(h/m/s):%d/%d/%d\n",tm->tm_year,tm->tm_mon,tm->tm_mday,tm->tm_hour,tm->tm_min,tm->tm_sec); regs[REG_SC] = BIN2BCD(tm->tm_sec); regs[REG_MN] = BIN2BCD(tm...

    LINUX编程白皮书 (全集)

    第9章 替换printk 199 第10章 任务调度 202 第11章 中断处理程序 207 第12章 对称多处理 211 第13章 常见错误 212 附录A 2.0和2.2之间的差异 213 附录B 其他资源 214 附录C 给出你的评价 215 第三部分 Linux程序员...

    浅谈在linux kernel中打印函数调用的堆栈的方法

    在Linux内核调试中,经常用到的打印函数调用堆栈的方法非常简单,只需在需要查看堆栈的函数中加入: dump_stack(); 或 __backtrace(); 即可 dump_stack()在~/kernel/ lib/Dump_stack.c中定义 void dump_stack(void)...

    Android 实现自己的LOG信息

    Android内核是基于Linux Kerne 2.36的,因此,Linux Kernel的LOG机制同样适合于Android内核,它就是有名的printk,与C语言的printf齐名。与printf类似,printk提供格式化输入功能,同时,它也具有所有LOG机制的

    Linux编程从入门到精通

    第9章 替换printk 199 第10章 任务调度 202 第11章 中断处理程序 207 第12章 对称多处理 211 第13章 常见错误 212 附录A 2.0和2.2之间的差异 213 附录B 其他资源 214 附录C 给出你的评价 215 第三部分 Linux程序员...

    LINUX编程白皮书

    第9章 替换printk 199 第10章 任务调度 202 第11章 中断处理程序 207 第12章 对称多处理 211 第13章 常见错误 212 附录A 2.0和2.2之间的差异 213 附录B 其他资源 214 附录C 给出你的评价 215 第三部分 Linux程序员...

    linux编程白皮书

    第9章 替换printk 199 第10章 任务调度 202 第11章 中断处理程序 207 第12章 对称多处理 211 第13章 常见错误 212 附录A 2.0和2.2之间的差异 213 附录B 其他资源 214 附录C 给出你的评价 215 第三部分 Linux程序员...

    Linux编程白皮书

    第9章 替换printk 199 第10章 任务调度 202 第11章 中断处理程序 207 第12章 对称多处理 211 第13章 常见错误 212 附录A 2.0和2.2之间的差异 213 附录B 其他资源 214 附录C 给出你的评价 215 第三部分 Linux程序员...

    Linux编程资料

    第9章 替换printk 199 第10章 任务调度 202 第11章 中断处理程序 207 第12章 对称多处理 211 第13章 常见错误 212 附录A 2.0和2.2之间的差异 213 附录B 其他资源 214 附录C 给出你的评价 215 第三部分 Linux程序员...

Global site tag (gtag.js) - Google Analytics