使用“push”或“sub”x86 指令时,堆栈内存是如何分配的?

2023-12-01

我已经浏览了一段时间,我试图了解在执行以下操作时如何将内存分配给堆栈:

push rax

或者移动堆栈指针为子例程的局部变量分配空间:

sub rsp, X    ;Move stack pointer down by X bytes 

我的理解是,堆栈段在虚拟内存空间中是匿名的,即不是文件支持的。

我还了解到,内核实际上不会将匿名虚拟内存段映射到物理内存,直到程序实际对该内存段执行某些操作(即写入数据)。因此,在写入该段之前尝试读取该段可能会导致错误。

在第一个示例中,如果需要,内核将在物理内存中分配一个帧页。 在第二个示例中,我假设内核不会将任何物理内存分配给堆栈段,直到程序实际将数据写入堆栈堆栈段中的地址。

我走在正确的轨道上吗?


是的,您几乎走在正确的道路上。sub rsp, X有点像“惰性”分配:内核仅在#PF页面错误异常是由于触摸新 RSP 之上的内存而引起的,而不仅仅是修改寄存器。但您仍然可以考虑“已分配”内存,即可以安全使用。

因此,在写入该段之前尝试读取该段可能会导致错误。

不,读取不会导致错误。从未写入过的匿名页会被写入时复制映射到物理零页,无论它们是在 BSS、堆栈还是在mmap(MAP_ANONYMOUS).

有趣的事实:在微基准测试中,请确保为输入数组写入每一页内存,否则实际上会重复循环相同的物理 4k 或 2M 零页,并且会获得 L1D 缓存命中,即使您仍然会出现 TLB 未命中的情况(和软页面错误)! gcc 将优化 malloc+memset(0) 为calloc, but std::vector无论您是否愿意,实际上都会写入所有内存。memset全局数组上的未优化,因此可以工作。 (或者非零初始化的数组将在数据段中进行文件支持。)


请注意,我忽略了映射与有线之间的区别。即访问是否会触发软/次要页错误来更新页表,或者是否只是 TLB 未命中并且硬件页表遍历将找到映射(到零页)。

但RSP以下的堆栈内存可能根本无法映射,因此在不先移动 RSP 的情况下触摸它可能是无效页面错误,而不是“次要”页面错误来解决写入时复制问题。


堆栈内存有一个有趣的变化:堆栈大小限制约为 8MB(ulimit -s),但在 Linux 中,进程的第一个线程的初始堆栈是特殊的。例如我设置了一个断点_start在 hello-world (动态链接)可执行文件中,并查看/proc/<PID>/smaps for it:

7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0                          [stack]
Size:                132 kB
Rss:                   8 kB
Pss:                   8 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:         8 kB
Referenced:            8 kB
Anonymous:             8 kB
...

仅 8kiB 堆栈已被引用并由物理页支持。这是预期的,因为动态链接器不使用大量堆栈。

甚至只有 132kiB 的堆栈被映射到进程的虚拟地址空间。但特殊的魔法停止了mmap(NULL, ...)从堆栈可以增长到的 8MiB 虚拟地址空间内随机选择页面。

触摸当前堆栈映射下方但在堆栈限制内的内存 导致内核增加堆栈映射(在页面错误处理程序中)。

(But only if rsp首先被调整; the red-zone仅低于 128 字节rsp, so ulimit -s unlimited不使触摸内存低于1GBrsp将堆栈增长到那里,但如果你减少它就会rsp到那里然后触摸内存.)

这仅适用于初始/主线程的堆栈. pthreads只是使用mmap(MAP_ANONYMOUS|MAP_STACK)映射无法增长的 8MiB 块。 (MAP_STACK目前是无操作。)因此线程堆栈在分配后不能增长(除非手动使用MAP_FIXED如果它们下面有空间),并且不受ulimit -s unlimited.


这种阻止其他事物选择堆栈增长区域中的地址的魔法并不存在mmap(MAP_GROWSDOWN), so do not用它来分配新的线程堆栈。 (否则,最终可能会耗尽新堆栈下方的虚拟地址空间,使其无法增长)。只需分配完整的 8MiB。也可以看看进程虚拟地址空间中其他线程的堆栈位于哪里?.

MAP_GROWSDOWN确实具有按需增长的功能,中描述的mmap(2)手册页,但没有增长限制(除了接近现有映射之外),因此(根据手册页)它基于 Windows 使用的保护页,而不是像主线程的堆栈。

触摸内存底部下方的多个页面MAP_GROWSDOWN区域可能会出现段错误(与 Linux 的主线程堆栈不同)。针对 Linux 的编译器不会生成堆栈“探针”来确保在大分配(例如本地数组或分配)后按顺序触及每个 4k 页面,所以这是另一个原因MAP_GROWSDOWN对于堆栈来说不安全。

编译器确实会在 Windows 上发出堆栈探测。

(MAP_GROWSDOWN甚至可能根本不起作用,请参阅@BeeOnRope 的评论。它对于任何东西来说都不是很安全,因为如果映射变得接近其他东西,则可能存在堆栈冲突安全漏洞。所以只是不要使用MAP_GROWSDOWN对于任何事情。我将在提及中描述 Windows 使用的保护页机制,因为有趣的是,Linux 的主线程堆栈设计并不是唯一可能的设计。)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用“push”或“sub”x86 指令时,堆栈内存是如何分配的? 的相关文章

随机推荐

  • tidyr::收集不同类型的多个列

    我的问题类似于这个问题 我试图tidyr gather多列 但是 链接中提供的解决方案不太理想 因为所有列的属性通常不相同 因此它们被删除 请注意 我知道如何使用基本 R 执行此操作 但我正在尝试学习如何使用 tidyr 和 或 dplyr
  • 如何操作data.table中的data.frame

    我有data table其中某些观察列包含data frame 例如 data table colA c A1 A2 A3 colB list data frame data frame colsubB1 c B2a B2b colsubB
  • 使用 char 指针输入与 char 数组输入

    考虑代码 include
  • npmpublish 的包名称与现有包太相似

    我想将我的包发布到 npm 我得到的错误是 包名称与现有包太相似 403 Forbidden PUT https registry npmjs org mypack Package name too similar to existing
  • ajax请求中的angularjs错误处理

    我想在我的应用程序中编写一个错误处理部分 我使用下面的代码 但是当错误 500 发生时 它可以正常工作 但是存在一个小或可能大的问题 这就是页面加载的第一次和几秒后的错误页面加载 如何才能我删除这几秒钟并直接进入错误页面而不加载释放错误的主
  • 如何使用多短语查询?

    http lucene apache org java 2 3 1 api core org apache lucene search MultiPhraseQuery html 对于 Microsoft app 这个例子 他说使用Inde
  • CSS 选择器中允许使用括号吗?

    在下面的示例中 我想创建一个仅适用于带有文本 Blockhead 的标题的 CSS 规则 div class gumby span class pokey span h3 Blockhead h3 h3 Clay rules h3 div
  • 如何获取嵌套fragment中的Activity?

    我试图在 ViewPager 中包含的片段中调用 getActivity 的活动 并且该 ViewPager 包含在 Fragment 中 我需要在这些片段中调用此活动的一些方法 但 getActivity 始终为此片段返回 NULL 我不
  • 实例字段的初始化与局部变量的初始化

    我一直想知道为什么在下面的例子中可以not初始化实例字段 依赖于它将具有默认值 并访问它 而局部变量显然must被初始化 即使我将其初始化为默认值 它无论如何都会得到 public class TestClass private bool
  • (!object) 和 (object == nil) 之间有区别吗? [复制]

    这个问题在这里已经有答案了 可能的重复 Objective C if obj 和 if obj null 哪个更好 这两个条件有区别吗 if object do something 和 if object nil do something
  • 在 MySQL 中的 accountID 之间转移“钱”

    我有一个问题 我尝试用谷歌搜索但尚未找到答案 我想做的是使用存储过程在 MySQL 中的两个帐户之间转账 例如 如果我使用呼叫转接 20 Test 3 5 然后我将从 accountID 3 转 20 美元到 accountID 5 并写入
  • 返回先前位置时如何避免 TDbgrid 滚动

    在下面的代码中 我们对某些选定的行进行一些操作 不是删除 然而 有时 完成后 顶部选定的行会滚动 使其显示在网格下方 1 2 处 有没有办法避免这种滚动 如果我的遍历下面选定行的代码由于某些不相关的原因不正确 我欢迎更正 Function
  • 里面有数字的叶子标记

    我想使用 Folium 在地图上做一些标记 并在标记内添加一些数字 我希望标记看起来像 Google 地图使用的标准倒置水滴形状 我看到对于 folium Marker 您可以使用参数 icon folium DivIcon html co
  • Codeigniter 查询生成器在 where_in 中使用 implode 函数

    这是我使用 implode 函数的正常 sql 查询 SELECT from search result WHERE skills IN implode s id 现在我想将其转换为 codeigniter 形式 我尝试了以下代码 但失败了
  • Unicode 组合字符的实际最大数量是多少?

    我正在寻找在非组合字符之后出现的 unicode 组合字符的最大数量在现实的自然文本中 我知道在 unicode 文本中 文本中的任何位置都可以放置任意数量的组合 但是 我正在编写一个专门的应用程序 该应用程序必须在资源有限的情况下运行 并
  • React - 从同级组件调用函数

    假设我有一个组件树 如下所示
  • 在基于 iframe 的文本编辑器中禁用 Firefox 和 Chrome 拼写检查

    有很多关于如何禁用 html 中的拼写检查的信息textarea元素通过使用spellcheck false 然而 要使文本区域具有更高级的功能 必须使用iframe with designMode on 参见例如这一页 这是在 GWT 中
  • 使用 C# 错误,可空对象必须具有值 datetime

    我在 C datetime 中遇到一个问题 我已经尝试了很多时间 它不是 给出问题的解决方案 所以给我一个解决方案 var accommodationcategoryList EmployeeAttendanceCacheMaster Ge
  • sprintf():作为参数的小数位数

    目前的解决方案是 dp lt 2 sprintf paste0 dp f 0 123 期望的解决方案没有 Paste0 并且类似于 sprintf 2 d f 0 123 2L 除了它有效之外 您可以使用 插入dp进入格式 dp lt 2
  • 使用“push”或“sub”x86 指令时,堆栈内存是如何分配的?

    我已经浏览了一段时间 我试图了解在执行以下操作时如何将内存分配给堆栈 push rax 或者移动堆栈指针为子例程的局部变量分配空间 sub rsp X Move stack pointer down by X bytes 我的理解是 堆栈段