madMapFile Unit

Borland's linker optionally creates a *.map file every time it links a project (a little example map file can be found here). Such a map file contains a lot of useful information. The unit "madMapFile" features functionality to parse those map files, search through the information and export the most important bits of information to (and import from) a compressed and encrypted string stream. A full list of what is contained in this unit can be found in the madMapFile Reference.

The class "TMapFile" handles the most important pieces of information of a map file:

type TMapFile = class;

There are several ways to get a TMapFile instance. The function "FindMapFile" lets you find the map file that belongs to a specific address. This address can be a code address, a data address or a HInstance value. If you just want to get the map file of the current module (application or dll), simply don't enter anything.

"FindMapFile" first checks in which module the specified address is located. Then it checks whether that module file has an appended map file. If not, it looks for a seperate map file with the same name as the module file. The seperate file can either be a compressed and encrypted ".mad" file or the uncompressed plain text ".map" file.

function FindMapFile (addr: pointer = nil) : TMapFile;

// Examples:
mf := FindMapFile;                                          // map file of the current module
mf := FindMapFile(pointer(GetModuleHandle('your.dll'  )));  // map file of "your.dll"
mf := FindMapFile(pointer(GetModuleHandle('user32.dll')));  // no map file will be found...  :-)

The function "LoadMapFile" loads and parses the specified external map file. The map file can either be a compressed and encrypted ".mad" file or the uncompressed plain text ".map" file.

function LoadMapFile (mapFile: string) : TMapFile;

The functions FindMapFile and LoadMapFile can succeed or fail, of course. If they succeed, you get a valid TMapFile instance back. Please do *not* destroy it, it's internally cached to speed things up. madMapFile automatically frees it when your module (exe/dll) is unloaded. If the functions mentioned above fail, you nevertheless get a map file instance back, but this time it's invalid and you are supposed to free it again. You can check the following property to find out whether the returned TMapFile instance is valid or not:

property TMapFile.IsValid : boolean;

// Example:
var mf : TMapFile;
  mf := FindMapFile;
  if mf.IsValid then
    ShowMessage('map file found, do not free it!')
    mf.Free;  // invalid, so we free it

Given a specific address, you can search for all the information that is available for this address. First we have "segments". Each unit can have one or more segments. A segment is either a code segment or a data segment. Then we have "publics", which are variable and function names. Finally we have line number information and the entry point.

// 0001:00000000 00004DBC C=CODE     S=.text    G=(none)   M=System   ACBP=A9
// 0001:00004DBC 00000174 C=CODE     S=.text    G=(none)   M=SysInit  ACBP=A9
// 0001:00004F30 00000A0C C=CODE     S=.text    G=(none)   M=Windows  ACBP=A9

  TMfSegment = record         // Example: see last line from the map file extract above
    IsValid      : boolean;
    Code         : boolean;   // true  (0001 -> Code)
    StartAddress : pointer;   // $00004F30 + BeginOfCode + HInstance
    Length       : dword;     // $00000A0C
    Unit_        : string;    // 'Windows'
    LineNumbers  : boolean;   // does this segment contain line numbers?

//  Address         Publics by Name
// 0001:000334DC       AllocateHWnd
// 0001:0000679C       AllocMem
// 0002:00001420       AllocMemCount

  TMfPublic = record     // Example: see last line from the map file extract above
    IsValid : boolean;
    Code    : boolean;   // false  (0002 -> Data)
    Name    : string;    // 'AllocMemCount'
    Address : pointer;   // $00001420 + BeginOfData + HInstance

// Line numbers for Project1(C:\Example\Project1.dpr) segment .text
//      9 0001:0003D4B8    10 0001:0003D4C8    11 0001:0003D4D4    12 0001:0003D4EC
//     13 0001:0003D4F8

function TMapFile.FindSegment (code: boolean; address: pointer) : TMfSegment;
function TMapFile.FindPublic  (               address: pointer) : TMfPublic;
function TMapFile.FindLine    (               address: pointer) : integer;

// Program entry point at 0001:0003D4B8

property TMapFile.EntryPoint : pointer;

If you need to save the map file in a compressed version to save space, you can use the method "Export". The map file stream coming from the "Export" function is zipped and encrypted. Beginning with madExcept 2.7h you can choose between exporting the map file in a "new" or in an "old" format. The old format is that which was and still is used in all madExcept 2.x builds. The new format is used by madExcept 3.x. If you export with the new format, you can optionally decide to only export minimal debug information. That means, no function names are exported and no line numbers, either.

The parameter "hideUglyItems" lets you specify whether you want functions/methods to be stored even if there's no line number information available for them. Set the parameter to "true" to exclude such functions/methods from being exported.

function TMapFile.Export (newFormat        : boolean;
                             minDebugInfoOnly : boolean;
                             hideUglyItems    : boolean = false) : string;

Some properties to get more informations about the map file, respectively about the module, to which the map file belongs:

property TMapFile.BaseOfCode     : dword;   // start of the code block
property TMapFile.TopOfCode      : dword;   // end   of the code block
property TMapFile.ModuleFileName : string;
property TMapFile.MapFileName    : string;

In order to make your life a bit easier, I've added the function "GetMapFileInfos", which will return all important pieces of information about a specific code address. You could collect the same information by using the TMapFile class, but it would be quite a bit more work.

function GetMapFileInfos (address : pointer;
                          var moduleName : string;
                          var unitName   : string;
                          var publicName : string;
                          var publicAddr : pointer;
                          var line       : integer) : boolean;

If you want to get information about the current function, simply call "GetMyProcName":

function GetMyProcName (includeLineNumber : boolean = false) : string;

// Example:
GetMyProcName -> 'yourUnit.yourFunction (105)'