ELF 可执行文件:许多零字节

Kol*_*.Ne 5 linux executable elf executable-format

介绍

我正在编译一个简单的汇编代码(Intel 语法、x86、Linux)打印“Hello World!”。这里是:

SECTION .rodata
    msg:        db 'Hello world!', 0xA
    msg_len:    equ $ - msg

SECTION .text
    global _start

_start:
    mov eax, 4  ; `write` system call
    mov ebx, 1  ; to stdout
    mov ecx, msg
    mov edx, msg_len
    int 0x80

    mov eax, 1  ; `exit` system call
    xor ebx, ebx    ; exit code 0
    int 0x80
Run Code Online (Sandbox Code Playgroud)

我使用以下命令编译它:

SECTION .rodata
    msg:        db 'Hello world!', 0xA
    msg_len:    equ $ - msg

SECTION .text
    global _start

_start:
    mov eax, 4  ; `write` system call
    mov ebx, 1  ; to stdout
    mov ecx, msg
    mov edx, msg_len
    int 0x80

    mov eax, 1  ; `exit` system call
    xor ebx, ebx    ; exit code 0
    int 0x80
Run Code Online (Sandbox Code Playgroud)

代码运行良好,但我担心的是文件大小:

nasm -f elf32 -o hello_world.o hello_world.s
ld -m elf_i386 -o hello_world hello_world.o
Run Code Online (Sandbox Code Playgroud)

问题

目标文件比源代码稍大,但似乎合理,因为ELF文件中应该有一些元数据或其他东西,源代码不包含,对吧?但是可执行文件甚至比目标文件还要大 10 倍以上!

此外,目标文件中有一些零字节,但我不会说它们太多。但是,可执行文件中有很多零(请参阅本Additional info节中两者的屏幕截图)。

调查

我曾尝试阅读一些关于 ELF 的文章,包括维基百科和手册页。我没有仔细阅读所有这些,所以我可能错过了一些东西,但我发现有用的是dumpelf实用程序(来自pax-utils包,可通过 安装apt),我使用它转储了我的精灵文件并找到了一些可能是这些零流的原因:

在可执行文件的所有三个标头中,都有p_align字段集:

.p_align  = 4096       , /* (min mem alignment in bytes) */
Run Code Online (Sandbox Code Playgroud)

这应该意味着每个部分都应该用零字节填充,以便其长度是 4096 的倍数。并且由于以下每个部分的大小相对较小,因此要添加很多零字节,那就是这些零来自哪里。

问题)

所以,我想知道:

  1. 我对吗?是否添加了这些零字节以使这些部分足够长?

    我还注意到前三个部分 ( '', '.rodata', '.text')分别从0,4096和开始8192,但接下来的部分 ( '.symtab', '.strtab', '.shstrtab') 似乎不再对齐:它们开始于8208,83688422... 为什么?这里发生了什么?

  2. 我们需要这种对齐方式是为了什么?在编程头中,有p_vaddrp_paddr字段设置为前三个部分开始的地址,那么如果我们已经从头中知道了部分的确切地址,那么对齐部分的原因是什么?它与内存页(在我的机器上大小为 4KiB)有关吗?

  3. 我什么时候想要/需要,以及如何更改对齐值?看起来应该有一个链接器参数来更改此值。我--nmagicld手册中找到了参数,它完全禁用了对齐(并且,万岁!,可执行文件与目标文件的大小相同),但我猜对齐是故意存在的,所以也许我只需要降低值以使其更适合我的情况?

如果您知道我遗漏了什么,我非常感谢您回答这些问题中的任何一个或任何其他细节。也请告诉我我是否在任何地方错了。先感谢您!

附加信息

我的目标文件的转储(带有xxd hello_world.o | grep -E '0000|$' --color=always | less -R):

hello_world.o 的十六进制转储

我的可执行文件转储的一部分(使用类似于上面的命令):一个新的部分从地址 0x1000 开始

hello_world 的十六进制转储

的输出dumpelf hello_world.o

-rwxrwxr-x 1 nikolay nikolay 8704 Apr 27 15:20 hello_world
-rw-rw-r-- 1 nikolay nikolay  243 Apr 26 22:16 hello_world.s
-rw-rw-r-- 1 nikolay nikolay  640 Apr 27 15:20 hello_world.o
Run Code Online (Sandbox Code Playgroud)

的输出dumpelf hello_world

.p_align  = 4096       , /* (min mem alignment in bytes) */
Run Code Online (Sandbox Code Playgroud)

小智 2

对齐方式为 4096 字节,这是该架构上的页面大小。这并非巧合,正如手册页中关于 nmagic 的描述:“关闭各节的页面对齐”。

通过正常(非 nmagic)二进制文件的大小,您可以猜测链接器布局了三个页面,大概具有不同的访问权限(代码 = 不可写,数据 = 不可执行,rodata = 只读),这些权限只能按每个设置-页。运行时磁盘布局与 RAM 中的布局相匹配。

这对于需求分页很重要。当程序启动时,整个可执行文件基本上被映射,并根据需要通过页面错误从磁盘加载页面。此外,页面可以在其他正在运行的实例之间共享(这对于动态库来说更重要),并且可以在需要时由于内存压力而从 RAM 中逐出。

nmagic 可执行文件在运行时仍会加载到三个页面中,但由于这些页面不再与磁盘上的内容匹配,因此不会按需分页。我不建议在更大的东西上使用该选项。

注意:如果您制作一个运行时间较长的可执行文件(可能添加输入读取),您可以通过查看 /proc/[pid]/maps 和 smaps 来检查正在运行的进程的内存布局详细信息。