本系列主要介绍Linux内核死机、异常重启类稳定性问题的调试方法。

在Linux系统中,一切皆为文件,而系统运行的载体,是一类特殊的文件,即进程。因此,我尝试从进程的角度分析Linux内核的死机、异常重启等问题。在内核空间,内核本身是一个特权级的进程,它包含一系列系统级线程,维护整个系统内核的运转。在用户空间,有很多用户进程实现不同的功能,它们有的是独立运行,有些相互之间有依赖(同步或者互斥)。在32位系统中,内核进程独享3GB~4GB的高1GB内存空间,而每个用户进程则分别占据0GB~3GB的低3GB内存空间。这里需要搞清楚的是,这里说的高1GB内存空间和低3GB内存空间是指的虚拟内存空间,而不是物理内存空间。(查阅《深入理解Linux虚拟内存管理》)高1GB内存空间在系统中只有一个拷贝,而低3GB内存空间则是每个用户进程均独自拥有,彼此相互隔离。 下面的图展示了对于用户进程而言,4GB虚拟内存空间的布局:

现在,我们要介绍一下进程的运行环境,从图上我们可以看到高1GB虚拟内存空间是内核空间,每个用户进程都是内核进程通过调用exec系统调用fork出来的子进程,在高1GB虚拟内存空间中,保存了用户进程的环境列表和参数列表。环境列表是一个全局的缓冲区,里面保存了一些键值对,如下图所示:

全局指针environ指向该缓冲区。另外,从进程入口函数int main(int argc, char *argv[], char *envp[]);可以获取进程的参数列表。

用户进程的.text段(即代码段)默认加载到0x08048000开始的地址空间。至于为什么是0x08048000,这个是从Unix延续过来的,在32位系统中这个值是0x08048000,在64位系统中这个值是0x00400000。0x00000000~0x08048000地址空间主要用于进程通过mmap系统调用映射动态链接库等用途。代码段上面是.data段(即数据段),保存了初始化的全局变量和静态变量。再往上是BSS段,保存未初始化的全局变量和静态变量。(注意,初始化为零的全局变量和静态变量也会保存到BSS段)剩下的中间地带是堆栈的区域,栈从0xc0000000往下增长,堆从BSS段末端(进程可能会包含其他段,这种情况下,另当别论)往上增长。栈保存了栈帧数据,即函数调用栈信息,每一层函数调用的栈帧保存被调用函数的参数、返回地址等信息。而堆则用于动态内存分配,即调用malloc函数分配的地址空间。(需要注意,malloc函数分配的内存空间除了缓冲区空间外,还包括缓冲区起始地址、大小等内存管理信息)堆和栈之间没有明确的分界线。

但是,关于运行时堆的位置,有一点要强调。运行时堆的位置与内存管理算法相关,也就是和malloc函数实现有关。在glibc实现的内存管理算法中,malloc小块内存是在0x40000000以下的虚拟内存空间分配,通过brk/sbrk系统调用向上增长;而分配大块内存时,malloc直接通过mmap系统调用实现,分配的虚拟内存位于文件映射区,即0x40000000以上的虚拟内存空间。

独立进程崩溃的原因可能是:

1)非法地址访问,用户空间进程不能直接访问高1GB地址空间和0x08048000以下的地址空间,需要通用系统调用进行读写操作,如果非法访问了这些区域,则会被内核线程捕捉到,内核会报segmentation fault段错误,而用户进程则会crash。另外,访问堆和栈之间的空白区域也会导致segmentation fault,因为他们没有经过系统调用通过MMU映射到真正的物理内存,即野指针错误。这一段内存区域只是名义上存在的,而不是物理上实际存在的。

2) 空指针错误,空指针是指向0x00000000的,用户进程读写空指针会引发非法地址访问错误,对于用户进程,系统会报segmentation fault,用户进程crash。对于内核进程,系统会报NULL pointer错误,系统会panic重启。重复删除同一指针节点,如多次free同一段内存空间的情况下,就会引发此类问题。

3) 内存泄露,内存泄露是指动态分配的内存使用完毕之后未及时释放,对于常驻内存的守护进程和服务而言,内存泄露会导致堆大小持续向上增长,进而覆盖栈内存区域,这样就会导致程序跑飞。因此,malloc的内存使用完毕,必须要及时释放。

4) 结构体或者变量字节对齐问题,对于跨平台和网络通讯而言,此类问题是多发问题。如果结构体或者变量不对齐,会导致读取的数据产生位移,取不到正确的信息,导致程序跑飞。

5)栈溢出问题, 一般是没有对函数的入参做检查,如通过入参把超过char型数组长度的字符串填充到函数内部的char型数组局部变量中, 字符串就会冲掉函数栈帧,从而导致程序执行异常。

6)缓冲区溢出,是指往固定大小的内存区域填充超过其长度的内容,从而导致缓冲区越界擦除其他数据导致程序跑飞的情况。常见的是字符串缓冲区无’\0’结束符,这样调用printf等函数时就会溢出访问超过缓冲区的内存进而引发错误。

7)栈溢出,这是一类特殊的缓冲区溢出问题,简单说就是函数的入参占据的内存大小超过了函数内部为保存该参数分配的内存空间,导致栈帧越界覆盖其他栈帧数据,规避这一类问题的方法是在函数入口严格对入参进行检查。

进程间通讯导致进程崩溃的原因可能是:

1)死锁,如果两个进程死锁得不到调度,会导致进程本身处于D状态,但是系统不会崩溃;如果进程死锁,抢占CPU资源,其他进程得不到调度,就会导致看门狗超时,从而引发系统异常重启。(注:个人对进程间通讯机制理解还不深刻,暂时只能总结这么多)

上面大致介绍了导致系统异常的一些原因,那么,针对不同的错误类型,可以通过不同的方法进行调试分析。接下来的部分会由浅入深,分别介绍一些常用的内核调试方法和工具。

备注:关于sysfs, debugfs, procfs, printk之类的调试技巧请自行百度。

Linux内核调试方法总结之序言的更多相关文章

  1. Linux内核调试方法总结

    Linux内核调试方法总结 一  调试前的准备 二  内核中的bug 三  内核调试配置选项 1  内核配置 2  调试原子操作 四  引发bug并打印信息 1  BUG()和BUG_ON() 2   ...

  2. Linux内核调试方法总结【转】

    转自:http://my.oschina.net/fgq611/blog/113249 内核开发比用户空间开发更难的一个因素就是内核调试艰难.内核错误往往会导致系统宕机,很难保留出错时的现场.调试内核 ...

  3. 【转】Linux内核调试方法总结

    目录[-] 一  调试前的准备 二  内核中的bug 三  内核调试配置选项 1  内核配置 2  调试原子操作 四  引发bug并打印信息 1  BUG()和BUG_ON() 2  dump_sta ...

  4. Linux内核调试方法【转】

    转自:http://www.cnblogs.com/shineshqw/articles/2359114.html kdb:只能在汇编代码级进行调试: 优点是不需要两台机器进行调试. gdb:在调试模 ...

  5. Linux内核调试方法总结之反汇编

    Linux反汇编调试方法 Linux内核模块或者应用程序经常因为各种各样的原因而崩溃,一般情况下都会打印函数调用栈信息,那么,这种情况下,我们怎么去定位问题呢?本文档介绍了一种反汇编的方法辅助定位此类 ...

  6. Linux内核调试方法总结之栈帧

    栈帧 栈帧和指针可以说是C语言的精髓.栈帧是一种特殊的数据结构,在C语言函数调用时,栈帧用来保存当前函数的父一级函数的栈底指针,当前函数的局部变量以及被调用函数返回后下一条汇编指令的地址.如下图所示: ...

  7. Linux内核调试方法总结之ddebug

    [用途] Linux内核动态调试特性,适用于驱动和内核各子系统调试.动态调试的主要功能就是允许你动态的打开或者关闭内核代码中的各种提示信息.适用于驱动和内核线程功能调试. [使用方法] 依赖于CONF ...

  8. Linux内核调试方法总结之调试宏

    本文介绍的内核调试宏属于静态调试方法,通过调试宏主动触发oops从而打印出函数调用栈信息. 1) BUG_ON 查看bug处堆栈内容,主动制造oops Linux中BUG_ON,WARN_ON用于调试 ...

  9. 转载:Linux内核调试方法

    转载文章请注明作者和二维码及全文信息. 转自:http://blog.csdn.net/swingwang/article/details/72331196 不会编程的程序员,不是好的架构师,编程和内 ...

随机推荐

  1. 【7.24校内交流赛】T1&T2

    T1: 一个脑洞很大的题,将输入的所有数异或起来输出就好了: (话说我为什么这么喜欢用异或啊) #include<bits/stdc++.h> using namespace std; i ...

  2. [BZOJ 3625] [Codeforces 438E] 小朋友的二叉树 (DP+生成函数+多项式开根+多项式求逆)

    [BZOJ 3625] [Codeforces 438E] 小朋友的二叉树 (DP+生成函数+多项式开根+多项式求逆) 题面 一棵二叉树的所有点的点权都是给定的集合中的一个数. 让你求出1到m中所有权 ...

  3. 不能将X*类型的值分配到X*类型的实体问题的解决方法

    今天在学习链表的过程中遇到了这个问题,我用如下方法定义了一个结构体,然后这个函数想要在链表头插入一个节点.但是在函数的最后一行却出现了报错:不能将MyLinkedList * 类型的值分配到MyLin ...

  4. 分布式之redis(转发)

    为什么写这篇文章? 博主的<分布式之消息队列复习精讲>得到了大家的好评,内心诚惶诚恐,想着再出一篇关于复习精讲的文章.但是还是要说明一下,复习精讲的文章偏面试准备,真正在开发过程中,还是脚 ...

  5. Python 进程之间共享数据(全局变量)

    进程之间共享数据(数值型): import multiprocessing def func(num): num.value=10.78 #子进程改变数值的值,主进程跟着改变 if __name__= ...

  6. 已知链表头结点指针head,写一个函数把这个链表逆序

    Node* ReverseList ( Node *head ) { if ( head == NULL || head->next == NULL ) return head; Node *p ...

  7. 【转】UBOOT——启动内核

    转自:https://www.cnblogs.com/biaohc/p/6403863.html 1:什么是UBOOT,为什么要有UBOOT? UBOOT的主要作用是用来启动linux内核,因为CPU ...

  8. PAT Advanced 1031 Hello World for U (20 分)

    Given any string of N (≥) characters, you are asked to form the characters into the shape of U. For ...

  9. 《码出高效:Java开发手册》第四章学习记录,内容想当的多,前后花了几天的时间才整理好。

    <码出高效:Java开发手册>第四章学习记录,内容想当的多,前后花了几天的时间才整理好. https://naotu.baidu.com/file/e667435a4638cbaa15eb ...

  10. Centos7.5 rpm安装zabbix_agent4.0.3

    1.下载并且安装 cd /data/tools/ ##切换到下载客户端目录 wget http://repo.zabbix.com/zabbix/4.0/rhel/7/x86_64/zabbix-ag ...