http://imPRove.dk/reverse-engineering-sql-server-page-headers/
在開發(fā)OrcaMDF 的時候第一個挑戰(zhàn)就是解析數(shù)據(jù)頁面頭部,我們知道數(shù)據(jù)頁面分兩部分,96字節(jié)的頁面頭部和8096字節(jié)的數(shù)據(jù)行
大神Paul Randal 寫了一篇文章很好的描述了頁頭結(jié)構(gòu),然而,即使文章描述得很詳細,但是我還是找不出任何關(guān)于頁頭存儲的格式
每一個字段的數(shù)據(jù)類型和他們的順序
我們可以使用DBCC PAGE命令,我填充一些隨機數(shù)據(jù)進去數(shù)據(jù)頁面,然后把頁面dump出來
頁面號是(1:101):
DBCC TRACEON (3604)DBCC PAGE (TextTest, 1, 101, 2)
結(jié)果分兩部分,首先,我們獲得DBCC PAGE已經(jīng)格式化好的頁面內(nèi)容,dump出來的內(nèi)容的第二部分是96字節(jié)的頁面頭
開始動手了,我們需要找出頁面頭部的這些數(shù)據(jù)值對應(yīng)的數(shù)據(jù)類型是什么
為了簡單,我們需要關(guān)注一些唯一值以便我們不會獲取到某些存在含糊的數(shù)值
我們從m_freeCnt這個字段開始,我們看到m_freeCnt的值是4066,而數(shù)據(jù)行大小是8060,所以很明顯,m_freeCnt的數(shù)據(jù)類型不可能是tinyint
m_freeCnt不太可能使用int類型,一種有依據(jù)的猜測是m_freeCnt有可能使用smallint類型,這讓數(shù)據(jù)行足夠容納0-8060 字節(jié)空間的數(shù)據(jù)
smallint:從-2^15(-32,768)到2^15-1(32,767)的整數(shù)數(shù)據(jù)存儲大小為 2 個字節(jié),本人也覺得m_freeCnt這個字段的值不可能太大
現(xiàn)在,4066這個十進制數(shù)換成十六進制是0x0FE2. 字節(jié)交換,變成0xE20F,現(xiàn)在我們知道,我們已經(jīng)匹配到m_freeCnt的數(shù)據(jù)類型了
另外,我們已經(jīng)知道頁頭里面第一個字段的數(shù)據(jù)類型和位置
/* Bytes Content ----- ------- 00-27 ? 28-29 FreeCnt (smallint) 30-95 ?*/
繼續(xù)我們的查找,我們看到m_freeData =3895,換算成十六進制是0x0F37 字節(jié)交換后0x370F
我們發(fā)現(xiàn)m_freeCnt這個字段存儲在m_freeCnt的后面
使用這個技巧,我們能匹配存儲在頁頭的并且沒有含糊的唯一數(shù)據(jù)值
不過 ,對于m_level這個字段,他跟m_xactReserved字段,m_reservedCnt字段,m_GhostRecCnt字段的值是一樣的
我們怎麼知道0這個值哪個才屬于m_level字段? 并且我們怎麼找出他的數(shù)據(jù)類型呢?這有可能是tinyint 到bigint類型
我們請出Visual Studio大神,然后shutdown SQLSERVER
把mdf文件拖入VS,VS會打開hex編輯器,我們根據(jù)頁面偏移算出頁面位置
101 * 8192 = 827,392
看著紅色框給我們標出的字節(jié)內(nèi)容,他已經(jīng)標識出我們的頁面頭內(nèi)容,并且確定了我們已經(jīng)跳轉(zhuǎn)到正確的位置
現(xiàn)在我們會填一些數(shù)值進去mdf文件里面然后保存文件,請不要胡亂在生產(chǎn)數(shù)據(jù)庫上進行測試
前
后
現(xiàn)在我們啟動SQLSERVER,然后再次運行DBCC PAGE命令
DBCC TRACEON (3604)DBCC PAGE (TextTest, 1, 101, 2)
可以注意到,現(xiàn)在頁面頭變成了這樣
有幾個數(shù)值變了,m_xactReserved 字段先前的數(shù)值是0,現(xiàn)在變成了30806,將這個數(shù)字轉(zhuǎn)換成十六進制并進行字節(jié)交換得到0x5678
看一下頁面頭,現(xiàn)在我們已經(jīng)識別出另外一個字段的值和數(shù)據(jù)類型(smallint)
我們更新一下我們頁頭表格
/* Bytes Content ----- ------- 00-27 ? 28-29 FreeCnt (smallint) 30-49 ? 50-51 XactReserved (smallint) 30-95 ?*/
沿著這種方法繼續(xù),把頁頭進行混亂修改,將修改后的頁頭和DBCC PAGE的輸出進行關(guān)聯(lián),有可能找出這些字段的數(shù)據(jù)類型
如果你看到下面的消息,你就知道已經(jīng)把頁面頭部搞混亂了
你應(yīng)該覺得自豪的,沒有人能修好你胡亂修改出來的錯誤
我已經(jīng)編好了一個頁頭結(jié)構(gòu)表
/* Bytes Content ----- ------- 00 HeaderVersion (tinyint) 01 Type (tinyint) 02 TypeFlagBits (tinyint) 03 Level (tinyint) 04-05 FlagBits (smallint) 06-07 IndexID (smallint) 08-11 PreviousPageID (int) 12-13 PreviousFileID (smallint) 14-15 Pminlen (smallint) 16-19 NextPageID (int) 20-21 NextPageFileID (smallint) 22-23 SlotCnt (smallint) 24-27 ObjectID (int) 28-29 FreeCnt (smallint) 30-31 FreeData (smallint) 32-35 PageID (int) 36-37 FileID (smallint) 38-39 ReservedCnt (smallint) 40-43 Lsn1 (int) 44-47 Lsn2 (int) 48-49 Lsn3 (smallint) 50-51 XactReserved (smallint) 52-55 XdesIDPart2 (int) 56-57 XdesIDPart1 (smallint) 58-59 GhostRecCnt (smallint) 60-95 ?*/
我不確定頁頭的其他字節(jié)跟DBCC PAGE輸出的字段對應(yīng)關(guān)系,我測試過的所有頁面這些字節(jié)似乎都存儲為0
我認為這些應(yīng)該都是為將來某種用途使用的保留字節(jié)。好了, 我們已經(jīng)獲得頁頭格式,讀取每個字段就很簡單了
HeaderVersion = header[0];Type = (PageType)header[1];TypeFlagBits = header[2];Level = header[3];FlagBits = BitConverter.ToInt16(header, 4);IndexID = BitConverter.ToInt16(header, 6);PreviousPage = new PagePointer(BitConverter.ToInt16(header, 12), BitConverter.ToInt32(header, 8));Pminlen = BitConverter.ToInt16(header, 14);NextPage = new PagePointer(BitConverter.ToInt16(header, 20), BitConverter.ToInt32(header, 16));SlotCnt = BitConverter.ToInt16(header, 22);ObjectID = BitConverter.ToInt32(header, 24);FreeCnt = BitConverter.ToInt16(header, 28);FreeData = BitConverter.ToInt16(header, 30);Pointer = new PagePointer(BitConverter.ToInt16(header, 36), BitConverter.ToInt32(header, 32));ReservedCnt = BitConverter.ToInt16(header, 38);Lsn = "(" + BitConverter.ToInt32(header, 40) + ":" + BitConverter.ToInt32(header, 44) + ":" + BitConverter.ToInt16(header, 48) + ")";XactReserved = BitConverter.ToInt16(header, 50);XdesID = "(" + BitConverter.ToInt16(header, 56) + ":" + BitConverter.ToInt32(header, 52) + ")";GhostRecCnt = BitConverter.ToInt16(header, 58);
大家可以看一下我寫的pageheader類
using System;using System.Text;namespace OrcaMDF.Core.Engine.Pages{public class PageHeader{public short FreeCnt { get; private set; }public short FreeData { get; private set; }public short FlagBits { get; private set; }public string Lsn { get; private set; }public int ObjectID { get; private set; }public PageType Type { get; private set; }public short Pminlen { get; private set; }public short IndexID { get; private set; }public byte TypeFlagBits { get; private set; }public short SlotCnt { get; private set; }public string XdesID { get; private set; }public short XactReserved { get; private set; }public short ReservedCnt { get; private set; }public byte Level { get; private set; }public byte HeaderVersion { get; private set; }public short GhostRecCnt { get; private set; }public PagePointer NextPage { get; private set; }public PagePointer PreviousPage { get; private set; }public PagePointer Pointer { get; private set; }public PageHeader(byte[] header){if (header.Length != 96)throw new ArgumentException("Header length must be 96.");/* Bytes Content ----- ------- 00 HeaderVersion (tinyint) 01 Type (tinyint) 02 TypeFlagBits (tinyint) 03 Level (tinyint) 04-05 FlagBits (smallint) 06-07 IndexID (smallint) 08-11 PreviousPageID (int) 12-13 PreviousFileID (smallint) 14-15 Pminlen (smallint) 16-19 NextPageID (int) 20-21 NextPageFileID (smallint) 22-23 SlotCnt (smallint) 24-27 ObjectID (int) 28-29 FreeCnt (smallint) 30-31 FreeData (smallint) 32-35 PageID (int) 36-37 FileID (smallint) 38-39 ReservedCnt (smallint) 40-43 Lsn1 (int) 44-47 Lsn2 (int) 48-49 Lsn3 (smallint) 50-51 XactReserved (smallint) 52-55 XdesIDPart2 (int) 56-57 XdesIDPart1 (smallint) 58-59 GhostRecCnt (smallint) 60-63 Checksum/Tornbits (int) 64-95 ? */HeaderVersion = header[0];Type = (PageType)header[1];TypeFlagBits = header[2];Level = header[3];FlagBits = BitConverter.ToInt16(header, 4);IndexID = BitConverter.ToInt16(header, 6);PreviousPage = new PagePointer(BitConverter.ToInt16(header, 12), BitConverter.ToInt32(header, 8));Pminlen = BitConverter.ToInt16(header, 14);NextPage = new PagePointer(BitConverter.ToInt16(header, 20), BitConverter.ToInt32(header, 16));SlotCnt = BitConverter.ToInt16(header, 22);ObjectID = BitConverter.ToInt32(header, 24);FreeCnt = BitConverter.ToInt16(header, 28);FreeData = BitConverter.ToInt16(header, 30);Pointer = new PagePointer(BitConverter.ToInt16(header, 36), BitConverter.ToInt32(header, 32));ReservedCnt = BitConverter.ToInt16(header, 38);Lsn = "(" + BitConverter.ToInt32(header, 40) + ":" + BitConverter.ToInt32(header, 44) + ":" + BitConverter.ToInt16(header, 48) + ")";XactReserved = BitConverter.ToInt16(header
新聞熱點
疑難解答
圖片精選