Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

Say you don't have a debugger available, what would be an effective approach to debug code which doesn't work (as expected)?

share|improve this question

closed as not a real question by gnat, AProgrammer, Walter, Glenn Nelson, GlenH7 Jan 2 '13 at 13:18

It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.If this question can be reworded to fit the rules in the help center, please edit the question.

20  
"The most effective debugging tool is still careful thought, coupled with judiciously placed print statements." -- Brian Kernighan –  delnan Feb 22 '11 at 16:24
3  
Totally agree. Most powerful debugging tool is still print statements. Another advice is to debug the actual code and not the comments. –  romeroqj Feb 22 '11 at 17:26
4  
@jromero: i wouldn't say print statements are the "most powerful". Most widespread and easy to use, sure. –  whatsisname Feb 22 '11 at 17:35
1  
That Kernighan quote makes me wish I could downvote comments. Print statement debugging is a tool of last resort. –  Mason Wheeler Feb 22 '11 at 18:07
2  
@Mason: The question assumes debuggers are not available (so the "real" way to trace execution is gone), so what else would you use to trace execution? –  delnan Feb 22 '11 at 20:57
show 9 more comments

9 Answers

up vote 6 down vote accepted

There is a number of techniques:

  1. Logging. If you've got no files access, log onto a serial console, or whatever output device is available. It is a good idea to always write your code with logging in mind, allowing a conditional compilation for different logging levels, from 'none' to 'overbloated'.

  2. Cutting it down. Exclude the parts of your code around a suspected bug point one by one, and analyse what you've done when the bug disappears.

  3. Assertions or contracts. It is a good idea to stuff your code with asserts from the very beginning. They not only help with debugging, but also serve as an additional documentation for your code.

  4. Similar to 2. - vary your input and reshuffle the code unless the bug disappears or changes its behaviour. It is often a good idea to play with various optimisation levels (if you're coding in C or C++), as pointer-related bugs tend to be extremely volatile in their behaviour.

  5. An automated version of 3. - use the instrumented code, e.g., run the binary under valgrind.

And of course there are many more tools and tricks, depending on the nature of your execution environment and your code.

share|improve this answer
1  
-1: Cannot agree 4). You appear to be proposing it as a solution to the bug? If so that is evil and you have a problem. All it does is make the symptoms go away- it's still there, you have just hidden it.... for now.... –  mattnz Sep 27 '12 at 2:02
    
@mattnz, mind explaining? I propose this approach as an alternative to an interactive debugger. Which, in turn, only highlights symptoms, not the actual causes. 4) is a way to identify a problem, not a solution. In fact, my approach is much better than debugging in most cases, since it gives a better coverage. So, it is likely you did not understand what I propose. Try reading it again. –  SK-logic Sep 27 '12 at 4:25
    
I have seen developers use the actions posed in Step 4 to "fix" a defect using the "I could make it happen, now I cannot, I have fixed it - ship it". Your post suggests this method delivers valid bug fixes. –  mattnz Sep 27 '12 at 21:03
    
@mattnz, no, it does not suggest anything like this. I'm describing a way to investigate a bug, not to fix it. Debuggers do not fix bugs, and the question was about an alternative to a debugger. –  SK-logic Sep 29 '12 at 9:36
add comment

Get a colleague and explain the problem in detail while you walk over the troublesome code step by step.

Frequently the act of explaining make it clear to either your colleague or yourself.

share|improve this answer
11  
+1 And if you don't find a colleague, use a teddy bear. –  Péter Török Feb 22 '11 at 16:40
4  
@Péter Török: I dunno... teddy bears just tend to stare back with their cold dead button eyes... ignoring everything you say, making you feel worthless, tiny, insignificant... It makes debugging with a teddy bear... difficult. –  FrustratedWithFormsDesigner Feb 22 '11 at 16:42
1  
@FrustratedWithFormsDesigner, please see the link I added. –  Péter Török Feb 22 '11 at 16:44
2  
@Peter, I fear that having a shelf full of teddybears ready for colleagues to grab for debugging, might make the wrong impression on customers. –  user1249 Feb 22 '11 at 17:49
1  
@FrustratedWithFormsDesigner: And how does that differ from many colleagues –  mattnz Sep 27 '12 at 2:04
add comment

Is there a logging system to manage program output? Is there at least a console to print to or files you can write to? Using consoles or log files are a way you can debug without a debugger. Give the program an input such that you know what the output should be, then verify that the output works and make sure your logging gives you plenty of details of the process. Then try with an input that gives the wrong output. hopefully, the logs will give you a detailed trace of what went wrong.

share|improve this answer
    
You may use anything BUT a debugger of the kind of gdb or what you would find in your IDE –  Anto Feb 22 '11 at 16:35
    
@Anto: That sounds like a line from a homework assignment, but in that case logging to a file or the console is not using a debugger such as "gdb or what you find find in your IDE". gdb and other debuggers allow you to step through your code line by line and inspect the values of variables as the program runs. Debugging-by-log means you have to use a trace (in file or console) of the program's execution after it finishes, and figure out what happened. –  FrustratedWithFormsDesigner Feb 22 '11 at 16:38
    
I know, therefore, what you recommended is allowed. No, this isn't any homework assignment; I'm in middle school and we have no programming/cs at all. –  Anto Feb 22 '11 at 16:42
2  
@Anto: Ok. The only downside to this method is if you are trying to debug a program that has synchronization issues. For example, if it's an IPC problem then your print/log statements might affect how fast the processes speak to each other and having logging turned on or off may affect whether the problem is reproduced (in this case, you really have to go with @Thorbjørn Ravn Andersen's advice). In some cases, logging can severly degrade performance, but usually only when lots of logging in a large system when processing very large amounts of data... but something to be aware of. –  FrustratedWithFormsDesigner Feb 22 '11 at 16:45
add comment

Depends. Did it work before? If the code that used to work broke all of a sudden, then you should very carefully examine the most recent changes.

share|improve this answer
2  
This approach should not be understated: revision history is a great way to identify errors in code that previously worked -- even when adding new features. –  edA-qa mort-ora-y Jun 26 '11 at 9:48
2  
I've heard that 'git bisect' automates this task somewhat. I have yet to try it though. –  Clayton Stanley Sep 27 '12 at 1:33
add comment

1) Do whatever you need to do to make the bug 100% reproducible, or as close to 100% as you can

2) Trace back the problem, using printf() or other logging facility. This is an art, though, and it depends on the nature of the bug.

If you have absolutely no idea about the location of the bug, but for example you know a condition becomes false at some point (the state of the program broke - let's call it isBroken()), you can do a drill down / partition search to figure out the location of the problem. For example, log the value of isBroken() at the beginning at end of major methods:

void doSomething (void)
{
    printf("START doSomething() : %d\n", isBroken());
    doFoo();
    doBar();
    printf("END doSomething() : %d\n", isBroken());
}

If in the log you see

START doSomething() : 0
END doSomething() : 1

you know something went wrong there. So you remove all the other logging code, and try this new version:

void doSomething (void)
{
    printf("START doSomething() : %d\n", isBroken());
    doFoo();
            printf("AFTER doFoo() : %d\n", isBroken());
    doBar();
    printf("END doSomething() : %d\n", isBroken());
}

Now in the log you may see this

START doSomething() : 0
AFTER doFoo() : 0
END doSomething() : 1

So now you know doBar() triggers the bug, and you can repeat the procedure above in doBar(). Ideally, you'll narrow down the error to a single line.

Of course this may help you reveal the symptoms of the bug and not the root cause - for example, you find a NULL pointer that shouldn't be NULL, but why? You can then log again, but checking for a different "broken" condition.

If you have a crash instead of a broken state, it's more or less the same - the last line of the log gives you a hint of where things break.

share|improve this answer
add comment

After the other answers have failed, there's always binary search debugging:

  1. Eliminate a certain portion (preferably half) of the possible causes (lines of code, revisions, input, etc)
  2. Try to reproduce the problem again.
  3. If the problem persists: go back to step 1.
  4. If you have only one cause (line of code, revision, piece of input, etc) left: hurray! Exit procedure.
  5. Otherwise: revert step 1, and now eliminate the other half.
  6. Go back to step 2.

Note: obviously, this technique only works if you can reliably reproduce the problem.

share|improve this answer
    
1. Eliminate half the possible causes. Problem vanishes. 2. Restore that half, and eliminate the other. Problem vanishes. 3. Eliminate only few possible causes. Problem vanishes if you eliminate any arbitrary 20% of them. 4. Start examining performance, underlying engine and running in circles. 5. Panic. –  SF. Jan 2 '13 at 9:03
    
With big, friendly letters. –  Jeroen Jan 2 '13 at 9:06
add comment

'"The most effective debugging tool is still careful thought, coupled with judiciously placed print statements." -- Brian Kernighan' In it's day it may have been true! The most effective method is to look at the unit tests but my guess is you do not have any.

share|improve this answer
    
I don't have any unit tests as I have no particular project or code; I'm just asking for debugging methods –  Anto Feb 22 '11 at 17:42
    
Why on Earth would you vote this answer down?Stop pissing around in the dark and unit test then. –  user17997 Feb 22 '11 at 17:50
    
It wasn't I who downvoted so go weep to someone else –  Anto Feb 22 '11 at 18:34
2  
Unit testing does not replace debugging, it simply helps compartmentalize and constrain the bugs. Which does make debugging simpler, when a bug happens to appear within a coded unit test. IME, most of the tricky bugs are in component interactions (difficult to unit test), and are spotted much more often in regression-style basher test suites. –  Clayton Stanley Sep 27 '12 at 1:53
    
-1) How do you fix code identified by a broken unit test - you debug it...... Unit tests detect bugs, debuggers and debugging are used to fix the defect. –  mattnz Sep 27 '12 at 21:19
add comment

It depends on the bug.

If the bug is the sort of 'why is the code doing A', then it can be useful to test your own understanding of the code surrounding the location of 'code doing A'. Introduce new code that you expect to generate new bugs (this code should make it do B, this should make it do C). I usually quickly find some new code that generates behavior that I don't expect. Then I wait patiently while my mind builds an updated mental model of the code behavior so that the last change makes sense, and then that mental model change usually explains why the code is doing A.

The second case has been discussed in detail here. Where you've either inherited the code, don't have a solid mental model of how the code works, don't have a good idea on the specific location of the bug, etc. In this case, drilldown/divide-and-conquer methods with print statements can work. And if it's under source control, make sure to check the most recent code change.

share|improve this answer
add comment

Expanding on the "The most effective debugging tool is still careful thought, coupled with judiciously placed print statements."

First, try to narrow down the moment the bug occurs. Make the user-observable symptoms system-observable. (say, some string changes to gibberish, add a loop that polls the content of the script and triggers your debug as it changes.) Of course if the error is a crash, add segfault handling.

Then try to narrow down the thread if the problem is with multi-threaded environment. Give each thread an identifier, and dump it when the bug occurs.

Once you have the thread, sprinkle the code of given thread with printfs copiously to nail down the point where it surfaces.

Then backtrace to where the actual action that creates it occurs (the destructive action will often be quite a bit before where the damaged data triggers the problem.) Examine what structures/variable occur nearby in the memory, observe loops that affect them, check points where the corrupted value is written to.

Once you have the point that was causing the problem, before fixing it, think twice what the correct behavior should be.

share|improve this answer
add comment

Not the answer you're looking for? Browse other questions tagged or ask your own question.