Bug 4558

Summary: branch on register condition accepts relocations >= 128K
Product: binutils Reporter: David S. Miller <davem>
Component: gasAssignee: unassigned
Status: RESOLVED FIXED    
Severity: normal CC: bug-binutils
Priority: P2    
Version: 2.18   
Target Milestone: ---   
Host: sparc-unknown-linux-gnu Target: sparc-unknown-liunx-gnu
Build: sparc-unknown-linux-gnu Last reconfirmed:
Attachments: Fix for BFD_RELOC_SPARC_WDISP16 overflow checking.

Description David S. Miller 2007-05-28 07:26:20 UTC
If the displacement from a "br*" instruction to the target of
the branch is 128K or more, gas writes garbace into the displacement
field(s) instead of generating a relocation overflow error.

The branch on register condition instructions have a 16-bit signed
displacement field, but it is split into two pieces of the instruction.
The low 14 bits are at bits 0-13 of the instruction, bits 14 and 15
of the displacement are at bits 21-20 of the instruction.

The test case is very simple:

.text
1: nop
   .skip (128 * 1024)
   brz,pt %o0, 1b

Build this with "as -Av9a -o test.o test.s".  GAS allows this
erroneously, instead of generating a relocation failure.

You can look at the assembler of the object file with
"objdump --disassemble test.o" you will see output like:

00000000 <.text>:
       0:       01 00 00 00     nop
        ...
   20004:       02 da 3f ff     brz  %o0, 0x40000
   20008:       01 00 00 00     nop

which is obviously bogus.

sparc-opc.c uses 'k' character class for the relocation, this is
handled in tc-sparc.c with:

            case 'k':
              the_insn.reloc = /* RELOC_WDISP2_14 */ BFD_RELOC_SPARC_WDISP16;
              the_insn.pcrel = 1;
              goto immediate;

The comment reference to the mythical "RELOC_WDISP2_14" is quite
curious :-)

Later down in tc-sparc.c we have the handler for BFD_RELOC_SPARC_WDISP16:

        case BFD_RELOC_SPARC_WDISP16:
          /* FIXME: simplify.  */
          if (((val > 0) && (val & ~0x3fffc))
              || ((val < 0) && (~(val - 1) & ~0x3fffc)))
            as_bad_where (fixP->fx_file, fixP->fx_line,
                          _("relocation overflow"));
          /* FIXME: The +1 deserves a comment.  */
          val = (val >> 2) + 1;
          insn |= ((val & 0xc000) << 6) | (val & 0x3fff);
          break;

And here we have the bug, the reloc range checking is wrong.
The masks should be 0x1fffc instead of 0x3fffc.
Comment 1 David S. Miller 2007-05-28 07:33:59 UTC
Created attachment 1868 [details]
Fix for BFD_RELOC_SPARC_WDISP16 overflow checking.