The GNU debugger gdb is a very useful utility on Unix-like systems for debugging C, C++, assembly language and some other programs (with limitations). Our interest in it arises because we are using floating-point instructions to implement many of the constructs in miniC and gdb has good facility for monitoring the activity of the x86 FPU.
We do not plan to discuss here the full working of GNU gdb, but we demonstrate only its usefulness for checking and debugging the floating-point code generated by our compiler. We used gdb extensively while developing the action terms (attribute rules) in the miniC yacc grammar, the code generator and the assembly language built-in library. We shall demonstrate the use of gdb by a small example.
A small example program is as follows:
float: a,b
a = 1.1111
b = –2.0
printr(a * b)
end
This program, though simple, checks several aspects of miniC:
The program file – test8a.miniC – was compiled by a command:
miniC test8a.miniC
This gave three output files: test8a.S, test8a.AST and test8a.MAT, out of which we are concerned here with the assembly language output file test8a.S only. It looks like:
.text
.globl _start
_start:
nop
flds FC1
fstps a
flds FC2
fstps b
flds a
flds b
fmulp
fstps FPacc
pushl FPacc
call printr
addl $4, %esp
movl %eax, %ebx
movl $1, %eax
int $0x80
.section .data
DEG: .float 57.295780
E: .float 2.718282
FC1: .float 1.111100
FC2: .float –2.000000
FPacc: .float 0.000000
GAMMA: .float 0.577216
PHI: .float 1.618034
PI: .float 3.141593
PREC: .float 7.000000
a: .float 0.000000
b: .float 0.000000
float: .float 0.000000
int: .int 0
Notice that the two constants 1.1111 and –2.000 have been stored as if they are variables with generated names FC1 and FC2. Others are mostly miniC built-in constants. FPacc is a place where a float value can be stored temporarily. The program variables a and b also appear in the .data section.
Also note the nop instruction at the beginning of the assembly code, at start. This is necessary for gdb to operate correctly and allow us single stepping the assembly code.
We assembled and link edited the assembly code file test8a.S by the commands:
as –g –o test8a.o test8a.S
ld –o test8a test8a.o builtina.o
The –g switch will include enough information in the assembled code for the gdb to work correctly and access the assembly source code. builtina.o is the object code file of our built-in functions, with which we should link our program. As mentioned previously, you can use the standard C library libc.so with a miniC program if you want to.
Now we are ready to check the executable program test8a with gdb. We give the following command:
gdb test8a
Assuming that the source test8a.S is in the same directory, gdb will load both the executable and the source and display a rather verbose starting prompt. We can give simply a run command and gdb will try to execute our program, catching any exceptions and if so, stop and display them. Let us try that first.
gdb test8a
GNU gdb (GDB) 7.1
Copyright (C) 2010 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 ″i486-slackware-linux″.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>…
Reading symbols from
/home/hbd/compiler_book/book/progs/miniC/AST/test8a…done.
(gdb)
Now we give the run command:
Starting program: /home/hbd/compiler_book/book/progs/miniC/AST/test8a
–2.22220E + 00
Program exited with code 014.
(gdb)
This shows that the program terminated normally. The exit code 014 shown is the value in CPU register %ebx and is irrelevant here. Note the program output –2.22220E + 00 also printed by gdb, as it will print all the outputs (and wait for inputs).
However, our interest here is to demonstrate the single stepping, with monitoring of floating-point unit actions. For that we have to stop the normal, full-speed execution of our program by gdb, by inserting a break-point. We do that by first deciding where we want to put the break-point. For that we see the listing first:
(gdb) li
1 .text
2 .globl _start
3 _start: nop
4 flds FC1
5 fstps a
6 flds FC2
7 fstps b
8 flds a
9 flds b
10 fmulp
(gdb)
As we want to trace the FPU actions right from the beginning, we put the break-point at line number 4 and give run command again:
(gdb) b 4
Breakpoint 1 at 0x8048075: file test8a.S, line 4.
(gdb) run
Starting program: /home/hbd/compiler_book/book/progs/miniC/AST/test8a
Breakpoint 1, _start() at test8a.S:4
4 flds FC1
(gdb)
The program execution is suspended at the break-point and gdb shows the next instruction that will be executed if we proceed further. We shall do that by the single-stepping command s, but before we do that, let us have a look at the FPU, by giving info float command:
(gdb) info float
R7: Empty 0x00000000000000000000
R6: Empty 0x00000000000000000000
R5: Empty 0x00000000000000000000
R4: Empty 0x00000000000000000000
R3: Empty 0x00000000000000000000
R2: Empty 0x00000000000000000000
R1: Empty 0x00000000000000000000
=>R0: Empty 0x00000000000000000000
Status Word: 0x0000
TOP: 0
Control Word: 0x037f IM DM ZM OM UM PM
PC: Extended Precision (64-bits)
RC: Round to nearest
Tag Word: 0xffff
Instruction Pointer: 0x00:0x00000000
Operand Pointer: 0x00:0x00000000
Opcode: 0x0000
(gdb)
The gdb shows a snap-shot of the FPU. The eight floating-point registers are shown numbered R0 to R7, with the status of all of them being “empty”. An arrow => indicates the Top-of-Stack. The Status Word includes flags C3, C2 and C0, which are modified according to the result of a floating-point instruction. Other information like Control Word, Tag Word, Instruction Pointer, Operand Pointer and Opcode is also shown. The display shows that the FPU is in initialized condition. Although these information is very useful in a more detailed debugging, we shall from now onwards show only the content of the eight FP registers.
Now we alternately single-step and display FPU state:
(gdb) s
5 fstps a
(gdb) info float
=>R7: Valid 0x3fff8e38860000000000 + 1.111099958419799805
R6: Empty 0x00000000000000000000
R5: Empty 0x00000000000000000000
R4: Empty 0x00000000000000000000
R3: Empty 0x00000000000000000000
R2: Empty 0x00000000000000000000
R1: Empty 0x00000000000000000000
R0: Empty 0x00000000000000000000
Note that the previous display instruction, flds FC1, is executed, as a result of which TOS gets the value 1.111… (note the single-precision approximation) and status is “Valid”. We take one more single-step and display FPU:
(gdb) s
6 flds FC2
(gdb) info float
R7: Empty 0x3fff8e38860000000000
R6: Empty 0x00000000000000000000
R5: Empty 0x00000000000000000000
R4: Empty 0x00000000000000000000
R3: Empty 0x00000000000000000000
R2: Empty 0x00000000000000000000
R1: Empty 0x00000000000000000000
=>R0: Empty 0x00000000000000000000
The executed instruction fstps a stores the value in variable a and pops the FPU stack. The next two instructions – flds FC2 and fstps b – do similar things for the variable b.
We then execute the two instructions flds a and flds b which will load the two values on the stack:
(gdb) s
10 fmulp
(gdb) info float
R7: Valid 0x3fff8e38860000000000 +1.111099958419799805
=>R6: Valid 0xc0008000000000000000 –2
R5: Empty 0x00000000000000000000
R4: Empty 0x00000000000000000000
R3: Empty 0x00000000000000000000
R2: Empty 0x00000000000000000000
R1: Empty 0x00000000000000000000
R0: Empty 0x00000000000000000000
The next instruction to be executed is fmulp which multiplies the top two numbers and pops one value:
(gdb) s
11 fstps FPacc
(gdb) info float
=>R7: Valid 0xc0008e38860000000000 –2.222199916839599609
R6: Empty 0xc0008000000000000000
R5: Empty 0x00000000000000000000
R4: Empty 0x00000000000000000000
R3: Empty 0x00000000000000000000
R2: Empty 0x00000000000000000000
R1: Empty 0x00000000000000000000
R0: Empty 0x00000000000000000000
On single stepping, we have the following FPU state:
(gdb) s
12 pushl FPacc
(gdb) info float
R7: Empty 0xc0008e38860000000000
R6: Empty 0xc0008000000000000000
R5: Empty 0x00000000000000000000
R4: Empty 0x00000000000000000000
R3: Empty 0x00000000000000000000
R2: Empty 0x00000000000000000000
R1: Empty 0x00000000000000000000
=>R0: Empty 0x00000000000000000000
Note that the stack is now empty. The next few instructions will invoke the printr function to print the value in FPacc. We shall not try to trace them.
While developing FPU-based functions, it is generally advisable at the end of the function, to leave the FPU stack in the same condition as at start. For a program, it is almost mandatory to leave the FPU stack empty at the end of the program.
The above example should have helped you appreciate the considerable amount of help that gdb is able to give to a code developer.
3.147.70.247