14

我已经实现了PARLANSE,这是一种 MS Windows 下的语言,它使用仙人掌堆栈来实现并行程序。堆栈块是在每个函数的基础上分配的,并且大小正好适合处理局部变量、表达式临时推送/弹出和库调用(包括库例程工作的堆栈空间)。这种堆栈帧实际上可以小到 32 字节,而且通常如此。

这一切都很好,除非代码做了一些愚蠢的事情并导致硬件陷阱......此时Windows似乎坚持将整个x86机器上下文“推入堆栈”。如果包含 FP/MMX/等,这大​​约是 500+ 字节。注册,它确实如此。自然地,对 32 字节堆栈的 500 字节推送会破坏它不应该的东西。(硬件在陷阱上推了几句话,但不是整个上下文)。

[编辑 2012 年 11 月 27 日:有关Windows 实际推送的可笑堆栈数量的测量详细信息,请参阅此内容]

我可以让 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 字节,程序不能递归到那么远,这对于我们正在编写的应用程序来说有时是一个真正的问题。所以我们有一个可行的解决方案,但它使调试变得复杂]

编辑 8 月 25 日:我设法将这个故事传达给了一位微软内部工程师,他显然有权找出 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 仅用于堆栈空间,而我

编辑:2014 年 4 月 29 日:(4 年过去了)。 我猜MS只是不读SO。 我在 PARLANSE 上做了足够多的工程,所以我们只在调试期间或进行 FP 操作时支付大堆栈帧的代价,所以我们设法找到了非常实用的方法来解决这个问题。MS继续令人失望;不同版本的 Windows 推送到堆栈上的东西的数量似乎有很大差异,而且超出了对硬件上下文的需求。有一些迹象表明,这种可变性的某些原因是由于非 MS 产品(例如防病毒)在异常处理链中卡住了它们的鼻子。为什么他们不能从我的地址空间之外做到这一点?无论如何,我们通过简单地为 FP/调试陷阱添加一个大的溢出因子来处理所有这些,并等待现场中不可避免的 MS 系统超过该数量。

4

5 回答 5

4

基本上,您需要重新实现许多中断处理程序,即将自己挂接到中断描述符表(IDT)。问题是,您还需要重新实现 kernelmode -> usermode 回调(对于 SEH,此回调驻留在ntdll.dll并命名为KiuserExceptionDispatcher,这会触发所有 SEH 逻辑)。关键是,系统的其余部分依赖于 SEH 以现在的方式工作,并且您的解决方案会破坏事情,因为您在系统范围内执行它。也许你可以检查你在中断时所处的进程。但是,整体概念容易出错,并且非常严重地影响系统稳定性恕我直言。
这些实际上是类似于 rootkit 的技术。

编辑:
更多细节:您需要重新实现中断处理程序的原因是,异常(例如除以零)本质上是软件中断,并且总是通过 IDT。当异常被抛出时,内核收集上下文并将异常信号返回给用户模式(通过前面提到的 ntdll 中的 KiUserExceptionDispatcher)。此时您需要进行干预,因此您还需要提供一种机制来返回用户模式。(在 ntdll 中有一个函数用作内核模式的入口点——我不记得名字了,但它是 KiUserACP 的东西.....)

于 2009-06-17T12:19:14.980 回答
3

考虑将参数/本地堆栈与实际堆栈分离。使用另一个寄存器(例如 EBP)作为有效的堆栈指针,让基于 ESP 的堆栈按 Windows 想要的方式保留。

您不能再使用 PUSH/POP。您必须使用 SUB/MOV/MOV/MOV 组合而不是 PUSH。但是,嘿,比修补操作系统要好。

于 2013-12-04T14:57:51.557 回答
1

如果 Windows 使用 x86 硬件来实现其陷阱代码,则需要环 0 访问(通过驱动程序或 API)来更改用于陷阱的门。

门的 x86 概念指向以下之一:

  • 一个中断地址(代码段 + 偏移指针),在整个寄存器上下文(包括返回地址)被压入当前堆栈(=当前 esp)时调用,或者
  • 一个任务描述符,它切换到另一个任务(可以看作是硬件支持的线程)。所有相关数据都被推送到该任务的堆栈(esp)。

你当然想要后者。我会看看Wine是如何实现它的,这可能比问谷歌更有效。

我的猜测是,不幸的是,您需要实现一个驱动程序才能使其在 x86 上运行,并且根据Wikipedia的说法,驱动程序不可能在 IA64 平台上更改它。第二个最佳选择可能是在堆栈中交错空间,以便从陷阱中推送上下文总是合适的?

于 2009-06-15T12:44:48.867 回答
1

评论区空间不够了。。。

无论如何,我不确定向量指向哪里,我的评论基于 SDD 的答案并提到“KiUserExceptionDispatcher”......除非进一步搜索(http://www.nynaeve.net/?p=201)它看起来在这一点上可能为时已晚。

SIDT可以在 ring 3 中执行...这将显示中断表的内容,您可能能够加载段并至少读取表的内容。运气好的话,您可以读取(例如)向量 0/除以零的条目,并读取处理程序的内容。

此时我会尝试匹配十六进制字节以将代码与系统文件匹配,但可能有更好的方法来确定代码属于哪个文件(不一定是 DLL,可能是 win32k.sys,或它可以动态生成,谁知道呢)。我不知道是否有办法从用户模式转储物理内存布局。

如果一切都失败了,您可以设置内核模式调试器或模拟 Windows ( Bochs ),您可以在其中直接查看中断表和内存布局。然后,您可以跟踪直到 CONTEXT 被推送的那一点,并在此之前寻找机会获得控制权。

于 2009-08-31T22:49:33.143 回答
0

Windows 异常处理称为 SEH。IIRC 您可以禁用它,但您使用的语言的运行时可能不喜欢它。

于 2009-06-15T08:34:00.157 回答