2010-06-21 123 views
2

我在我的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 

這表明問題是堆棧溢出的某種 - 消息處理使用的東西。

建議???謝謝!

+0

是否有任何類的構造函數的任何對象? – code4life 2010-06-21 03:15:14

+0

什麼是TDeviceChannel,TDeviceDetector,TStoMeasurement,TDeviceChannelNum,TMyEnum?即:他們是什麼SizeOf? – Alex 2010-06-21 07:35:34

+0

>是否有任何類的構造函數的任何對象? 這個對象是在調用這個例程之前在別處構造的,我們(未示出)檢索這些對象。 @Alexander:所有這些的大小是4(TDeviceChannel,TDeviceDetector和TStoMeasurement都是對象,因此它們的SizeOf將是指針(4)的SizeOf)。 – 2010-06-21 17:26:19

回答

3

從您的評論(「錯誤發生在這裏」),您的錯誤彈出設置堆棧空間的循環,所有212 Kb的!它與傳遞給過程的參數完全無關,與作爲參數傳遞的對象的可行性無關(在那裏沒有CALL,它只是一個JNZ,它循環到PUSH $ 00直到DEC ECX操作標記ZERO標誌,即$ 1a9e9次)。

由於您正在處理使用212Kb堆棧空間的過程,因此您應該嘗試增加更多堆棧空間!更好的是,弄清楚爲什麼你的程序使用了這麼多的空間,並且弄清楚其他程序是否處於相同的情況下(注意用作局部變量的大型記錄)。

+0

好點;更多關於這個在我添加的答案。 – 2010-06-21 22:18:38

4

我強烈懷疑所涉及的TDeviceModule引用無效。除非方法是虛擬的,在這種情況下方法本身的調用通常會(總是)產生一個AV,否則你不會總是看到任何對壞對象引用調用方法的不良影響。

+0

+1這是恕我直言,最有可能的一個,也沒有詳細檢查。 – 2010-06-21 06:50:22

+0

我記錄了這些對象的初始指針值,當它工作並比較自己剛纔執行的開始並且它是相同的值。 – 2010-06-21 18:38:51

+0

我也嘗試「行使」堆棧之前,發生錯誤發生的電話: 我:= 0; 而我<100000做 開始 asm 推00 結束;公司(i)公司; 結束; i:= 0; while I <100000 do begin asm pop ecx end;公司(i)公司; 結束; 我可以這樣做*正確* *之前*進行調用的錯誤發生沒有問題!我想我即將學習一些重要的東西... 謝謝大家的幫助! – 2010-06-21 18:41:14

2

我會將3個變量中的每一個註釋掉,然後每次不註釋一個,看看它們中是否有特定的一個正在炸燬。 如果是這樣,你剛剛減少了2/3的問題。

3

看到這個問題:Guard page exceptions in Delphi?

通常情況下,你應該得到堆棧溢出異常,當你走你的堆棧。但是如果你的守衛頁面被其他人觸動,並且異常在沒有擴展堆棧的情況下被默默食用 - 那麼當你擴展你的堆棧時,你的代碼將會隨着AV崩潰。

這完全是在你的代碼中發生的事情:你展開堆棧,你得到了AV。此彙編程序週期旨在通過觸發堆棧觸發堆棧擴展。由於guarg頁面消失了,但堆棧沒有擴展 - 你在這裏獲得了簡單的AV。

請注意,增加堆棧大小不會有幫助,因爲堆棧根本不會增長。

你需要找到誰玩你的堆棧。

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)通過檢查數組,你應該能夠看到是否出現垃圾。這表明它們正被上面直接聲明覆蓋。

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; 

每次我使用它(即使我不使用結果),我得到分配給結果值的堆棧空間。

我現在使用的解決方案是將這些函數更改爲過程,因爲我沒有使用返回值。

我增加了堆棧空間,但還不足以防止此問題。

我可以預計在這種情況下會報告堆棧溢出嗎?

謝謝大家的寶貴幫助!這個問題讓我擔心......

+0

我想你會看到別的。 第一:堆棧空間不足意味着堆棧溢出,而不是AV。第二:使用工具或代碼來檢查ESP /當前堆棧大小和堆棧頂部的線程。很有可能你的籌碼量遠未達到最大值。第三:將功能轉換爲程序並不能真正解決任何問題,它只能隱藏一些東西。這是因爲函數ABC():TSomeRecord實際上是二進制級別的過程ABC(var Result:TSomeRecord)。 – Alex 2010-06-22 11:32:13

+0

是的;我會希望看到堆棧溢出錯誤,而不是AV。你建議用什麼工具來研究這些?我有些擔心這是一個問題(即使問題已經「解決」了)......不禁要問,我是否會再次聽到這個問題。 我沒有使用返回值,所以我完全刪除它。 謝謝你的幫助! – 2010-06-22 14:30:43

+0

看看VMMap或類似的工具。 – Alex 2010-06-22 15:21:43

0

對不起,是簡單,但...

_DisplayList:TMeasDisplayListAncestor和_DisplayList:TObjectList同時都在範圍之內。

所以是兩個不同類型的eExposureStatus和兩個布爾型的bActiveErrorEnabled。

當您在本地過程中調用_GetMeasurement(ExpInfo,_DisplayList,eChannelNum,eExposureStatus,ctdVal1)時使用哪個變量和類型? TobjectList或TTMeasDisplayListAncestor?

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

+0

我沒有看到TMeasDisplayListAncestor和TObjectList用於任何地方的同一個對象。 我對兩個不同類型的eExposureStatus和重疊的變量名稱bActiveErrorEnabled沒關係。 _DisplayList是TTMeasDisplayListAncestor的後代。 – 2010-06-22 14:23:10