BUILD: debug: work around gcc-12 excessive -Warray-bounds warnings

As was first reported by Ilya in issue #1513, compiling with gcc-12
adds warnings about size 0 around each BUG_ON() call due to the
ABORT_NOW() macro that tries to dereference pointer value 1.

The problem is known, seems to be complex inside gcc and could only
be worked around for now by adjusting a pointer limit so that the
warning still catches NULL derefs in the first page but not other
values commonly used in kernels and boot loaders:
   https://gcc.gnu.org/git/gitweb.cgi?p=gcc.git;h=91f7d7e1b

It's described in more details here:
   https://gcc.gnu.org/bugzilla/show_bug.cgi?id=104657
   https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99578
   https://gcc.gnu.org/bugzilla/show_bug.cgi?id=103768

And some projects had to work around it using various approaches,
some of which are described in the bugs reports above, plus another
one here:
   https://mail.coreboot.org/hyperkitty/list/seabios@seabios.org/thread/HLK3BHP2T3FN6FZ46BIPIK3VD5FOU74Z/

In haproxy we can hide it by hiding the pointer in a DISGUISE() macro,
but this forces the pointer to be loaded into a register, so that
register is lost precisely where we want to get the maximum of them.

In our case we purposely use a low-value non-null pointer because:
  - it's mandatory that this value fits within an unmapped page and
    only the lowest one has this property
  - we really want to avoid register loads for the address, as these
    will be lost and will complicate the bug analysis, and they tend
    to be used for large addresses (i.e. instruction length limit).
  - the compiler may decide to optimize away the null deref when it
    sees it (seen in the past already)

As such, the current workaround merged in gcc-12 is not effective for
us.

Another approach consists in using pragmas to silently disable
-Warray-bounds and -Wnull-dereference only for this part. The problem
is that pragmas cannot be placed into macros.

The resulting solution consists in defining a forced-inlined function
only to trigger the crash, and surround the dereference with pragmas,
themselves conditionned to gcc >= 5 since older versions don't
understand them (but they don't complain on the dereference at least).

This way the code remains the same even at -O0, without the stack
pointer being modified nor any address register being modified on
common archs (x86 at least). A variation could have been to rely on
__builtin_trap() but it's not everywhere and it behaves differently
on different platforms (undefined opcode or a nasty abort()) while
the segv remains uniform and effective.

This may need to be backported to older releases once users start to
complain about gcc-12 breakage.
This commit is contained in:
Willy Tarreau 2022-05-09 20:07:21 +02:00
parent 393e42ae5f
commit 8a0fd3a36c

View File

@ -40,14 +40,28 @@
#define DUMP_TRACE() do { extern void ha_backtrace_to_stderr(void); ha_backtrace_to_stderr(); } while (0)
static inline __attribute((always_inline)) void ha_crash_now(void)
{
#if __GNUC_PREREQ__(5, 0)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warray-bounds"
#pragma GCC diagnostic ignored "-Wnull-dereference"
#endif
*(volatile char *)1 = 0;
#if __GNUC_PREREQ__(5, 0)
#pragma GCC diagnostic pop
#endif
my_unreachable();
}
#ifdef DEBUG_USE_ABORT
/* abort() is better recognized by code analysis tools */
#define ABORT_NOW() do { DUMP_TRACE(); abort(); } while (0)
#else
/* More efficient than abort() because it does not mangle the
* stack and stops at the exact location we need.
*/
#define ABORT_NOW() do { DUMP_TRACE(); (*(volatile int*)1=0); my_unreachable(); } while (0)
* stack and stops at the exact location we need.
*/
#define ABORT_NOW() do { DUMP_TRACE(); ha_crash_now(); } while (0)
#endif
/* This is the generic low-level macro dealing with conditional warnings and