Debugging with dbx Bill Tuthill Sun Microsystems, Inc. 2550 Garcia Avenue Kevin J. Dunlap Computer Systems Research Group University of California Berkeley, CA 94720 _I_n_t_r_o_d_u_c_t_i_o_n This short paper discusses _d_b_x, a symbolic debugger that is vastly superior to _a_d_b. It may be as good as the debuggers you remember from those non- UNIX* systems you worked on before. The advantage of symbolic debuggers is that they allow you to work with the same names (symbols) as in your source code. Like _a_d_b, _d_b_x is interactive and line-oriented, but _d_b_x is a source-level rather than an assembly-level debugger. It allows you to determine where a program crashed, to view the values of variables and expressions, to set breakpoints in the code, and to run and trace a program. Source code may be in C, Fortran, or Pascal. Mark Linton wrote _d_b_x as his master's thesis at UC Berkeley. Along with Eric Schmidt's Berknet, _d_b_x is among the most successful master's theses done on UNIX. Since _d_b_x required changes to the symbol tables generated by the vari- ous compilers, you need to compile programs for debugging with the -_g flag. For example, C programs should be com- piled as follows: % cc -g _p_r_o_g_r_a_m.c -o _p_r_o_g_r_a_m Programs compiled with the -_g option have good symbol tables, while programs compiled without -_g have old-style symbol tables intended for _a_d_b. Stripped programs have no __________________________ * UNIX is a Trademark of Bell Laboratories. September 2, 1987 - 2 - symbol tables at all. Invoke the debugger as follows, where _p_r_o_g_r_a_m is the pathname of the executable file that dumped core: % dbx _p_r_o_g_r_a_m The core image should be in the working directory; if it isn't, specify its pathname in the argument after the pro- gram name. Among the great advances of _d_b_x is that it has a help facility; type the _h_e_l_p request to see a list of possi- ble requests. You can obtain help on any _d_b_x request by giving its name as an argument to _h_e_l_p. September 2, 1987 - 3 - _E_x_a_m_i_n_i_n_g _C_o_r_e _D_u_m_p_s Much of the time, programmers use _d_b_x to find out why a program dumped core. As an example, consider the following program _d_u_m_p_c_o_r_e._c, which dereferences a NULL pointer. This is a legal operation on VAX/UNIX, but not on VAX/VMS or on MC68000-based UNIX systems, on one of which this example was run: #include 9 #define LIMIT 5 9 main() /* print messages and die */ { int i; 9 for (i = 1; i <= 10 ; i++) { printf("Goodbye world! (%d)\n", i); dumpcore(i); } exit(0); } 9 int *ip; 9 dumpcore(lim) /* dereference NULL pointer */ int lim; { if (lim >= LIMIT) *ip = lim; } The program core dumps because of a segmentation violation or memory fault - on most machines it is illegal to assign to address zero. Once the program has produced a core dump, here's how you can find out why the program died: % dbx dumpcore dbx version 3.17 of 4/24/86 15:04 (monet.Berkeley.EDU). Type 'help' for help. reading symbolic information ... [using memory image in core] (dbx) where dumpcore.dumpcore(lim = 5), line 22 in "dumpcore.c" main(0x1, 0x7fffe904, 0x7fffe90c), line 11 in "dumpcore.c" The _w_h_e_r_e request yields a stack trace. As you can see, the _d_u_m_p_c_o_r_e() routine was called from line 11 of the program, with the argument _l_i_m equal to 5. You can look at the _d_u_m_p_- _c_o_r_e() procedure by invoking the _l_i_s_t request as follows: 9 September 2, 1987 - 4 - (dbx) list dumpcore 18 dumpcore(lim) /* dereference NULL pointer */ 19 int lim; 20 { 21 if (lim >= LIMIT) 22 *ip = lim; 23 } We immediately suspect that the program's failure had some- thing to do with *_i_p, so we use the _p_r_i_n_t request to retrieve the value of the pointer and what it points to: (dbx) print *ip reference through nil pointer (dbx) print ip (nil) This tells us the program has dereferenced a null pointer. It is possible to run the program again from inside the debugger. The first line tells you name of the running pro- gram, and successive lines give output from the program: (dbx) run Goodbye world! (1) Goodbye world! (2) Goodbye world! (3) Goodbye world! (4) Goodbye world! (5) 9 Bus error in dumpcore.dumpcore at line 22 22 *ip = lim; (dbx) quit In this example the program dies with a Bus error at line 22. This method of running the program does not produce a core dump, but the _w_h_e_r_e request will still behave properly, because the debugger is in the same state as if it had just read the core file. _S_e_t_t_i_n_g _B_r_e_a_k_p_o_i_n_t_s With _d_b_x you can set breakpoints before each line of a program, not just at function and procedure boundaries, as with _a_d_b. The _s_t_o_p request sets a breakpoint. After set- ting a breakpoint, use the _r_u_n request to execute the pro- gram. The _c_o_n_t request continues execution from the current stopping point until the program finishes or another break- point is encountered. The _s_t_e_p request executes one source statement, following any function calls. The _n_e_x_t request executes one source statement, but does not stop inside any function calls. The _s_t_a_t_u_s request lists active break- points, while the _d_e_l_e_t_e request removes them if required. The _s_t_o_p request can take a conditional expression to 9 September 2, 1987 - 5 - avoid needless single-stepping. We will use a conditional in our example to make things simpler. Of course you can use _p_r_i_n_t and _l_i_s_t requests at any time during statement stepping if you want to print the value of variables or list lines of source code. This sample session shows a mixture of requests as we verify that the program fails when it tries to assign to *_i_p: (dbx) stop at 10 if (i == 5) [1] if i = 5 { stop } at 10 (dbx) run Goodbye world! (1) Goodbye world! (2) Goodbye world! (3) Goodbye world! (4) [1] stopped in main at line 10 10 printf("Goodbye world! (%d)\n", i); (dbx) next Goodbye world! (5) stopped in main at line 11 11 dumpcore(i); (dbx) step stopped in dumpcore at line 21 21 if (lim >= LIMIT) (dbx) step stopped in dumpcore at line 22 22 *ip = lim; (dbx) step Bus error in dumpcore.dumpcore at line 22 22 *ip = lim; Running the program with breakpoints assures us that our intuition was correct. We shouldn't be assigning anything to a null pointer - _i_p should have been initialized to point at an object of the proper type. To exit from the debugger, use the _q_u_i_t request. It is possible to set variables from inside _d_b_x. The previous breakpoint session, for example, could have gone like this: September 2, 1987 - 6 - % dbx dumpcore dbx version 3.17 of 4/24/86 15:04 (monet.Berkeley.EDU). Type 'help' for help. reading symbolic information ... [using memory image in core] (dbx) stop at 10 [1] stop at 10 (dbx) run Running: dumpcore stopped in main at line 10 10 printf("Goodbye world! (%d)\n", i); (dbx) assign i = 5 (dbx) next Goodbye world! (5) stopped in main at line 11 11 dumpcore(i); (dbx) next Bus error in dumpcore.dumpcore at line 22 22 *ip = lim; It is often useful to assign new values to variables to draw conclusions about alternative conditions. We can't fix the bug in this program, however, because there is no declared variable to which _i_p should point. _C_o_n_c_l_u_s_i_o_n Expressions in _d_b_x are similar to those in C, except that there is a distinction between / (floating-point divi- sion) and _d_i_v (integer division), as in Pascal. The table on the following page shows _d_b_x requests organized by func- tion: Like _a_d_b, _d_b_x can disassemble object code. It can also examine object files and print output in various formats; but _d_b_x requires the proper symbol tables, so _a_d_b is more useful to examine arbitrary binary files. The most impor- tant thing _a_d_b can do that _d_b_x cannot is to patch binary files - _d_b_x has no write option. Despite these shortcom- ings, _d_b_x is much easier to use than _a_d_b, so it contributes much more to individual programmer productivity. _A_c_k_n_o_w_l_e_d_g_e_m_e_n_t_s Material presented in this document was first presented in ``C Advisor'', _U_n_i_x _R_e_v_i_e_w _4, 1, pp 78-85. The Regents of the University California expresses their gratitude to Unix Review for allowing them to reprint this document. This document is a good starting point for a more thorough tutorial. Those with the ambition to expand on this document are encouraged to contact the Computer Systems Research Group at ``4bsd-ideas@Berkeley.Edu.'' September 2, 1987 - 7 - center box; cf s. Groups of _d_b_x Requests _ l lfI lp-1fCW l. execution and tracing _ run execute object file cont continue execution from where it stopped trace display tracing information at specified place stop stop execution at specified place status display active _t_r_a_c_e and _s_t_o_p requests delete delete specific _t_r_a_c_e or _s_t_o_p requests catch start trapping specified signals ignore stop trapping specified signals step execute the next source line, stepping into functions next execute the next source line, even if it's a function l lfI lp-1fCW l. _ displaying data _ print print the value of an expression whatis print the declaration of a given identifier or type which print outer block associated with identifier whereis print all symbols matching identifier assign set the value of a vari- able l lfI lp-1fCW l. _ function and procedure han- dling _ where display active procedures and functions on stack down move down the stack towards stopping point up move up the stack towards _m_a_i_n call call the named function or procedure dump display names and values of all local variables l lfI lp-1fCW l. _ accessing source files and directories _ edit invoke an editor on current source file file change current source file func change the current function or procedure list display lines of source code use set directory list to search for source files /.../ search down in file to match regular expression ?...? search up in file to match regular expression l lfI lp-1fCW l. _ miscel- laneous commands _ sh pass command line to the shell alias change _d_b_x command name help explain commands source read commands from external file quit exit the debugger September 2, 1987