gnu/lib/libgcc 's unwind-dw2.c and company: DW_CFA_{remember,restore}_state handling is incomplete and gcc 4.2.1 output tends to avoid those (powerpc examples)
Mark Millard
marklmi at yahoo.com
Fri Nov 9 05:01:11 UTC 2018
Some context:
/usr/src/gnu/lib/libgcc/Makefile has:
.if ${TARGET_CPUARCH} == "arm"
LIB2ADDEH = unwind-arm.c libunwind-arm.S pr-support.c unwind-c.c
.else
LIB2ADDEH = unwind-dw2.c unwind-dw2-fde-glibc.c unwind-sjlj.c gthr-gnat.c \
unwind-c.c
.endif
It appears that only powerpc families and sparc64 use
unwind-dw2.c and company unless someone uses
WITHOUT_LLVM_LIBUNWIND= deliberately for non-arm. This
is about unwind-dw2.c and company and uses a powerpc
context for illustration.
The problem:
gnu/lib/libgcc 's libgcc_s.so when based on unwind-dw2.c and
company do not correctly/completely handle:
DW_CFA_remember_state
DW_CFA_restore_state
as they are put to use by fairly modern c++ compilers,
use that requires cfa-rule save/restore.
Here I use g++8 for illustration, but g++6 is similar,
for example. Yet gcc 4.2.1 tends to "work" because it
has less complete stack (cfa) tracking in the exception
handling output it produces, thus commonly avoiding use
of DW_CFA_{remember,restore}_state .
The evidence:
I'm going to illustrate with 32-powerpc because I happen
to have g++ 4.2.1 handy in that context, as well as g++8 .
g++ 4.2.1 toolchain (and why it does not illustrate the problem):
(The c++ source code is not directly significant here --but
that it is the same for both g++*'s is important .)
(gdb) disass g
Dump of assembler code for function g():
(mixed with related dwarfdump lines and other notes)
0x018008e0: <off cfa=00(r1) >
0x018008e0 <+0>: mflr r0
0x018008e4 <+4>: stwu r1,-16(r1)
0x018008e8: <off cfa=16(r1) >
0x018008e8 <+8>: lis r9,385
0x018008ec <+12>: stw r0,20(r1)
0x018008f0: <off cfa=16(r1) > <off r65=04(cfa) >
0x018008f0 <+16>: lwz r0,3496(r9)
0x018008f4 <+20>: cmpwi cr7,r0,0
0x018008f8 <+24>: bne cr7,0x180090c <g()+44> (Note: the branch target requires cfa=16(r1) )
0x018008fc <+28>: lwz r0,20(r1)
0x01800900 <+32>: addi r1,r1,16
(Note: the cfa changed but nothing reports it here: still treated as cfa=16(r1) )
(Later: Compare to what a more modern g++ compilers produce after adjusting r1.)
0x01800904 <+36>: mtlr r0
0x01800908 <+40>: b 0x18008b8 <f()>
(Compare here to what more modern g++ compilers produce.)
0x0180090c <+44>: li r3,4
0x01800910 <+48>: bl 0x1810e0c <__cxa_allocate_exception at plt>
0x01800914 <+52>: lis r9,385
0x01800918 <+56>: addi r9,r9,3712
0x0180091c <+60>: lis r4,385
0x01800920 <+64>: lis r5,385
0x01800924 <+68>: stw r9,0(r3)
0x01800928 <+72>: addi r4,r4,3724
0x0180092c <+76>: addi r5,r5,3652
0x01800930 <+80>: bl 0x1810e14 <__cxa_throw at plt>
End of assembler dump.
No use of DW_CFA_remember_state or DW_CFA_restore_state, despite
the tail call optimization. This is why gcc 4.2.1 tests do not
show the DW_CFA_{remember,restore}_state problem here.
g++8 toolchain:
(but a.out using /lib/libgcc_s.so.1 in order to test that library)
(gdb) disass g
Dump of assembler code for function g():
(mixed with related dwarfdump lines)
(Note: f()'s code was inlined.)
0x01800978: <off cfa=00(r1) >
0x01800978 <+0>: lis r10,385
0x0180097c <+4>: stwu r1,-32(r1)
0x01800980: <off cfa=32(r1) >
0x01800980 <+8>: lwz r9,3176(r10)
0x01800984 <+12>: cmpwi cr7,r9,0
0x01800988 <+16>: bne cr7,0x18009ac <g()+52> (Note: branch target requires cfa=32(r1) )
0x0180098c <+20>: li r9,97
0x01800990 <+24>: stb r9,8(r1)
0x01800994 <+28>: lwz r9,3176(r10)
0x01800998 <+32>: addi r9,r9,1
0x0180099c <+36>: stw r9,3176(r10)
0x018009a0 <+40>: lbz r9,8(r1)
0x018009a4 <+44>: addi r1,r1,32
DW_CFA_remember_state is generate before the following change is made.
(unwind-dw2.c and company do not record the cfa=32(r1) material but should)
0x018009a8: <off cfa=00(r1) > (gcc 4.2.1 did not generate anything to cause this)
0x018009a8 <+48>: blr
DW_CFA_restore_state is generated to cause the following change:
0x018009ac: <off cfa=32(r1) >
(unwind-dw2.c and company did not record the cfa=32(r1) material but should have)
(unwind-dw2.c and company cause defaults here: cfa=0(r1), which is wrong)
0x018009ac <+52>: mflr r0
0x018009b0: <off cfa=32(r1) > <off r65=r0 >
(unwind-dw2.c and company caused defaults here: cfa=0(r1), which is wrong)
0x018009b0 <+56>: li r3,4
0x018009b4 <+60>: stw r0,36(r1)
0x018009b8: <off cfa=32(r1) > <off r65=04(cfa) >
(unwind-dw2.c and company caused defaults here: cfa=0(r1), which is wrong)
0x018009b8 <+64>: bl 0x1810cc4 <__cxa_allocate_exception at plt>
0x018009bc <+68>: lis r9,385
0x018009c0 <+72>: lis r5,385
0x018009c4 <+76>: addi r9,r9,2916
0x018009c8 <+80>: lis r4,385
0x018009cc <+84>: stw r9,0(r3)
0x018009d0 <+88>: addi r5,r5,3332
0x018009d4 <+92>: addi r4,r4,3380
0x018009d8 <+96>: bl 0x1810cec <__cxa_throw at plt>
End of assembler dump.
g++8 and the like track more of the cfa changes in the exception
handling information and so use DW_CFA_{remember,restore}_state
in more types of contexts. This leads to running into the
incomplete implementation in unwind-dw2.c and company more
as well.
Based on the incorrect cfa=0(r1) _Unwind_RaiseException ends up
looping, looking at the same frame each time, making no progress.
powerpc64 notes:
It turns out that devel/powerpc64-gcc used for buildworld generates
code in /lib/libgcc_s.so's exception handling code that hits the
problem, blocking all c++ exception handling via that library
because of _Unwind_RaiseException never finishing.
For testing I touched the library code to avoid ending up with the
DW_CFA_{remember,restore}_state usage where it was a problem. This
allowed for simple programs to be used for illustration of the
problem in that context --and programs that do not show the
problem as well (via lack of DW_CFA_{remember,restore}_state use).
clang++ does generate DW_CFA_{remember,restore}_state in some
contexts and these have the problem too.
Any DW_CFA_restore_state where the result should not have
cfa=0(r1) is broken for powerpc families.
For reference:
For the g++8 based a.out:
# ldd a.out
a.out:
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x41860000)
libm.so.5 => /lib/libm.so.5 (0x41972000)
libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x419ab000)
libc.so.7 => /lib/libc.so.7 (0x419cb000)
(g++8's own libgcc_s.so does not have such problems.)
# more exception_test1.cpp
#include <exception>
// -O2 context used.
volatile unsigned int v = 1;
extern int f()
{
volatile unsigned char c = 'a';
v++; // despite volatile the access to v in g
// was otherwise optimized out and the
// std::exception was not followed by
// code for f().
return c;
}
extern void g()
{
if (v) throw std::exception();
f(); // Modern g++'s: ends up inlined but the problem is demonstrated.
}
int main(void)
{
try {g();} // Used a separate function to avoid any potential
// special handling of code in main.
catch (std::exception& e) {}
return 0;
}
===
Mark Millard
marklmi at yahoo.com
( dsl-only.net went
away in early 2018-Mar)
More information about the freebsd-toolchain
mailing list