每个程序员都有些不畏死亡决战猛兽的英雄事迹。以下这些是我的。
  内存冲突
  毕业不到半年,拿着刚到手的文凭,我在Lexmark公司的一个嵌入式Linux固件开发团队中负责追踪一个内存冲突的问题。因为内存冲突的原因和问题表象总是相差非常大,所以这类问题很难调。有可能是因为缓存溢出,也有可能是指针未初始化,或是指针被多次free,亦或是某处的DMA错误,但是你所见的却是一堆神秘的问题:挂起、指令未定义、打印错误,以及未处理的内核错误。这些都非常频繁,内存冲突看上去似乎是随机出现又很难重现。
  要调试这种问题,第一步是可重现问题。在我们奇迹般地找到这样一个场景之后,故事开始变得好玩起来。
  当时,我们发现在运行时因内存冲突而产生的程序崩溃每几百小时会出现一次。之后有有人发现一个特别的打印任务会产生内存冲突从而在几分钟之内使程序崩溃。我从来不知道为什么这个打印任务会产生这个问题。现在,我们可以进一步做些什么了。
  调试
  这个问题可重现之后,我开始寻找崩溃中出现的模式。引人注意的是未定义指令和内核错误,它们差不多三分之一的时间会发生一次。未定义指令的地址是一个合理的内核代码地址,但是CPU读到的这个指令却不是我们期望出现的。这很简单了,可能是有人不小心写了这些指令。把这些未定义指令的句柄打印出来之后,我可以看到这些错误的指令所在位置的周边内存的状态。
  在做了大量失败的将更多的代码排除出崩溃的尝试之后,一个特殊的崩溃渐渐显现。
  崩溃
  这个崩溃解开了所有秘密。当时我们用了一个双核CPU。在这个特殊的崩溃里,首先CPU1在有效的模块地址范围内收到了一个未处理的内核错误,而此时它正在尝试执行模块代码,这段代码可能是一个冲突的页表或是一个无效TLB。而正在处理这个错误时,CPU0在内核地址空间内收到了一个非法的指令陷阱。
  以下是从修改后的未定义指令句柄中打印出来的数据(已转为物理地址,括号中是出错地址)
  undefined instruction: pc=0018abc4
  0018aba0: e7d031a2 e1b03003 1a00000e e2822008
  0018abb0: e1520001 3afffff9 e1a00001 e1a0f00e
  0018abc0: 0bd841e6 (ceb3401c) 00000004 00000001
  0018abd0: 0d066010 5439541b 49fa30e7 c0049ab8
  0018abe0: e2822001 eafffff1 e2630000 e0033000
  0018abf0: e16f3f13 e263301f e0820003 e1510000
  以下是内存域应该显示的数据:
  0018aba0: e7d031a2 e1b03003 1a00000e e2822008
  0018abb0: e1520001 3afffff9 e1a00001 e1a0f00e
  0018abc0: e3310000 (0afffffb) e212c007 0afffff3
  0018abd0: e7d031a2 e1b03c33 1a000002 e3822007
  0018abe0: e2822001 eafffff1 e2630000 e0033000
  0018abf0: e16f3f13 e263301f e0820003 e1510000
  确切地来说,只有一行缓存(中间那32byte)是有冲突的。一个同事指出冲突行中0x49fa30e7这个字是一个魔术cookie,它标记了系统中一个特殊环形缓冲区的入口。入口值的后一个字永远是一个时间戳,所以0x5439541b是上一个入口的时间戳。我决定去读取这个环形缓冲的内容,但它现在挂在一个不可执行的KGDB提示那了。机器现在跟死了一样。