RFC: Unify overflow checking
Daniel Jacobowitz
drow@false.org
Tue Apr 22 18:33:00 GMT 2008
Does anyone remember why bfd_check_overflow and _bfd_relocate_contents
have different overflow checks? It looks to me as if the ones in
_bfd_relocate_contents are always appropriate, and bfd_check_overflow
is simply stale. This (untested so far) patch unifies them.

Daniel Jacobowitz
CodeSourcery
20080422 Daniel Jacobowitz <dan@codesourcery.com>
* reloc.c (bfd_check_overflow): Update from _bfd_relocate_contents.
(_bfd_relocate_contents): Use bfd_check_overflow.
Index: reloc.c
===================================================================
RCS file: /cvs/src/src/bfd/reloc.c,v
retrieving revision 1.174
diff u p r1.174 reloc.c
 reloc.c 16 Apr 2008 08:51:18 0000 1.174
+++ reloc.c 22 Apr 2008 14:06:27 0000
@@ 495,7 +495,7 @@ bfd_check_overflow (enum complain_overfl
unsigned int addrsize,
bfd_vma relocation)
{
 bfd_vma fieldmask, addrmask, signmask, ss, a;
+ bfd_vma fieldmask, addrmask, signmask, ss, a, b, sum;
bfd_reloc_status_type flag = bfd_reloc_ok;
/* Note: BITSIZE should always be <= ADDRSIZE, but in case it's not,
@@ 506,6 +506,7 @@ bfd_check_overflow (enum complain_overfl
signmask = ~fieldmask;
addrmask = N_ONES (addrsize)  fieldmask;
a = (relocation & addrmask) >> rightshift;;
+ b = (x & howto>src_mask & addrmask) >> bitpos;
switch (how)
{
@@ 527,11 +528,53 @@ bfd_check_overflow (enum complain_overfl
ss = a & signmask;
if (ss != 0 && ss != ((addrmask >> rightshift) & signmask))
flag = bfd_reloc_overflow;
+
+ /* We only need this next bit of code if the sign bit of B
+ is below the sign bit of A. This would only happen if
+ SRC_MASK had fewer bits than BITSIZE. Note that if
+ SRC_MASK has more bits than BITSIZE, we can get into
+ trouble; we would need to verify that B is in range, as
+ we do for A above. */
+ ss = ((~howto>src_mask) >> 1) & howto>src_mask;
+ ss >>= bitpos;
+
+ /* Set all the bits above the sign bit. */
+ b = (b ^ ss)  ss;
+
+ /* Now we can do the addition. */
+ sum = a + b;
+
+ /* See if the result has the correct sign. Bits above the
+ sign bit are junk now; ignore them. If the sum is
+ positive, make sure we did not have all negative inputs;
+ if the sum is negative, make sure we did not have all
+ positive inputs. The test below looks only at the sign
+ bits, and it really just
+ SIGN (A) == SIGN (B) && SIGN (A) != SIGN (SUM)
+
+ We mask with addrmask here to explicitly allow an address
+ wraparound. The Linux kernel relies on it, and it is
+ the only way to write assembler code which can run when
+ loaded at a location 0x80000000 away from the location at
+ which it is linked. */
+ if (((~(a ^ b)) & (a ^ sum)) & signmask & addrmask)
+ flag = bfd_reloc_overflow;
break;
case complain_overflow_unsigned:
 /* We have an overflow if the address does not fit in the field. */
 if ((a & signmask) != 0)
+ /* Checking for an unsigned overflow is relatively easy:
+ trim the addresses and add, and trim the result as well.
+ Overflow is normally indicated when the result does not
+ fit in the field. However, we also need to consider the
+ case when, e.g., fieldmask is 0x7fffffff or smaller, an
+ input is 0x80000000, and bfd_vma is only 32 bits; then we
+ will get sum == 0, but there is an overflow, since the
+ inputs did not fit in the field. Instead of doing a
+ separate test, we can check for this by oring in the
+ operands when testing for the sum overflowing its final
+ field. */
+ sum = (a + b) & addrmask;
+ if ((a  b  sum) & signmask)
flag = bfd_reloc_overflow;
break;
@@ 1424,92 +1467,10 @@ _bfd_relocate_contents (reloc_howto_type
in a type larger than bfd_vma, which would be inefficient. */
flag = bfd_reloc_ok;
if (howto>complain_on_overflow != complain_overflow_dont)
 {
 bfd_vma addrmask, fieldmask, signmask, ss;
 bfd_vma a, b, sum;

 /* Get the values to be added together. For signed and unsigned
 relocations, we assume that all values should be truncated to
 the size of an address. For bitfields, all the bits matter.
 See also bfd_check_overflow. */
 fieldmask = N_ONES (howto>bitsize);
 signmask = ~fieldmask;
 addrmask = N_ONES (bfd_arch_bits_per_address (input_bfd))  fieldmask;
 a = (relocation & addrmask) >> rightshift;
 b = (x & howto>src_mask & addrmask) >> bitpos;

 switch (howto>complain_on_overflow)
 {
 case complain_overflow_signed:
 /* If any sign bits are set, all sign bits must be set.
 That is, A must be a valid negative address after
 shifting. */
 signmask = ~(fieldmask >> 1);
 /* Fall thru */

 case complain_overflow_bitfield:
 /* Much like the signed check, but for a field one bit
 wider. We allow a bitfield to represent numbers in the
 range 2**n to 2**n1, where n is the number of bits in the
 field. Note that when bfd_vma is 32 bits, a 32bit reloc
 can't overflow, which is exactly what we want. */
 ss = a & signmask;
 if (ss != 0 && ss != ((addrmask >> rightshift) & signmask))
 flag = bfd_reloc_overflow;

 /* We only need this next bit of code if the sign bit of B
 is below the sign bit of A. This would only happen if
 SRC_MASK had fewer bits than BITSIZE. Note that if
 SRC_MASK has more bits than BITSIZE, we can get into
 trouble; we would need to verify that B is in range, as
 we do for A above. */
 ss = ((~howto>src_mask) >> 1) & howto>src_mask;
 ss >>= bitpos;

 /* Set all the bits above the sign bit. */
 b = (b ^ ss)  ss;

 /* Now we can do the addition. */
 sum = a + b;

 /* See if the result has the correct sign. Bits above the
 sign bit are junk now; ignore them. If the sum is
 positive, make sure we did not have all negative inputs;
 if the sum is negative, make sure we did not have all
 positive inputs. The test below looks only at the sign
 bits, and it really just
 SIGN (A) == SIGN (B) && SIGN (A) != SIGN (SUM)

 We mask with addrmask here to explicitly allow an address
 wraparound. The Linux kernel relies on it, and it is
 the only way to write assembler code which can run when
 loaded at a location 0x80000000 away from the location at
 which it is linked. */
 if (((~(a ^ b)) & (a ^ sum)) & signmask & addrmask)
 flag = bfd_reloc_overflow;
 break;

 case complain_overflow_unsigned:
 /* Checking for an unsigned overflow is relatively easy:
 trim the addresses and add, and trim the result as well.
 Overflow is normally indicated when the result does not
 fit in the field. However, we also need to consider the
 case when, e.g., fieldmask is 0x7fffffff or smaller, an
 input is 0x80000000, and bfd_vma is only 32 bits; then we
 will get sum == 0, but there is an overflow, since the
 inputs did not fit in the field. Instead of doing a
 separate test, we can check for this by oring in the
 operands when testing for the sum overflowing its final
 field. */
 sum = (a + b) & addrmask;
 if ((a  b  sum) & signmask)
 flag = bfd_reloc_overflow;
 break;

 default:
 abort ();
 }
 }
+ flag = bfd_check_overflow (howto>complain_on_overflow, howto>bitsize,
+ rightshift,
+ bfd_arch_bits_per_address (input_bfd),
+ relocation);
/* Put RELOCATION in the right bits. */
relocation >>= (bfd_vma) rightshift;
More information about the Binutils
mailing list