Linker GCC ARM:vtable未初始化

Linker GCC ARM:vtable未初始化,linker,arm,vtable,Linker,Arm,Vtable,我正在使用arm-none-eabi-g++为arm Cortex m微控制器进行编译。我的代码静态地实例化一些模块,初始化它们,并在循环中顺序执行它们。每个模块(modulefo,ModuleBar…)都是一个类,它来自一个公共模块类。Module类定义了两个虚拟函数,init()和exec(),它们在每个派生模块中实现。基类和派生类中都没有显式构造函数。最后,我有一个传递的上下文结构,它包含指向模块的指针列表(Module*modules[]) 我使用了以下代码: int main() {

我正在使用arm-none-eabi-g++为arm Cortex m微控制器进行编译。我的代码静态地实例化一些模块,初始化它们,并在循环中顺序执行它们。每个模块(
modulefo
ModuleBar
…)都是一个类,它来自一个公共
模块
类。
Module
类定义了两个虚拟函数,
init()
exec()
,它们在每个派生模块中实现。基类和派生类中都没有显式构造函数。最后,我有一个传递的上下文结构,它包含指向模块的指针列表(
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);
        }
    }
}
main.cpp

ModuleFoo foo;
ModuleBar bar;

void initContext(Context& context) {
    context.nModules = 2;
    context.modules[0] = &foo;
    context.modules[1] = &bar;
}
#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);
        }
    }
}
我使用Git回滚以检查,使用前面的代码结构,vtable指针已正确初始化。根据链接器的映射文件和GDB,vtable存在(与以前的地址大致相同):

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

.bss           0x00000000200015fc      0x22c config.o
               0x00000000200015fc                foo
               0x000000002000164c                bar
这可能是问题所在吗

无论如何,感谢您抽出时间阅读本文

**编辑:**
问题来自启动代码和链接器脚本。我使用了Atmel的ARM GCC工具链提供的示例文件,这些文件似乎编写得很糟糕,最重要的是,没有调用
\uuu libc\u init\u array()
(用于调用全局构造函数)。我切换到使用ASF中的启动/链接器脚本,它工作得更好。谢谢@FreddieChopin

向我们展示您正在使用的启动代码。很可能您没有启用全局构造函数,这可以通过调用
\uu libc\u init\u array()
函数来实现。您可以通过在
main()
的开头手动调用此函数来测试此理论-然后应该可以正常工作。如果有,那么您应该将该函数添加到启动代码中(
Reset\u Handler

快速测试:

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

要正确地执行此操作,请找到启动代码调用main()的位置(通常类似于
ldr rX,=main
blx rX
,或者可能直接作为
bl main
),并且在这之前执行完全相同的操作,但使用
\u libc\u init\u array
而不是
main

@Foaly-hmm,奇怪。。。一般来说,为了让这个函数正常工作,您的链接器脚本必须提供几个符号,但我猜它们必须存在,否则链接将失败。但是值得检查的是:
\uuuu preinit\u array\u start
\uuu preinit\u array\u end
\uuu init\u array\u start
\uu init\u array\u end
。也许你可以把你的项目上传到某个地方?@Foaly-你有没有可能为链接添加了
-nostartfiles
标志?这将阻止整个过程的运行。@Foaly我强烈建议您不要使用这个愚蠢的脚本,它将init数组放在RAM中
\uuuu libc\u init\u array()
只执行存储在
\uuuu init\u array\u start
之间的函数。end
-@Foaly这是否可行取决于您为此启动文件传递的确切编译标志。@Foaly您的链接器脚本可能无法与此启动一起工作,并且此启动对任何特定的芯片都没有用处,因为它没有其他芯片向量。问题是,这对数组没有“匹配”,并且可能没有初始化.data节,而愚蠢的脚本将这些init数组放在那里,因此您最终跳转到随机地址并执行随机指令。你们有什么微控制器?您可以在我的C++ RTOS项目中找到一个生成链接器脚本的shell脚本——也有一个匹配的启动程序和一些不同芯片的向量表
.bss           0x00000000200015fc      0x22c config.o
               0x00000000200015fc                foo
               0x000000002000164c                bar
int main() {
    extern "C" void __libc_init_array();
    __libc_init_array();
    // rest of your code...