【发布时间】:2010-11-02 22:23:58
【问题描述】:
我已经实现了PARLANSE,这是一种在 MS Windows 下使用仙人掌堆栈来实现并行程序的语言。堆栈块在每个函数上分配 基础并且是只是处理局部变量的正确大小, 表达式临时推送/弹出,以及对库的调用(包括 库例程工作的堆栈空间)。这样的堆栈 实际上,帧可以小至 32 字节,而且通常如此。
这一切都很好,除非代码做了一些愚蠢的事情并且 导致硬件陷阱...此时 Windows 似乎 坚持将整个 x86 机器上下文“推入堆栈”。 如果包含 FP/MMX/等,这大约是 500+ 字节。寄存器, 它确实如此。自然地,在 32 字节堆栈上推送 500 字节 粉碎不应该的东西。 (硬件推几句 在一个陷阱上,但不是整个上下文)。
[编辑 2012 年 11 月 27 日:见this for measured details on the rediculous amount of stack Windows actually pushes]
我可以让 Windows 存储异常上下文块吗 其他地方(例如,到特定于线程的位置)? 然后软件可以接受异常 命中线程并处理它而不会溢出我的 小的堆栈帧。
我不认为这是可能的,但我想我会问一个更大的问题 观众。是否有操作系统标准调用/接口 会导致这种情况发生吗?
如果我可以让 MS 让我的 进程可选地定义一个上下文存储位置,“contextp”,它 默认情况下初始化为启用当前的旧行为。 然后替换中断/陷阱向量代码:
hardwareint: push context
mov contextp, esp
...与...
hardwareint: mov <somereg> contextp
test <somereg>
jnz $2
push context
mov contextp, esp
jmp $1
$2: store context @ somereg
$1: equ *
具有保存 somereg 等所需的明显更改。
[我现在要做的是:检查每个函数的生成代码。 如果它有机会产生陷阱(例如,除以零), 或者我们正在调试(可能是错误的指针 deref 等),添加 为 FP 上下文提供足够的堆栈帧空间。堆栈帧 现在最终大小为 ~~ 500-1000 字节,程序不能 recurse as far,这有时是一个真正的问题 我们正在写的应用程序。所以我们有一个可行的解决方案, 但它使调试复杂化]
编辑 8 月 25 日:我已设法将这个故事传达给 Microsoft 内部工程师 谁显然有权找出 MS 中的谁实际上可能 关心。解决方案可能希望渺茫。
编辑 9 月 14 日:MS Kernal Group Architect 听说了这个故事并表示同情。他说 MS 将考虑一种解决方案(如提议的解决方案),但不太可能包含在服务包中。可能需要等待下一个版本的 Windows。 (唉……我可能会变老……)
编辑:2010 年 9 月 13 日(1 年后)。微软没有采取任何行动。我最近的噩梦:在 Windows X64 上运行 32 位进程的陷阱是否会在中断处理程序假装推送 32 位上下文之前将整个 X64 上下文推送到堆栈上?那会更大(整数寄存器的两倍宽,SSE 寄存器的两倍(?))?
编辑:2012 年 2 月 25 日:(1.5 年过去了......)微软方面没有任何反应。我想他们只是不关心我的并行性。我认为这是对社区的伤害; MS在正常情况下使用的“大堆栈模型”限制了通过吃大量VM可以在任何时刻活着的并行计算量。 PARLANSE 模型将让一个应用程序拥有一百万个处于运行/等待状态的实时“颗粒”;这确实发生在我们的一些应用程序中,其中“并行”处理了 1 亿个节点图。 PARLANSE 方案可以使用大约 1Gb 的 RAM 来做到这一点,这非常易于管理。如果您尝试使用 MS 1Mb“大堆栈”,您将需要 10^12 字节的 VM 仅用于堆栈空间,我很确定 Windows 不会让您管理一百万个线程。
编辑:2014 年 4 月 29 日:(4 年过去了)。 我猜 MS 只是没有读懂 SO。 我在 PARLANSE 上做了足够多的工程,所以我们只在调试期间或正在进行 FP 操作时支付大堆栈帧的代价,所以我们已经设法找到非常实用的方法来解决这个问题。 MS继续令人失望;不同版本的 Windows 推送到堆栈上的东西的数量似乎有很大差异,而且超出了对硬件环境的需求。有一些迹象表明,这种可变性的一部分是由于非 MS 产品(例如防病毒)在异常处理链中卡住了它们的鼻子造成的;为什么他们不能从我的地址空间之外做到这一点?无论如何,我们通过简单地为 FP/调试陷阱添加一个大的 slop 因子来处理所有这些,并等待现场中不可避免的 MS 系统超过该数量。
【问题讨论】:
-
如果你在内存中打了 ntdll.dll,修改只会在当前进程中可见(copy-on-write)。我假设使用的是直接地址,而不是 IAT,但您可以使用 JMP 将处理程序的前几个字节覆盖到您自己的代码并返回到 ring 3。Windows 可能有一些安全措施来防止这种东西,但值得一试。
-
现在,这是一个想法。您是在建议 IDT 的目标在 ntdll.dll 中,我可以踩到它吗?如何确定 IDT 指向的位置,或者是 ntdll.dll 中已发布的入口点?在哪里可以找到有关 ntdll.dll 结构的更多信息?呼应我刚刚听到的一句话,“这会让我忙一阵子。谢谢”!
-
oops.. 我用过 IDT,我的意思是中断向量或者这些天 x86 架构所称的任何东西。 (我有 x86 手册,所以这是一个修辞声明 :-)
-
这个怎么样......在可能导致异常的指令之前,您将 xSP 设置为指向一个位置,该位置有足够的空间容纳所有包含 CPU/FPU 状态的堆栈上异常数据,以及什么不并在该指令后恢复 xSP?如果没有例外,开销很小。如果有,您甚至不会注意到开销。
-
@Alex:不错的主意,如果所有中断对于某些代码事件都是纯粹同步的。对于这种语言,我还异步启动和停止线程以确保某种程度的计算公平性。所以有时这种推送可能是由外部引起的。我可能会放弃它以获得更易于管理的堆栈帧。
标签: exception assembly stack-overflow cpu-registers threadcontext