基础意思
- Segment 段,不是指平时所说的代码段,数据段。在ELF格式里面,他表示某一块具有相同属性的节的集合,所谓属性主要是可读、可写、可执行等。比如可读可执行段
- Section 节,比如.data .bss叫做节 其实不是段
其他引用:
ELF 程序头是对二进制文件中段的描述,是程序装载必需的一部分。段(segment)是在内核装载时被解析的,描述了磁盘上可执行文件的内存布局以及如何映射到内存中。可以通过引用原始 ELF 头中名为 e_phoff(程序头表偏移量)的偏移量来得到程序头表
所以Segment本质是作为一个节簇,使用某种相同的方式加载到内存中的一个整体操作单元。代表的是加载属性。影响的是加载时期的行为。运行时期的行为是节来其主要作用的。
参考描述
所以还是用中文表达不准确。先看一段说明:参考如下描述:
节,不是段。段是程序执行的必要组成部分。
在每个段中,会有代码或者数据被划分为不同的节。节头表是对这些节的位置和大小的描述,主要用于链接和调试。节头对于程序的执行来说不是必需的,没有节头表,程序仍可以正常执行,因为节头表没有对程序的内存布局进行描述,对程序内存布局的描述是程序头表的任务。节头是对程序头的补充。readelf –l 命令可以显示一个段对应有哪些节,可以很直观地看到节和段之间的关系。
每一个节都保存了某种类型的代码或者数据。数据可以是程序中的全局变量,也可以是链接器所需要的动态链接信息。每个 ELF 目标文件都有节,但是不一定有节头,尤其是有人故意将节头从节头表中删除了之后。当然,默认是有节头的。
通常情况下,这是由于可执行文件被篡改导致的(如去掉节头来增加调试的难度)。GNU 的 binutils 工具,像 objcopy、objdump,还有 gdb 等,都需要依赖节头定位到存储符号数据的节来获取符号信息。如果没有节头,gdb和 objdump 这样的工具几乎无用武之地。
节头便于我们更细粒度地检查一个 ELF 目标文件的某部分或者某节。事实上,有了节头,一些需要使用节头的工具,如 objdump 等,就能为逆向工程带来很多便利。如果去掉了节头表,就无法获取像.dynsym 这样的节,而在.dynsym 节中包含了描述函数名和偏移量/地址的导入/导出符号。
–《Linux二进制分析》
实操例子
下面是一个普通c函数编译后的效果:
c语言:
#include "stdio.h"
int main()
{
printf("haha\n");
return 0;
}
二进制程序头 readelf -l
# readelf -l a.out
Elf file type is DYN (Shared object file)
Entry point 0x530
There are 9 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000000040 0x0000000000000040
0x00000000000001f8 0x00000000000001f8 R 0x8
INTERP 0x0000000000000238 0x0000000000000238 0x0000000000000238
0x000000000000001c 0x000000000000001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000830 0x0000000000000830 R E 0x200000
LOAD 0x0000000000000db8 0x0000000000200db8 0x0000000000200db8
0x0000000000000258 0x0000000000000260 RW 0x200000
DYNAMIC 0x0000000000000dc8 0x0000000000200dc8 0x0000000000200dc8
0x00000000000001f0 0x00000000000001f0 RW 0x8
NOTE 0x0000000000000254 0x0000000000000254 0x0000000000000254
0x0000000000000044 0x0000000000000044 R 0x4
GNU_EH_FRAME 0x00000000000006ec 0x00000000000006ec 0x00000000000006ec
0x000000000000003c 0x000000000000003c R 0x4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 0x10
GNU_RELRO 0x0000000000000db8 0x0000000000200db8 0x0000000000200db8
0x0000000000000248 0x0000000000000248 R 0x1
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame
03 .init_array .fini_array .dynamic .got .data .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06 .eh_frame_hdr
07
08 .init_array .fini_array .dynamic .got
正如前面表达的,Segment段是影响加载期间的,所以在上面程序头每个段有flags是加载为可读可写可执行的某些权限。(注意这里加载到内存后,在linux按照页映射到物理内存,页表里面就会指定权限,从而进一步就会影响平时经常出现的Segment Fault,所谓的段错误就是一个地址访问的时候发现不是合法的,怎么判断不是合法的,就是通过虚拟地址访问页表访问到物理页的时候页的属性可能不具有写的属性,就是段错误了。本来操作数据段 结果操作到了其他段)
ELF文件中程序头对应的就是段Segment,第03个Segment就是我们常说的数据段 data segment。然后数据段包含了 .data节和.bss节。并且.bss 节总是放在 data 段的末尾,这是链接器脚本决定的。
另外所谓的代码段不仅仅不好的是代码,比如.rodata是存储类似printf中的常量字符串,这个看似是数据段的,实际在代码段。
这些都是链接器脚本决定的,一个 ELF 可执行文件,链接器脚本能够决定该输出文件的布局,以及每个段里面包含哪些节。
综述
所以,ELF文件中的段Segment和节Section的区别是什么?平时说的代码段和数据段是Section还是Segment?.bss .data是什么?
- ELF文件中的段Segment和节Section的区别是什么?
Segment是ELF加载到内存时候映射使用的控制属性的单元,是节的集合。影响的是加载期间的行为。(当然加载后段的属性 也会控制安全 比如段错误 而不是节错误)
Section是根据功能组织的程序单元,也是我们平时谈及所谓的"段"(代码段、数据段),真正正的代码段是多个section组合出来的section。影响的运行期间的行为。
- 平时说的代码段和数据段是Section还是Segment?
平时说的代码段和数据段是section节,比如.bss节,.data节 .text节,但是.bss .data都在数据段,但是.rodata不在数据段。平时所说的真正数据段,是ELF的程序头,属性是DYNAMIC,权限flags是R W,包含了.bss .data等。平时所谓的代码段 属性也是DYNAMIC flags是R E。所以我们拿到某个函数地址 他所在的段映射到内存的地址就有E权限。
- .bss .data是什么?
是section 节,不是所谓的代码段,或者说他是代码段,但不是所有。是属于代码段。
最后记住readlef -l 是查看程序头的
参考:
https://bbs.kanxue.com/upload/attach/202007/767964_J5AKCEMWBFUQ655.pdf