Как предотвратить включение деструкторов библиотеки C и atexit()?

Используя arm-none-eabi-gcc для Cortex-M4 (голое приложение), код для malloc также испускается, хотя я никогда не использую malloc в своем коде.

Глядя на вывод сборки с arm-none-eabi-objdump -xS obj.elf, кажется, что malloc вызывается __register_exitproc вызывается atexit вызывается register_fini

004036a8 <register_fini>:
  4036a8:       4b02            ldr     r3, [pc, #8]    ; (4036b4 <register_fini+0xc>)
  4036aa:       b113            cbz     r3, 4036b2 <register_fini+0xa>
  4036ac:       4802            ldr     r0, [pc, #8]    ; (4036b8 <register_fini+0x10>)
  4036ae:       f000 b805       b.w     4036bc <atexit>
  4036b2:       4770            bx      lr
  4036b4:       00000000        .word   0x00000000
  4036b8:       004036c9        .word   0x004036c9

Однако register_fini никогда не вызывается в коде. main() вызывается с использованием следующего кода запуска, поэтому даже если main завершает работу, деструкторы (или функции, зарегистрированные с помощью atexit()) не будут вызываться.

/**
 * \brief This is the code that gets called on processor reset.
 * To initialize the device, and call the main() routine.
 */
void Reset_Handler(void)
{
    uint32_t *pSrc, *pDest;

    /* Initialize the relocate segment */
    pSrc = &_etext;
    pDest = &_srelocate;

    if (pSrc > pDest) {
        for (; pDest < &_erelocate;) {
            *pDest++ = *pSrc++;
        }
    } else if (pSrc < pDest) {
        uint32_t nb_bytes = (uint32_t)&_erelocate - (uint32_t)&_srelocate;
        pSrc = (uint32_t*)((uint32_t)pSrc + nb_bytes) - 1;
        pDest = (uint32_t*)((uint32_t)pDest + nb_bytes) - 1;
        for (;nb_bytes;nb_bytes -= 4) {
            *pDest-- = *pSrc--;
        }
    }
    __NOP();

    /* Clear the zero segment */
    for (pDest = &_szero; pDest < &_ezero;) {
        *pDest++ = 0;
    }

    /* Set the vector table base address */
    pSrc = (uint32_t *) & _sfixed;
    SCB->VTOR = ((uint32_t) pSrc);

    /* Initialize the C library */
    __libc_init_array();

    /* Branch to main function */
    main();

    /* Infinite loop */
    while (1);
}

Код компилируется с помощью -ffunction-sections и -fdata-sections и связывается с флагом --gc-sections, чтобы недостижимый код/функции не включались в выходной файл.


Итак, как я могу предотвратить включение этих функций (register_fini, atexit, malloc и т. д.), которые никогда не используются в моем коде, в объектный файл?


Параметры компиляции

arm-none-eabi-gcc -o build/main.o -c -mcpu=cortex-m4 -mthumb -pipe -g3 -Wall -Wextra -Wno-expansion-to-defined -Werror -std=gnu11 -fno-strict-aliasing -ffunction-sections -fdata-sections -DARM_MATH_CM4=true -D__SAM4SD32C__ -Ibunch -Iof -Iinclude -Idirs src/main.c

Параметры ссылки

arm-none-eabi-g++ -o build/tnc.elf -mcpu=cortex-m4 -mthumb -pipe -Wl,--entry=Reset_Handler -Wl,--gc-sections -Wl,--script my/linker/script.ld build/src/bunch.o build/src/of.o build/src/object.o build/src/files.o build/src/main.o -lm

person user80551    schedule 09.02.2018    source источник
comment
Используемые вами функции неявно вызывают atexit()? Использование таких функций, как fopen() или printf(), может привести к установке обработчиков выхода для потоков, чтобы они сбрасывались при выходе.   -  person Andrew Henle    schedule 09.02.2018
comment
Вы компилируете свою собственную библиотеку newlib C? Если это так, вы можете использовать параметр --disable-newlib-atexit-dynamic-alloc ./configure при его сборке, чтобы предотвратить atexit использование malloc.   -  person Ian Abbott    schedule 09.02.2018
comment
Перечислите все параметры, которые вы используете с gcc, чтобы у нас было лучшее представление о том, что вы на самом деле делаете. (-ffreestanding? -nodefaultlibs? -nostdlib?)   -  person Nominal Animal    schedule 10.02.2018
comment
@NominalAnimal Добавлено к вопросу. Мне нужны некоторые функции из stdib (memcpy, memset, математические функции и т. д.)   -  person user80551    schedule 10.02.2018
comment
@IanAbbott Нет, я сам не компилировал libc. Кажется, что /usr/arm-none-eabi/lib/thumb/v7e-m/libc.a используется. (устанавливается при использовании gcc-arm-embedded ppa). (не уверен, что это newlib)   -  person user80551    schedule 10.02.2018
comment
@AndrewHenle Не так, как это может быть обнаружено при статическом анализе. Ни register_fini, ни atexit не вызываются из любого места непосредственно в дизассемблированном окончательном связанном файле elf. (Я не знаю, можно ли их вызывать с помощью указателей на функции во время выполнения).   -  person user80551    schedule 10.02.2018
comment
GCC предоставляет для них встроенные функции. Есть ли что-то, что вам нужно, но не встроено в GCC?   -  person Nominal Animal    schedule 10.02.2018
comment
Почему вы связываетесь с G++, вызывая механизм C++, например register_fini? У вас есть С++? У вас есть статические объекты?   -  person artless noise    schedule 10.02.2018
comment
@artlessnoise answers.launchpad.net/gcc-arm-embedded/+question/ 253559 ; Короче говоря, компилятор вызовет компоновщик с необходимыми параметрами, так как это набор инструментов с несколькими библиотеками. Результаты одинаковы, даже если для связывания используется arm-none-eabi-gcc вместо arm-none-eabi-g++. Я не использую C++, хотя использую статические объекты. .bss инициализируется нулем во фрагменте Reset_Handler() вопроса.   -  person user80551    schedule 10.02.2018


Ответы (4)


Возможно, вам нужен аргумент -fno-use-cxa-atexit для компилятора.

Посмотрите на этот простой пример, чтобы получить работающий код C++ на чистом «голом железе»: https://github.com/cortexm/baremetal

person vlk    schedule 22.02.2018

В среде с ограниченной памятью, такой как Cortex M4, другим вариантом является использование newlib-nano. Он предоставляет __register_exitproc() со слабой связью. Поэтому легко переопределить вашу собственную пустую функцию, которая избегает вызова malloc(). Это также даст дополнительное преимущество удаления __call_exitprocs() из вашего двоичного файла.

  • Добавьте флаг --specs=nano.specs в параметры компилятора и компоновщика.
  • Создайте следующую функцию где-нибудь в скомпилированном коде: void __register_exitproc(void) { }

Обратите внимание, что это предотвратит вызов деструкторов для статических экземпляров классов. Это кажется уместным в вашем примере.

См. комментарии в newlib source для более подробной информации.

person richarddonkin    schedule 31.05.2018
comment
Это действительно сработало, даже без __register_exitproc! Хотя мне пришлось использовать один тире для опции -specs. - person Trass3r; 18.12.2018

Итак, вот обходной путь, который я использую. Не идеально, но достаточно хорошо.

Используя параметр --wrap параметра ld, я могу дать собственное определение для atexit это ничего не делает.

int __wrap_atexit(void __attribute__((unused)) (*function)(void)) {
    return -1;
}

а затем связать с опцией --wrap=atexit.

Теперь atexit не вызывает никаких других функций, а параметры -ffunction-sections и --gc-sections гарантируют, что malloc не будет включено в выходной файл.


Кроме того, чтобы принудительно исключить malloc, при связывании без определения __wrap_malloc можно передать параметр --wrap=malloc. Это завершится ошибкой ссылки, если какая-то другая функция использует malloc.

person user80551    schedule 13.02.2018

-nostartfiles и -nostdlib работали у меня в похожем контексте.

person Andrey Portnoy    schedule 09.08.2020