Tuesday, June 1, 2010

Converting a physical address into the source code line: how to read a MAP file

In the previous note, the C++ crash handler, I described how to obtain the physical address of an hard crash and gracefully terminate the program. In this note I'll try to recap the steps needed to convert the physical address of the crash to the related source code line.



The first step is to re-build your source code to generate the MAP file. In MS VC++6 go to Project Settings, open the Link tab and enable the Generate mapfile option. In the Project Options below add the switches /MAPINFO:EXPORTS /MAPINFO:LINES. When you rebuild the program, the linker will add a .map file; in our example the file is the CRUnitTests.map. We'll refer to this easy example to understand the logic behind the map file.

To convert the physical address into the source line we need to:
  1. open the map file with a text editor (I like VIM!)
  2. check if the map file is correct
  3. identify the source file and the function name where the crash occurred
  4. find the line number within the source file
1.
The map file contains many info about the program. However what we really need to note are:
- the Preferred load address
- the public function information section that shows Address, Public by Balue, Rva+Base, Lib:Object. The Rva+Base is the starting address of the function
- and the line information of each file. That section starts with something like "Line numbers for .cpp" and is generated only if the /MAPINFO switches are set.

2.
To validate the map file correctness you need to check that the crash address (in our example 0x00401602) is within the preffered load address (0x00400000) and the last Rva+Base address in the public function section (0x0047f324). Tip: to quickly go down to the end of the public function section search the string "entry point at". In our example the crash address 0x00401602 is within 0x00400000 and 0x0047f324, so the map file is correct.

3.
To find the file where the crash occurred, scan down the Rva+Base column of the public function section until you find the first function address that is greater then the crash address. The preceeding entry is the function that crashed. Our example is so small that the identification is very easy:

Address Publics by Value Rva+Base Lib:Object
0001:000005a0 _main 004015a0 f CRUnitTests.obj
0001:000008b0 ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@P6AAAV01@AAV01@@Z@Z 004018b0 f i CRUnitTests.obj
 
The crash occurred in the CRUnitTests file, in the function main because our crash address is before address 0x004018b0.
 
4.
To find the line number where the crash occurred we need to calculate the address as following:
 
target = crash address - preferred load address - 0x1000
 
In our example the target is:
0x00401602 - 0x00400000 - 0x1000 = 0x602
Now search the line numbers section for the crunittests.cpp file:
 
Line numbers for .\CRUnitTests.obj(c:\crashhandler\crunittests.cpp) segment .text
5 0001:000005a0 7 0001:000005d1 10 0001:000005df 11 0001:000005e6
12 0001:000005ed 13 0001:00000607 16 0001:00000625 20 0001:0000062a

22 0001:00000707 23 0001:00000725 27 0001:0000072b 28 0001:000007c2
30 0001:000007e0 33 0001:000007e6 34 0001:000007ef

Find the closest address that isn't over the calculated target (0x602). The closest address in this case is 000005ed that is mapped in the source file at line 12. If you check the source code, you should find that the division by 0 simulation is exactly at line 12.

This procedure works but it's pretty tedious. Next step is to enhance the CrashHandler dll to directly return the source line address instead of the physical address. Don't miss the next post!