CSE 127: Intro to Computer Security


A simple program with its O/P is given. Then, the GNU debugger is invoked on this program and some gdb commands are explained which are going to be helpful for assignment 1.

A short but nice tutorial could be found at  http://www.cpsc.ucalgary.ca/~ryansc/gdbtut/gdbtoc.html

If you want to read more about GDB, click  http://www.gnu.org/manual/gdb-4.17/html_mono/gdb.html


1         #include <stdio.h>
3         /* print a given string */
4         void
5             print_string(char* string)
6             {
7                 printf("String - %s\n", string);
8             }
10         int
11          main(int argc, char* argv[])
12         {
13             int i;
15         /* check for command line arguments */
16         if (argc < 2) { /* 2 -> 1 for program name (argv[0]) and one for a param. */
17             printf("Usage: %s [<string> ...]\n", argv[0]);
18         exit(1);
19         }
21         /* loop over all strings, print them one by one */
22         for (i = 1; i < argc; i++) {
23             print_string(argv[i]); /* function call */
24         }
26         printf("Total number of strings: %d\n", --i);
28         return 0;
29         }


bash-2.03$ cc -o simple simple.c
bash-2.03$ simple hello world
String - hello
String - world
Total number of strings: 2



Before invoking the debugger. make sure you compiled your program (all its modules, as well as during linking) with the "-g" flag.

bash-2.03$ cc -g -o simple simple.c
bash-2.03$ gdb simple

Note that we run the program from the same directory it was compiled in, otherwise gdb won't find the source file, and thus won't be able to show us where in the code we are at a given point. It is possible to ask gdb to search for extra source files in some directory after launching it, but for now, it's easier to just invoke it from the correct directory.


Once we invoked the debugger, we can run the program using the command "run". If the program requires command line parameters (like our simple program does), we can supply them to the "run" command of gdb. For example:

(gdb) run hello world


The problem with just running the code is that it keeps on running until the program exits, which is usually too late. For this, breakpoints are introduced. A breakpoint is a command for the debugger to stop the execution of the program before executing a specific source line.We can set break points using two methods:

1.Specifying a specific line of code to stop in:

(gdb) break simple.c:9

2.Specifying a function name, to break every time it is being called:

(gdb) break main

this will set a break point right when starting the program.


So lets see, we've invoked gdb, then typed:

(gdb) break main
(gdb) run hello world

Then the debugger gave something like the following:

Starting program: /home/solaris/ieng9/cs127w/cs127w2/simple hello world

Breakpoint 1, main (argc=3, argv=0xeffff7b4) at simple.c:16
16 if (argc < 2) { /* 2 -> 1 for program name (argv[0]) and one for a param. */

Now we want to start running the program slowly, step by step. There are two options for that:

1."next" or "n" - causes the debugger to execute the current command, stepping over function call.

(gdb) n
22 for (i = 1; i < argc; i++) {

"nexti" or "ni" - shows the next machine instruction, rather than source line

2."step" or "s"- causes the debugger to execute the current command, stepping into function call.

(gdb) s
23 print_string(argv[i]); /* function call */

One more "step" and we are going to be inside the print_string function.
(gdb) step
print_string (string=0xeffff8d6 "hello") at simple.c:7
7 printf("String - %s\n", string);

"stepi" or "si" - step by machine instructions, rather than source lines.

(gdb) info line 7                  (show starting, ending addresses of compiled code for source line num. )
Line 7 of "simple.c" starts at address 0x10568 <print_string+8> and ends at 0x1057c <print_string+28>.

We can also check this by using "stepi" or "nexti"
(gdb) stepi
0x1056c 7 printf("String - %s\n", string);
(gdb) stepi
0x10570 7 printf("String - %s\n", string);
(gdb) stepi
0x10574 7 printf("String - %s\n", string);
(gdb) stepi
0x10578 7 printf("String - %s\n", string);
(gdb) stepi
0x2079c in printf ()
(gdb) stepi
0x207a0 in printf ()
(gdb) next
Single stepping until exit from function printf,
which has no line number information.
String - hello
print_string (string=0xeffff8d6 "hello") at simple.c:8
8 }

As "gdb" doesn't have any information regarding the source file of printf.

(gdb) step
main (argc=3, argv=0xeffff7b4) at simple.c:22
22 for (i = 1; i < argc; i++) {
(gdb) step
23 print_string(argv[i]); /* function call */


Without being able to examine variables contents during program execution, the whole idea of using a debugger is quite lost. You can print the contents of a variable with a command like this:

(gdb) print i

And then you'll get a message like:

$1 = 2

which means that "i" contains the number "2".
Note that this requires "i" to be in scope, or you'll get a message such as:

No symbol "i" in current context.

For example, if you break inside the "print_string" function and try to print the value of "i", you'll get this message.

(gdb) step
print_string (string=0xeffff8dc "world") at simple.c:7
7 printf("String - %s\n", string);

(gdb) print i
No symbol "i" in current context.


Once we got into a break-point and examined some variables, we might also wish to see "where we are". That is, what function is being executed now, which function called it, and so on. This can be done using the "where" or "backtrace" or "bt"command. At the gdb command prompt, just type one of the above commands, and you'll see something like this:

(gdb) bt
#0 print_string (string=0xeffff8dc "world") at simple.c:7
#1 0x10608 in main (argc=3, argv=0xeffff7b4) at simple.c:23
(gdb) bt 1
#0 print_string (string=0xeffff8dc "world") at simple.c:7
(More stack frames follow...)

This means the currently executing function is "print_string", at file "simple.c", line 7. The function that called it is "main".We also see which arguments each function had received. If there were more functions in the call chain, we'd see them listed in order. This list is also called "a stack trace", since it shows us the structure of the execution stack at this point in the program's life.

Just as we can see contents of variables in the current function, we can see contents of variables local to the calling function, or to any other function on the stack.

For example, if we want to see the contents of variable "i" in function "main", we can type the following two commands:

(gdb) frame 1
#1 0x10608 in main (argc=3, argv=0xeffff7b4) at simple.c:23
23 print_string(argv[i]); /* function call */
(gdb) print i
$4 = 2

The "frame" command tells the debugger to switch to the given stack frame ('0' is the frame of the currently executing function). At that stage, any print command invoked will use the context of that stack frame. Of-course, if we issue a "step" or "next" command, the program will continue at the top frame, not at the frame we requested to see.

(gdb) next                                                      (Back to frame 0 that represents function print_string())
String - world
8 }

(gdb) info frame                                            (describe current frame or frame at [addr])
Stack level 0, frame at 0xeffff6d8:
pc = 0x1057c in print_string (simple.c:8); saved pc 0x10608
called by frame at 0xeffff750
source language c.
Arglist at 0xeffff6d8, args: string=0xeffff8dc "world"
Locals at 0xeffff6d8,

(gdb) info args                                             (arguments of the selected frame)
string = 0xeffff8dc "world"

(gdb) info locals                                         (local variables of selected frame)
No locals.

Similarly, info reg [rn]....                            (register values for regs rn in the selected frame)


(gdb) disassem                                             (display memory as machine instructions)
Dump of assembler code for function print_string:
0x10560 <print_string>: save %sp, -112, %sp
0x10564 <print_string+4>: st %i0, [ %fp + 0x44 ]
0x10568 <print_string+8>: sethi %hi(0x10400), %o1
0x1056c <print_string+12>:
or %o1, 0x2d0, %o0 ! 0x106d0 <_lib_version+8>
0x10570 <print_string+16>: ld [ %fp + 0x44 ], %o1
0x10574 <print_string+20>: call 0x2079c <printf>
0x10578 <print_string+24>: nop
0x1057c <print_string+28>: ret
0x10580 <print_string+32>: restore
End of assembler dump.

(gdb) x/i                                                     (display current machine instruction)
0x10580 <print_string+32>: restore