C语言读取GPT分区信息
最近作业需要读取MBR和GPT磁盘信息,上次读了MRB,这次读GPT
文章目录
GPT分区结构
GPT的分区格式,比MBR的要简明扼要不少。一开始第一扇区是PMBR,格式与MBR相同,但是作用不同,在GPT中,PMBR没有太多实质性作用,只有第一个分区表项中有内容。
第1个扇区,是GPT表头GPT Header,有512字节的大小。GPT表头与MBR作用类似,标志着分区格式的开始。第2个扇区开始,一般连着32个扇区,里面存放的内容是GPT的分区表项。分区表项的作用与MBR中的分区表类似,都是指明了分区在哪里,有多大或者分区范围是多少。与MBR不同的地方是,MBR中每个MBR中只有4个分区条目,要么有4个主分区,要么3个主分区加1个主扩展分区;在EBR中4个分区条目只有前两个用上了,形成类似链表一样的结构,前一个逻辑分区指向后一个,前者说明后者在哪里。GPT中,在windows中可以有128个分区项,就像数组一样,可以直接索引,不需要像链表一样,一个个遍历。
GPT分区表项之后有一部分没有用上的区域,然后开始是GPT的第1个分区,一直到最后一个分区。最后一个分区之后,是备份的GPT分区表,最后最后一个扇区,是备份的GPT表头。
PMBR
protective MBR的作用和MBR是不一样的,内容也不一样,PMBR中,分区表中只有第一个表项有内容。PMBR内容如下,主要关注从第1字节起,第5字节的分区标志和Relative这4字节的内容:
GPT Header
其中GUID顺序在磁盘中顺序是从低高,而 GUID读取出来需要是一种特殊的顺序
磁盘GUID:B9 8D F9 E9 13 4A 4D 49 99 8F B3 19 FD B7 60 30
读取出来是:E9F98DB9-4A13-494D-998F-B319FDB76030
GPT表项
其中GUID的顺序和之前第三部分中磁盘GUID的顺序是一致的。表明分区类型的PartitionType微软文档中提供了如下几种:
分区的属性标志GPT attributes bits的含义微软做了如下规定,需要注意的是,磁盘中是小端的顺序,高位在高地址,需要转换成数字中的顺序做比较。一旦某个二进制位置位上去了,该分区就具有了该种属性。置位可以置多个位。
源代码
使用的打开设备和I/O的函数与上一篇中读取MBR格式磁盘信息是一致的。
#include <windows.h>
#include <winioctl.h> //DDK驱动开发与控制
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <stdint.h>
struct gpt_header //GPT表头512字节
{
uint8_t signature[8];//无符号8字节签名
uint8_t version[4];//4字节版本号
uint8_t headersize[4];//GPT表头大小
uint8_t headercrc32[4];//GPT表头的CRC-32校验
uint8_t reserve[4];//保留,为0
uint8_t header_lba[8];//表头的扇区号
uint8_t backup_lba[8];//备份表头的扇区号
uint8_t pation_first_lba[8];//GPT分区起始扇区号
uint8_t pation_last_lba[8];//GPT分区结束扇区号
uint8_t guid[16];//磁盘的GUID
uint8_t pation_table_first[8];//分区表起始扇区号
uint8_t pation_table_entries[4];//分区表总项数
uint8_t pation_table_size[4];//单个分区表占用字节数
uint8_t pation_table_crc[4];//分区表的CRC校验
uint8_t notuse[420];//保留的420字节
};//GPT表头结构
struct partition_table//分区表是128字节
{
uint8_t pationtype[16];//分区类型,全0是未使用
uint8_t pationid[16];//分区唯一标识符
uint8_t pation_start[8];//分区起始扇区号
uint8_t pation_end[8];//分区结束扇区号
uint8_t pation_attr[8];//分区属性标志,区分分区是什么类型的
uint8_t pation_name[72];//分区名
};
struct MBR_disk_entry
{
uint8_t bootflag;//引导标志
uint8_t citouhao;//磁头号
uint8_t shanquhao;//扇区号
uint8_t zhumianhao;//柱面号
uint8_t disk_flag;//分区类型标志,如果是05H/0FH是扩展分区;GPT是0xEE
uint8_t someinfo[3];
uint8_t relative[4];//相对起始扇区
uint8_t sectors[4];//总扇区数
};
struct PMBR //不是真正的MBR
{
uint8_t boot_code[446];//引导代码
MBR_disk_entry pation_table_entry[4];//4个分区表,每个16字节,只有一个分区表有内容,对应的标志是0xEE,
uint8_t endflag[2];//55AA
};
//PartitionType
uint8_t PARTITION_BASIC_DATA_GUID[16] = {0xeb,0xd0,0xa0,0xa2,0xb9,0xe5,0x44,0x33,
0x87,0xc0,0x68,0xb6,0xb7,0x26,0x99,0xc7 };
uint8_t PARTITION_SYSTEM_GUID[16] = {0xc1,0x2a,0x73,28,0xf8,0x1f,0x11,0xd2,0xba,
0x4b,0x00,0xa0,0xc9,0x3e,0xc9,0x3b };
uint8_t PARTITION_MSFT_RESERVED_GUID[16] = {0xe3,0xc9,0xe3,0x16,0x0b,0x5c,0x4d,0xb8,
0x81,0x7d,0xf9,0x2d,0xf0,0x02,0x15,0xae};
uint8_t PARTITION_MSFT_RECOVERY_GUID[16] = {0xde,0x94,0xbb,0xa4,0x06,0xd1,0x4d,0x40,0xa1,
0x6a,0xbf,0xd5,0x01,0x79,0xd6,0xac };
uint8_t PARTITION_ENTRY_UNUSED_GUID[16] = { 0 };
uint8_t * partitiontype[5] = { PARTITION_BASIC_DATA_GUID,PARTITION_SYSTEM_GUID ,
PARTITION_MSFT_RESERVED_GUID ,PARTITION_MSFT_RECOVERY_GUID,PARTITION_ENTRY_UNUSED_GUID };
const char * partition_type_info[] = { "这是一个基本数据分区","这是一个EFI系统分区","这是一个微软保留分区",
"这是一个微软恢复分区","这是一个空分区"};
//GPT表项的attributes bits的最高位,也就是最左边的1位,索引是[0]
//相与不为0说明置位了
uint64_t read_only =0x1000000000000000;
uint64_t shadow_copy = 0x2000000000000000;//其它分区的影像0x200000....
uint64_t hide_partition = 0x4000000000000000; //Hides a partition's volume.
uint64_t no_letter = 0x8000000000000000;//不自动挂载,没有盘符的
uint64_t EFI_hide = 0x0000000000000010;//EFI不可见分区
uint64_t system_partition = 0x0000000000000001;//系统分区
uint64_t attribute_bits[6] = {read_only,shadow_copy,hide_partition,no_letter,EFI_hide,system_partition};
const char * attribute_bits_info[] = {"这是一个只读分区","这是一个其它分区的shadow copy\n","这是一个隐藏分区",
"这是一个不自动挂载、不自动分配盘符的分区","这是一个EFI不可见分区",
"这是一个系统分区"};
uint32_t uint8to32(uint8_t fouruint8[4]) {
return *(uint32_t*)fouruint8;
//return((uint32_t)fouruint8[3] << 24) | ((uint32_t)fouruint8[2] << 16) | ((uint32_t)fouruint8[1] << 8) | ((uint32_t)fouruint8[0]);
}
uint64_t uint8to64(uint8_t fouruint8[8]) {
return *(uint64_t*)fouruint8;
//return((uint64_t)fouruint8[7] << 56) | ((uint64_t)fouruint8[6] << 48) | ((uint64_t)fouruint8[5] << 40) | ((uint64_t)fouruint8[4] << 32) |
//((uint64_t)fouruint8[3] << 24) | ((uint64_t)fouruint8[2] << 16) | ((uint64_t)fouruint8[1] << 8) | ((uint64_t)fouruint8[0]);;
}
int compareuint8(uint8_t * a, uint8_t *b)
{
if (sizeof(*a) != sizeof(*b))
return 0;
for (int i = 0; i < sizeof(*a); i++)
{
if (a[i] != b[i])
return 0;
}
return 1;
}
void changeseqGUID(uint8_t *GUID, uint8_t *seqGUID)
{
//最左边4位,是大端,转过来
seqGUID[0] = GUID[3]; seqGUID[1] = GUID[2]; seqGUID[2] = GUID[1]; seqGUID[3] = GUID[0];
//交叉顺序
seqGUID[4] = GUID[5]; seqGUID[5] = GUID[4]; seqGUID[6] = GUID[7]; seqGUID[7] = GUID[6];
//顺序
for (int i = 8; i < 16; i++)
seqGUID[i] = GUID[i];
}
void show_partion_name(uint8_t*beginchar,int length) {
int j = 0;
for (int i = 0; i < length; i++) {
if (beginchar[i] == 0)
j++;
else
j = 0;
if (j > 2)
return;//后面都是0
else if (j == 0)
printf("%c", beginchar[i]);
}
}
void show_gpt_header(struct gpt_header* the_gpt_header) {
printf("GPT头签名为:");
for (int i = 0; i < 8; i++)
printf("%c", the_gpt_header->signature[i]);
printf("\n");
printf("版本号为:");
for (int i = 0; i < 4; i++)
printf("%0X", the_gpt_header->version[i]);
printf("\n");
printf("GPT头大小为 %u 字节\n", uint8to32(the_gpt_header->headersize));
printf("GPT头CRC校验值为:");
for (int i = 0; i < 4; i++)
printf("%0X", the_gpt_header->headercrc32[i]);
printf("\n");
printf("GPT表头起始扇区号为 %I64X\n", uint8to64(the_gpt_header->header_lba));
//备份表头在最后一个EFI扇区,可以得知整个磁盘的大小,扇区数*512/1024/1024/1024
printf("GPT备份表头扇区号为 %I64X\n", uint8to64(the_gpt_header->backup_lba));
printf("GPT分区区域的起始扇区号为 %I64X\n", uint8to64(the_gpt_header->pation_first_lba));
printf("GPT分区区域结束扇区号为 %I64X\n", uint8to64(the_gpt_header->pation_last_lba));
printf("磁盘GUID为:");
uint8_t GUID[16];
changeseqGUID(the_gpt_header->guid, GUID);
for (int i = 0; i < 16; i++)
{
printf("%0X", GUID[i]);
if (i == 3 || i == 5 || i == 7 || i == 9)
printf("-");
}
printf("\n");
printf("GPT分区表起始扇区号为 %I64X\n", uint8to64(the_gpt_header->pation_table_first));
printf("GPT分区表总项数为 %I32X\n", uint8to32(the_gpt_header->pation_table_entries));
printf("每个分区表占用字节数为 %I32X\n", uint8to32(the_gpt_header->pation_table_size));
printf("分区表CRC校验值为 %I32X\n", uint8to32(the_gpt_header->pation_table_crc));
}
void showPMBR(struct PMBR*the_pmbr)
{
printf("引导标志为%X\n", the_pmbr->pation_table_entry[0].bootflag);
printf("磁头号为%X\n", the_pmbr->pation_table_entry[0].citouhao);
printf("扇区号为%X\n", the_pmbr->pation_table_entry[0].shanquhao);
printf("柱面号为%X\n", the_pmbr->pation_table_entry[0].zhumianhao);
printf("分区类型标志为 %X\n", the_pmbr->pation_table_entry[0].disk_flag);
printf("第一个扇区为 %u\n", uint8to32(the_pmbr->pation_table_entry[0].relative));
printf("扇区数为 %u\n", uint8to32(the_pmbr->pation_table_entry[0].sectors));
}
uint8_t show_partition_table(struct partition_table * the_partition_table)
{
uint8_t GUID[16];
printf("分区类型值为:");
uint8_t flag = 0;
changeseqGUID(the_partition_table->pationtype, GUID);
for (int i = 0; i < 16; i++)
{
flag = flag | GUID[i];
printf("%0X", GUID[i]);
if (i == 3 || i == 5 || i == 7 || i == 9)
printf("-");
}
printf("\n");
for (int i = 0; i < 5; i++) {
if (compareuint8(GUID, partitiontype[i]))
printf("***%s***\n", partition_type_info[i]);
}
printf("分区GUID为:");
changeseqGUID(the_partition_table->pationid, GUID);
for (int i = 0; i < 16; i++)
{
printf("%0X", GUID[i]);
if (i == 3 || i == 5 || i == 7 || i == 9)
printf("-");
}
printf("\n该分区起始扇区号为%I64X\n", uint8to64(the_partition_table->pation_start));
printf("该分区结束扇区号为%I64X\n", uint8to64(the_partition_table->pation_end));
printf("该分区属性标志为%I64X\n", uint8to64(the_partition_table->pation_attr));
uint64_t attr = uint8to64(the_partition_table->pation_attr);
for (int i = 0; i < 6; i++)
{
if ((attr&attribute_bits[i])!=0)
printf("从attributes-bits中可知:%s\n", attribute_bits_info[i]);
}
printf("该分区名为:");
show_partion_name(the_partition_table->pation_name, 72);
uint64_t bytes = (uint8to64(the_partition_table->pation_end) - uint8to64(the_partition_table->pation_start)) * (uint64_t)512;
double MB = bytes / 1024.0/1024.0;
double GB = MB / 1024.0;
printf("\n该分区大小为%I64X字节,%lf GB",bytes,GB);
printf("\n\n\n");
return flag;
}
int read_partition_table(struct gpt_header * the_gpt_header, HANDLE hDevice, ULONGLONG baseaddr)
{
int entrynum = 0;
DWORD dwCB;
LARGE_INTEGER offset;
partition_table the_partition_tables[4];
ULONGLONG nextaddr = ((ULONGLONG)0 + (ULONGLONG)baseaddr) *(ULONGLONG)512;
offset.QuadPart = nextaddr;//找到下一个要读取的地址
SetFilePointer(hDevice, offset.LowPart, &offset.HighPart, FILE_BEGIN);//设置偏移准备读取
ReadFile(hDevice, &the_partition_tables, 512, &dwCB, NULL);
if (GetLastError())
{
return 0;
}
int endflag = 1;
int j = 0;//如果j=4,才重新读,因为某种限制,一次必须读512字节整数倍
while (endflag > 0) {
printf("\n第%d个分区表:\n", ++entrynum);
if (j == 4)
{
nextaddr = nextaddr + (ULONGLONG)512;
offset.QuadPart = nextaddr;//找到下一个要读取的地址
SetFilePointer(hDevice, offset.LowPart, &offset.HighPart, FILE_BEGIN);//设置偏移准备读取
if (GetLastError())
{
return 0;
}
memset(&the_partition_tables, 0, 512);
ReadFile(hDevice, &the_partition_tables, 512, &dwCB, NULL);
j = 0;
}
endflag = show_partition_table(&the_partition_tables[j]);
j++;
}
return 1;
}
int main()
{
DISK_GEOMETRY pdg; // 保存磁盘参数的结构体
HANDLE hDevice; // 设备句柄
BOOL bResult; // results flag
DWORD junk; // discard resultscc
int disk = 0;
const char *diskname[] = { "\\\\.\\PhysicalDrive0" ,"\\\\.\\PhysicalDrive1" };
printf("请输入要打开的硬盘号(一般为0,有2个硬盘可以输入0或1)\n");
scanf("%d", &disk);
if (disk != 0 && disk != 1)
{
disk = 0;
printf("输入无效,打开磁盘0\n");
}
//通过CreateFile来获得设备的句柄
hDevice = CreateFile(diskname[disk], // 设备名称,PhysicalDriveX表示打开第X个设备
GENERIC_READ, // no access to the drive
FILE_SHARE_READ | FILE_SHARE_WRITE, // share mode
NULL, // default security attributes
OPEN_EXISTING, // disposition
0, // file attributes
NULL); // do not copy file attributes
if (hDevice == INVALID_HANDLE_VALUE) //没能打开,可能是没有用管理员权限运行
{
printf("Creatfile error!May be no permission!ERROR_ACCESS_DENIED!\n");
system("pause");
return (FALSE);
}
//通过DeviceIoControl函数与设备进行IO
bResult = DeviceIoControl(hDevice, // 设备的句柄
IOCTL_DISK_GET_DRIVE_GEOMETRY, // 控制码,指明设备的类型
NULL,
0, // no input buffer
&pdg,
sizeof(pdg),
&junk, // # bytes returned
(LPOVERLAPPED)NULL); // synchronous I/O
LARGE_INTEGER offset;//long long signed
offset.QuadPart = (ULONGLONG)0 * (ULONGLONG)512;//0
SetFilePointer(hDevice, offset.LowPart, &offset.HighPart, FILE_BEGIN);//从0开始读PMBR
if (GetLastError())
printf("错误类型代号:%ld\n\n", GetLastError());//如果出错了
DWORD dwCB;
struct PMBR the_pmbr;
//从这个位置开始读512字节PMBR
//读取PMBR的512字节,里面的分区表第一项才有用,从1数,第5字节是0xEE
//相对起始扇区值是GPT Header的位置
BOOL bRet = ReadFile(hDevice, &the_pmbr, 512, &dwCB, NULL);
printf("----------------读取PMBR部分:---------------\n");
showPMBR(&the_pmbr);
if (the_pmbr.pation_table_entry[0].disk_flag == 0xEE)//如果的确是GPT格式分区
{
printf("PMBR中分区表第一项的标志位为 0xEE,是GPT格式,跳转到%u扇区\n",
uint8to32(the_pmbr.pation_table_entry[0].relative));
printf("GPT表头在第%u扇区", uint8to32(the_pmbr.pation_table_entry[0].relative));
}
else {
printf("PMBR中分区表第一项标志位为 %X,不是GPT格式,结束分析\n",
the_pmbr.pation_table_entry[0].disk_flag);
CloseHandle(hDevice);
system("pause");
return 0;
}
//读取分析GPT Header
//下一个要读取的线性地址=要读取的扇区号*512字节
printf("\n\n----------------读取GPT Header部分:---------------\n\n");
ULONGLONG nextaddr= (ULONGLONG)1*(ULONGLONG)512;
offset.QuadPart = nextaddr;//找到下一个要读取的地址
SetFilePointer(hDevice, offset.LowPart, &offset.HighPart, FILE_BEGIN);//设置偏移准备读取
if (GetLastError())
{
printf("读取GPT Header出错。错误类型代号:%ld\n\n", GetLastError());//如果出错了
CloseHandle(hDevice);
system("pause");
return 0;
}
//读取GPT Header
gpt_header the_gpt_header;
ReadFile(hDevice, &the_gpt_header, 512, &dwCB, NULL);
show_gpt_header(&the_gpt_header);
//读取主GPT分区表项,分区表项前16个字节如果全0,表示未用,后面都没有了,可以去读备份
printf("\n\n-------------读取分区表项:-------------\n\n");
ULONGLONG baseaddr = (ULONGLONG)uint8to64(the_gpt_header.pation_table_first);//GPT分区表起始位置
if (!read_partition_table(&the_gpt_header, hDevice, baseaddr))//如果出错
{
CloseHandle(hDevice);
system("pause");
return 0;
}
printf("\n\n---------------读取备份的GPT Header:---------------\n\n");
nextaddr = (ULONGLONG)uint8to32(the_gpt_header.backup_lba)*(ULONGLONG)512;
offset.QuadPart = nextaddr;//找到下一个要读取的地址
SetFilePointer(hDevice, offset.LowPart, &offset.HighPart, FILE_BEGIN);//设置偏移准备读取
if (GetLastError())
{
printf("读取备份GPT Header出错。错误类型代号:%ld\n\n", GetLastError());//如果出错了
CloseHandle(hDevice);
system("pause");
return 0;
}
//读取备份GPT Header
gpt_header backup_gpt_header;
ReadFile(hDevice, &backup_gpt_header, 512, &dwCB, NULL);
show_gpt_header(&backup_gpt_header);
//读取备份GPT分区表项,分区表项前16个字节如果全0,表示未用,后面都没有了,可以去读备份
printf("\n\n-------------读取备份分区表项:-------------\n\n");
baseaddr = (ULONGLONG)uint8to64(backup_gpt_header.pation_table_first);//GPT分区表起始位置
if (!read_partition_table(&backup_gpt_header, hDevice, baseaddr))//如果出错
{
CloseHandle(hDevice);
system("pause");
return 0;
}
printf("\n\n这块硬盘大小为 %lf GB\n", (double)uint8to64(the_gpt_header.backup_lba) * 512 / 1024 / 1024 / 1024);
CloseHandle(hDevice);
system("pause");
return 0;
}