编译好内核后,一般都会生成标题中的各种文件,这些文件都有什么不同呢?
vmlinux(elf文件)
vmlinux:Linux内核编译出来的原始的内核文件,elf格式,未做压缩处理。
该映像可用于定位内核问题,但不能直接引导Linux系统启动。
使用readelf命令查看头部信息如下:
Image(bin文件)
Linux内核编译时,使用objcopy处理vmlinux后生成的二进制内核映像。
该映像未压缩,可直接引导Linux系统启动。
Linux内核编译vmlinux和Image过程如下:
objcopy命令是将目标文件(elf文件)的一部分或者全部内容拷贝到另外一个目标文件中,也可以实现目标文件的格式转换。objcopy依赖GNU BFD库来读写文件,需要注意的是在两种不同格式之间拷贝可重定位的目标文件可能有异常。
objcopy在做转换的时候会创建临时文件,然后将这些临时文件删除。objcopy使用BFD做转换工作。如果没有明确地格式要求,则objcopy将访问所有在BFD库中已经描述了的并且它可以识别的格式。
objcopy命令是GNU binutils工具包中的一个工具,它可用于创建一个有特殊排列和格式的目标文件或者可执行文件。objcopy也可以将目标文件和可执行文件中的某些部分拷贝到新的文件中,或创建一个空的目标文件或可执行文件。该命令是一个多功能工具,具体请参考:深入理解objcopy命令的用途及应用_笔记大全_设计学院
GNU BFD库
由于现代的硬件和软件平台种类非常繁多,它们之间千差万别。比如,硬件中CPU有8/16/64位的;字节序有大端/小端的;有些对访问内存地址对齐有着特殊要求;有些有MMU。软件平台有些支持动态链接;有些支持调试。这些五花八门的软硬件平台导致了每个平台都有它独特的目标文件,即使同一个格式比如ELF在不同的软硬件平台都有着不同的变种。
BFD库(Binary File Descriptor library)就是这样的一个GNU项目,它的目标就是希望通过一种统一的接口来处理不同的目标文件格式。BFD这个项目本身是binutils项目的一个子项目。BFD把目标文件抽象成一个统一的模型,使得BFD库的程序只要通过操作这个抽象的目标文件模型就可以实现操作所有BFD支持的目标文件格式。
现在GCC(更具体地讲是GNU汇编器GAS,GNU Assembler)、链接器ld、调试器GDB及binutils的其他工具都通过BFD库来处理目标文件,而不是直接操作目标文件。这样做最大的好处是将编译器和链接器本身同具体的目标文件格式隔离开来,一旦我们需要支持一种新的目标文件格式,只需要在BFD库里面添加一种格式就可以了,而不需要修改编译器和链接器。到目前为止,BFD库支持大约25种处理器平台,将近50种目标文件格式。
zImage
zImage:使用gzip压缩vmlinux后,再使用objcopy命令生成的Linux内核映像。
该映像一般作为uboot的引导映像文件。uboot引导命令后续介绍。
uImage
uImage:在zImage前面增加一个64字节的头,描述映像文件类型,加载位置,大小等信息。
该映像是老版本uboot专用的引导映像。
比较zImage和uImage镜像,可以发现uImage比zImage多出64个字节,其他内容完全一样。
下图展示了不同类型的Linux映像生成过程。
system.map
System.map是编译内核时产生的一个文件,存放着内核符号表信息。每次编译一次内核,就会产生一个新的System.map文件。符号表是所有内核符号及其对应地址的一个列表,如下图所示:
左边是符号地址(默认为十六进制),中间是符号的类型,右边是内核符号。
备注:System.map是一个磁盘上的文件。可以由nm vmlinux经过重定向到文件中获得。
输出重定向:将命令的输出发送的一个文件中,终端输出的内容会被保存到输出文件中
bash shell : > 输出的内容会覆盖已有文件的内容
bash shell : >> 输出的内容追加到已有的文件中
选择一个符号将System.map和 nm vmlinux重定向后的文件 进行内容对比,结果一致。
/proc/kallsyms
/proc/kallsyms也是内核提供的一个符号表,包含了动态加载的内核模块的符号,kallsyms抽取了内核用到的所有函数地址和非栈数据变量地址,生成了一个数据块,作为只读数据链接进kernel image。使用root权限可以/proc/kallsyms查看。
可以在模块加载时动态扩展其长度,给出了所有内核符号及其对应地址的一个列表:
左边是符号地址(默认为十六进制),中间是符号的类型,右边是内核符号。
system.map和/proc/kallsyms的区别
(1)
/proc/kallsysms 具有动态加载模块的符号以及静态代码(kernel image)的符号表。
system.map 仅是静态代码(kernel image)的符号表。
正在运行的内核可能和System.map不匹配,所以/proc/kallsyms才是内核符号参考的主要来源,我们应该通过/proc/kallsyms获得符号的地址。
(2)
System.map 是文件系统上的实际文件。 每次内核编译都会生成一个新的 System.map。
/proc/kallsyms 是内核启动时动态创建的“proc 文件”。 实际上,它并不是真正的磁盘文件。 它是内核数据的表示,已经加载到内存中。因此,对于当前正在运行的内核,它总是正确的。
(3)
因此我们要获取一个内核符号的地址,要通过/proc/kallsyms来获取,比如:
T代表位于代码段,一个全局的符号地址。