【转载请注明出处及作者,文章发现问题会随时更新,要保证浏览最新版本,请访问官网:blog.qdac.cc】
Delphi或C++ Builder在编译程序链接时,可以选择生成扩展名为.map的文件符号映射文件,这个文件记录了程序中各个函数、变量的地址等信息,我们今天要做的就是解析这个文件的格式,以便在QWorker中,能够实时跟踪当前正在执行的作业函数。
首先,我们来了解一下如果让编译器生成map文件:
在工程选项里(Project->Options->Delphi Compiler->Linking),将其中的 Map file generate 设置为Detail模式,以便生成我们需要的符号映射文件。
然后我们了详细了解所生成的文件格式:
一、段定义表
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 是程序入口地址,我们也用不到,忽略。
【地址换算】
注意计算实际程序时址时,要加上一个起始偏移量的,如
图中的代码地址是0x006206CB,转换成Map文件中的地址要减去第一个段中声明的代码段的起始偏移量0x00401000,得到的结果是0x21F6CB,然后我们在Map文件中找这个地址,可以得出结果为: