summaryrefslogtreecommitdiff
path: root/_posts/2019-07-12-arm-bare-metal-flags.md
diff options
context:
space:
mode:
authoruvok cheetah2019-07-12 21:55:04 +0200
committeruvok cheetah2019-07-12 21:55:04 +0200
commitac2f9bb94b2bb0b77dcf0b8873b2b291002d425c (patch)
tree0a7d2e0afb36d4326c600a4ddec170276a00fe18 /_posts/2019-07-12-arm-bare-metal-flags.md
parentc3779197ba103c156c89e598d644aee652d09a4f (diff)
Add article about ARM programming
Diffstat (limited to '_posts/2019-07-12-arm-bare-metal-flags.md')
-rw-r--r--_posts/2019-07-12-arm-bare-metal-flags.md360
1 files changed, 360 insertions, 0 deletions
diff --git a/_posts/2019-07-12-arm-bare-metal-flags.md b/_posts/2019-07-12-arm-bare-metal-flags.md
new file mode 100644
index 0000000..7323819
--- /dev/null
+++ b/_posts/2019-07-12-arm-bare-metal-flags.md
@@ -0,0 +1,360 @@
+---
+title: Bare Metal ARM - Compiler flag analysis
+language: en
+---
+
+So... I'm doing software development for microcontrollers at work with a commercial
+toolchain. I also evaluated the arm-none-eabi-gcc toolchain in the past (briefly).
+
+One thing that continues to confuse me is the different compiler and linker options
+used in various online examples, tutorials and library Makefiles.
+
+What I especially wanted to know is: What happens before `main()` with each
+combination of options? Do the variables get initialized (i.e. does the `.data`
+section get copied from flash to RAM) and is the `.bss` section properly zeroed?
+
+*TL;DR: See last paragraph*
+
+
+Some libraries, e.g. the [libopencm3 project][locm], as well as the [ARM CMSIS][cmsis]
+contain explicit startup code that do these tasks.
+
+This post uses the latest version of the [GNU embedded toolchain for ARM][1].
+At the time of writing, that's 8‑2019‑q3‑update.
+
+This post will take a look at:
+- using `-nostartfiles` vs. not using it
+- using nano.specs vs. nosys.specs vs. not using any specs file
+
+I start with a simple `main()` function that should suffice to analyze what I want.
+This example *won't* run on an actual Cortex-M3 microcontroller, since it lacks the vector
+table. I might (read: probably not ;)) look into this in a later post.
+
+I'm gonna use two different variants, one `main()` function that exits/returns and one that
+doesn't. Note the latter is the usual case when programming microcontrollers.
+
+For sake of simplicity, lets do it like this:
+
+~~~ c
+static int zerobss;
+static int globalinit = 42;
+
+int main() {
+ int localvar = 21;
+#ifdef DOEXIT
+ return 0;
+#else
+ while(1);
+#endif
+}
+~~~
+
+To test the effect of various options, I wrote the following bash script which loops over all
+combinations:
+
+~~~ bash
+#!/bin/bash
+# make.sh
+
+export PATH=/opt/gcc-arm-none-eabi-8-2019-q3-update/bin/:$PATH
+
+COMMONFLAGS="-mcpu=cortex-m3 -mthumb "
+FILES=main.c
+STARTOPT=("" -nostartfiles)
+SPECS=("" "-specs=nosys.specs" "-specs=nano.specs")
+EXIT=("" "-DDOEXIT")
+rm -f *.elf
+
+for f in $FILES; do
+ for sf in "${STARTOPT[@]}"; do
+ for sp in "${SPECS[@]}"; do
+ for ex in "${EXIT[@]}"; do
+ # bash substitutions, remove leading -specs= and trailing .specs
+ specname=${sp:7}
+ specname=${specname%.specs}
+ outfile=`basename $f .c`${sf}-${specname}-${ex:2}.elf
+ arm-none-eabi-gcc $ex $COMMONFLAGS $sf $sp -o $outfile $f 2>/dev/null && echo "Compiling $outfile succeeded" || echo "Compiling $outfile failed"
+ done
+ done
+
+ done
+done
+
+arm-none-eabi-size *.elf
+~~~
+
+I silenced the error output, otherwise it gets too noisy.
+
+First, the following combinations fail:
+
+~~~
+Compiling main--.elf failed
+Compiling main--DOEXIT.elf failed
+Compiling main-nano-.elf failed
+Compiling main-nano-DOEXIT.elf failed
+~~~
+
+In these cases, linking fails because (shortened output)
+
+~~~
+x/arm-none-eabi/bin/ld: x/arm-none-eabi/lib/thumb/v7-m/nofp/libc.a(lib_a-exit.o): in function `exit':
+exit.c:(.text.exit+0x16): undefined reference to `_exit'
+x/arm-none-eabi/bin/ld: x/arm-none-eabi/lib/thumb/v7-m/nofp/libc_nano.a(lib_a-exit.o): in function `exit':
+exit.c:(.text.exit+0x1a): undefined reference to `_exit'
+~~~
+
+That is, when neither `-nostartfiles` nor `-specs=nosys.specs` is specified, the function `_exit` gets
+referenced without being defined.
+
+In every other case, the linker emits a warning, since the entry symbol is not defined:
+
+~~~
+cannot find entry symbol _start; defaulting to 0000000000008000
+~~~
+
+So, let's look what we've got (sorted by the arm-none-eabi-size output):
+
+~~~
+ text data bss dec hex filename
+ 12 4 4 20 14 main-nostartfiles--.elf
+ 12 4 4 20 14 main-nostartfiles-nano-.elf
+ 12 4 4 20 14 main-nostartfiles-nosys-.elf
+ 22 4 4 30 1e main-nostartfiles--DOEXIT.elf
+ 22 4 4 30 1e main-nostartfiles-nano-DOEXIT.elf
+ 22 4 4 30 1e main-nostartfiles-nosys-DOEXIT.elf
+ 932 1096 68 2096 830 main-nosys-.elf
+ 944 1096 68 2108 83c main-nosys-DOEXIT.elf
+~~~
+
+Looks interesting. Just using `nosys.specs` seems to add a lot of stuff.
+
+Let's look at the disassembly of the return vs. loop code.
+Since the specs file used makes no difference, use the first file of each group:
+
+~~~
+$ arm-none-eabi-objdump -S -d main-nostartfiles--.elf main-nostartfiles--DOEXIT.elf
+
+main-nostartfiles--.elf: file format elf32-littlearm
+
+
+Disassembly of section .text:
+
+00008000 <main>:
+static int zerobss;
+static int globalinit = 42;
+
+int main() {
+ 8000: b480 push {r7}
+ 8002: b083 sub sp, #12
+ 8004: af00 add r7, sp, #0
+ int localvar = 21;
+ 8006: 2315 movs r3, #21
+ 8008: 607b str r3, [r7, #4]
+#ifdef DOEXIT
+ return 0;
+#else
+ while(1);
+ 800a: e7fe b.n 800a <main+0xa>
+
+main-nostartfiles--DOEXIT.elf: file format elf32-littlearm
+
+
+Disassembly of section .text:
+
+00008000 <main>:
+static int zerobss;
+static int globalinit = 42;
+
+int main() {
+ 8000: b480 push {r7}
+ 8002: b083 sub sp, #12
+ 8004: af00 add r7, sp, #0
+ int localvar = 21;
+ 8006: 2315 movs r3, #21
+ 8008: 607b str r3, [r7, #4]
+#ifdef DOEXIT
+ return 0;
+ 800a: 2300 movs r3, #0
+#else
+ while(1);
+#endif
+}
+ 800c: 4618 mov r0, r3
+ 800e: 370c adds r7, #12
+ 8010: 46bd mov sp, r7
+ 8012: bc80 pop {r7}
+ 8014: 4770 bx lr
+
+~~~
+
+No real surprises here. However: There is no code initializing the `.bss` section,
+not copying the `.data` section from flash memory to RAM (as we already could've guessed
+from looking at the code size). In these cases, writing the code manually would indeed
+be required.
+
+So, what does `nosys.specs` add? Let's just look at the symbol table of the looping example:
+
+~~~
+$ arm-none-eabi-objdump -t main-nosys-.elf
+
+main-nosys-.elf: file format elf32-littlearm
+
+SYMBOL TABLE:
+00008000 l d .init 00000000 .init
+0000800c l d .text 00000000 .text
+00008388 l d .fini 00000000 .fini
+00008394 l d .rodata 00000000 .rodata
+00008398 l d .ARM.exidx 00000000 .ARM.exidx
+000083a0 l d .eh_frame 00000000 .eh_frame
+000183a4 l d .init_array 00000000 .init_array
+000183ac l d .fini_array 00000000 .fini_array
+000183b0 l d .data 00000000 .data
+000187ec l d .bss 00000000 .bss
+00000000 l d .comment 00000000 .comment
+00000000 l d .debug_aranges 00000000 .debug_aranges
+00000000 l d .debug_info 00000000 .debug_info
+00000000 l d .debug_abbrev 00000000 .debug_abbrev
+00000000 l d .debug_line 00000000 .debug_line
+00000000 l d .debug_frame 00000000 .debug_frame
+00000000 l d .debug_str 00000000 .debug_str
+00000000 l d .ARM.attributes 00000000 .ARM.attributes
+00000000 l df *ABS* 00000000 /opt/gcc-arm-none-eabi-8-2019-q3-update/bin/../lib/gcc/arm-none-eabi/8.3.1/thumb/v7-m/nofp/crti.o
+00000000 l df *ABS* 00000000 /opt/gcc-arm-none-eabi-8-2019-q3-update/bin/../lib/gcc/arm-none-eabi/8.3.1/thumb/v7-m/nofp/crtn.o
+00000000 l df *ABS* 00000000 exit.c
+00000000 l df *ABS* 00000000 __call_atexit.c
+0000802c l F .text 00000014 register_fini
+00000000 l df *ABS* 00000000 crtstuff.c
+000083a0 l O .eh_frame 00000000
+00008040 l F .text 00000000 __do_global_dtors_aux
+000187ec l .bss 00000001 completed.8885
+000183ac l O .fini_array 00000000 __do_global_dtors_aux_fini_array_entry
+00008064 l F .text 00000000 frame_dummy
+000187f0 l .bss 00000018 object.8890
+000183a8 l O .init_array 00000000 __frame_dummy_init_array_entry
+00000000 l df *ABS* 00000000 /opt/gcc-arm-none-eabi-8-2019-q3-update/bin/../lib/gcc/arm-none-eabi/8.3.1/../../../../arm-none-eabi/lib/thumb/v7-m/nofp/crt0.o
+00000000 l df *ABS* 00000000 main.c
+00018808 l .bss 00000004 zerobss
+000183b4 l O .data 00000004 globalinit
+00000000 l df *ABS* 00000000 impure.c
+000183c0 l O .data 00000428 impure_data
+00000000 l df *ABS* 00000000 init.c
+00000000 l df *ABS* 00000000 memset.c
+00000000 l df *ABS* 00000000 atexit.c
+00000000 l df *ABS* 00000000 fini.c
+00000000 l df *ABS* 00000000 lock.c
+00000000 l df *ABS* 00000000 __atexit.c
+00000000 l df *ABS* 00000000 _exit.c
+00000000 l df *ABS* 00000000 crtstuff.c
+000083a0 l O .eh_frame 00000000 __FRAME_END__
+00000000 l df *ABS* 00000000
+000183b0 l .fini_array 00000000 __fini_array_end
+000183ac l .fini_array 00000000 __fini_array_start
+000183ac l .init_array 00000000 __init_array_end
+000183a4 l .init_array 00000000 __preinit_array_end
+000183a4 l .init_array 00000000 __init_array_start
+000183a4 l .init_array 00000000 __preinit_array_start
+0001880c g O .bss 00000001 __lock___atexit_recursive_mutex
+00018810 g O .bss 00000001 __lock___arc4random_mutex
+000187e8 g O .data 00000004 __atexit_recursive_mutex
+000082e0 g F .text 00000002 __retarget_lock_close
+00018830 g .bss 00000000 _bss_end__
+000187ec g .bss 00000000 __bss_start__
+000183b0 g O .data 00000000 .hidden __dso_handle
+00018814 g O .bss 00000001 __lock___env_recursive_mutex
+00018818 g O .bss 00000001 __lock___sinit_recursive_mutex
+00008394 g O .rodata 00000004 _global_impure_ptr
+00008100 g F .text 00000048 __libc_init_array
+00008080 g F .text 00000000 _mainCRTStartup
+00008000 g F .init 00000000 _init
+000082a4 g F .text 00000034 __libc_fini_array
+0001881c g O .bss 00000001 __lock___malloc_recursive_mutex
+000082fc g F .text 00000002 __retarget_lock_release_recursive
+000082f4 g F .text 00000004 __retarget_lock_try_acquire_recursive
+00018830 g .bss 00000000 __bss_end__
+000081e8 g F .text 000000b0 __call_exitprocs
+00008080 g F .text 00000000 _start
+000082f0 g F .text 00000004 __retarget_lock_try_acquire
+00008300 g F .text 00000084 __register_exitproc
+000082e4 g F .text 00000002 __retarget_lock_close_recursive
+000082ec g F .text 00000002 __retarget_lock_acquire_recursive
+000187ec g .bss 00000000 __bss_start
+00008148 g F .text 000000a0 memset
+000080f4 g F .text 0000000c main
+000082dc g F .text 00000002 __retarget_lock_init_recursive
+00018830 g .bss 00000000 __end__
+000082d8 g F .text 00000002 __retarget_lock_init
+00008388 g F .fini 00000000 _fini
+00008298 g F .text 0000000c atexit
+000183b8 g O .data 00000004 _impure_ptr
+000187ec g .data 00000000 _edata
+00018830 g .bss 00000000 _end
+00018820 g O .bss 00000001 __lock___at_quick_exit_mutex
+0000800c g F .text 00000020 exit
+000082e8 g F .text 00000002 __retarget_lock_acquire
+000082f8 g F .text 00000002 __retarget_lock_release
+00008384 g F .text 00000002 _exit
+00018824 g O .bss 00000001 __lock___dd_hash_mutex
+00018828 g O .bss 00000001 __lock___tz_mutex
+00080000 g .comment 00000000 _stack
+000183b0 g .data 00000000 __data_start
+0001882c g O .bss 00000001 __lock___sfp_recursive_mutex
+
+~~~
+
+In the list, we see `_mainCRTStartup`. This is the code which is responsible
+for the tasks we want to be performed.
+
+We also see the `_exit` function defined (which gets referenced by the startup code).
+The disassembly doesn't really surprise, it's a simple infinite loop:
+
+~~~
+$ arm-none-eabi-objdump --disassemble=_exit main-nosys-.elf
+
+main-nosys-.elf: file format elf32-littlearm
+
+
+Disassembly of section .init:
+
+Disassembly of section .text:
+
+00008384 <_exit>:
+ 8384: e7fe b.n 8384 <_exit>
+
+Disassembly of section .fini:
+
+~~~
+
+I'll spare you with further disassembly output, since it's pretty long.
+
+## Summary
+
+So, a quick recap:
+
+* *not* using `-nostartfiles` means the linker fails because `_exit` is not defined
+ (except when using `nosys.specs` which contains a definition for that function).
+ While this might sound contradicting at first, this is because the function is
+ referenced by the startup code.
+ You could simply define the function yourself, if it's the same as with the commercial
+ Keil compiler, the signature should be `void _exit(int)`. Hint: Look for "retargeting".
+* When using `nosys.specs`, you can omit the `-nostartfiles` option, meaning `.bss`
+ gets initialized and data copied to ROM.
+ Note that the program will still not run on a microcontroller, like mentioned above,
+ because you need the vector table for this.
+ This will also require a custom linker script, which in turn
+ means this linker script will have to define the symbols assumed by the startup code
+ properly.
+ Hint: using `-Wl,--verbose` with gcc enables verbose linker output, which also shows
+ the default linker script.
+
+Some [StackOverflow answers][so1] also reference the option `-ffreestanding`. I found
+this makes no difference with my example, however.
+
+In short: Yes, you need to write the startup code that initializes `.bss` and copies
+`.data` from flash to RAM manually when doing bare-metal programming.
+
+
+[1]: https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads
+[locm]: https://github.com/libopencm3/libopencm3
+[cmsis]: https://github.com/ARM-software/CMSIS_5
+[so1]: https://stackoverflow.com/a/51657692
+