From 9c7477ef34ac8260c908b637fdc3c291a018425c Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 4 Oct 2017 17:29:21 +1100 Subject: [PATCH] cxx: Add KConfig option for C++ exceptions, disable by default Fixes https://github.com/espressif/esp-idf/issues/1072 (Additional 20KB is still used if C++ exception support is enabled in menuconfig.) --- Kconfig | 14 ++++- components/cxx/component.mk | 8 +++ components/cxx/cxx_exception_stubs.cpp | 87 ++++++++++++++++++++++++++ components/cxx/test/test_cxx.cpp | 6 ++ components/esp32/cpu_start.c | 2 + make/project.mk | 6 ++ 6 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 components/cxx/cxx_exception_stubs.cpp diff --git a/Kconfig b/Kconfig index 2cf2424812..c641882f84 100644 --- a/Kconfig +++ b/Kconfig @@ -93,7 +93,19 @@ config OPTIMIZATION_ASSERTIONS_DISABLED endchoice # assertions -endmenu # Optimization level +config CXX_EXCEPTIONS + bool "Enable C++ exceptions" + default n + help + Enabling this option compiles all IDF C++ files with exception support enabled. + + Disabling this option disables C++ exception support in all compiled files, and any libstdc++ code which throws + an exception will abort instead. + + Enabling this option currently adds an additional 20KB of heap overhead, and 4KB of additional heap is allocated + the first time an exception is thrown in user code. + +endmenu # Compiler Options menu "Component config" source "$COMPONENT_KCONFIGS" diff --git a/components/cxx/component.mk b/components/cxx/component.mk index 5d56384779..00e05e0db2 100644 --- a/components/cxx/component.mk +++ b/components/cxx/component.mk @@ -1,3 +1,11 @@ # Mark __cxa_guard_dummy as undefined so that implementation of static guards # is taken from cxx_guards.o instead of libstdc++.a COMPONENT_ADD_LDFLAGS += -u __cxa_guard_dummy + +ifndef CONFIG_CXX_EXCEPTIONS +# If exceptions are disabled, ensure our fatal exception +# hooks are preferentially linked over libstdc++ which +# has full exception support +COMPONENT_ADD_LDFLAGS += -u __cxx_fatal_exception +endif + diff --git a/components/cxx/cxx_exception_stubs.cpp b/components/cxx/cxx_exception_stubs.cpp new file mode 100644 index 0000000000..4c914b2b71 --- /dev/null +++ b/components/cxx/cxx_exception_stubs.cpp @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include + +#ifndef CONFIG_CXX_EXCEPTIONS + +const char *FATAL_EXCEPTION = "Fatal C++ exception: "; + +extern "C" void __cxx_fatal_exception(void) +{ + abort(); +} + +extern "C" void __cxx_fatal_exception_message(const char *msg) +{ + printf("%s%s\n", FATAL_EXCEPTION, msg); + abort(); +} + +extern "C" void __cxx_fatal_exception_int(int i) +{ + printf("%s (%d)\n", FATAL_EXCEPTION, i); + abort(); +} + +void std::__throw_bad_exception(void) __attribute__((alias("__cxx_fatal_exception"))); + +void std::__throw_bad_alloc(void) __attribute__((alias("__cxx_fatal_exception"))); + +void std::__throw_bad_cast(void) __attribute__((alias("__cxx_fatal_exception"))); + +void std::__throw_bad_typeid(void) __attribute__((alias("__cxx_fatal_exception"))); + +void std::__throw_logic_error(const char*) __attribute__((alias("__cxx_fatal_exception_message"))); + +void std::__throw_domain_error(const char*) __attribute__((alias("__cxx_fatal_exception_message"))); + +void std::__throw_invalid_argument(const char*) __attribute__((alias("__cxx_fatal_exception_message"))); + +void std::__throw_length_error(const char*) __attribute__((alias("__cxx_fatal_exception_message"))); + +void std::__throw_out_of_range(const char*) __attribute__((alias("__cxx_fatal_exception_message"))); + +void std::__throw_out_of_range_fmt(const char*, ...) __attribute__((alias("__cxx_fatal_exception_message"))); + +void std::__throw_runtime_error(const char*) __attribute__((alias("__cxx_fatal_exception_message"))); + +void std::__throw_range_error(const char*) __attribute__((alias("__cxx_fatal_exception_message"))); + +void std::__throw_overflow_error(const char*) __attribute__((alias("__cxx_fatal_exception_message"))); + +void std::__throw_underflow_error(const char*) __attribute__((alias("__cxx_fatal_exception_message"))); + +void std::__throw_ios_failure(const char*) __attribute__((alias("__cxx_fatal_exception_message"))); + +void std::__throw_system_error(int) __attribute__((alias("__cxx_fatal_exception_int"))); + +void std::__throw_bad_function_call(void) __attribute__((alias("__cxx_fatal_exception"))); + +void std::__throw_future_error(int) __attribute__((alias("__cxx_fatal_exception_int"))); + + +/* The following definitions are needed because libstdc++ is also compiled with + __throw_exception_again defined to throw, and some other exception code in a few places. + + This cause exception handler code to be emitted in the library even though it's mostly + unreachable (as any libstdc++ "throw" will first call one of the above stubs, which will abort). + + If these are left out, a bunch of unwanted exception handler code is linked. + + Note: these function prototypes are not correct. +*/ + +extern "C" void __cxa_allocate_exception(void) __attribute__((alias("__cxx_fatal_exception"))); +extern "C" void __cxa_begin_catch(void) __attribute__((alias("__cxx_fatal_exception"))); +extern "C" void __cxa_end_catch(void) __attribute__((alias("__cxx_fatal_exception"))); +extern "C" void __cxa_get_exception_ptr(void) __attribute__((alias("__cxx_fatal_exception"))); +extern "C" void __cxa_free_exception(void) __attribute__((alias("__cxx_fatal_exception"))); +extern "C" void __cxa_rethrow(void) __attribute__((alias("__cxx_fatal_exception"))); +extern "C" void __cxa_throw(void) __attribute__((alias("__cxx_fatal_exception"))); +extern "C" void __cxa_call_terminate(void) __attribute__((alias("__cxx_fatal_exception"))); + +bool std::uncaught_exception() __attribute__((alias("__cxx_fatal_exception"))); + +#endif // CONFIG_CXX_EXCEPTIONS diff --git a/components/cxx/test/test_cxx.cpp b/components/cxx/test/test_cxx.cpp index af73d9ea92..743a6869de 100644 --- a/components/cxx/test/test_cxx.cpp +++ b/components/cxx/test/test_cxx.cpp @@ -188,8 +188,12 @@ TEST_CASE("before scheduler has started, static initializers work correctly", "[ TEST_ASSERT_EQUAL(2, StaticInitTestBeforeScheduler::order); } +#ifdef CONFIG_CXX_EXCEPTIONS + TEST_CASE("c++ exceptions work", "[cxx]") { + /* Note: This test currently trips the memory leak threshold + as libunwind allocates ~4KB of data on first exception. */ int thrown_value; try { @@ -203,6 +207,8 @@ TEST_CASE("c++ exceptions work", "[cxx]") printf("OK?\n"); } +#endif + /* These test cases pull a lot of code from libstdc++ and are disabled for now */ #if 0 diff --git a/components/esp32/cpu_start.c b/components/esp32/cpu_start.c index 3aefdce0a4..ee0edc2e23 100644 --- a/components/esp32/cpu_start.c +++ b/components/esp32/cpu_start.c @@ -379,8 +379,10 @@ void start_cpu1_default(void) static void do_global_ctors(void) { +#ifdef CONFIG_CXX_EXCEPTIONS static struct object ob; __register_frame_info( __eh_frame, &ob ); +#endif void (**p)(void); for (p = &__init_array_end - 1; p >= &__init_array_start; --p) { diff --git a/make/project.mk b/make/project.mk index 60ecce3ec5..deaba74a3a 100644 --- a/make/project.mk +++ b/make/project.mk @@ -303,6 +303,12 @@ CXXFLAGS := $(strip \ $(CXXFLAGS) \ $(EXTRA_CXXFLAGS)) +ifdef CONFIG_CXX_EXCEPTIONS +CXXFLAGS += -fexceptions +else +CXXFLAGS += -fno-exceptions +endif + export CFLAGS CPPFLAGS CXXFLAGS # Set host compiler and binutils