bug-gnu-utils
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

BUG: Gas (tc-i386.c). The check for loop/jecxz disp is done unsigned.


From: Michel SIX
Subject: BUG: Gas (tc-i386.c). The check for loop/jecxz disp is done unsigned.
Date: Thu, 10 Oct 2002 17:53:49 +0200 (DFT)

                                           Paris, 10th october 2002

                                           Michel Six
                                           Departement de Geophysique Appliquee
                                           case 105
                                           Universite Pierre et Marie Curie
                                           4 Place Jussieu
                                           75252 Paris cedex 05
                                           France

                                           <address@hidden>

                                           to <address@hidden>


Dear colleague,

BUG: Gas (tc-i386.c). The check for loop/jecxz disp is done unsigned.
I.e, assembling a backwards loop instruction may produce a forward jump
without any warning.

I run Linux Slackware 8.1 distribution on a PC with an AMD K6 3D processor.
I use the ext2 filesystem.

Linux version 2.4.18 (address@hidden) (gcc version 2.95.3 20010315 (release))
 #4 Fri May 31 01:25:31 PDT 2002

GNU assembler 2.12.90.0.9 20020526


Description of the bug.
=======================
In order to make several assembly routines work, I had got to replace some
loop instructions by the equivalent suite of 2 instructions, "dec ecx" then a
"jnz".

So I decided to make some experiments, the conclusion of which is:

If the displacement (backwards) is greater than 256 "as" detects the error,
but says nothing if it is between 128 and 255 and writes a byte that will
send the instruction pointer in the forward direction.

Of course the same bug occurs for the jcxz instruction, that is similar.


Showing the bug (method+example).
=================================
In order to exercise the bug at leisure, I did contruct two simple routines.
They are written in gnu-fortran-77 (see listings in the mail attachments).

The main routine bugloop.for does in succession:

   - ask for a number n that gives by the simple formula 5*n+2 the relative
     jump of the loop.

   - create a file zasm.s that will exercise 2 times the loop and recompute
     the given number.

   - assemble the zasm.s into zasm.o

   - disassemble with objdump zasm.o into zasm.d

   - extract from the dump file the "loop" line and write it to the screen,
     showing then the displacement as computed by "as".

   - compile an auxiliary routine bugloop2.for and link it with zasm.o
     (that routine will call zasm and write the output to the screen)

   - run bugloop2

The formula is 5*n+2, so:
  with n <= 25 everything is ok and the output is n.
  with n >= 26 and n <= 50 no warning and bugloop2 writes nothing.
  with n >= 51 "as" refuses to assemble zasm and produces a message.
  
(Of course I did a complete backup before the 26 to 50 tries, just in case... )

Here is the shortened zasm.s for n=26 (full version in attachment)
----------------------------------------------------------
#***********************************************************************
#       subroutine zasm(ival)
#       ival is the returned value
#***********************************************************************
.text
.global zasm_
zasm_:
         push   %ebp
         mov    %esp,%ebp
         xor    %eax,%eax
         mov    $0x02,%ecx
bugloop:
         add    $0x10000,%eax  (26 times)
         ...
         loop   bugloop
         shr    $0x11,%eax
         mov    0x8(%ebp),%edx         # ival
         mov    %eax,(%edx)
         leave
         ret
----------------------------------------------------------

and here is the shortened disassembled zasm.d for n=26
----------------------------------------------------------

zasm.o:     file format elf32-i386

Disassembly of section .text:

00000000 <zasm_>:
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   31 c0                   xor    %eax,%eax
   5:   b9 02 00 00 00          mov    $0x2,%ecx

0000000a <bugloop>:
   a:   05 00 00 01 00          add    $0x10000,%eax    (26 times)
   ...
  8c:   e2 7c                   loop   10a <bugloop+0x100>    ! nice isnt'it
  8e:   c1 e8 11                shr    $0x11,%eax
  91:   8b 55 08                mov    0x8(%ebp),%edx
  94:   89 02                   mov    %eax,(%edx)
  96:   c9                      leave  
  97:   c3                      ret
----------------------------------------------------------
For that peculiar value of n -and only on one of my computers- at least there
was a message "Trace trap"


Diagnostic:
===========
 For n=55, the message is:
 "zasm.s:71: Error: value of fffffffffffffeeb too large for field of 1 bytes
  at 000000000000011e"

(Note: in the following all paths will be relative to a "binutils-2.12.90.0.9"
source tree)

This message comes from ./gas/write.c (fixup_segment) lines 2890-2908: i.e.
--------------------------------------------------------
            {
              valueT mask;

              mask = 0;
              mask--;           /* Set all bits to one.  */
              mask <<= size * 8 - (fixP->fx_signed ? 1 : 0);
              if ((add_number & mask) != 0 && (add_number & mask) != mask)
                {
                  char buf[50], buf2[50];
                  sprint_value (buf, fragP->fr_address + where);
                  if (add_number > 1000)
                    sprint_value (buf2, add_number);
                  else
                    sprintf (buf2, "%ld", (long) add_number);
                  as_bad_where (fixP->fx_file, fixP->fx_line,
                                _("value of %s too large for field of %d bytes 
at %s"),
                                buf2, size, buf);
                } /* Generic error checking.  */
            }
--------------------------------------------------------

A print of fixP->fx_signed showed that it was set to 0 , whence the bug for
26-50 values.
And yet a print of fixP->fx_r_type showed this to be 0x0d, i.e as it would
have been, BFD_RELOC_8_PCREL.


A solution:
===========
Searching for fx_signed in every file in the source tree, one can see that
it is set to 0 in fix_new_internal (subfunction of ./gas/write.c).

It is only set to 1, unconditionally in ./gas/config/tc-h3800.c, and conditio-
nally in ./gas/config/tc-m68k.c, which is not surprising for Motorola cpu's.
 
Looking more precisely into ./gas/config/tc-i386.c, one can see that in
the case of loop or jcxz, md_assemble() calls output_insn(), from where 
output_jump() is called.
This last routine then calls fix_new_exp(), that finally calls another write.c
subfunction -that does the real job- fix_new_internal().

fix_new_exp() returns the pointer of the fix. 

So the right place to set fx_signed is just after the call to fix_new_exp()
in output_jump(), but of course after a check that we are really dealing
with a loop/jecxz source line.

Testing again our "experiment" routines after a recompilation of the modified
gas package shows that the bug has indeed disappeared.


The diff -u file (also found in attachments):
=============================================
I hope I did put the diff in the right order. The READER writer obviously 
thinks that everybody has *his* idea of diff!. The only unambiguous way to
put what is wanted is through an example: diff file.original file.modified.
(from some examples of diffs I infer that it is the case) 
----------------------------------------------------------
--- tc-i386.cori        2002-05-24 00:10:10.000000000 +0200
+++ tc-i386.c   2002-10-08 13:50:04.000000000 +0200
@@ -2993,6 +2993,7 @@
 {
   char *p;
   int size;
+  fixS *fixP;
 
   if (i.tm.opcode_modifier & JumpByte)
     {
@@ -3043,8 +3044,11 @@
   p = frag_more (1 + size);
   *p++ = i.tm.base_opcode;
 
-  fix_new_exp (frag_now, p - frag_now->fr_literal, size,
+  fixP = fix_new_exp (frag_now, p - frag_now->fr_literal, size,
               i.op[0].disps, 1, reloc (size, 1, 1, i.reloc[0]));
+
+  if (i.tm.opcode_modifier & JumpByte)
+    fixP->fx_signed = 1;
 }
 
 static void
----------------------------------------------------------


A proposed ChangeLog item (also found in attachments):
======================================================
My english is probably not good enough!
----------------------------------------------------------
2002-mm-dd  Michel Six  <address@hidden>

        * config/tc-i386.c (output_jump): Set signed the test of disp value
        for loop/jecxz instructions.
----------------------------------------------------------


PS.
===
As I am curious, I did a recompile of gcc-2.95.3, using the modified as.
Of course I got no new warnings. It would indeed have been surprising if
the translation from RTL to intel codes would have used such special codes
as loop/jecxz.

Attached files:
===============
zasm.s   zasm.d   bugloop.for   bugloop2.for   tc-i386.c.diff  changelog.txt

Yours sincerely.

Attachment: zasm.s
Description: zasm.s

Attachment: zasm.d
Description: zasm.d

Attachment: bugloop.for
Description: bugloop.for

Attachment: bugloop2.for
Description: bugloop2.for

Attachment: tc-i386.c.diff
Description: tc-i386.c.diff

Attachment: changelog.txt
Description: changelog.txt


reply via email to

[Prev in Thread] Current Thread [Next in Thread]