Debugging Tools and Tips

This page is designed to provide some tools and tips for debugging your assignments in CSE 333. As this course has a considerable amount of programming, it is recommended to consider different tools and options when approaching and handling bugs in your development process.

Firstly, here are some initial tips to approach debugging:

Secondly, debugging C/C++ programs might be quite different from your experience debugging other programs, especially if you're using GDB when you're more used to a graphical debugger, so we have provided some guides to handle different tools in C and C++:

Please also take a look at the links provided in the navigation bar under “Debugging” that give more robust documentation for valgrind and gdb!

Debugging Tips

Program Walkthrough

Walking through your program is a good way to compare your expectations for your program's execution and the reality of the current implementation. Finding where the two diverge from each other can provide the scope in which a bug in your program may exist. Here are some things to keep in mind:

  • Try different inputs to your program. How does the input impact the execution of your program? Are there edge cases to take into consideration?
  • Double check the usage of library functions or objects in the man pages and the C/C++ Reference pages.
  • A useful tool to refer to is gdb which allows you to walk through your code using a debugging tool.

However, be wary that the size and complexity of programs make program walkthrough difficult, so utilizing tools such as gdb will help you “walkthrough” an execution of your program!

“Fail Fast”

Debugging is difficult when you do not know where the bug is in the code. “Fail fast” is a methodology to recognize and notify developers about bugs early in development through testing the expected execution of a program. Here are some ways to incorporate this into your programming:

  • If you are unsure about the current state of a variable in a program, try using Verify333(boolean) to ensure the truth about a statement. However, please make sure to remove these checks after fully developing the code and want to submit!
  • Frequently run tests on your homework to ensure that a section of code has passed the unit tests given. Remember that the tests given in the homework do not always catch all errors though, so still keep in mind of the program walkthrough tips as well.
  • Run valgrind consistently through your assignments to ensure that you are not poorly accessing memory.

Collaboration

We encourage collaboration in this course! Oftentimes, bugs can be easily overlooked individually, so collaboration allows more perspectives, ideas, and just another pair of eyes when developing! Here are some ideas to consider:

  • Talking to your partner: Review the partners page on tips to work with one another.
  • Going to office hours. Office hours are a wonderful place to work with other students that may be working on a similar problem, and it is facilitated by the staff, who have experience with the material beforehand.
  • Posting on the Ed Board. The Ed Discussion board is a place for student discussion asynchronously about course material.

Please review the academic conduct section in the syllabus on what differentiates collaboration and cheating.

Debugging Tools

printf Debugging

Debugging with printf is a good starting point when debugging your program. The idea of printf debugging is that it can be a fairly straightforward approach towards better understanding the state and execution of your program. Here are some places where it might be useful:

  • Printing out the current state of the program: Examples can be printing local variables or parameters and assessing their values over time. Note that this is entirely possible with gdb as well though.
  • Marking execution in different parts of the program: Checking if a line of code was executed in a program or not can give a better idea of how your program operates!
Do what works well for you, but keep in mind that the software we build in this class can be quite complex. While debugging with printf can be a great way to get started, finding more complex bugs is a lot easier with a dedicated debugger like GDB.

Valgrind

Valgrind is a memory error detector tool. You should use this in every homework and exercise to check for possible memory errors. Valgrind can also be used to check for appropriate reading and writing into memory! Here are some examples:

  • Reading/writing free'd memory (dangling pointers).
  • Reading/writing past the end of an array.
  • Reading/writing in inappropriate areas on the stack.

Generally, if there is an unexpected output in your solution or a segmentation fault, a quick way to check for possible errors is through Valgrind.

Keep in mind, while Valgrind is an extremely powerful tool, it's not a silver bullet! In particular, Valgrind is a dynamic analysis tool, so it can only catch memory issues that occur during a particular run of your program. This means that you will want to run Valgrind multiple times to ensure you get good code coverage.
It also by design chooses to ignore certain kinds of errors in order to avoid generating lots of false positives.

Here is a sample error for illegal reads or writes that you may receive from running Valgrind:

                  ...
                  ==235179== Invalid read of size 4
                  ==235179==    at 0x401183: main (valgrind-tutorial.c:16)
                  ==235179==  Address 0x520b068 is 20 bytes after a block of size 20 alloc'd
                  ==235179==    at 0x4C360A5: malloc (vg_replace_malloc.c:380)
                  ==235179==    by 0x40115E: main (valgrind-tutorial.c:8)
                  ...
                

When reading this error, the first line will explain the type of invalid access as well as an associated stack trace in the next line. Starting at the third line, there is a stack trace of which block of memory you may be referring to.

Here is an example error for not freeing all memory from the heap when running Valgrind:

                  ...
                  ==235179== HEAP SUMMARY:
                  ==235179==     in use at exit: 20 bytes in 1 blocks
                  ==235179==   total heap usage: 3 allocs, 2 frees, 1,064 bytes allocated
                  ==235179==
                  ==235179== 20 bytes in 1 blocks are definitely lost in loss record 1 of 1
                  ==235179==    at 0x4C360A5: malloc (vg_replace_malloc.c:380)
                  ==235179==    by 0x40115E: main (valgrind-tutorial.c:8)
                  ==235179==
                  ==235179== LEAK SUMMARY:
                  ==235179==    definitely lost: 20 bytes in 1 blocks
                  ==235179==    indirectly lost: 0 bytes in 0 blocks
                  ==235179==      possibly lost: 0 bytes in 0 blocks
                  ==235179==    still reachable: 0 bytes in 0 blocks
                  ==235179==         suppressed: 0 bytes in 0 blocks
                  ...
                

There will be information on lost blocks of memory, which means you have not free'd them before the end of the program execution.

  • Try reading the stack trace for each block in order to see which blocks of memory the program has not free'd during its execution.
  • Under LEAK SUMMARY, you want to ensure that “definitely lost” and “indirectly lost” are 0 to make sure there were no memory leaks in your code!
Using getaddrinfo() later in the course (ex10, ex11, hw4) will result in a leakage that is “still reachable”. Disregard this leakage as it is a bug in the <netdb.h> library rather than your code.

GDB

GDB (Gnu DeBugger) is a tool that will help debug your C and C++ programs. Although this tool may have been used in CSE 351 primarily for assembly, CSE 333 uses GDB more to walk through source code and analyze the program state throughout its execution. GDB allows:

  • Breakpoints in your programs so that you can stop execution and examine the contents of memory and registers.
  • Single-stepping your program one line of source code or one line of C/C++ code at a time.
  • A much richer and more productive debugging experience than just using printf statements.

Please also refer to gdb manual and the gdb cheatsheet attached on the nav bar!

Here are some additional compiled tips that the staff have when working with gdb:

  • backtrace displays the program stack. This may be particularly helpful for getting more information on program failures (e.g., segmentation fault)
  • Some ways to set breakpoints in files to set up points where you want to analyze the current state of the program:
                        break <function/line number> # break on a function or program line
                        break <filename>:<function/line number> # break at a specific file
                      
  • step and next allow you to walk through your program execution. Remember that step steps into function calls and next steps over function calls
  • Using x and p: Both are ways to view the state of any location in memory like a variable, location in memory, or parameter. Note that x examines memory at an address expression while p shows the value of an expression (which you may find more helpful in this course). Visit the gdb cheat sheet for more details (such as printing format specifiers).

Viewing Binary Files

Learning how to view binary files is particularly helpful later in the quarter with Homework 3 and Exercise 11 since we'll be doing a variety of activities that will require reading and writing binary. Primarily, a tool to help you start reading binary files is xxd!

              [attu]$ echo -ne "hello world\xca\xfe\xf0\x0d" > test.bin
              [attu]$ xxd test.bin 
              00000000: 6865 6c6c 6f20 776f 726c 64ca fef0 0d    hello world....
            

Let's discern between the three pieces of information in this single line (generally with larger files you will see a lot of lines, but still in this format. Follow the same tips described here):

  • The first block (the 00000000:) represents the left-most index of the line (in hex). This can be helpful for a longer file.
  • The second block is the bytes themselves. We can consider 68 to be the first-byte value!
  • The last block is the ASCII decoding of the bytes. For instance, the first byte of the file, 68, is the hex code value for h. For the last four bytes, there's just a ., which means the byte doesn't correspond to a printable ASCII character.