【问题标题】:Access a raw disk in Random Access mode C++以随机访问模式 C++ 访问原始磁盘
【发布时间】:2017-05-11 20:59:32
【问题描述】:

现在,我已经熟悉了 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++;
}

按顺序执行此操作意味着我必须一个接一个地遍历扇区,计数,直到达到我想要访问的扇区号。而且这在性能上并不是很理想。

如果我想直接访问特定区域,例如“转到 45535 扇区并读取 512 个字节”,该怎么办? IOCTL 操作甚至允许像这样的随机访问吗?我知道CreateFile 调用有随机访问标志,但在那之后,怎么办?就我所见,读取函数仍然不允许我将任何参数作为“目标”或类似的东西传递。

例如,HxD十六进制编辑器可以立即统计磁盘中的总扇区数,并且可以随时转到某个扇区。我需要的功能和这个能力差不多。

我收集了有关如何完成此操作的各种提示,但我在这里实际上陷入了僵局。

欢迎提出任何想法。

【问题讨论】:

  • 您需要在OVERLAPPED 中设置offsetOffsetHigh,并将此OVERLAPPED 用作ReadFile 中的最后一个参数
  • 我在这个问题中没有看到任何 Qt 代码...

标签: c++ winapi ioctl


【解决方案1】:

读取函数仍然不允许我将任何参数传递为 据我所知,“目的地”或类似的东西。

这不是真的 - 再次阅读 ReadFile

lpOverlapped [输入,输出,可选]

对于一个支持字节偏移的hFile,如果你使用这个参数你 必须指定开始读取文件的字节偏移量或 设备。这个偏移量是通过设置 Offset 和 OffsetHigh 来指定的 OVERLAPPED 结构的成员。

还有这个:

使用文件句柄的注意事项:

•如果 lpOverlapped 不为 NULL,则读取操作从偏移量开始 在 OVERLAPPED 结构中指定的...


I/O 管理器维护当前文件位置。 (寻找LARGE_INTEGER CurrentByteOffsetFILE_OBJECT 成员)。 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 标志) - 可以同时执行多个文件读/写操作。在这种情况下,I/O 管理器不能使用 FILE_OBJECT 中的文件位置 - 因为这里存在未定义的行为。

所以我们必须始终显式传递具有有效偏移量的lpOverlapped。如果我们为 lpOverlapped 传递 NULL 指针 - 我们得到 ERROR_INVALID_PARAMETER 错误

最后但很重要。来自 MSDN(ReadFileWriteFile 页面)

系统在ReadFileWriteFile)之前更新OVERLAPPED偏移量 返回。

这在文档中是不正确的和错误的 - 您可以检查自己的 OVERLAPPED 偏移量在操作后没有更新。可能意味着当前文件位置(FILE_OBJECT 中的CurrentByteOffset)已更新。但同样OVERLAPPED 偏移在操作后未更新

【讨论】:

  • 我最初丢弃了'lpOverlapped'参数,因为我认为它仅与“异步操作”有关,与偏移映射无关。我会更严格地研究它。感谢您指出这一点。
  • @DoğukanTunç - 我添加更多信息来回答以获得最大的清晰度
  • 感谢您的详细解释,它确实帮助我理解了这个想法并在我的代码中使用它。
猜你喜欢
  • 1970-01-01
  • 2021-08-09
  • 2015-01-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-20
  • 2011-05-23
  • 2011-06-16
相关资源
最近更新 更多