linux设备驱动四(调试技术)

内核中的调试支持

安装自己的内核,发行版内核会关闭映像性能的调试功能,kernel hacking的配置:

  • CONFIG_DEBUG_KERNEL,总开关,使其他调试选项可用
  • CONFIG_DEBUG_SLAB,可以检查许多内存溢出及忘记初始化的错误,分配给每个字节设置为0xa5,释放设置为0x6b
  • CONFIG_DEBUG_PAGEALLOC,可以快速定位内存损坏的位置
  • CONFIG_DEBUG_SPINLOCK,捕获未初始化,或者重复解锁的错误
  • CONFIG_DEBUG_SPINLOCK_SLEEP,检查持有自旋锁时休眠企图,可能引起休眠的函数
  • CONFIG_INIT_DEBUG,检查初始化完成之后对于初始化的内存空间的访问企图
  • CONFIG_DEBUG_INFO,内核构造包含完整的调试信息。计划使用gdb还应该打开CONFIG_FRAME_POINTER
  • CONFIG_MAGIC_SYSRQ,magic sysrq按键
  • CONFIG_DEBUG_STACKOVERFLOW
  • CONFIG_DEBUG_STACK_USAGE,跟踪内核栈的溢出,第一个选项明确栈溢出检查,第二个通过sysrq输出统计信息
  • CONFIG_KALLSYMS,用于调试上下文,没有此符号,oops清单只给出十六进制内核反向跟踪信息
  • CONFIG_IKCONFIG
  • CONFIG_IKCONFIG_PROC,内核配置状态包含在内核中,并通过proc访问
  • CONFIG_ACPI_DEBUG,ACPI(advanced configuration and power interface,高级配置和电源接口)
  • CONFIG_DEBUG_DRIVER,驱动程序核心中的调试信息
  • CONFIG_SCSI_CONSTANTS,打开详细的SCSI错误信息
  • CONFIG_INPUT_EVBUG,会打开对输入事件的详细记录
  • CONFIG_PROFILING,剖析通常用于系统性能的调节

通过打印调试

printk,根据级别或优先级锁表示的严重程度对消息进行分类。使用宏来标示日志界别,宏会展开为一个字符串,编译时和消息文本拼接在一起,它们之间不需要逗号分割

  • KERN_EMERG,系统崩溃前提示消息
  • KERN_ALERT,需要立即采取动作的情况
  • KERN_ERR,用于报告错误,驱动程序用它来报告来自硬件的问题
  • KERN_WARNING,可能出现的问题进行警告,不会对系统造成严重问题
  • KERN_NOTICE,与安全相关的情况使用这个级别
  • KERN_INFO,驱动程序在启动时打印找到的硬件信息
  • KERN_DEBUG,调试信息
  • 每个字符串代表0-7中的一个数值,数值越小优先级越高
  • 未指定时默认级别是DEFAULT_MESSAGE_LOGLEVEL,2.6.10是KERN_WARNING
  • 日志优先级小于console_loglevel的值时,会打印的控制台上,每次输出一行,不以newline字符结尾则不输出
  • 如果同时运行了klogd和syslogd,则无论console_loglevel为何值,消息都将追加到/var/log/messages中
  • 如果klogd没有运行,消息不会传递到用户空间,只能通过dmesg命令从/proc/kmsg文件读取
  • console_loglevel初始值是DEFAULT_CONSOLE_LOGLEVEL,可以通过sys_syslog系统调用修改,或者调用klogd时指定-c开关来修改(需要先kill klogd)
  • 新值设置为1-8,如果设置为1,则只有0级别(KERN_KMERG)消息能到达控制台
  • 可以通过/proc/sys/kernel/printk文件读取和修改控制台的日志级别,包括4个整数值分别是:当前日志级别,默认消息级别,最小允许的日志级别,及引导时默认日志级别,写入单个数值时,修改当前日志级别

重定向控制台信息

  • 默认情况下“控制台”就是当前的虚拟终端,可以在任何一个控制台设备上调用ioctl(TIOCLINUX)来指定接受消息的其他虚拟终端
  • TIOCLINUX,这个命令可以完成一些特定的linux功能。使用TIOCLINUX时,需要传给它一个指向字节数组的指针参数,数组的第一个字节指定所请求子命令的编号。

消息如何被记录

  • printk函数将消息写到一个长度为__LOG_BUF_LEN字节的循环缓冲区
  • 然后唤醒睡眠在syslog系统调用上的进程,或者在读取/proc/kmsg的进程,dmesg可以不刷新缓冲区
  • 停止klogd之后读取/proc/kmsg文件会阻塞进程,如果已有klogd或其他进程读取时,不能直接读取该文件,避免竞争
  • 缓冲区写满会覆盖,可能丢失旧数据
  • klogd读取内核消息发送到syslogd
  • syslogd根据/etc/syslog.conf找出处理这些数据的方法。
  • syslogd根据功能和优先级对消息进行区分。
  • 内核消息由LOG_KERN工具记录,并且与printk中对应的优先级记录,如果没有运行klogd,数据将保留在缓冲区
  • klogd -(file)可以只是klogd将消息保存到某个特定文件

开启及关闭消息

通过将printk定义为一个宏,使用该宏来打印消息,这样可以开启和关闭消息
在这里插入图片描述
在这里插入图片描述

速度限制

  • 信息输出到慢速控制台,过高的信息输出速度回导致系统变慢
  • 过多日志很难发现什么问题,正式版本不应该在正常情况下打印日志
  • 启动停止应该有日志,但是要注意那些不断重试的情况
  • 打印一条可能重复的信息之前,调用int printk_ratelimit(void),速度超过一个阈值则返回零
  • 可以修改/proc/sys/kernel/printk_ratelimit(重新打开消息之前等待的秒数)以及/proc/sys/kernel/printk_ratelimit_burst(速度限制之前可以接受的消息条数)

打印设备编号

  • int print_dev_t(char *buffer, dev_t dev); 返回打印的字符数,传入的缓冲区要能包含设备号(64位),缓冲区大小应该至少20字节
  • char *format_dev_t(char *buffer, dev_t dev); 返回缓冲区

通过查询调试

  • 大量使用printk会显著降低系统性能syslogd试图把每件事都记录到磁盘上
  • 在/etc/syslogd.conf中日志文件名字前加一个减号可以避免实时刷新磁盘
  • 获取信息最好的方法是需要的时候才去查询

使用/proc文件系统

  • int (*read_proc)(char *page, char **start, off_t offset, int count ,int eof, void*data ); start实际数据写到内存页的哪儿位置,如果把start设置为一个小的整数,调用程序可以利用它来增加filp->f_pos的值
  • struct proc_dir_entry *create_proc_read_entry(const char *name, mode_t mode, struct proc_dir_entry*, read_proc_t *read_proc, void *data);
  • remove_proc_entry

seq_file接口

必须实现四个迭代器对象:

  • void *start(struct seq_file *sfile, loff_t *pos); pos表明读取位置,位置通常解释为指向序列中下一个项目的游标,函数返回值供show使用

  • void *next(struct seq_file *sfile, void*v, loff_t *pos); v是之前start或next返回的迭代器,pos的值应该增加

  • void stop(struct seq_file sfile, void v); 内核使用迭代器之后,调用stop方法做清楚工作

  • int show(struct seq_file *sfile, void*v);
    其他一些seq_file输出函数,在show函数中被调用:

  • int seq_printf(struct seq_file *sfile, const char *fmt, …); 缓冲区写满,返回非零值,输出被丢弃

  • int seq_putc(struct seq_file *sfile, char c);

  • int seq_puts(struct seq_file *sfile, const char *s);

  • int seq_escape(struct seq_file *sfile, const char*s, const char *esc); s中包含了esc中某个字符,该泽肤以八进制形式打印。

  • int seq_path(struct seq_file *sfile, struct vfsmount *m, struct dentry *dentry, char *esc);输出与某个目录项关联的文件名。

  • 要与/proc中的某个文件链接起来,首先填充一个seq_operations结构:
    static struct seq_operations {
    .start
    .next
    .stop
    .show
    }

    其次创建file_operations,我们只需要实现open方法,用来连接到之前创建seq_operations,而其他的函数,使用默认的seq_read,seq_lseek,seq_release
    static int proc_open(struct inode *inode, struct file *file)
    {
    return seq_open(file, *seq_operations);
    }

    最后创建proc下的文件
    struct proc_dir_entry create_proc_entry(const char name, mode_t mode, struct proc_dir_entry *parent);
    如果包含大量输出行,建议使用seq_file接口,(而且最新的版本,已经不支持旧的proc方式)

ioctl方法

优点
获取数据比proc快得多,二进制比文本有效
不需要分割数据为不超过一个页的片段

缺点
需要一个程序来调用ioctl

通过监视调试

strace,可以显示程序发出的所有系统调用。不仅可以显示调用,而且还能显示调用参数以及用符号形式表示的返回值。
-t 显示调用发生的时间
-T 显示调用话费的时间
-e 限定被跟踪的调用类型
-o 将输出重定向到一个文件

调试系统故障

oops消息
引用一个非法指针,页表无法映射到物理地址,处理器向操作系统发出page fault。如果地址非法,内核无法换入page in 缺失页面,就会产生oops
通过栈清单确定局部变量和函数参数的值
在这里插入图片描述
栈顶部的ffffffff是导致故障的字符串,用户空间的默认栈自0xc00000000乡下,因此0xbfffda70可能使用空间的栈地址,该地址调用连上重复乡下传递。x86架构上,内核空间起始于0xc00000000,大于该地址的几乎肯定是内核空间地址等等。

系统挂起

通过在一些关键点上插入schedule调用可以防止死循环。当驱动程序因为错误陷入死循环时,借助schedule调用杀死这个进程。或者加入一些打印信息
显示器上时钟或者系统复核表任然在更新说明系统任然在工作
sysrq,通过ALT和SysRq组合键激活,通过SysRq和第三个按键,内核执行不同动作:

  • r 关闭键盘的raw模式,崩溃的应用程序可能让键盘处于奇怪状态
  • k 激活SAK功能,SAK将杀死当前控制台上所有进程,留下干净中断
  • s 对所有磁盘进行紧急同步
  • u 尝试以制度模式重新挂载所有磁盘
  • b 立即重启系统,先要执行同步并重新挂载磁盘
  • p 打印当前处理器寄存器信息
  • t 打印当前任务列表
  • m 打印内存信息

echo 0 > /proc/sys/kernel/sysrq 禁用SysRq功能

可以向/proc/sysrq-trigger写入字符来触发相应的SysRq动作,这个入口点始终可用,即使SysRq是禁止的。

构造一个打开补习功能的内核,并通过引导命令行参数profile=2引导该内核,利用readprofile工具重置剖析计数器,让驱动程序进入死循环,经过一段时间再次使用readprofile即可观察浪费CPU资源的内核位置。复现故障时要保护好数据,以只读挂载硬盘。

调试器和相关工具

调试器非常耗时,应尽量避免

使用gdb

gdb /usr/src/linux/vmlinux(正站在运行内核的未压缩映像文件) /proc/kcore(内核在内存中的核心映像)
kcore用来按照core文件的格式表示内核的“可执行文件”;由于它要表示对应于所有物理内存的整个内核地址空间,所以是一个非常巨大的文件。
使用gdb打印的数据,是正在运行内核数据的一个缓存,要保持更新可以执行core-file/proc/kcore命令。不过缓存的是引用过的,第一次访问总是访问到最新的数据
gdb不能修改内核数据,不能设置断点或观察点,也不能但不跟踪
gdb要起作用,必须打开CONFIG_DEBUG_INFO
模块不是传递给gdb的vmlinux映像的一部分,需要另外处理
模块有很多代码段,和调试相关的只有三个:
.text 模块的可执行代码
.bss
.data 这两个代码段保存模块的变量,编译时未初始化的在.bss中,其他初始化的在.data中
gdb要能处理模块,必须告诉调试器,模块代码段的具体位置,可以通过/sysfs/module获得,在/sys/module/module_name/sections目录中
通过add-symbol-file加入模块代码段地址信息

print *(address) 可以传入十六位地址,输出对应文件以及代码行数

kdb内核调试器

准备工作

oss.sgi.com上以非正式补丁形式提供
获得补丁
patch操作
重编内核
仅可用于IA-32(x86)系统

启动调试

在控制台按下Pause(或Break)启动调试
当内核发生oops,或到达某个断点也会启动调试

注意事项:

kdb运行时,内核所做的每件事都会停下来,激活kdb时,不应运行其他东西
如果要使用kdb,最好在启动时进入单用户模式

一些常用命令:

bp,设置断点(gdb中b)
go,开始运行(gdb中人r)
bt,堆栈跟踪信息
mds,用来查看数据,数据显示是以16进制地址显示的,没有符号名,所以需要自己转换
mm,可以修改内存数据

kgdb补丁

gdb和kdb均无法提供类似应用程序开发人员使用的环境
也是通过补丁的方式
kgdb将运行调试内核的系统和运行调试器的系统隔离开,通过串口线连接
以支持通过局域网通信,打开以太网模式,在引导时设置kgdboe参数,指出命令来源IP

用户模式的linux虚拟机

User-mode linux, UML,可以是linux内核称为一个用户模式的进程。
优点:可以很容易利用gdb进行调试。
缺点:无法访问主机硬件。无法调试正真和硬件打交道的驱动程序。

linux跟踪工具包

linux trace toolkit,lTT是一个内核补丁,可以跟踪时间信息,合理建立在一段制定时间内所发生时间的完整描述,可用于测试,或补货性能方面的问题。

动态探测

Dynamic Probes,DProbes,可以在系统任何地方防止一个探针,可以是用户空间也可以是内核空间。探针是一些特殊代码,,到达给定点时,开始执行。
内核需要编译进这个功能

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值