GCC ARM:vtable 未初始化

GCC ARM : vtable not initialized

我正在使用 arm-none-eabi-g++ 为 ARM Cortex M 微控制器进行编译。我的代码静态实例化了一些模块,初始化它们,然后在循环中顺序执行它们。每个模块(ModuleFooModuleBar...)都是一个 class,它继承自一个共同的 Module class。 Module class 定义了两个虚函数,init()exec(),它们在每个派生模块中实现。没有明确的构造函数,无论是在基础还是派生的 classes 中。最后,我有一个上下文结构,它被传递并包含指向模块的指针列表 (Module* modules[])。

我有以下有效的代码:

int main() {
    ModuleFoo foo;
    ModuleBar bar;
    Context context;
    const int N_MODULES = 2;
    context.modules[0] = &foo; // Indexes are actually an enum but I stripped it to make it shorter
    context.modules[1] = &bar;

    for (int i = 0; i < N_MODULES; i++) {
        context.modules[i]->init(context);
    }

    while (1) {
        for (int i = 0; i < N_MODULES; i++) {
            context.modules[i]->exec(context);
        }
    }
}

到目前为止,还不错(至少我是这么认为的,无论如何它都有效)。

现在,我想通过将与 "which modules are used in a particular configuration" 相关的所有代码移动到单独的 config.cpp/config.h 文件来使系统更易于维护:

config.cpp :

ModuleFoo foo;
ModuleBar bar;

void initContext(Context& context) {
    context.nModules = 2;
    context.modules[0] = &foo;
    context.modules[1] = &bar;
}

main.cpp :

#include "config.h"

int main() {
    Context context;
    initContext(context);

    for (int i = 0; i < context.nModules; i++) {
        context.modules[i]->init(context);
    }

    while (1) {
        for (int i = 0; i < context.nModules; i++) {
            context.modules[i]->exec(context);
        }
    }
}

在第一个模块(MCU HardFaults)上调用 init() 时出现问题。这是因为,根据 GDB,vtable 指针未初始化:

(gdb) p foo
 = {
  <Module> = {
    _vptr.Module = 0x0 <__isr_vector>, 
    _enabled = false
  },

我用 Git 回滚检查,使用之前的代码结构 vtable 指针已正确初始化。根据链接器的映射文件和 GDB,vtable 存在(与之前的地址大致相同):

.rodata        0x0000000000008e14       0x2c ModuleFoo.o
               0x0000000000008e14                typeinfo name for ModuleFoo
               0x0000000000008e1c                typeinfo for ModuleFoo
               0x0000000000008e28                vtable for ModuleFoo

根本没有设置指针。我在两个版本之间看到的唯一区别是,在第一个版本中,模块在堆栈上实例化,而在第二个版本中,它们在 bss 中全局实例化:

.bss           0x00000000200015fc      0x22c config.o
               0x00000000200015fc                foo
               0x000000002000164c                bar

这可能是问题所在吗?

无论如何,感谢您花时间阅读到这里!

**编辑:** 问题出在启动代码和链接描述文件中。我使用了 Atmel 的 ARM GCC 工具链提供的示例文件,这些文件似乎写得不好,最重要的是,没有调用 __libc_init_array() (用于调用全局构造函数)。我改用 ASF 的 startup/linker 脚本,效果更好。谢谢@FreddieChopin!

向我们展示您正在使用的启动代码。很可能您没有启用全局构造函数,这可以通过调用 __libc_init_array() 函数来完成。您可以通过在 main() 的开头手动调用此函数来测试这个理论 - 它应该可以正常工作。如果是这样,那么您应该将该函数添加到您的启动代码中 (Reset_Handler)。

快速测试:

int main() {
    extern "C" void __libc_init_array();
    __libc_init_array();
    // rest of your code...

要正确执行此操作,请找到您的启动代码调用 main() 的位置(通常是 ldr rX, =mainblx rX 或直接调用 bl main)以及在此之前完全相同,但使用 __libc_init_array 而不是 main.