MyException - 我的异常网
当前位置:我的异常网» C语言 » 学习一门新语言亟需了解的基础-03 可执行文件结构

学习一门新语言亟需了解的基础-03 可执行文件结构

www.MyException.Cn  网友分享于:2013-10-19  浏览:0次
学习一门新语言需要了解的基础-03 可执行文件结构

03 可执行文件结构

本节内容

  • 通用可执行文件结构(COFF)(readelf -h)
  • COFF用段(section)存储不同类型数据(readelf -S)
  • 常用段
  • 演示:使用readelf、xxd、objdump、gdb查看可执行文件结构信息[付费阅读]
  • 演示:objcopy -add-section;strip -remove-section;readelf -p[付费阅读]

编译完成了,链接完成了,我们现在得到可执行文件了,接下来问题是可执行文件是什么样子?

通用可执行文件结构(COFF)(readelf -h)

$ cat hello.c

这是很简单的c语言代码,有两个引入标准库的头,三个全局变量,x有初始化值,y没有初始化值,s是个字符串。

#include <stdio.h>
#include <stdlib.h>

int x = 0x1234;
int y;
char *s = "x = %x, y = %x\n";

int main()
{
    printf(s, x, y);

    return 0;
}

编译成可执行文件,包含调试信息,禁止优化

$ gcc -g -O0 -o test hello.c
$ ./test

这个可执行文件到底什么样子?先看可执行文件是怎么描述自己的,使用readelf -h查看可执行文件头部信息。

$ readelf -h test #输出可执行文件头部信息

输出

ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x400430
  Start of program headers:          64 (bytes into file)
  Start of section headers:          7512 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         36
  Section header string table index: 33

Magic标识用于判断是否是标准ELF文件。
Class代表是64位可执行文件。
Entry point address代表第一条执行指令地址,正好指向.text段。
下面描述可执行文件结构的,一共有多少节,各自尺寸,从哪开始。

我们把这种看上去像数据库这种格式的可执行文件通常称之为COFF,COFF是通用可执行文件结构,不同的公司对它做具体的一些定制,比如说Linux把它的改进版本称之为ELF,Windows的改进版本称之为PE,其实它们都属于COFF范畴,只不过每家公司对它细节处理不一样,但是它格式基本上类似的。

COFF用段(section)存储不同类型数据(readelf -S)

$ readelf -S test #输出可执行文件段信息

输出

There are 36 section headers, starting at offset 0x1d58:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000400238  00000238
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.ABI-tag     NOTE             0000000000400254  00000254
       0000000000000020  0000000000000000   A       0     0     4
  [ 3] .note.gnu.build-i NOTE             0000000000400274  00000274
       0000000000000024  0000000000000000   A       0     0     4
  [ 4] .gnu.hash         GNU_HASH         0000000000400298  00000298
       000000000000001c  0000000000000000   A       5     0     8
  [ 5] .dynsym           DYNSYM           00000000004002b8  000002b8
       0000000000000060  0000000000000018   A       6     1     8
  [ 6] .dynstr           STRTAB           0000000000400318  00000318
       000000000000003f  0000000000000000   A       0     0     1
  [ 7] .gnu.version      VERSYM           0000000000400358  00000358
       0000000000000008  0000000000000002   A       5     0     2
  [ 8] .gnu.version_r    VERNEED          0000000000400360  00000360
       0000000000000020  0000000000000000   A       6     1     8
  [ 9] .rela.dyn         RELA             0000000000400380  00000380
       0000000000000018  0000000000000018   A       5     0     8
  [10] .rela.plt         RELA             0000000000400398  00000398
       0000000000000030  0000000000000018  AI       5    24     8
  [11] .init             PROGBITS         00000000004003c8  000003c8
       000000000000001a  0000000000000000  AX       0     0     4
  [12] .plt              PROGBITS         00000000004003f0  000003f0
       0000000000000030  0000000000000010  AX       0     0     16
  [13] .plt.got          PROGBITS         0000000000400420  00000420
       0000000000000008  0000000000000000  AX       0     0     8
  [14] .text             PROGBITS         0000000000400430  00000430
       00000000000001a2  0000000000000000  AX       0     0     16
  [15] .fini             PROGBITS         00000000004005d4  000005d4
       0000000000000009  0000000000000000  AX       0     0     4
  [16] .rodata           PROGBITS         00000000004005e0  000005e0
       0000000000000014  0000000000000000   A       0     0     4
  [17] .eh_frame_hdr     PROGBITS         00000000004005f4  000005f4
       0000000000000034  0000000000000000   A       0     0     4
  [18] .eh_frame         PROGBITS         0000000000400628  00000628
       00000000000000f4  0000000000000000   A       0     0     8
  [19] .init_array       INIT_ARRAY       0000000000600e10  00000e10
       0000000000000008  0000000000000000  WA       0     0     8
  [20] .fini_array       FINI_ARRAY       0000000000600e18  00000e18
       0000000000000008  0000000000000000  WA       0     0     8
  [21] .jcr              PROGBITS         0000000000600e20  00000e20
       0000000000000008  0000000000000000  WA       0     0     8
  [22] .dynamic          DYNAMIC          0000000000600e28  00000e28
       00000000000001d0  0000000000000010  WA       6     0     8
  [23] .got              PROGBITS         0000000000600ff8  00000ff8
       0000000000000008  0000000000000008  WA       0     0     8
  [24] .got.plt          PROGBITS         0000000000601000  00001000
       0000000000000028  0000000000000008  WA       0     0     8
  [25] .data             PROGBITS         0000000000601028  00001028
       0000000000000020  0000000000000000  WA       0     0     8
  [26] .bss              NOBITS           0000000000601048  00001048
       0000000000000008  0000000000000000  WA       0     0     4
  [27] .comment          PROGBITS         0000000000000000  00001048
       0000000000000034  0000000000000001  MS       0     0     1
  [28] .debug_aranges    PROGBITS         0000000000000000  0000107c
       0000000000000030  0000000000000000           0     0     1
  [29] .debug_info       PROGBITS         0000000000000000  000010ac
       00000000000000de  0000000000000000           0     0     1
  [30] .debug_abbrev     PROGBITS         0000000000000000  0000118a
       000000000000005c  0000000000000000           0     0     1
  [31] .debug_line       PROGBITS         0000000000000000  000011e6
       000000000000003e  0000000000000000           0     0     1
  [32] .debug_str        PROGBITS         0000000000000000  00001224
       00000000000000c4  0000000000000001  MS       0     0     1
  [33] .shstrtab         STRTAB           0000000000000000  00001c06
       000000000000014c  0000000000000000           0     0     1
  [34] .symtab           SYMTAB           0000000000000000  000012e8
       0000000000000708  0000000000000018          35    52     8
  [35] .strtab           STRTAB           0000000000000000  000019f0
       0000000000000216  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

假如可执行文件是个数据库的话,这里面一大堆的表,这些表看上去有点晕,不知道里面到底什么东西。

我们知道所谓的段就是这些,每个段存储不同类型的数据,这里面有些信息可以看到,每一节的具体描述:Name名称、Type类型、Address起始地址、Offset相对于当前可执行文件的偏移量、Size长度、Flags权限开关。这些数据表里面有几个东西需要注意的,这些数据信息的数量和名字不是固定的,每种编译器都有自己的规则,这个信息对于编译器、反汇编有用,CPU不关心这些,CPU只关心地址从哪开始读多长数据到哪结束。至于你这东西属于哪个数据表叫什么名字跟CPU一点关系也没有,这些东西实际上是给编译器用的,但是这些名字虽然每种编译器有各自的习惯,也不太一样,但是有几个是约定俗成的。

常用段

有几个名字是约定俗成的

  • .text存储的全部是机器码。
  • .data存储的是有初始化值的全局变量,静态局部变量。
  • .bss存储没有初始化值的全局变量。
  • .rodata存储的是只读数据,比如字面量。
$ readelf -x .text test #查看.text段机器指令内容

输出

Hex dump of section '.text':
  0x00400430 31ed4989 d15e4889 e24883e4 f0505449 1.I..^H..H...PTI
  0x00400440 c7c0d005 400048c7 c1600540 0048c7c7 ....@.H..`.@.H..
  0x00400450 26054000 e8b7ffff fff4660f 1f440000 &.@.......f..D..
  0x00400460 b84f1060 0055482d 48106000 4883f80e .O.`.UH-H.`.H...
  0x00400470 4889e576 1bb80000 00004885 c074115d H..v......H..t.]
  0x00400480 bf481060 00ffe066 0f1f8400 00000000 .H.`...f........
  0x00400490 5dc30f1f 4000662e 0f1f8400 00000000 ]...@.f.........
  0x004004a0 be481060 00554881 ee481060 0048c1fe .H.`.UH..H.`.H..
  0x004004b0 034889e5 4889f048 c1e83f48 01c648d1 .H..H..H..?H..H.
  0x004004c0 fe7415b8 00000000 4885c074 0b5dbf48 .t......H..t.].H
  0x004004d0 106000ff e00f1f00 5dc3660f 1f440000 .`......].f..D..
  0x004004e0 803d610b 20000075 11554889 e5e86eff .=a. ..u.UH...n.
  0x004004f0 ffff5dc6 054e0b20 0001f3c3 0f1f4000 ..]..N. ......@.
  0x00400500 bf200e60 0048833f 007505eb 930f1f00 . .`.H.?.u......
  0x00400510 b8000000 004885c0 74f15548 89e5ffd0 .....H..t.UH....
  0x00400520 5de97aff ffff5548 89e58b15 1c0b2000 ].z...UH...... .
  0x00400530 8b0d020b 2000488b 05030b20 0089ce48 .... .H.... ...H
  0x00400540 89c7b800 000000e8 b4feffff b8000000 ................
  0x00400550 005dc366 2e0f1f84 00000000 000f1f00 .].f............
  0x00400560 41574156 4189ff41 5541544c 8d259e08 AWAVA..AUATL.%..
  0x00400570 20005548 8d2d9e08 20005349 89f64989  .UH.-.. .SI..I.
  0x00400580 d54c29e5 4883ec08 48c1fd03 e837feff .L).H...H....7..
  0x00400590 ff4885ed 742031db 0f1f8400 00000000 .H..t 1.........
  0x004005a0 4c89ea4c 89f64489 ff41ff14 dc4883c3 L..L..D..A...H..
  0x004005b0 014839eb 75ea4883 c4085b5d 415c415d .H9.u.H...[]A\A]
  0x004005c0 415e415f c390662e 0f1f8400 00000000 A^A_..f.........
  0x004005d0 f3c3                                ..

.bss在文件内没有存储,可查看offset确认

为何全局变量需要分开存储?全局变量实际上是要写到可执行文件里,.data和.bss,如果这个全局变量本身有初始化值是不是要找地方存起来,要不然程序执行时候根本不知道初始化值是多少,.data用来存储这些初始化具体的值,.bss虽然也是全局变量,但没有初始化值,没有初始化值没必要写到可执行文件里面,因为你根本没有数据,只有在运行期才初始化,你在编译时候根本没有值干嘛去写,所以它只是保留了.bss表用于映射地址信息,但是不存储里面任何数据,你会注意到它相对文件的偏移量与下个段节点的偏移量一样,就说明这里面根本没有数据,否则两个偏移量不可能相等。因为像int y;的值只有在运行期才被初始化或者给它一个莫名其妙随机值。既然没有初始化值我编译时候根本不需要类似1234这样的值保存起来,你不要忘了在没有执行之前这些有初始化值的数据必须保存起来,你保存在哪,肯定在可执行文件里面有个表存这些数据,否则数据执行时候数据从哪里来。

go区分有指针和没指针data(data、.noptrdata)、bss,便于GC操作[付费阅读]

演示:使用readelf、xxd、objdump、gdb查看可执行文件结构信息[付费阅读]

演示:objcopy -add-section;strip -remove-section;readelf -p[付费阅读]

这个系列的每篇文章有大半篇幅内容属于付费阅读。提供微信支付支付宝支付打赏50元备注留言手动提供付费文章访问密码。

文章评论

当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
程序员和编码员之间的区别
程序员和编码员之间的区别
每天工作4小时的程序员
每天工作4小时的程序员
十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
那些性感的让人尖叫的程序员
那些性感的让人尖叫的程序员
不懂技术不要对懂技术的人说这很容易实现
不懂技术不要对懂技术的人说这很容易实现
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
5款最佳正则表达式编辑调试器
5款最佳正则表达式编辑调试器
为什么程序员都是夜猫子
为什么程序员都是夜猫子
程序员都该阅读的书
程序员都该阅读的书
做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
程序员的鄙视链
程序员的鄙视链
鲜为人知的编程真相
鲜为人知的编程真相
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
编程语言是女人
编程语言是女人
 程序员的样子
程序员的样子
10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
“肮脏的”IT工作排行榜
“肮脏的”IT工作排行榜
什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
我跳槽是因为他们的显示器更大
我跳槽是因为他们的显示器更大
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
漫画:程序员的工作
漫画:程序员的工作
“懒”出效率是程序员的美德
“懒”出效率是程序员的美德
一个程序员的时间管理
一个程序员的时间管理
老程序员的下场
老程序员的下场
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
我的丈夫是个程序员
我的丈夫是个程序员
程序员应该关注的一些事儿
程序员应该关注的一些事儿
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
如何成为一名黑客
如何成为一名黑客
程序员必看的十大电影
程序员必看的十大电影
Google伦敦新总部 犹如星级庄园
Google伦敦新总部 犹如星级庄园
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
老美怎么看待阿里赴美上市
老美怎么看待阿里赴美上市
旅行,写作,编程
旅行,写作,编程
程序员的一天:一寸光阴一寸金
程序员的一天:一寸光阴一寸金
程序员眼里IE浏览器是什么样的
程序员眼里IE浏览器是什么样的
写给自己也写给你 自己到底该何去何从
写给自己也写给你 自己到底该何去何从
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
2013年美国开发者薪资调查报告
2013年美国开发者薪资调查报告
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
2013年中国软件开发者薪资调查报告
2013年中国软件开发者薪资调查报告
Java程序员必看电影
Java程序员必看电影
那些争议最大的编程观点
那些争议最大的编程观点
代码女神横空出世
代码女神横空出世
要嫁就嫁程序猿—钱多话少死的早
要嫁就嫁程序猿—钱多话少死的早
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有