Debugging Access Violation errors?

What tips can you share to help locate and fix access violations when writing applications in Delphi?

I believe access violations are usually caused by trying to access something in memory that has not yet been created such as an Object etc?

I find it hard to identify what triggers the access violations and then where to make the required changes to try and stop/fix them.

A example is a personal project I am working on now. I am storing in TTreeView Node.Data property some data for each node. Nodes can be multiple selected and exported (the export iterates through each selected node and saves specific data to a text file - the information saved to the text file is what is stored in the nodes.data). Files can also be imported into the Treeview (saving the contents of the text files into the node.data).

The issue in that example is if I import files into the Treeview and then export them, it works perfect. However if I add a node at runtime and export them I get:

"Access Violation at address 00405772 in module 'Project1.exe'. Read of address 00000388."

My thoughts on that must be the way I am assigning the data to created nodes, maybe differently to the way I assign it when they are imported, but it all looks ok to me. The access violation only shows up when exporting, and this never happens with imported files.

I am NOT looking for a fix to the above example, but mainly advice/tips how to find and fix such type of errors. I don't often get access violations, but when I do they are really hard to track down and fix.

So advice and tips would be very useful.


Solution 1:

It means your code is accessing some part of the memory it isn't allowed to. That usually means you have a pointer or object reference pointing to the wrong memory. Maybe because it is not initialized or is already released.

Use a debugger, like Delphi. It will tell you on what line of code the AV occurred. From there figure out your problem by looking at the callstack and local variables etc. Sometimes it also helps if you compile with Debug DCUs.

If you don't have a debugger because it only happens on a client side, you might want to use MadExcept or JclDebug to log the exception with callstack and have it send to you. It gives you less details but might point you in the right direction.

There are some tools that might be able to find these kind of problems earlier by checking more aggressively. The FastMM memory manager has such options.

EDIT

"Access Violation at address 00405772 in module 'Project1.exe'. Read of address 00000388."

So your problem results in a AV at addresss 00405772 in module 'Project1.exe'. The Delphi debugger will bring you to the right line of code (or use Find Error).

It is trying to read memory at address 00000388. That is pretty close to 00000000 (nil), so that would probably mean accessing some pointer/reference to an array or dynamic array that is nil. If it was an array of bytes, it would be item 388. Or it could be a field of a rather large object or record with lots of fields. The object or record pointer/reference would be nil.

Solution 2:

I find that the really hard-to-find access violations don't always occur while I'm running in a debugger. Worse yet, they happen to customers and not to me. The accepted answer mentions this, but I really think it should be given more detail: MadExcept provides a stack traceback which gives me valuable context information and helps me see where the code fails, or has unhandled exceptions (it's not just for access violations). It even provides a way for customers to email you the bug reports right from inside your program. That leads to more access violations found and fixed, reported by your beta testers, or your users.

Secondly, I have noticed that compiler hints and warnings are in fact detecting for you, some of the common problems. Clean up hints and warnings and you might find many access violations and other subtle problems. Forgetting to declare your destructors properly, for example, can lead to a compiler warning, but to serious problems at runtime.

Thirdly, I've found tools like Pascal Analyzer from Peganza, and the audits-and-metrics feature in some editions of Delphi, can help you find areas of your code that have problems. As a single concrete example, Pascal Analyzer has found places where I forgot to do something important, that lead to a crash or access violation.

Fourth, you can hardly beat the technique of having another developer critique your code. You might feel a bit sheepish afterwards, but you're going to learn something, hopefully, and get better at doing what you're doing. Chances are, there is more way than one to use a tree view, and more way than one to do the work you're doing, and a better architecture, and a clean way of doing things is going to result in more reliable code that doesn't break each time you touch it. THere is not a finite list of rules to produce clean code, it is rather, a lifetime effort, and a matter of degrees. You'd be surprised how innocent looking code can be a hotbed of potential crashes, access violations, race conditions, freezes and deadlocks.

Solution 3:

I would like to mention one more tool, that I use when other tools fail to detect AV. It's SafeMM (newer version). Once it pointed me to the small 5 line procedure. And I had to look more than 10 minutes at it, in order to see the AV that happened there. Probably that day my programming skills wasn't at their maximum, but you know, bad thing tend to happen exactly at such days.

Solution 4:

Just want to mention other debugging or "code guard" techniques that were not mentioned in previous answers:

"Local" tools:
* Use FastMM in DebugMode - have it write zeros each time it dealocates memory. This will make your program PAINFULLY slow but you have a HUGE chance to find errors like trying to access a freed object.
* Use FreeAndNil(Obj) instead of Obj.Free. Some, people were complaining about it as creating problems but without actually providing a clear example where this might happen. Additionally, Emarcadero recently added the recommendation to use FreeAndNil in their manual (finally!).
* ALWAYS compile the application in Release and Debug mode. Make sure the Project Options are correctly set for debug mode. The DEFAULT settings for Debug mode are NOT correct/complete - at last not in Delphi XE7 and Tokyo. Maybe one day they will set the correct options for Debug mode. So, enable things like:

  • "Stack frames"
  • "Map file generation (detailed)"
  • "Range checking",
  • "Symbol reference info"
  • "Debug information"
  • "Overflow checking"
  • "Assertions"
  • "Debug DCUs"
  • Deactivate the "Compiler optimizations"!

3rd Party Tools:

  • Use MadShi or EurekaLog (I would recommend MadShi over EurekaLog)
  • Use Microsoft's ApplicationVerfier