有趣的堆栈问题?

时间:2010-06-21 02:28:24

标签: delphi stack delphi-2009

我的delphi 2009应用程序中有一个有趣的问题。当在调试器中运行时,我在子例程的Begin关键字和第一个语句之间得到一个AV。我相信它是在设置局部变量的时候。这是调试器中显示的信息:

uDeviceModule.pas.940: begin  // _GetMeasurementsForChannel
00AF24C8 55               push ebp
00AF24C9 8BEC             mov ebp,esp
00AF24CB 51               push ecx
00AF24CC B9E9A90100       mov ecx,$0001a9e9    // isn't this a lot for the stack?

// error happens in here
00AF24D1 6A00             push $00
00AF24D3 6A00             push $00
00AF24D5 49               dec ecx
00AF24D6 75F9             jnz $00af24d1

00AF24D8 874DFC           xchg [ebp-$04],ecx
00AF24DB 53               push ebx
00AF24DC 894DF4           mov [ebp-$0c],ecx
00AF24DF 8955FC           mov [ebp-$04],edx
00AF24E2 8945F8           mov [ebp-$08],eax
00AF24E5 33C0             xor eax,eax
00AF24E7 55               push ebp
00AF24E8 687D2FAF00       push $00af2f7d
00AF24ED 64FF30           push dword ptr fs:[eax]
00AF24F0 648920           mov fs:[eax],esp
uDeviceModule.pas.941: SelectChannel(eChannelNum);       // first statement

这是这个嵌套子程序的简化版本(见下文)。

procedure TDeviceModule.GetMeasurements(ExpInfo:TExpInfo;
  _DisplayList:TMeasDisplayListAncestor; eExposureStatus:TExposureStatus;
  bActiveErrorEnabled:boolean);

  procedure _GetMeasurementsForChannel(_DisplayList:TObjectList;
    eChannelNum:TDeviceChannelNum; eExposureStatus:TMyEnum;
    bActiveErrorEnabled:boolean);
  var
    // these are all objects (not records)
    selChannel:TDeviceChannel;
    det:TDeviceDetector;
    shoKVMeas:TStoMeasurement;
  begin  // ********************* error happens on this line
    SelectChannel(eChannelNum);

    _GetMeasurement(ExpInfo, _DisplayList, eChannelNum, eExposureStatus, ctdVal1);
    _GetMeasurement(ExpInfo, _DisplayList, eChannelNum, eExposureStatus, ctdVal2);
    _GetMeasurement(ExpInfo, _DisplayList, eChannelNum, eExposureStatus, ctdVal3);
  end;  // _GetMeasurementsForChannel

begin
  // blah blah blah

      _GetMeasurementsForChannel(_DisplayList,
                                 eChannelNum,
                                 eExposureStatus,
                                 bActiveErrorEnabled);

  // blah blah blah
end;

它是一个单线程应用程序。

你怎么建议我去寻找这个问题的原因?我的第一个想法是:

1)增加最大堆栈大小 - 我做了但没有改变任何东西。现在它是$ 160000(1441792),但在此之前,我认为它是$ 150000。 2)这个对象仍然有效吗?似乎是......它正确地响应了ClassName方法& FastMM没有警告我任何问题。

有趣的是,堆栈跟踪没有提到导致问题的例程。

:7e42b35c USER32.MoveWindow + 0xbe
:7e4565b7 USER32.GetRawInputDeviceInfoW + 0x5f
:7e428eec ; C:\WINDOWS\system32\USER32.dll
:7c90e473 ntdll.KiUserCallbackDispatcher + 0x13
ActnMenus.CallWindowHook(???,0,$31104)
:7e42b372 USER32.MoveWindow + 0xd4
:7e4565b7 USER32.GetRawInputDeviceInfoW + 0x5f
:7e428eec ; C:\WINDOWS\system32\USER32.dll
:7c90e473 ntdll.KiUserCallbackDispatcher + 0x13
:007b882d aqDockingWndProcHook + $1D
:7e42b372 USER32.MoveWindow + 0xd4
:7e4565b7 USER32.GetRawInputDeviceInfoW + 0x5f
:7e428eec ; C:\WINDOWS\system32\USER32.dll
:7c90e473 ntdll.KiUserCallbackDispatcher + 0x13
:7e428dd9 USER32.DefWindowProcW + 0xb9
:7e428d77 USER32.DefWindowProcW + 0x57
:7e418734 USER32.GetDC + 0x6d
:7e418816 ; C:\WINDOWS\system32\USER32.dll
:7e42a013 USER32.IsWindowUnicode + 0xa1
:7e42a039 USER32.CallWindowProcW + 0x1b
Controls.TWinControl.DefaultHandler(???)
:0050fac8 TWinControl.DefaultHandler + $DC
:0050b4b9 TControl.WndProc + $2D5
:0050f9cc TWinControl.WndProc + $518
:0050f0e3 TWinControl.MainWndProc + $2F
:0048874e StdWndProc + $16
:7e418734 USER32.GetDC + 0x6d
:7e418816 ; C:\WINDOWS\system32\USER32.dll
:7e428ea0 ; C:\WINDOWS\system32\USER32.dll
:7e428eec ; C:\WINDOWS\system32\USER32.dll
:7c90e473 ntdll.KiUserCallbackDispatcher + 0x13
:7e428dd9 USER32.DefWindowProcW + 0xb9
:7e428d77 USER32.DefWindowProcW + 0x57
:7e418734 USER32.GetDC + 0x6d
:7e418816 ; C:\WINDOWS\system32\USER32.dll
:7e42a013 USER32.IsWindowUnicode + 0xa1
:7e42a039 USER32.CallWindowProcW + 0x1b
:0050fac8 TWinControl.DefaultHandler + $DC
:0050f9cc TWinControl.WndProc + $518
:0050f0e3 TWinControl.MainWndProc + $2F
:0048874e StdWndProc + $16
:7e418734 USER32.GetDC + 0x6d
:7e418816 ; C:\WINDOWS\system32\USER32.dll
:7e428ea0 ; C:\WINDOWS\system32\USER32.dll
:7e428eec ; C:\WINDOWS\system32\USER32.dll
:7c90e473 ntdll.KiUserCallbackDispatcher + 0x13
:7e428dd9 USER32.DefWindowProcW + 0xb9
:7e428d77 USER32.DefWindowProcW + 0x57
:7e418734 USER32.GetDC + 0x6d
:7e418816 ; C:\WINDOWS\system32\USER32.dll
:7e42a013 USER32.IsWindowUnicode + 0xa1
:7e42a039 USER32.CallWindowProcW + 0x1b
:0050fac8 TWinControl.DefaultHandler + $DC
:0050f9cc TWinControl.WndProc + $518
:0050f0e3 TWinControl.MainWndProc + $2F
:0048874e StdWndProc + $16
:7e418734 USER32.GetDC + 0x6d
:7e418816 ; C:\WINDOWS\system32\USER32.dll
:7e428ea0 ; C:\WINDOWS\system32\USER32.dll
:7e428eec ; C:\WINDOWS\system32\USER32.dll
:7c90e473 ntdll.KiUserCallbackDispatcher + 0x13
:7e428dd9 USER32.DefWindowProcW + 0xb9
:7e428d77 USER32.DefWindowProcW + 0x57
:7e418734 USER32.GetDC + 0x6d
:7e418816 ; C:\WINDOWS\system32\USER32.dll
:7e42a013 USER32.IsWindowUnicode + 0xa1
:7e42a039 USER32.CallWindowProcW + 0x1b
:0050fac8 TWinControl.DefaultHandler + $DC
:0050f9cc TWinControl.WndProc + $518
:0065279d TcxControl.WndProc + $121
:0070b38d TcxCustomGrid.WndProc + $5
:0048874e StdWndProc + $16
:7e418734 USER32.GetDC + 0x6d
:7e418816 ; C:\WINDOWS\system32\USER32.dll
:7e428ea0 ; C:\WINDOWS\system32\USER32.dll
:7e428eec ; C:\WINDOWS\system32\USER32.dll
:7c90e473 ntdll.KiUserCallbackDispatcher + 0x13
:7e428dd9 USER32.DefWindowProcW + 0xb9
:7e428d77 USER32.DefWindowProcW + 0x57
:7e418734 USER32.GetDC + 0x6d
:7e418816 ; C:\WINDOWS\system32\USER32.dll
:7e42a013 USER32.IsWindowUnicode + 0xa1
:7e42a039 USER32.CallWindowProcW + 0x1b
:0050fac8 TWinControl.DefaultHandler + $DC
:0050f9cc TWinControl.WndProc + $518
:0065279d TcxControl.WndProc + $121
:0075bbc4 TcxGridSite.WndProc + $20
:0048874e StdWndProc + $16
:7e418734 USER32.GetDC + 0x6d
:7e418816 ; C:\WINDOWS\system32\USER32.dll
:7e428ea0 ; C:\WINDOWS\system32\USER32.dll
:7e428eec ; C:\WINDOWS\system32\USER32.dll
:7c90e473 ntdll.KiUserCallbackDispatcher + 0x13
:0044c91e HandleException + $22A
:004539af InterceptAHandleExcept + $3F
:0048874e StdWndProc + $16
:7e418734 USER32.GetDC + 0x6d
:7e418816 ; C:\WINDOWS\system32\USER32.dll
:7e4189cd ; C:\WINDOWS\system32\USER32.dll
:7e418a10 USER32.DispatchMessageW + 0xf

这告诉我,问题是某种堆栈溢出 - 消息处理所使用的抨击事件。

建议???谢谢!

7 个答案:

答案 0 :(得分:4)

我强烈怀疑所涉及的TDeviceModule引用无效。除非方法是虚拟的,否则你不会总是看到调用方法对坏对象引用的任何不良影响,除非方法是虚拟的,在这种情况下,方法本身的调用通常(总是?)产生AV。 / p>

答案 1 :(得分:3)

从您的评论(“此处发生错误”)中,您的错误会在设置堆栈空间的循环中弹出,全部为212 Kb!它与您传递给程序的参数完全无关,与您作为参数传递的对象的可行性无关(那里没有CALL,它只是一个循环到PUSH $ 00的JNZ)直到DEC ECX操作标记ZERO标志,即$ 1a9e9次)。

由于您正在处理使用212Kb堆栈空间的过程,因此您应该尝试增加堆栈空间!更好的是,弄清楚为什么你的程序占用了那么多空间,并弄清楚其他程序是否处于相同的情况(注意用作局部变量的大型记录)。

答案 2 :(得分:3)

请参阅此问题:Guard page exceptions in Delphi?

通常,当你的堆栈用完时,你应该得到堆栈溢出异常。但是如果你的防护页面被别人触摸并且异常被静默地吃掉而没有扩展堆栈 - 那么当你扩展你的堆栈时你的代码将会崩溃。

这正是您的代码中发生的事情:您扩展堆栈并获得了AV。此汇编程序循环旨在触摸堆栈以通过防护页触发堆栈扩展。由于guarg页面消失了,但堆栈没有扩展 - 你在这里得到了简单的AV。

请注意,增加堆栈大小无济于事,因为堆栈根本不会增长。

你需要找到谁在玩你的筹码。

答案 3 :(得分:2)

我会注释掉3个变量中的每一个,然后一次取消注释,看看是否有任何特定的变量爆炸。 如果是这样,你只需要将你的问题减少2/3。

答案 4 :(得分:0)

一种可能性是3个局部变量(堆栈变量)的增长大于预期。我想如果对象是在一个包含在另一个BPL中的单元中声明并且没有正确重建(即你的程序认为它比它真的小),就会发生这种情况。
不管是什么原因,你可以试验一下,看看是否发生了这种情况。 在3个变量之间和之后放置“缓冲区”变量。

ex: 
  var 
    selChannel:TDeviceChannel; 
    Buff1 : array[1..1024] of AnsiChar;
    det:TDeviceDetector; 
    Buff2 : array[1..1024] of AnsiChar;
    shoKVMeas:TStoMeasurement; 
    Buff3 : array[1..1024] of AnsiChar;

这应该为你做两件事。 1)它应该阻止A / V,假设1024就足够了。 2)通过检查数组,您应该能够看到是否出现垃圾。这表明他们被上面的声明覆盖了。

答案 5 :(得分:0)

这是我学到的东西:

通过锻炼身体,我发现它很健康。

通过在堆栈上转储东西,我确定它确实耗尽了堆栈空间。

procedure TDeviceModule.Validate;
const
  icTestSize=400000;
var
  i:integer;
begin
  // ask the object stuff to try to see if it's healthy

  SelectChannel(dcCh1);

  ClassName;

  for eChannelNum:=low(TDeviceChannelNum) to high(TDeviceChannelNum) do
    if HasChannel(eChannelNum) then
      m_aChannels[eChannelNum].Validate;

  // exercise the stack to see if loading on extra stuff is a problem...it is

  i:=0;
  while i<icTestSize do
    begin
      asm
        push 00
      end;
      inc(i);
    end;

  i:=0;
  while i<icTestSize do
    begin
      asm
        pop ecx
      end;
      inc(i);
    end;
end;

有一些嵌套函数(既不是使用它也不是它的声明是问题的一部分,因为我没有意识到它们是问题的一部分)谁返回了一条记录我将称之为TBigRecord ...它是32 KB。不只是那个,但它被使用了很多次。

procedure TDeviceModule.GetMeasurements(blah blah blah);

  function _DoSomething1(blah blah blah):TBigRecord;
  begin
  end;

  function _DoSomething2(blah blah blah):TBigRecord;
  begin
  end;

  function _DoSomething3(blah blah blah):TBigRecord;
  begin
  end;

begin
  _DoSomething1(blah blah blah);
  _DoSomething2(blah blah blah);
  _DoSomething3(blah blah blah);
end;

每次我使用它(即使我不使用结果),我得到为结果值分配的堆栈空间。

我现在使用的解决方案是将这些函数更改为过程,因为我还没有使用返回值。

我增加了堆栈空间但不足以防止出现此问题。

我可以预期在这种情况下会报告堆栈溢出吗?

谢谢大家的宝贵帮助!这个问题让我担心......

答案 6 :(得分:0)

很抱歉过于简单但......

_DisplayList:TMeasDisplayListAncestor AND _DisplayList:TObjectList同时在范围内。

两个不同类型的eExposureStatus和两个布尔的bActiveErrorEnabled也是如此。

当你在本地程序中调用_GetMeasurement(ExpInfo,_DisplayList,eChannelNum,eExposureStatus,ctdVal1)时,变量和Type是否正在使用? TobjectList或TTMeasDisplayListAncestor?

除非我只是比我想的更醉......:)