以随机访问模式访问原始磁盘 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_OBJECTLARGE_INTEGER CurrentByteOffset 成员)。 ReadFileWriteFile 通过添加完成操作时读取或写入的字节数来更新当前文件位置。我们也可以通过调用 SetFilePointer[Ex]

来设置这个位置

如果我们以同步模式打开文件(没有 FILE_FLAG_OVERLAPPED 标志),所有对文件的操作都是顺序的 - 任何新操作都不会开始执行,直到上一个操作未完成。

在这种情况下,我们有两个选择:

  1. 我们可以指定使用当前文件位置偏移量。这个 可以通过传递 NULL 指针来进行规范 lpOverlapped
  2. 我们可以通过显式传递 lpOverlapped 来重置此位置 值为 ReadFileWriteFile。这样做会自动改变 当前文件位置到 (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 偏移量在操作后 未更新