Debuggen eines Stapelüberlaufs - Windows drivers (2025)

  • Artikel

Ein Stapelüberlauf ist ein Fehler, auf den Benutzermodusthreads stoßen können. Für diesen Fehler gibt es drei mögliche Ursachen:

  • Ein Thread verwendet den gesamten dafür reservierten Stapel. Dies wird oft durch unendliche Rekursion verursacht.

  • Ein Thread kann den Stapel nicht erweitern, da die Seitendatei maximal ausgecheckt ist, und daher können keine zusätzlichen Seiten zum Erweitern des Stapels zugesichert werden.

  • Ein Thread kann den Stapel nicht erweitern, da sich das System innerhalb des kurzen Zeitraums befindet, mit dem die Seitendatei erweitert wird.

Wenn eine Funktion, die auf einem Thread ausgeführt wird, lokale Variablen zuordnet, werden die Variablen im Aufrufstapel des Threads platziert. Der für die Funktion erforderliche Stapelplatz kann so groß sein wie die Summe der Größen aller lokalen Variablen. Der Compiler führt jedoch in der Regel Optimierungen durch, die den für eine Funktion erforderlichen Stapelspeicher reduzieren. Wenn sich beispielsweise zwei Variablen in unterschiedlichen Bereichen befinden, kann der Compiler für beide Variablen den gleichen Stapelspeicher verwenden. Der Compiler kann auch einige lokale Variablen vollständig beseitigen, indem Berechnungen optimiert werden.

Der Umfang der Optimierung wird durch Compilereinstellungen beeinflusst, die zur Buildzeit angewendet werden. Beispiel: durch die /F (Set Stack Size) - C++-Compileroption.

In diesem Thema werden allgemeine Kenntnisse von Konzepten wie Threads, Threadblöcken, Stapeln und Heap vorausgesetzt. Weitere Informationen zu diesen Basiskonzepten finden Sie unter Microsoft Windows Internals von Mark Russinovich und David Solomon.

Debuggen eines Stapelüberlaufs ohne Symbole

Hier ist ein Beispiel zum Debuggen eines Stapelüberlaufs. In diesem Beispiel wird NTSD auf demselben Computer wie die Zielanwendung ausgeführt und leitet die Ausgabe an KD auf dem Hostcomputer um. Details finden Sie unter Steuern des Benutzermodusdebuggers aus dem Kerneldebugger.

Im ersten Schritt wird gezeigt, welches Ereignis dazu führte, dass der Debugger eingebrochen wurde:

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

Sie können ausnahmecode 0xC00000FD in ntstatus.h nachschlagen. Dieser Ausnahmecode ist STATUS_STACK_OVERFLOW, der angibt , dass eine neue Schutzseite für den Stapel nicht erstellt werden kann. Alle Statuscodes werden in 2.3.1 NTSTATUS-Werten aufgeführt.

Sie können auch den Befehl "!error " verwenden, um Fehler im Windows-Debugger nachzuschlagen.

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

Um zu überprüfen, ob der Stapel überläuft, können Sie den Befehl k (Display Stack Backtrace) verwenden:

0:002> k ChildEBP RetAddr009fdd0c 71a32520 COMCTL32!_chkstk+0x25009fde78 77cf8290 COMCTL32!ListView_WndProc+0x4c4009fde98 77cfd634 USER32!_InternalCallWinProc+0x18009fdf00 77cd55e9 USER32!UserCallWinProcCheckWow+0x17f009fdf3c 77cd63b2 USER32!SendMessageWorker+0x4a3009fdf5c 71a45b30 USER32!SendMessageW+0x44009fdfec 71a45bb0 COMCTL32!CCSendNotify+0xc0e009fdffc 71a1d688 COMCTL32!CICustomDrawNotify+0x2a009fe074 71a1db30 COMCTL32!Header_Draw+0x63009fe0d0 71a1f196 COMCTL32!Header_OnPaint+0x3f009fe128 77cf8290 COMCTL32!Header_WndProc+0x4e2009fe148 77cfd634 USER32!_InternalCallWinProc+0x18009fe1b0 77cd4490 USER32!UserCallWinProcCheckWow+0x17f009fe1d8 77cd46c8 USER32!DispatchClientMessage+0x31009fe200 77f7bb3f USER32!__fnDWORD+0x22009fe220 77cd445e ntdll!_KiUserCallbackDispatcher+0x13009fe27c 77cfd634 USER32!DispatchMessageWorker+0x3bc009fe2e4 009fe4a8 USER32!UserCallWinProcCheckWow+0x17f00000000 00000000 0x9fe4a8 

Der Zielthread ist in COMCTL32!_chkstk unterteilt, was auf ein Stapelproblem hinweist. Nun sollten Sie die Stapelnutzung des Zielprozesses untersuchen. Der Prozess hat mehrere Threads, aber die wichtige ist die, die den Überlauf verursacht hat. Identifizieren Sie diesen Thread zuerst mithilfe des Befehls ~ (Threadstatus):

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 UnfrozenChildEBP RetAddr 009fdd0c 71a32520 COMCTL32!_chkstk+0x25 ..... 3 id: 570.760 Suspend: 1 Teb 7ffdb000 Unfrozen 

Jetzt müssen Sie Thread 2 untersuchen. Der Punkt links von dieser Zeile gibt an, dass dies der aktuelle Thread ist.

Die Stapelinformationen sind im TEB (Thread Environment Block) bei 0x7FFDC000 enthalten. Die einfachste Möglichkeit zum Auflisten ist die Verwendung von !teb.

0:000> !tebTEB 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```

Dies erfordert jedoch, dass Sie über die richtigen Symbole verfügen. Eine schwierigere Situation ist, wenn Sie keine Symbole haben und den Befehl dd (Anzeigespeicher) verwenden müssen, um die Rohwerte an diesem Speicherort anzuzeigen:

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

Um dies zu interpretieren, müssen Sie die Definition der TEB-Datenstruktur nachschlagen. Verwenden Sie den Befehl "dt Anzeigetyp ", um dies auf einem System zu tun, in dem Symbole verfügbar sind.

0:000> dt _TEBntdll!_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...

Threaddatenstrukturen

Um mehr über Threads zu erfahren, können Sie auch Informationen zum Threadsteuerungsblock-bezogenen Strukturen ethread und kthread anzeigen. (Beachten Sie, dass hier 64-Bit-Beispiele gezeigt werden.)

0:001> dt nt!_ethreadntdll!_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!_kthreadntdll!_KTHREAD +0x000 Header : _DISPATCHER_HEADER +0x018 SListFaultAddress : Ptr64 Void +0x020 QuantumTarget : Uint8B +0x028 InitialStack : Ptr64 Void +0x030 StackLimit : Ptr64 Void +0x038 StackBase : Ptr64 Void

Weitere Informationen zu Threaddatenstrukturen finden Sie unter Microsoft Windows Internals .

Wenn Sie eine 32-Bit-Version der _TEB-Struktur betrachten, zeigt sie an, dass die zweite und dritte DWORDs in der TEB-Struktur auf den unteren und oberen Rand des Stapels zeigen. In diesem Beispiel sind diese Adressen 0x00A00000 und 0x009FC000. (Der Stapel wächst im Arbeitsspeicher nach unten.) Sie können die Stapelgröße mit dem ? (Auswerten des Ausdrucks) Befehl:

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

Dies zeigt, dass die Stapelgröße 16 K beträgt. Die maximale Stapelgröße wird im Feld DeallocationStack gespeichert, das Teil dieser TEB-Struktur ist. Das DeallocationStack Feld gibt die Basis des Stapels an. Nach einer Berechnung können Sie bestimmen, dass der Offset dieses Felds 0xE0C ist.

0:002> dd 7ffdc000+e0c L1 7ffdce0c 009c0000 0:002> ? a00000-9c0000 Evaluate expression: 262144 = 00040000 

Dies zeigt, dass die maximale Stapelgröße 256 K beträgt, was bedeutet, dass mehr als ausreichendEr Stapelraum übrig bleibt.

Darüber hinaus sieht dieser Prozess sauber aus – es befindet sich nicht in einer unendlichen Rekursion oder überschreitet seinen Stapelraum, indem übermäßig große stapelbasierte Datenstrukturen verwendet werden.

Unterteilen Sie sich nun in KD, und sehen Sie sich die allgemeine Systemspeicherauslastung mit dem Erweiterungsbefehl !vm an:

0:002> .breakin Break instruction exception - code 80000003 (first chance)ntoskrnl!_DbgBreakPointWithStatus+4:80148f9c cc int 3kd> !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) ..... 

Betrachten Sie zunächst die Verwendung des nicht ausgelagerten und ausgelagerten Pools. Beide sind gut in Grenzen, daher sind diese nicht die Ursache des Problems.

Sehen Sie sich als Nächstes die Anzahl der zugesicherten Seiten an: 183528 von 202280. Dies ist sehr nahe an der Grenze. Obwohl in dieser Anzeige diese Zahl nicht vollständig im Grenzwert angezeigt wird, sollten Sie bedenken, dass während Sie das Debuggen im Benutzermodus ausführen, andere Prozesse auf dem System ausgeführt werden. Jedes Mal, wenn ein NTSD-Befehl ausgeführt wird, werden diese anderen Prozesse ebenfalls zuordnen und Arbeitsspeicher freigeben. Dies bedeutet, dass Sie nicht genau wissen, wie der Speicherzustand zum Zeitpunkt des Stapelüberlaufs war. Angesichts der Nähe der zugesicherten Seitenzahl am Grenzwert ist es sinnvoll, zu schließen, dass die Seitendatei zu einem bestimmten Zeitpunkt verwendet wurde und dies zu einem Stapelüberlauf führte.

Dies ist kein ungewöhnliches Vorkommen, und die Zielanwendung kann dafür nicht wirklich fehlerhaft sein. Wenn dies häufig der Fall ist, sollten Sie erwägen, die anfängliche Stapelzusage für die fehlerhafte Anwendung zu erhöhen.

Analysieren eines einzelnen Funktionsaufrufs

Es kann auch hilfreich sein, genau herauszufinden, wie viel Stapelraum ein bestimmter Funktionsaufruf angibt.

Dazu zerlegen Sie die ersten Anweisungen, und suchen Sie nach der Anweisungsnummersub esp. Dadurch wird der Stapelzeiger verschoben, wobei Zahlenbytes für lokale Daten effektiv reserviert werden.

Beispiel: Verwenden Sie zuerst den Befehl "k", um den Stapel zu betrachten.

0:002> k ChildEBP RetAddr009fdd0c 71a32520 COMCTL32!_chkstk+0x25009fde78 77cf8290 COMCTL32!ListView_WndProc+0x4c4009fde98 77cfd634 USER32!_InternalCallWinProc+0x18009fdf00 77cd55e9 USER32!UserCallWinProcCheckWow+0x17f009fdf3c 77cd63b2 USER32!SendMessageWorker+0x4a3009fdf5c 71a45b30 USER32!SendMessageW+0x44009fdfec 71a45bb0 COMCTL32!CCSendNotify+0xc0e009fdffc 71a1d688 COMCTL32!CICustomDrawNotify+0x2a009fe074 71a1db30 COMCTL32!Header_Draw+0x63009fe0d0 71a1f196 COMCTL32!Header_OnPaint+0x3f009fe128 77cf8290 COMCTL32!Header_WndProc+0x4e2

Verwenden Sie dann den Befehl u, ub, uu (Unassemble), um den Assemblercode an dieser Adresse anzuzeigen.

0:002> u COMCTL32!Header_Draw COMCTL32!Header_Draw :71a1d625 55 push ebp71a1d626 8bec mov ebp,esp71a1d628 83ec58 sub esp,0x5871a1d62b 53 push ebx71a1d62c 8b5d08 mov ebx,[ebp+0x8]71a1d62f 56 push esi71a1d630 57 push edi71a1d631 33f6 xor esi,esi 

Dies zeigt, dass Header_Draw 0x58 Bytes von Stapelspeicher zugewiesen wurden.

Der Befehl r (Registers) enthält Informationen zum aktuellen Inhalt der Register, z. B. esp.

Debuggen des Stapelüberlaufs, wenn Symbole verfügbar sind

Symbole stellen Bezeichnungen für Elemente bereit, die im Arbeitsspeicher gespeichert sind, und wenn verfügbar, können sie das Untersuchen von Code vereinfachen. Eine Übersicht über Symbole finden Sie unter Verwenden von Symbolen. Informationen zum Festlegen des Symbolpfads finden Sie unter .sympath (Set Symbol Path).For information on setting the symbols path, see .sympath (Set Symbol Path).For information on setting the symbols path, see .sympath (Set Symbol Path).

Zum Erstellen eines Stapelüberlaufs können wir diesen Code verwenden, der weiterhin eine Unterroutine aufruft, bis der Stapel erschöpft ist.

// 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();}

Wenn der Code kompiliert und unter WinDbg ausgeführt wird, wird er für einige Male in einer Schleife ausgeführt und löst dann eine Stapelüberlaufausnahme aus.

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

Verwenden Sie den Befehl "!analyse ", um zu überprüfen, ob wir tatsächlich ein Problem mit unserer Schleife haben.

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

Mit dem Kb-Befehl sehen wir, dass es viele Instanzen unseres Schleifenprogramms gibt, die jeweils Arbeitsspeicher verwenden.

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] ...

Wenn Symbole verfügbar sind, können die dt-_TEB verwendet werden, um Informationen zum Threadblock anzuzeigen. Weitere Informationen zum Threadspeicher finden Sie unter Threadstapelgröße.

0:000> dt _TEBntdll!_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

Wir können auch den !teb-Befehl verwenden, der stackBase abd StackLimit anzeigt.

0:000> !tebTEB 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

Mit diesem Befehl können wir die Stapelgröße berechnen.

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

Zusammenfassung der Befehle

  • k (Anzeigestapel-Backtrace)
  • ~ (Threadstatus)
  • d, da, db, dc, dd, dD, df, dp, dq, du, dw (Display-Speicher)
  • u, ub, uu (Unassemble)
  • r (Register)
  • .sympath (Set-Symbolpfad)
  • x (Symbole überprüfen)
  • dt (Anzeigetyp)
  • !analyze
  • !Teb

Siehe auch

Erste Schritte mit WinDbg (Benutzermodus)

/F (Set Stack Size) - C++-Compileroption

Debuggen eines Stapelüberlaufs - Windows drivers (2025)
Top Articles
Latest Posts
Recommended Articles
Article information

Author: Allyn Kozey

Last Updated:

Views: 6388

Rating: 4.2 / 5 (43 voted)

Reviews: 90% of readers found this page helpful

Author information

Name: Allyn Kozey

Birthday: 1993-12-21

Address: Suite 454 40343 Larson Union, Port Melia, TX 16164

Phone: +2456904400762

Job: Investor Administrator

Hobby: Sketching, Puzzles, Pet, Mountaineering, Skydiving, Dowsing, Sports

Introduction: My name is Allyn Kozey, I am a outstanding, colorful, adventurous, encouraging, zealous, tender, helpful person who loves writing and wants to share my knowledge and understanding with you.