Resolve address of AccessViolation in the map file
Asked Answered
L

2

10

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?

Lexical answered 5/7, 2016 at 13:44 Comment(5)
I so much hate when people downvote a question without telling what is wrong with the question. I have googled for hours and didn't find out the correct convertion technique between these memory addresses.Lexical
You can lookup the code in Jedi Code Library.. But actually its kind of scanning the address ranges which are provided in the map file. It depends on the detalization of the map file (to line numbers or just routines) how precise you can tell about the place where exception happened... But mainly you can use the JvDebugHandler for that - there is everything done already!Hellas
Put a breakpoint in your app and run ot until you reach the breakpoint. Then, in the IDE go to Search | Find error and enter the address.Headset
The AV does not occur in CrashMe, it occurs elsewhere whilst the assignment code is running. Raise an exception in CrashMe yourself if your intention is to have an exception there.Christopherchristopherso
In your particular example it's in TControl.GetTextLen. As you can see the problem address is not helpful without a stack trace.Christopherchristopherso
B
10

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.

Bethezel answered 5/7, 2016 at 18:44 Comment(0)
B
3

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.

Bumpkin answered 5/7, 2016 at 18:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.