C语言读取磁盘分区信息(MBR、DPT、EBR)

简介

  在win8以后,磁盘格式一般是GPT格式的,做实验是在winXP虚拟机上完成的,partition style是MBR。在虚拟机上分配了40GB的硬盘空间。三个主分区盘C、E、F,一个扩展分区里面有三个逻辑盘G、H、I。
在这里插入图片描述

使用的函数

1.CreateFile()函数打开设备

HANDLE CreateFile(
  LPCSTR lpFileName,
   DWORD dwDesiredAccess,
   DWORD dwShareMode,
  LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  DWORD dwCreationDisposition,
  DWORD dwFlagsAndAttributes,
  HANDLE hTemplateFile
);

  • 第1个参数lpFileName为要创建或打开的文件或设备的名称。磁盘为TEXT("\\.\PhysicalDrive0"),如果有两个盘,第2个为1。
  • 第2个参数dwDesiredAccess为请求访问文件或设备,常取值为GENERIC_READ, GENERIC_WRITE或两者(GENERIC_READ | GENERIC_WRITE)
  • 第3个参数为dwShareMode,是请求的文件或设备共享模式,可以是读取,写入,两者,删除
  • 第4个参数为lpSecurityAttributes,是指向SECURITY_ATTRIBUTES 结构的指针,如果此参数为NULL,则CreateFile返回的句柄不能由应用程序可能创建的任何子进程继承,使用默认安全属性。
  • 第5个参数dwCreationDisposition,对存在或不存在的文件或设备执行的操作。对于文件以外的设备,此参数通常设置为OPEN_EXISTING。
  • 第6个参数为dwFlagsAndAttributes,是文件或设备属性和标志。
  • 第7个参数为hTemplateFile,具有GENERIC_READ访问权限的模板文件的有效句柄。此参数可以为NULL。
  • 返回值:如果函数成功,则返回值是指定文件,设备,命名管道或邮件槽的打开句柄。如果函数失败,则返回值为INVALID_HANDLE_VALUE。获取错误码,可以调用GetLastError()函数。

2.DeviceIoControl()函数返回磁盘设备信息

BOOL DeviceIoControl(
  HANDLE hDevice,
  DWORD dwIoControlCode,
  LPVOID lpInBuffer,
  DWORD nInBufferSize,
  LPVOID lpOutBuffer,
  DWORD nOutBufferSize,
  LPDWORD lpBytesReturned,
  LPOVERLAPPED lpOverlapped
);

  • 第1个参数为hDevice,是要对其执行操作的设备的句柄,本实验中是CreateFile()函数的返回的设备句柄。
  • 第2个参数dwIoControlCode,是操作的控制代码,该值标识要执行的特定操作以及执行它的设备类型。使用IOCTL_DISK_GET_DRIVE_GEOMETRY,用于获取磁盘信息。
  • 第3个参数为lpInBuffer,是指向输入缓冲区的指针,可以为NULL。
  • 第4个参数为nInBufferSize,是输入缓冲区大小,以字节为单位。
  • 第5个参数为lpOutBuffer,指向输出缓冲区的指针,用于接收操作返回的数据。
  • 第6个参数为nOutBufferSize,是输出缓冲区的大小,以字节为单位。
  • 第7个参数为lpBytesReturned,是一个指向变量的指针,该变量接收存储在输出缓冲区中的数据大小(以字节为单位)。如果输出缓冲区太小而无法接收任何数据,则调用失败, GetLastError()返回 ERROR_INSUFFICIENT_BUFFER,并且lpBytesReturned为零。
  • 第8个参数为lpOverlapped,指向OVERLAPPED结构的指针。如果在未指定FILE_FLAG_OVERLAPPED的情况下打开 hDevice,则忽略lpOverlapped。
  • 返回值:如果操作成功完成,则返回值为非零值。

3.SetFilePointer()函数设置读取磁盘信息位置

DWORD SetFilePointer(
HANDLE hFile,
LONG lDistanceToMove,
PLONG lpDistanceToMoveHigh,
DWORD dwMoveMethod
);

  • 第1个参数hFile是文件的句柄。
  • 第2个参数为lDistanceToMove,是表示距离的有符号数的低32位,用来指定文件指针的字节偏移。
  • 第3个参数lpDistanceToMoveHigh,是距离的高32位。
  • 第4个参数dwMoveMethod是文件指针移动的起点。

4.ReadFile()函数读取磁盘内容

BOOL ReadFile(
HANDLE hFile,
LPVOID lpBuffer,
DWORD nNumberOfBytesToRead,
LPDWORD lpNumberOfBytesRead,
LPOVERLAPPED lpOverlapped
);

  • 第1个参数hFile,是文件/设备句柄。
  • 第2个参数lpBuffer,是指向缓冲区的指针,用于接收读取的数据。
  • 第3个参数nNumberOfBytesToRead,是要读取的最大字节数。
  • 第4个参数lpNumberOfBytesRead,用于接收读取的字节数。
  • 第5个参数lpOverlapped可以为NULL。

源代码

#include <windows.h>
#include <winioctl.h> //DDK驱动开发与控制
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <stdint.h>
#define BufferLength 1024

//将四个连续字节存放的值转为int型
uint32_t transtoint(unsigned char a[])
{
	uint32_t sum = 0UL;
	uint32_t temp = 0UL;
	
	for (int i = 3; i>=0; i--)
	{
		temp = a[i];//先赋值				
		temp = temp <<( 8 * (3-i));		
		sum = sum | temp;
				
	}
	return sum;
}
//十六进制输出
bool HexOutput(char* buf, size_t len,bool ismbr, ULONGLONG * baseaddr,ULONGLONG * nextaddr,int EBRnum)
{
	bool mbrflag=1;//在读取MBR的时候判断条目是主分区还是扩展分区条目 
	unsigned char a = buf[0];
	if(ismbr)
		printf("-----------------------------------------------------");
	else
		printf("--------------------------第%d个EBR------------------",EBRnum);
	//printf("第一字节是:%x\n\n", a);
	if(ismbr)
		printf("          第一部分(MBR):\n\n");
	else
		printf("          第一部分(EBR):\n\n");
	int flag = 0;
	for (size_t i = 0; i < len; ++i)
	{
		unsigned char c = buf[i]; // must use unsigned char to print >128 value 
		flag++;
		if (c < 16)
			printf("0%x ", c);
		else
			printf("%x ", c);
		if (i == 445)
		{
			flag = 0;
			printf("\n\n          第二部分(分区表):\n");
		}
		if (i == 509)
		{
			flag = 0;
			printf("\n\n          第三部分(结束标志):\n");
		}
		if ((flag) % 16 == 0)
			printf("\n");
	}
	printf("\n<-------------------分区表信息解析------------------->\n\n");
	printf("\n\n分区地址和大小分别为: \n\n");	
	int limit=ismbr?509:477;//如果已经知道是扩展分区的EBR,不用读取那么多,rank=1是分区表,rank=2是指向下一个的
	for (int m = 445, rank = 1; m < limit&&rank<=4; m += 16, rank++)
	{
		unsigned char fifth = buf[m + 5];//取得第五位文件系统标志位,十六进制为05H或者0FH是扩展分区 		
		if(fifth==0x5||fifth==0xf)//是扩展分区条目,不用往后读了 
		{
			printf("This is an extend patition!\n");
			mbrflag=0;	
			rank = 4;
		}//不是mbr是扩展分区EBR 
		if (fifth < 16) //调整输出格式
			printf("第%d分区表标志位为: 0%x\n", rank, fifth);
		else
			printf("第%d分区表标志位为: %x\n", rank, fifth);
		if (fifth == 0x00)//当第五位(标志位)是00时,代表分区表信息为空,无分区
		{
			printf(" 分区表为空\n\n");//也不用往后读了 
		}
		else {//分区项 
			unsigned char offsetadd[4] = { 0 };
			//倒着读,高字节在高地址处
			for (int n = m + 12, t = 0; n > m + 8, t < 4; n--, t++)
			{
				unsigned char temp = buf[n];
				if (temp < 16)
					printf("  0%x  ", temp);
				else
					printf("  %x  ", temp);
				offsetadd[t] = buf[n];
			}
			//计算地址,转换为十进制扇区数LBA
			printf("\n");
			uint32_t tempadd = transtoint(offsetadd);
			printf("\n开始地址为: %I64x", (ULONGLONG)tempadd * (ULONGLONG)512 + *baseaddr);
			
			if(ismbr&&!mbrflag)// if in mbr and got a extend entry,the EBR at relsecor+nowbase(0)
			{
				*baseaddr=(ULONGLONG)tempadd* (ULONGLONG)512 + *baseaddr;//only change once
				*nextaddr = (ULONGLONG)0UL;
			}				
			else if (!mbrflag)//if it's the extend entry
				{
					*nextaddr = (ULONGLONG)tempadd * (ULONGLONG)512;
				}
			printf("\n\n");
			printf("大小:");
			for (int p = m + 16, w = 0; p > m + 12, w < 4; p--, w++)
			{
				unsigned char temp1 = buf[p];
				if (temp1 < 16)
					printf("  0%x  ", temp1);
				else
					printf("  %x  ", temp1);
				offsetadd[w] = buf[p];
			}
			//计算大小,转化为GB单位
			printf("\n");
			uint32_t tempsize = transtoint(offsetadd);
			
			if(ismbr && !mbrflag)
				printf("\n扩展盘总大小为: %lu 扇区 = %lf GB \n", tempsize, ((double)tempsize / 2.0 / 1024.0 / 1024.0));
			else if(!ismbr)
			{
				printf("\n第%d逻辑盘大小为: %lu 扇区 = %lf GB \n",EBRnum, tempsize, ((double)tempsize / 2.0 / 1024.0 / 1024.0));
			}
			else
				printf("\n该盘大小为: %lu 扇区 = %lf GB \n", tempsize, ((double)tempsize / 2.0 / 1024.0 / 1024.0));
		}
	}
	printf("\n\n");
	return (mbrflag);
}

//函数:对主分区表进行解析,分别得到每个分区的偏移地址以及分区大小
BOOL GetDriveGeometry(DISK_GEOMETRY *pdg, int addr)
{
	HANDLE hDevice;               // 设备句柄
	BOOL bResult;                 // results flag
	DWORD junk;                   // discard resultscc
	char lpBuffer[BufferLength] = { 0 };


	//通过CreateFile来获得设备的句柄
	hDevice = CreateFile(TEXT("\\\\.\\PhysicalDrive0"), // 设备名称,这里指第一块硬盘
		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) // cannot open the drive
	{
		printf("Creatfile error!May be no permission!ERROR_ACCESS_DENIED!\n");
		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)addr * (ULONGLONG)512;//0
	SetFilePointer(hDevice, offset.LowPart, &offset.HighPart, FILE_BEGIN);//从0开始读MBR
	if(GetLastError())
		printf("错误类型代号:%ld\n\n", GetLastError());//如果出错了
		
	DWORD dwCB;
	//从这个位置开始读 
	BOOL bRet = ReadFile(hDevice, lpBuffer, 512, &dwCB, NULL);
	//如果不是MBR,
	bool finished=0; 
	int EBRnum=0;
	ULONGLONG *baseaddr=new ULONGLONG,*nextaddr= new ULONGLONG;//扩展分区起始地址,EBR地址 
	*baseaddr = (ULONGLONG)0;
	*nextaddr = (ULONGLONG)0;
	finished=HexOutput(lpBuffer, 512,true,baseaddr,nextaddr,EBRnum);//先是读取MBR
	
	if(finished)
		CloseHandle(hDevice);
	else
	{
		//继续读
		do{
			EBRnum++;
			memset(lpBuffer, 0, sizeof(lpBuffer));
			offset.QuadPart = (ULONGLONG)(*baseaddr + *nextaddr);//find the EBR
			SetFilePointer(hDevice, offset.LowPart, &offset.HighPart, FILE_BEGIN);//读EBR
			if(GetLastError())
				printf("错误类型代号:%ld\n\n", GetLastError());//如果出错了
			
			ReadFile(hDevice, lpBuffer, 512, &dwCB, NULL);
			
		}while(!HexOutput(lpBuffer, 512,false,baseaddr,nextaddr,EBRnum));
		CloseHandle(hDevice);
	}

	delete baseaddr;
	delete nextaddr;
	return bResult;
}
extern int add[20];
extern int disknum;
int main()
{
	DISK_GEOMETRY pdg;            // 保存磁盘参数的结构体
	BOOL bResult;                 // generic results flag
	ULONGLONG DiskSize;           // size of the drive, in bytes
	printf("<-----------------欢迎使用分区读取程序----------------->\n\n");
	bResult = GetDriveGeometry(&pdg, 0);

	if (bResult)
	{
		DiskSize = pdg.Cylinders.QuadPart * (ULONG)pdg.TracksPerCylinder *
			(ULONG)pdg.SectorsPerTrack * (ULONG)pdg.BytesPerSector;

		printf("磁盘总大小 = %I64d (Bytes) = %I64d (Gb)\n", DiskSize, DiskSize / (1024 * 1024 * 1024));
	}
	else
	{
		printf("GetDriveGeometry failed. Error %ld.\n", GetLastError());
	}
	return ((int)bResult);
}

结果分析

运行程序解析磁盘信息的结果,与使用Winhex查看计算是一致的。

MBR部分:

在这里插入图片描述

分区地址和大小分别为:

第1分区表标志位为: 07
00 00 00 3f
开始地址为: 7e00
大小: 02 7f 8c b2
该盘大小为: 41913522 扇区 = 19.985925 GB

第2分区表标志位为: 07
02 7f 8c f1
开始地址为: 4ff19e200
大小: 00 3f fa c5
该盘大小为: 4192965 扇区 = 1.999362 GB

第3分区表标志位为: 07
02 bf 87 b6
开始地址为: 57f0f6c00
大小: 00 3f fa c5
该盘大小为: 4192965 扇区 = 1.999362 GB

This is an extend patition!
第4分区表标志位为: 0f
02 ff 82 7b
开始地址为: 5ff04f600
大小: 02 00 53 aa
扩展盘总大小为: 33575850 扇区 = 16.010213 GB

查看C盘信息,开始地址7E00,20G:
在这里插入图片描述
查看E盘信息,开始抵制4FF19E200,大小2G:
在这里插入图片描述
查看F盘信息,开始地址57F0F6C00,大小2G:
在这里插入图片描述
查看第4个DPT对应的扩展分区的EBR,扩展分区的EBR所在地址位5FF04F600:
在这里插入图片描述

第一个EBR的DPT内容为:

在这里插入图片描述
  第一个条目地址信息中3F 00 00 00是63,指向第一个逻辑盘。计算这个逻辑盘开始地址为63*512=7E00H,加上5FF04F600H得到5FF057400H,这就是G盘的起始位置。G盘的大小信息为0E 12 A0 00,也就是10490382个扇区,10490382*512/1024/1024/1024=5GB。
在这里插入图片描述
  第2个条目是第5个字节内容是05H,表示是扩展分区。(之前MBR中的第4个扇区第5字节是0FH,也表示这是一个扩展分区)。扩展分区的地址信息为A0124DH,也就是相对于扩展分区开始位置5FF04F600H起的第10490445个扇区(换成字节是140249A00H)处存放着下一个扩展分区的MBR。这个位置是140249A00H+5FF04F600H =73F299000H。在这个位置,的确放着下一逻辑盘,也就是H盘的EBR。用同样方式可以计算。
 在这里插入图片描述

第1分区表标志位为: 07
00 00 00 3f
开始地址为: 5ff057400
大小: 00 a0 12 0e
第1逻辑盘大小为: 10490382 扇区 = 5.002204 GB

This is an extend patition!
第2分区表标志位为: 05
00 a0 12 4d
开始地址为: 73f299000
大小: 00 a0 12 4d
第1逻辑盘大小为: 10490445 扇区 = 5.002234 GB

第2个EBR内容

在这里插入图片描述

分区地址和大小分别为:
第1分区表标志位为: 07
00 00 00 3f
开始地址为: 5ff057400
大小: 00 a0 12 0e
第2逻辑盘大小为: 10490382 扇区 = 5.002204 GB

This is an extend patition!
第2分区表标志位为: 05
01 40 24 9a
开始地址为: 87f4e2a00
大小: 00 c0 2f 10
第2逻辑盘大小为: 12594960 扇区 = 6.005745 GB

总结

  MBR的四个分区表的组合有两种:全部是主分区;三个主分区+一个扩展分区。在阅读分区表条目的时候,(从第1字节开始)第5字节中存放的信息也比较重要,如果是05H或者是0FH,表示这是一个扩展分区条目,地址信息指向的是EBR,否则如果不是0,就是一个主分区条目,指向的是每个主分区的开始扇区。如果是在EBR中,在扩展分区表部分,只有前两个条目有效,最后两个是全为0的没有用到,第1个条目指向了目前已经发现了的逻辑盘,第2个条目如果不是全0,说明还有下一个逻辑盘,指向的就是下一个EBR。
  地址信息Relative在主分区中,是0。在扩展分区中,是主扩展分区的开始位置所在扇区,可以从MBR中获取到。而Sectors指的是分区占的总扇区数,在MBR中,第4个如果是扩展分区的话,表示是整个扩展分区里面所有逻辑盘的大小。
  自己电脑的分区逻辑图如图所示:
在这里插入图片描述


PS:程序写的时候有的显示BUG这种就没有改了。。。。。

  • 14
    点赞
  • 75
    收藏
    觉得还不错? 一键收藏
  • 13
    评论
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值