Delphi/C++ Builder Map文件格式解析

【转载请注明出处及作者,文章发现问题会随时更新,要保证浏览最新版本,请访问官网:blog.qdac.cc】

Delphi或C++ Builder在编译程序链接时,可以选择生成扩展名为.map的文件符号映射文件,这个文件记录了程序中各个函数、变量的地址等信息,我们今天要做的就是解析这个文件的格式,以便在QWorker中,能够实时跟踪当前正在执行的作业函数。

首先,我们来了解一下如果让编译器生成map文件:

在工程选项里(Project->Options->Delphi Compiler->Linking),将其中的 Map file generate 设置为Detail模式,以便生成我们需要的符号映射文件。

QQ截图20140926103358

然后我们了详细了解所生成的文件格式:

一、段定义表

Start         Length     Name                   Class
 0001:00401000 00227CC8H .text                   CODE(代码段)
 0002:00629000 00001728H .itext                  ICODE(导入代码段)
 0003:0062B000 00017AC4H .data                   DATA(数据库)
 0004:00643000 00005860H .bss                    BSS(块起始偏移,Block Start Segment缩写)
 0005:00000000 00000040H .tls                    TLS(线程局部存贮,Thread Local Storage缩写)
 0006:00400000 00000000H .pdata                  PDATA

此部分描述了各个段的索引号、起始地址、长度、名称和类型信息。我们要查找函数的地址和代码行号等信息,只需要关心第一个代码段的(CODE)内容就可以。

我们可以看到这个映射文件中,序号是0001,后面我们只关心它这一类型的定义。代码段的起始偏移地址是0x00401000,其长度是0x00227CC8(2260168)。

二、段详细映射表

这个表标明了段的详细分配情况,依次是段类别序号:段内偏移、长度、类型、段名称、分组(?不确定)、命名空间,ACBP不清楚是什么

Detailed map of segments

 0001:00000000 0000D0A0 C=CODE     S=.text    G=(none)   M=System   ACBP=A9
 0001:0000D0A0 00000734 C=CODE     S=.text    G=(none)   M=SysInit  ACBP=A9
 0001:0000D7D4 00001A8C C=CODE     S=.text    G=(none)   M=System.Types ACBP=A9
 0001:0000F260 00000768 C=CODE     S=.text    G=(none)   M=System.UITypes ACBP=A9
 0001:0000F9C8 00001C88 C=CODE     S=.text    G=(none)   M=Winapi.Windows ACBP=A9
 0001:00011650 00000330 C=CODE     S=.text    G=(none)   M=Winapi.Messages ACBP=A9
 0001:00011980 00000390 C=CODE     S=.text    G=(none)   M=System.SysConst ACBP=A9
 0001:00011D10 00000320 C=CODE     S=.text    G=(none)   M=System.RTLConsts ACBP=A9

通过段起始偏移+段内偏移,可以很容易看到相应部分代码所隶属的命名空间,反过来也可以很容易的找到命名空间对应的代码范围。在我们的需求中,这段没有使用。

三、函数映射表

这个表记录了每个函数的地址偏移情况,可以根据指定的函数指针地址或EIP寄存器的值,得到其对应的函数名称:

Address             Publics by Name

 0001:0021E30C       main..TAutoFreeTestObject
 0001:0021CFE0       main..TForm1
 0001:0021EE1C       main..TForm1.Button20Click$15$ActRec
 0001:0021F4C4       main..TForm1.Button31Click$30$ActRec
 0003:00017374       main.ACount
 0001:0021F37C       main.DoFreeJobDataC1
 0001:0021EA90       main.DoGlobalJob
 0004:0000585C       main.Form1
 0001:0021F874       main.RunWithPoster

这个只包含两列:地址(段序号:段内偏移地址)、函数名(Publics by Name),解析起来很简单。

四、代码行映射表

这个表记录了每一行对应的代码地址偏移,其行标题指名了其命名空间和文件名。同一命名空间,如果位于不同文件中,会被分成不行的列表。我们在QMapSymbols实现时,没有使用前面段详细信息映射表,直接解析了此处的内容,得到命名空间和源码所在的文件名。

Line numbers for qstring(qstring.pas) segment .text

   585 0001:001DA8E4   586 0001:001DA8EB   587 0001:001DA8F6   588 0001:001DA915
   589 0001:001DA920   591 0001:001DA92C   592 0001:001DA933   597 0001:001DA938
   598 0001:001DA958   599 0001:001DA966   600 0001:001DA96A   603 0001:001DA98E

这个映射表每项实际由两部分构成:行号、地址(段序号:段内偏移),解析时注意这个映射表有很多,每个源码+命名空间组合就是一个。

【附注】

我们忽略了三个部分的说明:

1、Publics by value小节的内容的解析,那里记录的是全局变量等的映射信息,我们用不到,所以没有使用。

2、Bound resource files是绑定的资源文件信息,我们用不到,所以也就忽略未曾添加。

3、Program entry point at 是程序入口地址,我们也用不到,忽略。

【地址换算】

注意计算实际程序时址时,要加上一个起始偏移量的,如

QQ截图20140924154616

图中的代码地址是0x006206CB,转换成Map文件中的地址要减去第一个段中声明的代码段的起始偏移量0x00401000,得到的结果是0x21F6CB,然后我们在Map文件中找这个地址,可以得出结果为:

分享到: