7.1. Using the GNU DebuggerIf software applications were bug-free, we would not have any use for debuggers. As software programmers, we all strive to write good programs. As the code gets bigger and more complicated, however, simple programming mistakes can creep in. Like software development, porting an application can introduce bugs in the program. Even if the application is ported without changing any application programming interfaces (APIs) used in the code, a hidden bug in the original code can suddenly expose itself simply by running the application on a different platform. This is one reason why you need to be familiar with the debugger available on Linux. The GNU debugger, GDB, is a powerful debugger for different applications written in C, C++, Objective-C, FORTRAN, and Ada. This section shows how to use the GDB for multiprocess or multithreaded applications. It serves as a quick how-to guide for anyone new to GDB. The information contained in this section deals with the basics of the GNU debugger; for more definitive information regarding the use of GDB, consult the GNU documentation project for GDB on the Internet.[1]
7.1.1. Compiling a Program for Use with GDBTo get the most effective use out of GDB, the application to be debugged (the debugee) needs to be compiled with the g flag, as follows: $ gcc g file.c o file When compiling using makefiles, the g flag may be included in CFLAGS or CPPFLAGS, as in the following example: DEBUG_FLAG = -g CFLAGS = $(DEBUG_FLAGS) D_USING_APA %.o: %.c $(CC) -c $(CFLAGS) $< -o $@ The gcc compiler supports the g flag with O (optimization), making it possible to debug optimized executable programs with GDB. The one thing to remember, though, is that debugging an optimized program may cause some execution path to be rearranged and not match the source files. 7.1.2. Invoking GDBThere are several ways to invoke GDB, as follows:
7.1.3. GDB Basic CommandsFor the most part, commands used inside GDB are intuitive. Once inside GDB, commands to show back traces or source line numbers can easily be remembered. Table 7-1 is a list of the basic GDB commands most widely used when debugging a ported application on Linux.
For commands that are not frequently used, the porting engineer can get help using the info command. 7.1.4. Debugging Multiple ProcessesOn most Linux platforms, GDB has no special support to debug programs that create child processes using the fork function. When a program forks, GDB continues to debug the parent process while the child continues to execute in the background, as shown in Example 7-1. Example 7-1. Code Listing for fork_sample.c
Compile fork_sample.c: $ gcc fork_sample. c o fork_sample g compile code with -g Run GDB against the compiled code: $ gdb fork_sample (gdb) break printf put a breakpoint in printf Breakpoint 1 at 0xfecadf0 (gdb) run Starting program: fork_sample Detaching after fork from child process 8012. I am the child 8012 Breakpoint 1, 0x0fecadf0 in printf () from /lib/tls/libc.so.6 (gdb) where #0 0x0fecadf0 in printf () from /lib/tls/libc.so.6 #1 0x10000548 in main () at fork.c:26 In the sample GDB session, we placed a breakpoint at a printf statement. But how do we know which printf statement the debugger will stop atthe child's or the parent's? The session shows GDB stopped at the printf invocation inside the parent process (line 26). GDB does not have any special provisions to create a separate GDB session to debug the child process. To follow the child process using GDB, use the command set follow-fork-mode within GDB. The following GDB session shows how to follow a child using GDB: $ gdb fork_sample (gdb) set follow-fork-mode child Set GDB to follow the child (gdb) break printf Breakpoint 1 at 0x10010aa8 (gdb) run Starting program: fork_sample Breakpoint 1 at 0xfecadf0 Attaching after fork to child process 8019. I am the parent 8016 [Switching to process 8019] Breakpoint 1, 0x0fecadf0 in printf () from /lib/tls/libc.so.6 (gdb) where #0 0x0fecadf0 in printf () from /lib/tls/libc.so.6 #1 0x10000528 in main () at fork.c:21 Notice that after setting follow-fork-mode to follow the child, GDB stopped at line 21, where the printf function was about to be invoked by the child process. 7.1.5. Debugging Multithreaded ApplicationsYou need to remember only a few thread-specific commands when using GDB to debug multithreaded applications. The rest of the GDB commands to get stack traces and other program information remain the same. The thread-specific commands for use in GDB are as follows:
The program and debugging session shown in Example 7-2 is an example of how to debug a multithreaded application with GDB. Example 7-2. Listing of thread_sample.c
Compile the sample multithreaded application: $ gcc thread_sample.c -o thread_sample -g -lpthread -D_REENTRANT Run GDB against the sample program: $ gdb thread_sample (gdb) run Starting program: thread_sample [Thread debugging using libthread_db enabled] [New Thread 4160589856 (LWP 8051)] [New Thread 4160586976 (LWP 8054)] [New Thread 4150101216 (LWP 8055)] [New Thread 4139615456 (LWP 8056)] ctl-c ctl-c to break into the debugger Program received signal SIGINT, Interrupt. [Switching to Thread 4160589856 (LWP 8051)] 0x0fc268a8 in pthread_join () from /lib/tls/libpthread.so.0 (gdb) info thread 4 Thread 4139615456 (LWP 8056) 0x0ff15a64 in __nanosleep_nocancel () from /lib/tls/libc.so.6 3 Thread 4150101216 (LWP 8055) 0x0ff15a64 in __nanosleep_nocancel () from /lib/tls/libc.so.6 2 Thread 4160586976 (LWP 8054) 0x0ff15a64 in __nanosleep_nocancel () from /lib/tls/libc.so.6 * 1 Thread 4160589856 (LWP 8051) 0x0fc268a8 in pthread_join () from /lib/tls/libpthread.so.0 Notice the output of the info thread command: One of the lines has an asterisk. The asterisk signifies this thread is the current thread about which GDB will display information: (gdb) where display stack trace #0 0x0fc268a8 in pthread_join () from /lib/tls/libpthread.so.0 #1 0x1000079c in main () at thread_sample.c:70 To make GDB switch to a different thread, issue the command tHRead <num> where num is the thread number taken from the info thread output: (gdb) thread 3 [Switching to thread 3 (Thread 4150101216 (LWP 8059))]#0 0x0ff15a64 in __nanosleep_nocancel () from /lib/tls/libc.so.6 (gdb) where #0 0x0ff15a64 in __nanosleep_nocancel () from /lib/tls/libc.so.6 #1 0x0ff157e8 in sleep () from /lib/tls/libc.so.6 #2 0x10000564 in common_func (x=10) at thread_sample.c:16 #3 0x100005e8 in func2 (arg=0x0) at thread_sample.c:29 #4 0x0fc25a58 in start_thread () from /lib/tls/libpthread.so.0 #5 0x0ff51c5c in clone () from /lib/tls/libc.so.6 #6 0x0ff51c5c in clone () from /lib/tls/libc.so.6 To cause a GDB command to apply to all threads, issue the tHRead apply all command. The following example applies the where (display back trace) command to all threads: (gdb) thread apply all where Thread 4 (Thread 4139615456 (LWP 8060)): #0 0x0ff15a64 in __nanosleep_nocancel () from /lib/tls/libc.so.6 #1 0x0ff157e8 in sleep () from /lib/tls/libc.so.6 #2 0x10000564 in common_func (x=15) at thread_sample.c:16 #3 0x10000630 in func3 (arg=0x0) at thread_sample.c:36 #4 0x0fc25a58 in start_thread () from /lib/tls/libpthread.so.0 #5 0x0ff51c5c in clone () from /lib/tls/libc.so.6 #6 0x0ff51c5c in clone () from /lib/tls/libc.so.6 Previous frame identical to this frame (corrupt stack?) Thread 3 (Thread 4150101216 (LWP 8059)): #0 0x0ff15a64 in __nanosleep_nocancel () from /lib/tls/libc.so.6 #1 0x0ff157e8 in sleep () from /lib/tls/libc.so.6 #2 0x10000564 in common_func (x=10) at thread_sample.c:16 #3 0x100005e8 in func2 (arg=0x0) at thread_sample.c:29 #4 0x0fc25a58 in start_thread () from /lib/tls/libpthread.so.0 #5 0x0ff51c5c in clone () from /lib/tls/libc.so.6 #6 0x0ff51c5c in clone () from /lib/tls/libc.so.6 Previous frame identical to this frame (corrupt stack?) Thread 2 (Thread 4160586976 (LWP 8058)): #0 0x0ff15a64 in __nanosleep_nocancel () from /lib/tls/libc.so.6 #1 0x0ff157e8 in sleep () from /lib/tls/libc.so.6 #2 0x10000564 in common_func (x=5) at thread_sample.c:16 #3 0x100005a0 in func1 (arg=0x0) at thread_sample.c:22 #4 0x0fc25a58 in start_thread () from /lib/tls/libpthread.so.0 #5 0x0ff51c5c in clone () from /lib/tls/libc.so.6 #6 0x0ff51c5c in clone () from /lib/tls/libc.so.6 Previous frame identical to this frame (corrupt stack?) Thread 1 (Thread 4160589856 (LWP 8057)): #0 0x0fc268a8 in pthread_join () from /lib/tls/libpthread.so.0 #1 0x1000079c in main () at thread_sample.c:70 7.1.6. Stopping Multithreaded ProgramsIf the application being debugged is a multithreaded program, as in the preceding example, it is possible to set breakpoints on a particular thread by using the following GDB commands: (gdb) break <line> thread <thread number> or (gdb) break <line> thread <thread number> if <qualifier> where line is a particular line number in the specified module used in the program and tHRead number is the thread number taken from the info thread output. The following is a sample invocation of the command: (gdb) break thread_sample.c:14 thread 2 To conditionally stop a thread, use a conditional qualifier as follows: (gdb) break thread_sample.c:14 thread 2 if foo < bar Remember that whenever GDB stops a particular thread, all threads in the program are stopped, too. Threads that are in the middle of a system call are interrupted and may return from the system call without completing the system call. It is therefore suggested that programs check system call return values so that the proper action can be taken if the call is interrupted. When the multithreaded program is restarted, all threads are started, too. When doing a step or next command, GDB does not guarantee that all threads will be stopped after executing a line or instruction in the program. Some threads may execute more than a single line or instruction depending on where the threads are in their current state of execution. 7.1.7. GDB Initialization FileDuring startup, GDB reads an initialization file if it exists. This file is read from the user's home directory and/or the current working directory and is normally named .gdbinit. The .gdbinit file is useful for customizing a GDB session. GDB reads commands from the .gdbinit file and executes them for the particular GDB session. An example of where .gdbinit can prove useful is when specifying source directories so that GDB can find source file information. Another example is setting the child follow-fork-mode before starting GDB, as in the following example: $ cat .gdbinit dir /home/user1/src/lib:/home/user1/src/unix set follow-fork-mode child 7.1.8. GDB and SignalsLike any normal application executing outside of GDB, applications under the control of GDB (debuggee) may receive signals from external sources. GDB can control the signals directed at the debuggee by either passing them to the debuggee or ignoring them. The commands to control signal behavior under GDB are as follows:
Example 7-3 shows a GDB session using a sample program that invokes some signals. Example 7-3. Listing of signal_sample.c
Compile the sample program with the g flag: $ gcc signal_sample.c -o signal_sample g Invoke GDB on the compiled program: $ gdb signal_sample (gdb) run Starting program: /home/avm/book/GDB/signal_sample ..... in alarm_handler() ..... in alarm_handler() ..... in alarm_handler() .....in alarm_handler() ctl-c (gdb) info signal Signal Stop Print Pass to program Description SIGHUP Yes Yes Yes Hangup SIGINT Yes Yes No Interrupt SIGQUIT Yes Yes Yes Quit SIGILL Yes Yes Yes Illegal instruction SIGTRAP Yes Yes No Trace/breakpoint trap SIGABRT Yes Yes Yes Aborted SIGEMT Yes Yes Yes Emulation trap SIGFPE Yes Yes Yes Arithmetic exception SIGKILL Yes Yes Yes Killed SIGBUS Yes Yes Yes Bus error SIGSEGV Yes Yes Yes Segmentation fault SIGSYS Yes Yes Yes Bad system call SIGPIPE Yes Yes Yes Broken pipe SIGALRM No No Yes Alarm clock SIGTERM Yes Yes Yes Terminated SIGURG No No Yes Urgent I/O condition SIGSTOP Yes Yes Yes Stopped (signal) ... Notice in the preceding output that SIGALRM has the Print column set to No. If that is changed to a Yes through the handle command, GDB exhibits the following behavior: (gdb) handle SIGALRM print change SIGALRM Print status to Yes Signal Stop Print Pass to program Description SIGALRM No Yes Yes Alarm clock (gdb) run The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/avm/book/GDB/signal_sample ..... Program received signal SIGALRM, Alarm clock. in alarm_handler() ..... Program received signal SIGALRM, Alarm clock. in alarm_handler() ..... Program received signal SIGALRM, Alarm clock. in alarm_handler() ... Notice that after changing the SIGALRM print state to Yes, GDB now prints a status message every time the program receives a SIGALRM. In the following example, we change the stop state of SIGALRM to Yes, which causes GDB to stop when the program receives a SIGALRM: (gdb) handle SIGALRM stop change SIGALRM Stop status to Yes Signal Stop Print Pass to program Description SIGALRM Yes Yes Yes Alarm clock (gdb) run run the application The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/avm/book/GDB/signal_sample ..... Program received signal SIGALRM, Alarm clock. 0x0ff15a30 in __nanosleep_nocancel () from /lib/tls/libc.so.6 (gdb) After the SIGALRM stop state was changed to Yes and the program restarted, GDB now stops every time a SIGALRM is generated. 7.1.9. Graphical Front End to GDBFor developers who are more used to graphical debuggers, there is a graphical front end available for GDB called DDD.[3] DDD is available from www.gnu.org/software/ddd/.
For developers who prefer an Integrated Development Environment (IDE), both open-source and commercial products are available. Eclipse[4] and Kdevelop[5] are available as open-source IDEs. Visual Slickedit[6] and Borland Kylix[7] are commercial IDEs available for Linux.
|