在解决问题时,如果解是已知的,那么你总会有章可循。
《墨菲定律—精确的法则》
Oracle的.MSDB文件是一种含有错误信息和相应错误编号的二进制文件。本章将与您共同研究它的文件格式,尝试解读其中的原始数据。
虽然Oracle RDBMS提供专门的、文本格式的错误信息文件,但是并非每个.MSB文件里都有相应的、文本格式的错误信息文件,所以有时需要把二进制文件和信息文本进行关联分析。
过滤掉ORAUS.MSG的注释以后,文件开头部分的内容如下所示:
00000, 00000, "normal, successful completion"
00001, 00000, "unique constraint (%s.%s) violated"
00017, 00000, "session requested to set trace event"
00018, 00000, "maximum number of sessions exceeded"
00019, 00000, "maximum number of session licenses exceeded"
00020, 00000, "maximum number of processes (%s) exceeded"
00021, 00000, "session attached to some other process;cannot switch session"
00022, 00000, "invalid session ID; access denied"
00023, 00000, "session references process private memory;cannot detach session"
00024, 00000, "logins from more than one process not allowed in single-process mode"
00025, 00000, "failed to allocate %s"
00026, 00000, "missing or invalid session ID"
00027, 00000, "cannot kill current session"
00028, 00000, "your session has been killed"
00029, 00000, "session is not a user session"
00030, 00000, "User session ID does not exist."
00031, 00000, "session marked for kill"
...
其中,第一个数字是错误编号,第二个数字可能是某种特殊的标识信息。
现在,我们打开ORAUS.MSB的二进制文件,然后找到这些字符串,如图87.1所示。
图87.1 Hiew:第一个消息块
文本字符串之间掺杂着二进制的数据。简单分析之后,可知文件的主体部分可分为多个固定长度的信息块,每个信息块的大小是0x200(512)字节。
首先查看第一个信息块的数据,如图87.2所示。
图87.2 Hiew:第一个消息块
可以看到第一条错误信息的文本内容。此外,我们还注意到错误信息之间没有零字节;也就是说,这些字符串不是以零字节分割的C语言字符串。作为一种替代机制,文件中必须有某个数据记录字符串的长度。
然后我们来找找它的错误代码。参照ORAUS.MSG文件起始部分的错误编号,我们在.msb文件中找到取值为错误编号的几个字节:0,1,17(0x11),18(0x12),19(0x13),20(0x14),21(0x15),22(0x16),23(0x17),24(0x18)……笔者在这个信息块里找到了这些数字,并且在图87.2里用红线标出它们。相邻两个错误代码之间的空间周期是6个字节。这意味着每条错误信息可能都占用6个字节。
第一个16位值(0xA即10)代表着每个信息块包含的错误信息的总数——其他信息块的调查结果印证了这一猜想。稍微想一下就知道错误信息(文本字符串)的长度不会是一致的。这种字符串有长有短,但是信息块的尺寸却是固定的。所以,程序无法事先知道每个信息块装载了多少个文本字符串。
刚才讲过,这些字符串不是以零字节分割的C语言字符串,其字符串长度肯定位于文件中的其他什么地方。字符串“normal,successful completion”有29(0x1D)字节,另一个字符串“unique constraint(%s.%s)violated”有34个(0x22)字节。但是在这个信息块里,我们找不到0x1d或者0x22。
一般来说,Oracle RDBMS应当需要确定每个字符串在信息块之中的相对位置。第一个字符串“normal,successful completion”在整个文件中的绝对位置是0x1444,在信息块中的相对位置是0x44。同理可得第二个字符串“unique constraint(%s.%s)violated”的位置0x1461和0x61。这两个数(0x44和0x61)并不陌生!在信息块中的前几个字节里就有这些数字。
综上所述,我们分析出了各个信息块里6字节非文本信息的格式:
16位错误代码。
16位的零(可能含有其他的标识信息)。
16位的字符串地址信息,用于标记字符串在当前信息块的相对位置。
这些猜想可以被事实验证。在信息块中的最后一个6字节的、错误信息为“dummy”的信息块,它的错误编号为零、起始位置指向最后一个错误信息的最后一个字符之后的位置。或许这个凑数的字符串偏改量用于标记上一个字符串的结束符?至此为止,我们可以根据6字节数据的信息,索引指定的错误编号,从而获取文本字符串的起始位置。我们还知道源程序会根据下一个6字节数据块推算本字符串的文本长度。这样一来,我们可以确定字符串的界限。这种文件格式不必存储字符串的长度,因而十分节省空间。我们可能无法判断它最终能压缩多少文件空间,但是这无疑是一种不错的思路。
此后,我们返回来分析.MSB文件的文件头信息。信息的总数如图87.3中红线部分所示。
图87.3 Hiew:文件头
对其他.MSB文件进行了验证之后,我确信所有的推测都准确无误。虽然文件头中还富含其他信息,但是我们的目标(供调试工具调用)已经达成,故而本文不再分析那些数据。除非我们要编写一个.MSB文件的封装程序,否则就不需要理解其他数据的用途。
如图87.4所示,在文件头之后还有一个16位数值构成的数据表。
图87.4 Hiew:last_errnos表
如图87.4中的红线所示,这些数据的数据宽度可以直接观察出来。在导出这些数据时,我发现这些16位数据就是每个信息块里最后一个错误信息的错误编号。
可见,Oracle RDBMS通过这部分数据进行快速检索的过程大体如下:
加载last_errnos(随便起的名字)数据表。这个数据表包含每个信息块里的错误信息总数。
依据错误编号找到相应的信息块。此处假设各信息块中的信息以错误代码的增序排列。
加载相应的信息块。
逐一检索6字节的索引信息。
通过当前6字节数据找到字符串的第一个字符的位置。
通过下一个6字节数据找到最后一个字符的位置。
加载这个区间之内的全部字符。
我编写了一个展开.MSB信息的C语言程序,有兴趣的读者可通过下述网址下载:http://beginners. re/examples/oracle/MSB/oracle_msb.c
。
本例还用到了Oracle RDBMS 11.1.06的两个文件,如需下载请访问:
go.yurichev.com/17214。
go.yurichev.com/17215。
对于现在的计算机系统来说,本章介绍的这种方法可能已经落伍了。恐怕只有那些在20世纪80年代中期做过大型工程、时刻讲究内存和磁盘的利用效率的老古董才会制定这样严谨的文件格式。无论怎样,这部分内容颇具代表性。我们可以在不分析Oracle RDBMS代码的前提下理解它的专用文件的文件格式。