Bug 21066

Summary: icf folds template functions with different exception handling semantics
Product: binutils Reporter: gandhi.shaheen
Component: goldAssignee: Cary Coutant <ccoutant>
Status: RESOLVED FIXED    
Severity: normal CC: ian, markus, oremanj
Priority: P2    
Version: 2.24   
Target Milestone: ---   
Host: Target:
Build: Last reconfirmed: 2017-01-24 00:00:00
Attachments: example source code
Proposed fix

Description gandhi.shaheen 2017-01-18 23:20:07 UTC
Created attachment 9763 [details]
example source code

compiling the attached file with either g++ or clang++ and then linking with gold with --icf=safe will fold the two template functions into one, whereas using the standard linker will produce the correct output.

> g++ -std=c++14 -o test main.cpp -v

Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/4.9/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 4.9.4-2ubuntu1~14.04.1' --with-bugurl=file:///usr/share/doc/gcc-4.9/README.Bugs --enable-languages=c,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.9 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.9 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-4.9-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-4.9-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-4.9-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --enable-objc-gc --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.9.4 (Ubuntu 4.9.4-2ubuntu1~14.04.1) 
COLLECT_GCC_OPTIONS='-std=c++1y' '-o' 'test' '-v' '-shared-libgcc' '-mtune=generic' '-march=x86-64'
 /usr/lib/gcc/x86_64-linux-gnu/4.9/cc1plus -quiet -v -imultiarch x86_64-linux-gnu -D_GNU_SOURCE main.cpp -quiet -dumpbase main.cpp -mtune=generic -march=x86-64 -auxbase main -std=c++1y -version -fstack-protector -Wformat -Wformat-security -o /tmp/user/1000/ccgCMiPR.s
GNU C++ (Ubuntu 4.9.4-2ubuntu1~14.04.1) version 4.9.4 (x86_64-linux-gnu)
        compiled by GNU C version 4.9.4, GMP version 5.1.3, MPFR version 3.1.3, MPC version 1.0.1
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
ignoring duplicate directory "/usr/include/x86_64-linux-gnu/c++/4.9"
ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/4.9/../../../../x86_64-linux-gnu/include"
#include "..." search starts here:
#include <...> search starts here:
 /usr/include/c++/4.9
 /usr/include/x86_64-linux-gnu/c++/4.9
 /usr/include/c++/4.9/backward
 /usr/lib/gcc/x86_64-linux-gnu/4.9/include
 /usr/local/include
 /usr/lib/gcc/x86_64-linux-gnu/4.9/include-fixed
 /usr/include/x86_64-linux-gnu
 /usr/include
End of search list.
GNU C++ (Ubuntu 4.9.4-2ubuntu1~14.04.1) version 4.9.4 (x86_64-linux-gnu)
        compiled by GNU C version 4.9.4, GMP version 5.1.3, MPFR version 3.1.3, MPC version 1.0.1
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: e7137bb4cc46fb63f5e22418eff966b7
COLLECT_GCC_OPTIONS='-std=c++1y' '-o' 'test' '-v' '-shared-libgcc' '-mtune=generic' '-march=x86-64'
 as -v --64 -o /tmp/user/1000/cc7eBpBQ.o /tmp/user/1000/ccgCMiPR.s
GNU assembler version 2.24 (x86_64-linux-gnu) using BFD version (GNU Binutils for Ubuntu) 2.24
COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/4.9/:/usr/lib/gcc/x86_64-linux-gnu/4.9/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/4.9/:/usr/lib/gcc/x86_64-linux-gnu/
LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/4.9/:/usr/lib/gcc/x86_64-linux-gnu/4.9/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/4.9/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/4.9/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-std=c++1y' '-o' 'test' '-v' '-shared-libgcc' '-mtune=generic' '-march=x86-64'
 /usr/lib/gcc/x86_64-linux-gnu/4.9/collect2 -plugin /usr/lib/gcc/x86_64-linux-gnu/4.9/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/4.9/lto-wrapper -plugin-opt=-fresolution=/tmp/user/1000/ccAUZIoP.res -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lgcc --sysroot=/ --build-id --eh-frame-hdr -m elf_x86_64 --hash-style=gnu --as-needed -dynamic-linker /lib64/ld-linux-x86-64.so.2 -z relro -o test /usr/lib/gcc/x86_64-linux-gnu/4.9/../../../x86_64-linux-gnu/crt1.o /usr/lib/gcc/x86_64-linux-gnu/4.9/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/4.9/crtbegin.o -L/usr/lib/gcc/x86_64-linux-gnu/4.9 -L/usr/lib/gcc/x86_64-linux-gnu/4.9/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/4.9/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/4.9/../../.. /tmp/user/1000/cc7eBpBQ.o -lstdc++ -lm -lgcc_s -lgcc -lc -lgcc_s -lgcc /usr/lib/gcc/x86_64-linux-gnu/4.9/crtend.o /usr/lib/gcc/x86_64-linux-gnu/4.9/../../../x86_64-linux-gnu/crtn.o


> ./test
caught expected exception
caught expected exception

> g++ -std=c++14 -c -o main.o main.cpp   
> "../binutils-2.27.90/gold/ld-new" "--sysroot=/" "--build-id" "--eh-frame-hdr" "-m" "elf_x86_64" "--hash-style=gnu" "--as-needed" "-dynamic-linker" "/lib64/ld-linux-x86-64.so.2" "-z" "relro" "-o" "test" "/usr/lib/gcc/x86_64-linux-gnu/4.9/../../../x86_64-linux-gnu/crt1.o" "/usr/lib/gcc/x86_64-linux-gnu/4.9/../../../x86_64-linux-gnu/crti.o" "/usr/lib/gcc/x86_64-linux-gnu/4.9/crtbegin.o" "-L/usr/lib/gcc/x86_64-linux-gnu/4.9" "-L/usr/lib/gcc/x86_64-linux-gnu/4.9/../../../x86_64-linux-gnu" "-L/usr/lib/gcc/x86_64-linux-gnu/4.9/../../../../lib" "-L/lib/x86_64-linux-gnu" "-L/lib/../lib" "-L/usr/lib/x86_64-linux-gnu" "-L/usr/lib/../lib" "-L/usr/lib/gcc/x86_64-linux-gnu/4.9/../../.." "main.o" "--icf=safe" "-lstdc++" "-lm" "-lgcc_s" "-lgcc" "-lc" "-lgcc_s" "-lgcc" "/usr/lib/gcc/x86_64-linux-gnu/4.9/crtend.o" "/usr/lib/gcc/x86_64-linux-gnu/4.9/../../../x86_64-linux-gnu/crtn.o"

> ./test
caught expected exception
ERROR: caught unexpected exception
terminate called after throwing an instance of 'second_exception'
[3]    11748 abort (core dumped)  ./test
Comment 1 Cary Coutant 2017-01-24 20:48:42 UTC
The problem here is that the two functions are identical in every aspect, except for the LSDA in .gcc_except_table. Since the LSDA has relocations pointing to the code, but not the other way around, the linker's ICF code doesn't spot the difference.

It looks like we'll have to disable ICF for any functions that have an LSDA associated with them. A more ambitious approach might be to try to parse the LSDAs for each function and include their contents and relocations as part of the function's signature.
Comment 2 Markus Trippelsdorf 2017-12-09 08:02:27 UTC
*** Bug 22575 has been marked as a duplicate of this bug. ***
Comment 3 Cary Coutant 2018-08-06 20:19:34 UTC
*** Bug 23482 has been marked as a duplicate of this bug. ***
Comment 4 Joshua Oreman 2018-08-10 21:49:18 UTC
As I pointed out in PR 23482, hot/cold function splitting (the new gcc -freorder-blocks-and-partition optimization) can make this an issue even without any LSDA at all. Seems a full fix will need to consider the CIE/FDE contents that are relevant to the function as well.

Example uses asm for clarity, but I can obtain analogous issues using gcc-produced code, as demonstrated on the above PR.

$ g++ -o t.nofold t.cc -fuse-ld=gold
$ g++ -o t.fold t.cc -fuse-ld=gold -Wl,--icf=safe
$ ./t.nofold
caught 1: 42
caught 2: 42
$ ./t.fold
caught 1: 42
terminate called after throwing an instance of 'int'
Aborted (core dumped)
$ cat t.cc

extern "C" int printf(const char *fmt, ...);

void throw_it() { throw 42; }
extern void call_1(void(*)());
extern void call_2(void(*)());

asm(R"(
        .section .text._Z6call_1PFvvE,"ax",@progbits
        .globl _Z6call_1PFvvE
        .type _Z6call_1PFvvE, @function
_Z6call_1PFvvE:
        .cfi_startproc
        pushq   %rbx
        .cfi_def_cfa_offset 16
        .cfi_offset 3, -16
        jmp     _Z6call_1PFvvE.cold
        .cfi_endproc

        .section .text.unlikely._Z6call_1PFvvE,"ax",@progbits
        .type _Z6call_1PFvvE.cold, @function
_Z6call_1PFvvE.cold:
        .cfi_startproc
        .cfi_def_cfa_offset 16
        .cfi_offset 3, -16
        call    *%rdi
        .cfi_endproc

        .section .text._Z6call_2PFvvE,"ax",@progbits
        .globl _Z6call_2PFvvE
        .type _Z6call_2PFvvE, @function
_Z6call_2PFvvE:
        .cfi_startproc
        subq    $24, %rsp
        .cfi_def_cfa_offset 32
        jmp     _Z6call_2PFvvE.cold
        .cfi_endproc

        .section .text.unlikely._Z6call_2PFvvE,"ax",@progbits
        .type _Z6call_2PFvvE.cold, @function
_Z6call_2PFvvE.cold:
        .cfi_startproc
        .cfi_def_cfa_offset 32
        call    *%rdi
        .cfi_endproc
)");

int main() {
    try {
        call_1(throw_it);
    } catch (int val) {
        printf("caught 1: %d\n", val);
    }
    try {
        call_2(throw_it);
    } catch (int val) {
        printf("caught 2: %d\n", val);
    }
}
Comment 5 Joshua Oreman 2018-09-26 18:40:38 UTC
Created attachment 11278 [details]
Proposed fix

I believe this patch fixes all the issues described in this thread. I also submitted it to the mailing list: https://sourceware.org/ml/binutils/2018-09/msg00346.html
Comment 6 Sourceware Commits 2019-05-10 23:27:58 UTC
The master branch has been updated by Cary Coutant <ccoutant@sourceware.org>:

https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=e173ea00c2941af42ea4e2267446d6039a70da6e

commit e173ea00c2941af42ea4e2267446d6039a70da6e
Author: Joshua Oreman <oremanj@hudson-trading.com>
Date:   Sat May 11 07:27:10 2019 +0800

    Fix problem with ICF where diffs in EH frame info is ignored.
    
    	PR gold/21066
    	* gc.h (gc_process_relocs): Track relocations in .eh_frame sections
    	when ICF is enabled, even though the .eh_frame sections themselves
    	are not foldable.
    	* icf.cc (get_section_contents): Change arguments to permit operation
    	on just part of a section. Include extra identity regions in the
    	referring section's contents recursively.
    	(match_sections): Lock object here instead of in get_section_contents
    	so that get_section_contents can operate recursively.
    	(Icf::add_ehframe_links): New method.
    	(Icf::find_identical_sections): Pass .eh_frame sections to
    	add_ehframe_links(). Increase default iteration count from 2 to 3
    	because handling exception info typically requires one extra iteration.
    	* icf.h (Icf::extra_identity_list_): New data member with accessor.
    	(is_section_foldable_candidate): Include .gcc_except_table sections.
    	* options.h: Update documentation for new default ICF iteration count.
    	* testsuite/Makefile.am (icf_test_pr21066): New test case.
    	* testsuite/Makefile.in: Regenerate.
    	* testsuite/icf_test_pr21066.cc: New source file.
    	* testsuite/icf_test_pr21066.sh: New test script.
Comment 7 Cary Coutant 2019-06-08 00:29:13 UTC
Fixed on master.