Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Ett stacköverflöd är ett fel som trådar i användarläge kan stöta på. Det finns tre möjliga orsaker till det här felet:
En tråd använder hela stacken som är reserverad för den. Detta orsakas ofta av oändlig rekursion.
En tråd kan inte utöka stacken eftersom sidfilen är maxad och därför kan inga ytterligare sidor checkas in för att utöka stacken.
En tråd kan inte utöka stacken eftersom systemet ligger inom den korta period som används för att utöka sidfilen.
När en funktion som körs på en tråd allokerar lokala variabler placeras variablerna i trådens anropsstack. Mängden stackutrymme som krävs av funktionen kan vara lika stort som summan av storleken på alla lokala variabler. Kompilatorn utför dock vanligtvis optimeringar som minskar det stackutrymme som krävs av en funktion. Om två variabler till exempel finns i olika omfång kan kompilatorn använda samma stackminne för båda dessa variabler. Kompilatorn kan också eliminera vissa lokala variabler helt genom att optimera beräkningar.
Mängden optimering påverkas av kompilatorinställningar som tillämpas vid byggtiden. Till exempel med alternativet /F (Ange stackstorlek) – C++-kompilator.
Det här avsnittet förutsätter allmän kunskap om begrepp, till exempel trådar, trådblock, stack och heap. Mer information om dessa grundläggande begrepp finns i Microsoft Windows Internals av Mark Russinovich och David Solomon.
Att felsöka ett stacköverflöd utan symboler
Här är ett exempel på hur man felsöker ett stacköverflöd. I det här exemplet körs NTSD på samma dator som målprogrammet och omdirigerar utdata till KD på värddatorn. Se Kontroll av User-Mode-felsökaren från kernelfelsökaren för mer information.
Det första steget är att se vilken händelse som gjorde att felsökningsprogrammet bröt sig in:
0:002> .lastevent
Last event: Exception C00000FD, second chance
Du kan söka efter undantagskod 0xC00000FD i ntstatus.h, Den här undantagskoden är STATUS_STACK_OVERFLOW, vilket indikerar att det inte går att skapa en ny skyddssida för stacken. Alla statuskoder visas i 2.3.1 NTSTATUS-värden.
Du kan också använda kommandot !error för att söka efter fel i Windows-felsökningsprogrammet.
0:002> !error 0xC00000FD
Error code: (NTSTATUS) 0xc00000fd (3221225725) - A new guard page for the stack cannot be created.
Om du vill dubbelkolla att stacken svämmade över kan du använda kommandot k (Display Stack Backtrace):
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
Måltråden har gått in i COMCTL32!_chkstk, vilket indikerar stackproblem. Nu bör du undersöka stackanvändningen för målprocessen. Processen har flera trådar, men den viktiga är den som orsakade spill, så identifiera den här tråden först med kommandot ~ (Trådstatus):
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
Nu måste du undersöka tråd 2. Perioden till vänster om den här raden anger att det här är den aktuella tråden.
Stackinformationen finns i TEB (Trådmiljöblock) vid 0x7FFDC000. Det enklaste sättet att lista den är att använda !teb.
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```
Detta kräver dock att du har rätt symboler. En svårare situation är när du inte har några symboler och behöver använda kommandot dd (Visningsminne) för att visa rådatavärdena på den platsen:
0:002> dd 7ffdc000 L4
7ffdc000 009fdef0 00a00000 009fc000 00000000
För att tolka detta måste du slå upp definitionen av TEB-datastrukturen. Använd kommandot dt Display Type för att göra detta i ett system där symboler är tillgängliga.
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
...
Tråddatastrukturer
Om du vill veta mer om trådar kan du också visa information om trådkontrollblocksrelaterade strukturer ethread och kthread. (Observera att 64-bitarsexempel visas här.)
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
Mer information om tråddatastrukturer finns i Microsoft Windows Internals .
Om du tittar på en 32-bitarsversion av _TEB struktur, anger det att den andra och tredje DWORD:erna i TEB-strukturen pekar mot botten respektive överst i stacken. I det här exemplet är dessa adresser 0x00A00000 och 0x009FC000. (Stacken växer nedåt i minnet.) Du kan beräkna stackstorleken med hjälp av ? (Utvärdera uttryck) befallning:
0:002> ? a00000-9fc000
Evaluate expression: 16384 = 00004000
Detta visar att stackstorleken är 16 K. Den maximala stackstorleken lagras i fältet DeallocationStack, som är en del av den här TEB-strukturen. Fältet DeallocationStack anger stackens bas. Efter en viss beräkning kan du fastställa att fältets förskjutning är 0xE0C.
0:002> dd 7ffdc000+e0c L1
7ffdce0c 009c0000
0:002> ? a00000-9c0000
Evaluate expression: 262144 = 00040000
Detta visar att den maximala stackstorleken är 256 K, vilket innebär att mer än tillräckligt stackutrymme finns kvar.
Dessutom ser den här processen ren ut – den är inte i en oändlig rekursion eller överskrider sitt stackutrymme med hjälp av överdrivet stora stackbaserade datastrukturer.
Bryt nu in i KD och titta på den övergripande systemminnesanvändningen med kommandot !vm-tillägg :
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)
.....
Börja med att titta på osidindelad och sidsidig poolanvändning. Båda ligger väl inom gränserna, så dessa är inte orsaken till problemet.
Titta sedan på antalet bekräftade sidor: 183528 av 202280. Detta ligger mycket nära gränsen. Även om den här visningen inte visar att det här talet är helt på gränsen bör du komma ihåg att medan du utför felsökning i användarläge körs andra processer i systemet. Varje gång ett NTSD-kommando körs allokerar och frigör dessa andra processer även minne. Det innebär att du inte vet exakt hur minnestillståndet var när stacköverflödet inträffade. Med tanke på hur nära det bekräftade sidnumret ligger till gränsen är det rimligt att dra slutsatsen att sidfilen användes någon gång och detta orsakade stackspill.
Detta är inte en ovanlig förekomst och målprogrammet kan inte riktigt läggas skulden på för detta. Om det händer ofta kanske du vill överväga att höja det första stackåtagandet för det program som misslyckas.
Analysera ett enda funktionsanrop
Det kan också vara användbart att ta reda på exakt hur mycket stackutrymme ett visst funktionsanrop allokerar.
Det gör du genom att ta isär de första instruktionerna och leta efter instruktionsnumretsub esp. Då flyttas stackpekaren, vilket effektivt reserverar talbyte för lokala data.
Här är ett exempel. Använd först k-kommandot för att titta på stacken.
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
Använd sedan kommandot u, ub, uu (Unassemble) för att titta på monteringskoden på den adressen.
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
Detta visar att Header_Draw allokerat 0x58 byte stackutrymme.
Kommandot r (Registers) innehåller information om det aktuella innehållet i registren, till exempel esp.
Felsöka stack-överskridning när symboler är tillgängliga
Symboler ger etiketter till objekt som lagras i minnet, och när de är tillgängliga kan det göra det enklare att undersöka kod. En översikt över symboler finns i Använda symboler. Information om hur du anger symbolsökvägen finns i .sympath (Set Symbol Path).
För att skapa ett stacköverflöde kan vi använda den här koden, som fortsätter att anropa en underrutin tills stacken är uttömd.
// 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();
}
När koden kompileras och körs under WinDbg kommer den att loopas ett antal gånger och sedan generera ett stacköverflödesfel.
(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)
Använd kommandot !analyze för att kontrollera att vi verkligen har problem med vår loop.
...
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:
Med hjälp av kb-kommandot ser vi att det finns många instanser av vårt loopprogram som var och en använder minne.
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]
...
Om symboler är tillgängliga kan dt-_TEB användas för att visa information om trådblocket. Mer information om trådminne finns i Trådstackens storlek.
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
Vi kan också använda kommandot !teb som visar StackBase abd StackLimit.
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
Vi kan beräkna stackstorleken med det här kommandot.
0:000> ?? int(@$teb->NtTib.StackBase) - int(@$teb->NtTib.StackLimit)
int 0n1044480
Sammanfattning av kommandon
- k (Visa stackspårning)
- ~ (trådstatus)
- d, da, db, dc, dd, dD, df, dp, dq, du, dw (Visningsminne)
- u, ub, uu (ej monterad)
- r (register)
- .sympath (Ange symbolväg)
- x (granska symboler)
- dt (visningstyp)
- !analysera
- !teb
Se även
Komma igång med WinDbg (User-Mode)
/F (Ställ in stackstorlek) – alternativen för C++-kompilatorn