简体   繁体   English

这个FastMM4无效指针异常是FastMM for Delphi 5中的一个错误吗?

[英]Is this FastMM4 Invalid Pointer Exception a bug in FastMM for Delphi 5?

In Delphi 5, with FastMM active, the call to FreeMem in the following minimum-reproducible code triggers an Invalid Pointer Exception : 在Delphi 5中,当FastMM处于活动状态时,在以下最小可重现代码中调用FreeMem会触发无效指针异常

program Project1;
{$APPTYPE CONSOLE}

uses
  FastMM4,
  SysUtils,
  Windows;

procedure Main;
var
    token: THandle;
    returnLength: Cardinal;
    p: Pointer;
begin
    OpenProcessToken(GetCurrentProcess, TOKEN_QUERY, {out}token);

    //Get the size of the buffer required.
    //It's normally going to be 38 bytes. We'll use 16KB to eliminate the possibility of buffer overrun
//  Windows.GetTokenInformation(token, TokenUser, nil, 0, {var}returnLength);
    p := GetMemory(16384); //GetMemory(returnLength);

    Windows.GetTokenInformation(token, TokenUser, p, 1024, {var}returnLength);

    FreeMem({var}p); //FreeMem is the documented way to free memory allocated with GetMemory.
//  FreeMemory(p); //FreeMemory is the C++ compatible version of FreeMem.
end;

begin
    Main;
end.

The call to FreeMme fails with an EInvalidPointerException : FreeMme的调用因EInvalidPointerException FreeMme失败:

FreeMem({var}p); //error

The error will stop happening if: 如果出现以下错误,则错误将停止:

  • i stop using FastMM4 我停止使用FastMM4
  • i stop calling GetTokenInformation 我停止调用GetTokenInformation
  • i call FreeMemory (rather than FreeMem ) 我叫FreeMemory (而不是FreeMem

I've reproduced the error on a fresh install of Delphi 5 on a freshly installed Windows 7 machine. 我在新安装的Windows 7机器上重新安装了Delphi 5的错误。 FastMM4 v4.992. FastMM4 v4.992。

  • The error does not happen in Delphi 7 在Delphi 7中不会发生错误
  • The error does not happen in Delphi XE6 Delphi XE6中不会发生此错误

It's only: 这只是:

  • Delphi 5 德尔福5
  • when using FastMM4 使用FastMM4时

Workaround 解决方法

If it is a bug in FastMM4, i can workaround it. 如果它是FastMM4中的错误,我可以解决它。 Rather than calling: 而不是打电话:

  • GetMemory GetMemory
  • FreeMem freemem在

I can manually allocate the buffer another way: 我可以用另一种方式手动分配缓冲区:

  • SetLength(buffer, cb) SetLength(缓冲区,cb)
  • SetLength(buffer, 0) SetLength(缓冲区,0)

If it's not a bug in FastMM4, i'd like to fix the above code. 如果它不是FastMM4中的错误,我想修复上面的代码。

Using FreeMemory, rather than FreeMem, doesn't trigger the error 使用FreeMemory而不是FreeMem不会触发错误

I was under the impression that FastMM takes over memory management, which is why i was surprised to discover: 我的印象是FastMM接管内存管理,这就是为什么我惊讶地发现:

  • FreeMem({var}p); failed 失败
  • FreeMemory(p); works 作品

Internally, FreeMem is implemented as a call to the memory manager. 在内部, FreeMem实现为对内存管理器的调用。 In this case the memory manager (FastMM) returns non-zero, causing the call to reInvalidPtr : 在这种情况下,内存管理器(FastMM)返回非零值,导致调用reInvalidPtr

System.pas System.pas

procedure _FreeMem;
asm
        TEST    EAX,EAX
        JE      @@1
        CALL    MemoryManager.FreeMem
        OR      EAX,EAX
        JNE     @@2
@@1:    RET
@@2:    MOV     AL,reInvalidPtr
        JMP     Error
end;

and the implementation of MemoryManager.FreeMem ends up being: 并且MemoryManager.FreeMem的实现最终是:

FastMM4.pas FastMM4.pas

function FastFreeMem(APointer: Pointer);

FreeMem takes a var pointer, FreeMemory takes a pointer FreeMem接受一个var指针,FreeMemory接受一个指针

The implementation of FreeMemory is: FreeMemory的实现是:

System.pas : System.pas

function FreeMemory(P: Pointer): Integer; cdecl;
begin
  if P = nil then
    Result := 0
  else
    Result := SysFreeMem(P);
end;

And SysFreeMem is implemented in: SysFreeMem实现在:

GetMem.inc : GetMem.inc

function SysFreeMem(p: Pointer): Integer;
// Deallocate memory block.
label
  abort;
var
  u, n : PUsed;
  f : PFree;
  prevSize, nextSize, size : Integer;
begin
  heapErrorCode := cHeapOk;

  if not initialized and not InitAllocator then begin
    heapErrorCode := cCantInit;
    result := cCantInit;
    exit;
  end;

  try
    if IsMultiThread then EnterCriticalSection(heapLock);

    u := p;
    u := PUsed(PChar(u) - sizeof(TUsed)); { inv: u = address of allocated block being freed }
    size := u.sizeFlags;
    { inv: size = SET(block size) + [block flags] }

    { validate that the interpretation of this block as a used block is correct }
    if (size and cThisUsedFlag) = 0 then begin
      heapErrorCode := cBadUsedBlock;
      goto abort;
    end;

    { inv: the memory block addressed by 'u' and 'p' is an allocated block }

    Dec(AllocMemCount);
    Dec(AllocMemSize,size and not cFlags - sizeof(TUsed));

    if (size and cPrevFreeFlag) <> 0 then begin
      { previous block is free, coalesce }
      prevSize := PFree(PChar(u)-sizeof(TFree)).size;
      if (prevSize < sizeof(TFree)) or ((prevSize and cFlags) <> 0) then begin
        heapErrorCode := cBadPrevBlock;
        goto abort;
      end;

      f := PFree(PChar(u) - prevSize);
      if f^.size <> prevSize then begin
        heapErrorCode := cBadPrevBlock;
        goto abort;
      end;

      inc(size, prevSize);
      u := PUsed(f);
      DeleteFree(f);
    end;

    size := size and not cFlags;
    { inv: size = block size }

    n := PUsed(PChar(u) + size);
    { inv: n = block following the block to free }

    if PChar(n) = curAlloc then begin
      { inv: u = last block allocated }
      dec(curAlloc, size);
      inc(remBytes, size);
      if remBytes > cDecommitMin then
        FreeCurAlloc;
      result := cHeapOk;
      exit;
    end;

    if (n.sizeFlags and cThisUsedFlag) <> 0 then begin
      { inv: n is a used block }
      if (n.sizeFlags and not cFlags) < sizeof(TUsed) then begin
        heapErrorCode := cBadNextBlock;
        goto abort;
      end;
      n.sizeFlags := n.sizeFlags or cPrevFreeFlag
    end else begin
      { inv: block u & n are both free; coalesce }
      f := PFree(n);
      if (f.next = nil) or (f.prev = nil) or (f.size < sizeof(TFree)) then begin
        heapErrorCode := cBadNextBlock;
        goto abort;
      end;
      nextSize := f.size;
      inc(size, nextSize);
      DeleteFree(f);
      { inv: last block (which was free) is not on free list }
    end;

    InsertFree(u, size);
abort:
    result := heapErrorCode;
  finally
    if IsMultiThread then LeaveCriticalSection(heapLock);
  end;
end;

So it makes that sense that FreeMemory doesn't trigger the error; 因此,感觉FreeMemory不会触发错误; FreeMemory is not taken over by the memory manager. FreeMemory不会由内存管理器接管。

I guess that is why FreeMemory is not the documented counterpart to GetMemory : 🕗 我想这就是为什么FreeMemory不是记录对口GetMemory: 🕗

在此输入图像描述

FreeMem is not the documented way to free memory allocated with GetMemory - that's apparently an error in the old documentation that has since been corrected. FreeMem不是用GetMemory分配的释放内存的文档化方法 - 这显然是旧文档中的错误,此后已被纠正。 From the documentation for System.GetMemory ( emphasis added): System.GetMemory文档重点添加):

GetMemory allocates a memory block. GetMemory分配一个内存块。

GetMemory allocates a block of the given Size on the heap, and returns the address of this memory. GetMemory分配给定Size的块,并返回该内存的地址。 The bytes of the allocated buffer are not set to zero. 分配的缓冲区的字节不设置为零。 To dispose of the buffer, use FreeMemory . 要处理缓冲区,请使用FreeMemory If there is not enough memory available to allocate the block, an EOutOfMemory exception is raised. 如果没有足够的可用内存来分配块,则会EOutOfMemory异常。

If you allocate the memory with GetMem , use FreeMem . 如果分配与内存GetMem ,使用FreeMem If the allocation is done with GetMemory , use FreeMemory . 如果分配与完成GetMemory ,使用FreeMemory

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM