Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Resolve address of AccessViolation in the map file

One of my users has reported a few rare AccessViolations which I want to analyze.

I have the source code of exactly that Build, so I could create a MAP file. But I don't know how to find the address provided by the AccessViolation in the MAP file.

(In future, we want to use a framework like JclDebug to create useable stacktraces).

I have setup an example:

procedure CrashMe;
var
  k: TMemo; a: TButton;
begin
  k.Text := 'abc';
  k.Color := clBlack;
  k.Assign(a);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  CrashMe;
end;

The access violation is:

Address 004146CF. Reading from address B2D88B53 .

In the map file, I find following contents:

 Start         Length     Name                   Class
 0001:00401000 000556A8H .text                   CODE
 0002:00457000 00000770H .itext                  ICODE
 0003:00458000 00001B0CH .data                   DATA
 0004:0045A000 00004CCCH .bss                    BSS
 0005:00000000 00000038H .tls                    TLS

 ....

 0001:000552F0       Unit1..TForm1
 0001:00055498       Unit1.CrashMe
 0004:00004CC8       Unit1.Form1
 0001:000554C8       Unit1.TForm1.Button1Click

Why does the AV say address 004146CF, while the MAP file says 0001:00055498 ?

Even if I subtract the start address of the CODE segment (0001), I still get 004146CF-00401000 = 136CF , which is not what I am looking for either.

I also tried to find the error adress by searching the string ":00414", but it didn't find anything.

How can I lookup the address from the AV in the MAP file?

like image 379
Daniel Marschall Avatar asked Jul 05 '16 13:07

Daniel Marschall


2 Answers

Why does the AV say address 004146CF, while the MAP file says 0001:00055498 ?

004146CF is the actual memory address of the code instruction that crashed at runtime, whereas addresses in the .map file are relative since the actual load address of the process is not known at compile time.

Even if I subtract the start address of the CODE segment (0001)

0001 is not an address, let alone a starting address. It is merely an ID number defined at the top of the .map file for a given segment. 0001:00055498 refers to relative address 00055498 within the segment identified as 0001.

I still get 004146CF-00401000 = 136CF , which is not what I am looking for either.

Usually the load address of a process is $400000 (the actual value is defined in the Project Options and is $400000 by default), but that may be different at runtime due to various reasons, such as re-basing. Once you determine the actual load address, you need to include the actual offset of the code segment within the process. That offset is usually $1000 (the actual value is defined in the compiled executable's PE header). So, to map a memory address at runtime to an address in the .map file, you usually subtract $401000 from the runtime memory address. Values may be different!

In this case, the resulting value 136CF would be the item that you want to look for withing the 0001 code segment in the .map file. You are not likely to find an EXACT match since the code that crashed is most likely in the middle of a function and rarely at the very beginning of the function. So you would look for a .map item whose starting address is closest to 136CF without exceeding it.

You did not show the entire .map file, so there is no item in your snippet that is close to 136CF. But the actual crash is not in CrashMe itself, like you are expecting. It is actually inside of another function that CrashMe() calls internally. Setting the TMemo.Text property calls TWinControl.SetText(), which calls TControl.GetText(), which calls TWinControl.GetTextLen(), which crashes when trying to access the FHandle or FText data member of an invalid TMemo object:

procedure TWinControl.SetText(const Value: TCaption);
begin
  if GetText <> Value then // <-- here
  begin
    if WindowHandle <> 0 then
      Perform(WM_SETTEXT, 0, string(Value))
    else
      FText := Value;
    Perform(CM_TEXTCHANGED, 0, 0);
  end;
end;

function TControl.GetText: TCaption;
{$IF DEFINED(CLR)}
begin
  Result := GetTextPiece(GetTextLen);
end;
{$ELSE}
var
  Len: Integer;
begin
  Len := GetTextLen; // <-- here
  SetString(Result, PChar(nil), Len);
  if Len <> 0 then
  begin
    Len := Len - GetTextBuf(PChar(Result), Len + 1);
    if Len > 0 then
      SetLength(Result, Length(Result) - Len);
  end;
end;
{$IFEND}

function TWinControl.GetTextLen: Integer;
begin
  if WindowHandle <> 0 then // <-- here
    Result := Perform(WM_GETTEXTLENGTH, 0, 0)
  else
    Result := Length(FText); // <-- or here
end;

When diagnosing an AV, if you want to map the crash to CrashMe(), it is not enough to have the memory address of the AV, since that memory address is not inside of CrashMe() itself. You need a full stack trace leading up to the AV to show that CrashMe() was called at some point and made subsequent calls that caused the actual AV. A .map file will not help you get a stack trace, you need a runtime library that handles that at the time of the crash, such as JclDebug, MadExcept, EurekaLog, etc.

like image 75
Remy Lebeau Avatar answered Nov 07 '22 10:11

Remy Lebeau


The detailed map file should contain a section that is sorted by identifier name and another section that is sorted by address (actually by RVA, Relative Virtual Address). Your technique to convert the physical address to RVA is correct. Just go to the section which is sorted by address and find the address closest to, but not greater than $136cf. That should be the function where the crash is happening.

You do, however, need to make sure you're building with the debug DCUs. Otherwise, you'll only see addresses for the parts of the program that is your code.

For DLLs/Packages, they will most likely load at addresses other than the default specified in the PE file. In this case, you need to find the base address of that specific module. Open the Modules view (Ctrl-Alt-M or View|Debug Views|Modules...). Look for the modules whose base address is closest to, but not greater than the address of the crash. That module's address will be the "base address". Use this value to calculate the RVA and then go to that module's MAP file to look up the location.

like image 2
Allen Bauer Avatar answered Nov 07 '22 10:11

Allen Bauer