c++exceptionembeddedcode-size

How do I enforce disabling of C++ exceptions?


I'm using C++ for a low-level firmware project for a microcontroller. Everything was fine for a while, with code size kept very small, until I introduced this line of code:

    uint8_t index = __builtin_ctz(exti_set_mask);
    auto &gpio_irq_info = irq_callback_table[index];

The issue here is that irq_callback_table is an std::array<> of size 16. exti_set_mask is a uint16_t that is probably being promoted to a uint32_t (32-bit CPU architecture), so the compiler cannot be sure that the result of __builtin_ctz() returns something smaller than 16. With the potential of an out-of-bounds access, the program code size blows up because of all the code added to support throwing of an exception.

Masking the result before using it as an index gets rid of all of that exception code:

uint8_t index = __builtin_ctz(exti_set_mask) & 0xf;
auto &gpio_irq_info = irq_callback_table[index];

Which brings the program code size back down to about 4 kB v.s. 90 kB!

I have these flags set via meson:

# Specify global compiler flags.
add_project_arguments(
    # Free standing environment (no OS, or stdlib)
    '-ffreestanding', '-nostdlib', '-specs=nosys.specs',
    # Enable linker garbage collection
    '-ffunction-sections', '-fdata-sections',
    # No dynamic memory allocation
    '-fno-builtin-malloc', '-fno-builtin-calloc',
    '-fno-builtin-realloc', '-fno-builtin-free',
    # Debug optimization
    '-Og',
    language: ['cpp', 'c']
)
add_project_arguments(
    '-std=c++20',
    '-fno-exceptions',
    '-fno-rtti',
    language: ['cpp']
)

But the -fno-exceptions flag does not seem to be enough to prevent emitting of exception code. Ideally, I would want any potential emission of exceptions (or malloc, etc) to show up as a warning or error instead. Any ideas on how to accomplish that?


EDIT: For those that want to compare the .elf file objdump output, here's the fully linked .elf with the masked index: https://pastebin.com/hSJgF3PW

And the .elf with the non-masked index:

The function where the two deviate:

Non-masked index:

08000d1c <(anonymous namespace)::CommonISRHandler(unsigned short)>:
void CommonISRHandler([[maybe_unused]] uint16_t exti_set_mask) {
 8000d1c:   b570        push    {r4, r5, r6, lr}
 8000d1e:   0005        movs    r5, r0
  while (exti_set_mask) {
 8000d20:   e011        b.n 8000d46 <(anonymous namespace)::CommonISRHandler(unsigned short)+0x2a>
      // Element access.
      [[__nodiscard__]]
      _GLIBCXX17_CONSTEXPR reference
      operator[](size_type __n) noexcept
      {
    __glibcxx_requires_subscript(__n);
 8000d22:   4b18        ldr r3, [pc, #96]   @ (8000d84 <(anonymous namespace)::CommonISRHandler(unsigned short)+0x68>)
 8000d24:   4a18        ldr r2, [pc, #96]   @ (8000d88 <(anonymous namespace)::CommonISRHandler(unsigned short)+0x6c>)
 8000d26:   4819        ldr r0, [pc, #100]  @ (8000d8c <(anonymous namespace)::CommonISRHandler(unsigned short)+0x70>)
 8000d28:   21ca        movs    r1, #202    @ 0xca
 8000d2a:   f000 f97d   bl  8001028 <std::__glibcxx_assert_fail(char const*, int, char const*, char const*)>
    EXTI->RPR1 &= (1 << index);
 8000d2e:   4a18        ldr r2, [pc, #96]   @ (8000d90 <(anonymous namespace)::CommonISRHandler(unsigned short)+0x74>)
 8000d30:   68d1        ldr r1, [r2, #12]
 8000d32:   2301        movs    r3, #1
 8000d34:   40a3        lsls    r3, r4
 8000d36:   4019        ands    r1, r3
 8000d38:   60d1        str r1, [r2, #12]
    EXTI->FPR1 &= (1 << index);
 8000d3a:   6911        ldr r1, [r2, #16]
 8000d3c:   4019        ands    r1, r3
 8000d3e:   6111        str r1, [r2, #16]
    exti_set_mask &= ~(1 << index);
 8000d40:   43db        mvns    r3, r3
 8000d42:   b21b        sxth    r3, r3
 8000d44:   401d        ands    r5, r3
  while (exti_set_mask) {
 8000d46:   2d00        cmp r5, #0
 8000d48:   d01a        beq.n   8000d80 <(anonymous namespace)::CommonISRHandler(unsigned short)+0x64>
    uint8_t index = __builtin_ctz(exti_set_mask);
 8000d4a:   0028        movs    r0, r5
 8000d4c:   f7ff fa40   bl  80001d0 <__ctzsi2>
    auto &gpio_irq_info = irq_callback_table[index];
 8000d50:   24ff        movs    r4, #255    @ 0xff
 8000d52:   4004        ands    r4, r0
 8000d54:   2c0f        cmp r4, #15
 8000d56:   d8e4        bhi.n   8000d22 <(anonymous namespace)::CommonISRHandler(unsigned short)+0x6>
    return stub_ptr != nullptr;
 8000d58:   4b0e        ldr r3, [pc, #56]   @ (8000d94 <(anonymous namespace)::CommonISRHandler(unsigned short)+0x78>)
 8000d5a:   0062        lsls    r2, r4, #1
 8000d5c:   1912        adds    r2, r2, r4
 8000d5e:   0092        lsls    r2, r2, #2
 8000d60:   189b        adds    r3, r3, r2
 8000d62:   689b        ldr r3, [r3, #8]
    if (gpio_irq_info.callback.IsValid()) {
 8000d64:   2b00        cmp r3, #0
 8000d66:   d0e2        beq.n   8000d2e <(anonymous namespace)::CommonISRHandler(unsigned short)+0x12>
      gpio_irq_info.callback(gpio_irq_info.pin);
 8000d68:   4a0a        ldr r2, [pc, #40]   @ (8000d94 <(anonymous namespace)::CommonISRHandler(unsigned short)+0x78>)
 8000d6a:   0060        lsls    r0, r4, #1
 8000d6c:   1901        adds    r1, r0, r4
 8000d6e:   0089        lsls    r1, r1, #2
 8000d70:   5c89        ldrb    r1, [r1, r2]
 8000d72:   1900        adds    r0, r0, r4
 8000d74:   0080        lsls    r0, r0, #2
 8000d76:   1880        adds    r0, r0, r2
 8000d78:   3004        adds    r0, #4
 8000d7a:   f7ff ffbb   bl  8000cf4 <rtlib::Delegate<void (mcu::stm32g070::Gpio::GpioId)>::operator()(mcu::stm32g070::Gpio::GpioId) const>
 8000d7e:   e7d6        b.n 8000d2e <(anonymous namespace)::CommonISRHandler(unsigned short)+0x12>
}
 8000d80:   bd70        pop {r4, r5, r6, pc}
 8000d82:   46c0        nop         @ (mov r8, r8)
 8000d84:   08013ccc    .word   0x08013ccc
 8000d88:   08013ce0    .word   0x08013ce0
 8000d8c:   08013dc4    .word   0x08013dc4
 8000d90:   40021800    .word   0x40021800
 8000d94:   20000734    .word   0x20000734

Masked index:

08000710 <(anonymous namespace)::CommonISRHandler(unsigned short)>:
void CommonISRHandler([[maybe_unused]] uint16_t exti_set_mask) {
 8000710:   b570        push    {r4, r5, r6, lr}
 8000712:   0005        movs    r5, r0
  while (exti_set_mask) {
 8000714:   e00b        b.n 800072e <(anonymous namespace)::CommonISRHandler(unsigned short)+0x1e>
    EXTI->RPR1 &= (1 << index);
 8000716:   4a14        ldr r2, [pc, #80]   @ (8000768 <(anonymous namespace)::CommonISRHandler(unsigned short)+0x58>)
 8000718:   68d1        ldr r1, [r2, #12]
 800071a:   2301        movs    r3, #1
 800071c:   40a3        lsls    r3, r4
 800071e:   4019        ands    r1, r3
 8000720:   60d1        str r1, [r2, #12]
    EXTI->FPR1 &= (1 << index);
 8000722:   6911        ldr r1, [r2, #16]
 8000724:   4019        ands    r1, r3
 8000726:   6111        str r1, [r2, #16]
    exti_set_mask &= ~(1 << index);
 8000728:   43db        mvns    r3, r3
 800072a:   b21b        sxth    r3, r3
 800072c:   401d        ands    r5, r3
  while (exti_set_mask) {
 800072e:   2d00        cmp r5, #0
 8000730:   d018        beq.n   8000764 <(anonymous namespace)::CommonISRHandler(unsigned short)+0x54>
    uint8_t index = __builtin_ctz(exti_set_mask) & 0xf;
 8000732:   0028        movs    r0, r5
 8000734:   f7ff fcc0   bl  80000b8 <__ctzsi2>
 8000738:   240f        movs    r4, #15
 800073a:   4004        ands    r4, r0
    return stub_ptr != nullptr;
 800073c:   4b0b        ldr r3, [pc, #44]   @ (800076c <(anonymous namespace)::CommonISRHandler(unsigned short)+0x5c>)
 800073e:   0062        lsls    r2, r4, #1
 8000740:   1912        adds    r2, r2, r4
 8000742:   0092        lsls    r2, r2, #2
 8000744:   189b        adds    r3, r3, r2
 8000746:   689b        ldr r3, [r3, #8]
    if (gpio_irq_info.callback.IsValid()) {
 8000748:   2b00        cmp r3, #0
 800074a:   d0e4        beq.n   8000716 <(anonymous namespace)::CommonISRHandler(unsigned short)+0x6>
      gpio_irq_info.callback(gpio_irq_info.pin);
 800074c:   4a07        ldr r2, [pc, #28]   @ (800076c <(anonymous namespace)::CommonISRHandler(unsigned short)+0x5c>)
 800074e:   0060        lsls    r0, r4, #1
 8000750:   1901        adds    r1, r0, r4
 8000752:   0089        lsls    r1, r1, #2
 8000754:   5c89        ldrb    r1, [r1, r2]
 8000756:   1900        adds    r0, r0, r4
 8000758:   0080        lsls    r0, r0, #2
 800075a:   1880        adds    r0, r0, r2
 800075c:   3004        adds    r0, #4
 800075e:   f7ff ffc3   bl  80006e8 <rtlib::Delegate<void (mcu::stm32g070::Gpio::GpioId)>::operator()(mcu::stm32g070::Gpio::GpioId) const>
 8000762:   e7d8        b.n 8000716 <(anonymous namespace)::CommonISRHandler(unsigned short)+0x6>
}
 8000764:   bd70        pop {r4, r5, r6, pc}
 8000766:   46c0        nop         @ (mov r8, r8)
 8000768:   40021800    .word   0x40021800
 800076c:   2000005c    .word   0x2000005c

EDIT #2, Some good comments so far, thank you. My reason for believing that exceptions are being pulled in is because of the presence of many functions in the non-masked objdump that are not present in the masked objdump output (grep -i exception, grep -i throw):

08000438 <_Unwind_RaiseException>:
 800045e:       f009 f831       bl      80094c4 <__gnu_Unwind_RaiseException>
 8001764:       f007 fb0e       bl      8008d84 <__cxa_current_exception_type>
080089d8 <__cxxabiv1::__is_gxx_exception_class(char*)>:
 80089e0:       d000            beq.n   80089e4 <__cxxabiv1::__is_gxx_exception_class(char*)+0xc>
 80089e8:       d1fb            bne.n   80089e2 <__cxxabiv1::__is_gxx_exception_class(char*)+0xa>
 80089ee:       d1f8            bne.n   80089e2 <__cxxabiv1::__is_gxx_exception_class(char*)+0xa>
 80089f4:       d1f5            bne.n   80089e2 <__cxxabiv1::__is_gxx_exception_class(char*)+0xa>
 80089fa:       d1f2            bne.n   80089e2 <__cxxabiv1::__is_gxx_exception_class(char*)+0xa>
 8008a00:       d1ef            bne.n   80089e2 <__cxxabiv1::__is_gxx_exception_class(char*)+0xa>
 8008a06:       d1ec            bne.n   80089e2 <__cxxabiv1::__is_gxx_exception_class(char*)+0xa>
 8008a12:       e7e6            b.n     80089e2 <__cxxabiv1::__is_gxx_exception_class(char*)+0xa>
 8008a20:       f7ff ffda       bl      80089d8 <__cxxabiv1::__is_gxx_exception_class(char*)>
 8008a44:       f7ff ffc8       bl      80089d8 <__cxxabiv1::__is_gxx_exception_class(char*)>
 8008b02:       f000 fad3       bl      80090ac <__cxa_allocate_exception>
 8008bc0:       f000 fce6       bl      8009590 <_Unwind_DeleteException>
 8008c2c:       f000 fcb0       bl      8009590 <_Unwind_DeleteException>
08008c34 <std::bad_exception::~bad_exception()>:
08008c38 <transaction clone for std::bad_exception::what() const>:
 8008c38:       4800            ldr     r0, [pc, #0]    @ (8008c3c <transaction clone for std::bad_exception::what() const+0x4>)
08008c40 <std::bad_exception::~bad_exception()>:
08008c60 <__gxx_exception_cleanup(_Unwind_Reason_Code, _Unwind_Control_Block*)>:
 8008c64:       d812            bhi.n   8008c8c <__gxx_exception_cleanup(_Unwind_Reason_Code, _Unwind_Control_Block*)+0x2c>
 8008c72:       d000            beq.n   8008c76 <__gxx_exception_cleanup(_Unwind_Reason_Code, _Unwind_Control_Block*)+0x16>
 8008c7e:       d001            beq.n   8008c84 <__gxx_exception_cleanup(_Unwind_Reason_Code, _Unwind_Control_Block*)+0x24>
 8008c86:       f000 fa27       bl      80090d8 <__cxa_free_exception>
 8008c8a:       e7f3            b.n     8008c74 <__gxx_exception_cleanup(_Unwind_Reason_Code, _Unwind_Control_Block*)+0x14>
08008c94 <__cxa_init_primary_exception>:
 8008cd0:       4b02            ldr     r3, [pc, #8]    @ (8008cdc <__cxa_init_primary_exception+0x48>)
 8008cf8:       f7ff ffcc       bl      8008c94 <__cxa_init_primary_exception>
 8008d06:       f7f7 fb97       bl      8000438 <_Unwind_RaiseException>
08008d84 <__cxa_current_exception_type>:
 8008d8e:       d004            beq.n   8008d9a <__cxa_current_exception_type+0x16>
 8008d96:       d001            beq.n   8008d9c <__cxa_current_exception_type+0x18>
 8008da0:       e7fa            b.n     8008d98 <__cxa_current_exception_type+0x14>
080090ac <__cxa_allocate_exception>:
 80090b8:       d007            beq.n   80090ca <__cxa_allocate_exception+0x1e>
 80090d2:       d1f2            bne.n   80090ba <__cxa_allocate_exception+0xe>
080090d8 <__cxa_free_exception>:
 80090d8:       4b07            ldr     r3, [pc, #28]   @ (80090f8 <__cxa_free_exception+0x20>)
 80090e6:       d201            bcs.n   80090ec <__cxa_free_exception+0x14>
 80090ea:       d302            bcc.n   80090f2 <__cxa_free_exception+0x1a>
 80090f6:       e7fb            b.n     80090f0 <__cxa_free_exception+0x18>
080094c4 <__gnu_Unwind_RaiseException>:
 80094e0:       e006            b.n     80094f0 <__gnu_Unwind_RaiseException+0x2c>
 80094ee:       d108            bne.n   8009502 <__gnu_Unwind_RaiseException+0x3e>
 80094fa:       d0f2            beq.n   80094e2 <__gnu_Unwind_RaiseException+0x1e>
 800950a:       d1f7            bne.n   80094fc <__gnu_Unwind_RaiseException+0x38>
 8009584:       f7ff ff9e       bl      80094c4 <__gnu_Unwind_RaiseException>
08009590 <_Unwind_DeleteException>:
 8009598:       d001            beq.n   800959e <_Unwind_DeleteException+0xe>

08000498 <_Unwind_Resume_or_Rethrow>:
 80004be:       f009 f857       bl      8009570 <__gnu_Unwind_Resume_or_Rethrow>
 80017c6:       f007 faa5       bl      8008d14 <__cxa_rethrow>
 8008ad0:       f000 f920       bl      8008d14 <__cxa_rethrow>
 8008b0e:       f000 f8e7       bl      8008ce0 <__cxa_throw>
08008ce0 <__cxa_throw>:
08008d14 <__cxa_rethrow>:
 8008d24:       d00c            beq.n   8008d40 <__cxa_rethrow+0x2c>
 8008d2c:       d00a            beq.n   8008d44 <__cxa_rethrow+0x30>
 8008d36:       f7f7 fbaf       bl      8000498 <_Unwind_Resume_or_Rethrow>
 8008d4a:       d1f0            bne.n   8008d2e <__cxa_rethrow+0x1a>
 8008d52:       d1ec            bne.n   8008d2e <__cxa_rethrow+0x1a>
 8008d5a:       d1e8            bne.n   8008d2e <__cxa_rethrow+0x1a>
 8008d62:       d1e4            bne.n   8008d2e <__cxa_rethrow+0x1a>
 8008d6a:       d1e0            bne.n   8008d2e <__cxa_rethrow+0x1a>
 8008d72:       d1dc            bne.n   8008d2e <__cxa_rethrow+0x1a>
 8008d7a:       d8d8            bhi.n   8008d2e <__cxa_rethrow+0x1a>
 8008d82:       e7d6            b.n     8008d32 <__cxa_rethrow+0x1e>
08009570 <__gnu_Unwind_Resume_or_Rethrow>:
 8009576:       d005            beq.n   8009584 <__gnu_Unwind_Resume_or_Rethrow+0x14>
 8009588:       e7fb            b.n     8009582 <__gnu_Unwind_Resume_or_Rethrow+0x12>

Solution

  • In recent years, safety has become a nuance in SW development world. As a result, compiler and library designers have shifted toward hardening programs against usual sources of UB. As part of their attemps recent compilers have hardening flags enabled by default in debug builds. Hardening flags eventually affect the behavior of indexing operators for std::array, std::vector and contagious ranges in general. As a result, index operator(operator[]) for the afformentioned classes practically become equivalent to at method.

    In order to disable the adverse effects of hardening on performance, the easiest solution is to compile in release mode(force define _NDRBUG via compiler flags). But this totally disables runtime debuggers and traps. The better choice is to keep the debug mode, but dissable the harmful hardening options.

    Disabling the hardening options eventually removes the bounds-checking logic from operator[] invocations and optimizes the exra dependency out, resulting in binary size shrinking. For further reading explor the GNU gcc docs, or search the keywords mentioned in this response.