I want to blog about how to resolve symbols manually, and realized I would have to assume that the reader would understand the CE VM layout. So I figure I’d better explain that first. You don’t need to know everything about the VM layout in order to work with Windows CE, but it can be handy at times to at least understand the basics. You need to understand some of it in order to be effective at debugging.
There are a few spots in this discussion that I could go into more “whys” but I avoided them because the answers aren’t necessary in order to debug. We can discuss them separately if they bug you.
Anybody who has worked with CE for very long knows that there are a maximum of 32 processes, and each process gets a maximum of 32MB of virtual address space to work inside. Part of the reason for that is because Windows CE keeps all processes’ address spaces available at all times, even when those processes are not running. What it means is that the lower part of the address space is split into 32MB “slots.” The important details to internalize are:
If you do the math, that adds up to 33 slots. Slots 0 & 1 are special slots that aren’t used by processes, and I’ll explain that in a bit. That leaves 31 slots for process to run inside – and the 32nd process is the kernel, which lives elsewhere in RAM somewhere above the 0x80000000 mark. [OK detail hounds, in CE 3.0 and earlier, the kernel ran in slot 1 instead of in the upper 2GB.]
Slot 0 is a special slot whose contents change depending on what process is running. The current process is always mapped into slot 0 in addition to having its own unique slot. Most of the addresses a processes is given to work with, are in slot 0. Get the address of a variable or function from an EXE in the debugger, and you’ll see something like 0x00012345. But if that variable is from a process that’s running in the slot from 0x12000000 to 0x13FFFFFF, that same variable will be present at address 0x12012345.
Understanding slot 0 is actually very important for debugging. If you hit the “break” button at a random time, your process might not be running at that time. If you use the Watch Window to try to look at a global variable inside your process – the debugger might know what address the variable is at, without knowing the value of the variable. For example, John Eldridge already blogged a bit about how to use the Watch Window to look at a variable inside a specific module. Suppose you did this:
Watch Window {,,helloworld.exe} &MyGlobalVar 0x00012345 {,,helloworld.exe} MyGlobalVar ??? If helloworld.exe isn’t the process that’s running at the moment you break into the debugger, the debugger knows what the address of the global variable is, but not the value. But you can manually map the slot 0 address to the right slot, if you know what slot the process is running in. If helloworld.exe is running in the slot starting at 0x12000000, then you can add the slot start address to the slot 0 address, like this:
Watch Window {,,helloworld.exe} &MyGlobalVar 0x00012345 {,,helloworld.exe} MyGlobalVar ??? *((DWORD*)0x12012345) 72
(yes, that’s not a DWORD-aligned address, please close your eyes if it bugs you)
Also, if you switch the debugger to helloworld.exe, it can figure things out. I usually use the Callstack Window and select a thread from helloworld.exe to switch to. Then the contents of the Watch Window might change.
Watch Window {,,helloworld.exe} &MyGlobalVar 0x00012345 {,,helloworld.exe} MyGlobalVar 72 The difference is that now the slot 0 address refers to the same slot the debugger is currently pointed at.
Slot 1 is also a special slot. All DLLs that are stored in the “MODULES” section of ROM load in slot 1. The code – not the data – for as many DLLs as possible loads in slot 1. Only ROM DLLs load in slot 1. If you drop a new copy of a DLL onto a device, to replace a DLL that was in ROM, that new DLL will not load in slot 1.
Slot 1 stays the same no matter what the current process is. That’s why only code can load there; if two processes load the same slot 1 DLL, they will share the same code, but the two instances of the DLL will have different globals. The globals are stored in the process slot – so they will most often be referenced in slot 0 just like everything else the process owns.
The main point to all of this description of slot 0 & 1 is that you need to understand when the memory you need to look at is process-specific, and how to view process-specific memory. If a DLL loads into multiple processes, and you want to look at a global variable inside the DLL, the value could be different in different processes. You need to know which process you’re looking at, and how to change it if necessary.
One other detail that’s handy to know, is that EXE code loads at the very bottom of the slot (closest to 0x00000000), while DLL code & data load from the top of the slot down (closest to 0x1FFFFFFF). In between are heap allocations, thread stacks, small VirtualAllocs, and other “goodies” that don’t have symbols.
With practice you will develop one of the ninja debugging skillz: recognizing the location of an address just by looking at it. Here’s some practice:
Doug Boling, one of our MVPs, has written a paper that explains the virtual address space, and its implications, in much more detail. You should review that if you have more questions. I was just trying to cover the aspects that are important for debugging.
Next I’ll write about how to resolve symbols for addresses (how to go from address --> symbol), and after that I’ll write about the other direction, how to figure out an address for a particular function or variable (symbol --> address).
[Note: This applies to all Windows CE versions up through 5.0; with the exception I already noted about the kernel running in slot 1 in CE 3.0 and earlier.]