三言两语聊Kernel: Undefined Instruction

前言

在写这篇文章的时候,我一直在犹豫要不要写, 因为它涉及到我的日常工作,而我司对信息安全监管的特别严。但是,我觉得最近遇到的这个bug确实很有趣,有必要记录一下,以后翻阅的时候也有参考。 所以,我禁掉了google对我个人博客的搜索,同时也关掉了往其他博客(比如sina微博)的同步。权当做个人记录。

常见的Undefined Instruction

一个可执行文件,简单的看就是指令和数据的集合。 CPU在执行该可执行文件的时候,会不断的去读取指令,然后解析该指令,最终执行,这是流水线的基本原理。

Undefined Instruction的意思就是,CPU无法解析从内存中读取到的指令,这条指令不是该CPU能够识别的指令集里面的。 出现undefined instruction,无非是以下几种情况:

  • 确实是非法指令
    这种情况一般都是硬编码导致,在代码里直接用16进制的数字来表示一条指令,这样就导致这些代码移植到其他平台的时候不能被解析
  • 踩内存
    存储指令的内存空间被踩掉,指令就变成了其他的内容,这种情况,出现什么错误都是有可能的。不仅仅是undefined instruction,还可能是bad syscall等等
  • 指令所在的内存被释放掉
    这种情况一般都是出现在操作系统里面。在操作系统初始化的时候,会执行很多初始化代码,这些代码在操作系统起来后就再也不被执行到了。为了节省内存,os就有了个机制:回收初始化代码,或者说,free init memory。一个函数使用_init memory机制的前提是,作者认为这部分代码以后永远也不会被用到。
    ok,问题就来了。 原作者很清楚自己写的代码,而后来的维护者就理解不是很深刻,他就可能会调用到_init memory里面的代码。可想而知,这必然是会出错的,因为init memory已>经被释放然后分配给其他用途了,这个地址空间不知道存储的是什么东西,你再到这个地址空间去取指令,显然发生的错误是不可预料的

这三种情况我都有遇到过。 从定位问题的难度来看,感觉第3种是最好定位的,第2种是最不好定位的.下面我要说的就是遇到的第3种情况的问题。

分析过程

我在移植一个特性到os上去,执行的时候出现undefined instruction的情况,每次都是在func_a()这个出现。我就去看func_a()的反汇编,那个地址对应的指令是正常的。于是,我猜测应该是踩内存了
接下来,我在调用func_a()的前面加了几行语句,来把func_a()所在地址空间里面的内容给打印出来,看看到底是哪里被踩了

1
2
for (i = (unsigned int)func_a; i < (unsigned int)func_a + 100; i += 4)
   printk(%08x : %08x\n, (unsigned int)func_a, *(unsignd int *)func_a);

打印出来的数据显示,func_a()这个函数里面的内容都被踩掉了。于是就去研究,为什么被踩。

在追内核代码的过程中,发现func_a()这个函数其实是初始化部分的函数。函数定义如下:

1
2
3
4
int __init func_a()
{
          // ...
}

__init这个宏的定义如下:
它的作用是,将这个函数放到.init.text这个section中。section是elf格式的一个概念,具体可google之。 os是怎么处理.init.text这个section的哪?

在free_initmem()这个函数里面会将这个section所占用的内存给释放掉:

一些解释:

  • __init_begin和__init_end的的初始化是在vmlinux.lds.S这个文件里面。 INIT_TEXT_SETION位于这两个变量之间
  • INIT_TEXT_SETION的定义是在include/asm-generic/vmlinux.lds.h这个头文件中,就是.init.text这个节

Comments