In my last article “Jwscl and FreeAndNil” there were some great comments on the source design. Oliver told me to use a guarded memory page so the memory is always invalid. With his information I wrote a source code that creates a pointer which always triggers an access violation.
Unfortunately, Access Violations are rather common and thus nobody can tell why it happens at a first glance. So I added an exception handler that maps this special access violation to a separate exception class (I called it EJwsclAccessedGuardPointerException).
uses
ExceptionLog,
//JwaWindows, //or
JwaWinBase, JwaWinNT, JwaNtStatus,
SysUtils;
type
EJwsclAccessedGuardPointerException = class(Exception)
public
ExceptionRecord: PExceptionRecord;
end;
resourcestring
SGuardPageException = 'It was tried to access a guarded pointer. This '+
'pointer was freed previously and is now accessed without initialization. '+
'JWSCL may guard its class members in this way. You should check for '+
' accesses to e.g. class members which instance was freed.'#13#10+
'Exception was raised at this address: $%p';
var
GuardPtr : Pointer;
OldExcpObj : function (P: PExceptionRecord): Exception;
OldExitProc : procedure;
procedure MyExitProcessProc;
begin
VirtualFree(GuardPtr, 0, MEM_RELEASE);
if @OldExitProc <> nil then
OldExitProc;
end;
function MyGetExceptionObject(P: PExceptionRecord): Exception;
var ErrorCode : Integer;
begin
if (P.ExceptionCode = STATUS_ACCESS_VIOLATION) and
(P.ExceptObject = GuardPtr) then
begin
result := EJwsclAccessedGuardPointerException.CreateFmt(SGuardPageException,
[P.ExceptionAddress]);
EJwsclAccessedGuardPointerException(Result).ExceptionRecord := P;
end
else
begin
result := OldExcpObj(P);
end;
end;
var
T : TObject;
SysInfo : TSystemInfo;
begin
OldExcpObj := ExceptObjProc;
ExceptObjProc := @MyGetExceptionObject;
OldExitProc := ExitProcessProc;
ExitProcessProc := MyExitProcessProc;
//On 32Bit OS call
//GetSystemInfo(@SysInfo);
//On WOW64Bit OS call (use IsWow64Process)
GetNativeSystemInfo(@SysInfo);
GuardPtr := VirtualAlloc(
Pointer($BADC0DE),// __in_opt LPVOID lpAddress,
SysInfo.dwPageSize,// __in SIZE_T dwSize,
MEM_RESERVE,// __in DWORD flAllocationType,
PAGE_NOACCESS// __in DWORD flProtect
);
if GuardPtr = nil then
RaiseLastOSError;
T := TObject(Pointer(GuardPtr));
T.Free;
end.
Since the object has this guard pointer value (which may be dynamic) a call to Free (or Destroy) will fail with the exception EJwsclAccessedGuardPointerException. Be aware that there is no actual memory allocated. It is all about virtual memory.
The MyExitProcessProc is called right before the process exits. There is another exit handler called ExitProc that is called before the units are finalized. So I’m stuck with ExitProcessProc which is fine though.
I must say that I like this approach since it seems to be safer than just using a nearly arbitrary magic value. The value is still there so you can see it as an invalid memory address in a debugger. However, Assigned or similar workarounds will not work because the memory location can vary.
And of course the exception message clearly states what happened here. There could added more information like stack trace but that I leave to you (or the JCL).
Be aware that there still might be errors. This code is hot.
What is your opinion?
8 Responses
Xepol
25|Feb|2010 1I think the FreeAndNil issue is a tempest in a teapot. While it is not a magic bullet, it helps solves more problems than it creates.
People have resorted to fake, contrived examples that had nothing to do with FreeAndNil to prove that it is evil, and now people are creating fake bogus objects to simulate what using a nil already does?
No matter what you do – none of this addreses copies of pointers that would still be rogue, and really is just a more complicated version of X :=Nil; If Assigned(x) Then …
This whole issue has crossed the line between absurd and what lies beyond.
Christian Wimmer
25|Feb|2010 2This is only about FreeAndNil in destructors. Nothin more or less. Accessing members of freed object is highly problematic. Imo it should crash immediately instead of running. However, I understand that in a release a crashing application doesn’t look good. Thus I think a crashing debug version is the best that can happen.
Allen Bauer
26|Feb|2010 3@Christian,
Yes, the whole issue is not whether or not FreeAndNil itself is bad or not, but merely its use in a destructor. If you’re object is in the throes of destruction, any internal object instance references shouldn’t be happening.
Xepol
26|Feb|2010 4The best solution is to find your bad pointer before release. This is why I keep suggesting that FreeAndNil become MORE common, and Embarcadero add a compiler flag that injects code EVERY time a pointer is dereferenced – if it is nil, throw an exception. Yes, it would be slow – but as with many other compiler flags, it is not intended for deployable apps – only to improve debugging.
This would actually help catch attempting to use invalidated pointers.
You’ve just created a second class of invalided pointer that will ultimately need its own “Assigned” function, which basically just puts the whole thing back where it was started (because Assigned no longer works, it might even be a step backwards)
Additionally, have you taken in to account that object instance pointers have negative offset members, and can have positive offset fields that are larger than your single allocated block? Will referencing them perhaps risk getting past your trap?
Oh, and if I am reading this right, GetNativeSystemInfo is only supported on WOW64 leading to a small problem with the large number of 32 bit only systems still out there. (that is assuming your GetNativeSystemInfo isn’t a front to provide similar functionality on win32 only systems)
The whole problem is getting over thought.
Christian Wimmer
26|Feb|2010 5“The best solution is to find your bad pointer before release. This is why I keep suggesting that FreeAndNil become MORE common, and Embarcadero add a compiler flag that injects code EVERY time a pointer is dereferenced – if it is nil, throw an exception”, well, this approach would upset a lot of people who want fast processing. So I think we could wait for it a long time.
“This would actually help catch attempting to use invalidated pointers.”, and what about this code above? It has the same effect. It works now, we don’t have to wait.
“You’ve just created a second class of invalided pointer that will ultimately need its own “Assigned” function, which basically just puts the whole thing back where it was started (because Assigned no longer works, it might even be a step backwards)”. Well, everything it crackable if you have enough effort. If one doesn’t like it then one can switch it off. I won’t force it because it is merely an aid to find “bad design” accesses after freeing a pointer. If you create an adapted Assigned then you shoot yourself into the foot (You could have switched it off btw.). But how to make it fool proof? Easy, put it into the operating system. But then many applications won’t work anymore…so we’re stuck with it.
“Additionally, have you taken in to account that object instance pointers have negative offset members, and can have positive offset fields that are larger than your single allocated block? Will referencing them perhaps risk getting past your trap?” Well, it was a first design. I still can increase the memory block and set the GuardPointer address anywhere in the middle of the pages. So there should be enough room. My pages are 4096bytes that should be enough for at least 1000 methods pointers and variables in the vmt. Unfortunately, history repeats itself so we’re are stuck (again) on predicting the future. We need to set some value and hope that it is sufficient. Recall that it is just an aid and not a fool proof concept.
@GetNativeSystemInfo: Yes, I forgot to mention it. I added the other version for 32bit only so everybody can try it out.
Oliver
26|Feb|2010 6I loves my CPP
What can I say. A preprocessor could make a huge difference here, because it would allow to inject information into the debug builds at compile time that don’t require extra magic in the compiler …
This way you cannot only keep a list of allocated blocks without requiring the compiler to offer that, you can even tell where (source file and line) a certain block originated, whether it was freed, from where (source file and line) it was freed and in the latter case you can mark a pseudo-freed page as reserved (i.e. not committed) and catch the exception. The nice part about it is that you get bounds checking with a little extra of preprocessor help
Ritsaert Hornstra
26|Feb|2010 7You should not set GuardPtr to the base address (what if there is a valid address in front and the pointer is accessed for some negative offset first (like a string, dyn array etc). You would not catch that. Place it somewhere in the middle of the whole page and you will protect yourself to positive and negative offsets.
You might want to allocate moe that a single page. Consider the following:
type
TSomething = class
public
FA: array [0..9999999] of Byte;
FB: Integer;
end;
var
L: TSomething;
begin
L := nil;
L.FB := 9999;
// this might even succeed because you will acess the base pointer plus 9999999 + 1 + sizeof( Pointer ) = address 10000004. You will have the same problem with your guarded page…
Christian Wimmer
27|Feb|2010 8Yes, I talked about this in comment #5. Unfortunately, increasing the size is not going to solve this problem. It just makes it less probably.
Leave a reply
You must be logged in to post a comment.
Search
Paypal donation (EUR)
Download Win 7 Search Provider
Categories
Archives
Tags
Recent Posts
Recent Comments
Blogroll
JEDI Sites
Pages
A design creation of Design Disease
Copyright © 2007 - JEDI Windows API - is proudly powered by WordPress
InSense 1.0 Theme by Design Disease brought to you by HostGator Web Hosting.