基于FS4412嵌入式系统移植(8) linux内核调试之printk

以下内容主要摘录自《Linux安全体系分析与编程》

1、基本原理

(1)在UBOOT里设置console=ttySAC0或者console=tty1

这里是设置控制终端,tySAC0 表示串口, tty1 表示lcd

(2)内核用printk打印

内核就会根据命令行参数来找到对应的硬件操作函数,并将信息通过对应的硬件终端打印出来!

2、printk及控制台的日志级别

函数printk的使用方法和printf相似,用于内核打印消息。printk根据日志级别(loglevel)对消息进行分类。

相似的在android中有使用Log函数进行调试信息的打印。

日志级别用宏定义,日志级别宏展开为一个字符串,在编译时由预处理器将它和消息文本拼接成一个字符串,因此printk 函数中日志级别宏和格式字符串间不能有逗号。

下面是两个printk的例子,一个用于打印调试信息,另一个用于打印临界条件信息。

printk(KERN_DEBUG "Here I am: %s:%i/n", _ _FILE_ _, _ _LINE_ _);

printk(KERN_CRIT "I'm trashed; giving up on %p/n", ptr);

printk的日志级别定义如下(在linux2.6和3.14/include/linux/kernel.h中):

#defineKERN_EMERG"<0>"/*紧急事件消息,系统崩溃之前提示,表示系统不可用*/

#defineKERN_ALERT"<1>"/*报告消息,表示必须立即采取措施*/

#defineKERN_CRIT"<2>"/*临界条件,通常涉及严重的硬件或软件操作失败*/

#defineKERN_ERR"<3>"/*错误条件,驱动程序常用KERN_ERR来报告硬件的错误*/

#defineKERN_WARNING"<4>"/*警告条件,对可能出现问题的情况进行警告*/

#defineKERN_NOTICE"<5>"/*正常但又重要的条件,用于提醒。常用于与安全相关的消息*/

#defineKERN_INFO"<6>"/*提示信息,如驱动程序启动时,打印硬件信息*/

#defineKERN_DEBUG"<7>"/*调试级别的消息*/

extern int console_printk[];

#define console_loglevel  (console_printk[0])

#define default_message_loglevel  (console_printk[1])

#define minimum_console_loglevel  (console_printk[2])

#define default_console_loglevel  (console_printk[3])

日志级别的范围是0~7,没有指定日志级别的printk语句默认采用的级别是 DEFAULT_ MESSAGE_LOGLEVEL,其定义列出如下(在linux2.6/kernel/printk.c中,在linux3.14/kernel/printk/printk.c中):

/*没有定义日志级别的printk使用下面的默认级别*/

#define DEFAULT_MESSAGE_LOGLEVEL 4 /* KERN_WARNING 警告条件*/

内核可把消息打印到当前控制台上,可以指定控制台为字符模式的终端或打印机等。默认情况下,“控制台”就是当前的虚拟终端。

为了更好地控制不同级别的信息显示在控制台上,内核设置了控制台的日志级别console_loglevel。printk日志级别的作用是打印一定级别的消息,与之类似,控制台只显示一定级别的消息。

当日志级别小于console_loglevel时,消息才能显示出来。控制台相应的日志级别定义如下:

/* 显示比这个级别更重发的消息*/

#define MINIMUM_CONSOLE_LOGLEVEL  1   /*可以使用的最小日志级别*/

#define DEFAULT_CONSOLE_LOGLEVEL  7 /*比KERN_DEBUG 更重要的消息都被打印*/

int console_printk[4] = {

DEFAULT_CONSOLE_LOGLEVEL,/*控制台日志级别,优先级高于该值的消息将在控制台显示*/

/*默认消息日志级别,printk没定义优先级时,打印这个优先级以上的消息*/

DEFAULT_MESSAGE_LOGLEVEL,

/*最小控制台日志级别,控制台日志级别可被设置的最小值(最高优先级)*/

MINIMUM_CONSOLE_LOGLEVEL,

DEFAULT_CONSOLE_LOGLEVEL,/* 默认的控制台日志级别*/

};

如果系统运行了klogd和syslogd,则无论console_loglevel为何值,内核消息都将追加到/var/log/messages中。如果klogd没有运行,消息不会传递到用户空间,只能查看/proc/kmsg。

变量console_loglevel的初始值是DEFAULT_CONSOLE_LOGLEVEL,可以通过sys_syslog系统调用进行修 改。调用klogd时可以指定-c开关选项来修改这个变量。如果要修改它的当前值,必须先杀掉klogd,再加-c选项重新启动它。

通过读写/proc/sys/kernel/printk文件可读取和修改控制台的日志级别。查看这个文件的方法如下:

#cat /proc/sys/kernel/printk
7   4  1   7

上面显示的4个数据分别对应控制台日志级别、默认的消息日志级别、最低的控制台日志级别和默认的控制台日志级别。

可用下面的命令设置当前日志级别:

# echo "4 4 1 7" > /proc/sys/kernel/printk

这里有个问题需要说一下,在一些开发板提供的内核源码printk的默认打印级别都是未经修改的,例如飞凌的开发板提供的内核,在实际使用时有一些驱动会输出一些调试信息,在实际使用时这些信息会影响正常的用户使用,比如飞凌开发板连接网线后串口终端会定时输出以下内容:

eth0: link down
eth0: link up, 100Mbps, full-duplex, lpa 0x4DE1

以上信息并不是错误,只是在打印调试信息,说明网络正常连接,但这样会干扰串口输入,所以在编译内核时,修改printk.c中的调试级别如下即可解决这个问题:

int console_printk[4] = {
DEFAULT_MESSAGE_LOGLEVEL,/*控制台日志级别,优先级高于该值的消息将在控制台显示*/
/*默认消息日志级别,printk没定义优先级时,打印这个优先级以上的消息*/
DEFAULT_MESSAGE_LOGLEVEL,/*最小控制台日志级别,控制台日志级别可被设置的最小值(最高优先级)*/
MINIMUM_CONSOLE_LOGLEVEL,
DEFAULT_CONSOLE_LOGLEVEL,/* 默认的控制台日志级别*/
};

3、printk打印消息机制

在内核中,函数printk将消息打印到环形缓冲区__log_buf中,并将消息传给控制台进行显示。控制台驱动程序根据控制台的日志级别显示日志消息。

应用程序通过系统调用sys_syslog管理环形缓冲区__log_buf,它可以读取数据、清除缓冲区、设置日志级别、开/关控制台等。

当系统调用sys_syslog从环形缓冲区__log_buf读取数据时,如果缓冲区没有数据,系统调用sys_syslog所在进程将被加入到 等待队列log_wait中进行等待。当printk将数据打印到缓冲区后,将唤醒系统调用sys_syslog所在进程从缓冲区中读取数据。等待队列 log_wait定义如下:

DECLARE_WAIT_QUEUE_HEAD(log_wait);//等待队列log_wait

环形缓冲区__log_buf在使用之前就是已定义好的全局变量,缓冲区的长度为1 << CONFIG_LOG_ BUF_SHIFT。变量CONFIG_LOG_BUF_SHIFT在内核编译时由配置文件定义,对于i386平台,其值定义如下(在 linux2.6/arch/i386/defconfig中):

CONFIG_LOG_BUF_SHIFT=18

在内核编译时,编译器根据配置文件的设置,产生如下的宏定义:

#define CONFIG_LOG_BUF_SHIFT  18

环形缓冲区__log_buf定义如下(在linux2.6/kernel/printk.c中,在linux3.14/kernel/printk/printk.c中):

#define __LOG_BUF_LEN(1 << CONFIG_LOG_BUF_SHIFT) //定义环形缓冲区的长度,i386平台为
static char __log_buf[__LOG_BUF_LEN]; //printk的环形缓冲区
static char *log_buf = __log_buf;
static int log_buf_len = __LOG_BUF_LEN;
/*互斥锁logbuf_lock保护log_buf、log_start、log_end、con_start和logged_chars */
static DEFINE_SPINLOCK(logbuf_lock);

通过宏定义LOG_BUF,缓冲区__log_buf具备了环形缓冲区的操作行为。宏定义LOG_BUF得到缓冲区指定位置序号的字符,位置序号超过缓冲区长度时,通过与长度掩码LOG_BUF_MASK进行逻辑与操作,位置序号循环回到环形缓冲区中的位置。

宏定义LOG_BUF及位置序号掩码LOG_BUF_MASK的定义列出如下:

#define LOG_BUF_MASK (log_buf_len-1)

#define LOG_BUF(idx)  (log_buf[(idx) & LOG_BUF_MASK])

为了指明环形缓冲区__log_buf字符读取位置,定义了下面的位置变量:

static unsigned long log_start;/*系统调用syslog读取的下一个字符*/

static unsigned long con_start;/*送到控制台的下一个字符*/

static unsigned long log_end;/*最近已写字符序号加1 */

static unsigned long logged_chars; /*自从上一次read+clear 操作以来产生的字符数*/

任何地方的内核调用都可以调用函数printk打印调试、安全、提示和错误消息。函数printk尝试得到控制台信号量(console_sem),如果得到,就将信息输出到环形缓冲区__log_buf中,然后函数release_console_sem()在释放信号 量之前把环形缓冲区中的消息送到控制台,调用控制台驱动程序显示打印的信息。如果没得到信号量,就只将信息输出到环形缓冲区后返回。

函数printk列出如下(在linux2.6/kernel/printk.c中,在linux3.14/kernel/printk/printk.c中):

asmlinkage int printk(const char *fmt, ...)
{
    va_list args;
    int r;
    va_start(args, fmt);
    r = vprintk(fmt, args);
    va_end(args);
    return r;
}

asmlinkage int vprintk(const char *fmt, va_list args)
{
    unsigned long flags;
    int printed_len;
    char *p;
    static char printk_buf[1024];
    static int log_level_unknown = 1;
    preempt_disable(); //关闭内核抢占
    if (unlikely(oops_in_progress) && printk_cpu == smp_processor_id())
        /*如果在printk运行时,这个CPU发生崩溃,
    确信不能死锁,10秒1次初始化锁logbuf_lock和console_sem,留时间
    给控制台打印完全的oops信息*/
        zap_locks();
    local_irq_save(flags);  //存储本地中断标识
    lockdep_off();
    spin_lock(&logbuf_lock);
    printk_cpu = smp_processor_id(); 
    /*将输出信息发送到临时缓冲区printk_buf */
    printed_len = vscnprintf(printk_buf, sizeof(printk_buf), fmt, args);
    /*拷贝printk_buf数据到循环缓冲区,如果调用者没提供合适的日志级别,插入默认值*/
    for (p = printk_buf; *p; p++) {
        if (log_level_unknown) {
            /* log_level_unknown signals the start of a new line */
            if (printk_time) {
                int loglev_char;
                char tbuf[50], *tp;
                unsigned tlen;
                unsigned long long t;
                unsigned long nanosec_rem;
                /*在时间输出之前强制输出日志级别*/
                if (p[0] == '<' && p[1] >='0' &&
                        p[1] <= '7' && p[2] == '>') {
                    loglev_char = p[1]; //获取日志级别字符
                    p += 3;
                    printed_len -= 3;
                } else {
                    loglev_char = default_message_loglevel
                            + '0';
                }
                t = printk_clock();//返回当前时钟,以ns为单位
                nanosec_rem = do_div(t, 1000000000);
                tlen = sprintf(tbuf,
                               "<%c>[%5lu.%06lu] ",
                               loglev_char,
                               (unsigned long)t,
                               nanosec_rem/1000);//写入格式化后的日志级别和时间
                for (tp = tbuf; tp < tbuf + tlen; tp++) 
                    emit_log_char(*tp);  //将日志级别和时间字符输出到循环缓冲区
                printed_len += tlen;
            } else {
                if (p[0] != '<' || p[1] < '0' ||
                        p[1] > '7' || p[2] != '>') {
                    emit_log_char('<');
                    emit_log_char(default_message_loglevel
                                  + '0');  //输出字符到循环缓冲区
                    emit_log_char('>');
                    printed_len += 3;
                }
            }
            log_level_unknown = 0;
            if (!*p)
                break;
        }
        emit_log_char(*p);//将其他printk_buf数据输出到循环缓冲区
        if (*p == '/n')
            log_level_unknown = 1;
    }
    if (!down_trylock(&console_sem)) {
        /*拥有控制台驱动程序,降低spinlock并让release_console_sem()打印字符 */
        console_locked = 1;
        printk_cpu = UINT_MAX;
        spin_unlock(&logbuf_lock);
        /*如果CPU准备好,控制台就输出字符。函数cpu_online检测CPU是否在线,
    函数have_callable_console()检测是否
    有注册的控制台启动时就可以使用*/
        if (cpu_online(smp_processor_id()) || have_callable_console()) {
            console_may_schedule = 0;
            release_console_sem();
        } else {
            /*释放锁避免刷新缓冲区*/
            console_locked = 0;
            up(&console_sem);
        }
        lockdep_on();
        local_irq_restore(flags); //恢复本地中断标识
    } else {
        /*如果其他进程拥有这个驱动程序,本线程降低spinlock,
    允许信号量持有者运行并调用控制台驱动程序输出字符*/
        printk_cpu = UINT_MAX;
        spin_unlock(&logbuf_lock);
        lockdep_on();
        local_irq_restore(flags); //恢复本地中断标识
    }
    preempt_enable();  //开启抢占机制
    return printed_len;
}

函数release_console_sem()给控制台系统开锁,释放控制台系统及驱动程序调用者持有的信号量。持有信号量时,表示printk 已在缓冲区存有数据。函数release_console_sem()在释放信号量之前将这些数据送给控制台显示。如果后台进程klogd在等待环形缓冲 区装上数据,它唤醒klogd进程。

函数release_console_sem列出如下(在linux2.6/kernel/printk.c中,在linux3.14/kernel/printk/printk.c中):

void release_console_sem(void)

{

    unsigned long flags;

    unsigned long _con_start, _log_end;

    unsigned long wake_klogd = 0;

    for ( ; ; ) {

        spin_lock_irqsave(&logbuf_lock, flags);

        wake_klogd |= log_start - log_end;

        if (con_start == log_end)

            break;/* 没有需要打印的数据*/

        _con_start = con_start;

        _log_end = log_end;

        con_start = log_end;/* Flush */

        spin_unlock_irqrestore(&logbuf_lock, flags);

        //调用控制台driver的write函数写入到控制台

        call_console_drivers(_con_start, _log_end);

    }

    console_locked = 0;

    console_may_schedule = 0;

    up(&console_sem);

    spin_unlock_irqrestore(&logbuf_lock, flags);

    if (wake_klogd && !oops_in_progress && waitqueue_active(&log_wait))

        wake_up_interruptible(&log_wait);//唤醒在等待队列上的进程

}

函数_call_console_drivers将缓冲区中从start到end - 1的数据输出到控制台进行显示。在输出数据到控制台之前,它检查消息的日志级别。只有日志级别小于控制台日志级别console_loglevel的消 息,才能交给控制台驱动程序进行显示。

函数_call_console_drivers列出如下:

static void _call_console_drivers(unsigned long start,

                                  unsigned long end, int msg_log_level)

{

    //日志级别小于控制台日志级别的消息才能输出到控制台

    if ((msg_log_level < console_loglevel || ignore_loglevel) &&

            console_drivers && start != end) {

        if ((start & LOG_BUF_MASK) > (end & LOG_BUF_MASK)) {

            /* 调用控制台驱动程序的写操作函数 */

            __call_console_drivers(start & LOG_BUF_MASK,  log_buf_len);

            __call_console_drivers(0, end & LOG_BUF_MASK);

        } else {

            __call_console_drivers(start, end);

        }

    }

}

函数__call_console_drivers调用控制台驱动程序的写操作函数显示消息。其列出如下:

static void __call_console_drivers(unsigned long start, unsigned long end)

{

    struct console *con;

    for (con = console_drivers; con; con = con->next) {

        if ((con->flags & CON_ENABLED) && con->write &&

                (cpu_online(smp_processor_id()) ||

                 (con->flags & CON_ANYTIME)))

            con->write(con, &LOG_BUF(start), end - start); //调用驱动程序的写操作函数

    }

}

4、sys_syslog系统调用

系统调用sys_syslog根据参数type的命令执行相应的操作。参数type定义的命令列出如下:

0 -- 关闭日志,当前没实现。

1 -- 打开日志,当前没实现。

2 -- 从环形缓冲区读取日志消息。

3 -- 读取保留在环形缓冲区的所有消息。

4 -- 读取并清除保留在环形缓冲区的所有消息。

5 -- 清除环形缓冲区。

6 -- 关闭printk到控制台的打印。

7 -- 开启printk到控制台的打印。

8 -- 设置打印到控制台的消息的日志级别。

9 -- 返回日志缓冲区中没读取的字符数。

10 -- 返回日志缓冲区的大小。

sys_syslog函数列出如下(在linux2.6/kernel/printk.c中,在linux3.14/kernel/printk/printk.c中):

asmlinkage long sys_syslog(int type, char __user * buf, int len)

{

    return do_syslog(type, buf, len);

}

int do_syslog(int type, char __user *buf, int len)

{

    unsigned long i, j, limit, count;

    int do_clear = 0;

    char c;

    int error = 0;

    error = security_syslog(type);  //检查是否调用这个函数的权限

    if (error)

        return error;

    switch (type) {

    case 0:/* 关闭日志 */

        break;

    case 1:/* 打开日志*/

        break;

    case 2:/*读取日志信息*/

        error = -EINVAL;

        if (!buf || len < 0)

            goto out;

        error = 0;

        if (!len)

            goto out;

        if (!access_ok(VERIFY_WRITE, buf, len)) { //验证是否有写的权限

            error = -EFAULT;

            goto out;

        }

        //当log_start - log_end为0时,表示环形缓冲区无数据可读,把当前进程放入

        等待队列log_wait

                error = wait_event_interruptible(log_wait,  (log_start - log_end));

        if (error)

            goto out;

        i = 0;

        spin_lock_irq(&logbuf_lock);

        while (!error && (log_start != log_end) && i < len) {

            c = LOG_BUF(log_start); //从环形缓冲区得到读取位置log_start

            log_start++;

            spin_unlock_irq(&logbuf_lock);

            error = __put_user(c,buf); //将c地址的字符传递到用户空间的buf中

            buf++;

            i++;

            cond_resched();  //条件调度,让其他进程有运行时间

            spin_lock_irq(&logbuf_lock);

        }

        spin_unlock_irq(&logbuf_lock);

        if (!error)

            error = i;

        break;

    case 4:/* 读/清除上一次内核消息*/

        do_clear = 1;

        /* FALL THRU */

    case 3:/*读取上一次内核消息*/

        error = -EINVAL;

        if (!buf || len < 0) 

            goto out;

        error = 0;

        if (!len)  //读取长度为0

            goto out;

        if (!access_ok(VERIFY_WRITE, buf, len)) { //验证有写权限

            error = -EFAULT;

            goto out;

        }

        count = len;

        if (count > log_buf_len) 

            count = log_buf_len;

        spin_lock_irq(&logbuf_lock);

        if (count > logged_chars) // logged_chars是上次读/清除以来产生的日志字符数

            count = logged_chars;

        if (do_clear)

            logged_chars = 0;

        limit = log_end; 

        /* __put_user() 可以睡眠,当__put_user睡眠时,printk()可能覆盖写正在

拷贝到用户空间的消息,因此,这些消息被反方向拷贝,将buf覆盖部分的数据重写到buf的起始位置*/

        for (i = 0; i < count && !error; i++) { //读取count个字符

            j = limit-1-i;

            if (j + log_buf_len < log_end)

                break;

            c = LOG_BUF(j); //从环形缓冲区得到读取位置j

            spin_unlock_irq(&logbuf_lock);

            //将c位置的字符传递到用户空间的buf中,如果发生错误,将发生错误的c位置给error

            error = __put_user(c,&buf[count-1-i]); 

            cond_resched();

            spin_lock_irq(&logbuf_lock);

        }

        spin_unlock_irq(&logbuf_lock);

        if (error)

            break;

        error = i;

        if (i != count) { //表示__put_user没有拷贝完成

            int offset = count-error;

            /* 拷贝期间缓冲区溢出,纠正用户空间缓冲区*/

            for (i = 0; i < error; i++) {

                if (__get_user(c,&buf[i+offset]) ||

                        __put_user(c,&buf[i])) { //将覆盖部分的数据

                    重写到buf的起始位置

                                  error = -EFAULT;

                    break;

                }

                cond_resched();

            }

        }

        break;

    case 5:/* 清除环形缓冲区*/

        logged_chars = 0;

        break;

    case 6:/*关闭向控制台输出消息*/

        console_loglevel = minimum_console_loglevel;

        break;

    case 7:/*开启向控制台输出消息*/

        console_loglevel = default_console_loglevel;

        break;

    case 8:/* 设置打印到控制台的日志级别*/

        error = -EINVAL;

        if (len < 1 || len > 8)

            goto out;

        if (len < minimum_console_loglevel)

            len = minimum_console_loglevel;

        console_loglevel = len;

        error = 0;

        break;

    case 9:/* 得到日志消息所占缓冲区的大小*/

        error = log_end - log_start;

        break;

    case 10:/*返回环形缓冲区的大小*/

        error = log_buf_len;

        break;

    default:

        error = -EINVAL;

        break;

    }

out:

    return error;

}
时间: 2024-08-09 20:05:40

基于FS4412嵌入式系统移植(8) linux内核调试之printk的相关文章

基于FS4412嵌入式系统移植(6) glib库的交叉编译与移植

今天和大家分享一下glib库的交叉编译过程和如何对程序进行编译以及运行. glib库的移植资料比较少,比较零散,这里我就写一下亲自移植的过程,和大家分享一下.按照我的过程移植应该是不会有问题,如果遇到了问题可以留言联系我,一起讨论研究. 这里需要强调一下:libc.glibc.glib是不同的库! glibc和 libc 都是 Linux 下的 C 函数库,而glib是GTK+的基础库 libc 是 Linux 下的 ANSI C 函数库:glibc 是 Linux 下的 GUN C 函数库.

Linux 内核调试之 printk

问题描述:最近这两天再调试 platform 驱动,程序老是有点小问题,得不到自己想要的结果,突然意识到内核调试重要性,重新整理一下 printk 基本用法.内核通过 printk() 输出相关信息,在调用 printk() 函数时必须要指定日志级别. 1.printk 日志等级 在 include/linux/kernel.h 中定义了如下几个日志级别 <span style="font-family:Microsoft YaHei;font-size:12px;">#d

嵌入式系统移植三部曲 李炎朔

嵌入式系统移植三部曲                            李炎朔 09机应一班 学号0906041053 三部曲<bootloader的移植><linux的移植><根文件系统的移植> 一 bootloader的移植(1)安装skyeye-1.2.6_rc1 [[email protected] Desktop]# tar -xjvf skyeye-1.2.6_rc1.tar.bz2 -C ./[[email protected] Desktop]# c

嵌入式系统移植三部曲 吴素芬

计算机与信息工程学院  09级嵌入式  吴素芬 嵌入式系统移植三部曲 一.BootLoader的移植 二.linux的移植 三.根文件系统的移植 一.准备工作 (1).创建交叉编译环境 1.[[email protected] opt]# ll arm-linux-* -rwxr-xr-x 1 root root 36273634 06-13 12:21 arm-linux-gcc-2.95.3.tar.bz2 -rwxr-xr-x 1 root root 42745480 06-13 12:2

Linux Kernel - Debug Guide (Linux内核调试指南 )

http://blog.csdn.net/blizmax6/article/details/6747601 linux内核调试指南 一些前言 作者前言 知识从哪里来 为什么撰写本文档 为什么需要汇编级调试 ***第一部分:基础知识*** 总纲:内核世界的陷阱 源码阅读的陷阱 代码调试的陷阱 原理理解的陷阱 建立调试环境 发行版的选择和安装 安装交叉编译工具 bin工具集的使用 qemu的使用 initrd.img的原理与制作 x86虚拟调试环境的建立 arm虚拟调试环境的建立 arm开发板调试环

linux内核调试指南

linux内核调试指南 一些前言 作者前言 知识从哪里来 为什么撰写本文档 为什么需要汇编级调试 ***第一部分:基础知识*** 总纲:内核世界的陷阱 源码阅读的陷阱 代码调试的陷阱 原理理解的陷阱 建立调试环境 发行版的选择和安装 安装交叉编译工具 bin工具集的使用 qemu的使用 initrd.img的原理与制作 x86虚拟调试环境的建立 arm虚拟调试环境的建立 arm开发板调试环境的建立 gdb基础 基本命令 gdb之gui gdb技巧 gdb宏 汇编基础--X86篇 用户手册 AT&

Linux内核调试的方式以及工具集锦

CSDN GitHub Linux内核调试的方式以及工具集锦 LDD-LinuxDeviceDrivers/study/debug 本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可, 转载请注明出处, 谢谢合作 因本人技术水平和知识面有限, 内容如有纰漏或者需要修正的地方, 欢迎大家指正, 也欢迎大家提供一些其他好的调试工具以供收录, 鄙人在此谢谢啦 "调试难度本来就是写代码的两倍. 因此, 如果你写代码的时候聪明用尽, 根据定义, 你就没有能耐去调试它了.&qu

Linux内核调试方法总结

一  调试前的准备 二  内核中的bug 三  内核调试配置选项 1  内核配置 2  调试原子操作 四  引发bug并打印信息 1  BUG()和BUG_ON() 2  dump_stack() 五  printk() 1  printk函数的健壮性 2  printk函数脆弱之处 3  LOG等级 4  记录缓冲区 5  syslogd/klogd 6  dmesg 7 注意 8 内核printk和日志系统的总体结构 9  动态调试 六  内存调试工具 1  MEMWATCH 2  YAMD

Linux内核调试技术——jprobe使用与实现

前一篇博文介绍了kprobes的原理与kprobe的使用与实现方式,本文介绍kprobes中的第二种探测技术jprobe,它基于kprobe实现,不能在函数的任意位置插入探测点,只能在函数的入口处探测,一般用于监测函数的入参值.本文首先通过一个简单的示例介绍jprobe的使用方式,然后通过源码详细分析jprobe的实现流程. 内核源码:Linux-4.1.x 实验环境:Fedora25(x86_64).树莓派1b 1.jprobe使用实例 使用jprobe探测函数的入参值,需要编写内核模块.同k