summaryrefslogtreecommitdiff
path: root/_posts/2019-07-12-arm-bare-metal-flags.md
blob: e6b7839333144904c47aa50c082c45355a9db957 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
---
title: Bare Metal ARM - Compiler flag analysis
lang: en
redirect_from:
 - /arm-bare-1
 - /arm-gcc-1
description: "Trying out different GCC compiler flags for embedded / microcontroller programming, specifically for ARM (arm-none-eabi-gcc)."
---

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.

*Update 2019-07-13*: Adding links

Also, supplying `-nostdlib` has the same effect as `-nostartfiles` *for this particular
example*.

## Links

Regarding this topic, please also read "[From zero to main(): Bare metal C][zeromain]"
on interrupt.memfault.com ([archive.org link][zeromainarch] in case website goes down).

Another source that really made things "click" for me is
"[Bare-metal C programming on ARM][bmarm]". Although the book handles the Cortex-A
series, the section about startup code is really worth a read.
The book is also [available as PDF][bmarmpdf] (again, [archive.org link][bmarch]).

[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
[zeromain]: https://interrupt.memfault.com/blog/zero-to-main-1
[zeromainarch]: https://web.archive.org/web/20190515111558/https://interrupt.memfault.com/blog/zero-to-main-1
[bmarm]: https://github.com/umanovskis/baremetal-arm
[bmarmpdf]: http://umanovskis.se/files/arm-baremetal-ebook.pdf
[bmarch]: https://web.archive.org/web/20190713091239/http://umanovskis.se/files/arm-baremetal-ebook.pdf