第一部分:System.map的作用
有关System.map文件的信息好象很缺乏。其实它一点也不神秘,并且在整个事情当中它并不象看上去那么得重要。但是由于缺乏必要的文档说明,使其显得比较神秘。它就象耳垂,我们每个人都有,但却不知道是干什么用的。注意,我并不会是百分之一百正确的。例如,一个系统很可能没有/proc文件系统支持,但是大多数系统肯定有。这里我假定你是“随大流的”,并有一个典型配置的系统。
某些有关内核出错(oops)的阐述来自于Alessandro Rubini的“Linux设备驱动程序” 一书,我是从其中学到大部分内核编程知识的。
什么是符号(Symbols)?
在编程中,一个符号(symbol)是一个程序的创建块:它是一个变量名或一个函数名。 正如你自己编制的程序一样,内核具有各种符号也是不应该感到惊奇的。当然,区别在 于内核是一非常复杂的代码块,并且含有许多、许多的全局符号。
内核符号表(Kernel Symbol Table)是什么东西?
内核并不使用符号名。它是通过变量或函数的地址(指针)来使用变量或函数的,而 不是使用size_t BytesRead,内核更喜欢使用(例如)c0343f20来引用 这个变量。
而另一方面,人们并不喜欢象c0343f20这样的名字。我们跟喜欢使用象 size_t BytesRead这样的表示。通常,这并不会带来什么问题。内核主要是用C语言写成的,所以在我们编程时编译器/连接程序允许我们使用符号名,并且使 内核在运行时使用地址表示。这样大家都满意了。
然而,存在一种情况,此时我们需要知道一个符号的地址(或者一个地址对应的 符号)。这是通过符号表来做到的,与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/kallsyms
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:[]
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
【转:http://edu.codepub.com/2011/0323/30291.php】
第二部分:System.map的格式
本文参考 binutils文档。
system.map内容格式为:线性地址 类型 符号
具体内容如下:
00100000 A phys_startup_32
c0100000 T startup_32
c0100000 A _text 注:表示内核代码第一个字节的地址
c01000c6 t checkCPUtype
c0100147 t is486
c010014e t is386
c0100199 t L6
c010019b t check_x87
c01001c2 t setup_idt
c01001df t rp_sidt
c01001ec t ignore_int
c0100220 t rest_init
c0100220 T stext
c0100220 T _stext
c0100252 t do_pre_smp_initcalls
c0100257 t run_init_process
c0100283 t init
c01003a8 t try_name
c0100529 T name_to_dev_t
c0100790 t calibrate_delay_direct
…
c02f95d7 T register_kretprobe
c02f96cd T unregister_kretprobe
c02f9760 t .text.lock.kprobes
c02f97b0 T __kprobes_text_end
c02f9abe t iret_exc
c02fa1af A _etext 注:内核代码结束的位置,之后为内核初始化的数据
c02fa1b0 A __start___ex_table
c02facc0 A __stop___ex_table
c02fb000 r __func__.12
c02fb000 A __start_rodata
c02fb00c r __func__.13
c02fb020 r __func__.2
c02fb02c r __func__.3
c02fb040 R linux_banner
c02fb0bb r __func__.0
c02fb0c7 r __func__.1
c02fb0e0 r __func__.0
c02fb0ec r __func__.1
c02fb100 r border
c02fb160 r cplens
…
c03e1b08 D ip_statistics
c03e1b10 D tcp_statistics
c03e1b18 D udp_statistics
c03e1b20 D icmp_statistics
c03e1b28 D net_statistics
c03e1b30 d fn_hash_kmem
c03e1b34 d fn_alias_kmem
c03e1b38 d xfrm_dst_cache
c03e1b3c d secpath_cachep
c03e1b40 A _edata 注:内核初始化数据结束,之后为未初始化数据
c03e2000 D init_thread_union
c03e4000 A __init_begin
c03e4000 t no_halt
c03e4000 T _sinittext
c03e400d t mca_pentium
c03e401d t no_387
c03e4033 t check_fpu
…
c043a058 b pfkey_table
c043a05c b pfkey_table_lock
c043a05c b pfkey_table_users
c043a060 b pfkey_socks_nr
c043a064 b acqseq.4
c043a068 b acqseq_lock.5
c043a068 A __bss_stop
c043a068 A _end 注:内核未初始化数据结束
c043b000 A pg0
ffffe400 A __kernel_vsyscall
ffffe410 A SYSENTER_RETURN
ffffe420 A __kernel_sigreturn
ffffe440 A __kernel_rt_sigreturn
符号类型.
小写字母表示局部; 大写字母表示全局(外部).
A
The symbol’s value is absolute, and will not be changed by further linking.
B
The symbol is in the uninitialized data section (known as BSS).
C
The symbol is common. Common symbols are uninitialized data. When linking, multiple common symbols may appear with the same name. If the symbol is defined anywhere, the common symbols are treated as undefined references. For more details on common symbols, see the discussion of -warn-common in Linker options.
D
The symbol is in the initialized data section.
G
The symbol is in an initialized data section for small objects. Some object file formats permit more efficient access to small data objects, such as a global int variable as opposed to a large global array.
I
The symbol is an indirect reference to another symbol. This is a GNU extension to the a.out object file format which is rarely used.
N
The symbol is a debugging symbol.
R
The symbol is in a read only data section.
S
The symbol is in an uninitialized data section for small objects.
T
The symbol is in the text (code) section.
U
The symbol is undefined.
V
The symbol is a weak object. When a weak defined symbol is linked with a normal defined symbol, the normal defined symbol is used with no error. When a weak undefined symbol is linked and the symbol is not defined, the value of the weak symbol becomes zero with no error.
W
The symbol is a weak symbol that has not been specifically tagged as a weak object symbol. When a weak defined symbol is linked with a normal defined symbol, the normal defined symbol is used with no error. When a weak undefined symbol is linked and the symbol is not defined, the value of the weak symbol becomes zero with no error.
-
The symbol is a stabs symbol in an a.out object file. In this case, the next values printed are the stabs other field, the stabs desc field, and the stab type. Stabs symbols are used to hold debugging information. For more information, see Stabs.
?
The symbol type is unknown, or object file format specific.
# info binutils查看更详细的信息
【转:http://blog.csdn.net/gates84/archive/2006/11/27/1416514.aspx】
看了一下Linux下的/boot/System.map,发现里边定义的东西还是比较有意思的
仔细的看了一下,写一些东西:)
部分摘录:
每次编译内核时符号的地址均不同,因而编译内核的同时都会生成一个相应的System.map文件。在System.map文件中,地址与符号之间的字母代表符号的类型。这里的类型定义与nm中类型的定义是一样的。下面我们便罗列出这些类型。
A | 绝对 |
B或b | 未初始化的数据段(BSS) |
D或d | 初始化的数据段 |
G或g | 小对象的初始化数据段(全局的) |
i | DLL相关的段 |
N | 调试用符号 |
p | 堆栈的展开段(stack unwind section) |
R或r | 只读数据段 |
S或s | 小对象的未初始化数据段 |
T或t | 代码段 |
U | 未定义 |
V或v | 弱对象(weak object) |
W或w | 没有标签的弱对象(weak object) |
- | a.out目标文件中的桩符号(stabs symbol) |
? | 未知符号 |