Men-debug Stack Overflow
Luapan tumpukan adalah kesalahan yang dapat ditemui utas mode pengguna. Ada tiga kemungkinan penyebab kesalahan ini:
Utas menggunakan seluruh tumpukan yang dicadangkan untuk itu. Ini sering disebabkan oleh rekursi tak terbatas.
Utas tidak dapat memperluas tumpukan karena file halaman dimaksimalkan, dan oleh karena itu tidak ada halaman tambahan yang dapat diterapkan untuk memperluas tumpukan.
Utas tidak dapat memperluas tumpukan karena sistem berada dalam periode singkat yang digunakan untuk memperluas file halaman.
Saat fungsi yang berjalan pada utas mengalokasikan variabel lokal, variabel diletakkan pada tumpukan panggilan utas. Jumlah ruang tumpukan yang diperlukan oleh fungsi bisa sebesar jumlah ukuran semua variabel lokal. Namun, pengkompilasi biasanya melakukan pengoptimalan yang mengurangi ruang tumpukan yang diperlukan oleh fungsi. Misalnya, jika dua variabel berada dalam cakupan yang berbeda, pengkompilasi dapat menggunakan memori tumpukan yang sama untuk kedua variabel tersebut. Pengkompilasi mungkin juga dapat menghilangkan beberapa variabel lokal sepenuhnya dengan mengoptimalkan perhitungan.
Jumlah pengoptimalan dipengaruhi oleh pengaturan kompilator yang diterapkan pada waktu build. Misalnya, dengan Opsi Kompilator /F (Atur Ukuran Tumpukan) - C++.
Topik ini mengasumsikan pengetahuan umum tentang konsep, seperti utas, blok utas, tumpukan dan timbunan. Untuk informasi tambahan tentang konsep dasar ini, lihat Microsoft Windows Internals oleh Mark Russinovich dan David Solomon.
Men-debug luapan tumpukan tanpa simbol
Berikut adalah contoh cara men-debug luapan tumpukan. Dalam contoh ini, NTSD berjalan di komputer yang sama dengan aplikasi target dan mengalihkan outputnya ke KD pada komputer host. Lihat Mengontrol Debugger Mode Pengguna dari Debugger Kernel untuk detailnya.
Langkah pertama adalah melihat peristiwa apa yang menyebabkan debugger rusak:
0:002> .lastevent
Last event: Exception C00000FD, second chance
Anda dapat mencari kode pengecualian 0xC00000FD di ntstatus.h, Kode pengecualian ini STATUS_STACK_OVERFLOW, yang menunjukkan halaman penjaga baru untuk tumpukan tidak dapat dibuat. Semua kode status tercantum dalam Nilai NTSTATUS 2.3.1.
Anda juga dapat menggunakan perintah !error untuk mencari kesalahan di Windows Debugger.
0:002> !error 0xC00000FD
Error code: (NTSTATUS) 0xc00000fd (3221225725) - A new guard page for the stack cannot be created.
Untuk memeriksa kembali apakah tumpukan meluap, Anda dapat menggunakan perintah 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
Utas target telah dipecah menjadi COMCTL32!_chkstk, yang menunjukkan masalah tumpukan. Sekarang Anda harus menyelidiki penggunaan tumpukan proses target. Proses ini memiliki beberapa utas, tetapi yang penting adalah yang menyebabkan luapan, jadi identifikasi utas ini terlebih dahulu menggunakan perintah ~ (Status Utas):
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
Sekarang Anda perlu menyelidiki utas 2. Periode di sebelah kiri baris ini menunjukkan bahwa ini adalah utas saat ini.
Informasi tumpukan terkandung dalam TEB (Blok Lingkungan Utas) pada 0x7FFDC000. Cara term mudah untuk mencantumkannya adalah menggunakan !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```
Namun, ini mengharuskan Anda untuk memiliki simbol yang tepat. Situasi yang lebih sulit adalah ketika Anda tidak memiliki simbol dan perlu menggunakan perintah dd (Memori Tampilan) untuk menampilkan nilai mentah di lokasi tersebut:
0:002> dd 7ffdc000 L4
7ffdc000 009fdef0 00a00000 009fc000 00000000
Untuk menginterpretasikan ini, Anda perlu mencari definisi struktur data TEB. Gunakan perintah Tipe Tampilan dt untuk melakukan ini pada sistem tempat simbol tersedia.
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
...
Struktur Data Utas
Untuk mempelajari selengkapnya tentang utas, Anda juga dapat menampilkan informasi tentang etos struktur terkait blok kontrol utas dan kthread. (Perhatikan bahwa contoh 64 bit ditampilkan di sini.)
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
Lihat Microsoft Windows Internals untuk informasi selengkapnya tentang struktur data utas.
Melihat versi 32 bit dari struktur _TEB, ini menunjukkan bahwa DWORD kedua dan ketiga dalam struktur TEB menunjuk ke bagian bawah dan atas tumpukan. Dalam contoh ini, alamat ini 0x00A00000 dan 0x009FC000. (Tumpukan tumbuh ke bawah dalam memori.) Anda dapat menghitung ukuran tumpukan menggunakan ? (Evaluasi Ekspresi) perintah:
0:002> ? a00000-9fc000
Evaluate expression: 16384 = 00004000
Ini menunjukkan bahwa ukuran tumpukan adalah 16 K. Ukuran tumpukan maksimum disimpan di bidang DeallocationStack, yang merupakan bagian dari struktur TEB ini. Bidang DeallocationStack
menunjukkan dasar tumpukan. Setelah beberapa perhitungan, Anda dapat menentukan bahwa offset bidang ini 0xE0C.
0:002> dd 7ffdc000+e0c L1
7ffdce0c 009c0000
0:002> ? a00000-9c0000
Evaluate expression: 262144 = 00040000
Ini menunjukkan bahwa ukuran tumpukan maksimum adalah 256 K, yang berarti lebih dari ruang tumpukan yang memadai dibiarkan.
Selain itu, proses ini terlihat bersih -- tidak dalam rekursi tak terbatas atau melebihi ruang tumpukannya dengan menggunakan struktur data berbasis tumpukan yang terlalu besar.
Sekarang masuk ke KD dan lihat keseluruhan penggunaan memori sistem dengan perintah ekstensi !vm :
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)
.....
Pertama, lihat penggunaan kumpulan yang tidak ditumpuk dan di-halaman. Keduanya berada dalam batas, jadi ini bukan penyebab masalahnya.
Selanjutnya, lihat jumlah halaman yang diterapkan: 183528 dari 202280. Ini sangat dekat dengan batas. Meskipun tampilan ini tidak menunjukkan angka ini sepenuhnya pada batas, Anda harus ingat bahwa saat Anda melakukan penelusuran kesalahan mode pengguna, proses lain berjalan pada sistem. Setiap kali perintah NTSD dijalankan, proses lain ini juga mengalokasikan dan membebaskan memori. Itu berarti Anda tidak tahu persis seperti apa status memori pada saat luapan tumpukan terjadi. Mengingat seberapa dekat nomor halaman yang diterapkan dengan batas, masuk akal untuk menyimpulkan bahwa file halaman digunakan pada beberapa titik dan ini menyebabkan luapan tumpukan.
Ini bukan kejadian yang jarang terjadi, dan aplikasi target tidak dapat benar-benar rusak untuk ini. Jika sering terjadi, Anda mungkin ingin mempertimbangkan untuk meningkatkan komitmen tumpukan awal untuk aplikasi yang gagal.
Menganalisis Panggilan Fungsi Tunggal
Ini juga dapat berguna untuk mengetahui dengan tepat berapa banyak ruang tumpukan yang dialokasikan oleh panggilan fungsi tertentu.
Untuk melakukan ini, bongkar beberapa instruksi pertama dan cari nomor instruksisub esp
. Ini memindahkan penunjuk tumpukan, secara efektif mempertahankan byte angka untuk data lokal.
Berikut adalah contohnya. Pertama gunakan perintah k untuk melihat tumpukan.
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
Kemudian gunakan perintah u, ub, uu (Unassemble) untuk melihat kode perakit di alamat tersebut.
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
Ini menunjukkan bahwa Header_Draw dialokasikan 0x58 byte ruang tumpukan.
Perintah r (Registers) menyediakan informasi tentang konten register saat ini, seperti esp.
Debugging stack overflow saat simbol tersedia
Simbol menyediakan label ke item yang disimpan dalam memori, dan jika tersedia, dapat mempermudah pemeriksaan kode. Untuk gambaran umum simbol, lihat Menggunakan Simbol. Untuk informasi tentang mengatur jalur simbol, lihat .sympath (Atur Jalur Simbol).
Untuk membuat luapan tumpukan, kita dapat menggunakan kode ini, yang terus memanggil subroutine hingga tumpukan habis.
// 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();
}
Ketika kode dikompilasi dan dijalankan di bawah WinDbg, kode akan mengulang beberapa kali dan kemudian melemparkan pengecualian luapan tumpukan.
(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)
Gunakan perintah !analyze untuk memeriksa bahwa kita memang memiliki masalah dengan perulangan kita.
...
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:
Menggunakan perintah kb kita melihat bahwa ada banyak instans program perulangan kita masing-masing menggunakan memori.
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]
...
Jika simbol tersedia , _TEB dt dapat digunakan untuk menampilkan informasi tentang blok utas. Untuk informasi selengkapnya tentang memori utas, lihat Ukuran Tumpukan Utas.
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
Kita juga dapat menggunakan perintah !teb yang menampilkan 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
Kita dapat menghitung ukuran tumpukan, menggunakan perintah ini.
0:000> ?? int(@$teb->NtTib.StackBase) - int(@$teb->NtTib.StackLimit)
int 0n1044480
Ringkasan perintah
- k (Tampilkan Stack Backtrace)
- ~ (Status Utas)
- d, da, db, dc, dd, dD, df, dp, dq, du, dw (Display Memory)
- u, ub, uu (Tidak Dapat Dirakit)
- r (Daftar)
- .sympath (Atur Jalur Simbol)
- x (Periksa Simbol)
- dt (Jenis Tampilan)
- !Menganalisis
- !Teb