Re: Strange access violations
From: Martin Harvey (Demon account) (martin_at_nospam_pergolesi.demon.co.uk)
Date: 10/01/04
- Next message: Martin Harvey (Demon account): "Re: Strange access violations"
- Previous message: Bjørge Sæther: "Re: TQuery and TTable"
- Maybe in reply to:(deleted message) L D Blake: "Re: Strange access violations"
- Next in thread: Martin Harvey (Demon account): "Re: Strange access violations"
- Reply: Martin Harvey (Demon account): "Re: Strange access violations"
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
Date: Thu, 30 Sep 2004 22:29:36 GMT
On Thu, 30 Sep 2004 14:52:14 -0400, Jud McCranie
<youknowwhat.mccranie@adelphia.net> wrote:
>On Thu, 30 Sep 2004 17:49:35 GMT, "Martin Harvey (Demon account)"
><martin@nospam_pergolesi.demon.co.uk> wrote:
>
>>When you shove that through the "find error", remember to include the
>>leading $ character, and adjust for offsets.
>
>How do you adjust for offsets?
>
OK ... here's everything you ever need to know (ish) about hunting
down application crashes, and all in one usenet post. I'll cover:
- Sorts of crash and demo app.
- Sorts of crash dump you may encounter in your travels.
- Reading a map file to go from a crash dump to a source line, and two
very important magic numbers you'll need to know.
First of all, here's an app that produces the 3 sorts of user mode
crashes you're likely to see in Delphi, and I'll show example error
messages and debugging techniques.
Button1: Crash caught by message handling loop.
Button2: Crash caught by RTL.
Button3: Crash not caught by app, but instead caught by OS.
************************************
unit CrashForm;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs,
StdCtrls;
type
TCrashFrm = class(TForm)
Button1: TButton;
Button2: TButton;
Button3: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
TCrashThread = class(TThread)
public
procedure Execute;override;
end;
var
CrashFrm: TCrashFrm;
implementation
{$R *.DFM}
function CrashBigTime(Arg: Pointer): DWORD; stdcall;
begin
TCrashFrm(nil).Button1Click(nil);
end;
procedure TCrashFrm.Button1Click(Sender: TObject);
begin
// Application.OnException := ExceptHandler;
PChar(0)^ := 'F';
end;
procedure TCrashThread.Execute;
begin
CrashFrm.Button1Click(Self);
end;
procedure TCrashFrm.Button2Click(Sender: TObject);
var
CrThread: TCrashThread;
begin
CrThread := TCrashThread.Create(true);
CrThread.FreeOnTerminate := true;
CrThread.Resume;
end;
procedure TCrashFrm.Button3Click(Sender: TObject);
var
ThreadHandle: THandle;
ThreadID: DWORD;
begin
ThreadHandle := CreateThread(nil, 0, @CrashBigTime, nil, 0,
ThreadID);
end;
end.
************************************
For the first two, I basically get the error message: "Access
violation at address 0043CBCE in module "Crash.exe" write of address
00000000"
What happens on the third is slightly more complex - depending on
whether a system debugger is registered:
The current system debugger is controlled by the registry key
HKLM\Software\Microsoft\Windows NT\AeDebug. Some MS documentation says
that under Win9x the debugger info is stored in the [AeDebug] section
of WIN.INI file, but it appears that the registry key takes
precendence over the INI file.
I'll skip the details, you can search on google,
I could actually set the Delphi debugger to be the default system
debugger whenever any application crashes (and I have done in the
past), which would allow me to use the CPU view on the program at the
point it crashes, but at the moment, my .reg file looks like this:
REGEDIT4
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows
NT\CurrentVersion\AeDebug]
"UserDebuggerHotKey"=dword:00000000
"Debugger"="drwtsn32 -p %ld -e %ld -g"
"Auto"="1"
What this does when an application crashes is automatically generate
two files: USER.DMP corresponding to the dump of the address space of
the program when it crashed, and drwtsn32.log which includes a nice
decode of the dump information.
When I run my program, perform a "hard" crash on it, and then get out
the drwatson log file, it looks like this - I'm skipping lots of extra
info that is not really pertinent:
Application exception occurred:
App: (pid=303)
When: 9/30/2004 @ 22:27:8.294
Exception number: c0000005 (access violation)
This gives us the process ID that faulted. We then get a process list,
that allows us to look up which application it was:
*----> Task List <----*
0 Idle.exe
2 System.exe
...
342 delphi32.exe
303 Crash.exe
So we know that crash.exe died.
We then get a dump of pertinent state for all the threads in the
application:
State Dump for Thread Id 0x193
eax=00524098 ebx=00000000 ecx=00524098 edx=00000000 esi=0012ff78
edi=027600b8
eip=77eaea67 esp=0012ff44 ebp=0012ff70 iopl=0 nv up ei pl nz
na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000
efl=00000202
function: WaitMessage
77eaea5c b80e120000 mov eax,0x120e
77eaea61 8d542404 lea edx,[esp+0x4]
ss:0102e94b=????????
77eaea65 cd2e int 2e
77eaea67 c3 ret
Clearly it's not this thread, it's currently blocked in a call to the
kernel. However, this next one clearly contains the problem:
State Dump for Thread Id 0x19e
eax=00000000 ebx=00000000 ecx=00000001 edx=00000000 esi=00000000
edi=0042326b
eip=0043cbce esp=00deffb0 ebp=00deffb8 iopl=0 nv up ei pl zr
na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000
efl=00000246
function: <nosymbols>
0043cbb9 8bec mov ebp,esp
0043cbbb 53 push ebx
0043cbbc 33d2 xor edx,edx
0043cbbe 33c0 xor eax,eax
0043cbc0 e807000000 call 0043cbcc
0043cbc5 8bc3 mov eax,ebx
0043cbc7 5b pop ebx
0043cbc8 5d pop ebp
0043cbc9 c20400 ret 0x4
0043cbcc 33c0 xor eax,eax
FAULT ->0043cbce c60046 mov byte ptr [eax],0x46
ds:00000000=??
0043cbd1 c3 ret
0043cbd2 8bc0 mov eax,eax
0043cbd4 8bd0 mov edx,eax
0043cbd6 a104e84300 mov eax,[0043e804]
ds:0043e804=00be1fd8
0043cbdb e8ecffffff call 0043cbcc
0043cbe0 c3 ret
0043cbe1 8d4000 lea eax,[eax]
ds:00efea06=????????
0043cbe4 b101 mov cl,0x1
0043cbe6 b201 mov dl,0x1
0043cbe8 a154cb4300 mov eax,[0043cb54]
ds:0043cb54=0043cba0
0043cbed e82e4afdff call 00411620
*----> Stack Back Trace <----*
FramePtr ReturnAd Param#1 Param#2 Param#3 Param#4 Function Name
00deffb8 77f04ede 00000000 0042326b 00000000 00000000 <nosymbols>
00deffec 00000000 00000000 00000000 00000000 00000000
kernel32!lstrcmpiW
00000000 00000000 00000000 00000000 00000000 00000000 !<nosymbols>
We get a nice view of the state of the registers, a disassembly of the
code that faulted, and a stack back trace. Unfortunately, because
Delphi apps use the register calling convention, and don't always
create stack frames, then this backtrace is stuffed. If you want
useful stack traces, then set "stack frames" to ON, and include TD
debug info in your application.
Anyways, at this point, all roads lead to rome, in that we know that
the instruction at 0043cbce caused the access violation. How do we
work out where that is?
Well, the easiest offline way is to use a map file - you can turn it
on in the linker options (I just go for "detailed" every time). Most
professional software shops I have worked for have insisted that every
module of software you ship has archived map files, and here's the
reason why: you can make sense out of the obscure error messages.
Warning: Map files may change from build to build, even if the code
doesn't. You should always use the exact map file produced with the
exact build.
Anyways, I built a map file along with the executable. It's *VERY*
big, so I'll skip 99% of it, to show you the important bits.
However, just before we get there, there's something important we need
to look at: part of the image (executable) header. Luckily, my version
of quickview allows you to read this for executable files, and gives
me this:
Image optional header.
Magic: 010b
Linker version: 2.25
Size of code: 00007400
Size of initialized data: 00002600
Size of uninitialized data: 00000000
Address entry point: 000081ec
Base of code: 00001000
Base of data: 00009000
Image base: 00400000
....
Now, what's important about this is that it's indicating how, at load
time, the loading process maps the executable into memory. In
particular, it's letting you know that the image as a whole is mapped
at address 00400000, and that on top of that, the code segment is
mapped at address 00001000. Later on in the executable header info,
there is in fact a segment map, which confirms these figures.
By and large, for Delphi programs, you can asume that these two
numbers remain constant. As it turns out, you can tweak the image base
in the project options, but there are rarely any reasons to.
So, having seen that we can normally take these numbers as given,
let's take a look at our map file. First of all, it starts out with a
basic map of the seguments:
Start Length Name Class
0001:00000000 0003BDC4H .text CODE
0002:00000000 00000D04H .data DATA
0002:00000D04 0000080DH .bss BSS
Note that the lengths given here are the actual lengths for the
segments (the lengths given earlier are a slightly different kettle of
fish). No particularly big deal here - code is code, data is constant
data, and BSS is data that should be inititalised to 0 at runtime.
We then get a detailed segment map. This is where it starts to get
useful.
Detailed map of segments
0001:00000000 00004B1C C=CODE S=.text G=(none) M=System
ACBP=A9
...
0001:0003B720 0000026D C=CODE S=.text G=(none) M=Dialogs
ACBP=A9
0001:0003B990 000002BC C=CODE S=.text G=(none) M=CrashForm
ACBP=A9
0001:0003BC4C 00000175 C=CODE S=.text G=(none) M=Crash
ACBP=A9
The useful bit here is that we can use this to easily work out which
unit a certain crash occured in. Take care when looking in this
segment map to look for stuff where: "S=.text" ... you're looking for
code addresses, not data addresses.
So, taking our "CrashForm" unit, we can determine that it starts at
0003B990 and has length 000002BC. Remembering our image base and
segment base from earlier, we can determine that this covers the range
of virtual adddresses from:
ImgBase + Text Segment Base + Start
to:
ImgBase + Text Segment Base + Start + Length
or:
00400000 + 00001000 + 0003B990 = 0043C990
to
00400000 + 00001000 + 0003B990 + 000002BC = 43CC4C
And, sure enough, our actual crash address (0043CBCE) is between those
two.
Conversely, if you want to go the other way from your crash address,
then subtract the two offsets, and go hunting for the resulting offset
in the text segment, which is: 3BBCE, and additionally, we know that
in our particular unit, it's at an offset of 23E.
The next section in the map file is:
" Address Publics by Name"
We'll skip this, as we're not interested in looking things up by name,
instead we'll go to:
" Address Publics by Value"
section to look up our magic number 3BBCE:
0001:0003B918 Finalization
0001:0003B974 Dialogs
0001:0003BBB8 CrashBigTime
0001:0003BBCC TCrashFrm.Button1Click
0001:0003BBD4 TCrashThread.Execute
0001:0003BBE4 TCrashFrm.Button2Click
0001:0003BBFC TCrashFrm.Button3Click
0001:0003BC14 Finalization
0001:0003BC44 CrashForm
0001:0003BC4C Finalization
Sure enough, we find that it's just after TCrashFrm.Button1Click.
Even better, once you know which source file you're in, the map file
then lets you go even further, and at the bottom, it has:
Line numbers for CrashForm(CrashForm.pas) segment .text
36 0001:0003BBB8 37 0001:0003BBBC 38 0001:0003BBC7 43
0001:0003BBCC
44 0001:0003BBD1 48 0001:0003BBD4 49 0001:0003BBE0 55
0001:0003BBE4
56 0001:0003BBF2 57 0001:0003BBF6 58 0001:0003BBFB 64
0001:0003BBFC
65 0001:0003BBFD 66 0001:0003BC10 68 0001:0003BC44 68
0001:0003BC4B
In our case, the magic address is just after:
43 0001:0003BBCC
and pulling the appropropriate line (43) from the .pas file we find:
" PChar(0)^ := 'F';"
Hey presto! We've gone from a raw virtual memory address to a line of
code, all without using the debugger or the IDE!
Hope this helps.
MH.
- Next message: Martin Harvey (Demon account): "Re: Strange access violations"
- Previous message: Bjørge Sæther: "Re: TQuery and TTable"
- Maybe in reply to:(deleted message) L D Blake: "Re: Strange access violations"
- Next in thread: Martin Harvey (Demon account): "Re: Strange access violations"
- Reply: Martin Harvey (Demon account): "Re: Strange access violations"
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
Relevant Pages
|