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.