3

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++.

  • The `gpio` structure would be allocated on the stack (if it contains members they may be heap allocated). Are you sure that `GPIO_A` is defined? – Den-Jason Jan 05 '23 at 23:27
  • Members of what - members of testpin? Yes, they will be on the stack. Because this looks like a "bare metal" environment, *make sure something in the startup sequence sets the stack pointer register to point to where the stack is supposed to go*. Possibly the stack pointer is starting at 0 and never getting set to the right address. Usually the compiler inserts this _crt0 file which does this, or the kernel does it, but you are using a customized linker script and stuff, and no kernel, so it might not happen automatically. – user253751 Jan 05 '23 at 23:28
  • 1
    @user253751 the initial stack pointer is copied from the vector table by hardware, no software is required or involved. This goes for all cortex-m. – Tom V Jan 06 '23 at 06:51
  • GPIO_A is defined as "const uint8_t GPIO_A = 0;" outside the GPIO class declaration in the header. I've checked the SP, MSP registers with GDB as that was the first error i encoutered (they are at 0x20000700 as i've defined in the vector table) – Alessandro Rossetti Jan 06 '23 at 12:20

3 Answers3

2

Address 0x7 is in the initial vector table in ROM, it is not writeable.

Unfortunately you don't have a section to populate the vector table, so this code is never going to work. You also don't appear to have a stack, which is where the members of gpio would be placed (because it is defined inside a function without the static keyword).

Start by taking the linker script provided as part of the STM32Cube package and then (if you must) modify it a little bit at a time until you break it. Then you will know what you have broken. It is not reasonable to write such a naïve linker script as this and expect it to work on a microcontroller.

Tom V
  • 4,827
  • 2
  • 5
  • 22
  • The vector table contains the starting value of the stack pointer at position 0. Which i've set to 0x20000700. I probably need to tell GCC to not use the stack pointer, but i don't think it's the issue *right now*. The question is why GCC is palcing variables at 0x00000xxx? – Alessandro Rossetti Jan 06 '23 at 12:18
  • Your program doesn't include a vector table! This is because you messed with the linker script, and took out the parts that tell it where to put things that it needs. If you don't include a command to discard leftover sections then it has to guess where to put them at the end, which will usually be wrong. – Tom V Jan 06 '23 at 12:41
  • At the beginning of text i did put the vector table. I'll check memory contents with GDB but being that the stack pointer is initialized with the value in my vector table, i think it's there. – Alessandro Rossetti Jan 06 '23 at 13:57
  • I'm seeing now that i'm loading .text to 0x08000000 which is the beginning of flash, i think it's then relocated at 0x00000000 at start, but i'll check with GDB. – Alessandro Rossetti Jan 06 '23 at 13:57
  • No it isn't relocated. 0x00000000 is an alias for 0x08000000. – Tom V Jan 06 '23 at 17:25
  • @user2154767 Where are you seeing that GCC is placing variables at 0x00000xxx? – user253751 Jan 06 '23 at 18:31
  • @TomV opening gdb, loading and breaking at main, i can see the output in Update#1 – Alessandro Rossetti Jan 08 '23 at 22:03
  • @user2154767 by printing the address of that variable with GDB – Alessandro Rossetti Jan 08 '23 at 22:10
0

of course the STM32 hard faults at the first write of that location.

STM32 does not "fault" if you try to write FLASH. It will simple have no effect.

You need to have a vector table in at the beginning of the FLASH memory. It has to contain as a minimum valid stack pointer address and the firmware entry point.

Your linker script and the code (I understand you do not use any STM supplied startup code) is far from being sufficient.

My advice:

  1. Create the project using STM32Cube.
  2. Then see how it should be done
  3. Having this knowledge you can start to reinvent the wheel
0___________
  • 60,014
  • 4
  • 34
  • 74
-1

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.