Monday, August 4, 2014

More GDB tips and tricks

http://www.openlogic.com/wazi/bid/351471/more-gdb-tips-and-tricks

The GNU Debugger (GDB) is a powerful tool for developers. In an earlier article I talked about how to use breakpoints and watchpoints, and how to auto-display values and call user-defined and system functions. This time, let's see how to use GDB to examine memory and debug macros and signal handlers. To create the examples here, I used GDB 7.6.1 and GCC 4.8.1, and compiled the C code using the -ggdb option.

Examine memory

Use GDB's x command to examine memory. The command offers several formatting options that let you control the number of bytes to display and the way you'd like to display them.
The syntax of the command is x/FMT ADDRESS, where FMT specifies the output format and ADDRESS is the memory address to be examined. FMT consists of three types of information: the repeat count, the display format, and the unit size. For example: x/3uh 0x786757 is a request to display three halfwords (h) of memory, formatted as unsigned decimal integers (u), starting at address 0x786757. Available format letters are o (octal), x (hex), d (decimal), u (unsigned decimal), t (binary), f (float), a (address), i (instruction), c (char) and s (string), while available unit size letters are b (byte), h (halfword), w (word), g (giant, 8 bytes). And it doesn't matter whether the unit size or format comes first; either order works.
To understand how to use the x command, consider the following code:
#include 
#include 

void func(char *ptr)
{
         char tmp[] = "someOtherString";
         char *new_ptr = ptr+3;
         if(strncmp(tmp, new_ptr, sizeof(tmp)))
         {
                 /*
                   * Some processing
                   */
         }
}

int main(void)
{
        func("1. Openlogic");
        return 0;
}
Suppose the main() and the func() functions are in different modules, and while debugging a problem you want to examine the value pointed to by the new_ptr pointer. Load the program using GDB, put a breakpoint at the strncmp line, then run the program. Once the program hits the breakpoint, you'll get a prompt:
$ gdb test
Reading symbols from /home/himanshu/practice/test...done.
(gdb) break 8
Breakpoint 1 at 0x80484a9: file test.c, line 8.
(gdb) run
Starting program: /home/himanshu/practice/test

Breakpoint 1, func (ptr=0x8048590 "1. Openlogic") at test.c:8
8             if(strncmp(tmp, new_ptr, sizeof(tmp)))
(gdb)
Run x/s new_ptr at this prompt, and you'll get the following output:
(gdb) x/s new_ptr
0x8048593:    "Openlogic"
(gdb)
The x command with the s format letter displays the value stored at the memory address pointed by new_ptr as a string. To display the value in character format, change the format specifier to c:
(gdb) x/9c new_ptr
0x8048593:    79 'O'    112 'p'    101 'e'    110 'n'    108 'l'    111 'o'    103 'g'    105 'i'
0x804859b:    99 'c'

Debugging macros

One of the biggest reasons developers prefer inline functions over macros is that debuggers tend to be better at dealing with the former. For example, consider the following code:
#include 

#define CUBE(x) x*x*x  

int main(void)
{
        int a =3;
        printf("\n The cube of [%d] is [%d]\n", a+1, CUBE(a+1));
        return 0;
}
The cube of 4 is 64, but watch what happens when you run the program:
$ ./test

 The cube of [4] is [10]
It can be a nightmare to pinpoint the cause of this kind of problem in a project with a large code base, as most debuggers simply aren't good at debugging macros.
GDB doesn't know anything about macros by default either, but you can enable macro debugging in the debugger using compile-time flags such as -gdwarf-2 and -g3; for more info on these options, read the man page of the GCC compiler. Once the program is compiled with the aforementioned command-line options, load and run it with GDB in the standard way. Put in a breakpoint so that you can debug the macro while the program is running:
$ gdb test
Reading symbols from /home/himanshu/practice/test...done.
(gdb) break 9
Breakpoint 1 at 0x8048459: file test.c, line 9.
(gdb) run
Starting program: /home/himanshu/practice/test

 The cube of [4] is [10]

Breakpoint 1, main () at test.c:9
9            return 0;
(gdb)
When the program hits the breakpoint, run the following command to see how the macro is expanded:
(gdb) macro expand CUBE(3+1)
expands to: 3+1*3+1*3+1
That expansion is equivalent to: 3 + ( 1 * 3 ) + ( 1 * 3 ) + 1, or 10. Once you know the problem, you can easily correct it by redefining the macro as #define CUBE(x) (x)*(x)*(x).
You can also use the info macro command to find out more about a macro. For example:
(gdb) info macro CUBE
Defined at /home/himanshu/practice/test.c:3
#define CUBE(x) x*x*x
Read this document to learn more on how to debug macros using GDB.

Debugging signal handlers

Debugging signal handlers is not as easy as debugging normal functions with GDB. For example, consider the following program:
#include 
#include 
#include 

void sighandler(int signum)
{
  printf("\n Caught SIGNIT - : %d", signum);
}

int main()
{
  signal(SIGINT, (void*)sighandler);

  while (1)
  {
        printf("\n Waiting for user action...");
        sleep(1);
  }
}
The program defines a signal handler for SIGINT, which is usually generated when someone presses Ctrl-C. Load the program using GDB, put a breakpoint at the sighandler() function, as you would do for any other normal function that you want to debug, then run the program:
$ gdb test
Reading symbols from /home/himanshu/practice/test...done.
(gdb) break sighandler
Breakpoint 1 at 0x8048483: file test.c, line 7.
(gdb) run
Starting program: /home/himanshu/practice/test

 Waiting for user action...
 Waiting for user action...
 Waiting for user action...
 Waiting for user action...
Now generate the SIGINT signal by pressing Ctrl-C, and you'll see the problem:
Waiting for user action...
 Waiting for user action...
^C
Program received signal SIGINT, Interrupt.
0xb7fdd424 in __kernel_vsyscall ()
(gdb)
Instead of the breakpoint being hit, the signal is intercepted by the debugger.
To alter this behavior, use the handle command. It expects a list of signals, along with the actions to be applied on them. In this case, the actions nostop and pass are of interest. The former makes sure that GDB doesn't stop the program when the signal happens, while the latter makes sure that GDB allows the program to see this signal.
Run the handle command after setting the breakpoint:
$ gdb test
Reading symbols from /home/himanshu/practice/test...done.
(gdb) break sighandler
Breakpoint 1 at 0x8048483: file test.c, line 7.
(gdb) handle SIGINT nostop pass
SIGINT is used by the debugger.
Are you sure you want to change it? (y or n) y
Signal            Stop    Print    Pass to program    Description
SIGINT            No    Yes    Yes            Interrupt
(gdb)
Make sure you answer in the affirmative the question asked by the debugger. Now when you run the program and press Ctrl-C to generate and send SIGINT, the breakpoint is hit:
(gdb) run
Starting program: /home/himanshu/practice/test

 Waiting for user action...
 Waiting for user action...
^C
Program received signal SIGINT, Interrupt.

Breakpoint 1, sighandler (signum=2) at test.c:7
7      printf("\n Caught SIGNIT - : %d", signum);
(gdb)

Conclusion

All the debugging commands described here can do even more for you. If you want to share another useful GDB feature or command, please leave a comment below.

No comments:

Post a Comment