c++gccembeddedstm32linker-scripts

Member variable allocated at start of memory


I'm trying to use c++ on an STM32 device compiling with gcc. The device loads the code and start executing it but hard faults on any member variable write.

I can see with GDB that member variables are stored at beginnning of memory (0x7 to be specific), of course the STM32 hard faults at the first write of that location.

I can see that BSS section is not generated unless i declare a variable in main (used readelf on the final elf file).

Shouldnt be member variables be placed in bss?

I'm compiling and linking with -nostdlib -mcpu=cortex-m0plus -fno-exceptions -O0 -g.

The linker script is:


ENTRY(start_of_memory);

MEMORY {

    rom (rx)  : ORIGIN = 0x08000000, LENGTH = 16K
    ram (xrw) : ORIGIN = 0x20000000, LENGTH = 2K

}

SECTIONS {

    .text : {
        *(.text)
    } > rom

    .data : { 
        *(.data)
        *(.data.*)
    } > ram

    .bss : { 
        *(.bss)
        *(.bss.*)
        *(COMMON)
    } > ram

}

The output of readelf (no variables declaration, only object usage):

ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           ARM
  Version:                           0x1
  Entry point address:               0x8000000
  Start of program headers:          52 (bytes into file)
  Start of section headers:          76536 (bytes into file)
  Flags:                             0x5000200, Version5 EABI, soft-float ABI
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         1
  Size of section headers:           40 (bytes)
  Number of section headers:         14
  Section header string table index: 13

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        08000000 010000 0005a8 00  AX  0   0  4
  [ 2] .rodata           PROGBITS        080005a8 0105a8 00005c 00   A  0   0  4
  [ 3] .ARM.attributes   ARM_ATTRIBUTES  00000000 010604 00002d 00      0   0  1
  [ 4] .comment          PROGBITS        00000000 010631 000049 01  MS  0   0  1
  [ 5] .debug_info       PROGBITS        00000000 01067a 000a93 00      0   0  1
  [ 6] .debug_abbrev     PROGBITS        00000000 01110d 0003b8 00      0   0  1
  [ 7] .debug_aranges    PROGBITS        00000000 0114c5 000060 00      0   0  1
  [ 8] .debug_line       PROGBITS        00000000 011525 000580 00      0   0  1
  [ 9] .debug_str        PROGBITS        00000000 011aa5 000416 01  MS  0   0  1
  [10] .debug_frame      PROGBITS        00000000 011ebc 000228 00      0   0  4
  [11] .symtab           SYMTAB          00000000 0120e4 000640 10     12  86  4
  [12] .strtab           STRTAB          00000000 012724 000344 00      0   0  1
  [13] .shstrtab         STRTAB          00000000 012a68 00008f 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  y (purecode), p (processor specific)

There are no section groups in this file.

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x010000 0x08000000 0x08000000 0x00604 0x00604 R E 0x10000

 Section to Segment mapping:
  Segment Sections...
   00     .text .rodata 

There is no dynamic section in this file.

There are no relocations in this file.

There are no unwind sections in this file.

Symbol table '.symtab' contains 100 entries:

Main (init platform probably does not use any variables):

int main(void) {

    init_platform(SPEED_4_MHz);

    gpio testpin(GPIO_A, 5);
    testpin.dir(MODE_OUTPUT);

    while (1) {

        testpin.high();
        wait();
        testpin.low();
        wait();
    }

    return 0;
    
}

Update #1:

The vector table is at beginning of memory, sp and msp are initialized successfully.

(gdb) p/x *0x00000000
$2 = 0x20000700
(gdb) p/x *0x00000004
$3 = 0x80000f1
(gdb) info registers
sp             0x20000700          0x20000700
lr             0xffffffff          -1
pc             0x80000f6           0x80000f6 <main()+6>
xPSR           0xf1000000          -251658240
msp            0x20000700          0x20000700
psp            0xfffffffc          0xfffffffc

Putting a breakpoint on a constructor for the GPIO class, i can see variables are at 0x00000XXX

Breakpoint 2, gpio::gpio (this=0x7, port=0 '\000', pin=5 '\005') at gpio.cpp:25
25              mypin = pin;
(gdb) p/x &mypin
$6 = 0xb

I tried to make mypin a public member variable (was private), did not make any change.

Starting to think that dynamic allocation is needed with C++.


Solution

  • The issue was in the launch script:

    Not working:

    toolchain\bin\arm-none-eabi-gdb.exe ^
        -ex "target remote 127.0.0.1:3333"  ^
        -ex "load"  ^
        -ex "b main" ^
        -ex "b unmanaged_isr_call"  ^
        -ex "b hard_fault_isr"  ^
        -ex "j main" binaries\main.elf
    

    Working:

    toolchain\bin\arm-none-eabi-gdb.exe ^
        -ex "target remote 127.0.0.1:3333"  ^
        -ex "load"  ^
        -ex "b unmanaged_isr_call"  ^
        -ex "b hard_fault_isr"  ^
        -ex "set $pc = &main" binaries\main.elf
    

    Made it work.

    The issue was in j main.

    The jump instruction does not modify the stack frame where all the object are placed by the compiler.

    Using set $pc, execution starts at the given address, using jump execution starts at the first C line after the address, a big difference!.

    From the gdb jump documentation:

    The jump command does not change the current stack frame, or the stack pointer, or the contents of any memory location or any register other than the program counter. If locspec resolves to an address in a different function from the one currently executing, the results may be bizarre if the two functions expect different patterns of arguments or of local variables. For this reason, the jump command requests confirmation if the jump address is not in the function currently executing. However, even bizarre results are predictable if you are well acquainted with the machine-language code of your program.

    The first lines make space in the stack for the objects "created" by main, space needed for the object to be used during execution. (verified by launching both commands and seeing differen msp values at the first C line).

    With jump, those lines are not executed and the space is not allocated on stack: when code calls a funxction, the parameters will overwrite member data.