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
|
---
title: Bare Metal ARM - Compiler flag analysis
language: en
redirect_from:
- /arm-bare-1
- /arm-gcc-1
---
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
|