1 基础知识
参考:;
程序员的自我修养:(1)目标文件。
1.1 百度百科目标文件:
目标文件(objectfile)即存放目标代码的计算机文件,它常被“称作”
二进制文件(binaries)。目标文件包含着机器代码(可直接被计算机中央处理器执行)以及代码在运行时使用的数据,如重定位信息,如用于链接或调试的程序符号表(变量和函数的名字),此外还包括其他调试信息。
1.2 Linux下有3种目标文件形式:
可执行目标文件,可重定位目标文件和共享目标文件。也有对应的称为
可执行文件,目标文件,共享库,只是说法不同,指的都是同样的东西。
可执行目标文件:包含二进制代码和数据,可以在存储器中直接执行。
可重定位目标文件:包含二进制代码和数据,可以在编译时与其他可重定位目标文件合并起来的,创建一个可执行目标文件。
共享目标文件: 是一种特殊的可重定位目标文件,可以在加载或者运行时被动态加载到存储器并链接。
事实上,
编译器和汇编器生成可重定位目标文件(包含共享目标文件)。链接器生成可执行目标文件。
1.3 Linux下目标文件文件格式:
由于编译后的中间文件(Windows下的.obj和Linux下的.o),动态链接库和静态链接库(Windows下的.lib.dll和Linux下的.a.so),跟可执行文件的内容和结构相似,所以一般跟可执行文件一起
采用同一种文件格式存储。
1.4 Linux下有3种主要的可执行目标文件格式:
a.out(assembler and link editor output 汇编器和链接编辑器的输出)、COFF(Common Object File Format 通用对象文件格式)、ELF(Executable and Linking Format 可执行和链接格式)。老的可执行文件格式缺乏可扩展性,如不能包含“现代”可执行文件中常见的调试信息,所以基本上已被ELF格式取代。
现代Linux使用的可执行目标文件格式都是ELF。
1.5 Linux下ELF格式的目标文件有4种主要类型:
可重定位文件:可以被用来链接成可执行目标文件或共享目标文件的文件。扩展名.o或.a。
可执行文件:可以直接执行的程序。ELF可执行文件一般没有扩展名。
共享目标文件:可以在两种情况下使用:1
链接器(Linux系统中为ld)可以将它与其他可重定向目标文件、共享目标文件链接,形成新的目标文件;2
动态加载器(Linux系统中为/lib/ld-linux.so.2)(注:dynamic loader,在很多文章中将之称之为动态链接器,但我认为称为动态加载器比较好,这样不容易和链接器相混淆)(注:动态加载器所在的路径在可执行文件可以查到:readelf -l xxx | grep interpreter)可以将它与可执行目标文件结合,作为进程映像的一部分。扩展名.so。
core dump文件:。
图片选自:
进程中的地址是从何而来
1.6 Linux下ELF文件文件格式:
ELF文件格式提供了两种视图,分别是链接视图和执行视图。
链接视图是以节(section)为单位,执行视图是以段(segment)为单位。链接视图就是在链接时用到的视图,而执行视图则是在执行时用到的视图。上图左侧的视角是从链接来看的,右侧的视角是执行来看的。总个文件可以分为四个部分:
点击(此处)折叠或打开
-
-ELF header:描述整个文件的组织。
-
-Program Header Table:描述文件中的各种segment,用来告诉系统如何创建进程映像的。
-
-sections 或者 segments:segments是从运行的角度来描述elf文件,sections是从链接的角度来描述elf文件,也就是说,在链接阶段,我们可以忽略program header table来处理此文件,在运行阶段可以忽略section header table来处理此程序(所以很多加固手段删除了section header table)。从图中我们也可以看出,segments与sections是包含的关系,一个segment包含若干个section。
-
-Section Header Table:包含了文件各个section的属性信息,section名字、section大小、section在elf文件中的偏移等。
选自:
ELF文件格式解析
1.7 Linux下ELF文件中各个section的含义:
ELF文件中各个section的含义如下图所示:
选自:
进程中的地址是从何而来
2 常见问题
2.1 Linux下ELF文件和.BIN文件的区别(注:通过如上对ELF的描述,ELF有两种视角:可执行、可链接,而通常讲的、也是这里讲的是ELF可执行的视角):
.BIN文件:是raw binary文件,这种文件只包含机器码。
执行时,只需要将程序加载到其起始地址,一般为0x00地址,就可以执行。
ELF文件:除了机器码外,还包含其它额外的信息,如段的加载地址,运行地址,重定位表,符号表等。
执行时,需要一个ELF Loader。正因为uboot和Linux kernel启动的时候是没有ELF Loader的,所以烧在flash上的文件只能是raw binary格式的,即镜像文件image。
一般情况下,通过
gcc编译出来的是elf文件,而通过objcpy可以把elf文件转换为bin文件。
小结:
ELF文件包含了一些调试信息;.BIN文件是将ELF文件中的代码段、数据段,还有一些自定义段,抽取出来做成的一个内存镜像文件。
参考:
ELF文件和BIN文件。
2.2 Linux下.ko文件和.o文件的区别(注:这里.o狭义的指用于内核使用的动态链接文件):
.o文件:是object文件。
.ko文件:是kernel object文件。是Linux 2.6内核使用的动态链接文件,用于在Linux系统启动时动态的加载卸载内核模块。
小结:
.ko是linux 2.6内核编译之后生成的,相比linux 2.4内核编译生成的.o,其多了一些module信息,如author,license之类的。
2.3 Linux下ELF文件和.out文件的区别:
历史由来:a.out是"assembler output"的缩写格式,代表汇编程序输出。在较早版本的类unix系统中,a.out是一种输出格式,用于可执行文件,目标文件和共享库。早期的 PDP-7系统上没有链接器,程序的创建过程是先把所有源文件连接成一个文件,然后进行汇编,产生的汇编程序保存在a.out中。这样a.out是名副其实的汇编输出。
但到PDP-11之后,人们为其编写了链接器,程序的创建是先编译然后链接输出保存到a.out中,这时a.out其实已经是链接输出了,但输出的可执行文件仍然延续这个命名习惯。
后来,因为构建a.out的复杂性,
a.out格式被现在普遍使用的ELF格式所替代,但输出文件名仍旧是a.out。现在我们看到的a.out只是一个可执行文件,而不再是文件格式。
实际操作:当编译程序时,如果不加-o参数,生成的binary代码的名字都是默认的a.out。如:gcc helloworld.c
当然,也可以使用-o选项给生成的文件起一个别的名字。如:gcc helloworld.c -o helloword.out,或者gcc helloworld.c -o helloword
事实上,如上两种生成的a.out、helloworld.out、helloworld其实都是ELF文件格式的可执行目标文件。
参考:
linux c/c++ a.out的由来。
2.4 Linux下ELF结构文件的确定:
ELF的可执行文件:第一个字节是八进制177也就是16进制的7F,紧跟其后的2,3,4字节是ELF三个字母。你可以
输入od -c a.out | head查看一下。如:a.out、helloworld.out、helloworld
ELF的其他目标文件,除.a文件:第一个字节是八进制177也就是16进制的7F,紧跟其后的2,3,4字节是ELF三个字母。如:helloworld.o、helloworld.so
ELF的.a文件:包含八进制177也就是16进制的7F,紧跟其后的2,3,4字节是ELF三个字母。如:helloworld.a
参考:linux c/c++ a.out的由来。
2.5 Linux下ELF文件的段和段表的区别关系:
ELF文件最重要的一个概念就是“段”(segment or section),例如:.text段、.data段、.bss段等等。
而,描述ELF文件中各个“段”的是“段表”(Section Header Table),包含了描述每个段的属性信息,比如段的名字、段的大小、段在elf文件中的偏移等。
另外,可以通过
readlef -S a.out或objdump -h a.out来查看ELF文件的段表;通过
readelf -s a.out或objdump -t a.out来查看ELF文件的符号表。如下是,常用的查看命令:
查看ELF文件
|
查看所有内容
|
|
readelf -a (-a=–all 相当于-e -r
-s)
|
查看header
|
all header
|
objdump -x (-x=–all-headers 包括file、section header以及符号表和重定位表)
|
readelf -e (-e=–headers 包括file、program、section header)
|
file header
|
objdump -f (-f=–file-headers)
|
readelf -h (-h=–file-header)
|
program header
|
readelf -l (-l=–program-header)
|
section header
|
objdump -h (-h=–section-headers)
|
readelf -S (-S=–section-headers)
|
readelf -t (-t=–section-details)
|
查看section
|
查看数据
|
objdump -s (-s=–full-contents 包括.text、.data、.rodata、.comment的二进制和ASCII码)
|
查看代码
|
objdump -d -z -r -l (-d=–disassemble, -z=–disassemble-zeroes, -r=–reloc, -l=–line-numbers 只反汇编.text段)
|
objdump -D (-D=–disassemble-all 不只反汇编.text段,也把.data、.bss、.rodata、.comment当成代码反汇编)
|
在objdump后加上 |
grep -A15 “” 可以查看某个函数的15行反汇编代码
|
查看符号表
|
objdump -t (-t=–syms)
|
readelf -s (-s=–syms)
|
查看重定位表
|
objdump -r (-r=–reloc)
|
readelf -r (-r=–relocs)
|
查看某section内容
|
objdump -j .text -s/-d (根据section是数据还是代码)
|
|
|
|
选自:
程序员的自我修养:(1)目标文件
阅读(3744) | 评论(0) | 转发(1) |