On Platform Builder versions prior to 5.0, one thing
I find myself doing in a lot of debugging cases is verifying that debugger
symbols match what is actually running on a device. In most cases the debugger
can detect incorrect symbols and will notify the user in two ways.
The first
notification comes when the module loads. There will be a notification in the
debug output window that says something like “no matching symbols
found”. For instance, here’s an example of loading OSSVCS.DLL when
the PDB is missing from the release directory.
Debug Output Window
Loaded '<path>\OSSVCS.DLL', no
matching symbolic information found.
The
second way to see status is to look in the modules and symbols window in
Platform Builder. It will show the symbol load status and where the symbols
have been loaded from.
Modules and Symbols Window
oleaut32.dll
0x037D0000 - 0x037FFFFF 0x01F3E000 - 0x01F3EED0 Loaded
ossvcs.dll
0x033F0000 - 0x0341BFFF 0x01EFB000 - 0x01EFB9B0 Unloaded
Unfortunately,
there are cases where this isn’t enough. The Windows CE tools format
modules in the MODULES section of the BIB to strip out any information that is
not necessary to run the module. In doing so, the original file format is
not maintained and it’s possible for the code resident on the device to
not match the symbols that the debugger loads. The most common case of this is
when you receive a .BIN file from someone without the matching symbols. The
second common case can happen if you regenerate a module, updating it in the
release directory, without updating it on the device. In this case the DLL and
PDB in the release directory match each other, but don’t match
what’s running on the device.
Mismatched
symbols can lead to a poor or misleading debugging experience. One method I use
to manually verify the symbols is to look at the start of a few functions from
the module I’m working with. Pick a function and find out what
address it’s loaded at by typing the module and function name into the
watch window, as follows…
Watch Window
{,,ossvcs.dll} PlaySoundScript
This
should resolve to a specific address like this…
Watch Window
{,,ossvcs.dll}
PlaySoundScript 0x033f895c PlaySoundScript(…)
If it
doesn’t, you may not have symbols at all, in which case you may need to
regenerate or get the symbols from whoever built the module originally.
Now that
you have the function address, click on the result and drag the
contents to the disassembly window. The assembly code for this function should
appear. This is the code that is being read from the device at this address. If
the symbols match, then you should see something like this…
Disassembly Window
PlaySoundScript:
033F895C
E92D4010
stmdb sp!, {r4, lr}
033F8960
E24DD004
sub sp, sp, #4
$M36115:
033F8964
E3A04000
mov r4, #0
…
How can
you tell if this is correct? Well, you need to know generally what a function
entry looks like; let me explain that here. I’m going to show example
from ARM assembly, but other processors have similar entry methods. The
compiler generates a prolog on function entry. A prolog is a
handful of machine instructions that prepare the processor to execute a new
function. In most cases this is simply to push a set of registers onto the
stack and to decrement the stack pointer by enough to allow room for local
function variables to live on the stack if necessary.
For
instance, the most common first instruction on ARM processor for a function
entry point is a ‘store multiple’ instruction, here saving R4 and
LR onto the stack.
Disassembly Window
PlaySoundScript:
033F895C
E92D4010
stmdb sp!, {r4, lr}
Now
here’s an example of a case of symbol mismatch where the debugger
could not detect the mismatch on module load.
Disassembly Window
SetPolicy:
0340863C
E8BD8030
ldmia sp!, {r4, r5, pc}
03408640
033F19A4
???
$M30427:
03408644
033F2A00
???
03408648
E92D40F0
stmdb sp!, {r4 - r7, lr}
0340864C
E24DD01C
sub sp, sp, #0x1C
In this
case, I had updated the module in the release directory, but was running an
older binary image in the device. Here you see that there is not ‘store
multiple’ instruction at the beginning. Instead the ‘store
multiple’ is a few assembly instructions down because of the symbol
mismatch.
Mismatched
symbols can affect your debugging experience, even if you are not specifically
looking at an issue that directly involves the mismatched module. For instance,
here’s a call stack for a thread from a Windows Mobile device with correct
symbols:
Callstack Window
0x0c0bfdbc NK!SC_WaitForMultiple()
schedule.c line 4434 + 20 bytes
0x0c0bfde8 NK!UB_WaitForMultiple()
schedule.c line 4483
0x0c0bfe00 COREDLL!xxx_WaitForMultipleObjects()
tkfuncs.c line 101 + 52 bytes
0x0c0bfe18
GWES!MsgQueue::MsgWaitForMultipleObjectsEx_I() msgque.cpp line 5296 + 16 bytes
0x0c0bfe64
COREDLL!xxx_MsgWaitForMultipleObjectsEx() twinuser.cpp line 1205
0x0c0bfe70 OSSVCS!SSUpdate::Thread()
ssupdate.cpp line 1444
0x0c0bfed4 COREDLL!ThreadBaseFunc()
apis.c line 419
Here’s
that same exact call stack if I manually unload the symbols and put wrong
symbols for OSSVCS.DLL into the release directory.
Callstack Window
0x0c0bfdbc NK!SC_WaitForMultiple()
schedule.c line 4434 + 20 bytes
0x0c0bfde8 NK!UB_WaitForMultiple()
schedule.c line 4483
0x0c0bfe00
COREDLL!xxx_WaitForMultipleObjects() tkfuncs.c line 101 + 52 bytes
0x0c0bfe18
GWES!MsgQueue::MsgWaitForMultipleObjectsEx_I() msgque.cpp line 5296 + 16 bytes
0x0c0bfe64
COREDLL!xxx_MsgWaitForMultipleObjectsEx() twinuser.cpp line 1205
0x0c0bfe70 OSSVCS!SSUpdate::s_WndProc()
ssupdate.cpp line 1543 + 4 bytes
0x0c0bfe70 NK!PrefetchAbort + 336 bytes
0x0c0bfe88 NK!PrefetchAbort + 336 bytes
0x0c0bfea0 NK!PrefetchAbort + 336 bytes
0x0c0bfeb8 NK!PrefetchAbort + 336 bytes
0x0c0bfed0 NK!PrefetchAbort + 336 bytes
0x0c0bfee8 NK!PrefetchAbort + 336 bytes
…
The
debugger tries to unwind the call stack. When it unwinds to a function in OSSVCS.DLL,
it looks for the prolog in order to fix up the stack. But because the prolog
isn’t at the right address, the debugger miscalculates where to find the
next function on the callstack and ends up showing garbage.
The
bottom line: It is critical to have the correct symbols to effectively
use the debugger.