Say you don't have a debugger available, what would be an effective approach to debug code which doesn't work (as expected)?
|
closed as not a real question by gnat, AProgrammer, Walter, Glenn Nelson, GlenH7 Jan 2 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, see the FAQ.
|
There is a number of techniques:
And of course there are many more tools and tricks, depending on the nature of your execution environment and your code. |
|||||||||||
|
|
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. |
|||||||||||||||||||||
|
|
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. |
|||||||||||
|
|
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. |
|||||||||
|
|
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:
If in the log you see
you know something went wrong there. So you remove all the other logging code, and try this new version:
Now in the log you may see this
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. |
|||
|
|
|
After the other answers have failed, there's always binary search debugging:
Note: obviously, this technique only works if you can reliably reproduce the problem. |
|||||
|
|
'"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. |
|||||||||||||
|
|
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. |
|||
|
|
|
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. |
|||
|
|
