F#作为一种语言非常适合编写语言解释器或编译器,但是,有一件事情让我们不知所措:StackOverflowException.
众所周知,无法捕获SO异常,无法从中恢复.防止这种异常的一种显而易见的技术是在你进行时计算堆栈的深度.开销,是的,但是可行,也许在每个功能中都没有必要.
使用F#,这种技术虽然没有带来太多好处.我们在解释器的动态生成表达式中大量使用尾调用优化技术.我们在SO异常中遇到的问题是:
只是增加堆栈大小不会有足够的帮助,我们希望给用户一个可记录的错误,最好是由调用应用程序捕获.为此,我们需要能够手动抛出异常,这使它可以捕获.但我们如何确定合适的时机?
更新:
Hans Passant在这里正确地提出了可预测性.但是,使用此DSL的程序员期望(某些)调用获得TCO,因此他们不希望有强大的堆栈限制.他们知道自己在做什么.尽管如此,他们的程序仍然需要能够优雅地消亡,至少在任何调用应用程序(即使用我们的库的C#程序)都不会受到损害的情况下.
精简版
我想要一个ADPlus脚本,它将在第一次机会StackOverflowException上执行完全内存转储,然后清除任何内容,并忽略所有其他异常类型.
日志版本
在发布新的ASP.NET代码之后,我们开始获得间歇性的StackOverflowExceptions.我们已经寻找无限递归和自上次已知良好安装以来添加的修订版中的所有常见嫌疑人,并且找不到任何内容.该网站将运行长达一个小时,然后崩溃.
我们使用了WinDbg和SOS,并尝试使用ADPlus获取崩溃日志,使用以下命令:
adplus -crash -o D:\Crash -NoDumpOnFirst -iis
Run Code Online (Sandbox Code Playgroud)
-NoDumpOnFirst的原因是我们只能在繁忙的服务器上重现生产中的这个错误.为了对每个第一次机会异常执行minidump(嘿,它发生),调试器必须暂停IIS工作进程足够长时间写出一个16兆字节的文件,因此请求排队并且应用程序变得不稳定.因为这个错误可能需要长达一个小时才能让它变得难看,这是有问题的.
所以使用-NoDumpOnFirst,我得到一个转储文件,WinDbg输出这些线程:
PDB symbol for mscorwks.dll not loaded
ThreadCount: 69
UnstartedThread: 0
BackgroundThread: 69
PendingThread: 0
DeadThread: 0
Hosted Runtime: no
PreEmptive GC Alloc Lock
ID OSID ThreadOBJ State GC Context Domain Count APT Exception
XXXX 1 c6c 000fa758 11808221 Disabled 3b49ee4c:3b49efe8 00120888 1 Ukn (Threadpool Worker)
XXXX 2 1294 000fd258 b220 Enabled 00000000:00000000 000df4e0 0 Ukn (Finalizer)
XXXX 3 1eb0 0011cdd0 80a220 Enabled 00000000:00000000 000df4e0 0 Ukn (Threadpool …Run Code Online (Sandbox Code Playgroud) 如何StackOverflowError在Java中处理?
我正在制作自己的类似Lisp的解释语言,我想做尾调优化.我想从C堆栈中释放我的解释器,这样我就可以管理自己从函数到函数的跳转以及我自己的堆栈魔法来实现TCO.(我真的不是指无堆栈本身,只是调用不向C堆栈添加帧的事实.我想使用我自己的堆栈,不会随着尾调用而增长).就像Stackless Python一样,不像Ruby或者......标准Python我猜.
但是,由于我的语言是Lisp派生词,所有对s表达式的评估目前都是以递归方式完成的(因为这是我想到的最明显的方式来做这种非线性,高度分层的过程).我有一个eval函数,每次遇到函数调用时都会调用Lambda :: apply函数.apply函数然后调用eval来执行函数体,依此类推.相互堆栈饥饿的非尾部C递归.我目前使用的唯一迭代部分是评估一系列连续的s表达式.
(defun f (x y)
(a x y)) ; tail call! goto instead of call.
; (do not grow the stack, keep return addr)
(defun a (x y)
(+ x y))
; ...
(print (f 1 2)) ; how does the return work here? how does it know it's supposed to
; return the value here to be used by print, and how does it know
; how to continue execution here??
Run Code Online (Sandbox Code Playgroud)
那么,我如何避免使用C递归?或者我可以使用跳过c函数的某种goto吗?也许是longjmp?我真的不知道.请耐心等待,我主要是自编(Internet)教程.
我正在编写一个函数,可以调用自己大约5000次.当然,我得到了一个StackOverflowError.有什么方法可以用相当简单的方式重写这段代码吗?:
void checkBlocks(Block b, int amm) {
//Stuff that might issue a return call
Block blockDown = (Block) b.getRelative(BlockFace.DOWN);
if (condition)
checkBlocks(blockDown, amm);
Block blockUp = (Block) b.getRelative(BlockFace.UP);
if (condition)
checkBlocks(blockUp, amm);
//Same code 4 more times for each side
}
Run Code Online (Sandbox Code Playgroud)
那么,我们可以称之为功能的深度有多大限制?
今天,我从Elance.com上做了一个简短的"C++技能测试".一个问题如下:
以下代码行的安全漏洞是什么:
printf("%s", argv[1]);选项1:格式字符串
选项2:堆栈溢出 < - 这被Elance标记为正确答案
在最初几秒钟看到问题后(或自动使问题无效),用户被提供10秒钟来回答此问题.(还有另外两个明显不相关的答案,没有被Elance标记为正确答案.)
我正在寻找缓冲区溢出或缓冲区溢出作为选项.
我本能地不喜欢答案堆栈溢出,因为在我10秒内我精神上使用了我认为是"Stack Overflow"的标准定义:
在软件中,当堆栈指针超出堆栈限制时,会发生堆栈溢出.调用堆栈可能包含有限数量的地址空间,通常在程序开始时确定...
根据"Stack Overflow"的定义,在没有堆栈溢出的情况下完全可以实现缓冲区溢出 ; 只有当程序试图在调用程序的总堆栈分配之外写入时才会发生堆栈溢出(无论是由于缓冲区溢出,还是由于它是否是合法写入,例如为基于堆栈的变量分配内存过多而次).
我的10秒本能告诉我,"缓冲区溢出"是对上面有问题的代码行的更准确的描述 - 因为通常(根据我的经验),有足够的空字符('\0')通过RAM中的垃圾数据来填充,以避免在这种情况下实际的堆栈溢出,但实现中的缓冲区溢出似乎是合理可能的,甚至可能.(但是,这可能printf在这里读的垃圾可能会认为argc == 1,这样有是没有用户提供的argv[1];如果argv[1]存在,或许可以假设很可能是调用函数还没有插入NULL的这不是在问题陈述是否.argv[1]为当下.)
因为我想象这里可能存在缓冲区溢出问题,即使没有堆栈溢出,我回答了格式字符串,因为简单地通过传递不同的格式字符串"%.8s",问题可以大部分避免,所以它看起来像一个整体更通用,因此更好,回答.
我的回答被标记为错误.正确答案标记为"Stack Overflow".
现在我想到,如果假设argv[1]存在,那么唯一可能的缓冲区溢出是堆栈溢出,在这种情况下,堆栈溢出实际上可能是正确的答案.但是,即使在这种情况下,将这称为堆栈溢出也不会被认为是奇怪的吗?缓冲区溢出是否是描述此问题的更好方法,即使假设argv[1]存在?而且,如果argv[1]是不存在的,是不是非常不正确地指出,问题是 …
为什么在下面详述的场景中,堆栈空间在x64中增加但在x32中使用相同的代码减少?
背景:
我们的客户可以使用域语言编写脚本,该语言在运行时使用递归技术解释并在Web服务器上执行.它们可能会在抛出异常的脚本中出错,此异常会被捕获并记录.
由于这种递归技术,我们通过检查用作解释器执行脚本的堆栈空间来防止堆栈溢出异常,并在我们实际耗尽堆栈之前终止脚本.
在32位模式下,一切运行良好,当脚本编写器Exception生成错误时,它被记录,堆栈展开,在此期间堆栈上剩余的空间增加,脚本很好地终止.
在64位模式下,一切都不太好,当脚本编写Exception器生成错误时,它被记录,堆栈展开,在此期间堆栈上剩余的空间减少.这是非常糟糕的,因为有可能如果脚本碰巧使用了大量的堆栈空间并抛出然后解除堆栈并记录错误本身的行为导致StackOverflowException隐藏原始异常,软管IIS并杀死所有机上要求(坏,非常糟糕,非常糟糕).
重新创建问题:
这是一个控制台应用程序,它模拟我在生产中使用的代码,并在设置为x64时重新创建问题,并在x32中正常工作.
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
namespace ConsoleApplication16
{
class Program
{
const Int32 MaxNumberRecursions = 10;
static Int32 _currentRecursionDepth;
static UInt64 _lastSpaceUsed;
static void Main(string[] args)
{
System.Diagnostics.Debug.WriteLine(String.Format("Is64BitProcess = {0}", System.Environment.Is64BitProcess));
try
{
_lastSpaceUsed = GetStackBytesLeft();
RecurseXTimes();
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine(e);
}
}
unsafe static void RecurseXTimes()
{
System.Diagnostics.Debug.WriteLine("--> RecurseXTimes()");
ReportStackSpaceUsage();
try
{
_currentRecursionDepth++;
if (_currentRecursionDepth > MaxNumberRecursions)
{ …Run Code Online (Sandbox Code Playgroud) 我们有很多嵌套的异步方法,看到我们并不真正理解的行为.以这个简单的C#控制台应用程序为例
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace AsyncStackSample
{
class Program
{
static void Main(string[] args)
{
try
{
var x = Test(index: 0, max: int.Parse(args[0]), throwException: bool.Parse(args[1])).GetAwaiter().GetResult();
Console.WriteLine(x);
}
catch(Exception ex)
{
Console.WriteLine(ex);
}
Console.ReadKey();
}
static async Task<string> Test(int index, int max, bool throwException)
{
await Task.Yield();
if(index < max)
{
var nextIndex = index + 1;
try
{
Console.WriteLine($"b {nextIndex} of {max} (on threadId: {Thread.CurrentThread.ManagedThreadId})");
return await Test(nextIndex, …Run Code Online (Sandbox Code Playgroud)