Wednesday, January 16, 2008

Development in D with use of backtrace hack

With every release of dmd since DMD 1.020 team0xf announces release of phobos backtrace hack. Now with dmd0xf we present Tango backtrace hack. But what is it really for? In this short article article I will try to attract you with it and show some examples how useful it is.

First of all, it's for Windows developers. What does it do? When you install the hack in your compiler, and compile your application with -g switch, whenever your Windows program crashes it prints stacktrace. Lets take an example:

void main(){
assert(false,"oups!");
}
We will compile it with rebuild -g -exec Main.d and we here's what we get:
Tango backtrace hack intiated
Unhandled D exception!
Error: oups! (tango.core.Exception.AssertException) in Main.d [2]
backtrace:
0040417a onAssertErrorMsg (+36)
004023e5 extern (C) int dmain2.main(int, char**) . void runMain(void*) (+d)
0040e616 mainCRTStartup (+aa)
7c816fd8 ???
00000001 ???
As we can see an AsssertException was thown. We can read that it came from Main.d [2] so in second line of our code (in file Main.d). As the example is not very complicated, backtrace is short and not very attractive, let's do something more complicated:

module Main;

int div(int a, int b){
assert(b!=0);
return a/b;
}

void foo(){
div(8,3);
div(2,2);
div(7,0);
div(8,2);
}

void main(){
foo();
}

rebuild -g -exec Main.d again, and let's look at our backtrace:
Tango backtrace hack intiated
Unhandled D exception!
Error: Assertion failure (tango.core.Exception.AssertException) in Main.d [4]
backtrace:
0040413e onAssertError (+2e)
00402028 int Main.div(int, int) (+18) Main.d:5
00402059 void Main.foo() (+25) Main.d:12
00402071 _Dmain (+9) Main.d:16
00402425 extern (C) int dmain2.main(int, char**) . void runMain(void*) (+d)
0040e5d6 mainCRTStartup (+aa)
7c816fd8 ???
00000001 ???
OK, so we can now trace full route AssertException went from the place it was thrown. So Main.d [4] here the exception was thrown. Let's see it's route (top to bottom). Main.div(int, int) (+18) Main.d:5 is route start. The number 5 is a bit tricky here, and might be confusing. Of course routes' start is in line 4. When we know that it was Main.div(int, int) which caused our program to break, we can say it was called from void Main.foo() in Main.d:12. Once again real code was in line 11 (see how it works?). foo() was colled from _Dmain in Main.d:16 (and that line has appropriate number).

That's how, more or less, we can trace all exceptions, including assert. This go for all design by contract. We can trace things in unittests also, see what exactly fails in class invariants and so on. But there's some more!

There are common bugs we all make from time to time our hack helps to fights with. Like array index out of bounds:
int[] foo;

void crssh(){
foo[667]=66;
}

void main(){
crssh();
}
By analyzing stacktrace, we can find out that all happens in void Main.crash() Main.d:3 . Also we can trace how it happened. This example is an obvious one, but with hash maps or in more complicated code finding where bug is can become complicated. Next common mistake, specially for newcomers from c++ world:
class Foo { int a=1; }
void main () {
Foo foo;
int i=foo.a;
}
Access Violation is another bug every coder is familiar with. Here the bug is simple, we created reference set to null and trying to access it. _Dmain (+9) Main.d:4 shows us where we are accessing null reference.

Using design by contract (like asserts and unittests) with backtrace hack is the way to make debugging easy and quick. There will be very rare cases when you will need to run actual debbugger.

It might be obvious but I was asked to write it: backtrace will only be shown on uncaught exceptions. If you catch the exception, you dont get the trace.

I hope this short Article made you more familiar with this powerful tool. Of course you can leave without it. You can use debbugers or just analysing code, but analyzing stacktrace is the simplest method there is. What's more having it will never harm you, but surely will help. Happy debugging.

PS
Here I used Tango and tango backtrace hack, of course working with phobos backtrace hack is the same.

1 comments:

sergk said...

Well said, Peter, nice and simple explanation.

I always wonder why standard library do not provide this capability by default.