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;
}

结果分析

磁盘信息

在这里插入图片描述

读取PMBR

在这里插入图片描述
在这里插入图片描述

读取GPT Header

在这里插入图片描述
在这里插入图片描述

读取第1个分区表项

在这里插入图片描述
在这里插入图片描述

第2个分区表项

在这里插入图片描述
在这里插入图片描述

第3个分区表项

在这里插入图片描述
在这里插入图片描述

第4个分区表项

在这里插入图片描述
在这里插入图片描述

备份GPT Header

在这里插入图片描述
在这里插入图片描述

备份分区表项

在这里插入图片描述
在这里插入图片描述

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值