I started using WinDbg to debug VB6 runtime errors but can't really get it to work, all I need is to know the source of the error (the procedure where the error comes from).
I created a small app to test and made it to throw an overflow runtime error like this:
Private Sub Command1_Click()
Dim a As Byte
a = 1000
End Sub
I compiled it with "Create Symbolic debug Info" option checked to create the PDB file.
Then I attached the application to WinDbg and clicked the button to throw the error, but when I check the call stack I don't find any trace of the Command1_Click procedure. All I get is the following :
0:001> ~* k
0 Id: 56c.173c Suspend: 1 Teb: 7ffde000 Unfrozen
ChildEBP RetAddr
0012ea08 7e419418 ntdll!KiFastSystemCallRet
0012ea40 7e4249c4 USER32!NtUserWaitMessage+0xc
0012ea68 7e43a956 USER32!InternalDialogBox+0xd0
0012ed28 7e43a2bc USER32!SoftModalMessageBox+0x938
0012ee78 7e43a10b USER32!MessageBoxWorker+0x2ba
0012eee4 729af829 USER32!MessageBoxIndirectA+0xb8
WARNING: Stack unwind information not available. Following frames may be wrong.
0012ef24 729af6a5 MSVBVM60!IID_IVbaHost+0x411e9
0012ef4c 729af9a0 MSVBVM60!IID_IVbaHost+0x41065
0012ef7c 729a3d68 MSVBVM60!IID_IVbaHost+0x41360
0012efe0 729a3db6 MSVBVM60!IID_IVbaHost+0x35728
0012f000 72a0c411 MSVBVM60!IID_IVbaHost+0x35776
0012f01c 72a0c6f3 MSVBVM60!_vbaOnGoCheck+0xba
0012f05c 7c9032a8 MSVBVM60!EbGetErrorInfo+0x115
0012f080 7c90327a ntdll!ExecuteHandler2+0x26
0012f130 7c90e46a ntdll!ExecuteHandler+0x24
0012f130 7c812aeb ntdll!KiUserExceptionDispatcher+0xe
0012f484 72a10dcf kernel32!RaiseException+0x53
0012f4a4 72a0e228 MSVBVM60!EbGetHandleOfExecutingProject+0x22b3
0012f4b8 72a0e28c MSVBVM60!rtcDoEvents+0x131
0012f4c8 72a219ee MSVBVM60!rtcDoEvents+0x195
0012f644 72992667 MSVBVM60!_vbaUI1I2+0x12
0012f668 729f4657 MSVBVM60!IID_IVbaHost+0x24027
0012f698 7299ce49 MSVBVM60!DllCanUnloadNow+0x149a5
0012f6c0 7299f97d MSVBVM60!IID_IVbaHost+0x2e809
0012f71c 7299e22c MSVBVM60!IID_IVbaHost+0x3133d
0012f740 7299dc6d MSVBVM60!IID_IVbaHost+0x2fbec
0012f7ac 729c223a MSVBVM60!IID_IVbaHost+0x2f62d
0012f92c 7299ce49 MSVBVM60!IID_IVbaHost+0x53bfa
0012f954 7299f97d MSVBVM60!IID_IVbaHost+0x2e809
0012f9b0 7e418734 MSVBVM60!IID_IVbaHost+0x3133d
0012f9dc 7e418816 USER32!InternalCallWinProc+0x28
0012fa44 7e42927b USER32!UserCallWinProcCheckWow+0x150
0012fa80 7e4292e3 USER32!SendMessageWorker+0x4a5
0012faa0 7e44ff7d USER32!SendMessageW+0x7f
0012fab8 7e4465d2 USER32!xxxButtonNotifyParent+0x41
0012fad4 7e425e94 USER32!xxxBNReleaseCapture+0xf8
0012fb58 7e43b082 USER32!ButtonWndProcWorker+0x6df
0012fb78 7e418734 USER32!ButtonWndProcA+0x5d
0012fba4 7e418816 USER32!InternalCallWinProc+0x28
0012fc0c 7e42a013 USER32!UserCallWinProcCheckWow+0x150
0012fc3c 7e42a998 USER32!CallWindowProcAorW+0x98
0012fc5c 7299d082 USER32!CallWindowProcA+0x1b
0012fcc8 729f492d MSVBVM60!IID_IVbaHost+0x2ea42
0012fcf0 7299ce49 MSVBVM60!DllCanUnloadNow+0x14c7b
0012fd18 7299f97d MSVBVM60!IID_IVbaHost+0x2e809
0012fd74 7e418734 MSVBVM60!IID_IVbaHost+0x3133d
0012fda0 7e418816 USER32!InternalCallWinProc+0x28
0012fe08 7e4189cd USER32!UserCallWinProcCheckWow+0x150
0012fe68 7e4196c7 USER32!DispatchMessageWorker+0x306
0012fe78 7294a6c8 USER32!DispatchMessageA+0xf
0012feb8 7294a63f MSVBVM60!_vbaStrToAnsi+0x2f1
0012fefc 7294a51d MSVBVM60!_vbaStrToAnsi+0x268
0012ff18 7294a4e8 MSVBVM60!_vbaStrToAnsi+0x146
0012ff3c 72943644 MSVBVM60!_vbaStrToAnsi+0x111
0012ffb8 00401246 MSVBVM60!ThunRTMain+0xa0
0012fff0 00000000 Project1!__vbaS+0xa
# 1 Id: 56c.10a8 Suspend: 1 Teb: 7ffdd000 Unfrozen
ChildEBP RetAddr
00f0ffc8 7c950010 ntdll!DbgBreakPoint
00f0fff4 00000000 ntdll!DbgUiRemoteBreakin+0x2d
Note that WinDbg doesn't break on the error, so I manually break into it after I get the error message and before clicking OK, because if I clicked OK, the application closes.
I'm new to WinDbg but I read that it should break on error but it doesn't, all I get is "First chance Exception" and I don't get a "Second chance Exception" when I click OK and let it crash.
I think that somehow WinDbg considers that VB6 handled the error even though it didn't handle it. Is there something I'm missing here? How can I get the trace of the error to the calling procedure?
Running the application in VB6 itself results in a VB6 runtime error 6, which stands for "overflow".
First chance exception
Running the application under WinDbg (6.2.9200), I first see a
(6cc.6d4): Unknown exception - code c000008f (first chance)
which is probably the most common VB6 exception. By default, WinDbg does not break on first chance exceptions of this type. If you want it to do so, you need to enable that explicitly by
sxe c000008f
Second chance exception
The first chance exception is followed by a message box
This message box is the first indicator that the exception must have been caught, otherwise there would have been a second chance exception immediately afterwards.
And in fact, VB6 creates an exception handler:
0:000> u
Project1!Form1::Command_Click [Form1 # 5]:
004019a0 55 push ebp
004019a1 8bec mov ebp,esp
004019a3 83ec0c sub esp,0Ch
004019a6 68b6104000 push offset Project1!__vbaExceptHandler (004010b6)
004019ab 64a100000000 mov eax,dword ptr fs:[00000000h]
...
004019df b9e8030000 mov ecx,3E8h
004019e4 ff1530104000 call dword ptr [Project1!_imp___vbaUI1I2 (00401030)]
...
At 004019a6 you see that VB6 is working with __vbaExceptHandler. mov dword ptr fs:[0],esp is the beginning of a try/catch block. At 004019df it stores 0x3E8 (hex) or 1000 (dec) to ECX. That's where your code is.
After confirming the message, the process terminates, but not due to a second chance exception but because of an ExitProcess() call. That's the way VB6 "handles" exceptions.
0:000> k
ChildEBP RetAddr
WARNING: Stack unwind information not available. Following frames may be wrong.
0012ff28 7c81bfb6 ntdll!KiFastSystemCallRet
0012ff3c 73393657 kernel32!ExitProcess+0x14
0012ffb8 0040113a MSVBVM60!ThunRTMain+0xb3
0012fff0 00000000 image00400000+0x113a
Symbols
To see if the symbols have been loaded correctly, type lm (list modules). It should look like this:
0:000> lm
start end module name
00400000 00404000 Project1 (private pdb symbols) C:\Programme\Microsoft Visual Studio\VB98\Project1.pdb
If you see (deferred), the symbols have not been loaded yet. Type ld Project1 to load them.
With symbols, you can set breakpoints by first finding the method with x and then setting a breakpoint bp:
0:000> x Project1!Form1*
004019a0 Project1!Form1::Command_Click (void)
0:000> bp Project1!Form1::Command_Click
0:000> bl
0 e 004019a0 0001 (0001) 0:**** Project1!Form1::Command_Click
[...]
Breakpoint 0 hit
eax=004016b4 ebx=00000001 ecx=00000000 edx=733a3dd8 esi=0012f5e4 edi=0012f514
eip=004019a0 esp=0012f50c ebp=0012f514 iopl=0 nv up ei pl nz ac pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000216
Project1!Form1::Command_Click:
004019a0 55 push ebp
0:000> k
ChildEBP RetAddr
0012f508 733e1ce3 Project1!Form1::Command_Click [Form1 # 5]
WARNING: Stack unwind information not available. Following frames may be wrong.
0012f524 733e1fe4 MSVBVM60!IID_IVbaHost+0x23703
...
But a breakpoint is not what you wanted...
Why the method is not on the call stack
Unfortunately enabling first chance exceptions and loading symbols does still not help seeing the method on the call stack at the time the exception is thrown. It's somewhere in DoEvents():
0:000> .exr -1
ExceptionAddress: 7c812fd3 (kernel32!RaiseException+0x00000052)
ExceptionCode: c000008f
ExceptionFlags: 00000001
NumberParameters: 2
Parameter[0]: deadcafe
Parameter[1]: deadcafe
0:000> k
ChildEBP RetAddr
WARNING: Stack unwind information not available. Following frames may be wrong.
0012f484 73460c29 kernel32!RaiseException+0x52
0012f4a4 7345e082 MSVBVM60!EbGetHandleOfExecutingProject+0x22b3
0012f4b8 7345e0e6 MSVBVM60!rtcDoEvents+0x131
...
Have you noticed the warning about the stack unwind information? Let's look at the stack manually:
0:000> dps #ebp
0012f484 0012f4a4
0012f488 73460c29 MSVBVM60!EbGetHandleOfExecutingProject+0x22b3
0012f48c c000008f
0012f490 00000001
0012f494 00000002
0012f498 0012f49c
0012f49c deadcafe
0012f4a0 deadcafe
...
0012f4d8 004019e7 Project1!Form1::Command_Click+0x53 [Form1 # 7]
0012f4dc 0012f514
0012f4e0 0012f5e4
0012f4e4 00000001
0012f4e8 00000000
0012f4ec 00000000
0012f4f0 00000000
0012f4f4 0012fa34
0012f4f8 004010b6 Project1!__vbaExceptHandler
I guess you can't do anything about it. VB6 is not very good in creating symbols and also not in maintaining a nice call stack.
Related
This is a code from kernel_entry macro (in liniux-5.10.0 arch/arm64/entry.S ),
.macro kernel_entry, el, regsize = 64
...(reduced)
.if \el == 0
clear_gp_regs
mrs x21, sp_el0
ldr_this_cpu tsk, __entry_task, x20
msr sp_el0, tsk
/*
* Ensure MDSCR_EL1.SS is clear, since we can unmask debug exceptions
* when scheduling.
*/
ldr x19, [tsk, #TSK_TI_FLAGS]
disable_step_tsk x19, x20 <== Question
When the exception occured in EL0, the EL0's stack pointer is stored in x21 and the original kernel stack pointer is loaded from memory (per-cpu) to tsk(this is defined to be x28). In the line marked Q1, tsk contains the flag value which is the first element of the thread_info which is also the first element of task_struct. (task_struct sits at the bottom of the process stack area).
My question is : why does it clear the softstep(MDSCR_EL1.SS) for the process? I can't understand the comment in the code below.
/* Ensure MDSCR_EL1.SS is clear, since we can unmask debug exceptions
when scheduling. */
Is it to prevent debug exception during scheduling, so that the debugger works only for the user process? If someone could cleary explain it to me, I would be very thankful.
I have a virtual address (instruction pointer) of the function, obtained from backtrace call. I need to figure out the debug information about it.
For example, I need the information about attach_backtraces function.
nm -a Backtrace.so | grep attach_backtraces
000000000002cdfe t _ZN2xsL17attach_backtracesENS_3RefE
The offset 0x2cdfe can be determined by substracting from PC (IP) the .so loaded address. And it matches the output from nm.
The following infrormation I get from readelf -w Backtrace.so
<3><3a14f>: Abbrev Number: 0
<2><3a150>: Abbrev Number: 161 (DW_TAG_subprogram)
<3a152> DW_AT_name : (indirect string, offset: 0x21bf5): attach_backtraces
<3a156> DW_AT_decl_file : 22
<3a157> DW_AT_decl_line : 201
<3a158> DW_AT_decl_column : 13
<3a159> DW_AT_declaration : 1
<3a159> DW_AT_sibling : <0x3a163>
<3><3a15d>: Abbrev Number: 1 (DW_TAG_formal_parameter)
<3a15e> DW_AT_type : <0x36aac>
<3><3a162>: Abbrev Number: 0
Why it's offset is 0x21bf5 and not the expected 0x2cdfe? What a piece do I miss? My next step would be query DWARF-info for the function at the offset 0x2cdfe to get debug info.
PS. I'm gathering full backtrace, where symbol name, file and line should be presented. What C/C++ library are better to use to parse/get information from DWARF ?
Addon:
No, there are no other attach_backtraces in the readelf -w output. I have found that
DW_AT_sibling : <0x3a163>
and it's definition:
No, there are no other attach_backtraces in the readelf -w output. I have found that
DW_AT_sibling : <0x3a163>
and it's definition:
<1><3f9f5>: Abbrev Number: 27 (DW_TAG_subprogram)
<3f9f6> DW_AT_specification: <0x3a163>
<3f9fa> DW_AT_low_pc : 0x2c59e
<3fa02> DW_AT_high_pc : 0x860
<3fa0a> DW_AT_frame_base : 1 byte block: 9c (DW_OP_call_frame_cfa)
<3fa0c> DW_AT_GNU_all_tail_call_sites: 1
<3fa0c> DW_AT_sibling : <0x3fb21>
0x2c59e (DW_AT_low_pc) - 0x860 (DW_AT_high_pc) = 0x2cdfe (the target function address).
Is this calculation correct?
Why it's offset is 0x21bf5 and not the expected 0x2cdfe?
The offset 0x21bf5 is the offset of the name of the symbol ("attach_backtraces" here) in the .debug_str section (where the names off all types, parameters, variables and functions are collected).
That offset has absolutely no relation to the value of the symbol being represented (0x2cdfe here). These offsets just happened to be close to each other to confuse you.
What a piece do I miss?
Normally, a function should have DW_AT_low_pc attribute which represents its starting address (and the value of that attribute for attach_backtraces routine described by your output would be 0x2cdfe).
I am not sure why you are missing low_pc and high_pc here.
One possibility is that there are actually many instances of xs::attach_backtraces(xs::Ref) routine (if it's declared as inline in a header file), and the instance that you are looking at in readelf -w output was discarded by the linker (the function will appear in all object files which #included that header, but the linker will only keep a single instance of the function). If this is the case, look for another attach_backtraces in readelf -w output, with low_pc and high_pc present.
In first block of DWARF dump <3a150> (where we can see offset to function name, 0x21bf5) we also see DW_AT_declaration flag which indicates that declaration of a function was not completed in this DIE (see section 2.13 of DWARF 5 documentation).
To find the completion of declaration you should find DIE with DW_AT_specification attribute, which value is a reference to DIE which it completes (as in your 2nd block, <3f9f5>) and should have in your case value <3a150>.
Taking care on mentioned above I suppose that your 2nd block is not what you want to find since it references to another DIE <0x3a163>.
When you'll find right block, then you should use DW_AT_low_pc as a parameter you need (offset to 'attach_backtraces' from process base address).
Hope this helps.
Also, from my point of view dwarfdump tool shows a better output than readelf.
I'm trying to understand the following symbol dump from the LLDB shell
(lldb) target create --no-dependents '9.0/Symbols/Library/Application Support/WatchKit/WK'
Current executable set to '9.0/Symbols/Library/Application Support/WatchKit/WK' (armv7k).
(lldb) image list
[ 0] 675ED1EB-BAA0-3453-B7B1-3E69310F40FD 0x00004000 9.0/Symbols/Library/Application Support/WatchKit/WK
(lldb) image dump symtab
Dumping symbol table for 1 modules.
Symtab, file = 9.0/Symbols/Library/Application Support/WatchKit/WK, num_symbols = 6:
Debug symbol
|Synthetic symbol
||Externally Visible
|||
Index UserID DSX Type File Address/Value Load Address Size Flags Name
------- ------ --- --------------- ------------------ ------------------ ------------------ ---------- ----------------------------------
[ 0] 0 Code 0x0000000000007fcc 0x0000000000000030 0x001e0000 stub helpers
[ 1] 1 X Data 0x0000000000004000 0x0000000000003fcc 0x000f0010 _mh_execute_header
[ 2] 2 X ReExported 0x000b0000 main -> /System/Library/PrivateFrameworks/SockPuppetGizmo.framework/SockPuppetGizmo`_SPApplicationMain
[ 3] 3 X Undefined 0x0000000000000000 0x0000000000000000 0x00010100 _SPApplicationMain
[ 4] 4 X Undefined 0x0000000000000000 0x0000000000000000 0x00010500 dyld_stub_binder
[ 5] 5 S Trampoline 0x0000000000007ffc 0x0000000000000004 0x00000000 main
Most of it I can kinda understand because there are addresses and sizes associated with the symbol but some of them I don't understand. In this case there are 2 "undefined" symbols with 0x00 for the address and 0x00 for the size. My question is what do those symbols mean? Does that mean they are resolved at runtime and I really shouldn't be concerned about them when trying to make sense of things in crash logs?
Your guess is correct, Undefined symbols are symbols that one binary wants to use from some other binary. They will get fixed up by the loader when your program runs.
So for instance, if you write the standard "hello world" program, the main binary will have an Undefined symbol for "printf". BTW, these are the same as the symbols of type U that you see in the output of nm.
I read in Windows Internals that when a thread is created, by default 1 MB of virtual memory is reserved for the user stack. Out of this 1 MB, only the first page (0x1000) will be committed.
I can see this when i dump the image header using dumpbin.exe. Here is what dumpbin shows:
However when i dump the address space of this exe in Windbg using !address command, I see a difference. Windbg shows me that the initial committed size is equal to 3 pages i.e 0x3000
Does anyone know why there is a difference between the initial stack commit size that the image header and debugger shows?
That's a nice question and the key to the answer is understanding what the initial breakpoint is. How initial is it, for starters?
TLDR: The initial breakpoint it too late. That stack has already grown.
You haven't shared the binary you're dealing with, so I chose a binary that exhibits the same behaviour - cacls.exe on 64-bit Windows 10 (file version: 10.0.14393.0).
During the initial breakpoint we observe:
CommandLine: "c:\Windows\System32\cacls.exe"
Symbol search path is: srv*
Executable search path is:
ModLoad: 00007ff6`83bd0000 00007ff6`83bdc000 cacls.exe
ModLoad: 00007ff8`29ce0000 00007ff8`29eb1000 ntdll.dll
ModLoad: 00007ff8`27500000 00007ff8`275ab000 C:\Windows\System32\KERNEL32.DLL
ModLoad: 00007ff8`26f30000 00007ff8`2714d000 C:\Windows\System32\KERNELBASE.dll
ModLoad: 00007ff8`280b0000 00007ff8`2814e000 C:\Windows\System32\msvcrt.dll
ModLoad: 00007ff8`29b10000 00007ff8`29bb2000 C:\Windows\System32\advapi32.dll
ModLoad: 00007ff8`273d0000 00007ff8`27429000 C:\Windows\System32\sechost.dll
ModLoad: 00007ff8`254f0000 00007ff8`25522000 c:\Windows\System32\NTMARTA.dll
ModLoad: 00007ff8`27150000 00007ff8`27245000 C:\Windows\System32\ucrtbase.dll
ModLoad: 00007ff8`277c0000 00007ff8`278e1000 C:\Windows\System32\RPCRT4.dll
(1310.17b0): Break instruction exception - code 80000003 (first chance)
ntdll!LdrpDoDebuggerBreak+0x30:
00007ff8`29db34e0 cc int 3
0:000> !dh -f cacls
File Type: EXECUTABLE IMAGE
FILE HEADER VALUES
8664 machine (X64)
6 number of sections
57899A04 time date stamp Sat Jul 16 05:20:52 2016
0 file pointer to symbol table
0 number of symbols
F0 size of optional header
22 characteristics
Executable
App can handle >2gb addresses
OPTIONAL HEADER VALUES
20B magic #
14.00 linker version
4C00 size of code
3600 size of initialized data
0 size of uninitialized data
52F0 address of entry point
1000 base of code
----- new -----
00007ff683bd0000 image base
1000 section alignment
200 file alignment
3 subsystem (Windows CUI)
10.00 operating system version
10.00 image version
10.00 subsystem version
C000 size of image
400 size of headers
AF10 checksum
0000000000080000 size of stack reserve
0000000000002000 size of stack commit
0000000000100000 size of heap reserve
0000000000001000 size of heap commit
C160 DLL characteristics
High entropy VA supported
Dynamic base
NX compatible
Guard
Terminal server aware
0 [ 0] address [size] of Export Directory
7010 [ 1CC] address [size] of Import Directory
A000 [ 7F0] address [size] of Resource Directory
9000 [ 2DC] address [size] of Exception Directory
0 [ 0] address [size] of Security Directory
B000 [ 54] address [size] of Base Relocation Directory
6A10 [ 38] address [size] of Debug Directory
0 [ 0] address [size] of Description Directory
0 [ 0] address [size] of Special Directory
0 [ 0] address [size] of Thread Storage Directory
60E0 [ D0] address [size] of Load Configuration Directory
0 [ 0] address [size] of Bound Import Directory
61B0 [ 3B8] address [size] of Import Address Table Directory
0 [ 0] address [size] of Delay Import Directory
0 [ 0] address [size] of COR20 Header Directory
0 [ 0] address [size] of Reserved Directory
0:000> !address #rsp
Mapping file section regions...
Mapping module regions...
Mapping PEB regions...
Mapping TEB and stack regions...
Mapping heap regions...
Mapping page heap regions...
Mapping other regions...
Mapping stack trace database regions...
Mapping activation context regions...
Usage: Stack
Base Address: 00000049`8fbbc000
End Address: 00000049`8fbc0000
Region Size: 00000000`00004000 ( 16.000 kB)
State: 00001000 MEM_COMMIT
Protect: 00000004 PAGE_READWRITE
Type: 00020000 MEM_PRIVATE
Allocation Base: 00000049`8fb40000
Allocation Protect: 00000004 PAGE_READWRITE
More info: ~0k
Content source: 1 (target), length: 180
We see the initial stack commit size is 0x2000, but 0x4000 is actually committed.
But that's already very late during the process initialization process (no pun intended). All the import DLL are already loaded, for example.
The so-called "initial break-point" is simply a (more or less1) hardcoded int 3 instruction called by the process initializtion code in NTDLL. If you look at the stack at this point you'll see the aptly named LdrpDoDebuggerBreak function which is called by LdrpInitializeProcess:
0:000> k
# Child-SP RetAddr Call Site
00 00000049`8fbbee80 00007ff8`29d72e22 ntdll!LdrpDoDebuggerBreak+0x30
01 00000049`8fbbeec0 00007ff8`29da8986 ntdll!LdrpInitializeProcess+0x1962
02 00000049`8fbbf2c0 00007ff8`29d59fae ntdll!_LdrpInitialize+0x4e982
03 00000049`8fbbf340 00000000`00000000 ntdll!LdrInitializeThunk+0xe
By the time that happened the stack has already been used (for example, to load statically linked DLLs and perform their initialization code), so it shouldn't be much surprise that the stack has grown already.
To examine the process when it has just been created we need to break on the process creation event rather than on the initial breakpoint (which isn't that initial as we now understand).
We can do that either using sxe cpr and .restarting like I did or running WinDbg/NTSD with -xe cpr. Doing that reveals something interesting2:
0:000> .restart
CommandLine: C:\Windows\System32\cacls.exe
************* Symbol Path validation summary **************
Response Time (ms) Location
Deferred srv*
Symbol search path is: srv*
Executable search path is:
ModLoad: 00007ff6`83bd0000 00007ff6`83bdc000 cacls.exe
00007ff8`29d470b0 4883ec48 sub rsp,48h
0:000> .imgscan /l
MZ at 00007ff6`83bd0000, prot 00000002, type 01000000 - size c000
Name: cacls.exe
Loaded cacls.exe module
MZ at 00007ff8`29ce0000, prot 00000002, type 01000000 - size 1d1000
Name: ntdll.dll
Loaded ntdll.dll module
0:000> !address #rsp
Mapping file section regions...
Mapping module regions...
Mapping PEB regions...
Mapping TEB and stack regions...
Mapping heap regions...
Mapping page heap regions...
Mapping other regions...
Mapping stack trace database regions...
Mapping activation context regions...
Usage: Stack
Base Address: 0000004a`5428e000
End Address: 0000004a`54290000
Region Size: 00000000`00002000 ( 8.000 kB)
State: 00001000 MEM_COMMIT
Protect: 00000004 PAGE_READWRITE
Type: 00020000 MEM_PRIVATE
Allocation Base: 0000004a`54210000
Allocation Protect: 00000004 PAGE_READWRITE
More info: ~0k
Content source: 1 (target), length: 478
The committed region size is 0x2000 - like the header says!
If we let it continue we'll eventually get to the initial breakpoint with more stack comitted.
1I said more or less hardcoded because the actual code of the function is
0:000> uf ntdll!LdrpDoDebuggerBreak
ntdll!LdrpDoDebuggerBreak:
00007ff8`29db34b0 4883ec38 sub rsp,38h
00007ff8`29db34b4 488364242000 and qword ptr [rsp+20h],0
00007ff8`29db34ba 41b901000000 mov r9d,1
00007ff8`29db34c0 4c8d442440 lea r8,[rsp+40h]
00007ff8`29db34c5 418d5110 lea edx,[r9+10h]
00007ff8`29db34c9 48c7c1feffffff mov rcx,0FFFFFFFFFFFFFFFEh
00007ff8`29db34d0 e88b30fdff call ntdll!NtQueryInformationThread (00007ff8`29d86560)
00007ff8`29db34d5 85c0 test eax,eax
00007ff8`29db34d7 780a js ntdll!LdrpDoDebuggerBreak+0x33 (00007ff8`29db34e3) Branch
ntdll!LdrpDoDebuggerBreak+0x29:
00007ff8`29db34d9 807c244000 cmp byte ptr [rsp+40h],0
00007ff8`29db34de 7503 jne ntdll!LdrpDoDebuggerBreak+0x33 (00007ff8`29db34e3) Branch
ntdll!LdrpDoDebuggerBreak+0x30:
00007ff8`29db34e0 cc int 3
00007ff8`29db34e1 eb00 jmp ntdll!LdrpDoDebuggerBreak+0x33 (00007ff8`29db34e3) Branch
ntdll!LdrpDoDebuggerBreak+0x33:
00007ff8`29db34e3 4883c438 add rsp,38h
00007ff8`29db34e7 c3 ret
It does stuff like checking whether or not this thread is to be "hidden from the debugger", but basically it just breaks into the debugger.
2The .imgscan /l is needed because without it we get:
0:000> !address #rsp
No symbols for ntdll. Cannot continue.
I'm currently trying to debug a system deadlock and I'm having a hard time understanding this.
Child-SP RetAddr : Args to Child : Call Site
fffff880`035cb760 fffff800`02ecef72 : 00000000`00000002 fffffa80`066e8b50 00000000`00000000 fffffa80`066a16e0 : nt!KiSwapContext+0x7a
fffff880`035cb8a0 fffff800`02ee039f : fffffa80`0b9256b0 00000000`000007ff 00000000`00000000 00000000`00000000 : nt!KiCommitThreadWait+0x1d2
fffff880`035cb930 fffff880`0312a5e4 : 00000000`00000000 fffff800`00000000 fffffa80`079a3c00 00000000`00000000 : nt!KeWaitForSingleObject+0x19
Why would the first argument for KeWaitForSingleObject be null?
Unless I'm misunderstanding isn't the first argument the object being waited on?
Is the deadlock simply that this thread is waiting on nothing or is this ordinary behavior?
Additionally I see another process (services.exe) showing a similar stack trace:
1: kd> .thread fffffa800d406b50
Implicit thread is now fffffa80`0d406b50
1: kd> kv
*** Stack trace for last set context - .thread/.cxr resets it
Child-SP RetAddr : Args to Child : Call Site
fffff880`09ed4800 fffff800`02ecef72 : fffffa80`0d406b50 fffffa80`0d406b50 00000000`00000000 fffff8a0`00000000 : nt!KiSwapContext+0x7a
fffff880`09ed4940 fffff800`02ee039f : 00000000`000000b4 fffffa80`0b1df7f0 00000000`0000005e fffff800`031ae5e7 : nt!KiCommitThreadWait+0x1d2
fffff880`09ed49d0 fffff800`031d1e3e : fffffa80`0d406b00 00000000`00000006 00000000`00000001 00000000`093bf000 : nt!KeWaitForSingleObject+0x19f
fffff880`09ed4a70 fffff800`02ed87d3 : fffffa80`0d406b50 00000000`77502410 fffff880`09ed4ab8 fffffa80`0b171a50 : nt!NtWaitForSingleObject+0xde
Is this thread waiting on itself essentially?
You're debugging a 64-bit process.
Remember the x64 calling convention, which is explained here. The first 4 arguments are passed in registers. After that, arguments are pushed onto the stack.
Unfortunately, kv blindly displays the stack arguments. In fact, it would be quite difficult (and sometimes impossible) for it to determine what the first 4 arguments actually were at the time of the call since they may not have been stored anywhere that can ever be recovered.
So, you are looking at the 5th argument to nt!NtWaitForSingleObject, where a nullptr is a pretty typical argument for a Timeout.
Luckily for us debugging types, all is not lost! There is a windbg extension which does its best to reconstruct the arguments when the function was called. The extension is called CMKD. You can place the extension DLL in your winext folder and call it like so:
0:000> !cmkd.stack -p
Call Stack : 7 frames
## Stack-Pointer Return-Address Call-Site
00 000000a408c7fb28 00007ffda95b1148 ntdll!NtWaitForSingleObject+a
Parameter[0] = 0000000000000034
Parameter[1] = 0000000000000000
Parameter[2] = 0000000000000000
Parameter[3] = (unknown)
01 000000a408c7fb30 00007ff7e44c13f1 KERNELBASE!WaitForSingleObjectEx+98
Parameter[0] = 0000000000000034
Parameter[1] = 00000000ffffffff
Parameter[2] = 0000000000000000
Parameter[3] = 00007ff7e44cba28
02 000000a408c7fbd0 00007ff7e44c3fed ConsoleApplication2!main+41
Parameter[0] = (unknown)
Parameter[1] = (unknown)
Parameter[2] = (unknown)
Parameter[3] = (unknown)
Notice that it does not always succeed at finding the argument, as some of them are (unknown). But, it does a pretty good job and can be an invaluable tool when debugging 64-bit code.
This looks like a 64-bit OS, and therefore the calling convention is not to pass all the parameters on the stack. Rather, the first four parameters get passed in RCX, RDX, R8, and R9, with the remaining parameters on the stack. So if you catch the call to KeWaitForSingleObject it's easy to see what's in RCX and go from there. Once you are a few stack frames beyond that, it's much hard to tell since something will have been loaded into that register. The original value is probably stored somewhere, but it will be difficult to find.