Search code examples
delphifastmmfiremonkey-fm2

Block footer corruption after freeing dynamic array


I am encountering this error when a user logins into a server and the server sends the response related to username/password match result. The array which has the reply packet is causing the error. I do not know as to how can I get this fixed, its my first time seeing such an error.

Code

Main Method

procedure TAgUDP.ProcessLoginPacket
          ( const APacket : TBytes; const ABinding : TIdSocketHandle );
var
  Username : String;
  Password : String;
  Index    : WORD;
  ID       : TGUID;
  Reply    : TBytes;
  Peer     : TAgUDPSPeer;

begin

  // Extracting Username
  SetLength ( Username, APacket [LI_LNUN] );
  Index := 5 + HEADER_END + ( APacket [LI_LNIP] * 2 );
  Move  ( APacket [Index], Username [1], APacket [LI_LNUN] * 2 );

  // Checking whether the peer is already connected
  if not Assigned ( fClientsList.FindPeer ( Username )) then
  begin

    // Extracting Password
    SetLength ( Password, APacket [LI_LNPW] );
    Index := Index + ( APacket [LI_LNUN] * 2 );
    Move  ( APacket [Index], Password [1], APacket [LI_LNPW] * 2 );

    // Creating Peer Object
    CreateGUID ( ID );
    Peer :=
    TAgUDPSPeer.Create ( ABinding.PeerIP, ABinding.PeerPort, ID, Self );

    // Checking whether the database has a record of the user
    if fOnLogin ( Peer, Username, Password, Reply ) then
    begin

      // Assigning username and adding to connected clients list
      Peer.fName := Username;
      fClientsList.Add ( Peer );

      // Extracting local ip+port pair
      SetLength ( Peer.fLIP,  APacket [LI_LNIP] );
      Move  ( APacket [LI_PORT], Peer.fLPort,   2 );
      Move  ( APacket [LI_IPST], Peer.fLIP [1], APacket [LI_LNIP] * 2 );

      // -----------------------------------------------------------------------
      // Building login reply packet

      Index := StrSize ( Peer.fIP ) + HEADER_END + 3;
      SetLength ( Reply, Index + Length ( Reply ));
      Move ( Reply [0], Reply [Index], Length ( Reply ) - Index );

      Reply [0] := Byte ( AgOp_LoginReply );
      Move ( fID,      Reply [HEADER_SID], 16 );
      Move ( Peer.fID, Reply [HEADER_RID], 16 );

      Reply [LR_LNIP] := Length ( Peer.fIP );
      Move ( Peer.fPort,   Reply [LR_PORT], 2 );
      Move ( Peer.fIP [1], Reply [LR_IPST], Length ( Reply ) - 3 );

      SendBuffer ( ABinding.PeerIP, ABinding.PeerPort, Reply );

    end
    else
    begin

      // Building login reply packet - for login rejection

      Peer.Free;
      SetLength ( Reply, 35 );
      Reply [0] := Byte ( AgOp_LoginReply );
      SendBuffer ( ABinding.PeerIP, ABinding.PeerPort, Reply );;

    end;

  end;
  // Deliberate freeing of the array to make the debugger stop after this, after exception otherwise it stops after end
  SetLength ( Reply, 0 );

end;

fOnLogin Method

function  TNetworkModule.OnLogin
          ( const APeer: TAgUDPSPeer; const AUsername, APassword: String;
            var ABuffer: TBytes ): Boolean;
var
  DS      : TSQLDataset;
  Profile : TUser;
begin

  DS := TSQLDataSet.Create ( nil );
  DS.SQLConnection := DBConnection;
  DS.CommandText   :=
  'SELECT user_id,rating,clan '+
  'FROM users '+
  'WHERE username='''+ AUsername +''' AND password='''+ APassword +'''';
  DS.Open;
  Result  := not DS.Eof;

  if Result then
  begin

    fLog.Log( ['Event'],
              ['A login request has been accepted. Username: '+ AUsername] );

    // Storing user information
    Profile := TUser.Create ( APeer, DS.Fields [0].AsInteger,
                              DS.Fields [1].AsInteger, DS.Fields [2].AsString );

    // Adding user to lobby
    fHall.AddUser ( Profile );

    // Making a packet to have additional information of the user to be transmitted to
    // it in the login reply packet 
    SetLength ( ABuffer, 4 + StrSize ( Profile.Clan ));
    Move ( Profile.Rating, ABuffer [0], 4 );
    Move ( Profile.Clan,   ABuffer [4], Length ( ABuffer ) - 4 );

  end
  else
    fLog.Log( ['Event'],
              ['A login request has been denied. Username: '+ AUsername] );

  DS.Close;
  DS.Free;

end;

FastMM Log

FastMM has detected an error during a FreeMem operation. The block footer has been corrupted. 

The block size is: 68

This block was allocated by thread 0x9C, and the stack trace (return addresses) at the time was:
419E3E [FastMM4.pas][FastMM4][DebugReallocMem$qqrpvi][8935]
40692F [System.pas][System][@ReallocMem$qqrrpvi][4325]
40C165 [System.pas][System][DynArraySetLength$qqrrpvpvipi][31888]
40C296 [System.pas][System][@DynArraySetLength$qqrv][31967]
A4B26B [AgUDPServer.pas][AgUDPServer][TAgUDP.ProcessLoginPacket$qqrx25System.%DynamicArray$tuc%xp30Idsockethandle.TIdSocketHandle][872]
A4BF94 [AgUDPServer.pas][AgUDPServer][TAgUDP.DoUDPRead$qqrp32Idudpserver.TIdUDPListenerThreadx25System.%DynamicArray$tuc%p30Idsockethandle.TIdSocketHandle][1247]
A486A0 [IdUDPServer.pas][IdUDPServer][TIdUDPListenerThread.UDPRead$qqrv][415]
A485E4 [IdUDPServer.pas][IdUDPServer][TIdUDPListenerThread.Run$qqrv][392]
A466D8 [IdThread.pas][IdThread][TIdThread.Execute$qqrv][363]
484E7D [System.Classes.pas][System.Classes][Classes.ThreadProc$qqrp22System.Classes.TThread][14569]
409F9E [System.pas][System][ThreadWrapper$qqspv][21627]

The block was previously used for an object of class: TDBXDynalinkConnection

The block is currently used for an object of class: Unknown

The allocation number is: 182223

The block was previously freed by thread 0x9C, and the stack trace (return addresses) at the time was:
40690E [System.pas][System][@FreeMem$qqrpv][4251]
4082FD [System.pas][System][TObject.FreeInstance$qqrv][14978]
408A8D [System.pas][System][@ClassDestroy$qqrxp14System.TObject][16273]
AF0F57 [Data.DBXCommon.pas][Data.DBXCommon][Dbxcommon.TDBXConnection.$bdtr$qqrv][8476]
4083FB [System.pas][System][TObject.Free$qqrv][15046]
B6E0A3 [Data.SqlExpr.pas][Data.SqlExpr][Sqlexpr.TSQLConnection.DoDisconnect$qqrv][2563]
A77FE5 [Data.DB.pas][Data.DB][Db.TCustomConnection.SetConnected$qqro][3405]
A77F6F [Data.DB.pas][Data.DB][Db.TCustomConnection.Close$qqrv][3386]
B6D010 [Data.SqlExpr.pas][Data.SqlExpr][Sqlexpr.TSQLConnection.$bdtr$qqrv][2217]
4083FB [System.pas][System][TObject.Free$qqrv][15046]
B7181C [Data.SqlExpr.pas][Data.SqlExpr][Sqlexpr.TCustomSQLDataSet.InternalFreeCommand$qqrv][4046]

The current thread ID is 0x9C, and the stack trace (return addresses) leading to this error is:
40690E [System.pas][System][@FreeMem$qqrpv][4251]
40C3EA [System.pas][System][@DynArrayClear$qqrrpvpv][32152]
40C085 [System.pas][System][DynArrayClear$qqrrpvpv][31815]
40C0C1 [System.pas][System][DynArraySetLength$qqrrpvpvipi][31839]
40C296 [System.pas][System][@DynArraySetLength$qqrv][31967]
A4B3FC [AgUDPServer.pas][AgUDPServer][TAgUDP.ProcessLoginPacket$qqrx25System.%DynamicArray$tuc%xp30Idsockethandle.TIdSocketHandle][904]
A4BF94 [AgUDPServer.pas][AgUDPServer][TAgUDP.DoUDPRead$qqrp32Idudpserver.TIdUDPListenerThreadx25System.%DynamicArray$tuc%p30Idsockethandle.TIdSocketHandle][1247]
A486A0 [IdUDPServer.pas][IdUDPServer][TIdUDPListenerThread.UDPRead$qqrv][415]
A485E4 [IdUDPServer.pas][IdUDPServer][TIdUDPListenerThread.Run$qqrv][392]
A466D8 [IdThread.pas][IdThread][TIdThread.Execute$qqrv][363]
484E7D [System.Classes.pas][System.Classes][Classes.ThreadProc$qqrp22System.Classes.TThread][14569]

Solution

  • Block footer corruption is a FastMM4 FullDebugMode error. It means that somewhere, something in your code is writing past the end of allocated memory. Probably the best candidate to look at is any Move calls.

    How reproducible is this? If it happens every time, there's an easy way to track it down:

    • Put a breakpoint at the line where you allocate the dynamic array (the SetLength call)
    • When you hit that breakpoint, run it (so the SetLength call runs), and then hit CTRL-F7 (Evalute/modify)
    • Use the @ operator to get the address of the last byte in the array. (If the array is 20 elements long, for example, evaluate @Reply[19].) This will give you a memory address, in hexadecimal. Add 1 to this value to get the address of the first byte beyond the boundaries of the array.
    • Go to the Breakpoints pane, and click the drop-down arrow on the side of the Add Breakpoint button. Add a Data Breakpoint.
    • For the address, give the memory address you just came up with. For Length, say 1.
    • Run the program, and when the memory at that location gets overwritten, it will break to the debugger, and you'll be able to see what's changing it.