【发布时间】:2019-11-23 05:45:40
【问题描述】:
我想知道macOS如何为进程分配堆栈和堆内存,即macOS中进程的内存布局。我只知道 mach-o 可执行文件的段被加载到页面中,但我找不到对应于进程的堆栈或堆区域的段。有相关文件吗?
【问题讨论】:
标签: macos assembly operating-system
我想知道macOS如何为进程分配堆栈和堆内存,即macOS中进程的内存布局。我只知道 mach-o 可执行文件的段被加载到页面中,但我找不到对应于进程的堆栈或堆区域的段。有相关文件吗?
【问题讨论】:
标签: macos assembly operating-system
堆栈和堆只是内存。使堆栈成为堆栈或堆或堆的唯一想法是访问它的方式。堆栈和堆的分配方式与所有内存相同:通过将页面映射到逻辑地址空间。
【讨论】:
mmap() 返回值。这称为 ASLR。所以不,它们通常不是固定的。但是以 Linux 为例,堆栈仍然靠近用户空间虚拟地址空间的顶部;地址的前几位不是随机的。
让我们退后一步——Mach-o 格式描述了将二进制段映射到虚拟内存中。重要的是,您提到的内存页面具有读写和执行权限。如果它是一个可执行文件(即不是 dylib),它必须包含 __PAGEZERO 段,根本没有权限。这是防止意外访问虚拟内存的低地址的安全保护区(这就是臭名昭著的 Null 指针异常,如果尝试访问零内存地址)。__TEXT 读取可执行文件(通常没有写入)段如下在虚拟内存中将包含文件表示本身。这意味着所有可执行代码都在这里。还有不可变的数据,如字符串常量。
顺序可能会有所不同,但通常接下来您会遇到__LINKEDIT 只读段。这是dyld 用于设置外部加载函数的部分,这里太宽泛了,无法涵盖,但是关于这个主题有很多答案。
最后,我们有了可读可写__DATA 段,这是进程实际可以写入的第一个位置。这用于全局/静态变量、由 dyld 填充的调用的外部地址。
我们已经大致介绍了通过LC_UNIXTHREAD 或现代MacOS (10.7+) LC_MAIN 启动的进程初始设置。这将启动进程主线程。每个线程必须包含它自己的stack。它的创建由操作系统处理(包括分配它)。请注意,到目前为止,该进程根本不知道堆(它是操作系统在做繁重的工作以准备堆栈)。
总而言之,到目前为止,我们有 2 个独立的内存来源——代表 Mach-o 结构的进程内存(大小是固定的,由可执行结构决定)和主线程堆栈(也具有预定义的大小)。该进程即将运行一个类似 C 的 main 函数,任何声明的局部变量将移动线程堆栈指针,同样任何对函数(本地和外部)的调用至少设置堆栈帧以返回地址。访问全局/静态变量将直接引用__DATA 段虚拟内存。
在 x86-64 程序集中保留堆栈空间如下所示:
sub rsp,16
对于 System V / AMD64 ABI(包括 MacOS)的堆栈对齐要求,有一些很棒的 SO anwers,例如 one
创建的任何新线程都将有自己的堆栈,以允许为局部变量和调用函数设置堆栈帧。
现在我们可以覆盖 heap 分配 - 这可以通过 libSystem(又名 MacOS C 标准库)提供 malloc/free 来缓解。这在内部由mmap 和munmap 系统调用处理——用于管理内存页面的内核API。
直接使用这些系统调用是可能的,但可能效率低下,因此malloc/free 使用内部内存池来限制系统调用的数量(执行成本很高)。
您在评论中提到的更改地址是由于:
【讨论】:
brk / sbrk),并且有 mmap,它是为您请求的每个新映射随机分配的。 (malloc / new 通常使用 mmap(MAP_ANONYMOUS) 从操作系统获取新页面。)
%fs:0x10 之类的地址进行线程本地存储的记录非常详细。如果您不了解 x86 汇编,我不会担心。 FS 基地址只是操作系统可以编程的特殊寄存器,用户空间可以将其用作常规 64 位虚拟地址空间的偏移量。从总体上看,它与 MIPS 上的代码或从某处从内存中加载每个线程偏移量并使用它的东西没有什么不同。 TLS 对内存布局的全局影响为零。 segment->linear 发生在分页之前 virt->phys.