无法在Windows 7 64位上读取最后几个Kb的逻辑驱动器

时间:2015-06-05 16:26:02

标签: delphi wmi freepascal lazarus

我在处理逻辑驱动器方面遇到了一些问题。为清楚起见,我对物理磁盘的定义'无论分区如何,(PD)都是原始磁盘。 ' Logical Drive' (LD)指的是诸如Drive E:,Drive F:等的卷

使用RRUZ(我的英雄SO成员)中的示例并实现WMI Class我创建了一个用于读取磁盘的Freepascal程序。我通过\。\ PhyscialDiskX解决PD问题,并且RRUZ(here)创建的示例工作得很好。我可以读取PD的所有字节没有问题。

我对逻辑卷使用相同的句柄技术,它是\?\ E:或\?\ F:等。然后我使用IOCTL_DISK_GET_LENGTH_INFO来获取PD或LV的长度,然后读取字节范围,直到ReadBytes =总长度。我在MSDN网站上看到,它将自动检索传递的任何设备句柄的大小 - PD或LD都一样。事实上,我已经检查了我的程序returnzt WinHex,FTK Imager,HxD和其他几个低级磁盘工具返回的szie值。除了由0或1个起始位置引起的1字节差异外,它们匹配。

但是,出于某种原因,我的程序无法在Windows 7 Pro 64位上获得最终的32Kb,尽管以管理员身份运行该程序。它读取整个磁盘,然后读取最终缓冲区(以64Kb缓冲区完成)BytesRead返回-1。使用调试器我计算出以下值:

493,846,527 exact LV size of Drive F:
493,813,760 total bytes read at time of failure
32,767 bytes missing

以下结果

BytesRead    := FileRead(hDiskHandle, Buffer, (DiskSize - TotalBytesRead));
最终缓冲区读取时,

为-1。这是通过说&#34来测试磁盘末尾的行;如果剩下要读取的数量小于缓冲区大小的大小,则只尝试读取剩下的内容"。因此,FileRead最后要求存储的字节值为32,767(因为此时DiskSize - TotalBytesRead为32,767,这意味着要读取磁盘的剩余字节数)。 缓冲区的指定大小为64Kb。我的理解是你可以减少缓冲区而不是它能够保持but not more(FileRead状态:" 缓冲区必须至少是Count字节长。不执行检查"?这是正确的吗?如果不是那么这可能是(也可能是)问题。

我不知道它是否是由于IOCTL_DISK_GET_LENGTH_INFO,缓冲存储器还是别的什么?希望有人能帮忙吗?我还在Lazarus Freepascal forum.发布了一些屏幕截图。这是我的相关代码部分:

句柄:

// Create handle to source disk. Abort if fails
  hSelectedDisk := CreateFileW(PWideChar(SourceDevice), FILE_READ_DATA,
                   FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0);

  if hSelectedDisk = INVALID_HANDLE_VALUE then
  begin
    RaiseLastOSError;
  end 

计算给定设备的大小离子字节:

ExactDiskSize := GetDiskLengthInBytes(hSelectedDisk);

现在读取设备并将输入存储为平面文件

ImageResult   := WindowsImageDisk(hSelectedDisk, ExactDiskSize, HashChoice, hImageName);

上述功能:

function GetDiskLengthInBytes(hSelectedDisk : THandle) : Int64;
const
  // These are defined at the MSDN.Microsoft.com website for DeviceIOControl
  // and https://forum.tuts4you.com/topic/22361-deviceiocontrol-ioctl-codes/
  {
  IOCTL_DISK_GET_DRIVE_GEOMETRY      = $0070000
  IOCTL_DISK_GET_PARTITION_INFO      = $0074004
  IOCTL_DISK_SET_PARTITION_INFO      = $007C008
  IOCTL_DISK_GET_DRIVE_LAYOUT        = $007400C
  IOCTL_DISK_SET_DRIVE_LAYOUT        = $007C010
  IOCTL_DISK_VERIFY                  = $0070014
  IOCTL_DISK_FORMAT_TRACKS           = $007C018
  IOCTL_DISK_REASSIGN_BLOCKS         = $007C01C
  IOCTL_DISK_PERFORMANCE             = $0070020
  IOCTL_DISK_IS_WRITABLE             = $0070024
  IOCTL_DISK_LOGGING                 = $0070028
  IOCTL_DISK_FORMAT_TRACKS_EX        = $007C02C
  IOCTL_DISK_HISTOGRAM_STRUCTURE     = $0070030
  IOCTL_DISK_HISTOGRAM_DATA          = $0070034
  IOCTL_DISK_HISTOGRAM_RESET         = $0070038
  IOCTL_DISK_REQUEST_STRUCTURE       = $007003C
  IOCTL_DISK_REQUEST_DATA            = $0070040
  IOCTL_DISK_CONTROLLER_NUMBER       = $0070044
  IOCTL_DISK_GET_PARTITION_INFO_EX   = $0070048
  IOCTL_DISK_SET_PARTITION_INFO_EX   = $007C04C
  IOCTL_DISK_GET_DRIVE_LAYOUT_EX     = $0070050
  IOCTL_DISK_SET_DRIVE_LAYOUT_EX     = $007C054
  IOCTL_DISK_CREATE_DISK             = $007C058
  IOCTL_DISK_GET_LENGTH_INFO         = $007405C  // Our constant...
  SMART_GET_VERSION                  = $0074080
  SMART_SEND_DRIVE_COMMAND           = $007C084
  SMART_RCV_DRIVE_DATA               = $007C088
  IOCTL_DISK_GET_DRIVE_GEOMETRY_EX   = $00700A0
  IOCTL_DISK_UPDATE_DRIVE_SIZE       = $007C0C8
  IOCTL_DISK_GROW_PARTITION          = $007C0D0
  IOCTL_DISK_GET_CACHE_INFORMATION   = $00740D4
  IOCTL_DISK_SET_CACHE_INFORMATION   = $007C0D8
  IOCTL_DISK_GET_WRITE_CACHE_STATE   = $00740DC
  IOCTL_DISK_DELETE_DRIVE_LAYOUT     = $007C100
  IOCTL_DISK_UPDATE_PROPERTIES       = $0070140
  IOCTL_DISK_FORMAT_DRIVE            = $007C3CC
  IOCTL_DISK_SENSE_DEVICE            = $00703E0
  IOCTL_DISK_INTERNAL_SET_VERIFY     = $0070403
  IOCTL_DISK_INTERNAL_CLEAR_VERIFY   = $0070407
  IOCTL_DISK_INTERNAL_SET_NOTIFY     = $0070408
  IOCTL_DISK_CHECK_VERIFY            = $0074800
  IOCTL_DISK_MEDIA_REMOVAL           = $0074804
  IOCTL_DISK_EJECT_MEDIA             = $0074808
  IOCTL_DISK_LOAD_MEDIA              = $007480C
  IOCTL_DISK_RESERVE                 = $0074810
  IOCTL_DISK_RELEASE                 = $0074814
  IOCTL_DISK_FIND_NEW_DEVICES        = $0074818
  IOCTL_DISK_GET_MEDIA_TYPES         = $0070C00
  }
  IOCTL_DISK_GET_LENGTH_INFO  = $0007405C;
type
  TDiskLength = packed record
    Length : Int64;
  end;

var
  BytesReturned: DWORD;
  DLength: TDiskLength;
  ByteSize: int64;

begin
  BytesReturned := 0;
  // Get the length, in bytes, of the physical disk
  if not DeviceIOControl(hSelectedDisk,  IOCTL_DISK_GET_LENGTH_INFO, nil, 0,
         @DLength, SizeOf(TDiskLength), BytesReturned, nil) then
           raise Exception.Create('Unable to determine byte capacity of disk.');
  ByteSize := DLength.Length;
  ShowMessage(IntToStr(ByteSize));
  result := ByteSize;
end;

磁盘阅读器功能

function WindowsImageDisk(hDiskHandle : THandle; DiskSize : Int64; HashChoice : Integer; hImageName : THandle) : Int64;
var
  Buffer                   : array [0..65535] of Byte;   // 1048576 (1Mb) or 262144 (240Kb) or 131072 (120Kb buffer) or 65536 (64Kb buffer)

  BytesRead                : integer;

  NewPos, SectorCount,
  TotalBytesRead, BytesWritten, TotalBytesWritten : Int64;
  ... 
 // Now to seek to start of device
      FileSeek(hDiskHandle, 0, 0);
        repeat
          // Read device in buffered segments. Hash the disk and image portions as we go
          if (DiskSize - TotalBytesRead) < SizeOf(Buffer) then
            begin
              // Read 65535 or less bytes
              BytesRead    := FileRead(hDiskHandle, Buffer, (DiskSize - TotalBytesRead));
              BytesWritten := FileWrite(hImageName, Buffer, BytesRead);
            end
          else
            begin
              // Read 65536 (64kb) at a time
              BytesRead     := FileRead(hDiskHandle, Buffer, SizeOf(Buffer));
              BytesWritten  := FileWrite(hImageName, Buffer, BytesRead);
            end;
          if BytesRead = -1 then
            begin
              ShowMessage('There was a read error encountered. Aborting');
              // ERROR IS THROWN AT THIS POINT ONLY WITH LD's - not PD's
              exit;
            end
          else
          begin
          inc(TotalBytesRead, BytesRead);
          inc(TotalBytesWritten, BytesWritten);
          NewPos := NewPos + BytesRead;   
...
  until (TotalBytesRead = DiskSize);

4 个答案:

答案 0 :(得分:7)

可能是边界检查错误。引自MSDN(CreateFile,关于打开物理驱动器和卷的说明,您称之为逻辑驱动器):

  

要读取或写入卷的最后几个扇区,必须调用DeviceIoControl并指定FSCTL_ALLOW_EXTENDED_DASD_IO

答案 1 :(得分:4)

我怀疑问题源于使用64位整数和算术来计算作为32位整数传递的值:

FileRead(hDiskHandle, Buffer, (DiskSize - TotalBytesRead));

我无法肯定地解释为什么这可能只影响LD而不影响PD,除了推测报告的 DiskSize 可能存在某些差异,以某种方式避免 Int64 在这种情况下算术/ 32位问题。

e.g。如果 Int64 算术的32位截断结果是 NEGATIVE (只需要设置高位,即1不是0)那么 FileRead() 将返回 -1 ,因为“要读取的字节”的负值无效。

但是如果未设置结果中的高位,导致正值,那么即使该值明显大于 <64>,这也不会导致错误,因为调用此调用只有当您已经确定少于而不是64K字节才能被读取。 32位截断的Int64算法可能会导致读取 2 BILLION 字节的请求,但 FileRead()只会读取仍然存在的实际32K字节。

然而,这一事实指出了一个解决方案(假设这种诊断是正确的)。

如上所述, FileRead()(它只是Windows上 ReadFile()的包装器)将读取指定的字节数或还有许多字节需要读取,以更低为准。

因此,如果指定64KB但只剩下32KB,则只能读取32KB。

您可以替换所有这些代码:

if (DiskSize - TotalBytesRead) < SizeOf(Buffer) then
begin
  // Read 65535 or less bytes
  BytesRead    := FileRead(hDiskHandle, Buffer, (DiskSize - TotalBytesRead));
  BytesWritten := FileWrite(hImageName, Buffer, BytesRead);
end
else
begin
  // Read 65536 (64kb) at a time
  BytesRead     := FileRead(hDiskHandle, Buffer, SizeOf(Buffer));
  BytesWritten  := FileWrite(hImageName, Buffer, BytesRead);
end;

简单地说:

BytesRead     := FileRead(hDiskHandle, Buffer, SizeOf(Buffer));
BytesWritten  := FileWrite(hImageName, Buffer, BytesRead);

这消除了64位算术以及由于64位结果以32位值传递而导致的错误的任何可能性。如果最终段仅包含32KB,则只读取32KB。

您还可以简化循环终止(删除64位算术,除非您为其他目的需要累积值)。只需在 FileRead()读取的内容少于指定的字节数时,就可以终止循环,而不是累计读取的总字节数。即 BytesRead&lt; 64KB

即使您的磁盘是64KB块的精确倍数,倒数第二个 FileRead()也会返回64KB的完整缓冲区,而下一个 FileRead()将会读 0 字节,即&lt; 64KB,终止循环。 :)

答案 2 :(得分:2)

32,767是奇数,而不是扇区大小的倍数。这意味着最终部分部门根本不可读。

答案 3 :(得分:1)

好的,我已经完成了。

归功于user2024154,因为这是第一件大事。基于此,我已经在那里给出了答案。

但是,不清楚的是如何正确分配其const值。经过几个小时的谷歌搜索,我偶然发现了this。这是我能找到的唯一一个实际显示FSCTL_ALLOW_EXTENDED_DASD_IO定义的Delphi示例,但是必须仔细查看它才能将值拉到一起。

为了其他人的利益,我现在需要的价值是:

const
   FILE_DEVICE_FILE_SYSTEM = $00000009;
   FILE_ANY_ACCESS = 0;
   METHOD_NEITHER = 3;
   FSCTL_ALLOW_EXTENDED_DASD_IO = ((FILE_DEVICE_FILE_SYSTEM shl 16)
                                    or (FILE_ANY_ACCESS shl 14)
                                    or (32 shl 2) or METHOD_NEITHER);   

然后我在首次创建句柄后使用了FSCTL_ALLOW_EXTENDED_DASD_IO,然后:

 if not DeviceIOControl(hSelectedDisk, FSCTL_ALLOW_EXTENDED_DASD_IO, nil, 0,
             nil, 0, BytesReturned, nil) then
               raise Exception.Create('Unable to initiate FSCTL_ALLOW_EXTENDED_DASD_IO disk access.');

这适用于freepascal,并且可以使用Delphi进行一些小调整。

感谢大家的继续帮助,特别是user2024154,以及David一如既往的继续帮助。

更新:除了现在的物理磁盘访问根本不起作用!但我会解决一些问题。