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

ldd3学习笔记:调试技术

1.内核中支持的调试选项打开:kernelhacking菜单

1.内核中支持的调试选项打开:kernel hacking菜单

CONFIG_DEBUG_KERNEL:使其他调试选项可用; 但不激活任何特性.

CONFIG_DEBUG_DRIVER:在"Device drivers"下 打开驱动核心的调试信息,

CONFIG_INPUT_EVBUG:在"Device drivers/Input device support"下打开输入事件的详细日志.

2.prink打印调试:

1). printk函数中能够指定优先级,在头文件 里定义了0-7共8个级别的输出优先级,数字越小,优先级越高;未指定优先级的prink采用默认级别DEFAULT_MESSAGE_LOGLEVEL, 这个宏在kernel/printk.c中定义为一个整数。

#define DEFAULT_MESSAGE_LOGLEVEL 4 /* KERN_WARNING */

  头文档linux/printk.h中宏定义了8个级别,0-8从高到低分别是:
    KERN_EMERG, KERNEL_ALERT, KERN_CRIT, KERN_ERR, KERN_WARNING, KERN_NOTICE, KERN_INFO, KERN_DEBUG
2). 根据日志级别,内核可能会将消息打印到当前控制台上。当优先级小于console_loglevel时,消息才能显示出来。控制台输出级别 console_loglevel 初始化成 DEFAULT_CONSOLE_LOGLEVEL。
  a.能够使用系统调用sys_syslog或klogd -c来修改console_loglevel值。注意要改变当前值, 你必须先杀掉 klogd, 接着使用 -c 选项重启它.
  b.也能够直接通过文档 /proc/sys/kernel/printk 读写控制台记录级别(这个文档包含4个整数值,前两个表示系统当前的优先级和缺省优先级), 指定一个整数在 1 和 8 之间, 包含 1 和 8. 如果它设为 1, 只有 0 级消息( KERN_EMERG )到达控制台; 如果它设为 8, 所有消息, 包括调试消息, 都显示.

例如, 你可以使所有内核消息出现在控制台, 通过简单地输入:

#echo 7 > /proc/sys/kernel/printk

#cat /proc/sys/kernel/printk  

7 4 1 7  

4个整数值分别是:当前的loglevel、默认loglevel、最小允许的loglevel、引导时的默认loglevel。

  c.也能够指定显示在其他控制台,通过调用ioctl(TIOCLINUX)或shell命令setconsole来配置。在 setconsole 里, 使用子命令 11, 下一个字节(存于 bytes[1])指定虚拟控制台.
     使用特殊的 ioctl 命令 TIOCLINUX来设定控制台设备号,(TIOCLINUX在drivers/char/tty_io.c 里定义):

char bytes[2] = {11,3}; /* 11 is the TIOCLINUX cmd number , 3 is the the chosen console number */ 

ioctl(STDIN_FILENO, TIOCLINUX, bytes); /* use stdin */

  d.如果同时运行了klogd和syslogd,则无论console_loglevel为何值,内核消息都将追加到/var/log/messages中。如果klogd没有运行,这些消息就不会传递到用户空间,此时只能查看/proc/kmsg文件(使用dmesg命令轻松做到)。

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

3)消息如何被记录:

printk函数将消息写到一个长度为__LOG_BUF_LEN字节的环形缓冲区,可在配置内核时为__LOG_BUF_LEN指定为4KB~1MB之间。

如果循环缓冲区填满了,printk就绕回缓冲区的开始出填写新的数据,将覆盖最早的数据。

对/proc/kmsg进行读操作,日志缓冲区被读取的数据就不再保留。

syslog系统调用能通过选项返回日志数据并保留数据。

dmesg命令可在不刷新缓冲区数据的情况下获得缓冲区内容。这个命令将缓存区的整个内容返回给 stdout, 不管它是否已经被读过.

 klogd 进程运行, 它获取内核消息并分发给 syslogd, syslogd 接着检查 /etc/syslog.conf 来找出如何处理它们.给 klogd 指定一个 -f (文件) 选项来指示它保存消息到一个特定的文件。

todo:klogd, syslogd和dmesg命令详解

4) 可通过预处理指令开启和关闭调试信息

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

int printk_ratelimit(void);  当输出级别超过一个限度, printk_ratelimit 开始返回 0 并使消息被扔掉.

应用例子:

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的行为。

3.查询调试

查询系统的三种方法:在 /proc 文件系统下创建文件; 使用 ioctl 驱动方法;借助 sysfs 输出属性(驱动模型,14章介绍)

方法一:在 /proc 文件系统下创建文件

1) /proc 文件系统是动态的, 因此你的模块可以在任何时候添加或去除条目.大部分时间, /proc 条目是只读的文件.

2)所有使用 /proc 的模块应当包含 来定义正确的函数.

3)创建一个只读 /proc 文件

int (*read_proc)(char *page, char **start, off_t offset, int count, int *eof, void *data);

连接它到 /proc 层次中的一个入口项. 使用一个 creat_proc_read_entry 调用,可以在目录base(NULL 时默认为在 /proc 根下创建 )下创建一个名为name的文件。

struct proc_dir_entry *create_proc_read_entry(const char *name,mode_t mode, struct proc_dir_entry *base, read_proc_t *read_proc, void *data);

a

模块卸载后去除/proc 中的入口:

remove_proc_entry("scullmem", NULL /* parent dir */);

注意:内核不会检查名字是否已经注册, 如果用同样的名字注册两个入口,在存取或移出入口时都是不能区分的。

4)采用seq_file 接口来创建大内核虚拟文件.

包含

接着你必须创建 4 个 iterator 方法, 称为 start, next, stop, 和 show.

void *start(struct seq_file *sfile, loff_t *pos);

void *next(struct seq_file *sfile, void *v, loff_t *pos); //next 函数应当移动 iterator 到下一个位置

void stop(struct seq_file *sfile, void *v); //当内核处理完 iterator, 它调用 stop 来清理

int show(struct seq_file *sfile, void *v); //内核调用 show 方法来真正输出有用的东西给用户空间,这个方法应当创建序列中由 iterator v 指示的项的输出. 不应当使用 printk

(值得注意 seq_file 代码在调用 start 和 stop 之间不睡眠或者进行其他非原子性任务. 你也肯定会看到在调用 start 后马上有一个 stop 调用. 因此, 对你的 start 方法来说请求信号量或自旋锁是安全的. 只要你的其他 seq_file 方法是原子的, 调用的整个序列是原子的.)

int seq_printf(struct seq_file *sfile, const char *fmt, ...); //给 seq_file 实现的 printf 对等体
int seq_putc(struct seq_file *sfile, char c);
int seq_puts(struct seq_file *sfile, const char *s); //用户空间 putc 和 puts 函数的对等体.int seq_escape(struct seq_file *m, const char *s, const char *esc); // seq_puts 的对等体, 除了 s 中的任何也在 esc 中出现的字符以八进制格式打印. esc 的一个通用值是"\t\n\\", 它使内嵌的空格不会搞乱输出和可能搞乱 shell 脚本.

现在已有了一个完整的 iterator 操作的集合, scull 必须包装起它们, 并且连接它们到 /proc 中的一个文件. 第一步是填充一个 seq_operations 结构:

static struct seq_operations scull_seq_ops = {
.start = scull_seq_start,
.next = scull_seq_next,
.stop = scull_seq_stop,
.show = scull_seq_show
};


创建一个 file_operations 结构(是的, 和字符驱动使用的同样结构) 来实现所有内核需要的操作, 来处理文件上的读和移动. 幸运的是, 这个任务是简单的. 第一步是创建一个 open 方法连接文件到 seq_file 操作:

static int scull_proc_open(struct inode *inode, struct file *file)
{
return seq_open(file, &scull_seq_ops);
}

调用 seq_open 连接文件结构和我们上面定义的序列操作. 事实证明, open 是我们必须自己实现的唯一文件操作, 因此我们现在可以建立我们的 file_operations 结构:

static struct file_operations scull_proc_ops = {
.owner = THIS_MODULE,
.open = scull_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release
};

最后的步骤是调用低层的 create_proc_entry创建 /proc 中的实际文件:

struct proc_dir_entry *create_proc_entry(const char *name,mode_t mode,struct proc_dir_entry *parent);

entry = create_proc_entry("scullseq", 0, NULL);
if (entry)
entry->proc_fops = &scull_proc_ops;


方法二:ioctl 方法:

ioctl是一个系统调用, 作用于一个文件描述符; 作为一个使用 /proc 文件系统的替代, 你可以实现几个用来调试用的 ioctl 命令. 

这种方式使用 ioctl 来获取信息有些比使用 /proc 困难, 因为你需要另一个程序来发出 ioctl 并且显示结果. 必须编写这个程序, 编译, 并且与你在测试的模块保持同步. 另一方面, 驱动侧代码可能容易过需要实现一个 /proc 文件的代码.

有时候 ioctl 是获取信息最好的方法, 因为它运行比读取 /proc 快. 如果在数据写到屏幕之前必须做一些事情, 获取二进制形式的数据比读取一个文本文件要更有效. 另外, ioctl 不要求划分数据为小于一页的片段.

ioctl 方法的另一个有趣的优点是信息获取命令可留在驱动中, 当调试被禁止时, 不象对任何查看目录的人都可见的 /proc 文件, 不记入文档的 ioctl 命令可能保持不为人知. 另外, 如果驱动发生了怪异的事情, 它们仍将在那里. 唯一的缺点是模块可能会稍微大些.


4. 用观察来调试
strace 命令可以 显示所有的用户空间程序发出的系统调用, 还以符号形式显示调用的参数和返回值. 当一个系统调用失败, 错误的符号值(例如, ENOMEM)和对应的字串(Out of memory) 都显示. strace 有很多命令行选项; 其中最有用的是 -t 来显示每个调用执行的时间, -T 来显示调用中花费的时间, -e 来限制被跟踪调用的类型, 以及-o 来重定向输出到一个文件. 缺省地, strace 打印调用信息到 stderr.
 strace ls /dev > /dev/scull0 

5.调试系统故障
1)oops 消息:当引用了一个NULL指针或其他不正确的时,通常会产生oops错误。通常通过指令指针EIP来判断错误类型。其次通过查看调用堆栈. 

Unable to handle kernel NULL pointer dereference at virtual address 00000000

EIP is at faulty_write+0x4/0x10 [faulty]



这是运行一个NULL指针导致的结果,指令指针EIP中显示了出错的函数faulty_write.

Unable to handle kernel paging request at virtual address ffffffff

EIP is at 0xffffffff

当拷贝一个字符串到数组,字串长于目的数组. 当函数返回时导致的缓存区溢出引起一次 oops . 因为返回指令使指令指针到不知何处, 

EIP返回0xffffffff. 同时,我们只看到部分的调用堆栈( vfs_read 和 faulty_read 丢失 ), 内核抱怨一个"坏 EIP 值". 这个抱怨和在开头列出的犯错的地址 ( ffffffff ) 都暗示内核堆栈已被破坏.


Unable to handle kernel paging request at virtual address 0xa5a5a5a5a5

EIP is at 0xa5a5a5a5a5

如果犯错地址时 0xa5a5a5a5a5, 你几乎肯定 - 某个地方在初始化动态内存.

(注意:只在你的内核是打开 CONFIG_KALLSYMS 选项而编译时可以看到符号的调用堆栈. )

2)系统挂起
 sysrq 功能:参考documentation/sysrq.txt,sysrq p 功能直接指向出错的函数.
开启/关闭:echo 1 > /proc/sys/kernel/sysrq;echo 0 > /proc/sys/kernel/sysrq

内核剖析功能:参考documentation/basic_profiling.txt 。建立一个打开剖析的内核, 并且用命令行中 profile=2 来启动它. 使用 readprofile 工具复位剖析计数器, 接着使你的驱动进入它的循环. 一会儿后, 使用 readprofile 来看内核在哪里消耗它的时间. 另一个更高级的选择是 oprofile, 你可以也考虑下. 

在追逐系统挂起时一个值得使用的防范措施是以只读方式加载你的磁盘(或者卸载它们). 如果磁盘是只读或者卸载的, 就没有风险损坏文件系统或者使它处于不一致的状态. 另外的可能性是使用一个通过 NFS, 网络文件系统, 来加载它的全部文件系统的计算机, 内核的"NFS-Root"功能必须打开, 在启动时必须传递特殊的参数. 在这个情况下, 即便不依靠 sysrq 你也会避免文件系统破坏, 因为文件系统的一致由NFS 服务器来管理, 你的设备驱动不会关闭它.

6.调试器和工具

gdb, kdb, kgdb,UML(用户模式linux), LTT(linux追踪工具), DProbes(动态探针)





推荐阅读
  • 使用C++编写程序实现增加或删除桌面的右键列表项
    本文介绍了使用C++编写程序实现增加或删除桌面的右键列表项的方法。首先通过操作注册表来实现增加或删除右键列表项的目的,然后使用管理注册表的函数来编写程序。文章详细介绍了使用的五种函数:RegCreateKey、RegSetValueEx、RegOpenKeyEx、RegDeleteKey和RegCloseKey,并给出了增加一项的函数写法。通过本文的方法,可以方便地自定义桌面的右键列表项。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • 我是这样学习Linux下C语言编程的-把程序输出信息加到系统日志里去关键词:Linux系统日志syslog服务程序syslogd ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • Metasploit攻击渗透实践
    本文介绍了Metasploit攻击渗透实践的内容和要求,包括主动攻击、针对浏览器和客户端的攻击,以及成功应用辅助模块的实践过程。其中涉及使用Hydra在不知道密码的情况下攻击metsploit2靶机获取密码,以及攻击浏览器中的tomcat服务的具体步骤。同时还讲解了爆破密码的方法和设置攻击目标主机的相关参数。 ... [详细]
  • Python语法上的区别及注意事项
    本文介绍了Python2x和Python3x在语法上的区别,包括print语句的变化、除法运算结果的不同、raw_input函数的替代、class写法的变化等。同时还介绍了Python脚本的解释程序的指定方法,以及在不同版本的Python中如何执行脚本。对于想要学习Python的人来说,本文提供了一些注意事项和技巧。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • PDO MySQL
    PDOMySQL如果文章有成千上万篇,该怎样保存?数据保存有多种方式,比如单机文件、单机数据库(SQLite)、网络数据库(MySQL、MariaDB)等等。根据项目来选择,做We ... [详细]
  • 如何查询zone下的表的信息
    本文介绍了如何通过TcaplusDB知识库查询zone下的表的信息。包括请求地址、GET请求参数说明、返回参数说明等内容。通过curl方法发起请求,并提供了请求示例。 ... [详细]
  • 本文主要复习了数据库的一些知识点,包括环境变量设置、表之间的引用关系等。同时介绍了一些常用的数据库命令及其使用方法,如创建数据库、查看已存在的数据库、切换数据库、创建表等操作。通过本文的学习,可以加深对数据库的理解和应用能力。 ... [详细]
  • STM32与FPGA的对比及学习建议
    本文对比了野火STM32F103指南针板和Xilinx的PYNQ-Z2板(ZYNQ-7020),介绍了野火STM32F103指南针板的学习资料和讲解视频的详细程度,建议初学者学习野火的资料。同时,介绍了STM32开发所用的Keil程序和C指针的重要性。对于ZYNQ-7020的开发,提到了其自带的Linux、Ubuntu18.4系统以及使用SD卡烧入镜像的方法。 ... [详细]
  • LINUX学习之centos7营救模式
    今天卸载软件的时候,不小心把GNOME的一些组件给卸了,导致桌面无法正常开启,会卡在启动过程中,而我的开机启动模式又是设置为图形界面,所以一开LINUX就卡住了,进入不了命令行界面 ... [详细]
  • Linux Shell脚步的格式
    Shell脚步等多个命令的组合,可以做成一个shell文件(1.sh)赋权执行执行命令的方式前两张新的进程中执行,对当前进程不产生影响(cdtmp;pwds ... [详细]
  • 原文地址:https:blog.csdn.netu011784994articledetails73878822这里是通过对udev的设置,让udev收到内核发来的U盘消息后自动挂 ... [详细]
author-avatar
phpfinder
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有