By Linkai Yu
When debugging, there are those situations when you don't have access to source code, you might have debugging symbols of a program that neither crashes nor generates an exception but simply displays an error message such as "The program encounters an error: 12055". So you can't use a debugger to break into the precise location of the error. By the time the error message is displayed, all you can see on the call stack is the code related to error display. The real location of instruction where the error was encountered is gone from the call stack. If you can set a break point at the instruction that determines the error, you might look at the assembly instructions and guess what could cause the problem. The question then is how can we find the instruction that generates the error code?
Here is what I do, not bullet proof but with a good success rate.
The idea is to search the relevant DLL(s) for the error number. Then verify the error number by unassemblying the instructions backward from the location to see if the instructions makes sense. If you see instruction pattern mov <target>, <value>, then most likely it's the right instruction.
The following is an example of an error code returned from WININET.DLL: 12055.
step 1. get the hex of 12055 by debugger command "?"
Evaluate expression: 12055 = 00002f17
step 2. find the start and end address of WININET.DLL
0:003> lm m wininet
start end module name
46a70000 46b40000 WININET (private pdb symbols)
step 3. search for hex 00002f17. since the search starts from lower address towards higher address, the serach byte order is: 17 2f 00 00
0:003> s 46a70000 46b40000 17 2f 00 00
46ac2623 17 2f 00 00 8b 8e d8 00-00 00 57 e8 07 3e ff ff ./........W..>..
46ac2870 17 2f 00 00 e9 27 5a ff-ff 8b 46 34 83 e8 07 0f ./...'Z...F4....
46ac4273 17 2f 00 00 0f 84 62 4c-fc ff 81 f9 7d 2f 00 00 ./....bL....}/..
46afcbbb 17 2f 00 00 0f 84 46 01-00 00 48 48 0f 84 3e 01 ./....F...HH..>.
46b0e9b8 17 2f 00 00 7d 00 00 00-30 f0 af 46 01 00 00 00 ./..}...0..F....
this search turns out 5 results.
step 4. unassemble backward from the location to verify if the instructions make sense.
0:003> ub 46ac2623 +4
WININET!ICSecureSocket::ReVerifyTrust+0x1f0 [d:\vista_gdr\inetcore\wininet\common\ssocket.cxx @ 1380]:
46ac2600 2f das
46ac2601 0000 add byte ptr [eax],al
46ac2603 755a jne WININET!ICSecureSocket::ReVerifyTrust+0x292 (46ac265f)
46ac2605 8b8d24fdffff mov ecx,dword ptr [ebp-2DCh]
46ac260b 8b86d8000000 mov eax,dword ptr [esi+0D8h]
46ac2611 81c900000004 or ecx,4000000h
46ac2617 0988f8020000 or dword ptr [eax+2F8h],ecx
46ac261d c78528fdffff172f0000 mov dword ptr [ebp-2D8h],2F17h
0:003> ub 46ac2870 +4
WININET!ICSecureSocket::VerifyTrust+0x4e9 [d:\vista_gdr\inetcore\wininet\common\ssocket.cxx @ 1674]:
46ac2844 89bd24fdffff mov dword ptr [ebp-2DCh],edi
46ac284a 899d1cfdffff mov dword ptr [ebp-2E4h],ebx
46ac2850 399d1cfdffff cmp dword ptr [ebp-2E4h],ebx
46ac2856 748f je WININET!ICSecureSocket::VerifyTrust+0x422 (46ac27e7)
46ac2858 8b86d8000000 mov eax,dword ptr [esi+0D8h]
46ac285e 8b8d24fdffff mov ecx,dword ptr [ebp-2DCh]
46ac2864 0988f8020000 or dword ptr [eax+2F8h],ecx
46ac286a c78528fdffff172f0000 mov dword ptr [ebp-2D8h],2F17h
of the five search results, two show valid instructions:
c78528fdffff172f0000 mov dword ptr [ebp-2D8h],2F17h
[ebp-2D8h] points to a local variable. The C++ equivalent code would look like:
intError = 12055;
now you can set break point at address 46ac286a and 46ac261d and have fun debugging!
bp 46ac2623;bp 46ac2870 ;g