首先阐明一点,我调试的目的是为了学习,看看内核代码是如何运行,打印一些内核运行时候的信息帮助自己学习,不是为了调试出系统的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表示就绪态。
分享到:
相关推荐
三 内核调试配置选项 1 内核配置 2 调试原子操作 四 引发bug并打印信息 1 BUG()和BUG_ON() 2 dump_stack() 五 printk() 1 printk函数的健壮性 2 printk函数脆弱之处 3 LOG等级 4 记录缓冲区 5 syslogd/klogd 6 dmesg...
首先,`printk()`函数是内核调试的基本工具,它允许在代码中插入打印语句,以输出关键变量的值和执行流程。这种方法简单易用,但在复杂的内核环境中可能不够全面。 其次,`kdb`是一个内核调试补丁,可以在系统运行...
在Linux系统开发和调试过程中,`printk`是一个至关重要的工具,它被广泛用于内核级别的日志输出和故障排查。`printk`是Linux内核中的一个打印函数,能够帮助开发者获取内核运行时的信息,这对于理解内核行为、诊断...
《Linux内核调试技术的研究》一文探讨了在Linux操作系统中进行内核调试的关键方法。Linux内核的开放源码特性使其成为开发者进行定制和优化的重要平台,然而,由于内核的复杂性和特殊性,调试内核驱动和应用程序时会...
最直接的内核调试方式之一是通过在代码中插入`printk()`函数调用来打印变量状态和执行路径,这与用户级程序中常见的`printf()`类似。`printk()`用于输出内核级别的日志信息,帮助开发者追踪代码执行流程,定位潜在的...
在早期的内核调试中,`printk()`函数是最常用的方法之一。通过在内核代码的关键位置插入`printk()`调用,开发者可以监控程序的执行流程并查看关键变量的状态。虽然这种方法简单易行,但它也有一定的局限性,例如输出...
总而言之,Linux内核调试是一个涉及多种技术手段的复杂过程,包括但不限于printk日志记录、Oops分析、栈回溯以及使用各种调试工具。通过掌握这些技术,开发者能够更加有效地解决内核中的问题,提高系统的稳定性和...
Linux系统内核调试是IT领域中的一个重要话题,它涉及到操作系统的核心功能分析、问题排查和性能优化。这篇由王振东、周忠海、刘军礼、廉月仙、周扬等人撰写的论文“Linux系统内核调试的研究”深入探讨了这一主题。...
### Linux2.6下内核调试技术的改进与研究 #### 摘要与背景介绍 随着Linux操作系统的不断发展和完善,其内核版本也经历了多次更新。Linux2.6作为其中一个重要的版本,不仅提升了整体性能,还在内存管理、中断处理及...
### Linux内核调试技术详解 #### 一、概述 在开发Linux设备驱动程序的过程中,调试是必不可少的一个环节。由于驱动程序运行在内核空间,其调试方法与用户空间的应用程序有所不同。本文旨在介绍如何在Linux内核中...
在内核调试技术之中,简单的是printk的使用了,它的用法和C语言应用程序中的printf使用类似,在应用程序中依靠的是stdio.h中的库,而在linux内核中没有这个库,所以在linux内核中,使用这个printk要对内核的实现...
Linux嵌入式内核调试和开发是软件开发领域中一项重要的任务,特别是在嵌入式系统中,由于资源有限,内核的性能和稳定性至关重要。本文将深入探讨在Linux环境下进行内核调试和开发的一些关键技术和工具。 首先,...
首先,Linux内核调试技术中使用printk()函数是最为简单和直接的调试方法。在内核代码中,开发者可以添加printk()函数,并根据需要打印信息,从而观察程序的执行路径以及变量和指针等关键信息的变化。这种方式虽然...
在Linux内核调试中,尤其是涉及早期启动过程时,理解MMU(Memory Management Unit)的工作原理至关重要。MMU是处理器中的一个硬件单元,用于管理内存的物理地址与虚拟地址之间的映射关系。在Linux内核启动初期,MMU...
内核调试是操作系统开发和维护中的重要环节,它允许开发者深入到系统的核心,理解并解决在内核级别出现的问题。"内核简易代码"这个压缩包文件很可能是提供了一些简化内核调试过程的代码示例或者工具。在这个压缩包中...
内容概要:这本书主要介绍了如何有效地利用现有工具和高级技术来调试Linux内核以及内核模块。作者通过对多个真实世界中由软件缺陷导致的悲剧事故进行了简述,以此来强调软件质量的重要性。书中详细探讨了调试的艺术...
Linux 内核驱动调试方法 Linux 内核驱动调试方法是指在 ...printk 是 Linux 内核中最基本的调试工具之一,通过 printk 可以打印出内核中的错误信息、警告信息和调试信息等,从而帮助开发者调试和优化内核驱动程序。