乐者为王

Do one thing, and do it well.

PBL文件格式解析

PBL文件是PowerBuilder库文件,在其中存储了应用程序所使用到的所有系统对象和用户自定义对象的集合,同时PBL文件中还存储了源代码控制信息(Source Code Control,简称SCC)。对其文件格式的研究,可以准确地了解程序结构并能对PBL文件中的对象进行修改,同时也有利于库文件的修复,程序动态执行等方面的工作。

PBL文件的存储结构

PBL文件存储信息时是以块(Block)为单位为对象分配存储空间的,每个块的大小固定为512字节,块号从0开始计算,块号与块首字节的偏移地址有如下关系:

1
块号 = 块首字节的偏移地址 / 512

整个PBL文件由Header块、Bitmap块、Node块、Data块组成。其中除Header块外,其它块均以链表结构组织,其中Data块是Node块中Entry表项的具体内容,是从属于Node块的。下图说明了这些块的关系。

pbl-datastruct

图中Header块、首个Bitmap块及首个Node块在存储空间上是相邻的,其中Node块比较特别,占6个块共3072字节,其余块只占512字节,其空间大小及起始地址如下表所示:

块号 地址范围 描述
0 0000-01FF Header块
1 0200-03FF 首个Bitmap块
2-7 0400-0FFF 首个Node块

Header块解析

Header块是整个PBL的描述信息,它包含了PBL的版本标志,库注释,首个SCC数据块的偏移地址等信息。具体内容如下表所示:

块内地址范围 所占字节 数据类型 描述
0000-0003 4 char 'HDR*'
0004-0011 14 char 'PowerBuilder' + 0x00 + 0x00
0012-0015 4 char PBL格式版本(如0900表示9.0版本)
0016-0019 4 long 创建/修改日期时间
001A-001B 2 byte 保留
001C-011B 256 char 库注释
011C-011F 4 long 首个SCC数据块的偏移地址
0120-0123 4 long SCC数据块实际大小
0124-01FF 220 byte 保留

Bitmap块解析

Bitmap块中存放的是表示PBL文件存储空间的使用情况。该块数据结构如下表所示:

块内地址范围 所占字节 数据类型 描述
0000-0003 4 char 'FRE*'
0004-0007 4 long 下一个Bitmap块的偏移地址或0
0008-01FF 504 bit 位图(每个位标识一个块)

由上表可知,包含一个Bitmap块的PBL文件最多可使用504 * 8 = 4032个块。当文件空间超过4032个块时,就需要使用第二个Bitmap块,它的偏移地址由当前Bitmap块块内偏移0004-0007处的值表示。如果是最后一个Bitmap块,则对应的字节处为00 00 00 00,即偏移地址为0。这样就形成了Bitmap块的单向链表。

bitmap-block-chain

位图用于标识块的使用/空闲情况。在位图中为1的位,表示与该位序号对应的块已被使用;反之,表示对应块未使用。例如FF FF 40 00还原为位图则为11111111 11111111 01000000 00000000,该位图表示PBL文件共有18个块,其中的第16号块空闲未使用。

注意:在实际分析多个PBL文件后发现,位图中的位并不能真实反映对应块的空闲/使用情况,只是记录PBL文件使用了多少个块。

Node块解析

Node块是目录块,主要用于存放Entry目录表项。下表是Node块的数据结构:

块内地址范围 所占字节 数据类型 描述
0000-0003 4 char 'NOD*'
0004-0007 4 long 左Node块的偏移地址或0
0008-001B 4 long 父Node块的偏移地址或0
000C-000F 4 long 右Node块的偏移地址或0
0010-0011 2 short 块内可用空间(初始值3040)
0012-0013 2 short 按字母顺序最后一个对象名的位置
0014-0015 2 short 该Node块中的Entry表项数
0016-0017 2 short 按字母顺序第一个对象名的位置
0018-001F 8 char 保留
0020-00BF 3040 chunks Entry目录表

其中Entry目录表是顺序表。当一个Node块的空间不足以存储所有Entry表项时,可以再使用一个Node块来存储,并且Entry表项不能跨Node块存储,因此Node块中的空间不能完全利用,会有一定的剩余,这个值记录在块内偏移0010-0011处。

Node块的链接方式有些复杂,它使用一种称之为三叉链表(节点包含四个域:数据域、左指针域、右指针域、父指针域)的链式存储结构把所有Node块组织成为一颗二叉树,这可能是PowerBuilder为了提高查找速度而做的一些优化吧。

node-block-tree

Entry表项解析

每个Entry表项对应于一个对象的源代码或PCODE的描述信息,因此Entry目录表就是整个库中各个对象的索引表,存储了各对象的索引信息。例如,在编程中创建一个名为“pbltest”的Window对象类型,那么在Entry目录表中要存放该对象的两个索引表项,分别为“pbltest.srw”用于存储源代码,“pbltest.win”用于存储PCODE。在Entry目录表中存储的对象有以下这些:

对象类型 源代码后缀 PCODE后辍
Application sra apl
Window srw win
DataWindow srd dwo
Menu srm men
Function srf fun
Query srq -
Structure srs str
User object sru udo
Pipeline srp -
Project srj -
? - pra
? - prp

Entry表项的具体数据结构如下表所示:

块内地址范围 所占字节 数据类型 描述
0000-0003 4 char 'ENT*'
0004-0007 4 char PBL格式版本(如0900表示9.0版本)
0008-000B 4 long 首个Data块的偏移地址
000C-000F 4 long 对象的实际大小
0010-0013 4 long 对象的创建/修改日期时间
0014-0015 2 short 对象的注释长度
0016-0017 2 short 对象名的长度
0018-???? ? char 对象名 + 0x00

这里需要说明的是,每个Entry表项的长度并不是固定的,它随着对象名的长度变化而变化,所以要读取下一个Entry表项,只能通过计算上一个Entry表项的长度即24 + 对象名长度来得 到,或者通过搜索下一个ENT*得到。

Data块解析

在Entry目录表中的各对象的实际数据内容是存储在Data块中的。Data块的数据结构如下表所示:

块内地址范围 所占字节 数据类型 描述
0000-0003 4 char 'DAT*'
0004-0007 4 char 下一个Data块的偏移地址或0
0008-0009 2 short 本块内存储的数据的实际长度
0010-01FF 502 char 对象的实际数据

由上表可知,若对象的数据内容在502字节以上时,就需用多个Data块存放,这些Data块形成一个单向链表。链表的最后一个Data块的0004-0007中存储的偏移地址为00 00 00 00,表示链表结束。0010-01FF处存放的是对象的实际数据,只有最后一个Data块的长度有可能小于502,且以0x00字节表示结束。

根据上面对PBL文件格式的解析,使用Ruby开发了一个小工具,用来输出PBL文件中存储的各种信息。源代码被放在GitHub上面,供大家参考。

代码下载:https://github.com/dohkoos/pblanalyzer

Comments