Estou usando C++ para um projeto de firmware de baixo nível para um microcontrolador. Tudo estava bem por um tempo, com o tamanho do código mantido muito pequeno, até que introduzi esta linha de código:
uint8_t index = __builtin_ctz(exti_set_mask);
auto &gpio_irq_info = irq_callback_table[index];
O problema aqui é que irq_callback_table
é um std::array<>
de tamanho 16. exti_set_mask
É um uint16_t
que provavelmente está sendo promovido para uma uint32_t
(arquitetura de CPU de 32 bits), então o compilador não pode ter certeza de que o resultado de __builtin_ctz()
retorna algo menor que 16. Com o potencial de um acesso fora dos limites, o tamanho do código do programa explode por causa de todo o código adicionado para dar suporte ao lançamento de uma exceção.
Mascarar o resultado antes de usá-lo como índice elimina todo esse código de exceção:
uint8_t index = __builtin_ctz(exti_set_mask) & 0xf;
auto &gpio_irq_info = irq_callback_table[index];
O que reduz o tamanho do código do programa para cerca de 4 kB contra 90 kB!
Tenho essas bandeiras definidas 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']
)
Mas o -fno-exceptions
sinalizador não parece ser suficiente para evitar a emissão de código de exceção. Idealmente, eu gostaria que qualquer emissão potencial de exceções (ou malloc, etc.) aparecesse como um aviso ou erro. Alguma ideia de como fazer isso?
EDIT: Para aqueles que desejam comparar a saída do arquivo .elf objdump, aqui está o .elf totalmente vinculado com o índice mascarado: https://pastebin.com/hSJgF3PW
E o .elf com o índice não mascarado:
- https://pastebin.com/p9yDxdjx pt1
- https://pastebin.com/NU5nV0tr pt2
- https://pastebin.com/9CX1EtH0 pt3
- https://pastebin.com/evvnmCCa pt4
A função onde os dois se desviam:
Índice não mascarado:
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
Índice mascarado:
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 , Alguns bons comentários até agora, obrigado. Minha razão para acreditar que exceções estão sendo puxadas é por causa da presença de muitas funções no objdump não mascarado que não estão presentes na saída do objdump mascarado ( 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>
Nos últimos anos, a segurança se tornou uma nuance no mundo do desenvolvimento de SW. Como resultado, os designers de compiladores e bibliotecas mudaram para programas de proteção contra fontes usuais de UB. Como parte de suas tentativas, os compiladores recentes têm sinalizadores de proteção habilitados por padrão em compilações de depuração. Os sinalizadores de proteção eventualmente afetam o comportamento dos operadores de indexação para
std::array
estd::vector
intervalos contagiosos em geral. Como resultado, o operador de índice (operator[]
) para as classes mencionadas praticamente se torna equivalente aoat
método.Para desabilitar os efeitos adversos do hardening no desempenho, a solução mais fácil é compilar no modo release (forçar definição
_NDRBUG
via flags do compilador). Mas isso desabilita totalmente os depuradores e traps de tempo de execução. A melhor escolha é manter o modo debug, mas desabilitar as opções prejudiciais de hardening._GLIBCXX_ASSERTIONS
é uma macro que permite asserções seletivas em bibliotecas GNU e frameworks relacionados. Como parte do endurecimento do código, os desenvolvedores podem optar por defini-la por meio doD
switch(as-D_GLIBCXX_ASSERTIONS
). Mas, como mencionado antes, esse é o padrão em compilações de depuração. Assim, caso seja necessário, a definição dessa macro pode ser suprimida por meio doU
switch(-U_GLIBCXX_ASSERTIONS
) do compilador. Rastreando as definições no IDE, podemos ver que_GLIBCXX_ASSERTIONS
a macro controla condicionalmente as verificações de limites na implementação gnu destd::array::operator[]
.hardened
opção do compilador é uma fortificação mais recente em versões mais recentes da compilação do modo de depuração das plataformas GNU e CLANG. O padrão no modo de depuração é of
switch(-fhardened
), e define_GLIBCXX_ASSERTIONS
junto com mais algumas opções de proteção em compilações de depuração. Desabilitá-lo comfno-
switch(-fno-hardened
), eventualmente suprime a macro também.Desabilitar as opções de hardening eventualmente remove a lógica de verificação de limites das
operator[]
invocações e otimiza a dependência exra out, resultando em redução do tamanho binário. Para leitura adicional, explore os documentos do GNU gcc ou pesquise as palavras-chave mencionadas nesta resposta.Existe algum motivo para que
irq_callback_table
seja necessário umstd::array<>
? Se fosse um array de estilo C antigo, não haveria nenhuma verificação de limites, então não haveria possibilidade de uma exceção.