Cri*_*scu 73 .net c# stack-overflow jit tail-recursion
我试图弄清楚C#编译器如何处理尾调用.
(答案:他们不是.但是64位JIT会做TCE(尾部呼叫消除).限制适用.)
所以我使用递归调用编写了一个小测试,它打印了在StackOverflowException杀死进程之前调用它的次数.
class Program
{
static void Main(string[] args)
{
Rec();
}
static int sz = 0;
static Random r = new Random();
static void Rec()
{
sz++;
//uncomment for faster, more imprecise runs
//if (sz % 100 == 0)
{
//some code to keep this method from being inlined
var zz = r.Next();
Console.Write("{0} Random: {1}\r", sz, zz);
}
//uncommenting this stops TCE from happening
//else
//{
// Console.Write("{0}\r", sz);
//}
Rec();
}
Run Code Online (Sandbox Code Playgroud)
在提示上,程序以以下任意一个的SO Exception结束:
相反,使用"优化构建" ON +(目标= 64或AnyCPU与"身高32位" OFF(一个64位的CPU上)),TCE发生,计数器保持永远旋转起来(确定,它可以说是自旋向下每次其数值溢出).
但我注意到在这种StackOverflowException情况下我无法解释的行为:它从不(?)发生在完全相同的堆栈深度.以下是几个32位运行的输出,Release build:
51600 Random: 1778264579
Process is terminated due to StackOverflowException.
51599 Random: 1515673450
Process is terminated due to StackOverflowException.
51602 Random: 1567871768
Process is terminated due to StackOverflowException.
51535 Random: 2760045665
Process is terminated due to StackOverflowException.
Run Code Online (Sandbox Code Playgroud)
和Debug构建:
28641 Random: 4435795885
Process is terminated due to StackOverflowException.
28641 Random: 4873901326 //never say never
Process is terminated due to StackOverflowException.
28623 Random: 7255802746
Process is terminated due to StackOverflowException.
28669 Random: 1613806023
Process is terminated due to StackOverflowException.
Run Code Online (Sandbox Code Playgroud)
堆栈大小是常量(默认为1 MB).堆栈帧的大小是不变的.
那么,当StackOverflowException命中时,什么可以解释堆栈深度的(有时是非平凡的)变化?
Hans Passant提出了Console.WriteLine触及P/Invoke,互操作以及可能非确定性锁定的问题.
所以我将代码简化为:
class Program
{
static void Main(string[] args)
{
Rec();
}
static int sz = 0;
static void Rec()
{
sz++;
Rec();
}
}
Run Code Online (Sandbox Code Playgroud)
我在没有调试器的Release/32bit/Optimization ON中运行它.当程序崩溃时,我附加调试器并检查计数器的值.
在几次运行中它仍然不一样.(或者我的测试有缺陷.)
正如fejesjoco建议的那样,我研究了ASLR(地址空间布局随机化).
这是一种安全的技术,使得它很难对缓冲区溢出攻击找到的(例如)特定系统调用的精确位置,通过在进程的地址空间随机化的各种东西,包括堆栈位置,显然,它的大小.
这个理论听起来不错.让我们付诸实践吧!
为了测试这一点,我使用了专门用于该任务的Microsoft工具:EMET或增强型缓解体验工具包.它允许在系统级或进程级设置ASLR标志(以及更多).
(还有一个系统范围的注册表黑客替代方案,我没试过)

为了验证该工具的有效性,我还发现Process Explorer在流程的"属性"页面中适当地报告了ASLR标志的状态.直到今天才看到它:)

从理论上讲,EMET可以(重新)为单个进程设置ASLR标志.在实践中,它似乎没有任何改变(见上图).
但是,我为整个系统禁用了ASLR并且(稍后重新启动)我终于可以验证确实,SO异常现在总是发生在相同的堆栈深度.
在旧版新闻中与ASLR相关:Chrome是如何实现的
fej*_*oco 51
我认为可能是ASLR在工作.你可以关闭DEP来测试这个理论.
请参阅此处了解用于检查内存信息的C#实用程序类:https://stackoverflow.com/a/8716410/552139
顺便说一句,使用这个工具,我发现最大和最小堆栈大小之间的差异大约是2 KiB,这是半页.那真是怪了.
更新:好的,现在我知道我是对的.我对半页理论进行了跟进,发现这篇文档检查了Windows中的ASLR实现:http://www.symantec.com/avcenter/reference/Address_Space_Layout_Randomization.pdf
引用:
一旦放置了堆栈,初始堆栈指针就会随机递减量进一步随机化.初始偏移量选择为最多半页(2,048字节)
这就是你问题的答案.ASLR随机取出初始堆栈的0到2048个字节.
| 归档时间: |
|
| 查看次数: |
2890 次 |
| 最近记录: |