以随机访问模式访问原始磁盘 C++
Access a raw disk in Random Access mode C++
现在,我已经熟悉了 DeviceIoControl (ioctl) 进程并且可以从磁盘顺序读取,一次 512 字节。
我从 \.\PhysicalDrive(s) 列表中创建一个句柄并通过 IOCTL_STORAGE_QUERY_PROPERTY 命令识别它。然后处理数据需要的设备。
此时,我可以通过创建一个循环来逐步读取它,每次使用此代码(Qt C++环境)读取区域1个扇区
#include <minwindef.h>
#include <devioctl.h>
#include <ntdddisk.h>
#include <ntddscsi.h>
#include <ioapiset.h>
#include <fileapi.h>
#include <handleapi.h>
#include <winbase.h>
...
HANDLE devHandle = NULL;
devHandle = CreateFile(charArray, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0 ,NULL);
unsigned long secCount = 0;
while(true)
{
bSuccess = ReadFile(devHandle, lpSecBuf, 512, &dwRead, NULL);
if (bSuccess & (dwRead < 512))
{
qDebug()<<"EOF";
break;
}
if(!bSuccess)
{
qDebug()<<"No data could be read from the device.";
break;
}
if(bSuccess && dwRead>0)
{
qDebug()<<"Sector "<<secCount<<" data : "<<lpSecBuf;
}
secCount++;
}
按顺序执行此操作意味着我必须逐个扇区进行计数,直到到达我想要访问的扇区号。这在性能上不是很理想。
如果我想直接访问特定区域怎么办,比如"go to sector 45535 and read 512 bytes"? IOCTL 操作甚至允许这样的随机访问吗?我知道 CreateFile
调用有随机访问标志,但之后呢?据我所知,读取函数仍然不允许我将任何参数作为 "destination" 或类似的东西传递。
例如HxD,十六进制编辑器可以立即统计磁盘中的总扇区数,并可以随时转到某个扇区。我需要的功能和这个能力差不多
我已经收集了关于如何完成的各种技巧,但我实际上陷入了僵局。
欢迎提出任何想法。
The read function still doesn't allow me to pass any arguments as
"destination" or something like that, as far as I can see.
这不是真的 - 再读一遍 ReadFile
lpOverlapped [in, out, optional]
For an hFile that supports byte offsets, if you use this parameter you
must specify a byte offset at which to start reading from the file or
device. This offset is specified by setting the Offset and OffsetHigh
members of the OVERLAPPED structure.
还有这个:
Considerations for working with file handles:
•If lpOverlapped is not NULL, the read operation starts at the offset
that is specified in the OVERLAPPED structure ...
I/O 管理器维护当前文件位置。 (寻找 FILE_OBJECT
的 LARGE_INTEGER CurrentByteOffset
成员)。 ReadFile
和 WriteFile
通过添加完成操作时读取或写入的字节数来更新当前文件位置。我们也可以通过调用 SetFilePointer[Ex]
来设置这个位置
如果我们以同步模式打开文件(没有 FILE_FLAG_OVERLAPPED
标志),所有对文件的操作都是顺序的 - 任何新操作都不会开始执行,直到上一个操作未完成。
在这种情况下,我们有两个选择:
- 我们可以指定使用当前文件位置偏移量。这个
可以通过传递
NULL
指针来进行规范
lpOverlapped
- 我们可以通过显式传递
lpOverlapped
来重置此位置
值为 ReadFile
或 WriteFile
。这样做会自动改变
当前文件位置到 (Offset, OffsetHigh)
值,
执行读(写)操作,然后更新位置
根据实际读取(写入)的字节数。这个
技术为调用者提供原子查找和读取(写入)服务。
所以根据代码我们有 2 个变体:
BOOL _ReadFile(HANDLE hFile, LARGE_INTEGER ByteOffset, PVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead)
{
#if 1
OVERLAPPED ov = {};
ov.Offset = ByteOffset.LowPart;
ov.OffsetHigh = ByteOffset.HighPart;
return ReadFile(hFile, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, &ov);
#else
if (SetFilePointerEx(hFile, ByteOffset, 0, FILE_BEGIN))
{
return ReadFile(hFile, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, 0);
}
return FALSE;
#endif
}
当然,与使用 SetFilePointer[Ex]
(对内核的额外 API 调用)相比,原子查找和读取(写入)更有效。
如果我们以异步模式打开文件(使用 FILE_FLAG_OVERLAPPED
标志)- 可以同时执行对文件的多个 read/write 操作。在这种情况下 I/O 管理器不能使用 FILE_OBJECT
中的文件位置 - 因为这里有未定义的行为。
所以我们必须始终显式传递具有有效偏移量的 lpOverlapped
。如果我们为 lpOverlapped 传递一个 NULL
指针 - 我们得到 ERROR_INVALID_PARAMETER
错误
最后但很重要。来自 MSDN(在 ReadFile and WriteFile 页)
The system updates the OVERLAPPED offset before ReadFile (WriteFile)
returns.
这不是真的,文档中有错误 - 您可以自己检查 OVERLAPPED
偏移量是否在操作后未更新。可能意味着当前文件位置(FILE_OBJECT
中的CurrentByteOffset
)已更新。但再次 OVERLAPPED
偏移量在操作后 未更新
现在,我已经熟悉了 DeviceIoControl (ioctl) 进程并且可以从磁盘顺序读取,一次 512 字节。
我从 \.\PhysicalDrive(s) 列表中创建一个句柄并通过 IOCTL_STORAGE_QUERY_PROPERTY 命令识别它。然后处理数据需要的设备。
此时,我可以通过创建一个循环来逐步读取它,每次使用此代码(Qt C++环境)读取区域1个扇区
#include <minwindef.h>
#include <devioctl.h>
#include <ntdddisk.h>
#include <ntddscsi.h>
#include <ioapiset.h>
#include <fileapi.h>
#include <handleapi.h>
#include <winbase.h>
...
HANDLE devHandle = NULL;
devHandle = CreateFile(charArray, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0 ,NULL);
unsigned long secCount = 0;
while(true)
{
bSuccess = ReadFile(devHandle, lpSecBuf, 512, &dwRead, NULL);
if (bSuccess & (dwRead < 512))
{
qDebug()<<"EOF";
break;
}
if(!bSuccess)
{
qDebug()<<"No data could be read from the device.";
break;
}
if(bSuccess && dwRead>0)
{
qDebug()<<"Sector "<<secCount<<" data : "<<lpSecBuf;
}
secCount++;
}
按顺序执行此操作意味着我必须逐个扇区进行计数,直到到达我想要访问的扇区号。这在性能上不是很理想。
如果我想直接访问特定区域怎么办,比如"go to sector 45535 and read 512 bytes"? IOCTL 操作甚至允许这样的随机访问吗?我知道 CreateFile
调用有随机访问标志,但之后呢?据我所知,读取函数仍然不允许我将任何参数作为 "destination" 或类似的东西传递。
例如HxD,十六进制编辑器可以立即统计磁盘中的总扇区数,并可以随时转到某个扇区。我需要的功能和这个能力差不多
我已经收集了关于如何完成的各种技巧,但我实际上陷入了僵局。
欢迎提出任何想法。
The read function still doesn't allow me to pass any arguments as "destination" or something like that, as far as I can see.
这不是真的 - 再读一遍 ReadFile
lpOverlapped [in, out, optional]
For an hFile that supports byte offsets, if you use this parameter you must specify a byte offset at which to start reading from the file or device. This offset is specified by setting the Offset and OffsetHigh members of the OVERLAPPED structure.
还有这个:
Considerations for working with file handles:
•If lpOverlapped is not NULL, the read operation starts at the offset that is specified in the OVERLAPPED structure ...
I/O 管理器维护当前文件位置。 (寻找 FILE_OBJECT
的 LARGE_INTEGER CurrentByteOffset
成员)。 ReadFile
和 WriteFile
通过添加完成操作时读取或写入的字节数来更新当前文件位置。我们也可以通过调用 SetFilePointer[Ex]
如果我们以同步模式打开文件(没有 FILE_FLAG_OVERLAPPED
标志),所有对文件的操作都是顺序的 - 任何新操作都不会开始执行,直到上一个操作未完成。
在这种情况下,我们有两个选择:
- 我们可以指定使用当前文件位置偏移量。这个
可以通过传递
NULL
指针来进行规范lpOverlapped
- 我们可以通过显式传递
lpOverlapped
来重置此位置 值为ReadFile
或WriteFile
。这样做会自动改变 当前文件位置到(Offset, OffsetHigh)
值, 执行读(写)操作,然后更新位置 根据实际读取(写入)的字节数。这个 技术为调用者提供原子查找和读取(写入)服务。
所以根据代码我们有 2 个变体:
BOOL _ReadFile(HANDLE hFile, LARGE_INTEGER ByteOffset, PVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead)
{
#if 1
OVERLAPPED ov = {};
ov.Offset = ByteOffset.LowPart;
ov.OffsetHigh = ByteOffset.HighPart;
return ReadFile(hFile, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, &ov);
#else
if (SetFilePointerEx(hFile, ByteOffset, 0, FILE_BEGIN))
{
return ReadFile(hFile, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, 0);
}
return FALSE;
#endif
}
当然,与使用 SetFilePointer[Ex]
(对内核的额外 API 调用)相比,原子查找和读取(写入)更有效。
如果我们以异步模式打开文件(使用 FILE_FLAG_OVERLAPPED
标志)- 可以同时执行对文件的多个 read/write 操作。在这种情况下 I/O 管理器不能使用 FILE_OBJECT
中的文件位置 - 因为这里有未定义的行为。
所以我们必须始终显式传递具有有效偏移量的 lpOverlapped
。如果我们为 lpOverlapped 传递一个 NULL
指针 - 我们得到 ERROR_INVALID_PARAMETER
错误
最后但很重要。来自 MSDN(在 ReadFile and WriteFile 页)
The system updates the OVERLAPPED offset before ReadFile (WriteFile) returns.
这不是真的,文档中有错误 - 您可以自己检查 OVERLAPPED
偏移量是否在操作后未更新。可能意味着当前文件位置(FILE_OBJECT
中的CurrentByteOffset
)已更新。但再次 OVERLAPPED
偏移量在操作后 未更新