Search code examples
delphistackdelphi-5

Access violation between lines


The following code seems to work okay on my machine, but gives an access violation for a customer.

procedure TSummaryThread.Execute;
var
  When: String;
begin
  try
    FCarcassListCS := TCriticalSection.Create;
    FCarcassLists := TObjectList.Create;
    while (not Terminated) do
      begin
        try
          When := 'GetMessage';
          if (GetMessage(Msg, 0, 0, 0))
            then begin
                  if (Msg.message = WM_SUMMARISE)
                  then begin
                        When := 'WM_SUMMARISE';
                        Update(True);
                       end
                else if (Msg.message = WM_UPDATE)
                  then begin
                        When := 'WM_UPDATE';
                        Update(False);
                       end
                else if (Msg.message = WM_RELOAD_SETUP)
                  then begin
                        When := 'WM_RELOAD_SETUP';
                        ReloadSetup(Pointer(Msg.wParam));
                       end
                else if (Msg.message = WM_SEND_TO)
                  then begin
                        When := 'WM_SEND_TO';
                        SendToThread(Pointer(Msg.wParam));
                       end
                else if (Msg.message = WM_UPDATE_ORDER_SUM)
                  then begin
                        When := 'WM_UPDATE_ORDER_SUM';
                        DoOrderSummary;
                       end
                else if (Msg.message = WM_SUMMARISE_LOAD)
                  then begin
                        When := 'WM_SUMMARISE_LOAD';
                        LoadAndSummarise;
                       end
                  else begin
                        When := 'DispatchMessage';
                        DispatchMessage(Msg);
                       end;
                 end;
        except
          on E: Exception do
            Log('Exception (' + E.ClassName + ') in summary  thread at ' + When + ': ' + E.Message);
        end;
      end;
  except
    on E: Exception do
      Log('Exception (' + E.ClassName + ') in summary  thread (2) at ' + When + ': ' + E.Message);
end;

procedure TSummaryThread.Update(Full: Boolean);
var
  CarcassList: TObjectList;
  Where: String;
begin
  try
    Where := 'GetList';
    FCarcassListCS.Enter;
    try
      if (FCarcassLists.Count = 0)
        then raise Exception.Create('No carcass list found');
      CarcassList := FCarcassLists[0] as TObjectList;
      FCarcassLists.Extract(CarcassList);
    finally
      FCarcassListCS.Leave;
    end;
    FSummaryListCS.Enter;
    try
      Where := 'LoadFiles';
      SetFiles;
      try
        Where := 'AddCcs';
        CarcassList := GetShiftCarcassList(CarcassList);
        AddCarcassesToSummaries(CarcassList, Full);
      finally
        CloseFiles;
      end;
      Where := 'Send';
      SendSummaries;
      Where := 'Done';
    finally
      FreeAndNil(CarcassList);
      FSummaryListCS.Leave;
    end;
  except
    on E: Exception do
    begin
      E.Message := 'Update(' + Where + '): ' + E.Message;
      raise;
    end;
  end;
end;

From the logging I can tell that When := 'WM_UPDATE'; is being hit and Where := 'GetList'; is not. This leads me to think that the access violation is occurring when the local Where variable in the Update() procedure is being added to the Stack.

What extra diagnostics could I add to help work out what's causing the issue? Is there any way I can check if the stack is corrupted and/or where the Where variable is being added to the stack?

I realize this is a rather vague question, but I cannot think of anything to try and narrow this down.

Exact error is:

Exception (EAccessViolation) in summary thread at WM_UPDATE: Access violation at address 004B6759 in module 'Application.exe'. Read of address 00000008


Solution

  • In your executable's Project Options, locate the Linker settings. Ensure that the Map file setting is configured to Detailed. While you're on the Linker settings page, check the Image base address. It is most likely $00400000, if not write it remember what it is set to.

    Rebuild your executable (I'm assuming your project is a single executable, which it appears to be based on the exception text you provided). In the compiler's output directory, you should find a file named "Application.map". Open this file in your favorite text editor.

    Now, take the address reported in the exception message (in this case $004B6759). Subtract the executable's base address ($00400000) from the exception's address. This gives you $B6759. Subtract $1000 (I'm pretty sure it's subtract, if not someone will point out that it should add in the comments). Leaving $B5759. This is the offset within the executable where the access violation occurred.

    Using the map file that you loaded earlier, look in the Detailed map of segments section. Locate the first entry that is less than or equal to $B5759 and is immediately followed by a segment that is greater than $B5759. Scan unit name of associated with the segment (it's the one marked with "M=").

    Search for the unit name in the map file, you will find a section titled "Line numbers for XXX(..) segment .text" where XXX is the name of the unit you are looking for. In this section will be a list of line numbers and the offset to the first byte of code for that line number. Locate the first line number (based on the address) that is less than or equal to $B5759 and is immediately followed by an offset greater than $B5759.

    This will tell you the line number (and source file) that contains the compiled code that is actually causing the access violation.

    And as others have stated, given that the exception is a read of $8, you are likely trying to read from a nil pointer or object.. probably the second field of record or object (assuming 32bit code).

    Best of luck.