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 via freebsd-toolchain <[email protected]>
Newsgroups gmane.os.freebsd.devel.toolchain,gmane.os.freebsd.devel.ppc
Message-ID <[email protected]>
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@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@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@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@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)
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.