It's free to join Gamasutra!|Have a question? Want to know who runs this site? Here you go.|Targeting the game development market with your product or service? Get info on advertising here.||For altering your contact information or changing email subscription preferences.
Registered members can log in here.Back to the home page.

Search articles, jobs, buyers guide, and more.

By Jelle van der Beek
[Author's Bio]

Gamasutra
April 16, 2004

Introduction

PS2

Printer Friendly Version
   

 

Change Login/Pwd
Post A Job
Post A Project
Post Resume
Post An Event
Post A Contractor
Post A Product
Write An Article
Get In Art Gallery
Submit News

 


 


Latest Letters to the Editor:
Perpetual Layoffs by Alexander Brandon [09.21.2007]

Casual friendliness in MMO's by Colby Poulson [09.20.2007]

Scrum deals and 'What is Scrum?' by Tom Plunket [08.29.2007]


[Submit Letter]

[View All...]
  



Upcoming Events:
SPARK Animation Festival
Vancouver, Canada
09.10.08

Women In Games Conference
Coventry, United Kingdom
09.10.08

3rd ACM International Conference on Digital Interactive Media in Entertainment and Arts - DIMEA 2008
Athens, Greece
09.10.08

GDC Austin
Austin, United States
09.15.08

Game Career Seminar
Austin, United States
09.17.08

[Submit Event]
[View All...]

 


[Enter Forums...]

Note: Discussion forums for Gamasutra are hosted by the IGDA, which is free to join.
 


Features

Monitoring Your Console's Memory Usage, Part Two

In part one I discussed the overview of our tool, MemAnalyze, and how to make a memory dump on Xbox and PS2 consoles. In this part, we will read the memory dump, convert function names to addresses using Map files or PDB files, then we will process the data to create several views on the data. I will also discuss future plans to make MemAnalyze much more powerful.

Converting return addresses to function names

Xbox

In part one, I chose not to output function names to the memory dump, but only the return addresses. This means that if you are using XbMemdump, you might want to turn off the symbol information and do the converting yourself, to make it easier to parse the memory dump. When using symbol information in XbMemdump, the function addresses are replaced with the names and not added. We can turn off the symbol information simply by not supplying a PDB path on the command line.

Before getting into detail on either map or PDB files, I will share a few more details regarding absolute addresses and image- and section base addresses.

Absolute address

The return addresses we store in our memory dump are all absolute addresses.

Image base address

Image base addresses on PCs work differently then on the Xbox. It took me quite some time to figure this out.

On a PC, you can enter a preferred base address for the image (/BASE linker setting), though the operating system can relocate this address when loading the image. The operating system will only relocate the image if there is not enough space to load the image. This only applies when loading DLLs in the same process, not when loading a single executable. For DLLs, the image base address can be tweaked to avoid DLL conflicts and to gain performance.

With this knowledge in mind, I checked the Xbox project settings. There is still the /FIXED setting in the advanced link options. This option specifies if you always want to load the image at the preferred base address. If the operating system cannot load it at that address, the load fails. So the /FIXED option is there, but the /BASE setting is not. I figured the linker would default to some static value. So, the first thing I did was look up the base address in the MAP file that is produced by the linker (listing 1).


XyanideD

Timestamp is 4048a4d1 (Fri Mar 05 17:03:29 2004)

Preferred load address is 00400000

Start         Length      Name            Class
0001:00000000 00887928H   .textbss        DATA

Listing 1: The preferred load address in the map file.

And there it is! Obviously, the preferred load address is 0x400000. Well, not really. There must be some PC legacy involved here. The Xbox image loader works differently, and the 0x400000 value is not used at all. When starting an Xbox application, multiple modules are loaded into the private virtual address space. Other modules, including the kernel and debugging modules, are also loaded. Unlike the PC platform, there is just one process running at a time, so all modules are loaded into the same address space. The image base address is completely determined by the Xbox image loader, which is also why the /BASE setting is not present in the link options. We can retrieve the image base address as described in part one, by calling DmWalkLoadedModules, in the game. An example of the output of DmWalkLoadedModules is shown in listing 2.


--- List of all loaded modules ----

Name: xboxkrnl.exe
  BaseAddress:80010000
  Size: 1268224

Name: xbdm.dll
  BaseAddress:B0011000
  Size: 401056

Name: vx.dxt
  BaseAddress:B00B2000
  Size: 101184

Name: XboxProject.exe
  BaseAddress:00010CE0
  Size: 285376

--- End of list ----


Listing 2: Output of all loaded modules, while running my test project 'XboxProject'.

In listing 2, we can also see that the system modules are loaded in the space above 2GB--the shared address space. Our own module is loaded near the beginning of the virtual address space. (The first 0x10000 bytes of the virtual address space is reserved by the operating system.)

Section address

Each image consists of a set of sections. These are both DATA and CODE sections. The .text section will contain all your game code. A map file displays a list of all the sections in the image (listing 3).


0001:00000000 00887928H .textbss DATA
0002:00000000 00e2d232H .text CODE
0002:00e2d240 00001eceH .text$x CODE
0002:00e2f110 000e6d24H .text$yc CODE
0002:00f15e40 00039da8H .text$yd CODE
0003:00000000 0000ce65H XMV CODE
0003:0000ce68 00001f03H XMV_RD CODE
0003:0000ed70 0002833cH XMV_RW CODE
0003:000370b0 00002baaH XMV_URW CODE
0004:00000000 0002dc73H XACTENG CODE
0004:0002dc78 000098b6H XACTENG_RD CODE
0004:00037530 000013dfH XACTENG_RW CODE
0004:00038910 000014e7H XACTENG_URW CODE


Listing 3: sections from the map file.

Section addresses are absolute addresses, 32-byte aligned. Figure 1 shows the layout of the virtual memory if a program is loaded into memory.

Figure 1: The status of the virtual memory when an Xbox image is loaded.

Note: the image base address of the game module is never 0x10000, but a couple of hundreds or thousands bytes further in memory (see Listing 2). (This block is not displayed in Figure 1; I have not been able to find out what data resides there.) Also, the start of the first section is never the same as the start of the image base address. In Figure 1, I have displayed this as "Image information". Most likely, this is where the section headers will reside, as the sections themselves are contiguous (mind the section alignment though).

Map files

Map files store their functions in relative virtual addresses. The first question that pops up is: "Relative to what?" Well, map files store their functions along with two relative addresses:

  • Section-relative.
  • Relative to the image base (the column header says rva+base, but we will see why this is incorrect in a moment).

First, we will discuss section-relative conversion.

If we take a look at a line of text from a map file (listing 4), column 1 displays the section index and the offset into the section. Column 2 displays the function's decorated name and column 3 displays the offset of the function, relative to the image base. The rest is of no use to us.


0002:008bfb00 ??$Write@V?$TSharedPtr@UIWorldAttachmentProvider@@@GFW@@@
TPointer Converter@GFW@@SAXAAUIPropertySaver@1@PBDABV?
$TShared Ptr@UIWorld AttachmentProvider@@@1@@Z 015325a0 f i
_WaveSpawner.obj


Listing 4: A line of text from the map file.

To compute the absolute address of the function displayed in listing 3, we need the start address of the section. The section index is in front of the section relative address. In listing 4, this is 0002. Unfortunately, the section base addresses are not present in the map file. Don't be thrown off by the list on top of the map file--it does not display the section base addresses. We need to parse the game's executable using ImageBld to obtain section information. We dumped our Xyanide section information on the command line by typing:

ImageBld /dump XyanideD.xbe >XyanideSectionDump.txt

Now we need to parse the section dump (listing 5), find the start address, and add it to the offset of the function. This gives us the absolute start address of the function, but we stored the return addresses! So we first have to compute the size of the function. The map file displays all the functions in order of appearance in memory, so we can simply subtract the address of our function from the address of the next function in the map file.


SECTION HEADER #2    .text
  884140 virtual address
  F21C18 virtual size
    2000 file pointer to raw data
  F21C18 size of raw data
000107D2 head shared page reference count address
000107D4 tail shared page reference count address
       6 flags
         Preload
         Executable

Listing 5: Layout of a section of the ImageBld.exe tool. We need to parse the 'virtual address' field.

We have now parsed the section dump from ImageBld, but we could also have used a function from the debug library on the Xbox itself: DmWalkModuleSections. We can save the module section addresses in the memory dump from within the game, just as we did by saving the image base.

To convert the decorated function name to a readable name, use the helper function UnDecorateSymbolName from the DbgHelp library.
Now we know how to convert a function address to a function name. There are two drawbacks to using section relative conversion if you are using ImageBld to parse the executable:

  • If the image base address is relocated by the operating system, the section base addresses are incorrect, because they are provided as absolute addresses. (This code assumes that the default image base is being used.) This has not happened to me yet, but in theory, it is possible.
  • We need to parse two files to find the function name.

By using DmWalkModuleSections, you will only have to parse the MAP file, and it has the advantage that the kernel knows if the image base has been relocated, so the correct absolute addresses are written to file.

Another approach is to use the third column of the map file. This is the image-base relative conversion. The header of the column says this is the rva+base address, meaning it is the absolute address of the function name. Well, that is simply not true. This code also uses the preferred base address of 0x400000. We've just seen that this value is not used, and incorrect. However, we can use it to our advantage! By simply subtracting 0x400000 from this value, we get the address relative to the image base. We can work with image base relative values, because they are independent of the location of the image in memory. So, regardless of whether the OS has relocated the base address, we can find our function name. We only need to use the memory dump's runtime image base address. We already saved this value in part one. Thus, this is how you convert the rva+base column to an absolute address:

AbsFunctionAddress = RvaBase - 0x400000 + RealImageBase

Now we get to the final drawback of map files: Xbox map files do not include static functions! This will sometimes result in incorrect function names. They are usually not completely off; the resulting function name will probably be a function of the same object file, so we are pointed in the right direction. But, we could do even better. We can read Program Databases!

PDB files

Program Databases store the functions in image base relative addresses, so no problems there. The PDB file holds all the information you could possibly think off. If we try hard enough, we can even double-click a callstack item in the tool and display the function and the exact line of code that matches the allocation.

We can parse PDB files using the DbgHelp library and by using the DIA SDK.

Note: The Microsoft DIA SDK is an SDK that parses symbol information from PDB files. I have had a really hard time figuring out how to use the SDK, particularly because there is so little documentation on it. I have asked Microsoft about this, and they told me that version 8.0 of Visual Studio will contain a much more comprehensive documentation.

I have stripped and adjusted the dia2dump sample that comes with the SDK. The code from listing 6 will dump absolute function addresses, function names and sizes, based on a given image base address.


#include "stdafx.h"
#include "diacreate.h"
#include "cvconst.h"

CComPtr<IDiaSession> psession;
CComPtr<IDiaSymbol> pglobal;

void Fatal( const char *msg )
{
   printf( msg );
   printf( "\n" );
   exit(-1);
}

void Dump(
   char* szFilename,
   IDiaDataSource* pSource,
   wchar_t* szLookup,
   DWORD ImageBaseAddress
)
{
   HRESULT hr;
   wchar_t wszFilename[_MAX_PATH];
   mbstowcs(wszFilename, szFilename,
   sizeof(wszFilename) /sizeof( wszFilename[0]));

   if(FAILED(pSource->loadDataFromPdb(wszFilename)))
   {
      if(FAILED(pSource->loadDataForExe(wszFilename, NULL,       NULL)))
      {
         Fatal( "loadDataFromPdb/Exe" );
      }
   }

   if(FAILED(pSource->openSession(&psession)))
   {
      Fatal("openSession");
   }
   if(FAILED(psession->get_globalScope(&pglobal)))
   {
      Fatal("get_globalScope");
   }
   DWORD id = 0;
   pglobal->get_symIndexId(&id);
   if(id == 0)
   {
      Fatal( "get_indexId" );
   }

   psession->put_loadAddress(ImageBaseAddress);

   ULONG celt = 0;

   CComPtr<IDiaEnumSymbols> pEnum;
   CComPtr<IDiaSymbol> pSymbol;

   pglobal-
>findChildren(SymTagFunction,NULL,nsfCaseInsensitive
              |nsfUndecoratedName,&pEnum);
   while(SUCCEEDED(hr = pEnum->Next(1, &pSymbol, &celt))
   && celt == 1)

   {
      BSTR name;
      if(pSymbol->get_name(&name) != S_OK)
      {
         Fatal("get_name");
      }
      printf("Function: %ws\n", name);

      ULONGLONG address;
      pSymbol->get_virtualAddress(&address);
      if(address == 0)
      {
         printf(" could not get address!\n");
      }
      printf(" Address: %08X\n", address);

      ULONGLONG len = 0;
      pSymbol->get_length(&len);
      if(len == 0)
      {
         printf(" could not get length!\n");
      }
      printf(" Length: %ld\n", len);

      pSymbol = 0;
   }
}

DWORD GetImageBaseAddress()
{
   // I've hardcoded the image base address here. Replace    // this by proper code that reads the image base address    // from the memory dump.
      return 0x10CE0;
}

int main(int argc, char* argv[])
{
   if(argc < 2)
   {
      printf( "usage: %s <pdb-filename>\n", argv[0] );
      return -1;
   }

   HRESULT hr;
   hr = CoInitialize(NULL);
   if (FAILED(hr))
   {
      Fatal("CoInitialize failed\n");
   }

   CComPtr<IDiaDataSource> pSource;
   // Initialize The Component Object Module Library
   // Obtain Access To The Provider
   hr = CoCreateInstance( CLSID_DiaSource, NULL,    CLSCTX_INPROC_SERVER,
   __uuidof( IDiaDataSource ), (void **) &pSource);

   if (FAILED(hr))
   {
      Fatal("Could not CoCreate CLSID_DiaSource. Register       msdia71.dll." );
   }

   Dump(argv[1], pSource, NULL, GetImageBaseAddress());

   pglobal = 0;
   psession = 0;
   pSource = 0;

   CoUninitialize();

   return 0;
}

Listing 6: Parsing symbol information using the DIA SDK.

The DbgHelp library, on the other hand, is very easy to use, and its documentation is very comprehensive. Listing 7 does almost the same as the sample code from listing 6, but using the DebugHlp library. Keep in mind that if you are not running on Windows XP, you need the latest DDK DLLs to run this code. These can be found at http://www.microsoft.com/ddk/debugging. For more information on DbgHelp, see the DbgHelp documentation.


#include <stdafx.h>

// Just for this sample, I use a big buffer to store the
// text in (no testing for boundaries!)
char g_BigBuf[1024*1024*4] = { '\0' };

DWORD64 GetImageBaseAddress()
{
// Again, hardcoded image base address. Rewrite this to
// proper image base code.
return 0x10CE0;
}

BOOL CALLBACK EnumerateSymbolsProc(
PSYMBOL_INFO pSymInfo,
ULONG SymbolSize,
PVOID UserContext)
{
if(pSymInfo->Flags | SYMFLAG_FUNCTION)
{
char intbuf[64];

strcat(g_BigBuf, "Function: ");
strcat(g_BigBuf, pSymInfo->Name);

strcat(g_BigBuf, " Address: ");
_itoa(pSymInfo->Address, intbuf, 16);
strcat(g_BigBuf, intbuf);

strcat(g_BigBuf, " Size: ");
_itoa(SymbolSize, intbuf, 10);
strcat(g_BigBuf, intbuf);

strcat(g_BigBuf, "\n");
}

return TRUE;
}

bool SetupDbgHelp(
HANDLE hProcess
)
{
bool bResult = false;

SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS);

if(SymInitialize(hProcess, NULL, FALSE))
{
bResult = true;
}

return bResult;
}

int GetPDBFileSize(const char* fileName)
{
HANDLE hFile = CreateFile(fileName,
GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);

int size = GetFileSize(hFile, NULL);
CloseHandle(hFile);
return size;
}

bool OpenPDB(
HANDLE hProcess,
const char* pFileName
)
{
bool bResult = false;
DWORD64 dwBaseAddr = GetImageBaseAddress();

int fileSize = GetPDBFileSize(pFileName);

if(SymLoadModule64(
hProcess,
NULL,
pFileName,
NULL,
dwBaseAddr,
fileSize))
{
if(SymEnumSymbols(
hProcess,
dwBaseAddr,
"",
EnumerateSymbolsProc,
NULL))
{
printf(g_BigBuf);
bResult = true;
}
SymUnloadModule64(hProcess, dwBaseAddr);
}

return bResult;
}

bool ParsePDB(
const char* pFileName
)
{
HANDLE hProcess;
bool bResult = false;

hProcess = GetCurrentProcess();

if(SetupDbgHelp(hProcess))
{
if(OpenPDB(hProcess, pFileName))
{
bResult = true;
}

SymCleanup(hProcess);
}

return bResult;
}

int main(
int argc,
char* argv[]
)
{
int retCode = -1;

if(argc != 2)
{
printf("Usage: ParsePDB_DbgHelp <pdb-file>\n");
}
else
{
if(ParsePDB(argv[1]))
{
retCode = 0;
}
else
{
DWORD error = GetLastError();
printf("\nDbgHelp returned error : %d\n", error);
}
}

return retCode;
}


Listing 7: Parsing symbol information using the DbgHelp library.


______________________________________________________

PS2


join | contact us | advertise | write | my profile
news | features | companies | jobs | resumes | education | product guide | projects | store



Copyright © 2003 CMP Media LLC

privacy policy
| terms of service

 

 

 

join