Bug 6945

Summary: ld -r severely broken on 64-bit mingw / pe-x86-64
Product: binutils Reporter: Mikael Pettersson <mikpelinux>
Component: ldAssignee: unassigned
Status: RESOLVED FIXED    
Severity: critical CC: bug-binutils
Priority: P2    
Version: 2.19   
Target Milestone: ---   
Host: i686-pc-cygwin Target: x86_64-pc-mingw32
Build: Last reconfirmed:
Attachments: Do not update the vma's of output sections when performing a relocatable link on COFF objects.

Description Mikael Pettersson 2008-10-06 12:22:07 UTC
x86_64-pc-mingw32-ld -r runs without warnings, but gcc jump tables in the
resulting .o file are broken. At runtime a switch() that references a jump table
now branches off to la-la land and the process crashes.

My x86_64-pc-mingw32 cross toolchain is composed of binutils-2.19.50.20081006
and gcc-4.4.0 20080926, with runtime libs from mingw-w64-snapshot-20080917.
The cross toolchain was compiled by a i686-pc-cygwin native toolchain composed
of binutils-2.18.91 and gcc-4.3.2. The host runs Win XP 64 Pro.

To reproduce, compile and link the file below (bug.c):
1. x86_64-pc-mingw32-gcc -O -c bug.c
2. x86_64-pc-mingw32-ld -r -o bug2.o bug.o
3. x86_64-pc-mingw32-gcc -o bug bug.o
4. x86_64-pc-mingw32-gcc -o bug2 bug2.o

Running ./bug works and produces output like:
L90 == 000000000040165A
L95 == 0000000000403000
L95.L90_minus_L95 == -6566, L95 + -6566 == 000000000040165A

However, running ./bug2 instead fails with:
L90 == 000000000040165A
L95 == 0000000000403000
L95.L90_minus_L95 == -6406, L95 + -6406 == 00000000004016FA
ERROR

This error does not occur when compiling for 32-bit mingw, or when compiling for
Linux or Solaris (32- or 64-bit x86).

The source code for bug.c follows below. The inline asm() block constructs a gcc
switch() jump table in "pic" mode: entries aren't code lables but the
differences from the table itself to the code labels. The rest of the code just
verifies the contents of the jump table.

#include <stdio.h>

struct L95 {    /* mimics a gcc -fpic jump table */
    int L90_minus_L95;
};
extern const struct L95 L95;

struct L95info {        /* provides the actual target labels */
    void *L90;
};
struct L95info L95info;

void __attribute__((noinline)) foo(void)
{
    asm(
        "leaq _L95info(%rip), %rax\n\t"
        "leaq L90(%rip), %rdx\n\t"
        "movq %rdx, 0(%rax)\n\t"
        ".section .rdata,\"dr\"\n\t"
        ".align 4\n"
        "_L95:\n\t"
        ".long L90-_L95\n\t"
        ".text\n\t"
        "movl $1, %eax\n"
        "L90:\n\t"
        "addl %eax, %eax");
}

int main(void)
{
    int diff;
    void *result;

    foo();
    printf("L90 == %p\n", L95info.L90);
    printf("L95 == %p\n", &L95);
    diff = L95.L90_minus_L95;
    result = (char*)&L95 + diff;
    printf("L95.L90_minus_L95 == %d, L95 + %d == %p\n",
           diff, diff, result);
    if (result != L95info.L90) {
        printf("ERROR\n");
        return 1;
    }
    return 0;
}
Comment 1 Nick Clifton 2008-10-09 13:23:27 UTC
Hi Mikael,

  So - what is the difference between bug.o and bug2.o ?

  Is it possible to reproduce the bug without using inline assembly ?

Cheers
  Nick


Comment 2 Mikael Pettersson 2008-10-09 15:02:15 UTC
(In reply to comment #1)
> Hi Mikael,
> 
>   So - what is the difference between bug.o and bug2.o ?

objdump -h bug*.o

bug.o:     file format pe-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         000000a0  0000000000000000  0000000000000000  000000b4  2**4
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000000  0000000000000000  0000000000000000  00000000  2**4
                  ALLOC, LOAD, DATA
  2 .bss          00000000  0000000000000000  0000000000000000  00000000  2**4
                  ALLOC
  3 .rdata        00000050  0000000000000000  0000000000000000  00000154  2**4
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA

bug2.o:     file format pe-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         000000a0  0000000000000000  0000000000000000  000000b4  2**4
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000000  00000000000000a0  00000000000000a0  00000000  2**4
                  ALLOC, LOAD, DATA
  2 .rdata        00000050  00000000000000a0  00000000000000a0  00000154  2**4
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
  3 .bss          00000000  00000000000000f0  00000000000000f0  00000000  2**4
                  ALLOC

The files are the same size, but ld -r changes the order of the sections, and
appears to add the size of .text (0x0a) to the VMA/LMA of .data and .rdata.
And the offsets in the jump table are also off by 0x0a, but since the code and
the jump table haven't moved the jump table targets are now wrong.

>   Is it possible to reproduce the bug without using inline assembly ?

Certainly, it's just a gcc switch() jump table. I used inline asm in order to
write a test program that could inspect and verify the jump table instead of
crashing. The program below (cbug.c) shows the bug in plain C:

x86_64-pc-mingw32-gcc -O -c cbug.c
x86_64-pc-mingw32-ld -r -o cbug2.o cbug.o
x86_64-pc-mingw32-gcc -o cbug cbug.o
x86_64-pc-mingw32-gcc -o cbug2 cbug2.o
./cbug
at state 0
at state 1
at state 2
at state 3
at state 4
at state 5
at state 6
at state 7
2173
echo $?
0
./cbug2
at state 0
echo $?
128

Running cbug2 in gdb confirms that it branches to la-la land the first time it
enters the switch(), and then SEGVs.

#include <stdio.h>

enum state { q0, q1, q2, q3, q4, q5, q6, q7 };

int run(int x)
{
    enum state state = q0;

    for (;;) {
        printf("at state %d\n", state);
        fflush(stdout);
        switch (state) {
        case q0: x += 1; state = q1; continue;
        case q1: x *= 2; state = q2; continue;
        case q2: x += 3; state = q3; continue;
        case q3: x *= 4; state = q4; continue;
        case q4: x += 5; state = q5; continue;
        case q5: x *= 6; state = q6; continue;
        case q6: x += 7; state = q7; continue;
        case q7: return x;
        }
    }
}

int main(void)
{
    printf("%d\n", run(42));
    return 0;
}

Comment 3 Nick Clifton 2008-10-22 14:49:57 UTC
Created attachment 3018 [details]
Do not update the vma's of output sections when performing a relocatable link on COFF objects.
Comment 4 Nick Clifton 2008-10-22 14:51:50 UTC
Hi Mikael,

   Please could you try out the uploaded patch and see if it works for you.  I
think that it does the right thing, although I am not very happy with the method
I have chosen.  Still it does not seem to introduce any new regressions into the
linker testsuite and it does appear to work for the testcase you sent.

Cheers
  Nick
Comment 5 Mikael Pettersson 2008-10-25 09:41:56 UTC
(In reply to comment #4)

Nick,

I've tested your patch and it fixes both the two test cases I posted and the
original application where I first noticed the bug. Thanks.

/Mikael
Comment 6 Nick Clifton 2008-10-25 09:54:49 UTC
Hi Mikael,

  Ok, I have checked the patch in along with this changelog entry.

Cheers
  Nick

ld/ChangeLog
2008-10-25  Nick Clifton  <nickc@redhat.com>

	PR 6945
	* ldlang.c (lang_size_sections_1): Do not update the VMAs of
	output sections when performing a relocatable link on COFF
	objects.
Comment 7 Dave Korn 2009-09-12 15:11:14 UTC
FTR, this caused bug 10634.