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-ppc mailing list