Linux内核调试技术

来源:互联网 发布:上海plc编程培训 编辑:程序博客网 时间:2024/05/25 05:38
一、内核中的调试支持
在前面已经建议过:学习编写驱动程序要构建安装自己的内核(标准主线内核)。最重要的原因之一是:内核开发者已经建立了多项用于调试的功能。但是由于这些功能会造成额外的输出,并导致性能下降,因此发行版厂商通常会禁止发行版内核中的调试功能。
为了实现内核调试,我在内核配置上增加了几项:
Kernel hacking  --->     
        [*] Magic SysRq key
        [*] Kernel debugging
        [*]   Debug slab memory allocations  
        [*]   Spinlock and rw-lock debugging: basic checks 
        [*]   Spinlock debugging: sleep-inside-spinlock checking
        [*]   Compile the kernel with debug info  
        [*] Magic SysRq key 
Device Drivers  --->  
        Generic Driver Options  --->
          [*] Driver Core verbose debug messages 
General setup  --->
        [*] Configure standard kernel features (for small systems)  --->
          [*]   Load all symbols for debugging/ksymoops
等等。

二、通过打印调试
最普通的调试技术就是监视,即在应用程序编程中,在一些适当的地点调用printf显示监视信息。调试内核代码的时候,可以用printk来完成相同的工作。
首先,printk有8个loglevel,定义在<linux/kernel.h>中:

#define KERN_EMERG   "<0>"  //用于紧急事件消息,它们一般是系统崩溃之前提示的消息
#define KERN_ALERT   "<1>"  
//用于需要立即采取动作的情况
#define KERN_CRIT    "<2>"  
//临界状态,通常涉及严重的硬件或软件操作失败
#define KERN_ERR     "<3>"  
//用于报告错误状态,设备驱动程序会经常使用KERN_ERR来报告来自硬件的问题
#define KERN_WARNING "<4>"  //对可能出现问题的情况进行警告,但这类情况通常不会对系统造成严重问题
#define KERN_NOTICE  "<5>"  
//有必要进行提示的正常情形。许多与安全有关的情况用这个级别进行汇报
#define KERN_INFO    "<6>"  
//提示性信息。很多驱动程序在启动的时候以这个级别来打印它们找到的硬件信息
#define KERN_DEBUG   "<7>"  //用于调试信息


所以printk()可以这样用:printk(KERN_INFO "Hello, world!\n");

未指定日志级别的printk("Hello, world!\n")采用的默认级别是DEFAULT_MESSAGE_LOGLEVEL,这个宏在kernel/printk.c中被定义为整数4,即对应KERN_WARNING。

#define DEFAULT_MESSAGE_LOGLEVEL 4 /* KERN_WARNING */


在/proc/sys/kernel/printk 会显示4个数值(可由echo修改),如下图。
#cat 
/proc/sys/kernel/printk
#echo 8 > /proc/sys/kernel/printk  //通过echo修改当前日志级别为8
《Linux设备驱动程序》第四章-调试技术 - myswirl - myswirl
 
根据日志级别,内核可能会把消息打印到当前控制台上,这个控制台可以是一个字符模式的终端、一个串口打印机或一个并口打印机。当优先级小于console_loglevel这个整数变量的值,消息才能显示出来,而且每次输出一行(如果不以newline字符结尾,则不会输出)。

如果系统同时运行了klogd和syslogd,则无论console_logleve为何值,内核消息都会追加到/var/log/messages(用户空间)中。

如果klogd没有运行,这些消息就不会传递到用户空间,这时只能查看/proc/kmsg文件(使用dmesg命令可以轻松做到)。

变量console_loglevel的初始值DEFAULT_CONSOLE_LOGLEVEL也定义在/kernel/printk.c中:

#define DEFAULT_CONSOLE_LOGLEVEL 7 /* anything MORE serious than KERN_DEBUG */



2.1 而在运行是改变console_loglevel的程序《Linux设备驱动程序(第3版)》提供,如下setlevel.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#define __LIBRARY__ /* _syscall3 and friends are only available through this */
#include <linux/unistd.h>

/* define the system call, to override the library function */
_syscall3( int syslog, int type, char *bufp, int len);

int main(int argc, char **argv)
{
    int level;

    if(argc==2)  
    {
        level = atoi(argv[1]); /* the chosen console */
    }else 
    {
        fprintf(stderr, "%s: need a single arg\n",argv[0]); exit(1);
    }

    if( syslog(8,NULL,level) < 0 )
    { 
        fprintf(stderr,"%s: syslog(setlevel): %s\n", argv[0],strerror(errno));
        exit(1);
    }
    exit(0);
}

#gcc -o setlevel setlevel.c    //编译setlevel
#./setlevel 2  //设置日志级别,我测试没有设置成功???


2.2 重定向到控制台的程序《Linux设备驱动程序(第3版)》提供,如下setconsole.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/ioctl.h>

int main(int argc, char **argv)
{
    char bytes[2] = {11,0}; /* 11 is the TIOCLINUX cmd number */

    if (argc==2) 
    {
        bytes[1] = atoi(argv[1]); /* the chosen console */
    }else 
    {
        fprintf(stderr, "%s: need a single arg\n",argv[0]); exit(1);
    }
    if ( ioctl(STDIN_FILENO, TIOCLINUX, bytes)<0 ) 
    {    /* use stdin */
        fprintf(stderr,"%s: ioctl(stdin, TIOCLINUX): %s\n", argv[0], strerror(errno));
        exit(1);
    }
    exit(0);
}
 

#gcc -o setconsole setconsole.c    //编译setconsole
#./setconsole tty0  //参数总是错误的,不知道如何设置???

三、开启及关闭消息

下面给出了一个调用printk的编码方法,它可个别或全局地开关printk语句。是否定义符号PDEBUG取决于是否定义了SCULL_DEBUG,并且,它能根据代码所运行的环境来选择合适的方式显示信息。
这些宏被证实是非常有用的,仅有的缺点是每次开启和关闭调试都要重新编译模块。
另一种方法:使用C条件语句,它在运行时执行,因此可以在程序运行期间打开或者关闭,但每次代码执行时系统都要进行额外的处理,甚至在禁用消息后仍然会影响性能,而有时这种性能损失是无法接收的。

#undef PDEBUG          //取消对PDEBUG的定义,以防重复定义
#ifdef SCULL_DEBUG
#  ifdef __KERNEL__
   //表明打开调试并处于内核空间
#    define PDEBUG(fmt, args...) printk( KERN_DEBUG "scull: " fmt, ## args)
#  else              
//表明处于用户空间
#    define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args)
#  endif
#else
#  define PDEBUG(fmt, args...) 
//调试被关闭:不作任何事情
#endif

#undef PDEBUGG
#define PDEBUGG(fmt, args...)  
//不作任何事情,仅仅是占个符号位

Makefile中要添加的语句:

# Comment/uncomment the following line to disable/enable debugging
DEBUG = y

# Add your debugging flag (or not) to CFLAGS

ifeq ($(DEBUG),y)

    DEBFLAGS = -O -g -DSCULL_DEBUG

else

    DEBFLAGS = -O2

endif


CFLAGS += $(DEBFLAGS)


速度限制

为了避免printk重复输出过快而阻塞系统,内核使用以下函数跳过部分输出:

int printk_ratelimit(void);

典型的应用如下:

if (printk_ratelimit( ))
    printk(KERN_NOTICE "The printer is still on fire\n");

可以通过修改/proc/sys/kernel/printk_ratelimit(重开信息前应等待的秒数)和/proc/sys/kernel/printk_ratelimit_burst(在速度限制前可接受的信息数)来定制printk_ratelimit的行为。

打印设备编号

Linux还提供了打印设备编号的宏(在<linux/kdev_t.h>中定义):

int print_dev_t(char *buffer, dev_t dev);
char *format_dev_t(char *buffer, dev_t dev);

两个函数的唯一区别是:print_dev_t返回打印字符数,format_dev_t返回缓冲区指针。注意缓冲区char *buffer的大小应至少有20B。


三、通过查询调试

驱动开发人员可以用如下方法对系统进行查询:在/proc文件系统中创建文件、使用驱动程序的ioctl方法、以及通过sysfs导出属性等。

使用/proc文件系统
/proc文件系统是一种特殊的、由软件创建的文件系统,内核使用他向外界导出信息。/proc下面的每个文件都绑定于一个内核函数,用户读取其中的文件时,该函数动态的生成文件的“内容”。
如以前用过的:/proc/modules列出的是当前载入模块的列表。
原创粉丝点击