Aracılığıyla paylaş


Yığın Taşmasında Hata Ayıklama

Kullanıcı modu iş parçacıklarının karşılaşabileceği bir hata olan yığın taşması. Bu hatanın üç olası nedeni vardır:

  • İş parçacığı, bunun için ayrılmış yığının tamamını kullanır. Bunun nedeni genellikle sonsuz özyinelemedir.

  • Sayfa dosyası üst düzeye çıkarıldığından ve yığını genişletmek için ek sayfalar ayrılamadığından, iş parçacığı yığını genişletemez.

  • Sistem sayfa dosyasını genişletmek için kullanılan kısa süre içinde olduğundan iş parçacığı yığını genişletemiyor.

İş parçacığında çalışan bir işlev yerel değişkenler ayırdığında, değişkenler iş parçacığının çağrı yığınına yerleştirilir. İşlev için gereken yığın alanı miktarı, tüm yerel değişkenlerin boyutlarının toplamı kadar büyük olabilir. Ancak, derleyici genellikle bir işlevin gerektirdiği yığın alanını azaltan iyileştirmeler gerçekleştirir. Örneğin, iki değişken farklı kapsamlardaysa, derleyici bu değişkenlerin her ikisi için de aynı yığın belleğini kullanabilir. Derleyici ayrıca hesaplamaları iyileştirerek bazı yerel değişkenleri tamamen ortadan kaldırabilir.

İyileştirme miktarı, derleme zamanında uygulanan derleyici ayarlarından etkilenir. Örneğin, /F (Yığın Boyutunu Ayarla) - C++ Derleyici Seçeneğine göre.

Bu konuda, iş parçacıkları, iş parçacığı blokları, yığın ve kütük gibi kavramlar hakkında genel bilgi varsayılır. Bu temel kavramlar hakkında daha fazla bilgi için Mark Russinovich ve David Solomon tarafından kullanılan Microsoft Windows Internals konusuna bakın.

Semboller olmadan yığın taşması hatasını ayıklama

Yığın taşmasında hata ayıklama örneği aşağıda verilmiştir. Bu örnekte NTSD, hedef uygulamayla aynı bilgisayarda çalışıyor ve çıktısını konak bilgisayardaki KD'ye yönlendiriyor. Ayrıntılar için bkz. Çekirdek Hata Ayıklayıcısı'ndan User-Mode Hata Ayıklayıcısını Denetleme .

İlk adım, hata ayıklayıcının hangi olay nedeniyle durduğunu görmektir.

0:002> .lastevent 
Last event: Exception C00000FD, second chance 

Ntstatus.h dosyasında özel durum kodu 0xC00000FD arayabilirsiniz. Bu özel durum kodu STATUS_STACK_OVERFLOW, bu da yığın için yeni bir koruma sayfası oluşturulamadığını gösterir. Tüm durum kodları 2.3.1 NTSTATUS Değerlerinde listelenir.

Windows Hata Ayıklayıcısı'nda hataları aramak için !error komutunu da kullanabilirsiniz.

0:002> !error 0xC00000FD
Error code: (NTSTATUS) 0xc00000fd (3221225725) - A new guard page for the stack cannot be created.

Yığının taştığını doğrulamak için k (Yığın Geri İzleme Görüntüleme) komutunu kullanabilirsiniz:

0:002> k 
ChildEBP RetAddr
009fdd0c 71a32520 COMCTL32!_chkstk+0x25
009fde78 77cf8290 COMCTL32!ListView_WndProc+0x4c4
009fde98 77cfd634 USER32!_InternalCallWinProc+0x18
009fdf00 77cd55e9 USER32!UserCallWinProcCheckWow+0x17f
009fdf3c 77cd63b2 USER32!SendMessageWorker+0x4a3
009fdf5c 71a45b30 USER32!SendMessageW+0x44
009fdfec 71a45bb0 COMCTL32!CCSendNotify+0xc0e
009fdffc 71a1d688 COMCTL32!CICustomDrawNotify+0x2a
009fe074 71a1db30 COMCTL32!Header_Draw+0x63
009fe0d0 71a1f196 COMCTL32!Header_OnPaint+0x3f
009fe128 77cf8290 COMCTL32!Header_WndProc+0x4e2
009fe148 77cfd634 USER32!_InternalCallWinProc+0x18
009fe1b0 77cd4490 USER32!UserCallWinProcCheckWow+0x17f
009fe1d8 77cd46c8 USER32!DispatchClientMessage+0x31
009fe200 77f7bb3f USER32!__fnDWORD+0x22
009fe220 77cd445e ntdll!_KiUserCallbackDispatcher+0x13
009fe27c 77cfd634 USER32!DispatchMessageWorker+0x3bc
009fe2e4 009fe4a8 USER32!UserCallWinProcCheckWow+0x17f
00000000 00000000 0x9fe4a8 

Hedef iş parçacığı, bir yığın sorununu gösteren COMCTL32!_chkstk'ye arızalanmıştır. Şimdi hedef işlemin yığın kullanımını araştırmanız gerekir. İşlemin birden çok iş parçacığı vardır, ancak önemli olan taşma işlemine neden olan iş parçacığıdır, bu nedenle önce ~ (İş Parçacığı Durumu) komutunu kullanarak bu iş parçacığını tanımlayın:

0:002> ~*k

   0  id: 570.574   Suspend: 1 Teb 7ffde000 Unfrozen
   .....

   1  id: 570.590   Suspend: 1 Teb 7ffdd000 Unfrozen
   .....

. 2  id: 570.598   Suspend: 1 Teb 7ffdc000 Unfrozen
ChildEBP RetAddr
 009fdd0c 71a32520 COMCTL32!_chkstk+0x25 
.....

   3  id: 570.760   Suspend: 1 Teb 7ffdb000 Unfrozen 

Şimdi iş parçacığı 2'yi araştırmanız gerekiyor. Bu satırın sol tarafındaki nokta, bunun geçerli konu olduğunu gösterir.

TEB'de (İş Parçacığı Ortam Bloğu) 0x7FFDC000 adresinde yığın bilgileri yer alır. Listelemenin en kolay yolu !teb kullanmaktır.

0:000> !teb
TEB at 000000c64b95d000
    ExceptionList:        0000000000000000
    StackBase:            000000c64ba80000
    StackLimit:           000000c64ba6f000
    SubSystemTib:         0000000000000000
    FiberData:            0000000000001e00
    ArbitraryUserPointer: 0000000000000000
    Self:                 000000c64b95d000
    EnvironmentPointer:   0000000000000000
    ClientId:             0000000000003bbc . 0000000000004ba0
    RpcHandle:            0000000000000000
    Tls Storage:          0000027957243530
    PEB Address:          000000c64b95c000
    LastErrorValue:       0
    LastStatusValue:      0
    Count Owned Locks:    0
    HardErrorMode:        0```

Ancak bunun için uygun sembollere sahip olmanız gerekir. Daha zor bir durum, simgeniz olmaması ve ham değerleri o konumda görüntülemek için dd (Belleği Görüntüle) komutunu kullanmanız gerektiği durumlardır:

0:002> dd 7ffdc000 L4 
7ffdc000   009fdef0 00a00000 009fc000 00000000 

Bunu yorumlamak için TEB veri yapısının tanımını araştırmanız gerekir. Bunu simgelerin kullanılabildiği bir sistemde yapmak için dt Görüntüleme Türü komutunu kullanın.

0:000> dt _TEB
ntdll!_TEB
   +0x000 NtTib            : _NT_TIB
   +0x01c EnvironmentPointer : Ptr32 Void
   +0x020 ClientId         : _CLIENT_ID
   +0x028 ActiveRpcHandle  : Ptr32 Void
   +0x02c ThreadLocalStoragePointer : Ptr32 Void
   +0x030 ProcessEnvironmentBlock : Ptr32 _PEB
   +0x034 LastErrorValue   : Uint4B
   +0x038 CountOfOwnedCriticalSections : Uint4B
   +0x03c CsrClientThread  : Ptr32 Void
   +0x040 Win32ThreadInfo  : Ptr32 Void
   +0x044 User32Reserved   : [26] Uint4B
   +0x0ac UserReserved     : [5] Uint4B
   +0x0c0 WOW32Reserved    : Ptr32 Void
...

İş Parçacığı Veri Yapıları

İş parçacıkları hakkında daha fazla bilgi edinmek için, iş parçacığı denetim bloğuyla ilgili yapılar ethread ve kthread hakkında da bilgi görüntüleyebilirsiniz. (Burada 64 bit örneklerin gösterildiğini unutmayın.)

0:001> dt nt!_ethread
ntdll!_ETHREAD
   +0x000 Tcb              : _KTHREAD
   +0x430 CreateTime       : _LARGE_INTEGER
   +0x438 ExitTime         : _LARGE_INTEGER
   +0x438 KeyedWaitChain   : _LIST_ENTRY
   +0x448 PostBlockList    : _LIST_ENTRY
   +0x448 ForwardLinkShadow : Ptr64 Void
   +0x450 StartAddress     : Ptr64 Void
...
0:001> dt nt!_kthread
ntdll!_KTHREAD
   +0x000 Header           : _DISPATCHER_HEADER
   +0x018 SListFaultAddress : Ptr64 Void
   +0x020 QuantumTarget    : Uint8B
   +0x028 InitialStack     : Ptr64 Void
   +0x030 StackLimit       : Ptr64 Void
   +0x038 StackBase        : Ptr64 Void

İş parçacığı veri yapıları hakkında daha fazla bilgi için Microsoft Windows internals'e bakın.

_TEB yapısının 32 bitlik bir sürümüne baktığımızda, TEB yapısındaki ikinci ve üçüncü DWORD'lerin sırasıyla yığının altına ve üstüne işaret ettiğini gösterir. Bu örnekte, bu adresler 0x00A00000 ve 0x009FC000. (Yığın bellekte aşağı doğru büyür.) ? (İfadeyi Değerlendir) komutunu kullanarak yığın boyutunu hesaplayabilirsiniz.

0:002> ? a00000-9fc000
Evaluate expression: 16384 = 00004000 

Bu, yığın boyutunun 16 K olduğunu gösterir. Maksimum yığın boyutu, bu TEB yapısının bir parçası olan DeallocationStack alanında depolanır. DeallocationStack alanı yığının tabanını gösterir. Bir hesaplamadan sonra, bu alanın uzaklığının 0xE0C olduğunu belirleyebilirsiniz.

0:002> dd 7ffdc000+e0c L1 
7ffdce0c   009c0000 

0:002> ? a00000-9c0000 
Evaluate expression: 262144 = 00040000 

Bu, en büyük yığın boyutunun 256 K olduğunu gösterir ve bu da yeterli yığın alanının kalmadığı anlamına gelir.

Ayrıca, bu süreç temiz görünüyor; aşırı büyük yığın tabanlı veri yapıları kullanarak sonsuz bir özyineleme oluşturmuyor veya yığın alanını aşmıyor.

Şimdi KD'ye girin ve !vm extension komutuyla genel sistem belleği kullanımına bakın:

0:002> .breakin 
Break instruction exception - code 80000003 (first chance)
ntoskrnl!_DbgBreakPointWithStatus+4:
80148f9c cc               int     3

kd> !vm 

*** Virtual Memory Usage ***
        Physical Memory:     16268   (   65072 Kb)
        Page File: \??\C:\pagefile.sys
           Current:    147456Kb Free Space:     65988Kb
           Minimum:     98304Kb Maximum:       196608Kb
        Available Pages:      2299   (    9196 Kb)
        ResAvail Pages:       4579   (   18316 Kb)
        Locked IO Pages:        93   (     372 Kb)
        Free System PTEs:    42754   (  171016 Kb)
        Free NP PTEs:         5402   (   21608 Kb)
        Free Special NP:       348   (    1392 Kb)
        Modified Pages:        757   (    3028 Kb)
        NonPagedPool Usage:    811   (    3244 Kb)
        NonPagedPool Max:     6252   (   25008 Kb)
        PagedPool 0 Usage:    1337   (    5348 Kb)
        PagedPool 1 Usage:     893   (    3572 Kb)
        PagedPool 2 Usage:     362   (    1448 Kb)
        PagedPool Usage:      2592   (   10368 Kb)
        PagedPool Maximum:   13312   (   53248 Kb)
        Shared Commit:        3928   (   15712 Kb)
        Special Pool:         1040   (    4160 Kb)
        Shared Process:       3641   (   14564 Kb)
        PagedPool Commit:     2592   (   10368 Kb)
        Driver Commit:         887   (    3548 Kb)
        Committed pages:     45882   (  183528 Kb)
        Commit limit:        50570   (  202280 Kb)

        Total Private:       33309   (  133236 Kb)
         ..... 

İlk olarak, sayfalanmamış ve sayfalı havuz kullanımına bakın. Her ikisi de sınırlar içindedir, bu nedenle sorunun nedeni bunlar değildir.

Ardından, işlenen sayfa sayısına bakın: 183528 / 202280. Bu sınıra çok yakın. Bu ekran, bu sayıyı tamamen sınırda olarak göstermese de, kullanıcı modu hata ayıklaması gerçekleştirirken diğer işlemlerin sistemde çalıştığını aklınızda bulundurmanız gerekir. Bir NTSD komutu her çalıştırıldığında, bu diğer işlemler de bellek ayırır ve boşaltır. Bu, yığın taşması oluştuğu sırada bellek durumunun tam olarak nasıl olduğunu bilmediğiniz anlamına gelir. İşlenmiş sayfa numarasının sınıra ne kadar yakın olduğu göz önünde bulundurulduğunda, sayfa dosyasının bir noktada kullanıldığı ve bunun yığın taşmasına neden olduğu sonucuna varmak mantıklıdır.

Bu, alışılmadık bir durum değildir ve hedef uygulama gerçekten bu konuda suçlanamaz. Sık sık gerçekleşiyorsa, hata veren uygulama için başlangıç yığını taahhüdünü yükseltmeyi düşünebilirsiniz.

Tek bir İşlev Çağrısını Çözümleme

Ayrıca, belirli bir işlev çağrısının tam olarak ne kadar yığın alanı ayırıyor olduğunu öğrenmek de yararlı olabilir.

Bunu yapmak için ilk birkaç yönergeyi sökün ve yönerge sub espnumarasını arayın. Yığın işaretçisini kaydırarak, yerel veriler için sayı baytını etkili bir şekilde rezerve eder.

Aşağıda bir örnek verilmiştir. Önce yığına bakmak için k komutunu kullanın.

0:002> k 
ChildEBP RetAddr
009fdd0c 71a32520 COMCTL32!_chkstk+0x25
009fde78 77cf8290 COMCTL32!ListView_WndProc+0x4c4
009fde98 77cfd634 USER32!_InternalCallWinProc+0x18
009fdf00 77cd55e9 USER32!UserCallWinProcCheckWow+0x17f
009fdf3c 77cd63b2 USER32!SendMessageWorker+0x4a3
009fdf5c 71a45b30 USER32!SendMessageW+0x44
009fdfec 71a45bb0 COMCTL32!CCSendNotify+0xc0e
009fdffc 71a1d688 COMCTL32!CICustomDrawNotify+0x2a
009fe074 71a1db30 COMCTL32!Header_Draw+0x63
009fe0d0 71a1f196 COMCTL32!Header_OnPaint+0x3f
009fe128 77cf8290 COMCTL32!Header_WndProc+0x4e2

Ardından u, ub, uu (Unassemble) komutunu kullanarak bu adreste derleyici koduna bakın.

0:002> u COMCTL32!Header_Draw
 COMCTL32!Header_Draw :
71a1d625 55               push    ebp
71a1d626 8bec             mov     ebp,esp
71a1d628 83ec58           sub     esp,0x58
71a1d62b 53               push    ebx
71a1d62c 8b5d08           mov     ebx,[ebp+0x8]
71a1d62f 56               push    esi
71a1d630 57               push    edi
71a1d631 33f6             xor     esi,esi 

Bu, Header_Draw yığın alanı 0x58 bayt ayırdığını gösterir.

r (Registers) komutu, kayıt defterlerinin geçerli içeriği hakkında esp gibi bilgiler sağlar.

Simge mevcutken yığın taşmasını ayıklama

Simgeler, bellekte depolanan öğelere etiketler sağlar ve kullanılabilir olduğunda kodu incelemeyi kolaylaştırabilir. Simgelere genel bakış için bkz. Sembolleri Kullanma. Sembol yolunu ayarlama hakkında bilgi için bkz. .sympath (Sembol Yolunu Ayarla).

Yığın taşması oluşturmak için, yığın tükenene kadar devamlı olarak bir alt rutin çağıran bu kodu kullanabiliriz.

// StackOverFlow1.cpp 
// This program calls a sub routine using recursion too many times
// This causes a stack overflow
//

#include <iostream>

void Loop2Big()
{
    const char* pszTest = "My Test String";
    for (int LoopCount = 0; LoopCount < 10000000; LoopCount++)
    {
        std::cout << "In big loop \n";
        std::cout << (pszTest), "\n";
        std::cout << "\n";
        Loop2Big();
    }
}


int main()
{
    std::cout << "Calling Loop to use memory \n";
    Loop2Big();
}

Kod derlendiğinde ve WinDbg altında çalıştırıldığında, belirli bir sayıda kez döngüye girer ve ardından bir yığın taşması istisnası fırlatır.

(336c.264c): Break instruction exception - code 80000003 (first chance)
eax=00000000 ebx=00000000 ecx=0fa90000 edx=00000000 esi=773f1ff4 edi=773f25bc
eip=77491a02 esp=010ffa0c ebp=010ffa38 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ntdll!LdrpDoDebuggerBreak+0x2b:
77491a02 cc              int     3
0:000> g
(336c.264c): Stack overflow - code c00000fd (first chance)

Döngümüzde gerçekten bir sorun olup olmadığını denetlemek için !analyze komutunu kullanın.

...

FAULTING_SOURCE_LINE_NUMBER:  25

FAULTING_SOURCE_CODE:  
    21: int main()
    22: {
    23:     std::cout << "Calling Loop to use memory \n";
    24:     Loop2Big();
>   25: }
    26: 

Kb komutunu kullanarak her biri bellek kullanan döngü programımızın birçok örneği olduğunu görüyoruz.

0:000> kb
 # ChildEBP RetAddr      Args to Child      
...
0e 010049b0 00d855b5     01004b88 00d81023 00ff5000 StackOverFlow1!Loop2Big+0x57 [C:\StackOverFlow1\StackOverFlow1.cpp @ 13] 
0f 01004a9c 00d855b5     01004c74 00d81023 00ff5000 StackOverFlow1!Loop2Big+0x85 [C:\StackOverFlow1\StackOverFlow1.cpp @ 17] 
10 01004b88 00d855b5     01004d60 00d81023 00ff5000 StackOverFlow1!Loop2Big+0x85 [C:\StackOverFlow1\StackOverFlow1.cpp @ 17] 
11 01004c74 00d855b5     01004e4c 00d81023 00ff5000 StackOverFlow1!Loop2Big+0x85 [C:\StackOverFlow1\StackOverFlow1.cpp @ 17] 
12 01004d60 00d855b5     01004f38 00d81023 00ff5000 StackOverFlow1!Loop2Big+0x85 [C:\StackOverFlow1\StackOverFlow1.cpp @ 17] 
13 01004e4c 00d855b5     01005024 00d81023 00ff5000 StackOverFlow1!Loop2Big+0x85 [C:\StackOverFlow1\StackOverFlow1.cpp @ 17] 
14 01004f38 00d855b5     01005110 00d81023 00ff5000 StackOverFlow1!Loop2Big+0x85 [C:\StackOverFlow1\StackOverFlow1.cpp @ 17] 
15 01005024 00d855b5     010051fc 00d81023 00ff5000 StackOverFlow1!Loop2Big+0x85 [C:\StackOverFlow1\StackOverFlow1.cpp @ 17] 
16 01005110 00d855b5     010052e8 00d81023 00ff5000 StackOverFlow1!Loop2Big+0x85 [C:\StackOverFlow1\StackOverFlow1.cpp @ 17] 
17 010051fc 00d855b5     010053d4 00d81023 00ff5000 StackOverFlow1!Loop2Big+0x85 [C:\StackOverFlow1\StackOverFlow1.cpp @ 17] 
18 010052e8 00d855b5     010054c0 00d81023 00ff5000 StackOverFlow1!Loop2Big+0x85 [C:\StackOverFlow1\StackOverFlow1.cpp @ 17] 
19 010053d4 00d855b5     010055ac 00d81023 00ff5000 StackOverFlow1!Loop2Big+0x85 [C:\StackOverFlow1\StackOverFlow1.cpp @ 17] 
1a 010054c0 00d855b5     01005698 00d81023 00ff5000 StackOverFlow1!Loop2Big+0x85 [C:\StackOverFlow1\StackOverFlow1.cpp @ 17] 
1b 010055ac 00d855b5     01005784 00d81023 00ff5000 StackOverFlow1!Loop2Big+0x85 [C:\StackOverFlow1\StackOverFlow1.cpp @ 17] 

...

Simgeler varsa dt _TEB iş parçacığı bloğu hakkındaki bilgileri görüntülemek için kullanılabilir. İş parçacığı belleği hakkında daha fazla bilgi için, İş Parçacığı Yığını Boyutu'na bkz.

0:000> dt _TEB
ntdll!_TEB
   +0x000 NtTib            : _NT_TIB
   +0x01c EnvironmentPointer : Ptr32 Void
   +0x020 ClientId         : _CLIENT_ID
   +0x028 ActiveRpcHandle  : Ptr32 Void
   +0x02c ThreadLocalStoragePointer : Ptr32 Void
   +0x030 ProcessEnvironmentBlock : Ptr32 _PEB
   +0x034 LastErrorValue   : Uint4B
   +0x038 CountOfOwnedCriticalSections : Uint4B
   +0x03c CsrClientThread  : Ptr32 Void
   +0x040 Win32ThreadInfo  : Ptr32 Void
   +0x044 User32Reserved   : [26] Uint4B
   +0x0ac UserReserved     : [5] Uint4B
   +0x0c0 WOW32Reserved    : Ptr32 Void

StackBase abd StackLimit'i görüntüleyen !teb komutunu da kullanabiliriz.

0:000> !teb
TEB at 00ff8000
    ExceptionList:        01004570
    StackBase:            01100000
    StackLimit:           01001000
    SubSystemTib:         00000000
    FiberData:            00001e00
    ArbitraryUserPointer: 00000000
    Self:                 00ff8000
    EnvironmentPointer:   00000000
    ClientId:             0000336c . 0000264c
    RpcHandle:            00000000
    Tls Storage:          00ff802c
    PEB Address:          00ff5000
    LastErrorValue:       0
    LastStatusValue:      c00700bb
    Count Owned Locks:    0
    HardErrorMode:        0

Bu komutu kullanarak yığın boyutunu hesaplayabiliriz.

0:000> ?? int(@$teb->NtTib.StackBase) - int(@$teb->NtTib.StackLimit)
int 0n1044480

Komutların özeti

Ayrıca bkz.

WinDbg kullanmaya başlama (User-Mode)

/F (Yığın Boyutunu Ayarla) - C++ Derleyici Seçeneği