.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 中同时有 AX
和 A
部分),所以 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-X
和 RW-
权限。
.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-X
、R--
和 R-W
保护)并且还减少了虚拟 space 的碎片。此外,在 Linux 上有一个系统范围内的总映射限制,所以如果你 link 全部都可以 运行 一次,你可以减少 50% 的程序总数--rosegment
.
更新二:
最近的 Linux 发行版停止合并 .text
和 .rodata
,现在有三个或四个单独的 LOAD
部分。参见 。
所以出于好奇,我今天尝试运行这段代码(用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 中同时有 AX
和 A
部分),所以 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-X
和 RW-
权限。
.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-X
、R--
和 R-W
保护)并且还减少了虚拟 space 的碎片。此外,在 Linux 上有一个系统范围内的总映射限制,所以如果你 link 全部都可以 运行 一次,你可以减少 50% 的程序总数--rosegment
.
更新二:
最近的 Linux 发行版停止合并 .text
和 .rodata
,现在有三个或四个单独的 LOAD
部分。参见