热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【原创】《Linux设备驱动开发》学习笔记:逐步掌握调试技巧

【原创】《Linux设备驱动程序》学习之循序渐进---调试技术第四章---调试技术内核编程带有它自己的,独特的调试挑战性.内核代码无法轻易地在一个调试器下运行,也无法轻易的被跟踪,因为


【原创】《Linux设备驱动程序》学习之循序渐进 --- 调试技术


第四章 --- 调试技术

内核编程带有它自己的, 独特的调试挑战性. 内核代码无法轻易地在一个调试器下运行, 也无法轻易的被跟踪, 因为它是一套没有与特定进程相关连的功能的集合. 内核代码错误也特别难以重现, 它们会牵连整个系统与它们一起失效, 从而
破坏了大量的能用来追踪错误的证据。一句话,内核编程的调试相对于应用程序来说比较困难。

我们建议你建立并安装你自己的内核, 而不是运行来自你的发布商的现成的内核. 运行你自己的内核的最充分的理由之一是内核开发者已经在内核自身中构建了多个调试特性. 但是这些特性能产生额外的输出并降低性能, 因此发布商的产品内核中往往不会使能它们.除了另外指出的, 所有的这些选项都在 "kernel hacking" 菜单, 注意有些选项不是所有体系都支持.  

最普通的调试技术是监视, 在应用程序编程当中是通过在合适的地方调用 printf 来实现. 在你调试内核代码时, 你可以通过 printk 来达到这个目的.

printk 

printk 允许你根据消息的严重程度对其分类, 通过附加不同的记录级别或者优先级在消息上. 你常常用一个宏定义来指示记录级别.

有 8 种可能的记录字串, 在头文件 里定义; 我们按照严重性递减的顺序列出它们: 
KERN_EMERG 
用于紧急消息, 常常是那些崩溃前的消息. 
KERN_ALERT 
需要立刻动作的情形. 
KERN_CRIT 
严重情况, 常常与严重的硬件或者软件失效有关. 
KERN_ERR 
用来报告错误情况; 设备驱动常常使用 KERN_ERR 来报告硬件故障. 
KERN_WARNING 
有问题的情况的警告, 这些情况自己不会引起系统的严重问题. 
KERN_NOTICE 
正常情况, 但是仍然值得注意. 在这个级别一些安全相关的情况会报告. 
KERN_INFO 
信息型消息. 在这个级别, 很多驱动在启动时打印它们发现的硬件的信息. 
KERN_DEBUG 
用作调试消息. 

每个字串( 在宏定义扩展里 )代表一个在角括号中的整数. 整数的范围从 0 到 7, 越小的数表示越大的优先级. 一条没有指定优先级的 printk 语句缺省是 DEFAULT_MESSAGE_LOGLEVEL, 在kernel/printk.c 里指定作为一个整数. 

printk 函数将消息写入一个 __LOG_BUF_LEN 字节长的环形缓存, 长度值从 4 KB 到 1 MB, 由配置内核时选择. 这个函数接着唤醒任何在等待消息的进程, 就是说, 任何在系统调用中睡眠或者在读取 /proc/kmsg 的进程. 

dmesg 命令可用来查看缓存的内容, 不会冲掉它; 实际上, 这个命令将缓存区的整个内容返回给 stdout, 不管它是否已经被读过.

Linux 对于消息的解决方法的另一个特性是, printk 可以从任何地方调用, 甚至从一个中断处理里面, 没有限制能打印多少数据. 唯一的缺点是可能丢失一些数据.

如果你要避免你的系统被来自你的驱动的监视消息击垮, 你或者给 klogd 指定一个 -f (文件) 选项来指示它保存消息到一个特定的文件, 或者定制 /etc/syslog.conf 来适应你的要求. 但是另外一种可能性是采用粗暴的方式: 杀掉 klogd 和详细地打印消息在一个没有用到的虚拟终端上, 或者从一个没有用到的 xterm 上发出命令 cat /proc/kmsg. 

重定向到控制台

Linux 在控制台记录策略上允许一些灵活性, 它允许你发送消息到一个指定的虚拟控制台(如果你的控制台使用的是文本屏幕). 缺省地, 这个"控制台"是当前虚拟终端. 为了选择一个不同地虚拟终端来接收消息, 你可对任何控制台设备调用 ioctl(TIOCLINUX). 程序setconsole, 可以用来选择哪个控制台接收内核消息; 它必须由超级用户运行, 可以从 misc-progs 目录得到. 

开启及关闭消息

这里我们展示一种编码 printk 调用的方法, 你可以单独或全局地打开或关闭它们; 这个技术依靠定义一个宏, 在你想使用它时就转变成一个 printk (或者 printf)调用. 
  每个 printk 语句可以打开或关闭, 通过去除或添加单个字符到宏定义的名子. 
  所有消息可以马上关闭, 通过在编译前改变 CFLAGS 变量的值. 
  同一个 print 语句可以在内核代码和用户级代码中使用, 因此对于格外的消息, 驱动和测试程序能以同样的方式被管理. 
下面的代码片断实现了这些特性, 直接来自头文件 scull.h: 

#undef PDEBUG /* undef it, just in case */ 
#ifdef SCULL_DEBUG
# ifdef __KERNEL__

/* This one if debugging is on, and kernel space */
# define PDEBUG(fmt, args...) printk( KERN_DEBUG "scull: " fmt, ## args)
# else

/* This one for user space */
# define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args)
# endif
#else
# define PDEBUG(fmt, args...) /* not debugging: nothing */
#endif

#undef PDEBUGG #define PDEBUGG(fmt, args...) /* nothing: it's a placeholder */
为进一步简化过程, 添加下面的行到你的 makfile 里:

# 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 # "-O" is needed to expand inlines
else
DEBFLAGS = -O2
endif

CFLAGS += $(DEBFLAGS)

速率限制

在很多情况下, 最好的做法是设置一个标志说, "我已经抱怨过这个了", 并不打印任何后来的消息只要这个标志设置着. 然而, 有几个理由偶尔发出一个"设备还是坏的"的提示. 内核已经提供了一个函数帮助这个情况:  
int printk_ratelimit(void);  
这个函数应当在你认为打印一个可能会常常重复的消息之前调用. 如果这个函数返回非零值, 继续打印你的消息, 否则跳过它. 这样, 典型的调用如这样:  
if (printk_ratelimit()) 
    printk(KERN_NOTICE "The printer is still on fire\n"); 
printk_ratelimit 通过跟踪多少消息发向控制台而工作. 当输出级别超过一个限度, printk_ratelimit 开始返回 0 并使消息被扔掉. 
printk_ratelimit 的行为可以通过修改 /proc/sys/kern/printk_ratelimit( 在重新使能消息前等待的秒数 ) 和 /proc/sys/kernel/printk_ratelimit_burst(限速前可接收的消息数)来定制. 

打印设备号

偶尔地, 当从一个驱动打印消息, 你会想打印与感兴趣的硬件相关联的设备号. 打印主次编号不是特别难, 但是, 为一致性考虑, 内核提供了一些实用的宏定义( 在 中定义)用于这个目的:  
int print_dev_t(char *buffer, dev_t dev);  
char *format_dev_t(char *buffer, dev_t dev); 
两个宏定义都将设备号编码进给定的缓冲区; 唯一的区别是 print_dev_t 返回打印的字符数, 而 format_dev_t 返回缓存区; 因此, 它可以直接用作 printk 调用的参数, 但是必须记住 printk 只有提供一个结尾的新行才会刷行. 缓冲区应当足够大以存放一个设备号; 如果 64 位编号在以后的内核发行中明显可能, 这个缓冲区应当可能至少是 20 字节长. 

通过查询调试

大量使用 printk 能够显著地拖慢系统, 即便你降低 cosole_loglevel 来避免加载控制台设备, 因为 syslogd 会不停地同步它的输出文件; 因此, 要打印的每一行都引起一次磁盘操作.然而, 你不想只是为了调试信息的原因而拖慢你的系统. 可以在出现于 /etc/syslogd.conf 中的你的日志文件名前加一个连字号来解决这个问题。

使用 /proc 文件系统

/proc文件系统是一个特殊的软件创建的文件系统, 内核用来输出消息到外界. /proc 下的每个文件都绑到一个内核函数上, 当文件被读的时候即时产生文件内容. 我们已经见到一些这样的文件起作用; 例如, /proc/modules, 常常返回当前已加载的模块列表. 

seq_file 接口 

如我们上面提到的, 在 /proc 下的大文件的实现有点麻烦. 一直以来, /proc 方法因为当输出数量变大时的错误实现变得声名狼藉. 为使内核开发者活得轻松些, 添加了 seq_file 接口. 这个接口提供了简单的一套函数来实现大内核虚拟文件. 我们建议使用 seq_file , 来实现包含多个非常小数目的输出行数的文件.

ioctl 方法 

我们在第 6 章展示给你如何使用, 是一个系统调用, 作用于一个文件描述符; 它接收一个确定要进行的命令的数字和(可选地)另一个参数, 常常是一个指针. 

通过监视调试

有几个方法来监视用户空间程序运行. 你可以运行一个调试器来单步过它的函数, 增加打印语句, 或者在 strace 下运行程序. 这里, 我们将讨论最后一个技术。strace 命令是一个有力工具, 显示所有的用户空间程序发出的系统调用. 它不仅显示调用, 还以符号形式显示调用的参数和返回值. 当一个系统调用失败, 错误的符号值(例如, ENOMEM)和对应的字串(Out of memory) 都显示. strace 有很多命令行选项; 其中最有用的是 -t 来显示每个调用执行的时间, -T 来显示调用中花费的时间, -e 来限制被跟踪调用的类型, 以及-o 来重定向输出到一个文件. 缺省地, strace 打印调用信息到 stderr.

调试系统故障 

即便你已使用了所有的监视和调试技术, 有时故障还留在驱动里, 当驱动执行时系统出错. 当发生这个时, 能够收集尽可能多的信息来解决问题是重要的. 注意"故障"不意味着"崩溃". Linux 代码是足够健壮地响应大部分错误:一个故障常常导致当前进程崩溃, 系统仍会继续运行。

大部分 bug 是对 NULL 指针取值或者使用其他不正确指针值. 此类 bug 通常的输出是一个 oops 消息.

使用 gdb 

gdb 对于看系统内部是非常有用. 在这个级别精通调试器的使用要求对 gdb 命令有信心, 需要理解目标平台的汇编代码, 以及对应源码和优化的汇编码的能力. 
调试器必须把内核作为一个应用程序来调用. 除了指定内核映象的文件名之外, 你需要在命令行提供一个核心文件的名子. 对于一个运行的内核, 核心文件是内核核心映象, /proc/kcore. 一个典型的 gdb 调用看来如下:  
gdb /usr/src/linux/vmlinux /proc/kcore  
第一个参数是非压缩的 ELF 内核可执行文件的名子, 不是 zImage 或者 bzImage 或者给启动环境特别编译的任何东东. gdb 命令行的第二个参数是核心文件的名子. 

kdb 内核调试器 

许多读者可能奇怪为什么内核没有建立更多高级的调试特性在里面.答案, 非常简单, 是 Linus 不相信交互式的调试器. 他担心它们会导致不好的修改, 这些修改给问题打了补丁而不是找到问题的真正原因. 因此, 没有内嵌的调试器. 其他内核开发者, 但是, 见到了交互式调试工具的一个临时使用. 一个这样的工具是 kdb 内嵌式内核调试器, 作为来自 oss.sgi.com 的一个非官方补丁. 要使用 kdb, 你必须获得这个补丁(确认获得一个匹配你的内核版本的版本), 应用它, 重建并重安装内核.

原文链接:



推荐阅读
  • 1:有如下一段程序:packagea.b.c;publicclassTest{privatestaticinti0;publicintgetNext(){return ... [详细]
  • golang常用库:配置文件解析库/管理工具viper使用
    golang常用库:配置文件解析库管理工具-viper使用-一、viper简介viper配置管理解析库,是由大神SteveFrancia开发,他在google领导着golang的 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 本文详细探讨了Java中的24种设计模式及其应用,并介绍了七大面向对象设计原则。通过创建型、结构型和行为型模式的分类,帮助开发者更好地理解和应用这些模式,提升代码质量和可维护性。 ... [详细]
  • 深入理解 SQL 视图、存储过程与事务
    本文详细介绍了SQL中的视图、存储过程和事务的概念及应用。视图为用户提供了一种灵活的数据查询方式,存储过程则封装了复杂的SQL逻辑,而事务确保了数据库操作的完整性和一致性。 ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • 本文介绍如何解决在 IIS 环境下 PHP 页面无法找到的问题。主要步骤包括配置 Internet 信息服务管理器中的 ISAPI 扩展和 Active Server Pages 设置,确保 PHP 脚本能够正常运行。 ... [详细]
  • 本文将介绍如何编写一些有趣的VBScript脚本,这些脚本可以在朋友之间进行无害的恶作剧。通过简单的代码示例,帮助您了解VBScript的基本语法和功能。 ... [详细]
  • 本文详细介绍了如何在Linux系统上安装和配置Smokeping,以实现对网络链路质量的实时监控。通过详细的步骤和必要的依赖包安装,确保用户能够顺利完成部署并优化其网络性能监控。 ... [详细]
  • 1.如何在运行状态查看源代码?查看函数的源代码,我们通常会使用IDE来完成。比如在PyCharm中,你可以Ctrl+鼠标点击进入函数的源代码。那如果没有IDE呢?当我们想使用一个函 ... [详细]
  • 数据管理权威指南:《DAMA-DMBOK2 数据管理知识体系》
    本书提供了全面的数据管理职能、术语和最佳实践方法的标准行业解释,构建了数据管理的总体框架,为数据管理的发展奠定了坚实的理论基础。适合各类数据管理专业人士和相关领域的从业人员。 ... [详细]
  • 本文详细介绍了 Dockerfile 的编写方法及其在网络配置中的应用,涵盖基础指令、镜像构建与发布流程,并深入探讨了 Docker 的默认网络、容器互联及自定义网络的实现。 ... [详细]
  • 数据库内核开发入门 | 搭建研发环境的初步指南
    本课程将带你从零开始,逐步掌握数据库内核开发的基础知识和实践技能,重点介绍如何搭建OceanBase的开发环境。 ... [详细]
  • DNN Community 和 Professional 版本的主要差异
    本文详细解析了 DotNetNuke (DNN) 的两种主要版本:Community 和 Professional。通过对比两者的功能和附加组件,帮助用户选择最适合其需求的版本。 ... [详细]
  • Python自动化处理:从Word文档提取内容并生成带水印的PDF
    本文介绍如何利用Python实现从特定网站下载Word文档,去除水印并添加自定义水印,最终将文档转换为PDF格式。该方法适用于批量处理和自动化需求。 ... [详细]
author-avatar
黑鸽子
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有