PB Symbols

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.