GCC ARM:vtable 未初始化
GCC ARM : vtable not initialized
我正在使用 arm-none-eabi-g++ 为 ARM Cortex M 微控制器进行编译。我的代码静态实例化了一些模块,初始化它们,然后在循环中顺序执行它们。每个模块(ModuleFoo
、ModuleBar
...)都是一个 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, =main
和 blx rX
或直接调用 bl main
)以及在此之前完全相同,但使用 __libc_init_array
而不是 main
.
我正在使用 arm-none-eabi-g++ 为 ARM Cortex M 微控制器进行编译。我的代码静态实例化了一些模块,初始化它们,然后在循环中顺序执行它们。每个模块(ModuleFoo
、ModuleBar
...)都是一个 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, =main
和 blx rX
或直接调用 bl main
)以及在此之前完全相同,但使用 __libc_init_array
而不是 main
.