Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
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 esp
numarası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
- k (Görüntü Yığını geri izleme)
- ~ (Konu Durumu)
- d, da, db, dc, dd, dD, df, dp, dq, du, dw (Monitör Belleği)
- u, ub, uu (Unassemble)
- r (Registerler)
- .sympath (Simge Yolunu Ayarla)
- x (Simgeleri İncele)
- dt (Görüntü Türü)
- !Analiz
- !Teb