Friday, January 18, 2013

Debugging tutorial

There is no denying that a good way to learn is to explain to others. That's why I thought I would do this write up of practical debugging tips and techniques.

 What is debugging and why do I need it? 

So, what is debugging? Its the part of software development when you hit the "build" button and you get no errors in the code. Yes, there are no problems with how you've written the code for the computer to interpret it, but what about the logical errors? What about the while-loops that never ends and the if-statments that never returns true? This is what debugging is, resolving your logical errors that cause your software to misbehave or crash.

Easiest way to debug is just put a "print(x);" somewhere in the code to see the value of  x at that point. We've all started out debugging this way. But there comes a time when the logic of the programs we write gets too big, and using this "print debugging" won't be enough. Either your code will be filled with loads of  print-calls to check why your code doesn't enter a if-clause or why the values of our variables doesn't make any sense. This type of debugging is also insufficient since it will never actually tell you where your problem is, just that somewhere after this, something is wrong. That's why you should use debugging tools that many (if not all) better IDEs (Integrated Development Environment) ships with. I will have Visual studios 2008 as a base when talking about most of the parts in this tutorial, but your IDE should have something similar.

Below are some of the key features to use when debugging.

One of the main tools you'll be using while debugging is the breakpoints. The name explains its purpose very good; it's a point within your code that will break(or halt) the execution of code. While the code is halted you'll be able to inspect values of variables and, in some cases, even edit them.
Note: The line with the break point has NOT been executed yet.  
Lets look at the code below:
int apples;
apples = 5;
apples = a + 5;

If our breakpoint is set on:
  • Line 1, apples will still not be declared, most (smarter) editors will just move your breakpoint down to line 2 since nothing in your program changes during deceleration.
  • Line 2, apples has been declared but has not been given a value, it will have something like -5468165819 as its value.
  • Line 3, apples has the value of 5.
  • Line 4(not shown), apples will have the value of 10.

But since we don't want to have breakpoints for every line, we have some tools that allow us to "step" in our code.
There are three types of stepping, Step over, Step into, Step out.

Step Over:  The most used one. This basically say "execute the line we're currently on, but halt at the next line".
Step Into: This stepping method allows us to go into functions. If we look at the code below. if we were to have a breakpoint on line 5 and use Step over we would go to line 6 and be able to see the value of ourApples after the function sum has been run. However, if we press Step into, we will jump into the sum function and halt execution on the first line of the function (line 12). We can then continue using Step over to look at every line in this function.
int main()
    int myApples = 5;
    int johnsApples = 10;
    int ourApples = Sum(myApples, johnsApples);
    print ourApples;

int Sum(int a, int b)
    //no need for this, but for showing debugging, we do this.
    int returnSum = a;
    returnSum += b;
    return returnSum;
Step Out: If we realize, "The error we're looking for isn't caused in this function", then Step out is used to exit that function and go back to the line that called the function (read more about keeping track of how other functions call functions in call stack part further down).
Let say we have a breakpoint at line 12 in the code above, if we would press Step out we would end up on line 6 again, with no need of stepping over line 14 to go back.

We also have Resume which will continue the execution of code until we hit the next breakpoint.

To set a breakpoint you often press to the left of the editor and some sort of indicator should appear. You can often right-click on a line and pick "set breakpoint" there aswell. There are some other, more advanced topics about breakpoints that you can read further below.

One key feature is the fact that when you hit a breakpoint, you can edit the values of variables! So lets say that right now you're not intersted why your variable's value makes no sense, but you want to try your an if-loop thats within a if-case. If you variable's value is hogwash, the if-case will never be true and you'll never enter the loop - make a breakpoint at the if-case and just change the value of the variable and try out your look!

Breakpoint has been hit, indicated by the yellow arrow, 
By hovering over a we can see that it has a very small value, 
we could draw the conclusion that it has only be declared bu never given a value.

Watch/Local Window:
These two windows help us keep track of our variables without having to hover over them in our editor. In Visual studios, we have both "watch window" and "local window". (some IDEs might not have the "local" one)

The Watch window is at first empty. You have to add variables to it by right clicking them in your editor and click "add watch". Then you'll be able to see the value of that variable as long as it remains in the memory or you remove them, which could be nice if that variable, for some reason, is not close to your current breakpoint.

The Local window shows all variables active in the current scope and therefore changes quite radical when changing scope (when you exit a loop or function).
The local window, the value of the calculate array is indicated by red, 
that means that the last row edited this variable

This way you can see how multiple variables are modified quickly.

Call Stack:
When your programs get more and more complicated and class-functions are called from multiple places, you sometimes what to know "From where is this function called?". This is what the window Call stack can do for you. In the picture below, 
  • The top row shows where our current breakpoint is, its in the function called MoreCalculations on line 39.
  • The next row shows us that MoreCalculations was called from another function, CalculateSum and it was called on line 24 of that function.
  • The rows after that get more complicated but shows us that CalculateSum was called from our windows main and after that we get to even more complicated entries.

But what to take away from this is that we can use this window to keep track of, when and where was this function called, and we can use the call stack to retrace those steps.
Advanced Techniques
Conditional Breakpoints
Sometimes when working with breakpoints within loops, it could be a pain in the butt to having to click resume 45 times, since the error we're looking for occurs on the 45th time the loop runs. That's why we want to use conditional breakpoints. There are often two methods for this.

  • The first one is to tell your editor "I only want to break when i've passed this breakpoint 45 times or more" (could be useful in a while(true) loop)
  • Second one is "I want to halt execution when this variable has the value of 45" (useful in for loops e.g i == 45).
This can be useful to speed our debugging processes up.

Disassembly Code
Just before your code is turned into binary, its first turned into Assembly Code. These are CPU specific instructions that are incredibly cryptic and quite difficult to read if you don't know how. This is a very advanced topic that related more to optimization debugging, but in certain cases, the assembler code can help you find the problem to why your software crashes.
I won't talk about it here, because at the time my knowledge for this is quite limited. But if you're intersted, I'd recommend Alex Darby's Low-level Curriculum series over at #AltDevBlogADay

No comments:

Post a Comment