I'm using arm-none-eabi-g++ to compile for an ARM Cortex M microcontroller. My code statically instantiates some modules, initialize them, and execute them sequentially in a loop. Each module (ModuleFoo
, ModuleBar
...) is a class which herits from a common Module
class. The Module
class defines two virtual functions, init()
and exec()
, which are implemented in every derived module. There are no explicit constructors, neither in the base nor the derived classes. Finally, I have a Context struct which is passed around and contains a list of pointers to the modules (Module* modules[]
).
I had the following code which worked :
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);
}
}
}
So far, so good (at least I think so, in any case it worked).
Now, I want to make the system more maintainable by moving all the code related to "which modules are used in a particular configuration" to a separate config.cpp
/config.h
file :
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);
}
}
}
The problem appears when init()
is called on the first module (the MCU HardFaults). This is because, according to GDB, the vtable pointer is not initialized :
(gdb) p foo
$1 = {
<Module> = {
_vptr.Module = 0x0 <__isr_vector>,
_enabled = false
},
I rolled back with Git to check, with the previous code structure the vtable pointer was correctly initialized. And according to the linker's map file and GDB, the vtable exists (at around the same address as before):
.rodata 0x0000000000008e14 0x2c ModuleFoo.o
0x0000000000008e14 typeinfo name for ModuleFoo
0x0000000000008e1c typeinfo for ModuleFoo
0x0000000000008e28 vtable for ModuleFoo
The pointer is simply not set. The only difference I see between the two versions is that in the first one the modules are instanciated on the stack, whereas on the second they are instanciated globally in the bss :
.bss 0x00000000200015fc 0x22c config.o
0x00000000200015fc foo
0x000000002000164c bar
Could this be the problem?
In any case, thanks for taking the time to read this far!
**EDIT : **
The problem was coming from the startup code and the linker script. I used the sample files provided with Atmel's ARM GCC toolchain, which seem to be poorely written and, most importantly, didn't call __libc_init_array()
(which is used to call global constructors). I switched to using the startup/linker script from ASF, and it works way better. Thanks @FreddieChopin !
Show us the startup code you are using. Most likely you did not enable global constructors, which can be done by calling __libc_init_array()
function. You can test this theory, by manually calling this function at the beginning of main()
- it should work fine then. If it does, then you should add that function to your startup code (Reset_Handler
).
Quick test:
int main() {
extern "C" void __libc_init_array();
__libc_init_array();
// rest of your code...
To do it properly, find the place where your startup code calls main() (usually sth like ldr rX, =main
and blx rX
or maybe directly as bl main
) and right before that do exactly the same but with __libc_init_array
instead of main
.