当前位置:嗨网首页>书籍在线阅读

02-内核调试带来的挑战

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

14.1 内核调试带来的挑战

调试一个现代操作系统涉及很多挑战。虚拟内存操作系统更是有其独特之处。使用在线仿真器(In-Circuit Emulator,ICE)替代处理器的日子已经一去不复返了。处理器已经变得非常快速和复杂。而且,处理器内部的流水线架构隐藏了一些重要的代码执行细节。这是因为总线上的内存访问顺序可能和代码的执行不一致,特别是因为指令流的内部缓存机制。一般不太可能将外部总线的活动和内部处理器的指令执行关联起来,除非是在一个相当粗略的层次之上。

这里列出一些调试Linux内核代码时会遭遇的挑战。

  • Linux内核代码在很多方面是针对执行速度而高度优化的。
  • 编译器所使用的优化技术会使C源码与实际机器指令流之间的关系变得复杂。内联函数就是个很好的例子。
  • 单步调试编译器优化的代码时会产生一些不寻常和意外的结果。
  • 虚拟内存机制将用户空间内存和内核内存隔离开来,这会使各种调试场景特别地困难。
  • 有些代码不能够使用传统的编译器进行单步调试。
  • 调试内核的启动代码是非常困难的,因为它和硬件的联系很紧密,而且可用资源非常有限(比如没有控制台,内存映射有限,等等)。

Linux内核已经发展成为一个性能极高的操作系统,能够和最好的商业操作系统竞争。内核中的很多区域并不容易分析,不是简单地阅读一下源码就行的。要想理解某些特定的代码流程,我们常常需要理解硬件的架构和软件的详细设计。有几本书详细描述了内核的设计,它们都非常不错,请参考本章最后一节中的阅读建议。

GCC是一个能够优化代码的编译器。默认情况下,Linux内核在编译时会采用编译器标志 -O2 。这个标志开启了很多优化算法,能够改变内核代码的基本结构和顺序[1]。例如,Linux内核大量使用了内联函数。内联函数是指那些由 inline 关键字声明的小函数,结果是编译器会将函数体直接包含到执行线程中,而不是产生一个函数调用,从而省去了相关的开销[2]。内联函数要求优化级别至少为 -O1 。因此,不能够关闭编译器的优化,虽然这有利于调试。

[1] 请参考本章末尾的GCC手册,以了解更多有关优化级别的细节。

[2] 内联函数类似于宏,但它具有在编译时进行类型检查的优点。

在Linux内核中的很多地方,单步调试代码很困难,有时根本不可能。最明显的例子就是修改虚拟内存设置的那部分代码路径。当应用程序执行了一个系统调用并进入内核时,进程所看到的地址空间会发生改变。实际上,任何涉及处理器异常的转换都会改变运行环境,从而很难或不可能进行单步调试。