Creating and Reading a MAP File

[Previous] [Next]

Many people have asked me why I keep recommending that everyone create MAP files with their release builds. Simply put, MAP files are the only textual representation of your program's global symbols and source file and line number information. Although using CrashFinder is far easier than deciphering a MAP file, your MAP files can be read anywhere and anytime, without requiring a supporting program and without requiring all your program's binaries to get the same information. Trust me, at some point in the future, you're going to need to figure out where a crash happened on an older version of your software, and the only way you'll be able to find the information is with your MAP file.

You can create MAP files for modules compiled in both Microsoft Visual C++ and Microsoft Visual Basic. In Visual C++, on the Link tab of the Project Settings dialog box, type the switches /MAPINFO:EXPORTS and /MAPINFO:LINES in the Project Options edit box. Select Debug in the Category list box, and check Generate Mapfile.

If you're working on a real-world project, you probably have your binary files going to their own output directory. By default, the linker writes the MAP file to the same directory as the intermediate files, so you need to specify that the MAP file goes to the binary file output directory. In the Mapfile Name edit box, you can type $(OUTDIR)\<project>.MAP where <project> is the name of your particular project. The $(OUTDIR) is an NMAKE.EXE macro that the build system will substitute with the real output directory. Figure 8-1 shows the completed MAP file settings for the MapDLL project, which is included on the companion CD.

Creating a MAP file for a Visual Basic module involves setting the same flags but in a different, interesting way. Visual Basic uses the same LINK.EXE as Visual C++, and you can set some of the command-line switches through the LINK environment variable. If you set the LINK environment variable to "/MAP:<project>.MAP /MAPINFO:EXPORTS /MAPINFO:LINES," Visual Basic will generate the MAP file in the link step of the compilation. After you set the environment variable in a Command Prompt window, you must start Visual Basic from that same window so that the LINK environment variable is visible to VB6.EXE.

click to view at full size.

Figure 8-1 The MAP file settings in the Project Settings dialog box

Although you might not need the MAP files in your day-to-day operation, chances are that you'll need them in the future. CrashFinder and your debugger rely on symbol tables and a symbol engine to read them. If the format of the symbol table changes or if you forget to save the Program Database (PDB) files, you're completely out of luck. Forgetting to save the PDB files is your fault, but you have no control over symbol table formats. They change frequently. For example, many people who upgraded from Microsoft Visual Studio 5 to Visual Studio 6 noticed that tools such as CrashFinder quit working with programs compiled with Visual Studio 6. Microsoft changed the symbol table format and does so on a regular basis. MAP files are your only savior at that time.

Even though you, as a developer, might be up to Windows 2005 with Visual Studio 11 Service Pack 6 in five years, I can assure you that you'll still have customers who will be running the software you released back in 1999. When they call you in alarm and give you a crash address, you could spend the next two days trying to find the Visual Studio 6 CDs so that you can read your saved PDB files. Or if you have the MAP files, you can find the problem in five minutes.

MAP File Contents

Listing 8-1 shows an example MAP file. The top part of the MAP file contains the module name, the timestamp indicating when LINK.EXE linked the module, and the preferred load address. After the header comes the section information that shows which sections the linker brought in from the various OBJ and LIB files.

After the section information, you get to the good stuff, the public function information. Notice the "public" part. If you have static-declared C functions, they won't show up in the MAP file. Fortunately, the line numbers will still reflect the static functions.

The important parts of the public function information are the function names and the information in the Rva+Base column, which is the starting address of the function. The line information follows the public function section. The lines are shown as follows:

10 0001:00000030

The first number is the line number, and the second is the offset from the beginning of the code section in which this line occurred. Yes, that sounds confusing, but later I'll show you the calculation you need to convert an address into a source file and line number.

If the module contains exported functions, the final section of a MAP file lists the exports. You can get this same information by running "DUMPBIN /EXPORTS <modulename>."

 MapDLL Timestamp is 37f41936 (Thu Sep 30 22:15:18 1999) Preferred load address is 03900000 Start Length Name Class 0001:00000000 00001421H .text CODE 0002:00000000 0000012cH .rdata DATA 0002:00000130 00000193H .edata DATA 0003:00000000 00000104H .CRT$XCA DATA 0003:00000104 00000104H .CRT$XCZ DATA 0003:00000208 00000104H .CRT$XIA DATA 0003:0000030c 00000104H .CRT$XIZ DATA 0003:00000410 00000176H .data DATA 0003:00000588 00000030H .bss DATA 0004:00000000 00000014H .idata$2 DATA 0004:00000014 00000014H .idata$3 DATA 0004:00000028 00000050H .idata$4 DATA 0004:00000078 00000050H .idata$5 DATA 0004:000000c8 00000179H .idata$6 DATA Address Publics by Value Rva+Base Lib:Object 0001:00000030 _DllMain@12 03901030 f MapDLL.obj 0001:0000004c ?MapDLLFunction@@YAHXZ 0390104c f MapDLL.obj 0001:00000076 ?MapDLLHappyFunc@@YAPADPAD@Z 03901076 f MapDLL.obj 0001:000000f6 _printf 039010f6 f MSVCRTD:MSVCRTD.dll 0001:000000fc __chkesp 039010fc f MSVCRTD:MSVCRTD.dll 0001:00000110 __CRT_INIT@12 03901110 f MSVCRTD:crtdll.obj 0001:00000220 __DllMainCRTStartup@12 03901220 f MSVCRTD:crtdll.obj 0001:00000314 __free_dbg 03901314 f MSVCRTD:MSVCRTD.dll 0001:0000031a __initterm 0390131a f MSVCRTD:MSVCRTD.dll 0001:00000320 __onexit 03901320 f MSVCRTD:atonexit.obj 0001:00000360 _atexit 03901360 f MSVCRTD:atonexit.obj 0001:00000378 __malloc_dbg 03901378 f MSVCRTD:MSVCRTD.dll 0001:0000037e ___dllonexit 0390137e f MSVCRTD:MSVCRTD.dll 0002:0000001c ??_C@_08JKC@crtdll?4c?$AA@ 0390301c MSVCRTD:crtdll.obj 0003:00000000 ___xc_a 03904000 MSVCRTD:cinitexe.obj 0003:00000104 ___xc_z 03904104 MSVCRTD:cinitexe.obj 0003:00000208 ___xi_a 03904208 MSVCRTD:cinitexe.obj 0003:0000030c ___xi_z 0390430c MSVCRTD:cinitexe.obj 0003:0000058c __adjust_fdiv 0390458c <common> 0003:00000598 ___onexitend 03904598 <common> 0003:000005a8 ___onexitbegin 039045a8 <common> 0003:000005ac __pRawDllMain 039045ac <common> 0004:00000000 __IMPORT_DESCRIPTOR_MSVCRTD 03905000 MSVCRTD:MSVCRTD.dll 0004:00000014 __NULL_IMPORT_DESCRIPTOR 03905014 MSVCRTD:MSVCRTD.dll 0004:00000078 __imp___malloc_dbg 03905078 MSVCRTD:MSVCRTD.dll 0004:0000007c __imp___chkesp 0390507c MSVCRTD:MSVCRTD.dll 0004:00000080 __imp___free_dbg 03905080 MSVCRTD:MSVCRTD.dll 0004:00000084 __imp___initterm 03905084 MSVCRTD:MSVCRTD.dll 0004:00000088 __imp__printf 03905088 MSVCRTD:MSVCRTD.dll 0004:0000008c __imp___adjust_fdiv 0390508c MSVCRTD:MSVCRTD.dll 0004:00000090 __imp____dllonexit 03905090 MSVCRTD:MSVCRTD.dll 0004:00000094 __imp___onexit 03905094 MSVCRTD:MSVCRTD.dll 0004:00000098 \177MSVCRTD_NULL_THUNK_DATA 03905098 MSVCRTD:MSVCRTD.dll entry point at 0001:00000220 Line numbers for .\Debug\MapDLL.obj(D:\MapFile\MapDLL\MapDLL.cpp) segment .text 10 0001:00000030 12 0001:0000003b 19 0001:00000041 20 0001:00000046 24 0001:0000004c 25 0001:00000050 26 0001:00000067 27 0001:0000006c 35 0001:00000076 36 0001:0000007a 37 0001:0000007f 38 0001:00000096 39 0001:0000009c 40 0001:0000009f 30 0001:000000a9 31 0001:000000ad  32 0001:000000c4 Line numbers for g:\vc\LIB\MSVCRTD.lib(atonexit.c) segment .text 84 0001:00000320 89 0001:00000324 98 0001:0000035b 103 0001:00000360 104 0001:00000363 105 0001:00000376 Line numbers for g:\vc\LIB\MSVCRTD.lib(crtdll.c) segment .text 135 0001:00000110 140 0001:00000114 141 0001:0000011a 142 0001:00000123 143 0001:00000130 147 0001:00000132 156 0001:00000139 164 0001:00000147 170 0001:0000014d 175 0001:00000175 177 0001:0000017c 179 0001:00000187 184 0001:00000193 189 0001:000001a5 192 0001:000001b4 219 0001:000001bc 220 0001:000001c5 222 0001:000001cd 227 0001:000001e1 228 0001:000001e9 236 0001:000001ee 238 0001:00000202 242 0001:0000020c 243 0001:00000211 251 0001:00000220 252 0001:00000224 258 0001:0000022b 259 0001:0000023a 261 0001:00000241 263 0001:0000024d 264 0001:00000256 266 0001:0000026b 267 0001:00000271 269 0001:00000285 270 0001:0000028b 273 0001:0000028f 276 0001:000002a3 284 0001:000002af 287 0001:000002be 289 0001:000002ca 290 0001:000002df 292 0001:000002e6 293 0001:000002f5 296 0001:0000030a 297 0001:0000030d Exports ordinal name 1 ?MapDLLFunction@@YAHXZ (int __cdecl MapDLLFunction(void)) 2 ?MapDLLHappyFunc@@YAPADPAD@Z (char * __cdecl MapDLLHappyFunc(char *)) 

Finding the Function, Source File, and Line Number

The algorithm for extracting the function, source file, and line number from a MAP file is straightforward, but you need to do a few hexadecimal calculations when using it. As an example, let's say that a crash in MAPDLL.DLL, the module shown in Listing 8-1, occurs at address 0x03901099.

The first step is to look in your project's MAP files for the file that contains the crash address. First look at the preferred load address and the last address in the public function section. If the crash address is between those values, you're looking at the correct MAP file.

To find the function—or the closest public function if the crash occurred in a C static function—scan down the Rva+Base column until you find the first function address that's greater than the crash address. The preceding entry in the MAP file is the function that had the crash. For example, in Listing 8-1, the first function address greater than the 0x3901099 crash address is 0x39010F6, so the function that crashed is "?MapDLLHappyFunc@@YAPADPAD@Z." Any function name that starts with a question mark is a C++ decorated name. To translate the name, pass it as a command-line parameter to the Platform SDK program UNDNAME.EXE. In the example, "?MapDLLHappyFunc@@YAPADPAD@Z" translates into MapDLLHappyFunc, which you probably could figure out just by looking at the decorated name. Other C++ decorated names are harder to decipher, especially when overloaded functions are used. To find the line number, you get to do a little hexadecimal subtraction using the following formula:

(crash address) _ (preferred load address) _ 0x1000

Remember that the addresses are offsets from the beginning of the first code section, so the formula does that conversion. You can probably guess why you subtract the preferred load address, but you earn extra credit if you know why you still have to subtract 0x1000. The crash address is an offset from the beginning of the code section, but the code section isn't the first part of the binary. The first part of the binary is the PE (Portable Executable) header, which is 0x1000 bytes long.

I'm not sure why the linker still generates MAP files that require this odd calculation. The linker team put in the Rva+Base column a while ago, so I don't see why they didn't just fix up the line number at the same time.

Once you've calculated the offset, look through the MAP file line information until you find the closest number that isn't over the calculated value. Keep in mind that during the generation phase the compiler can jiggle the code around so that the source lines aren't in ascending order. With my crash example, I used the following formula:

0x03901099 _ 0x03900000 _ 0x1000 = 0x99

If you look through the MAP file in Listing 8-1, you'll see that the closest line that isn't over is 38 0001:00000096 (Line 38) in MAPDLL.CPP.

If you're reading a MAP file for a module written in Visual Basic, you need to be aware that the line numbers reported in the MAP file (and in CrashFinder as well) don't correspond to the lines that you see in the Visual Basic editor. The compiled binaries take into account the complete header at the top of the source file, which Visual Basic hides from view. To find the line that the compiler reports, you need to open the Visual Basic file in a text editor, such as the Visual C++ editor, and go to the line listed in the MAP file.



Debugging Applications
Debugging Applications for MicrosoftВ® .NET and Microsoft WindowsВ® (Pro-Developer)
ISBN: 0735615365
EAN: 2147483647
Year: 2000
Pages: 122
Authors: John Robbins

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net