.rodata 部分加载到可执行页面

.rodata section loaded in executable page

所以出于好奇,我今天尝试运行这段代码(用gcc -m32 1.c编译):

int main(void)
{
    // EB is the opcode for jmp rel/8
    // FE is hex for -2
    // So this is essentially an infinite loop

    ((void(*)(void))"\xEB\xFE")();
}

... 成功了!没有段错误,程序(正确?)进入无限循环。查看反汇编 (objdump -d a.out),您可以看到调用...地址 0x8048480:

中的任何内容
080483d6 <main>:
 ....
 80483e7:   b8 80 84 04 08          mov    [=11=]x8048480,%eax
 80483ec:   ff d0                   call   *%eax
 ....

objdump -s -j .rodata a.out 给出:

Contents of section .rodata:
 8048478 03000000 01000200 ebfe00             ...........
                           ~~~~  

所以它确实在执行存储在.rodata部分的字符串。所以我 运行 readelf --sections a.out 得到:

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        08048154 000154 000013 00   A  0   0  1
  [ 2] .note.ABI-tag     NOTE            08048168 000168 000020 00   A  0   0  4
  [ 3] .note.gnu.build-i NOTE            08048188 000188 000024 00   A  0   0  4
  [ 4] .gnu.hash         GNU_HASH        080481ac 0001ac 000020 04   A  5   0  4
  [ 5] .dynsym           DYNSYM          080481cc 0001cc 000040 10   A  6   1  4
  [ 6] .dynstr           STRTAB          0804820c 00020c 000045 00   A  0   0  1
  [ 7] .gnu.version      VERSYM          08048252 000252 000008 02   A  5   0  2
  [ 8] .gnu.version_r    VERNEED         0804825c 00025c 000020 00   A  6   1  4
  [ 9] .rel.dyn          REL             0804827c 00027c 000008 08   A  5   0  4
  [10] .rel.plt          REL             08048284 000284 000008 08  AI  5  23  4
  [11] .init             PROGBITS        0804828c 00028c 000023 00  AX  0   0  4
  [12] .plt              PROGBITS        080482b0 0002b0 000020 04  AX  0   0 16
  [13] .plt.got          PROGBITS        080482d0 0002d0 000008 00  AX  0   0  8
  [14] .text             PROGBITS        080482e0 0002e0 000182 00  AX  0   0 16
  [15] .fini             PROGBITS        08048464 000464 000014 00  AX  0   0  4
  [16] .rodata           PROGBITS        08048478 000478 00000b 00   A  0   0  4
  [17] .eh_frame_hdr     PROGBITS        08048484 000484 000034 00   A  0   0  4
  [18] .eh_frame         PROGBITS        080484b8 0004b8 0000e0 00   A  0   0  4
  [19] .init_array       INIT_ARRAY      08049f0c 000f0c 000004 04  WA  0   0  4
  [20] .fini_array       FINI_ARRAY      08049f10 000f10 000004 04  WA  0   0  4
  [21] .dynamic          DYNAMIC         08049f14 000f14 0000e8 08  WA  6   0  4
  [22] .got              PROGBITS        08049ffc 000ffc 000004 04  WA  0   0  4
  [23] .got.plt          PROGBITS        0804a000 001000 000010 04  WA  0   0  4
  [24] .data             PROGBITS        0804a010 001010 000008 00  WA  0   0  4
  [25] .bss              NOBITS          0804a018 001018 000004 00  WA  0   0  1
  [26] .comment          PROGBITS        00000000 001018 00001a 01  MS  0   0  1
  [27] .symtab           SYMTAB          00000000 001034 0003f0 10     28  45  4
  [28] .strtab           STRTAB          00000000 001424 0001bd 00      0   0  1
  [29] .shstrtab         STRTAB          00000000 0015e1 000105 00      0   0  1

因此在 ELF 二进制文件中,该部分被标记为不可执行。但在内存中,页面是可执行的(cat /proc/xxx/maps):

08048000-08049000 r-xp 00000000 08:01 663551 /home/andrew/Desktop/a.out
08049000-0804a000 r--p 00000000 08:01 663551 /home/andrew/Desktop/a.out
0804a000-0804b000 rw-p 00001000 08:01 663551 /home/andrew/Desktop/a.out

我最初的猜测是这些部分间隔太近(08048000-08049000 运行ge 中同时有 AXA 部分),所以 Linux 强制为页面提供 ELF 权限位的并集 (AX | A == AX)。然而,即使增加 .rodata 部分的大小(通过添加许多长字符串),所有包含 .rodata 部分的页面仍然是可执行的。这是为什么?

(郑重声明,我 运行 正在 Linux 内核 4.11.7、GCC 7.1.1 上编译,64 位编译仍然表现出这种行为)

My original guess was that the segments too closely-spaced

你应该调用sections segments(ELF两者都有,而且它们mean different things)。

节仅在静态 link 时有效,并且可以完全删除(在 运行 时不需要)。在 运行 时只有 重要,典型的 ELF 二进制文件将有两个段,具有 R-XRW- 权限。

.rodata段通常与.text段合并,放入可执行段。如果您使用 gold linker(patch 引入了这个),您可以使用 --rosegment 标志更改它。

您可以在 readelf -Wl a.out 输出中看到部分到段的映射。

更新:

Can there ever be a situation where .rodata needs to be executable, or is it for optimization, or something else?

没有 可移植 需要 .rodata 可执行的情况。正如您在问题中所做的那样,可以构建一个需要它的不可移植程序。

.rodata.text 的合并是一种优化:它需要两次 mmap 调用而不是三个(用 --rosegment 编写的程序 link 将具有三个独立的 PT_LOAD 段,具有 R-XR--R-W 保护)并且还减少了虚拟 space 的碎片。此外,在 Linux 上有一个系统范围内的总映射限制,所以如果你 link 全部都可以 运行 一次,你可以减少 50% 的程序总数--rosegment.

更新二:

最近的 Linux 发行版停止合并 .text.rodata,现在有三个或四个单独的 LOAD 部分。参见