12.10 Use of gdb to Debug the FPU Operations

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.

12.10.1 An Example miniC Program

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:

  • Type declaration and type setting of the variables.
  • Ability to handle variable list in declarations.
  • Memory locations assignment to variables.
  • Detection and conversion of floating-point numbers (in contrast to integers).
  • Checking unary-minus operation.
  • Working of assignment statement.
  • Checking arithmetic expression evaluation.
  • Proper argument passing to a function.
  • Ability of the printr to print a floating-point number.
  • Program termination detection.

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.227.72.212