ELF程序头段大小和偏移量

7

我正在尝试理解ELF格式,目前我对程序头中定义的段有些疑问。我有一小段代码,用g++(在Linux上的x86_x64)将其转换为ELF文件:

#include <stdlib.h>
#include <iostream>

using namespace std;

int main(int argc, char *argv[])
{
    if (argc == 1)
    {
        cout << "Hello world!" << endl;
    }
    return 0;
}

使用命令 g++ -c -m64 -D ACIS64 main.cpp -o main.og++ -s -O1 -o Main main.o 编译。 现在,使用readelf命令获取以下段的列表:

Program Headers:
Type           Offset             VirtAddr           PhysAddr
               FileSiz            MemSiz             Flags      Align
PHDR           0x0000000000000040 0x0000000000400040 0x0000000000400040
               0x00000000000001f8 0x00000000000001f8 R E        8
INTERP         0x0000000000000238 0x0000000000400238 0x0000000000400238
               0x000000000000001c 0x000000000000001c R          1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
               0x0000000000000afc 0x0000000000000afc R E        200000
LOAD           0x0000000000000df8 0x0000000000600df8 0x0000000000600df8
               0x0000000000000270 0x00000000000003a0 RW         200000
DYNAMIC        0x0000000000000e18 0x0000000000600e18 0x0000000000600e18
               0x00000000000001e0 0x00000000000001e0 RW         8
NOTE           0x0000000000000254 0x0000000000400254 0x0000000000400254
               0x0000000000000044 0x0000000000000044 R          4
GNU_EH_FRAME   0x00000000000009a4 0x00000000004009a4 0x00000000004009a4
               0x0000000000000044 0x0000000000000044 R          4
GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
               0x0000000000000000 0x0000000000000000 RW         10
GNU_RELRO      0x0000000000000df8 0x0000000000600df8 0x0000000000600df8
               0x0000000000000208 0x0000000000000208 R          1

通过Bless Hex Editor,我正在查看代码并尝试找到每个片段。
  • 我在ELF头之后找到了PHDR段,并且它有整个程序头的大小。它具有8字节的对齐方式,并且可读/可执行。[!] 我不明白为什么可执行PHDR

  • 我找到了解释器声明的段,就在PHDR之后。它具有解释器路径的大小和1字节的对齐方式。正确
    INTERP

  • 现在我有了一个可读和可执行的段,[!]我认为这是代码段。我不明白为什么它从0x0000000000000000开始。难道这不应该从入口点开始吗?为什么它的大小是0xafc字节?文件的可执行部分有多少?另外,我不明白为什么对齐方式是0x200000字节。这是为一个在内存中保留的LOAD段预留的空间吗? 这就是这个段的结束位置,之后跟着764个0x0字节:
    LOAD1

  • 下一个(可读/可写)的[!]我认为是存储变量的段。它在某个类似于部分标头开始的地方结束
    LOAD2
  • 现在下一个是动态头部。它从上面那个里面开始,位置是0xe18。[!]我认为这是一个存储对外部函数和变量引用的段,但我不确定。它可读/可写。我不知道这个段是什么以及为什么它“在”上面的LOAD段内 DYNAM
  • 一个NOTE段,包含一些我认为现在不重要的信息
  • GNU特定段,其中一个偏移和大小都等于0x0000000000000000,其他干扰其他段,我也不理解。

我来自PE世界,在那里每件事都有自己定义良好的偏移量和大小,而在这里我看到这些奇怪的地址和大小,我感到困惑。

1个回答

12
readelf输出显示程序头表。它包含ELF文件中段(可加载或不可加载的)的列表。一个段通常包含其他段,正如在这里看到的那样。
引用: 我发现PHDR段紧跟在ELF头之后,并具有整个程序头的大小。它的对齐方式为8字节,可读/可执行。[!]我不明白为什么是可执行的。
如果仔细阅读readelf输出,您会注意到PHDR实际上是代码段的一部分(请注意VirtAddr和MemSiz字段)。这就解释了为什么它与代码段(RX)共享相同的权限。
现在我有一个可读和可执行的段,我想这就是代码段。我不明白为什么它从0x0000000000000000开始。难道这不应该从入口点开始吗?为什么它的大小是0xafc字节?大小不是只有代码的大小吗?文件中有多少是可执行的?此外,我不明白为什么对齐方式是0x200000字节。这是在内存中为一个LOAD段保留了多少空间吗?这就是这个段的结束位置,后面跟着764个0x0字节。
是的,这是代码段。它从文件开头(即偏移量0)开始,并延伸到文件中的0xafc字节。标头指定了当加载ELF时,文件的这一部分映射到内存中的0x0000000000400000。该段不仅包含来自C++文件的main(),编译器还添加了其他可执行内容。对齐只指定下一个段应该从哪里开始,而不是段的大小。可装载段的VirtAddr和PhysAddr字段模除页面大小(或Align字段,如果Align!= 0 && Align!= 1)应具有同余值。这就解释了为什么数据段的VirtAddr为0x0000000000600df8(0x0000000000600df8-0x0000000000000df8%0x200000 == 0)。文本段和数据段之间文件中的区域(即在0xafc和0xdf8之间)填充了零。
下一个(可读写的)段,我想是变量存储的段。它的结束位置恰好是类似于段头的东西开始的地方。

正确,这是存储全局和静态变量(以及其他东西)的数据段。它在节标题之前结束。

现在下一个是动态头。它从0xe18开始,位于上面的内部。[!]我认为这是存储对外部函数和变量的引用的段,但我不确定。它是可读写的。我只是不知道这是哪个段,以及为什么它“在”上面的LOAD段中

就像PHDR段是代码段的一部分一样,DYNAMIC段是数据段的一部分。这就是为什么权限相同(RW)。它包含.dynamic部分,其中包含诸如符号和字符串表的地址数组。

GNU特定的段,其中一个具有任何偏移量和大小等于0x0000000000000000,其他干扰其他段,我也不明白。

GNU_EH_FRAME是代码段的一部分,而GNU_RELRO是数据段的一部分(请参阅VirtAddr和MemSiz字段)。GNU_STACK只是一个程序头,告诉系统如何在将ELF加载到内存时控制堆栈(FileSiz和MemSiz为0)。
参考资料:
  1. ELF文件格式规范
  2. 链接器和装载器,约翰·R·莱文

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接