https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86854

            Bug ID: 86854
           Summary: crash on stack unwinding with
                    reorder-blocks-and-partition + linker code folding +
                    C++ exceptions
           Product: gcc
           Version: 8.2.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: rtl-optimization
          Assignee: unassigned at gcc dot gnu.org
          Reporter: oremanj at mit dot edu
  Target Milestone: ---

With -freorder-blocks-and-partition in GCC 8 (but not in 7.x), GCC shows a
tendency to move exception-throwing C++ code into .cold fragments. The
resulting fragments are often identical between different functions that throw
the same type of exception, and thus will get merged by a linker's "identical
code folding" feature such as gold's --icf=safe. If the functions from which
the identical .cold fragments were split off have different stack layouts, the
merging will lead to misbehavior during stack unwinding, since the single
shared PC of the __cxa_throw call in the merged .cold fragment can't be
associated with the multiple different stack layouts of the multiple functions
that jump to it.

It's not immediately clear to me whether it's GCC or gold that should be doing
something to prevent this, but GCC seems to be much better positioned to
identify the danger, and the documentation for -freorder-blocks-and-partition
says suggestive things about inapplicability to exception-handling scenarios,
so I'm reporting here first.

This is an x86_64 Linux system with GCC from SVN 20180719 (r262861), very close
to 8.2 RC, and none of the few commits between there and 8.2 final appear
related to the issue at hand. gold is version 1.14 from Binutils 2.29.1. Issue
still appears with gold pulled from git master just now; haven't tried with a
newer gcc yet.

$ cat t1.cc
extern "C" int random();
struct nontrivial
{
    ~nontrivial() { (void) random(); }
};
int fn1(int value)
{
    nontrivial guard;
    if (value < 0) throw -value;
    return 0;
}

$ cat t2.cc
extern "C" int random();
struct nontrivial
{
    ~nontrivial() { (void) random(); }
};
int fn2(int value)
{
    // try to use a lot of registers
    int vals[] = {random(), random(), random(), random(),
                  random(), random(), random(), random()};
    int res = (((vals[0] + vals[1]) * (vals[2] + vals[3])) /
               ((vals[4] + vals[5]) * (vals[6] + vals[7])));
    nontrivial guard;
    if (value + (res & 1) < 0) throw -value;
    return 0;
}

$ cat tm.cc
extern int fn1(int);
extern int fn2(int);
extern "C" int puts(const char *s);
int main()
{
    try {
        fn1(-1);
        puts("didn't throw 1");
    } catch (int) {
        puts("caught 1");
    }
    try {
        fn2(-2);
        puts("didn't throw 2");
    } catch (int) {
        puts("caught 2");
    }
}

$ g++-8 -O2 -std=c++17 -freorder-blocks-and-partition -fuse-ld=gold -o t t1.cc
t2.cc tm.cc
$ ./t
caught 1
caught 2

$ g++-8 -O2 -std=c++17 -freorder-blocks-and-partition -fuse-ld=gold
-Wl,--icf=safe -o t t1.cc t2.cc tm.cc
$ ./t
caught 1
Segmentation fault (core dumped)

Reply via email to