Inspecting Command Line Arguments with GDB


1. Compiling and running the Caesar encrypt program

Let's take a look at a program that encrypts a file, that is, it scrambles it so that it is unreadable except to those who know the decryption method and the secret keyword.

We will use a method familiar to Julius Caesar. The person performing any encryption chooses an encryption key; here the key is a number between 1 and 25 that indicates the shift to be used in encrypting each letter. For example, if the key is 3, we will replace A with a D, B with an E, and so on.

The program takes the following command line arguments:

If no key is specified, then 3 is used. For example,

caesar input.txt encrypt.txt 

encrypts the file input.txt with a key of 3 and places the result into encrypt.txt.

caesar -d -k11 encrypt.txt output.txt 

decrypts the file encrypt.txt with a key of 11 and places the result into output.txt.

Here is the program:

Create a directory, and download the program to this directory.

To compile the program use the command line:

g++ -I/cs/course/cs113/textbook/horstmann/cccfiles -o caesar -g caesar.cpp 

Now run the program, encrypting the file caesar.cpp itself by running:

caesar caesar.cpp encrypt.txt 

and then decrypt it by running:

caesar -d -k3 encrypt.txt output.txt 

Can you guess what will be the content of output.txt?

Verify your guess by running:

diff caesar.cpp output.txt 

You can now test the program using different arguments. Make sure you completely understand what the program does.

2. Tracing the program using GDB

If you're not already running Emacs, start it up:

emacs & 

Then, start up GDB under emacs:

  1. Type <Esc> x gdb
  2. When it prompts you for the command to use to start GDB:
    Run gdb (like this): gdb
    

    complete the command with the name of the executable as in:

    Run gdb (like this): gdb caesar
    

GDB should now be running in Emacs. Let's first set a breakpoint for the first statement of the main() function:

(gdb) break 91
Breakpoint 1 at 0x13dbc: file caesar.cpp, line 91.

And, set the args variable of the GDB environment:

(gdb) set args -k3 caesar.cpp out.txt

This tells GDB to use "-k3 caesar.cpp out.txt" as the argument to the run command.

Then, run the program:

(gdb) run

Let's inspect the value of argc, the number of arguments passed to the program. What should this value be?

(gdb) print argc 
$1 = 4

Let's now inspect the actual arguments. In GDB, we can achieve this by using print FOO@NUM, which prints an array whose first element is FOO, whose second element is stored in the space following where FOO is stored, etc. for a total of NUM elements. (Type help print for more details). Following this specification, type:

(gdb) print argv[0]@argc
$2 = {0xeffff9f8 "/home/grad1/jalon/cs113/lab6/caesar",
     0xeffffa1c "-k3", 0xeffffa20 "caesar.cpp", 0xeffffa2b "out.txt"}

Of course, we can also display specific elements of the array as in:

(gdb) print argv[1][2]
$3 = 51 '3'

What value would you expect to be displayed as a result of the following?

(gdb) print argv[1][4]
$4 = 99 'c'

In this example argv[1][4] = argv[2][0] = 'c'. This is not surprising considering this picture of the memory:

argv[1]   0   1   2    3   4
          |
          v
        ------------------------------------------
        | - | k | 3 | \0 | c | a | e | s | a | r |
        ------------------------------------------
                           ^
                           |
                   argv[2] 0   1   2   3   4   5

Let's now continue to line 101:

(gdb) until 101
main (argc=4, argv=0xeffff8c4) at caesar.cpp:101

and inspect the string variable arg:

(gdb) print arg
$5 = {_str = 0x3d7a0 "-k3"} 

You can continue to trace the program using the step and next GDB commands. In particular, step through the statements inside the loop:

for (i = 1; i < argc; i++)

This piece of code parses the command line, by inspecting each argument and acting accordingly. If there is a parse error, then that means that the user has entered an invalid command line, and a usage message appears on the screen. This procedure is general and should be performed whenever your program makes use of command line arguments.

(For the interested student, you may want to verify that the remainder() function does indeed compute the mathematically correct remainder for negative values. To do that, you may want to introduce new temporary variables in remainder() that hold the results just before the function returns. Then, recompile the code, run it under the debugger and examine those temporary variables.)

3. Improving the program's efficiency

As you may have noticed, the function encrypt() calls the function remainder() on every character of the input stream. This is rather inefficient when considering files of even moderate sizes. A better choice would be to precompute the cipher for all characters ahead of time (by calling remainder() for each character), store the cipher in an array, and then use this array to map a character to its encrypted counterpart.

Your task is to fill in a function that precomputes this map.

The function prototype and description is:

void compute_map(int k)
/*
PURPOSE: Compute cipher for all characters and store in the map array
RECEIVES: k - the encryption key, or shift
RETURNS: void
SIDE EFFECTS: load the global array map with the cipher
*/

You will then need to modify the function encrypt() so that instead of calling remainder() on every character, it will index the corresponding map array element.

Here is the revised program, that you must complete:

To compile and run it follow review 1. If you also want to debug it review section 2.


BU CAS CS - Inspecting Command Line Arguments with GDB
This page created by Jonathan Alon <jalon@cs.bu.edu>.
Material adapted from the textbook "Computing Concepts with C++ Essentials", by Horstmann.