静态布局和动态布局
这里分析可执行文件,静态布局是指可执行文件在存储器上的内部结构,
动态布局是指程序被加载到内存上的动态布局
可执行文件包含包含了程序编译形成的指令和数据,这是对可执行文件的粗略的理解,这里我会更加详细的探究其内部
Linux 中,可执行文件的标准是 ELF(Excutable and Linkable Foramt)格式
ELF的静态布局
ELF文件结构包含一个固定长度的文件头和多个可以拓展的数据块
文件头是可执行文件总地图,描述了整个文件的组织结构
数据块分为两种视图 如下图所示:
链接视图和执行视图.png
- 链接视图下,数据块的单位是节(section), 用节区头索引其他内容
- 执行视图下,数据块的单为是段(segment), 用程序头索引所有的段
下面以一个在 Ubuntu 上用gcc 编译出来的ELF文件为例,剖析一下bin文件的内部
ELF 头文件
使用 readelf -h 可以读取ELF 头文件的内容
ELF_header.png
• 生成的可执行文件的大小是 6836 字节,文件格式是 64 位的 Linux 标准可执行文件(ELF 64-bit LSB Executable),目标平台是 x86-64。
• 程序的入口地址是 0x770,该文件的 ELF 头的大小是 64 字节。
• 文件中有 9 个 Program Header,每个 Program Header 的长度是 56 字节 ,信息存放在从文件头算起 56 字节的位置。
• 另外还有 35 个 Section Header,每个 Section Header 的大小是 64 字节,信息位于从文件头算起 14592 字节的位置。
程序头和节区头
使用 readelf -S 读取ELF文件的节区头信息
ELF_sectionheader.png
Type : 表示节区的类型
Address: 表示该节区占用虚拟内存的起始地址
Offset: 该节区在可执行文件中的存放位置
Size: 节区
使用 readelf -l 命令读取bin文件的程序头表信息
ELF_programeheader.png
表示该可执行文件有九个段,并且列出了每个段在文件中的起始地址(Offset 列)、程序加载后占据的虚拟地址(VirtAddr 列)、物理地址(PhysAddr 列)、在文件中占据的空间大小(FileSiz 列)、在内存中占据的空间大小(MemSiz 列)等信息,另外,还列出了节区与段的映射关系,指出了哪些节区在运行时会归入哪个段。
其他分析指令
objdump -s -j sectionName dump ELF 文件 section 内容
比如: objdump -s -j .rodata demobin 可以dump出 demobin 的 rodata 节区
静态布局总结
到这里,ELF文件的静态布局就已经分析完了,我们可以在总结一下,可执行文件从前到后依次为 ELF文件头,程序头表,多个节区,节区头表,最后编译系统还会附加.symtab 和 .strtab 节区
影响静态布局的因素
.text 节区
.text 节区存放的是程序源码编译后产生的机器指令,所有的程序逻辑都会存放在这里
编译时可以添加优化选项,使用 -O1 优化选项编译程序可以生成尽量紧凑的 .text 节区,而用 -O2 优化选项会使编译器倾向于生成执行速度更快的指令组合,但有可能让 .text 节区的体积轻微地增大。
.rodata 节区
存储了程序中的常量数据,比如下面的常量数组
比如下面的语句:
const char ptr[] = "demo data tdebug";
会最终编译到.rodata 段
ELF_rodata.png
程序运行的时候 .rodata 和 .text section 都会被加载到相同的 segment 中,它们的权限只有读和执行,如果强制写会触发 segment fault 错误.
但需要注意的是,只有静态和全局的 const 数据才能享受这样的待遇,如果在函数内部声明 const value, 其本质上还是一个函数内的局部变量,存储区在该函数的栈帧内,而程序对该内存区拥有修改的权限。
.data 节区
存放全局和静态的已经初始化的变量,比如下面的语句:
char ptr[] = "demo data tdebug";
使用objdump 验证:
ELF_data.png
.bss 节区
该节区存储了所有未初始化或初始化为 0 的全局和静态变量
.got和 .glt 节区
这两个节区存储了动态链接用到的全局入口表和跳转表。当程序中用到动态链接库中的某个函数时,会在该节区内记录相应的数据
网友评论