sqlite源码分析之路(二) 数据库文件格式探索(1) 数据库文件头分析

26 篇文章 2 订阅
9 篇文章 1 订阅

引入

要深入了解sqlite数据库,数据库文件的存储格式必须熟悉。

在分析sqlite数据库文件时,选择用php做分析工具。在做文本分析方面,php差不多是最方便的工具了,当然用c语言也可以,不过c语言擅长的是高效的执行,在处理文本串时,就显得太烦琐了。
php真是一个好东西,好多人只把它当作一个编写网页的工具。其实它在命令行工具里面也很出色,比起perl,tcl来说,优点更突出。这次选php来分析sqlite数据库文件的格式,就是看中了它的简单易用,功能强大。准备用这把瑞士军刀好好地剖析一下sqlite的数据库文件格式。

sqlite官网上文件格式的描述文档,很详细,英文好的朋友可以细看。
网上“空转”先生的一篇文章《SQLite数据库文件格式全面分析》,写得不错,是用中文写的。需要的朋友可以在百度文库中搜一下。
”空转“的文章给我很大的启发,他用了实际的例子做引导,很对我的胃口。他的文章主要是用16进制文件编辑器来对文件格式做说明,我则使用php对sqlite的数据库文件做更细致地分析。

本系列探索sqlite数据库文件格式,本篇文章用于分析数据库文件的前100个字节的文件头。sqlite官网上有详细说明,我引用sqlite源文件btreeInt.h中的相关说明部分如下:

** The first page is always a btree page.  The first 100 bytes of the first
** page contain a special header (the "file header") that describes the file.
** The format of the file header is as follows:
**
**   OFFSET   SIZE    DESCRIPTION
**      0      16     Header string: "SQLite format 3\000"
**     16       2     Page size in bytes.  (1 means 65536)
**     18       1     File format write version
**     19       1     File format read version
**     20       1     Bytes of unused space at the end of each page
**     21       1     Max embedded payload fraction (must be 64)
**     22       1     Min embedded payload fraction (must be 32)
**     23       1     Min leaf payload fraction (must be 32)
**     24       4     File change counter
**     28       4     Reserved for future use
**     32       4     First freelist page
**     36       4     Number of freelist pages in the file
**     40      60     15 4-byte meta values passed to higher layers
**
**     40       4     Schema cookie
**     44       4     File format of schema layer
**     48       4     Size of page cache
**     52       4     Largest root-page (auto/incr_vacuum)
**     56       4     1=UTF-8 2=UTF16le 3=UTF16be
**     60       4     User version
**     64       4     Incremental vacuum mode
**     68       4     Application-ID
**     72      20     unused
**     92       4     The version-valid-for number
**     96       4     SQLITE_VERSION_NUMBER
**
** All of the integer values are big-endian (most significant byte first).

这里面最后一句话值得注意,所有的整数都是大端形式,因为大端形式更符合人的从左自右的自然阅读习惯

为了方便用php处理文件头,本人对照sqlite源文件btreeInt.h中的文件头描述手工编写了json格式的文件头描述对象

文件头的描述

下面是文件头的描述文件,json格式

{
"fileHeader":
{"size":"unused",
"elements":
{
"HeaderString":{"offset":0,"size":16,"type":"char"},
"PageSize": {"offset":16,"size":2,"type":"u16"},
"writeVersion": {"offset":18,"size":1,"type":"u8"},
"readVersion": {"offset":19,"size":1,"type":"u8"},
"unusedSpace": {"offset":20,"size":1,"type":"u8"},
"MaxEmbeddedFraction": {"offset":21,"size":1,"type":"u8"},
"MinEmbeddedFraction": {"offset":22,"size":1,"type":"u8"},
"MinLeafFraction": {"offset":23,"size":1,"type":"u8"},
"FileChangeCounter": {"offset":24,"size":4,"type":"u32"},
"Reserved": {"offset":28,"size":4,"type":"u32"},
"FirstFreelistPage": {"offset":32,"size":4,"type":"u32"},
"NumberOfFreelistPages": {"offset":36,"size":4,"type":"u32"},
"SchemaCookie": {"offset":40,"size":4,"type":"u32"},
"FileFormat": {"offset":44,"size":4,"type":"u32"},
"SizeOfPageCache": {"offset":48,"size":4,"type":"u32"},
"LargestRootPage": {"offset":52,"size":4,"type":"u32"},
"UTF-Type": {"offset":56,"size":4,"type":"u32"},
"UserVersion": {"offset":60,"size":4,"type":"u32"},
"IncVacuumMode": {"offset":64,"size":4,"type":"u32"},
"Application-ID": {"offset":68,"size":4,"type":"u32"},
"unused": {"offset":72,"size":20,"type":"char"},
"version-valid": {"offset":92,"size":4,"type":"u32"},
"VERSION_NUMBER": {"offset":96,"size":4,"type":"u32"}
}
}
}

文件头解析成文本

下面是文件头的解析代码:

<?php
//将sqlite数据库文件的最前面的100个字节的文件头解析成文本形式

$outPath = "workingLog";

$i = 1;
$headerFileName = $outPath.'/page'.$i.'-header.dat';
$headerSize =filesize($headerFileName);
echo $headerFileName,'->size = ',$headerSize," bytes\n";

$fin = fopen($headerFileName,'rb');


//根据文件头描述json,将文件内容解析成文本

$jsonArray = json_decode(file_get_contents('structJson/fileHeader.json'),true);
$elements = $jsonArray["fileHeader"]["elements"];

//print_r($headerElements );

echo "start unpacking...\n\n";

$result='';

foreach ($elements as $name => $info) {	
	//fseek($fin, $info['offset']*1);
	$rawVal = fread($fin, $info['size']*1);
	$val = unpackValue($rawVal,$info['size']*1,$info['type']);
	$hexOffset = int2HexStr($info['offset']);
	$result .= '0x'.$hexOffset.' -> '.$name .' : '.$val."\n";
}

fclose($fin);

echo $result;

echo "\nunpack over!\n";

//将原始值解包成可读值
function unpackValue($rawVal,$size,$type){
	if($type=='char'){
		$a = unpack('A*s', $rawVal);
		return $a['s'];
	}
	if($type=='u8'){
		$a = unpack('Cs', $rawVal);
		return $a['s'];
	}
	if($type=='u16'){
		$a = unpack('ns', $rawVal);
		return $a['s'];
	}
	if($type=='u32'){
		$a = unpack('Ns', $rawVal);
		return $a['s'];
	}
	return 'unpackError';
}

//将整数转成16进制串
//将整形转换成16进制显示时,应先pack成真正的整型字节存储形式,然后再以16进制格式unpack
function  int2HexStr($int){
	//echo $int,"\n";
	//转换成16位整数字节数组时,选择大端序,在字节顺序上符合人们的一般习惯,即高位在前(在字节流的前面)。
	$raw = pack('n',$int);	
	//转换成16进制串时,一次性吃进半个字节,所以这里要解包4次,
	//一般人的阅读习惯,16进制表示的字符串都是高位(半字节)在前,所以格式符选H而不选h
	return unpack('H4s',$raw)['s'];	
}

下图是用php对文件头解析后的结果:可以清楚地查看文件头各个字段的含义,第一列是各个字段的偏移量,第二列是各个字段的名字,紧接着的就是各个字段的具体值。各个字段的具体含义请参照官网文献或“空转”先生的文章
在这里插入图片描述
下图是用16进制编辑软件显示的文件头的16进制数据形式,大家可以对比一下,用文本方式查看要比用16进制方式查看清楚多了。
在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值