#include <stdio.h>
void fun(int i) {
double d[1] = {3.14};
long a[2];
a[i] = 1073741824;
printf("%.24f\n", d[0]);
}
int main(void) {
fun(0);
fun(1);
fun(2);
fun(3);
fun(4);
fun(5);
fun(6);
fun(7);
fun(8);
fun(9);
return 0;
}
If you save this file as float.c
, you can compile it with gcc as follows:
gcc -o float float.c
GCC stands for the GNU Compiler Collection. It is an open source compiler available for virtually every platform in existence, and it is the most popular (and free!) way to compile C programs in the world.
This will produce an executable binary file called float
, which you can verify
with the ls
(list directory) command. The "$" below stands for the command prompt
in your operating system's terminal/shell.
$ ls -l float
You'll see the following directory listing. The letter "x" means you have the ability to execute the file.
-rwxr-xr-x 1 buy-ppham staff 12604 Apr 16 13:31 float
Normally, gcc
sets the execute permissions on the programs it produces automatically.
After all, why would you compile a program if you can't run it? However, occasionally the
permissions will get messed up if, for example, you extract a file created on Linux onto a Windows
computer, and then copy it back to Linux. You can fix the execute permission as follows
(on Linux), where program
is the name of your program file.
$ chmod +x program
If you run the float program, you'll see the results we described in lecture.
3.140000000000000124344979
3.140000000000000124344979
3.139999866485595703125000
2.000000610351562624344979
3.140000000000000124344979
3.140000000000000124344979
3.140000000000000124344979
3.140000000000000124344979
Segmentation fault
You can use the following code to print out the binary representation of any float (single or double representations.) Use the same compilation commands with gcc as above.
#include <stdio.h>
union ufloat {
float f;
unsigned u;
};
int main(void) {
union ufloat u;
u.f = 3.75;
printf("%x\n", u.u);
return 0;
}
You can change the value "3.75" to whatever float you wish to inspect. When you run the program above, you get the following output in hex:
40700000
This translates to the following binary number:
0100 0000 0111 0000 0000 0000 0000 0000
Separating this into sign, exponent, and fractional parts:
0 10000000 11100000000000000000000
The sign bit is zero, as we would expect for a positive number.
The exponent is a 1 shifted 7 positions to the left, or 2 to the 7th power, which is 128. However, for an 8-bit exponent field, we have a bias of 2**(8-1) - 1, or 127. So the actual exponent is 1.
The fractional part begins with three ones, and there is an implied 1 before that.
1.111
Shifting by the exponent, 2**1, gives us:
11.11
This is the binary representation of 3.75, which you can verify by visual inspection.
Here are some common tasks that you will need to do in gdb in order to complete Lab 3. First, follow the instructions in the lab handout to extract the lab file into your home directory.
First, start gdb on the program you wish to debug. Again, the '$' below just stands for the
command prompt in your operating system's terminal/shell. Substitute your actual bomb program
name for bombx
.
$ gdb bombx
You'll see the following startup preamble if gdb was able to load the debug information in your executable program correctly.
GNU gdb (GDB) Fedora (6.8.50.20090302-40.fc11)
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i586-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
(gdb)
You can run the list
command to show the source code that corresponds to the
original program.
(gdb) list
By default, list
will display the ten lines previous to the current execution
point in the program. In this case, we haven't started our program yet, so we are at the
beginning, in the main
function, which is the program entry point.
35 int main(int argc, char *argv[])
36 {
37 char *input;
38
39 /* Note to self: remember to port this bomb to Windows and put a
40 * fantastic GUI on it. */
41
42 /* When run with no arguments, the bomb reads its input lines
43 * from standard input. */
44 if (argc == 1) {
Let's start running the program, since we can't really inspect its state before we do that.
(gdb) start
Temporary breakpoint 1 at 0x8048995: file bomb.c, line 44.
Starting program: /homes/iws/ppham/bomb7/bomb
Temporary breakpoint 1, main (argc=1, argv=0xbffff654) at bomb.c:44
44 if (argc == 1) {
As you can see, gdb sets an automatic (temporary) breakpoint at the first executable line in
the main
function. A breakpoint interrupts normal program execution, freezing it
in time, so you can inspect the internal state of your program, dissecting it on your laboratory
table (don't worry, programs can't feel pain, and you put it all back together before anyone
notices!)
Let's step through the program line by line.
(gdb) step
This will take us to the next executable line.
45 infile = stdin;
Let's step a few more times until we get to the line that asks for
input. You'll note that printf
commands produce output interleaved with the gdb prompt.
72 input = read_line();
When you get to the line above, you'll need to enter in a string at the blank line following it in order to gdb to continue running the program.
(gdb) step
blah
73 phase_1(input);
One of the most useful gdb command is print
, which lets you print out different
expressions, especially source code variables. This doesn't work with registers and assembly
instructions, but you can get around that using x
and info registers
commands below.
In normal C programs thought, print is pretty useful. Below, while we are in the main
function, we can print the arguments that main was called with (strings from the command-line).
(gdb) print argc
$4 = 1
(gdb) print argv
$5 = (char **) 0xbffff644
(gdb) print argv[0]
$6 = 0xbffff78f "/homes/iws/ppham/bomb7/bomb"
argc
is the first argument to main, and it tells us how many command-line arguments
the user gave us. In this case, there was only 1.
argv
is the second argument to main, and if we try to print it directly, it tells us
it is of type char**
, that is, an array of strings (pointer to a pointer to a char).
Since we know there is exactly one string in this array (because argc was 1), we can index the
first element with argv[0]
. This gives us the full path and name of the bomb
executable which we used to call gdb in the first place, as expected.
Let's set a breakpoint at the beginning of the phase_1
program, since it seems
likely this is where we defuse the bomb's first phase.
(gdb) break phase_1
Breakpoint 2 at 0x8048e2a
Now let's use the continue
command to keep running until we run into another breakpoint,
in this case, the one we just set at the beginning of the phase_1 function.
(gdb) continue
Continuing.
Breakpoint 2, 0x08048e2a in phase_1 ()
At any point while debugging a program, you can check where you are by using the where
command. It will show all the nested function calls that you executed to get where you are now,
starting with the main
function at the bottom of the stack, and the current
function at the top of the stack.
(gdb) where
#0 0x08048e2a in phase_1 ()
#1 0x08048a35 in main (argc=1, argv=0xbffff654) at bomb.c:73
Sometimes the source code isn't available for a function, or you would like to inspect the
assembly instructions directly. You can do this by using the disas
command.
(gdb) disas
0x08048e24 : push %ebp
0x08048e25 : mov %esp,%ebp
0x08048e27 : sub $0x18,%esp
0x08048e2a : movl $0x80497c4,0x4(%esp)
0x08048e32 : mov 0x8(%ebp),%eax
0x08048e35 : mov %eax,(%esp)
0x08048e38 : call 0x8048e6b <strings_not_equal>
0x08048e3d : test %eax,%eax
0x08048e3f : je 0x8048e46 <phase_1+34>
0x08048e41 : call 0x8048f45 <explode_bomb>
0x08048e46 : leave
0x08048e47 : ret
End of assembler dump.
You can also specify the name of a function to disassemble. As with any of the commands above,
you can use the help
command to get more details about usage and options.
(gdb) help disas
Disassemble a specified section of memory.
Default is the function surrounding the pc of the selected frame.
With a /m modifier, source lines are included (if available).
With a single argument, the function surrounding that address is dumped.
Two arguments are taken as a range of memory to dump.
Typing help
by itself will show you a list of more general topics you can use to
navigate down to a specific command, if you don't know exactly what you are looking for.
To step through individual lines of assembly code, without returning to the next line of C
code, you must use the stepi
command instead of step
.
(gdb) stepi
0x08048e32 in phase_1 ()
(gdb) stepi
0x08048e35 in phase_1 ()
(gdb) stepi
0x08048e38 in phase_1 ()
Note that you can press the up and down arrow keys to go backwards and forwards through your command history. This may save you typing if you want to reuse or slightly modify a previous command.
You can print out the current value of the processor's registers by using the following command.
(gdb) info registers
eax 0x804a860 134522976
ecx 0x6 6
edx 0x1 1
ebx 0x4d7a5ff4 1299865588
esp 0xbffff550 0xbffff550
ebp 0xbffff568 0xbffff568
esi 0xbffff644 -1073744316
edi 0x0 0
eip 0x8048e2a 0x8048e2a
eflags 0x200286 [ PF SF IF ID ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
You can inspect locations in memory using the x
command, and optionally specify what
type to expect, and how many, at the given address. For example, we can use the address stored
in the %eax register from the previous command, and tell it to expect one string (1s):
(gdb) x/1s 0x804a860
0x804a860 : "blah"
Run help x
to see other datatypes you can use besides strings.