What can you learn from a map file

Mik 25/01/98

Basically, map file - generated by project/options/link/map-publics - reports three things:

1) Segment

All units exist in the project. Size of each. Where they are stored. In 16 bit version of Delphi, the addresses are in Seg:offset format, it look like this 1AFC:24F6 and size looks like this 6B5CH. H stands for Hex This section is the first in the map file. Typically in Delphi I, it is as follows:

Start

Length

Name

Class

0004:0002

6B5CH

Controls

CODE

0005:0002

7790H

Forms

CODE

0006:0002

5075H

Classes

CODE

0007:0002

2242H

SysUtils

CODE

0007:2244

104EH

System

CODE

0008:0000

0F6CH

DATA

DATA

As you can see, the Start column indicate where the unit are stored in the exe file. The length is the actual size of each unit in the total size of the exe file. Note that the start address of one unit is equal the start of previous unit plus the length of it. Ex. As you can see the start address of Forms unit is 0005:0002 = (controls) 0004:0002 + 6B5C. The last coloumn is the type. The unit names indicate that they are code or in code segment. The last row is Data which locates in Data segment. From this look we can say the size of the global data is F6C.

Similarly, the 32 bit map file generated from Delphi 3 has same meaning, just slightly different in format.

Start Length Name Class

0001:00000000 000433C0H .text CODE

0002:00000000 00000ED8H .data DATA

0002:00000ED8 00000761H .bss BSS

 

Detailed map of segments

0001:00000000 00004460 C=CODE S=.text G=(none) M=System ACBP=A9

0001:00004460 00000164 C=CODE S=.text G=(none) M=SysInit ACBP=A9

0001:000045C4 000009D8 C=CODE S=.text G=(none) M=Windows ACBP=A9

0001:00004F9C 00000038 C=CODE S=.text G=(none) M=Messages ACBP=A9

0001:00004FD4 00004E70 C=CODE S=.text G=(none) M=SysUtils ACBP=A9

.....

0002:00000000 0000009E C=DATA S=.data G=DGROUP M=System ACBP=A9

0002:000000A0 00000010 C=DATA S=.data G=DGROUP M=SysInit ACBP=A9

The addresses presented in 32 bit format, each takes eight digits after the colon. However, we can still find the start address, the length, name of unit and segment either code or data.

At first look, I can tell the benefit of this section is to find out what are units used in this project. For a small project, this may not be a issue, but consider a 1M LOC project, a unit is called by others in a mystery way and very hard to find out why it still exists and adds its size into the project.

Another example is when I decide not to use a unit in this project where still to use it in other projects. I remember that unit was called in several places. I then go to those to remove the uses section and also the related codes. After recompiled, map file will tell me that my memory is not good enough. That unit is still there. Without map file, only one way to do so it to delete all dcu files, and then suffer big time to recompile project again. The map file can also let me know the size of each unit. In 16 bit, if the size of a unit go near 65K, I got to find a way to split it into tow smaller unit to reduce swapping.

 

2) Address

In Delphi I, the address section looks like this

Address Publics by Value

0001:0002 @

0001:26BF @

0007:0824 StrECopy

0007:0847 StrLCopy

0007:086F StrPCopy

0007:088D StrPLCopy

................

0008:0DC4 LongMonthNames

0008:0E84 ShortDayNames

0008:0EBC LongDayNames

There is only two columns, the first one is the segment address where the function or variable located and next is the name of it. In the table of previous section, we know that Data segment starts at address 0008:0000H and it is the last segment in the table. Therefore, all of the names we find with the address starts from 0008 forward are all variables. Again, the start address minus the previous start address is the size of the variable. Note that all variables appear in this section are global variable. They occupy a static size of the exe file. Unless people can prove otherwise, the big size of global var always shows a poor coding approach which leads to several practical problems: the size of the program is big. Global data is hard to maintain. Something can change it during the flow. The unit has the global data will be used by all other units that refer to global data. If the global data unit refer to other units, this makes a spaghetti link. Although it is not considered relevant, Dos overlay will make lot swapping with large global data segment.

We can also find the export functions or procedures of other units by checking the address against the start address in table of segmen section. The StrLCopy from address 0007:0847 belongs to segment 0007:0002 of unit Sysutils. Instead of trying to open all the source codes to find out where a function is implemented. Map file can report such unit quickly. By refer to the size of each function, we can optimize that function to reduce the size. I found that a function which was written for a specific task can be optimized several times to reach the best state of it. Consider the Opstring unit from Turbo Power - in several versions of it- many of the functions in this unit have been rewritten to improve speed and size.

The address segment of map file in Delphi 3 is very much similar to Delphi I. It has two columns: first is address, second is the name. There is only one thing different that it is sorted on name in alphabetic order where it is sorted in address order in Delphi I. As you can see, the Delphi I version has only " Address Publics by Value" and Delphi 3 has both "Address Publics by Name" and " Address Publics by Value". Therefore, the approach we used to calculate the size of each procedure in Delphi I is not applicable for the public by name section.

Address Publics by Name

0001:00008FE4 AnsiLowerCaseFileName

0001:00008FAC AnsiPos

0001:00005D14 AnsiStrLIComp

0001:00009070 AnsiStrPos

0001:00009110 AnsiStrRScan

0001:00009138 AnsiStrScan

0001:00005C7C AnsiUpperCase

0002:000016AC Application

0002:00001014 AssertErrorProc

0002:00000C6C BadDate2

0002:00000C74 BadDateStr

0002:00000C60 BadFloat

Delphi 3 reports the PUBLIC methods of each class used in the project. We can see some methods can be easily moved to private section of the class to save the project from export pointers.

0001:0000C1C0 TBits.Destroy

0001:0000C31C TBits.GetBit

0001:0000C334 TBits.OpenBit

0001:0000C2F0 TBits.SetBit

0001:0000C24C TBits.SetSize

The third section is not very helpful and we will skip it for now.