System.map文件的做用解析

有关System.map文件的信息好象很缺少。其实它一点也不神秘,而且在整个事情当中它并不象看上去那么得重要。可是因为缺少必要的文档说明,使其显得比较神秘。它就象耳垂,咱们每一个人都有,但殊不知道是干什么用的。本网页就是用来讲明这个问题的。linux

注意,我并不会是百分之一百正确的。例如,一个系统极可能没有/proc文件系统支持,可是大多数系统确定有。这里我假定你是“随大流的”,并有一个典型配置的系统。ios

某些有关内核出错(oops)的阐述来自于Alessandro Rubini的“Linux设备驱动程序” 一书,我是从其中学到大部份内核编程知识的。编程

什么是符号(Symbols)?并发

在编程中,一个符号(symbol)是一个程序的建立块:它是一个变量名或一个函数名。 正如你本身编制的程序同样,内核具备各类符号也是不该该感到惊奇的。固然,区别在 于内核是一很是复杂的代码块,而且含有许多、许多的全局符号。less

内核符号表(Kernel Symbol Table)是什么东西?ide

内核并不使用符号名。它是经过变量或函数的地址(指针)来使用变量或函数的,而 不是使用size_t BytesRead,内核更喜欢使用(例如)c0343f20来引用 这个变量。函数

而另外一方面,人们并不喜欢象c0343f20这样的名字。咱们跟喜欢使用象 size_t BytesRead这样的表示。一般,这并不会带来什么问题。内核主要 是用C语言写成的,因此在咱们编程时编译器/链接程序容许咱们使用符号名,而且使 内核在运行时使用地址表示。这样你们都满意了。oop

然而,存在一种状况,此时咱们须要知道一个符号的地址(或者一个地址对应的 符号)。这是经过符号表来作到的,与gdb可以从一个地址给出函数名(或者给出一个 函数名的地址)的状况很类似。符号表是全部符号及其对应地址的一个列表。这里是 一个符号表例子:指针

c03441a0 B dmi_broken

c03441a4 B is_sony_vaio_laptop

c03441c0 b dmi_ident

c0344200 b pci_bios_present

c0344204 b pirq_table

c0344208 b pirq_router

c034420c b pirq_router_dev

c0344220 b ascii_buffer

c0344224 b ascii_buf_bytes

你能够看出名称为dmi_broken的变量位于内核地址c03441a0处。调试

什么是System.map文件?

有两个文件是用做符号表的:

  1. /proc/ksyms
  2. System.map

这里,你如今能够知道System.map文件是干什么用的了。

每当你编译一个新内核时,各类符号名的地址定会变化。

/proc/ksyms 是一个 "proc文件" 而且是在内核启动时建立的。实际上 它不是一个真实的文件;它只是内核数据的简单表示形式,呈现出象一个磁盘文件似 的。若是你不相信我,那么就试试找出/proc/ksyms的文件大小来。所以, 对于当前运行的内核来讲,它老是正确的..

然而,System.map倒是文件系统上的一个真实文件。当你编译一个新内核时,你原 来的System.map中的符号信息就不正确了。随着每次内核的编译,就会产生一个新的 System.map文件,而且须要用该文件取代原来的文件。

什么是一个Oops?

在本身编制的程序中最多见的出错状况是什么?是段出错(segfault),信号11。

Linux内核中最多见的bug是什么?也是段出错。除此,正如你想象的那样,段出 错的问题是很是复杂的,并且也是很是严重的。当内核引用了一个无效指针时,并不 称其为段出错 -- 而被称为"oops"。一个oops代表内核存在一个bug,应该老是提出 报告并修正该bug。

请注意,一个oops与一个段出错并非一回事。你的程序并不能从段出错中恢复 过来,当出现一个oops时,并不意味着内核确定处于不稳定的状态。Linux内核是很是 健壮的;一个oops可能仅杀死了当前进程,并使余下的内核处于一个良好的、稳定的 状态。

一个oops并不是是内核死循环(panic)。在内核调用了panic()函数后,内核就不能 继续运行了;此时系统就处于停顿状态而且必须重启。若是系统中关键部分遭到破坏 那么一个oops也可能会致使内核进入死循环(panic)。例如,设备驱动程序中 出现的oops就几乎不会致使系统进行死循环。

当出现一个oops时,系统就会显示出用于调试问题的相关信息,好比全部CPU寄存器 中的内容以及页描述符表的位置等,尤为会象下面那样打印出EIP(指令指针)的内容:

EIP: 0010:[<00000000>]

Call Trace: []

一个Oops与System.map文件有什么关系呢?

我想你也会认为EIP和Call Trace所给出的信息并很少,可是重要 的是,对于内核开发人员来讲这些信息也是不够的。因为一个符号并无固定的地址, c010b860能够指向任何地方。

为了帮助咱们使用oops含糊的输出,Linux使用了一个称为klogd(内核日志后台程序)的 后台程序,klogd会截取内核oops而且使用syslogd将其记录下来,并将某些象c010b860 的信息转换成咱们能够识别和使用的信息。换句话说,klogd是一个内核消息记录器(logger), 它能够进行名字-地址之间的解析。一旦klogd开始转换内核消息,它就使用手头的记录器, 将整个系统的消息记录下来,一般是使用syslogd记录器。

为了进行名字-地址解析,klogd就要用到System.map文件。我想你如今 知道一个oops与System.map的关系了。

深刻说明: 其实klogd会执行两类地址解析活动。

  • 静态转换,将使用System.map文件。
  • 动态转换,该方式用于可加载模块,不使用System.map,所以与本讨论没有关系,但我仍然对其加以简单说明。

Klogd动态转换
假设你加载了一个产生oops的内核模块。因而就会产生一个oops消息,klogd就会截获它,并发现该oops发生在d00cf810处。因为该地址属于动态加载模块,所以在System.map文件中没有对应条目。klogd将会在其中寻找并会毫无所获,因而判定是一个可加载模块产生了oops。此时klogd就会向内核查询该可加载模块输出的符号。即便该模块的编制者没有输出其符号,klogd也起码会知道是哪一个模块产生了oops,这总比对一个oops一无所知要好。
还有其它的软件会使用System.map,我将在后面做一说明。

System.map应该位于什么地方?

System.map应该位于使用它的软件可以寻找到的地方,也就是说,klogd会在什么地方寻找它。在系统启动时,若是没有以一个参数的形式为klogd给出System.map的位置,则klogd将会在三个地方搜寻System.map。依次为:

  1. /boot/System.map
  2. /System.map
  3. /usr/src/linux/System.map

System.map 一样也含有版本信息,而且klogd可以智能化地搜索正确的map文件。例如,假设你正在运行内核2.4.18而且相应的map文件位于/boot/System.map。如今你在目录/usr/src/linux中编译一个新内核2.5.1。在编译期间,文件 /usr/src/linux/System.map就会被建立。当你启动该新内核时,klogd将首先查询 /boot/System.map,确认它不是启动内核正确的map文件,就会查询 /usr/src/linux/System.map, 肯定该文件是启动内核正确的map文件并开始读取其中的符号信息。

几个注意点:

  • 在2.5.x系列内核的某个版本,Linux内核会开始untar成linux-version,而非只是linux (请举手表决 -- 有多少人一直等待着这样作?)。我不知道klogd是否已经修改成在/usr/src/linux-version/System.map中搜索。TODO:查看klogd源代码。
  • 在线手册上对此也没有完整描述,请看:
    strace -f /sbin/klogd | grep 'System.map'
    31208 open("/boot/System.map-2.4.18", O_RDONLY|O_LARGEFILE) = 2显然,不只klogd在三个搜索目录中寻找正确版本的map文件,klogd也一样知道寻找名字为 "System.map" 后加"-内核版本",象 System.map-2.4.18. 这是klogd未公开的特性。

有一些驱动程序将使用System.map来解析符号(由于它们与内核头链接而非glibc库等),若是没有System.map文件,它们将不能正确地工做。这与一个模块因为内核版本不匹配而没有获得加载是两码事。模块加载是与内核版本有关,而与即便是同一版本内核其符号表也会变化的编译后内核无关。

还有谁使用了System.map?

不要认为System.map文件仅对内核oops有用。尽管内核自己实际上不使用System.map,其它程序,象klogd,lsof,
satan# strace lsof 2>&1 1> /dev/null | grep System
readlink("/proc/22711/fd/4", "/boot/System.map-2.4.18", 4095) = 23

ps,
satan# strace ps 2>&1 1> /dev/null | grep System
open("/boot/System.map-2.4.18", O_RDONLY|O_NONBLOCK|O_NOCTTY) = 6

以及其它许多软件,象dosemu,须要有一个正确的System.map文件。

若是我没有一个好的System.map,会发生什么问题?

假设你在同一台机器上有多个内核。则每一个内核都须要一个独立的 System.map文件!若是所启动的内核没有对应的System.map文件,那么你将按期地看到这样一条信息:
System.map does not match actual kernel (System.map与实际内核不匹配)
不是一个致命错误,可是每当你执行ps ax时都会恼人地出现。有些软件,好比dosemu,可能不会正常工做。最后,当出现一个内核oops时,klogd或ksymoops的输出可能会不可靠。

我如何对上述状况进行补救?

方法是将你全部的System.map文件放在目录/boot下,并使用内核版本号从新对它们进行命名。假设你有如下多个内核:

  • /boot/vmlinuz-2.2.14
  • /boot/vmlinuz-2.2.13

那么,只需对应各内核版本对map文件进行更名,并放在/boot下,如:

/boot/System.map-2.2.14
/boot/System.map-2.2.13

若是你有同一个内核的两个拷贝怎么办?例如:

  • /boot/vmlinuz-2.2.14
  • /boot/vmlinuz-2.2.14.nosound

最佳解决方案将是全部软件可以查找下列文件:

/boot/System.map-2.2.14
/boot/System.map-2.2.14.nosound

可是说实在的,我并不知道这是不是最佳状况。我曾经见到搜寻"System.map-kernelversion",可是对于搜索"System.map-kernelversion.othertext"的状况呢? 我不太清楚。此时我所能作的就是利用这样一个事实:/usr/src/linux是标准map文件的搜索路径,因此你的map文件将放在:

  • /boot/System.map-2.2.14
  • /usr/src/linux/System.map (对于nosound版本)

你也可使用符号链接:

System.map-2.2.14
System.map-2.2.14.sound
System.map -> System.map-2.2.14.sound
相关文章
相关标签/搜索