Windows CE Virtual Memory Layout for Debugging

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:

  • 32MB in hex is 0x02000000
  • Slot 0 is 0x00000000 through 0x01FFFFFF
  • Slot 1 is 0x02000000 through 0x03FFFFFF
  • The last process ends at 0x41FFFFFF
  • Memory from 0x42000000 to 0x7FFFFFFF is mostly the “shared area” used for VirtualAllocs and memory-mapped files.  In other words there will never be any symbols there.
  • Memory above 0x80000000 is “kernel stuff” – the kernel does run there, as well as DLLs that load into the kernel, like installable interrupt service routine (ISR) DLLs.

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.

  • Try it out.  Break into the debugger while your EXE is running.  Use the Modules & Symbols Window to figure out what slot your process is in, then change a slot 0 address into one from the process slot and see if you get the same data.

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.

  • Check it out.  Open the Modules & Symbols Window and view the addresses that various DLLs are loaded at.  Some will be in slot 0, some will be in slot 1.  A few will be in the kernel range.
  • So now look back at the addresses John Eldridge posted on his blog entry, and you can see that the DLL addresses he posted were all from 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:

  • 0x00012345 is probably code or global variables from an EXE.  0x0056789A is probably not.  Why?  Too big, that’s a gigantic EXE.  It’s more likely to be stack or heap, some other dynamically allocated memory, not EXE or DLL contents.  It’s easy to ballpark if you can look at the actual size of the EXE.
  • 0x03123456 is a slot 1 DLL.  So is 0x02123456.
  • Address 0x3456789A is inside the process that’s running in the slot that starts at 0x34000000; you can use the Modules and Symbols window to figure out which process that is.  But it’s most likely not code or global variables – for the exact same reason as 0x0056789A in my first bullet.  This is just a “slot-mapped” version of the same address.
  • Address 0x019ABCDE is probably from a DLL.  It's near the top of the slot so it's more likely to be a DLL than heap or stack.  The same with 0x3519ABCDE.  Get used to noticing whether the 2nd nibble of the address is odd or even.
  • Address 0x6789ABCD is not from a module.  Maybe something from VirtualAlloc or a memory-mapped file, but you’ll never find a symbol that matches that address.
  • While we’re at it, okay, I am using bad sample addresses. 0x00012345 is not DWORD-aligned.  Things are much more likely to be aligned on a 4-byte boundary: ending in 0, 4, 8 or C.  Using consecutive numbers just seems more “friendly” to me, so I’m sorry if that bugs you.  If you noticed, then you already have some ninja debugging skillz.  It’s a good thing to be attuned to, since non-x86 processors can’t cope with accessing 4-byte values that are not 4-byte aligned.

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.]