如何防止包含C库析构函数和atexit()函数?

13

使用 arm-none-eabi-gcc 编译 Cortex-M4 (裸机应用程序)时,即使我从未在我的代码中使用过 mallocmalloc 的代码也会被编译。

通过 arm-none-eabi-objdump -xS obj.elf 查看汇编输出,似乎是由 register_fini 调用的 atexit 再调用 __register_exitproc 来调用 malloc

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_finiatexitmalloc等)被包含在目标文件中?


编译选项

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

1
你使用的任何函数是否隐式调用了 atexit()?例如使用 fopen()printf() 函数可能会导致为流安装退出处理程序,以便在退出时刷新它们。 - Andrew Henle
列出你在使用 gcc 时所用的所有选项,这样我们就能更好地了解你实际在做什么。(-ffreestanding-nodefaultlibs-nostdlib?) - Nominal Animal
@NominalAnimal 已添加到问题中。我需要一些来自 stdib 的函数(memcpymemset、数学函数等)。 - user80551
@IanAbbott 不,我没有自己编译libc。似乎使用的是/usr/arm-none-eabi/lib/thumb/v7e-m/libc.a。(在使用gcc-arm-embedded ppa时安装)。 (不确定这是否是newlib)。 - user80551
@AndrewHenle 以一种不会在静态分析中显示的方式。 register_finiatexit都没有直接从反汇编的最终链接的elf文件中调用任何地方。(我不知道它们是否可以在运行时使用函数指针调用)。 - user80551
显示剩余4条评论
4个回答

5

3
在像Cortex M4这样内存有限的环境中,另一个选择是使用newlib-nano。它提供了一个弱链接的__register_exitproc()函数。因此很容易用自己的空函数覆盖它,避免调用malloc()。这将额外有利于从二进制文件中删除__call_exitprocs()
  • 将标志--specs=nano.specs添加到您的编译器和链接器选项中。
  • 在已编译代码的某个位置创建以下函数:void __register_exitproc(void) { }

请注意,这将防止静态类实例的析构函数被调用。在您的示例中似乎是适当的。

有关更多详细信息,请参见newlib源代码中的注释。


这实际上起作用了,甚至没有使用 __register_exitproc!尽管我不得不为 -specs 选项使用单破折号。 - Trass3r

3
这是我正在使用的解决方法。不完美,但足够好。
使用ld--wrap选项,我可以提供自己的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,则会出现链接错误。

0

-nostartfiles-nostdlib在类似的情况下对我有用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接