Search This Blog

Thursday 24 September 2015

WinDbg : Deferred Symbol Loading

WinDbg : Deferred Symbol Loading



Sometimes WinDbg won't load the symbols for a particular module. If you want to debug that module or process call stacks containing that module's symbols you are going to be in for a surprise. So lets try and see what can be done to resolve the situation.

16.0: kd> lmv m mydll.dll
start             end                 module name

Unable to enumerate user-mode unloaded modules, NTSTATUS 0xC0000147

So looks like the module was not found. Lets try and see if the module is actually there.

16.0: kd> lm
start             end                 module name
00000000`00b30000 00000000`00b55000   MySvc   (deferred)             
00000000`704f0000 00000000`70513000   MyDll   (deferred)             
00000000`710b0000 00000000`711e3000   msxml3     (deferred)             
00000000`711f0000 00000000`711f7000   WSOCK32    (deferred)             
00000000`71200000 00000000`7135f000   sis        (deferred)             
00000000`73a10000 00000000`73a6c000   wow64win   (deferred)             
00000000`73a70000 00000000`73aaf000   wow64      (pdb symbols)          d:\websyms\wow64.pdb\7818D2D03E1C4936B815636B5EC869B21\wow64.pdb
00000000`747e0000 00000000`7497e000   COMCTL32   (deferred)             
00000000`74e20000 00000000`74e28000   wow64cpu   (pdb symbols)          d:\websyms\wow64cpu.pdb\C8D4F6038D014F62AB0C6A28A7FAFEF21\wow64cpu.pdb
00000000`74ed0000 00000000`74edc000   CRYPTBASE   (deferred)             
00000000`771a0000 00000000`77349000   ntdll      (pdb symbols)          d:\websyms\ntdll.pdb\6192BFDB9F04442995FFCB0BE95172E12\ntdll.pdb

Unloaded modules:
fffff880`01846000 fffff880`01854000   crashdmp.sys
fffff880`01854000 fffff880`0185e000   dump_storport.sys
fffff880`0185e000 fffff880`0187b000   dump_LSI_SAS.sys
fffff880`0187b000 fffff880`0188e000   dump_dumpfve.sys

Unable to enumerate user-mode unloaded modules, NTSTATUS 0xC0000147

Aaah, so this the problem, the module is present but the symbols are not. For the more keen observer, this looks to be a dump from a 64 bit machine. Hence the 64 bit addresses and the presence of wow64.

Okay, lets try and see what is there at the address where the module is loaded.

16.0: kd> db 00000000`704f0000 
0000:0000  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
0000:0010  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
0000:0020  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
0000:0030  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
0000:0040  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
0000:0050  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
0000:0060  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
0000:0070  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????

Ouch!!!!

Now lets try to load the symbols. However, to do so, we need to calculate the size of the module.

70513000 - 704f0000 = 0x23000

Lets use reload to force load the symbols.

16.0: kd>.reload /i MyDll.Dll= 704f0000 

This should load the symbols for the Dll and map it to the address given. At this point any reffernces to this address zone will lead WinDbg to use the symbols mapped to it.

It is important to map the symbols to the right address, else the stack dumps will be gibberish.

Wednesday 12 August 2015

WinDbg : Getting The Last Error in WinDbg

WinDbg : Getting The Last Error


One of the most used Windows APIs is perhaps GetLastError(). It returns us the error code of the last error encountered. However, many a times, we do not query for the last error while writing the code, and while debugging later on, we have no idea why it failed, we just know that it did. Here is an example:

HANDLE hVol = INVALID_HANDLE_VALUE;
hVol = CreateFile( csDrvName,
                           0,
                           FILE_SHARE_READ | FILE_SHARE_WRITE,
                           NULL,
                           OPEN_EXISTING,
                           0,                           // FILE_FLAG_NO_BUFFERING | FILE_FLAG_RANDOM_ACCESS
                           NULL);

if( hVol == INVALID_HANDLE_VALUE )
{
        return E_FAIL;
}

Now this function never queried the last error and if it had logging, then it never printed it to the logs. 

Which brings us to the question, if we had called GetLastError(), where does it get the info from, and if it did, then can we also get the same information from the debugger in cases when we wrote code like the above. The answer is Yes, we can, and here is how... 

Commands Used:
  1. !teb
  2. !error
Before we proceed, all error codes are thread specific, so being in the context of the thread you are debugging is important. To know know more about changing thread contexts this link might be helpful.  

Note : The fields of !teb sometimes vary between operating system versions. So if your output is slightly different from mine, there is no need to be concerned.

0:002> !teb
TEB at 7efd7000
    ExceptionList:        00b8fdf0
    StackBase:            00b90000
    StackLimit:           00b8e000
    SubSystemTib:         00000000
    FiberData:            00001e00
    ArbitraryUserPointer: 00000000
    Self:                 7efd7000
    EnvironmentPointer:   00000000
    ClientId:             0000099c . 00001d48
    RpcHandle:            00000000
    Tls Storage:          7efd702c
    PEB Address:          7efde000
    LastErrorValue:       2
    LastStatusValue:      0
    Count Owned Locks:    0
    HardErrorMode:        0


The value in LastErrorValue field is what GetLastError reports to us. It lives in the thread level, and that is why we can be multi threaded, and each thread can be calling a different API and the error codes don't get mangled.

Coming to the interpretation part. The !error command can interpret all errors and status messages in Windows.

0:001> !error 0n2
Error code: (Win32) 0x2 (2) - The system cannot find the file specified.

Note : Please be careful to use the right numeric base for !error. That is, if the value shown in !teb is in decimal, we should append a 0n to ensure it is not interpreted as hexadecimal.

Similarly, the LastStatusValue field will have the NTSTATUS codes.