我目前正在研究将 ELF 从磁盘加载到内存的代码,这对应于 Linux 内核中的函数
load_elf_binary()
。
此类函数设置不同段的地址(例如文本、数据、bss、堆、堆栈、映射区域)。通过跟踪代码,我注意到一个函数:
setup_new_exec()
,它是在 /fs/exec.c 中定义的。在这样的函数内部,它调用arch_pick_mmap_layout()
,它的定义在here。请注意,我并不是针对 X86 等特定架构,因此我指的是通用函数定义。
以下是部分代码:
if (mmap_is_legacy(rlim_stack)) {
mm->mmap_base = TASK_UNMAPPED_BASE + random_factor;
mm->get_unmapped_area = arch_get_unmapped_area;
} else {
mm->mmap_base = mmap_base(random_factor, rlim_stack);
mm->get_unmapped_area = arch_get_unmapped_area_topdown;
}
根据代码,我知道有两种获取未映射区域的方法 - 自下而上(遗留)和自上而下。这两种方式也在这篇LWN文章中进行了讨论。
为了区分,我们需要,即return sysctl_legacy_va_layout;
。
sysctl_legacy_va_layout
默认初始化为0。
这是否意味着默认情况下,进程的内存映射区域从上到下增长(从高地址到低地址;从堆栈到堆增长)?
“默认情况下,进程的内存映射区域从上到下增长”是正确的。
现在的默认布局和旧版布局应该如下所示:
DEFAULT LEGACY
0xffffffffffffffff 0xffffffffffffffff
stack stack
🡓 🡓
mmap ...
🡓 🡑
... heap
... ELF
🡑 ...
heap 🡑
ELF mmap
... ...
0x0000000000000000 0x0000000000000000
[...] 如今,传统布局的 mmap 段是低地址。有代码证明这一点吗?另外,现在的遗留内存布局是从虚拟地址0开始的吗?当然,您可以在通用 实现中链接的代码中准确地看到这一点,该实现为旧版布局选择较低的
mmap_base
。计算结果为
TASK_UNMAPPED_BASE + random_factor
(
random_factor
来自 ASLR,请参阅
/proc/sys/kernel/randomize_va_space
)。请注意,某些架构(即 x86、PA-RISC、PowerPC、S390、Sparc)会覆盖该函数并提供自己的函数,但完成的计算几乎相同(您可以检查源代码)。
TASK_UNMAPPED_BASE
代表“mmap”虚拟内存区域的下边界,并且它因体系结构而异。但它不应该被定义为低至
0
(零)。它通常用
TASK_SIZE
来定义。一些例子:
TASK_SIZE / 3
=
0x2aaaaaaab000
x86-64
TASK_SIZE / 3
= x86 32 位上的
0x40000000
,默认
VMSPLIT_3G
配置
TASK_SIZE / 4
= ARM64 上的
0x400000000000
CONFIG_PAGE_OFFSET / 3
= ARM 32 位上的
0x40000000
,默认
VMSPLIT_3G
配置
TASK_SIZE / 3
=
0x5555555000
在具有 40 VA 位的 MIPS 64 位上
TASK_SIZE / 8 * 3
=
0x30000000
在 PowerPC 8xx(32 位)上
0x10000
)。此类低地址必须通过提示
mmap
显式请求,由于
mmap(0, ...)
,它们不会由内核自愿映射。因此,我们确定对于遗留布局,“mmap”区域从某个低地址开始,并且我们已经知道堆栈始终从最高地址开始。
根据 ELF 本身,文件仅由内核根据其类型和程序头映射到内存中,通常是连续的,没有漏洞,并且计算是相同的无论默认/旧版 mmap 布局如何。您将看到 ELF 程序头中指定的多个段映射了不同的权限(请参阅 readelf -l
的输出),并且这些段将包含不同的部分,例如
.text
、
.rodata
、
.bss
等(参见
readelf -S
的输出)。对于 ELF
Executables(即 e_type
=
ET_EXEC
,参见
man 5 elf
),基虚拟地址由 ELF 本身选择:它在编译时固定并确定,这样的 ELF 无法加载位于不同的地址以使其正常工作。
对于 ELF 共享对象(即 e_type
=
ET_DYN
),这是当今的标准,基虚拟地址由内核本身选择,并由
ELF_ET_DYN_BASE
定义(如果启用 ASLR,则进行调整) 。 我的另一个答案涵盖了 x86。该值是 above TASK_UNMAPPED_BASE
,因此您将在旧布局中看到 ELFabove 的“mmap”区域(较高地址),以及在默认布局中的 below it(较低地址)。 无论如何,根据定义,“堆”区域(又名
/proc/[pid]/maps
时默认布局与旧布局的样子。请注意,低地址位于顶部。
默认:
传统: