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?
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With