在Win8上,使用CopyMemory会导致访问冲突。

3

我有一段代码,使用Delphi XE3编译成64位COM DLL。

function TRPMFileReadStream.Read(var Buffer; const Count: Longint): Longint;
begin
  if ((Self.FPosition >= 0) and (Count > 0)) then
  begin
    Result := Self.FSize - Self.FPosition;
    if ((Result > 0) and (Result >= Count)) then
    begin
      if (Result > Count) then
      begin
        Result := Count;
      end;
      CopyMemory(
        Pointer(@Buffer),
        Pointer(LongWord(Self.FMemory) + Self.FPosition),
        Result
      );
      Inc(Self.FPosition, Result);
      Exit;
    end;
  end;
  Result := 0;
end;

在Win7-64位操作系统上,以上代码可以正常运行。 但是在Win8-64位操作系统上,同样的DLL文件会在CopyMemory中抛出访问冲突错误。 CopyMemory函数实现在WinAPI.windows单元中。
如下所示:
procedure CopyMemory(Destination: Pointer; Source: Pointer; Length: NativeUInt);
begin
  Move(Source^, Destination^, Length);
end;

有任何想法吗?谢谢。

2
@Arioch'The 32位编译器使用FastCode的“Move”指令,但是没有64位的FastCode“Move”指令。 - David Heffernan
2
我不能代表Doctorlai和@Arioch发言,但是更喜欢使用CopyMemory,因为我觉得指针比无类型引用参数更容易理解,并且我每天都在多种语言中工作,所以拥有一个通用的API非常有帮助。此外,由于API直接将所有内容转发到内置函数,因此任何人选择哪个函数几乎没有区别。 - Rob Kennedy
由于 CopyMemory 是内联的,编译器会发出相同的代码。因此,这取决于个人选择。 - David Heffernan
在现代C++中,指针很少使用,而引用则很常见。 - David Heffernan
@David,我听说FastCode在被收购后被AnyDAC禁用了,我想知道它是否也会被XE4或XE5禁用... - Arioch 'The
显示剩余2条评论
2个回答

7
在这一点上:
Pointer(LongWord(Self.FMemory) + Self.FPosition)

你将一个64位指针截断为32位,因此导致访问冲突。相反,你需要

Pointer(NativeUInt(Self.FMemory) + Self.FPosition)

你的代码在Win7上也是有问题的,但不知道为什么你只在地址小于4GB的指针上运行了这段代码。

你应该运行一些自上而下的内存分配测试来排除其他类似的错误。


请听取我的建议并运行自上而下的分配测试。我相信你会发现更多的错误。 - David Heffernan
1
如何运行特定的自上而下分配测试? - justyy
2
@DoctorLai,我拒绝了你的建议修改。很抱歉。我真正想表达的是不幸。很不幸这个漏洞直到现在才显现出来。任何已经发布的代码都有这个漏洞。如果这个漏洞早些时候就显现出来,那么更少有缺陷的代码可能会逃脱。也许你还没有发布这段代码,但你知道我的意思。 - David Heffernan
好的,谢谢...我是指带引号的“幸运”。 :) 是的,在这个阶段发现这个bug很好。在我的笔记本电脑Win7-64位8GB上,之前的测试没有发现任何错误。 - justyy
通常情况下,将32位指针转换为LongWord比将有符号的32位整数(例如Integer、Longint)进行转换更为常见。谢谢。 - justyy
显示剩余7条评论

4

David指出了你问题的根源——你在64位上的指针类型转换是错误的。更好的解决方案是使用指针算术运算,让编译器为你处理指针大小:

CopyMemory(
  @Buffer,
  PByte(Self.FMemory) + Self.FPosition,
  Result
);

或者:

Move(
  (PByte(Self.FMemory) + Self.FPosition)^,
  Buffer,
  Result
);

谢谢。所以这个 PByte 的东西需要 {$POINTERMATH ON} 才能实现,对吗?两者生成的代码是相同的,对吗? - justyy
POINTERMATH 只针对 PByte 自动开启,但对于任何其他你想要使用指针算术的指针类型,需要手动开启。 - Remy Lebeau
我认为由于历史原因,它也适用于 PAnsiChar - David Heffernan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接