Phillip Stanley-Marbell
Foundations of
Embedded Systems
Physical Constraints, Sensor Uncertainty, Error Propagation,
Low-Level C on RISC-V, and Open-Source FPGA Tools
Draft Version of Michaelmas 2020
4
C Programming for Embedded Systems
Figure 4.1: The compu-
tational problem speci-
fication of sorting partly
expressed in the Sal set
assembly language.
Use the following formula to tur n off the rightmost 1-bit in a word x,
producing 0 if none (e.g., 0101 1000 0101 0000):
x&(x 1)
Henry S. Warren Jr., Hackers Delight, Addison-Wesley, 2003.
The most amazing achievement of the computer software industry
is its continuing cancellation of the steady and staggering gains
made by the computer hardware industry.
Henry Petroski.
One of the main causes of the fall of the Roman Empire
was that, lacking zero, they had no way to indicate
successful termination of their C programs.
—Robert Firth.
Table 4.1: Concepts.
Concept Section
Firmware § 4
Libraries and linking § 4.3,4.19
C pointers § 4.5
C structs § 4.6
C arrays § 4.7
Bitwise arithmetic § 4.8
volatile § 4.9
Executables § 4.10
Object files § 4.10
The linker, ld § 4.10
Linker script files § 4.11
Preprocessor § 4.12
#include § 4.12
crt0.o § 4.13.2
Memory map § 4.13,??
Stack §
4.13
Heap § 4.13
Debug information § 4.14,4.16
The function main § 4.2.14.15.1
The utility objdump § 4.15.1,4.16
The utility objcopy § 4.15.1,4.16
Memory layout § 4.16
Memory-mapped I/O § 4.17
Makefiles § 4.20
2 phillip stanley-marbell
Computational problems relate a set of inputs to a set of outputs. Effi-
cient algorithms and heuristics define concrete mechanisms by which to solve
computational problems and to do so either exactly or approximately. Fig-
ure
4.1 illustrates part of an example computation problem specification us-
ing the Sal set assembly language.
1
Programs implement algorithms and
1
Stanley-Marbell 2010.
heuristics and specify a concrete sequence of operations in the language of
some machine model. In most computing systems, the machine model you
need to understand is usually an abstract machine
2
exposed to you by the
2
Engler, Kaashoek, and
O’Toole
1995.
operating system.
In embedded systems, there is typically no abstraction provided to you by
an operating system: quite often, there is no operating system at all and you
are either writing the firmware (software running directly over the processor),
or you might be the one responsible for implementing the operating sys-
tem. The machine model that you must become familiar with in embedded
systems is therefore the model defined by the combination of the hardware
architecture of the microcontroller or embedded processor in the system and
the compiler for the programming language you will be using.
Platforms such as the Arduino (and its variants and derivatives), Rasp-
berry Pi, and the BBC micro:bit are wonderful educational tools for school
children and amateur adults who want to be able to read from a sensor or
control an actuator. They are great tools for those who do not need to under-
stand the fundamentals of what is going on under the hood. But experience
with platforms such as Arduino, Raspberry Pi, or the BBC micro:bit, though
fun and easy, leaves you with none of the foundations you need to design the
hardware or firmware for a new embedded system from scratch. These are
skills which you would need if you wanted to, e.g., implement new instru-
mentation for an autonomous ground or aerial vehicle, or if you wanted to
design a custom embedded system to go into a surgical implant.
This chapter introduces the inner workings of C programs for embedded
systems compiled with the GCC compiler and tools from the Binutils suite
of binary manipulation tools. The ideas introduced in this chapter also ap-
ply to other compilers such as Clang and to compilers in embedded system
IDEs such as those from IAR, TI’s Code Composer Studio, or Analog De-
vices’ CrossCore tools. The target processor architecture we will use in the
examples below will be the Hitachi SuperH RISC (Hitachi SH) architecture
and we will use the open-source Sunflower embedded system emulator
3,4
to
3
Stanley-Marbell and Hsiao
2001.
4
Stanley-Marbell and
Marculescu
2007.
allow us to interactively explore the execution of programs. The concepts and
examples introduced in this Chapter apply largely unchanged to other target
processor architectures suc h as the ARM Cortex-M0+ in the hardware plat-
form used in this course and to modern architectures such as RISC-V which
we will see in Chapter
7.
foundations of embedded systems 3
4.1 Intended Learning Outcomes
By the end of this chapter, you should be able to:
1. Write C programs that use the basic facilities of the language (e.g., arrays,
structs, pointers), and language facilities needed in embedded systems
programming (e.g., memory-mapped I/O and the static and volatile
type modifiers).
2. Describe the role of the C preprocessor and explain what happens when a
C program includes a header file, e.g., by the directive #include <stdio.h>.
3. List the steps and tools involved in converting a C-language program into
a sequence of instructions for execution on a processor.
4. List the steps involved in executing a binary on a processor.
5. Differentiate between binary file formats such as the Motorola S-record
format, ELF, and
a.out formats.
6. Explain the special role of the function main in C programs.
7. Design linker script/command files to control where parts of object files
are placed in the address space of the final binary generated by the linker.
8. Explain the difference between linker script files and map files, explain the
contents of a program’s map file, and determine what object files have
been linked into a binary based on the contents of the map file.
9. Create programs with a mixture of C code and assembly language code.
10. Design C programs which access specific memory-mapped registers, given
the corresponding memory addresses defined in a data sheet.
4.1.1 Learning outcomes pre-assessment
Complete the following
quiz to evaluate your prior knowledge of the material
for this chapter.
4.1.2 Things to think about
Complete the following
thinking exercise to stimulate your thoughts about
the contents of this chapter before proceeding with the material.
4 phillip stanley-marbell
4.1.3 Things to look out for
Concepts people sometimes get confused by in this chapter include:
1. What linker script files do.
2. What interrupts and exceptions are.
3. How interrupt handlers work.
4. How the Sunflower embedded system emulator works.
5. The difference between linker script files and map files.
6. What the statement #include does in C.
7. The role of the function called main in a C program.
8. The different roles of the S-record/SREC, ELF, and a.out formats and how
all of those relate to linker script files, map files, and so on.
9. What labels in assembly language do.
10. The difference between a symbol and a variable.
11. What is in a
.o file.
4.1.4 The muddiest point
As you go through the material in this chapter, think about the following two
questions and note your responses for yourself or using the annotation tools
of the online version of the chapter. You will have the opportunity to submit
your responses to these questions at the end of the chapter:
1. What is least clear to you in this chapter? (You can simply list the section
numbers or write a few words.)
2. What is most clear to you in this chapter? (You can simply list the section
numbers or write a few words.)
foundations of embedded systems 5
4.1.5 Quiz
In the next five minutes, carefully and sequentially go through the questions be-
low and mark those which you think you can explain to someone in less than
15 seconds. In particular, make sure you answer Question 50, Question 51,
and Question 52.
At the end of the five minutes, pick one of the questions that does not have
a simple “yes”/“no” answer. Tell the person sitting closest to you which
question it is and wait for them to read that question. Next, explain your
answer to them in less than 30 seconds. We will then go round the class
and each person will state the question number for one question to which
they did not know the answer (pick the question whose answer you are most
interested in knowing the answer to).
1. What is an algorithm?
2. What is a heuristic?
3. What are the main stages of a processor’s pipeline?
4. What is the instruction set architecture (ISA) of a processor?
5. What are common instructions you would expect to find in the ISA of any
processor?
6. How would you define the term compiler?
7. How would you define the term assembler?
8. How would you define the term linker?
9. What does #include <stdio.h> in a C program do?
10. Is #include part of the C language syntax?
11. What about main?
12. Is main a reserved keyword in C?
13. What is in an executable?
14. What are the parts of an executable?
15. Does everything in an executable get loaded into the processor to run?
16. What is a binary?
17. What is an object file?
18. How is an object file different from a binary?
6 phillip stanley-marbell
19. What are some of the different executable and object file formats?
20. What is the ELF file format?
21. What is the COFF file format?
22. Can you create an object file from a C program without a function called
main?
23. Can you (under normal circumstances) create an executable without a
function called main?
24. What is the difference between an executable and an object file?
25. What does a compiler do to convert a C program into an executable?
26. What is a linker script file?
27. Are linker scripts meant for embedded systems?
28. When you compile using gcc without the -c flag, does it also use a linker
script? If yes what linker script does it use?
29. Does the content of the linker script affect the output of the .text section?
30. What is the memory map of a program?
31. What is a Von Neumann (Princeton) architecture?
32. What is a Harvard architecture?
33. If a processor uses a Harvard architecture, how does that affect the mem-
ory map and linker script files?
34. What is a cache in a processor?
35. Are caches common in microcontrollers? If yes, why? If no, why not?
36. In the context of a program’s address space, what is the heap?
37. In the context of a program’s address space, what is the stack?
38. What determines how large the stack is?
39. What goes on the stack?
40. Who determines the size of the stack?
41. What goes on the heap?
42. Who determines the size of the heap?
foundations of embedded systems 7
43. What tools can you use to convert between different object file formats?
44. What is a
Makefile?
45. What is the difference between make, gmake, cmake, and mk?
46. What is a
configure script?
47. What is the memory map of an object file or binary?
48. How can you control the memory map of a binary?
49. What command-line tools can you use to inspect the memory map of a
binary?
50. Given just a pen and piece of paper, a disassembled program binary, and
sufficient time, could you determine exactly what the result of the program
will be?
51. What constitutes the “result” of executing a given program binary? In
terms of state of a processor, how would you quantify this?
52. Given a program binary (e.g., given an S-record file), could you imple-
ment a program that runs on your PC, using any programming language
of your choice, which will determine precisely the result of running the
S-record file on the processor for which it was compiled? What is such a
program called?
53. Would a program such as the one in the question above always produce
the same output? Why?
54. What is the length (in characters) of the shortest C program that you can
run from a command shell?
55. Is the program below a compilable C program?
1 ??=include <stdio.h>
2 int main(void)??<printf("Hello, world??/n");??>
56. Is the program below a valid C program?
1 #include <stdio.h>
2
3 int
4 hello(void) {
5 printf("Hello, World.\n");
6
7 return 0;
8 }
57. Could the program above be run from a command shell?
8 phillip stanley-marbell
58. If, on an embedded system, your program used printf to print out a
string, what is the complete sequence of steps by which the string reached
the terminal / console output?
59. What is the signature of a function?
60. What is an interrupt?
61. What is an exception?
62. Are interrupts different from exceptions?
63. What happens when a processor incurs an interrupt or exception?
64. What is an exception handler? What is an interrupt handler?
65. Will the following C program ever terminate? What could cause the
program to terminate? (You can find the associated assembly language
startup code in Appendix
1.)
1 #include "print.h"
2
3 /
*
8 GPRs + PR
*
/
4 unsigned char REGSAVESTACK[36];
5 static void hdlr
_
install(void);
6 volatile int gFlag = 1;
7
8 int
9 main(void) {
10 hdlr
_
install();
11
12 print("\n\n\nStarting...\n\n\n");
13 while (gFlag) {
14 }
15 print("\n\n\nExiting, bye!\n\n\n");
16
17 return 0;
18 }
19
20 void
21 intr
_
hdlr(void) {
22 gFlag = 0;
23
24 return;
25 }
26
27 void
28 hdlr
_
install(void) {
29 extern unsigned char vec
_
stub
_
begin, vec
_
stub
_
end;
30 unsigned char
*
dstptr = (
unsigned char
*
)0x8000600;
31 unsigned char
*
srcptr = &vec
_
stub
_
begin;
32
33 /
*
Copy the vector instructions to vector base
*
/
34 while (srcptr < &vec
_
stub
_
end) {
35
*
dstptr++ =
*
srcptr++;
foundations of embedded systems 9
36 }
37
38 return;
39 }
66. In the example above, why is the variable gFlag defined as volatile int
gFlag? What could happen if it was just defined as int gFlag?
67. What do you think the following sequence of Hitachi SH assembly in-
structions does?
1 MOVL r15, @-r0
2 MOVL r14, @-r0
3 MOVL r13, @-r0
4 MOVL r12, @-r0
5 MOVL r11, @-r0
6 MOVL r10, @-r0
7 MOVL r9, @-r0
8 MOVL r8, @-r0
68. What do you think the following sequence of Hitachi SH assembly in-
structions does?
1 MOVL @r0+, r8
2 MOVL @r0+, r9
3 MOVL @r0+, r10
4 MOVL @r0+, r11
5 MOVL @r0+, r12
6 MOVL @r0+, r13
7 MOVL @r0+, r14
8 MOVL @r0+, r15
69. What does the following function do?
1 unsigned long
2 devexcp
_
getintevt(void) {
3 return
*
(0xFFFFFFD8);
4 }
70. Is sizeof a library function or a C language operator?
71. Is strlen a library function or a C language operator?
72. What is the value of sizeof("hello")
73. What is the value of strlen("hello")?
74. What does the program
objdump do?
75. What does the program objcopy do?
10 phillip stanley-marbell
4.2 A Quick Introduction to C and Compiling C Programs from the
Command Line
C is a small language
5
. C is straightforward to learn if you know what to
5
You can find a complete
BNF grammar for ISO C
here or in Appendix B of
Harbison and Steele.
prioritize, if you know what infor mation about C matters, and what details
of the language can be left to later.
You will first need to know how to compile C programs. On most com-
puting systems
6
, you can access a C compiler as the program
cc from the
6
We will assume a Unix-
based system such as
Linux, FreeBSD, OpenBSD,
or macOS.
command line. In some systems, this will be an alias for
gcc, on others it will
be an alias for the clang compiler, and so on.
There are three main ways in which you will invoke the C compiler. The
first method will be to pass it the flag -c and a C program. We will cover the
other two invocation methods later.
C programs that are meant to be executed (e.g., from the command shell)
are different from C programs that are intended for other purposes: An
empty program is a valid C program but can only be compiled to an ob-
ject file, not to an executable. In the examples below,
shell prompt% indicates
the command line prompt; on your system, it will be something different.
Try this out:
1 shell prompt% echo ’’ > empty.c
2 shell prompt% cat empty.c
3 shell prompt% cc -c empty.c
4 shell prompt% ls
5 empty.c empty.o
The -c flag tells the C compiler to compile your C source file into what is
called an object file. Object files are compiled source code that has not yet
been linked to create an executable. They are not executables per se and cannot
be run from the command line.
If you want to compile a C program to yield an executable binary, pass it
to a C compiler without the -c flag. In the example below, we also specify
the -o flag to tell the compiler where to store the executable
7
: Try this out:
7
Otherwise the compiler
places the executable in the
default file a.out.
1 shell prompt% echo ’’ > empty.c
2 shell prompt% cat empty.c
3 shell prompt% cc -o empty empty.c
4 Undefined symbols for architecture x86
_
64:
5 "
_
main", referenced from:
6 implicit entry/start for main executable
7 ld: symbol(s) not found for architecture x86
_
64
8 clang: error: linker command failed with exit code 1 (use -v to see invocation)
9 shell prompt% ls
10 empty.c
Clearly, this time, for the same file, the C compiler,
cc, did not accept the
input file empty.c even though it generated an object file when given the
same file with the -c flag.
foundations of embedded systems 11
Exercise
Why do you think the empty C program compiles when compiled with flags -c to generate
an object file, but not when compiled to generate an executable? The error message should give you a
clue. We will see more about this later in this chapter.
4.2.1 The function main
By convention, every C program that will be run by the command line (i.e.,
by a shell) needs to have a function called main. There is some leeway in the
convention of what the signature of the function main should look like. The
signature of a function is the function’s name, its return type, and its argu-
ment types (regardless of their names). We will discuss function signatures
again later in this Chapter. Two common forms for the function main are:
1 /
*
2
*
A function called ‘main‘ taking no argument (the keyword/type ‘void‘)
3
*
and returning an integer (the keyword/type ‘int‘).
4
*
/
5 int
6 main(void)
7 {
8 return 0;
9 }
and
1 /
*
2
*
A function called ‘main‘ taking two arguments. The first
3
*
argument, ‘argc‘ is a scalar of type ‘int‘. The second
4
*
argument is an array in which each element has type ‘char
*
‘.
5
*
The function returns an integer (the keyword/type ‘int‘).
6
*
/
7 int
8 main(int argc, char
*
argv[])
9 {
10 return 0;
11 }
The type specifier int at the beginning of the program is a C language
built-in datatype. In the examples above, it specifies the type of values that
the function main can return. What follows (main(int argc, char
*
argv[]))
says that the function main takes two arguments, the first of which is of type
int, and the second of which is of type char
*
(and because that second
argument ends in [], it is an array). This part of the function definition is
called the function’s signature. Next, the curly braces ({}) define the body or
implementation of the function.
There is nothing special about the argument variable names argc and argv:
We could have used any other names in defining the function arguments as
long as we are consistent in using those names in the body of the function
too. In other words, the signature of a function is about its types and the
names of the arguments do not matter (as long as you are consistent between
12 phillip stanley-marbell
the function’s signature and its body or implementation). That is all you need
to write a C program that can be run from the command line.
4.2.2 A larger example
The program in the example above does not do much. What if we wanted
to create a slightly more sophisticated program, to take all its command-line
arguments and to print out the one of its arguments that is a palindrome?
That is, we want the program
palindrome that behaves like:
1 shell prompt% cc -o palindrome palindrome.c
2 shell prompt% ./palindrome the quick brown fox jumped over the madam on the way to the ball
3
4 madam
In the example above, to invoke the compiled binary
palindrome, we typed
./palindrome. The command shell treats executables specially (to help pre-
vent you from shooting yourself in the foot) and you therefore have to give
the explicit path to a binary if it is not already in your shell’s search path. For
a file in the current directory, the explicit path is given by prefixing the file
name with ./”.
Back to the task of printing out which of the arguments is a palindrome,
we would need to know how to make the program know what its arguments
are. That is where the second convention of the form of the function main
comes in. If the signature of your main function is of the form int main(int
argc, char
*
argv[]), the shell or whatever program that runs it will pass it
the arguments with which the program was invoked, as an array of strings.
Using an exec system call
8
, the shell (or equivalent program) will request
8
You can find a brief
synopsis of the exec system
call for POSIX-compliant
systems
here.
your program to be executed by the operating system and will set the argu-
ment argc to the number of arguments with which your program was called
and will set the array argv to the strings. One aspect of this convention worth
noting is that the list of arguments passed in via argv (and whose number is
in the integer variable argc) will start with the name of the program itself.
So in the case of palindrome, when invoked from the shell, the string of its
first argument will be "./palindrome".
Strings in C are arrays of elements of type char. In C, you can also refer
to any item in memory using a pointer and you will often refer to strings as
pointers to char items (i.e., as char
*
items). We will learn more about point-
ers in Section
4.5. Here is a first part of the implementation of palindrome.c:
1 #include <stdio.h>
2 #include <stdbool.h>
3
4 bool isPalindrome(char
*
string);
5
6 int
foundations of embedded systems 13
7 main(int argc, char
*
argv[])
8 {
9 for (int i = 0; i < argc; i++)
10 {
11 if (isPalindrome(argv[i]))
12 {
13 /
*
14
*
See the man page for printf() (or Harbison and Steele,
15
*
fifth edition, page 387) to find out more about its syntax.
16
*
/
17 printf("\n\t%s\n\n", argv[i]);
18 }
19 }
20 }
The snippet above introduces the for construct for looping, the if state-
ment for conditional branching, and the printf library routine for formatted
output. The syntax of for and if and the behavior of the library routine
printf you can find in a language reference for the C language. A very good
one is Harbison and Steele.
9
9
Steele and Harbison
2007.
The snippet above is missing the function isPalindrome. If we know the
length of a string is n, then a simple way to check if a string is a palindrome
is to check if the ith character is equal to the n i 1th character. The full
program is thus:
1 #include <stdio.h>
2 #include <stdbool.h>
3 #include <string.h>
4
5 bool isPalindrome(char
*
s);
6
7 int
8 main(int argc, char
*
argv[])
9 {
10 for (int i = 0; i < argc; i++)
11 {
12 if (isPalindrome(argv[i]))
13 {
14 /
*
15
*
See the man page for printf() (or Harbison and Steele,
16
*
fifth edition, page 387) to find out more about its syntax.
17
*
/
18 printf("\n\t%s\n\n", argv[i]);
19 }
20 }
21 }
22
23 bool
24 isPalindrome(char
*
s)
25 {
26 int check = 0;
27
28 /
*
29
*
See the man page for strlen() (or Harbison and Steele,
30
*
fifth edition, page 351) to find out more about its syntax.
14 phillip stanley-marbell
31
*
/
32 int n = strlen(s);
33 for (int i = 0; i < n; i++)
34 {
35 if (s[i] == s[n - i - 1])
36 {
37 check++;
38 }
39 }
40
41 return (check == n);
42 }
Running this program, we get the desired result:
1 shell prompt% ./palindrome the quick brown fox jumped over the madam on the way
to the ball
2
3 madam
4.3 Linking
A linker combines one or more object files into an executable that can be
loaded by an operating system. Individual object files may contain references
to functions in other object files and one of the tasks of the linker is to make
sure that all the cross references between the object files it is linking together
are met.
In simplest use, you can provide the linker with a complete list of object
files (i.e.,
.o files) to link together to generate an executable. Because multiple
object files might together implement some single piece of functionality, you
can group together multiple object files into what is known as an archive,
using the ar command. Archives have the file suffix .a. Archives are also
usually referred to as libraries . There are at least two variants of libraries:
static libraries and dynamic libraries (also referred to as shared objects). The
following discussion refers primarily to static libraries.
In common use, the linker takes two kinds of command line flags. The
flag -l directs the linker to link in an archive (i.e., a library). By convention,
when you specify the linker flag -lABC, the linker will try to find a file called
libABC.a and will link the objects in that library with the other objects you
have specified such as other libraries or your own object file. The linker will
by default search through a number of standard locations in your filesystem
for the libraries such as
libABC.a in the above example. You can explicitly
direct the linker to the location of a library specified by the -l (lowercase)
flag, by specifying a library search path using the -L (uppercase) flag.
foundations of embedded systems 15
4.4 The Basic C Datatypes
The basic numeric datatypes in the C language are char, short, int, long,
long long, float, and double.
The sizes of the basic datatypes (on most systems) are 8 bits for char, 16
bits for short, 32 bits for int, 32-bits for float and 64 bits for double. This
is not the always the case on all embedded platforms. On the TI C55x family
of DSPs, float and double are both 32-bit, and int is 16 bits.
10
As a result
10
Texas Instruments Inc.
2007.
of these variations across platforms, it is better to use the data type aliases
defined in the header file stdint.h. These define types such as uint32
_
t
(always refers to a 32-bit unsigned integer), int64
_
t (a 64-bit signed integer),
and so on. The section titled “Very difficult computers” on page 187 of the fifth
edition of Harbison and Steele
11
gives a few other examples of architectures
11
Steele and Harbison
2007.
that are challenging to map C programs to.
As we saw before, there is no string datatype per se in C: strings are repre-
sented in C programs using arrays of char. The design of the C programming
language chose to allow arrays to be used interchangeably with references to
the first elements of the array: these references to memory locations (whether
these memory locations contain an array or another datatype) are referred to
as pointers.
4.5 Pointers in C
The C programming language allows programs to access arbitrary memory
addresses. A pointer in C is a program variable that refers to a specific
location in the memory of the computing system. Because pointers allow a
program to refer to any memory location, they are used in a broad variety of
contexts. However, because pointers allow a program to refer to any memory
location, they are also easy to abuse.
C programs declare a variable as a pointer by prefacing the variable with
an asterisk (
*
). So, for example, the following program declares two variables
value and pointer, and uses accesses to the variable pointer, which is setup
to refer to the memory occupied by the variable value using the reference
operator, &, to change the value of the variable value:
1 #include <stdio.h>
2
3 /
*
4
*
For uint32
_
t
5
*
/
6 #include <stdint.h>
7
8 int
9 main(int argc, char
*
argv[])
10 {
16 phillip stanley-marbell
11 uint32
_
t value = 100;
12 uint32
_
t
*
pointer = &value;
13
14 printf("Value is [%d]\n", value);
15
*
pointer = 20;
16 printf("Value is [%d]\n", value);
17 }
4.6 Aggregate Data Types and structs in C
Other than arrays, which are collections of data items of the same type, the
C programming language also allows programs to create collections of data
items of differing data types. A canonical example is representing infor-
mation about a person (name, age, gender). The C programming language
provides the construct of a struct to group such data items together.
There are a few different ways to define structs in C. We will stick with
just one of the ways of defining structs. The program below defines a struct
for the personal information example, takes its command line arguments
(assuming the arguments are in the order name, age, gender), and populates
the fields of the struct with the information provided on the command line:
1 /
*
2
*
For the uint64
_
t
3
*
/
4 #include <stdint.h>
5
6 /
*
7
*
For the function prototype of printf()
8
*
/
9 #include <stdio.h>
10
11 /
*
12
*
For the function prototype of strcmp()
13
*
/
14 #include <string.h>
15
16 /
*
17
*
For the function prototype of strtoul()
18
*
/
19 #include <stdlib.h>
20
21 /
*
22
*
This defines an "enumeration type" or ‘enum‘.
23
*
Enumeration types are useful when you want to
24
*
define variables which will take on a small set
25
*
of values and where it is is useful to name those
26
*
values.
27
*
/
28 typedef enum
29 {
30 kGenderFemale,
31 kGenderMale,
foundations of embedded systems 17
32 } Gender;
33
34 typedef struct
35 {
36 char
*
name;
37 uint64
_
t age;
38 Gender gender;
39 } PersonInfo;
40
41 int
42 main(int argc, char
*
argv[])
43 {
44 PersonInfo p;
45 char tmp;
46 char
*
ep = &tmp;
47
48 p.name = argv[1];
49
50 /
*
51
*
See the man page for strtoul() (or Harbison and Steele,
52
*
fifth edition, page 412) to find out more about its syntax.
53
*
/
54 p.age = strtoul(argv[2], &ep, 0);
55
56 /
*
57
*
See the man page for strcmp() (or Harbison and Steele,
58
*
fifth edition, page 349) to find out more about its syntax.
59
*
/
60 if (!strcmp(argv[3], "female"))
61 {
62 p.gender = kGenderFemale;
63 }
64 else
65 {
66 p.gender = kGenderMale;
67 }
68
69 /
*
70
*
We choose to just print out the struct we just filled:
71
*
/
72 printf("Name: [%s], age: [%lu], gender: [%s]\n",
73 p.name, p.age,
74 (p.gender == kGenderFemale ? "female" : "male"));
75 }
4.7 Arrays in C
Arrays in C are collections of data elements of the same type. The example
below defines a variable numbers which is an array of 10 integers:
1 int
2 main(void)
3 {
4 int numbers[10];
5
6 return 0;
18 phillip stanley-marbell
7 }
Arrays in C are zero-indexed: the first element in the array numbers is numbers[0]
and the last (tenth) element in the array is numbers[9]. You can initialize the
elements of an array when you declare it, and you need not specify initializers
for all elements. The elements for which you specify no initializers will have
undefined values (may be zero if you are lucky) if the array is defined within
a function (i.e., is on the stack
12
) and will be initialized with zeros if the array
12
We will discuss the stack
and heap in more detail in
Section
4.13.
is global. The example below declares a global array of ints (globalNumbers)
and a function-local array of ints (localNumbers) and initializes the first, sec-
ond, and last elements in each array:
1 #include <stdio.h>
2
3 typedef enum
4 {
5 kArraySize = 10,
6 } Sizes;
7
8 int globalNumbers[kArraySize] = {
9 [0] = 1123,
10 [1] = 33,
11 [9] = 33,
12 };
13
14 int
15 main(void)
16 {
17 int localNumbers[kArraySize] = {
18 [0] = 1123,
19 [1] = 33,
20 [9] = 33,
21 };
22
23 for (int i = 0; i < kArraySize; i++)
24 {
25 printf("globalNumbers[%d] = [%d]\n", i, globalNumbers[i]);
26 printf("localNumbers[%d] = [%d]\n", i, localNumbers[i]);
27 }
28
29 return 0;
30 }
You can define multi-dimensional arrays using additional sets of square
braces, such as int localNumbers2D[kArraySize][kArraySize]. When defin-
ing multi-dimensional arrays with dynamic memory allocation (Section
4.13),
you will need to allocate memory for each of the additional dimensions. A
common mistake is to mistakenly allocate a single-dimensional array with as
many elements as are desired in the multi-dimensional array.
foundations of embedded systems 19
4.8 Bitwise Arithmetic in C
In programming embedded systems, you will often need to extract parts
of the bit-level representation of a variable or to combine bit values from
different variables into a single word. For example, many sensors provide
their readings as individual 8-bit words which you then need to combine to
get, e.g., a 10-, 12-, 16-, or 20-bit (or larger) word. For example, the following
snippet of code constructs a 10-bit value, to be stored in a 16-bit variable,
combined. The upper 2 bits of the 10-bit value will be taken from the lower
2 bits of data[1] and the lower 8 bits of the 10-bit value will be taken from
data[0]:
1 uint16
_
t combined = ((data[1] & 0x3) << 8) | (data[0]);
In the example above, the operation data[1] & 0x3 performs a bitwise and
(the operator &) of the value of data[1] with the constant 0x3, zeroing out
the upper 6 bits but preserving the lower two bits. Because they are used
to set or clear parts of a binary word, constants such as 0x3 used in this
way are called bit masks. The expression (data[1] & 0x3) « 8) combines
the masking operation with a left shift (the operator «), by 8 bits, of the result
of the masking operation. Finally, the expression ((data[1] & 0x3) « 8) |
(data[0]) combines the mask and shift with a bitwise or (the operator |) to
concatenate the contents of data[0] with the lower two bits of data[1]. The
lower ten bits of the 16-bit variable combined are therefore from data[0] and
data[1] and the upper six bits are zero.
4.9 The Keyword volatile
When a C compiler processes programs you provide to it, it will often per-
form many transformations of your program, such as simplifying arithmetic
expressions in your program whose outcome can be determined before your
program runs, and much more. These transformations are often referred to
as optimizations, though they may have little to do with mathematical op-
timization
13
and there is no guarantee their results will be optimal in any
13
Bertsekas, Nedi,
Ozdaglar, et al.
2003;
Boyd and Vandenberghe
2004; Papadimitriou and
Steiglitz
1998.
sense. As part of these transformations, a compiler may infer that parts of
your program compute values that are never used and it may therefore “sim-
plify” your program by omitting instructions for those unused parts of the
program.
One program transformation that could be troublesome in embedded sys-
tems programming is the omission of reads from memory whose result are
apparently never used. In the example in Question
65 of Section 4.1.5 above,
a compile-time analysis of the program would incorrectly indicate that the
variable gFlag can never be 0. Because an interrupt may trigger the execution
20 phillip stanley-marbell
of the function intr
_
hdlr(), gFlag may indeed get set to 0. The C language
provides the keyword volatile as a way for programs to indicate to the com-
piler that a variable’s value may change even if analysis of the code indicates
that it should not. Compilers ensure that reads from variables declared with
the type modifier volatile are always performed.
Applications executing in the absence of an operating system are often
constructed as a main event loop, with interrupts handled asynchronously
by an interrupt handler. In such a situation, you need to ensure that data
structures which are modified by interrupt handlers asynchronously do not
adversely affect the execution of the main event loop. In such situations,
always declare variables to be used to exchange information between the
main event loop and the interrupt handler as volatile. This ensures that
the C compiler will generate code that ensures that variable updates always
occur, even when the compiler thinks such updates can be optimized away.
If the main event loop is something like the following:
1 int flag;
2
3 flag = 0;
4 while (flag)
5 {
6 print("hello");
7 }
then the compiler might think that since the variable flag is never updated
in the body of the loop and it can decide not to generate code for the while
loop in its dead code elimination transformation. If the variable flag is modified
by an interrupt handler, this will however be an incorrect transformation to
make. To tell a C compiler that a variable might be changed asynchronously,
such a variable must be marked as volatile. For example, the following is a
corrected implementation of the above:
1 volatile int flag;
2
3 flag = 0;
4 while (flag)
5 {
6 print("hello");
7 }
4.10 Object Files, Executables, and their File Formats
An object file contains the instructions (code) and data of a given compiled
source file. A single object file will not contain all the instructions and data
that are needed in an executable, such as the instructions of functions from
any statically-linked libraries that it invokes. Object files are created by the
compiler, which takes the input source file, converts that to assembly lan-
guage for a given target processor, and these assembly instructions are then
foundations of embedded systems 21
converted by the assembler into object code. Typically, this distribution of
work between what is usually referred to as the compiler (converts, e.g., C to
assembler) and the assembler (converts assembler into object code) is trans-
parent to the user. And some compiler implementations might fuse the im-
plementations of these two components into the same program.
An executable file is created from one or more object files which are com-
bined using the linker. Again, even though the process of linking together
one or more object files is logically distinct from compilation to assembly or
generation of object files from assembly, the process of linking also happens
transparently behind the scenes if you are compiling a single source file to an
executable.
In many compiler implementations, the command you invoke (e.g.,
gcc)
is often what is called the compiler driver program (not to be confused with
device drivers). This front-end or driver program invokes the necessary tools
to perform the compilation, assembly, and linking. To see these intermediate
steps happening, you can invoke the compiler driver with an appropriate flag
to request it to be verbose. For
sh-elf-gcc, this is the -v flag. We can see
more details when we invoke sh-elf-gcc with the -v flag:
1 shell prompt% sh-elf-gcc -v simple.c -o simple
2
3 Using built-in specs.
4 COLLECT
_
GCC=sh-elf-gcc
5 COLLECT
_
LTO
_
WRAPPER=/usr/local/src/git/sunflower-simulator-github-clone/tools/superH/libexec/gcc/sh-elf/8.2.0/
lto-wrapper
6 Target: sh-elf
7 Configured with: ../configure --disable-docs --target=sh-elf --prefix=/usr/local/src/git/sunflower-simulator-
github-clone/tools/superH --disable-libssp --with-gnu-as --with-gnu-ld --with-newlib --enable-languages=c
--disable-multilib --disable-libssp --disable-libstdcxx-pch --disable-libmudflap --disable-libgomp --with-
headers=/usr/local/src/git/sunflower-simulator-github-clone/tools/source/newlib-2.5.0.20170922/newlib/libc
/include --with-gmp=/opt/local --with-mpfr=/opt/local --with-mpc=/opt/local --with-libiconv-prefix=/opt/
local -v
8 Thread model: single
9 gcc version 8.2.0 (GCC)
10 COLLECT
_
GCC
_
OPTIONS=’-v’ ’-o’ ’simple’
11 /usr/local/src/git/sunflower-simulator-github-clone/tools/superH/libexec/gcc/sh-elf/8.2.0/cc1 -quiet -v simple
.c -quiet -dumpbase simple.c -auxbase simple -version -o /tmp/ccpqQ3U8.s
12 GNU C17 (GCC) version 8.2.0 (sh-elf)
13 compiled by GNU C version 5.4.0 20160609, GMP version 6.1.0, MPFR version 3.1.4, MPC version 1.0.3, isl
version none
14 GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
15 #include "..." search starts here:
16 #include <...> search starts here:
17 /usr/local/src/git/sunflower-simulator-github-clone/tools/superH/lib/gcc/sh-elf/8.2.0/include
18 /usr/local/src/git/sunflower-simulator-github-clone/tools/superH/lib/gcc/sh-elf/8.2.0/include-fixed
19 /usr/local/src/git/sunflower-simulator-github-clone/tools/superH/lib/gcc/sh-elf/8.2.0/../../../../sh-elf/sys-
include
20 /usr/local/src/git/sunflower-simulator-github-clone/tools/superH/lib/gcc/sh-elf/8.2.0/../../../../sh-elf/
include
21 End of search list.
22 GNU C17 (GCC) version 8.2.0 (sh-elf)
22 phillip stanley-marbell
23 compiled by GNU C version 5.4.0 20160609, GMP version 6.1.0, MPFR version 3.1.4, MPC version 1.0.3, isl
version none
24 GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
25 Compiler executable checksum: 3645f49a73a1199f14dd83119fb2d30f
26 COLLECT
_
GCC
_
OPTIONS=’-v’ ’-o’ ’simple’
27 /usr/local/src/git/sunflower-simulator-github-clone/tools/superH/lib/gcc/sh-elf/8.2.0/../../../../sh-elf/bin/
as -v -big -o /tmp/ccEwASy4.o /tmp/ccpqQ3U8.s
28 GNU assembler version 2.29.1 (sh-elf) using BFD version (GNU Binutils) 2.29.1
29 COMPILER
_
PATH=/usr/local/src/git/sunflower-simulator-github-clone/tools/superH/libexec/gcc/sh-elf/8.2.0/:/usr/
local/src/git/sunflower-simulator-github-clone/tools/superH/libexec/gcc/sh-elf/8.2.0/:/usr/local/src/git/
sunflower-simulator-github-clone/tools/superH/libexec/gcc/sh-elf/:/usr/local/src/git/sunflower-simulator-
github-clone/tools/superH/lib/gcc/sh-elf/8.2.0/:/usr/local/src/git/sunflower-simulator-github-clone/tools/
superH/lib/gcc/sh-elf/:/usr/local/src/git/sunflower-simulator-github-clone/tools/superH/lib/gcc/sh-elf
/8.2.0/../../../../sh-elf/bin/
30 LIBRARY
_
PATH=/usr/local/src/git/sunflower-simulator-github-clone/tools/superH/lib/gcc/sh-elf/8.2.0/:/usr/local/
src/git/sunflower-simulator-github-clone/tools/superH/lib/gcc/sh-elf/8.2.0/../../../../sh-elf/lib/
31 COLLECT
_
GCC
_
OPTIONS=’-v’ ’-o’ ’simple’
32 /usr/local/src/git/sunflower-simulator-github-clone/tools/superH/libexec/gcc/sh-elf/8.2.0/collect2 -m shelf -o
simple /usr/local/src/git/sunflower-simulator-github-clone/tools/superH/lib/gcc/sh-elf/8.2.0/crt1.o /usr/
local/src/git/sunflower-simulator-github-clone/tools/superH/lib/gcc/sh-elf/8.2.0/crti.o /usr/local/src/git
/sunflower-simulator-github-clone/tools/superH/lib/gcc/sh-elf/8.2.0/crtbegin.o -L/usr/local/src/git/
sunflower-simulator-github-clone/tools/superH/lib/gcc/sh-elf/8.2.0 -L/usr/local/src/git/sunflower-
simulator-github-clone/tools/superH/lib/gcc/sh-elf/8.2.0/../../../../sh-elf/lib /tmp/ccEwASy4.o -lgcc -
lgcc-Os-4-200 -lc -lgcc -lgcc-Os-4-200 /usr/local/src/git/sunflower-simulator-github-clone/tools/superH/
lib/gcc/sh-elf/8.2.0/crtend.o /usr/local/src/git/sunflower-simulator-github-clone/tools/superH/lib/gcc/sh-
elf/8.2.0/crtn.o
33 COLLECT
_
GCC
_
OPTIONS=’-v’ ’-o’ ’simple’
In the GCC open source tools, the assembler and linker have traditionally
been part of a separate open-source project called Binutils.
Executables are, as their names suggest, files that can be executed (e.g.,
from the command line). As such, they need to obey certain conventions
so that the executor (typically the shell or some other program that invokes
an exec system call) can execute them. For example, where in the stream
of bytes in an executable do instructions start? Where are constants stored?
How can constants be distinguished from instructions? and so on. On POSIX-
compliant operating systems, these conventions are defined by the exec sys-
tem call.
Every executable has at least three main sections: the .text, .data, and
.bss sections. These sections are sometimes also referred to as the text,
data, and bss sections or the TEXT, DATA, and BSS sections. The .text section
contains executable instructions, the .data section contains initialized data
(e.g., global constants), and the .bss section contain uninitialized data
14
. The
14
BSS is a historical term
that stands for block started
by symbol.
basic task of a program or operating system that loads and runs an executable
is to load all three sections into the memory of a processor, (optionally) zero
out the .bss section, and then jump to the beginning of the .text section and
start executing the instructions encountered there.
There are several file formats for object files and executables. These file
formats define how, e.g., the .text, .data, and .bss sections can be found
foundations of embedded systems 23
in an object file. They typically also contain additional information such
as information needed by debuggers. Examples of some object file formats
include the ELF format, the COFF format, and the a.out format.
In addition to these object file formats, there are several (simpler) file for-
mats used in embedded systems to represent sequences of bytes that can be
loaded directly into a processor’s memory for execution. These formats in-
clude the Motorola S-record format (SREC), and the Hex format. S-record files
are human-readable text files. Each line in an S-record file begins with an
S (hence the name) followed by a number of characters indicating the type
of information encoded in the line, the memory address to which any subse-
quent data field should be loaded, and a data field, all encoded as plaintext
15
.
15
You can find a succinct
illustration of the S-record
format
here.
As a result of this simple line-based text structure, S-record files can be de-
coded incrementally by a very small program (e.g., a bootloader) running on a
microcontroller.
4.11 Linker Command / Script Files
Most linkers allow you to specify what is usually referred to as a linker script
file or linker command file. The linker script file provides the linker with in-
structions on how to layout the .text, .data, and .bss sections in memory
(e.g., at what memory addresses and how any alignment on word boundaries
should be enforced). The listing below shows a basic linker script file:
1 OUTPUT
_
FORMAT("elf32-littlearm")
2 SECTIONS
3 {
4 .text . :
5 {
6
_
text = . ;
7
*
(.text)
8
*
(.strings)
9
_
etext = . ;
10 }
11 .data . :
12 {
13
_
data = . ;
14
*
(.data)
15
_
edata = . ;
16 }
17 .bss . :
18 {
19
_
bss = . ;
20
*
(.bss)
21
*
(COMMON)
22
_
ebss = . ;
23
_
end = . ;
24 }
25 }
This linker script instructs the linker to place the .text, .data, and .bss
24 phillip stanley-marbell
sections in the binary in that order. The linker script also instructs the linker
to insert the symbols
_
text,
_
etext,
_
data,
_
edata,
_
bss, and
_
ebss at the
start and end of the .text, .data, and .bss sections, respectively. These
symbols are useful because they can be referred to by a C program linked
using this linker script file, thereby allowing a program to introspect its own
memory layout. Such a capability is invaluable for programs running directly
over the processor without an operating system and is also invaluable in
implementing embedded operating systems of your own.
On line 6, the linker script instructs the linker to place the symbol
_
text
at the start the start of the .text section
16
so that the address of the symbol
16
That is, before all the
symbols destined for the
.text section, indicated on
line 7 with
*
(.text).
(which can be taken by a C program using the construct &text) will indicate
the start of the .text section. Similarly, on line 9, the linker script instructs
the linker to place the symbol
_
etext at the end of the .text section. In both
cases, the linker script gives the location implicitly as .
17
.
17
“.”, in the world of
Unix (and computing in
general), is often used as
a shorthand to indicate
“here”.
For example, a C program can take the address of the symbol bss to obtain
the memory address of the start of the BSS memor y section. Because the
BSS memory section holds uninitialized data, it is often desired to zero it
out at firmware initialization. Being able to know the start and end of the
BSS memory section makes this possible. The following routine, taken from
an implementation of an embedded operating system, zeroes out the BSS
memory section:
1 void
2 bssinit(void)
3 {
4 long
*
dst;
5
6 for (dst=&bss; dst<&ebss; dst++)
7 {
8
*
dst = 0;
9 }
10
11 return;
12 }
The example above illustrates one important peculiarity of referring to sym-
bols defined by the linker or assembler, from C: to reference symbols defined
in the assembler or linker from C, those symbols must have been defined
with a leading underscore and you reference them by removing one leading
underscore. Thus, in the example above, the symbol
_
ebss defined in the
linker script file is referenced in C code as ebss. We will discuss linking to
assembly sources and symbol referencing between C and assembler again in
Section
4.19.
foundations of embedded systems 25
4.12 What #include Does: the Preprocessor
The #include statement is not a part of the C language. Rather, it is what
is known as a preprocessor directive. In traditional C compilers, before the
compiler received code to be compiled, that code would have been processed
by a separate program, the C preprocessor (
cpp). The C preprocessor recog-
nizes a number of directives such as #include, #define, #ifdef, and so on.
The task performed by the C preprocessor is to perform textual replacement of
these preprocessor directives: a #include statement is replaced with the text
contained in the file referenced by the #include statement, a #define state-
ment causes the preprocessor to textually substitute occurrences of the string
defined with the string’s definition given in the second part of the #define
statement, and so on.
It is accepted convention within the circles of people who created the C
programming language, those who have worked closely with the language’s
creators, and the like, that #include statements should only be used to in-
clude header files (.h files). Similarly, it is convention that header files should
in turn only contain data structure definitions and function prototypes and
should never contain the definitions of function bodies or complete programs.
Many of the tools and libraries that process C programs implicitly assume
this convention is kept.
4.13 Memory Map, Stack, Heap, and Dynamic Memory Allocation
During program execution, data in a program (e.g., variables, arrays, struc-
tures) reside either in the processor’s registers (if they fit within the proces-
sor’s word size and if there are unoccupied registers available) or in memory
(RAM).
Most microcontrollers used in embedded systems have some amount of
RAM integrated into the processor. Usually, this on-chip RAM is static
random-access memory (SRAM). For example, the KL03 microcontroller used
in this course has 2 kB of SRAM and the KL05 microcontroller used in this
course has 4 kB of SRAM. The KL03 and KL05 also have 32 kB of non-volatile
Flash memory which we will use primarily for code storage. In this course,
we will refer to this non-volatile flash memory simply as “Flash” and will
refer to the volatile SRAM simply as “RAM” or “memory”.
Processors and microcontrollers typically reserve regions of memory for a
number of distinct purposes. We will refer to the logical way in which the
set of memory addresses that are available given the amount of memory in a
processor, as the memory map.
The memory map of a program indicates where the program is located in
memory when loaded and how the different regions of memory accessible
26 phillip stanley-marbell
by a program are laid out. Typically, this is just an extension of the layout
indicated by the linker script file implicitly used by the linker or explicitly
specified by a programmer in compiling a program.
Figure
4.2 shows a generic memory map. Figure 4.3 illustrates the memory
map of the Hitachi SH version of the architecture modeled by the Sunflower
emulator. The base of the address space is at memory address 0x0800 0000.
The region of memory beginning at address 0x0800 0600 contains the inter-
rupt vector base. On the occurrence of an interrupt (a hardware-generated
exceptional condition) or exception (a software-generated exceptional condi-
tion), the processor starts fetching instructions at this address, and code in
this region of memory is executed. Code at the interrupt vector base address
must perform necessary saving of register state, determine the actual cause of
the exceptional condition (i.e., the type of interrupt or exception raised) and
call the appropriate routines to handle the condition. The type of interrupt or
exception is determined by reading the EXCP
_
INTEVT or EXCP
_
EXPEVT memory
mapped registers in the Hitachi SH architecture, respectively.
PC + 0: addiu sp,sp,-56
PC + 4: sw s0,24(sp)
PC + 8: move s0,a0
PC + c: lui a0,0x0
PC + 10: sw s1,28(sp)
PC + 14: addiu a0,a0,0
PC + 18: li a2,16
PC + 1c: addiu v0,sp,16
PC + 20: move s1,a1
PC + 24: lui a1,0xfff4
PC + 28: sw ra,40(sp)
PC + 2c: sw s3,36(sp)
PC + 30: sw s2,32(sp)
PC + 34: swc1 $f21,48(sp)
PC + 38: swc1 $f20,52(sp)
...
PC =
Interrupt Vector Table
Save registers
...
Handle interrupt
...
Restore registers
Interrupt
(e.g., network,
battery, timer,
failure, etc.)
Memory-Mapped
Registers
Main Memory
Interrupt Vector Table
(a) Example memory map.
(b) Example flow of instruction execution in the
presence of interrupts from modeled peripherals.
Heap
Stack
.text, .data, .bss
Figure 4.2: A generic
memory map.
Figure 4.3: Example mem-
ory map: Memory map of
the Hitachi SH machine
model in the Sunflower
simulator.
Memory-mapped registers in the Hitachi SH are mapped to a separate
region of memory starting at 0xFFFF 0000 and ending at 0xFFFF FFF0. The
manner in which such memory mapped registers are accessed is described
in Section 4.17.
The region of memory from 0x0800 1000 upwards is used for application
memory. In Figure 4.3, the region of memory above 0x0800 3000 is used
foundations of embedded systems 27
to hold general application code, followed by the application heap (grow-
ing upwards from the end of the application code) and the stack growing
downwards from the top of memory. The region of memory occupied by an
application, is further broken down into regions for code (text), initialized
data (data) and uninitialized data or bss.
4.13.1 The stack and the heap
During execution, in addition to its machine registers, a processor executing
a program will use memory
18
to hold data that could not fit into registers
19
.
18
In the case of the KL03
and KL05, SRAM.
19
Every processor has a
limited number of registers,
typically between 16 and
32.
When the data stored in memory are inherently temporary in nature, it
is useful to have a mechanism to easily decide where in memory to store
this data. One example of inherently temporary data are local variables in a
function: Once the function returns, they are no longer valid. The standard
mechanism for handling such temporary memory is for the processor to treat
part of memory as a stack. However, unlike a stack in the colloquial sense, a
program’s stack grows downwards, from higher memory addresses to lower
memory addresses. The convention is to use the highest address in the region
of memory a program can access
20
as the starting point of the stack and to
20
That is, the program’s
address space.
grow the stack downwards from there.
For data that is long-living and which is not limited in scope to a specific
function, it is useful to have another region of memory for storing data. The
standard mechanism is to reserve a region of memory for storing long-lived
data. This region of memory is called the heap. By convention, the heap
starts in memory right after the end of the .text, .data, and .bss sections
and grows upwards towards the stack. Unlike the stack, the heap is not
necessarily organized temporally and it might have holes.
Figure
4.3 shows an example address space, with the stack growing down-
wards from the address 0x80FFFFF and the heap growing upwards from the
address just above the end of the region of executable code in memory.
On each C function call, the code generated by most compilers will allo-
cate a new region of the stack for local variables, growing the stack further
downwards. Programs that have a very long chain of function calls (e.g., as a
result of recursion) can therefore in principle have a stack that overflows into
the heap or into the application code region in Figure
4.3. Appendix C de-
scribes how to control the default size of the stack and heap when compiling
programs for the KL03 and KL05.
4.13.2 Dynamic memory allocation with malloc, calloc, and alloca
You can statically-allocate long-lived data structures by defining them as
global variables. To dynamically allocate non-temporary data structures, you
28 phillip stanley-marbell
use the malloc and calloc C librar y calls. You can find the details of the
syntax of malloc and calloc in Section 16.1 of the fifth edition of Harbison
and Steele.
21
21
Steele and Harbison
2007.
In working with the KL03 and KL05, we will almost never dynamically
allocate memory. In many deeply-embedded systems, you will have so little
memory to work with that you will rarely (if ever) resort to using malloc and
calloc.
Finally, you can dynamically allocate memory on the stack, in the same
way malloc and calloc allow you to allocate memory on the heap. To do
this, you use the C library function alloca.
The program’s dynamically allocated memory will typically grow up-
wards from the top of the .bss section, and the program’s stack will typically
grow downwards from an address setup either by
crt0.o
22
, or from an ad-
22
The C RunTime 0 which is
low-level initialization code
that sets up the stack and
calls main.
dress explicitly set by the executable (or by the operating system) at the point
at which the program begins executing.
4.14 Debugging Information
In addition to executable code, an object file or an executable file may con-
tain additional infor mation to assist debuggers. There are several standard
formats for such debugging information, including the DWARF debugging
format
23
and the STABS debugging format.
24
23
Eager et al.
2007.
24
Menapace, Kingdon, and
MacKenzie
1992.
4.15 Anatomy of Four C Programs
To see what goes into running a C program on an embedded system, we will
use the Sunflower embedded system emulator
25,26
and its associated tools.
25
Stanley-Marbell and
Hsiao
2001.
26
Stanley-Marbell and
Marculescu
2007.
Sunflower is a program, analogous to VirtualBox or VMWare, which mod-
els an entire embedded system platform (see Question 50, Question 51, and
Question 52 in the quiz of Section 4.1.5 above). Sunflower implements, in
software, a complete processor, memory, peripherals such as communication
interfaces and sensors, the (analog) physical signals to be sensed by sensors,
the power dissipation of computation and its dependence on supply voltage
(Equation
1.1 in Section 1.4), the interdependence between maximum clock
frequency and supply voltage (Equation 1.2 in Section 1.4), the power dissi-
pation of communication interfaces, the electrochemical properties of batter-
ies, and more. You can compile any C/C++ program and load the binary
into Sunflower for execution. Sunflower implements processors for multiple
processor architectures. In the examples below, we will use its Hitachi SH
processor support.
foundations of embedded systems 29
4.15.1 Program 1: A simple C program with a function named main
The directory
benchmarks/source/SimpleMainExample/ in the Sunflower GitHub
repository contains the source code and additional files for a simple example.
The file
simple.c contains a simple C program:
1 int
2 main(void)
3 {
4 return 0;
5 }
The function main in C is special. It is the default entry point at which
code execution begins when a program is loaded by the shell or by an exec()
system call. However, when your program runs directly over the bare hard-
ware, there is no shell or operating system. The symbol
27
main is also special
27
The term symbol is
typically used to refer to
any identifier or label in a
program.
because programs containing a function called main are treated differently by
the C compiler and linker.
When we run
make
28
in the directory
benchmarks/source/SimpleMainExample/,
28
If running this in your
clone of your fork of the
Sunflower repository on
the coursework server,
and you have not gone
through the process of
configuring and compiling
your complete Sunflower
repository, you can to
use the command
make
TREEROOT=/usr/local/src/git/sunflower-simulator-github-clone
to use the pre-installed
Sunflower elf-format compiler
tools on the coursework
server.
the following is executed:
sh-elf-gcc -E init.S > init.i; sh-elf-as init.i -o init.o
sh-elf-gcc -gstabs3 -O0 -c simple.c
sh-elf-ld -Ttext 0x08004000 -TsuperH.ld -Map simple.map init.o simple.o -o simple
sh-elf-objcopy -O srec simple simple.sr
The file benchmarks/source/SimpleMainExample/init.S contains assem-
bly language code which initializes the processor’s status register and sets up
the processor’s stack pointer register. When C programs are compiled to ma-
chine code, those variables are either assigned to machine registers, assigned
to offsets from the top of the stack, or assigned to other locations in the pro-
gram’s memory map as introduced in Section
4.13.1 and detailed further in
Section
4.16 below. The low-level initialization code in init.S therefore needs
to setup the processor’s stack pointer to point to a valid region of memory.
The command sh-elf-gcc -E init.S > init.i invokes the C preprocessor
to process the #include preprocessor directive in the file
init.S, and places
the preprocessed output in the file init.i. The file init.i is then assembled
(converted into an object file) by the assembler (sh-elf-as). Similarly, the
command
sh-elf-gcc compiles simple.c into an object file. In the penulti-
mate step, the linker,
sh-elf-ld, links the object files init.o and simple.o.
The directive -Ttext 0x08004000 instructs the linker to setup the binary to
be loaded at address 0x0800 4000 and the directive -TsuperH.ld instructs the
linker that the objects in the binary should be laid out in memory according
to the rules defined in the linker script file
superH.ld. The directive -Map
simple.map tells the linker to output a “map” of the linked binary’s address
space into the file simple.map. In the final step, the program sh-elf-objcopy
converts the executable (linked) binary in simple, which happens to be in the
30 phillip stanley-marbell
ELF file format, into the Motorola S-record format, placing the result in the
file
simple.sr.
We can disassemble the binary in the file simple to see what instructions
and data it contains and at what memory addresses those instructions and
data will be loaded when the binary is loaded into a processor’s memory:
1 % sh-elf-objdump -d simple
2
3 simple: file format coff-sh
4
5
6 Disassembly of section .text:
7
8 08004000 <
_
text>:
9 8004000: c9 00 and #0,r0
10 8004002: 40 0e ldc r0,sr
11 8004004: df 02 mov.l 8004010 <stack
_
addr>,r15 ! c001000
12 8004006: d0 03 mov.l 8004014 <start
_
addr>,r0 ! 800401c <
_
main>
13 8004008: 40 0b jsr @r0
14 800400a: 00 09 nop
15 800400c: e4 01 mov #1,r4
16 800400e: c3 22 trapa #34
17
18 08004010 <stack
_
addr>:
19 8004010: 0c 00 .word 0x0c00
20 8004012: 10 00 mov.l r0,@(0,r0)
21
22 08004014 <start
_
addr>:
23 8004014: 08 00 .word 0x0800
24 8004016: 40 1c shad r1,r0
25
26 08004018 <
___
errno>:
27 8004018: 00 00 .word 0x0000
28 800401a: 00 00 .word 0x0000
29
30 0800401c <
_
main>:
31 800401c: 2f e6 mov.l r14,@-r15
32 800401e: 6e f3 mov r15,r14
33 8004020: e1 00 mov #0,r1
34 8004022: 60 13 mov r1,r0
35 8004024: 6f e3 mov r14,r15
36 8004026: 6e f6 mov.l @r15+,r14
37 8004028: 00 0b rts
38 800402a: 00 09 nop
39
40 0800402c <
_
etext>:
41 800402c: 00 00 .word 0x0000
42 800402e: 00 00 .word 0x0000
The output of
sh-elf-objdump is a total of 41 lines. We see from the dis-
assembled binary that the first two instructions are an AND and an LDC, and
that these will be loaded into memory at address 0x0800 4000 as we explicitly
requested in the linker command line arguments. These instructions are the
first two instructions of the assembly language file
init.S.
There is no requirement in the C language for a program to have a function
foundations of embedded systems 31
called main. It is however necessary for an operating system (or, by extension,
a command shell) to know where (e.g., at what address or offset) in a binary
to start executing and the signature
29
of the function in an executable it will
29
Recall from Section
4.2.1
that the signature of a
function is the types and
number of its arguments
and return value.
invoke. Therefore, by convention, an executable to be launched by the op-
erating system has a function called main whose signature is typically either
int main(void); or int main(int argc, char
*
argv[]);.
Including a function called main in a C program also has other side effects
which are important when compiling programs to run directly over a proces-
sor without an operating system. At the linking stage, if the compiler’s linker
detects a function called main in an object file, it links in a special object file
(often named
crt0.o
30
). The object file
crt0.o (or its equivalent) is generated
30
crt0 stands for C Run-
Time 0 and is low-level
initialization code that sets
up the stack and calls main.
from a special startup code sequence, usually in an assembly language file
(e.g.,
crt0.S). The assembly language instructions in a given system’s crt0.S
perform important tasks such as setting up the processor’s stack and frame
pointer registers before jumping to the first instruction in main.
For the GCC compiler, you can control whether or not crt0.o and other
functionality will be linked in, by either building the compiler with an ap-
propriate “specs” configuration file, or by controlling the compiler’s behavior
by specifying a specs file using the flag -specs=specs-file-name. In the exam-
ple above, the compiler,
sh-elf-gcc, is preconfigured to use a configuration
which does not link in
crt0.o. In practice, you are likely to run into C compil-
ers that are not configured this way and which treat main differently. On the
coursework server, we have also installed a version of GCC
31
that for compil-
31
It is installed as
sh-coff-gcc and its as-
sociated assembler, linker,
and object dump tools are
sh-coff-as, sh-coff-ld,
and
sh-coff-objdump,
respectively.
ing programs for Sunflower that treats the presence of main() the same way
a C compiler on your Desktop does by linking in crt0.o.
We can repeat the example above using the C compiler that behaves more
closely to C compilers on your desktop by running
make
32
in the directory
32
If running this in your
clone of your fork of the
Sunflower repository on
the coursework server,
and you have not gone
through the process of
configuring and compiling
your complete Sunflower
repository, you can to
use the command
make
TREEROOT=usr/local/src/git/sunflower-simulator-coff-build-github-clone-a1798
to use the pre-installed
alternative Sunflower
coff-format compiler tools on
the coursework server.
benchmarks/source/SimpleMainExample/:
sh-coff-gcc -E init.S > init.i; sh-coff-as init.i -o init.o
sh-coff-gcc -DM32 -Wall -gstabs3 -O0 -c simple.c
sh-coff-ld -Ttext 0x08004000 -TsuperH.ld -L/usr/local/src/git/sunflower-
simulator-coff-build-github-clone-a1798/tools/tools-lib/superH -L/usr/local/
src/git/sunflower-simulator-coff-build-github-clone-a1798/sys/libOS/mOS -Map
simple.map init.o simple.o -o simple -lc -lgcc -lm -lmOS-superH
/usr/local/src/git/sunflower-simulator-coff-build-github-clone-a1798/tools/bin/
sh-coff-objcopy -O srec simple simple.sr
We can again disassemble the binary in the file simple
33
to see what instruc-
33
Remember, this is com-
piled from exactly the same
source files as the example
above.
tions and data it contains and at what memory addresses those instructions
and data will be loaded when the binary is loaded into a processor’s memory:
1 % sh-coff-objdump -d simple
2
3 simple: file format coff-sh
4
5 Disassembly of section .text:
32 phillip stanley-marbell
6
7 0000000008004000 <
_
text>:
8 8004000: c9 00 and #0,r0
9 8004002: 40 0e ldc r0,sr
10
11 ... 152 lines deleted ...
12
13 80040ee: 00 09 nop
The output of
sh-coff-objdump this time is a total of 160 lines. Here, GCC
detects a function named main. Because it has not been told to behave differ-
ently by its default specs, it links in a function named
__
main from object file
__
main.o
in the library libgcc.a. This in turn expects to be linked against
the symbol atexit which the linker finds in the object file atexit.o from the
library
libmOS-superH.a.
As described above, he objective of GCC linking in these additional pieces
of code is GCC’s expectation that, given the special nature of the function
main (the main entry point of a C program), it needs to appropriately setup
the processor’s stack pointer before execution and to appropriately cleanup
after the function main exits.
4.15.2 Program 2: A simple C program with no main
To see the difference the name of the function in the body of the C pro-
gram has, for our example on the Hitachi SH architecture, we compile a
program that does not contain the function main to an object file. We will
then disassemble that object file and the assembly code we get is very dif-
ferent from the assembly code we get if the program contains a function
called main for the second version of the compiler (
sh-coff-gcc). The di-
rectory
benchmarks/source/SimpleNoMainExample/ in the Sunflower GitHub
repository contains the source code and additional files for a simple example
without a function named main.
1 int
2 noMain(void)
3 {
4 return 0;
5 }
Compiling this just as we did for the previous example and disassembling
this binary, the output of
sh-elf-objdump is now just of 38 lines, both for
sh-coff-gcc as well as for sh-elf-gcc:
1 % sh-elf-objdump -d simple
2
3 simple: file format coff-sh
4
5 Disassembly of section .text:
6
7 0000000008004000 <
_
text>:
foundations of embedded systems 33
8 8004000: c9 00 and #0,r0
9 8004002: 40 0e ldc r0,sr
10 8004004: df 02 mov.l 8004010 <stack
_
addr>,r15 ! 0xc001000
11 8004006: d0 03 mov.l 8004014 <start
_
addr>,r0 ! 0x8004020 <
_
noMain>
12 8004008: 40 0b jsr @r0
13 800400a: 00 09 nop
14 800400c: e4 01 mov #1,r4
15 800400e: c3 22 trapa #34
16
17 0000000008004010 <stack
_
addr>:
18 8004010: 0c 00 .word 0x0c00
19 8004012: 10 00 mov.l r0,@(0,r0)
20
21 0000000008004014 <start
_
addr>:
22 8004014: 08 00 .word 0x0800
23 8004016: 40 20 shal r0
24
25 0000000008004018 <
___
errno>:
26 8004018: 00 00 .word 0x0000
27 800401a: 00 00 .word 0x0000
28 800401c: 00 09 nop
29 800401e: 00 09 nop
30
31 0000000008004020 <
_
noMain>:
32 8004020: 2f e6 mov.l r14,@-r15
33 8004022: 6e f3 mov r15,r14
34 8004024: e1 00 mov #0,r1
35 8004026: 60 13 mov r1,r0
36 8004028: 6f e3 mov r14,r15
37 800402a: 6e f6 mov.l @r15+,r14
38 800402c: 00 0b rts
39 800402e: 00 09 nop
If you try compiling this same program, this time not using the cross-
compiler, but instead using the host compiler, the compilation will fail:
1 SimpleNoMainExample$ gcc simple.c
2 /usr/lib/gcc/x86
_
64-linux-gnu/5/../../../x86
_
64-linux-gnu/crt1.o: In function ‘
_
start’:
3 (.text+0x20): undefined reference to ‘main’
4 collect2: error: ld returned 1 exit status
This is because the version of GCC on the host machine will by default always
link in the C runtime initialization library
34
. As a result, there will be a
34
Unless overridden with a
specs file.
reference to the symbol main which will be missing when the linker (
ld)
attempts to link together the object files to generate the final binary.
4.15.3 Program 3: Program with only main and #include <stdio.h>
The directory
benchmarks/source/SimpleMainStdioExample/ contains the source,
makefile, and other associated files for the program below:
1 #include <stdio.h>
2
3 int
4 main(void)
34 phillip stanley-marbell
5 {
6 return 0;
7 }
Compiling and disassembling the program above, we obtain the same output
as for the example in Section
4.15.1. The preprocessor directive #include
<stdio.h> only causes the definition of function prototypes and constants. It
does not cause the inclusion of any source code.
4.15.4 Program 4: A “Hello, World” program with a function named main
The directory benchmarks/source/HelloWorld/ contains the source code and
associated files for the example below:
1 #include <stdio.h>
2
3 int
4 main(void)
5 {
6 printf("Hello, World\n");
7
8 return 0;
9 }
In the example above, we have added a call to the standard C library function
printf to the example of Section
4.15.3. If you compile, link, and then disas-
semble the resulting binary, you will observe that there are now 5039 lines in
the output (4124 lines when using the COFF version of the tools). To deter-
mine exactly what object files are linked into a binary, Section
4.16 describes
how we can use map files. Map files are output by the linker when it is invoked
with the flag -Map followed by a file into which to place the generated map.
Unlike linker script files which are inputs to the compiler and linker and give
the linker additional instructions for what to do during linking, map files are
outputs by the linker and describe what the linker did during linking.
4.16 Program Variables, Object Addresses, Memory Locations, Ma-
chine Registers, and Debugging Information
Variables defined global to the scope of a C program are either stored in the program’s .data section
if they are initialized with values in the source program, or in the .bss section of the binary if they
are global uninitialized variables. We can determine the memory locations of all objects in a compiled
program by looking at its map file. For example, for the C program in the Sunflower Github repository
in the directory
benchmarks/source/SimpleNoMainMemoryObjectsExample/,
1 int gInitializedInt = 42;
2 int gUninitializedInt;
3
4 int
5 noMain(void)
foundations of embedded systems 35
6 {
7 int i, localInt;
8
9 for (i = 0; i < 10; i++) {
10 localInt += gInitializedInt + gUninitializedInt;
11 }
12
13 return 0;
14 }
We can compile, link, and instruct the linker to generate a map file as we did
in the examples above. The resulting map file output by the linker for this
example is:
1 Allocating common symbols
2 Common symbol size file
3
4 gUninitializedInt 0x4 simple.o
5
6 Memory Configuration
7
8 Name Origin Length Attributes
9
*
default
*
0x0000000000000000 0xffffffffffffffff
10
11 Linker script and memory map
12
13 Address of section .text set to 0x8004000
14
15 .text 0x0000000008004000 0x80
16 0x0000000008004000
_
text = .
17
*
(.text)
18 .text 0x0000000008004000 0x1c init.o
19 0x0000000008004018
__
errno
20 .text 0x000000000800401c 0x5c simple.o
21 0x000000000800401c noMain
22 0x0000000008004078
_
etext = .
23
24 .data 0x0000000008004078 0x10
25 0x0000000008004078
_
data = .
26
*
(.data)
27 .data 0x0000000008004078 0x0 init.o
28 .data 0x0000000008004078 0x4 simple.o
29 0x0000000008004078 gInitializedInt
30 0x000000000800407c
_
edata = .
31
32 .bss 0x000000000800407c 0x4
33 0x000000000800407c
_
bss = .
34
*
(.bss)
35 .bss 0x000000000800407c 0x0 init.o
36 .bss 0x000000000800407c 0x0 simple.o
37 0x000000000800407c
_
ebss = .
38 0x000000000800407c
_
end = .
39 COMMON 0x000000000800407c 0x4 simple.o
40 0x000000000800407c gUninitializedInt
41 LOAD init.o
42 LOAD simple.o
43 OUTPUT(simple coff-sh)
44
36 phillip stanley-marbell
45 .stab 0x0000000000000000 0x1e0
46 .stab 0x0000000000000000 0x1e0 simple.o
47
48 .stabstr 0x0000000000000000 0x360
49 .stabstr 0x0000000000000000 0x355 simple.o
50
51 .comment 0x0000000000000000 0x20
52 .comment 0x0000000000000000 0x12 simple.o
For each of the sections defined in the linker script file used to create
a given object file or final linked binary (left-most column in the example
above), the map file output by the linker describes the object files, the global
symbols within those object files, and the memory addresses to which they
are mapped. For example, we see from the example above that the .text,
.data, and .bss sections of the binary for our example begin at memory
addresses 0x0800 4000, 0x0800 4078, and 0x0800 407c respectively. The global
initialized variable gInitializedInt is in the .data segment like it should be
(and that it is at address 0x0800 4078)
35
and the uninitialized global variable
35
Recall that the .data
segment is also known as
the initialized data segment.
gUninitializedInt is in the .bss segment like it should be (and that it is at
address 0x0800 407c)
36
.
36
Recall that the .bss
segment is also known
as the uninitialized data
segment.
The map file provides information about all the symbols in a binary and
is useful to determine what objects the linker linked into a given executable
as well as to determine what symbols are visible outside of the scope of an
object file during the linking process.
The map file however does not provide us any information about automatic
variables: Variables allocated by the compiler to registers or allocated space on
the stack. To figure out whether variables in a given program are allocated to
registers, allocated on the stack, and to what registers or at what offset on the
stack they are allocated, we can use information generated by the compiler
to aid debugging.
There are several debugging formats in use today. These include the
DWARF format,
37
the STABS
38
format, and several others. Most program-
37
Eager et al.
2007.
38
Menapace, Kingdon, and
MacKenzie
1992.
mers never manually extract the debugging information from their binaries
but instead indirectly benefit from the debugging information generated by
compilers, by using a debugger such as
gdb or lldb.
The format in which debugging information is stored in binaries is not
generally well documented. One debugging format which is reasonably well
documented is the STABS format. The example in benchmarks/source/SimpleNoMainMemoryObjectsExample/
of the Sunflower GitHub repository is setup to dump debugging information
from the final linked binary into a human-readable text file. It does so us-
ing the sh-elf-objdump command which we used previously to convert the
linked binary into an S-record file. To extract the STABS debugging informa-
tion from the binary for our example, we invoke
sh-elf-objdump with the
flag -G:
foundations of embedded systems 37
1 % sh-elf-objdump -G simple
2
3 simple: file format coff-sh
4
5 Contents of .stab section:
6
7 Symnum n
_
type n
_
othr n
_
desc n
_
value n
_
strx String
8
9 -1 HdrSym 0 39 00000355 1
10 0 SO 0 2 0800401c 10 simple.c
11 1 OPT 0 0 00000000 19 gcc2
_
compiled.
12 2 LSYM 0 0 00000000 34 int:t(0,1)=r(0,1);-2147483648;2147483647;
13 3 LSYM 0 0 00000000 76 char:t(0,2)=r(0,2);0;127;
14 4 LSYM 0 0 00000000 102 long int:t(0,3)=r(0,3);-2147483648;2147483647;
15 5 LSYM 0 0 00000000 149 unsigned int:t(0,4)=r(0,4);0;4294967295;
16 6 LSYM 0 0 00000000 190 long unsigned int:t(0,5)=r(0,5);0;4294967295;
17 7 LSYM 0 0 00000000 236
__
int128:t(0,6)=r(0,6);0;-1;
18 8 LSYM 0 0 00000000 265
__
int128 unsigned:t(0,7)=r(0,7);0;-1;
19 9 LSYM 0 0 00000000 303 long long int:t(0,8)=r(0,8);-9223372036854775808;9223372036854775807;
20 10 LSYM 0 0 00000000 373 long long unsigned int:t(0,9)=r(0,9);0;-1;
21 11 LSYM 0 0 00000000 416 short int:t(0,10)=r(0,10);-32768;32767;
22 12 LSYM 0 0 00000000 456 short unsigned int:t(0,11)=r(0,11);0;65535;
23 13 LSYM 0 0 00000000 500 signed char:t(0,12)=r(0,12);-128;127;
24 14 LSYM 0 0 00000000 538 unsigned char:t(0,13)=r(0,13);0;255;
25 15 LSYM 0 0 00000000 575 float:t(0,14)=r(0,1);4;0;
26 16 LSYM 0 0 00000000 601 double:t(0,15)=r(0,1);8;0;
27 17 LSYM 0 0 00000000 628 long double:t(0,16)=r(0,1);8;0;
28 18 LSYM 0 0 00000000 660
_
Float32:t(0,17)=r(0,1);4;0;
29 19 LSYM 0 0 00000000 689
_
Float64:t(0,18)=r(0,1);8;0;
30 20 LSYM 0 0 00000000 718
_
Float32x:t(0,19)=r(0,1);8;0;
31 21 LSYM 0 0 00000000 748 void:t(0,20)=(0,20)
32 22 GSYM 0 0 00000000 768 gUninitializedInt:G(0,1)
33 23 GSYM 0 0 00000000 793 gInitializedInt:G(0,1)
34 24 FUN 0 0 0800401c 816 noMain:F(0,1)
35 25 SLINE 0 6 00000000 0
36 26 SLINE 0 9 00000006 0
37 27 SLINE 0 9 0000000e 0
38 28 SLINE 0 11 00000012 0
39 29 SLINE 0 11 0000001c 0
40 30 SLINE 0 9 0000002a 0
41 31 SLINE 0 9 00000038 0
42 32 SLINE 0 14 00000044 0
43 33 SLINE 0 15 00000046 0
44 34 LSYM 0 0 00000004 830 i:(0,1)
45 35 LSYM 0 0 00000000 838 localInt:(0,1)
46 36 LBRAC 0 0 00000000 0
47 37 RBRAC 0 0 0000005c 0
48 38 SO 0 0 08004078 0
The second column of the output indicates the type of symbol described by
a given row (line) in the output. For example, FUN indicates a function defi-
nition, in this case, indicating the function noMain. The fifth column contains
information specific to the symbol type (in the case of noMain, its location in
the address space of the program when the program is running); for the local
variable localInt, this column indicates the offset from the frame pointer on
38 phillip stanley-marbell
the stack, at which storage for the variable is allocated.
The Sunflower emulator can decode the output of
sh-elf-objdump -G. By
supplying Sunflower with both a binar y in S-record format as well as the
STABS debugging output report, Sunflower can generate a report of the his-
tory of all accesses to any program variable, whether that variable is on the
stack, in a register, or in the .data or .bss segments:
1 % sf
2 Initialized random number generator with seed -548296926...
3
4 Sunflower 1.1 (build 10-12-2018-12:38:22-pip@physcomplab-cpu0-Linux)
5 Authored, 1999-2018, by Phillip Stanley-Marbell <phillip.stanleymarbell@gmail.com>
6 Public key fingerprint 62A1 E95D 304D 9876 D5B1 1FB2 BF7E B65F BD89 20AB
7 This software is provided with ABSOLUTELY NO WARRANTY. Read LICENSE.txt
8
9 New node created with node ID 0
10
11 [ID=0 of 1][PC=0x8000000][3.3E+00V, 6.0E+01MHz] sizemem 96000000
12 Set memory size to 93750 Kilobytes
13 [ID=0 of 1][PC=0x8000000][3.3E+00V, 6.0E+01MHz] srecl simple.sr
14 Loading S-RECORD to memory at address 0x8004000
15 [M] Done.
16 [ID=0 of 1][PC=0x8004000][3.3E+00V, 6.0E+01MHz] registerstabs simple.stabs
17 Global variable "gUninitializedInt" in global scope, typename "int", ptr=0
18 Global variable "gInitializedInt" in global scope, typename "int", ptr=0
19 Stack var "i" in fn scope, typename "int", fn start=0x800401c, frame offset=4, ptr=0
20 Stack var "localInt" in fn scope, typename "int", fn start=0x800401c, frame offset=0, ptr=0
21 [ID=0 of 1][PC=0x8004000][3.3E+00V, 6.0E+01MHz] run
22 args = [], argc = 0
23 R4 = [0x00000000], R5 = [0x0db7d700]
24 Running...
25
26 [ID=0 of 1][PC=0x8004000][3.3E+00V, 6.0E+01MHz] on
27 [ID=0 of 1][PC=0x8004000][3.3E+00V, 6.0E+01MHz]
28
29 NODE 0 exiting...
30 User Time elapsed = 0.000000 seconds.
31 Simulated CPU Time elapsed = 5.466667E-06 seconds.
32 Simulated Clock Cycles = 328
33 Estimated CPU-only Energy = 4.423023E-06
34
35
36
37 [ID=0 of 1][PC=0x8004016][3.3E+00V, 6.0E+01MHz] valuestats
38
39 Name: int localInt
40 PCstart: 0x800401c
41 Frame offset: 0x0
42 Size: 0x4
43 Read accesses: 11
44 Write accesses: 10
45
46 Value History: 0 42 42 84 84 126 126 168 168 210 210 252 252 294 294 336 336 378 378 420 0
47
48 Name: int i
49 PCstart: 0x800401c
foundations of embedded systems 39
50 Frame offset: 0x4
51 Size: 0x4
52 Read accesses: 21
53 Write accesses: 11
54
55 Value History: 0 0 0 1 1 1 2 2 2 3 3 3 4 4 4 5 5 5 6 6 6 7 7 7 8 8 8 9 9 9 10 10
56 [ID=0 of 1][PC=0x8004016][3.3E+00V, 6.0E+01MHz]
4.17 Memory-Mapped I/O
In most architectures, several of the processor status facilities are imple-
mented as memory-mapped registers. These are essentially addresses in the
address space which, when read, yield the value of a hardware system reg-
ister. For example, for the Hitachi SH architecture modeled in Sunflower,
the EXCP
_
INTEVT memory-mapped register is a hardware register which is
accessed by reading from memory address 0xFFFF FFD4. Typically, in most
architectures, some memory-mapped registers are byte addressed, others are
word (16-bit) addressed, and yet others are long-word (32-bit) addressed.
4.18 Compiling C programs: A recap using the ARM architecture
Many people who have written C programs and even some of those who
write and modify small or large programs as part of their daily activities do
not really know what goes on behind the scenes when those programs are
compiled and run. What does #include <stdio.h> in a C program do? Is
#include part of the C language syntax? What about main? Is main a reserved
keyword in C? Can you create an executable without a function called main?
What is the difference between an executable and an object file?
Even if you don’t write programs yourself (maybe you consider yourself a
hardware person; see the quote by Alan Kay at the start of this chapter), you
might occasionally have to compile a program and run it. What does a com-
piler do to convert a C program into an executable? What is in an executable?
What are the parts of an executable? Does everything in an executable get
loaded into the processor to run? What are some of the different executable
and object file formats? What tools can you use to convert between different
object file formats? What is the difference between an executable file and an
object file? What is a
Makefile? What is the difference between make, gmake,
and cmake?
39
What is a
configure script?
39
And there’s also mk
When writing software that runs directly on a processor without the in-
termediation of an operating system (often referred to as “running over bare
metal”), you invariably need to know the answers to all the above questions.
In addition, you will need to be intimately familiar with the answers to many
more related questions, such as: What is the memory map of a binary? How
40 phillip stanley-marbell
can you control the memory map of a binary? What command line tools can
you use to inspect the memory map of a binary?
We can illustrate the answers to the above questions with an example. For
the remainder of the chapter, we will switch from using the Hitachi SH in
our examples, to using the ARM Cortex-M0. As you will see, all of the ideas
presented so far using the Hitachi SH carry over directly to the ARM with
only a change of the name prefix of the tools we invoke from
sh-elf- to
arm-none-eabi-. Consider the following C program in the file simple.c:
1 int
2 main(void)
3 {
4 return 0;
5 }
We compile it from the command line using gcc for the ARM architecture
(i.e., arm-none-eabi-gcc):
1 % arm-none-eabi-gcc -c simple.c
The result is the file simple.o. The file simple.o is an object file. As we
saw previously in Section
4.2, an object file contains the compiled code (ma-
chine instructions) for a given source file, but does not contain any code for
libraries that might be referenced from the source file. We can use the utility
arm-none-eabi-objdump to view the contents of the object file simple.o:
1 % arm-none-eabi-objdump -d simple.o
2
3 main.o: file format elf32-littlearm
4
5
6 Disassembly of section .text:
7
8 00000000 <main>:
9 0: e52db004 push {fp} ; (str fp, [sp, #-4]!)
10 4: e28db000 add fp, sp, #0
11 8: e3a03000 mov r3, #0
12 c: e1a00003 mov r0, r3
13 10: e24bd000 sub sp, fp, #0
14 14: e49db004 pop {fp} ; (ldr fp, [sp], #4)
15 18: e12fff1e bx lr
The values on the left hand side of the output (before each colon) are
addresses, and the rest of each line is a disassembly of the binary machine
code contained in the object file at the given address. Thus, for example,
at address 0x18 (the last address) in the object file, there is a bx lr (branch
exchange) machine instruction.
What would the object file look like if we included one of the typical stan-
dard header files such as stdio.h? For the following modified
simple.c
1 #include <stdio.h>
2
foundations of embedded systems 41
3 int
4 main(void)
5 {
6 return 0;
7 }
if we again compile using
arm-none-eabi-gcc, and dump the object file con-
tents using
arm-none-eabi-objdump,
1 % arm-none-eabi-gcc -c simple.c
2 % arm-none-eabi-objdump -d simple.o
3
4 main.o: file format elf32-littlearm
5
6
7 Disassembly of section .text:
8
9 00000000 <main>:
10 0: e52db004 push {fp} ; (str fp, [sp, #-4]!)
11 4: e28db000 add fp, sp, #0
12 8: e3a03000 mov r3, #0
13 c: e1a00003 mov r0, r3
14 10: e24bd000 sub sp, fp, #0
15 14: e49db004 pop {fp} ; (ldr fp, [sp], #4)
16 18: e12fff1e bx lr
the contents of the object file
simple.o are unchanged.
Header files (such as stdio.h) are intended to be used to include data
structure definitions, function prototypes, and the like. By convention, they
should never contain executable code. Standard system header files such as
stdio.h do not contain any executable program statements. So, the statement
#include <stdio.h> does not add any code to
simple.c. It simply adds new
function prototypes, constant definitions, and the like which might be needed
if we were to have program statements that need those definitions, such as a
call to the function printf().
The object file simple.o is not an executable, i.e., we cannot run it. To ob-
tain an executable, we would have had to either compile the original
simple.c
file without the -c flag to arm-none-eabi-gcc, or we would have to link the
object file simple.o to obtain an executable. In the following example, we
also explicitly specify a linker script file
40
,
arm.ld, via the -T flag, to instruct
40
More on linker script files
in Section
4.11 below
the linker how to lay out the symbols and sections in memory:
1 % arm-none-eabi-ld -Tarm.ld -o simple simple.o
2 % arm-none-eabi-objdump -d simple
3
4 examples/simple: file format elf32-littlearm
5
6
7 Disassembly of section .text:
8
9 00000000 <main>:
10 0: e52db004 push {fp} ; (str fp, [sp, #-4]!)
11 4: e28db000 add fp, sp, #0
42 phillip stanley-marbell
12 8: e3a03000 mov r3, #0
13 c: e1a00003 mov r0, r3
14 10: e24bd000 sub sp, fp, #0
15 14: e49db004 pop {fp} ; (ldr fp, [sp], #4)
16 18: e12fff1e bx lr
What happens if we now include a call to a function in the standard I/O
library:
1 #include <stdio.h>
2
3 int
4 main(void)
5 {
6 printf("Hello\n");
7
8 return 0;
9 }
Compiling this source file to obtain a new object file:
1 % arm-none-eabi-gcc -c hello.c -o hello.o
2 % arm-none-eabi-objdump -d hello.o
3
4 examples/hello.o: file format elf32-littlearm
5
6
7 Disassembly of section .text:
8
9 00000000 <main>:
10 0: e92d4800 push {fp, lr}
11 4: e28db004 add fp, sp, #4
12 8: e59f0014 ldr r0, [pc, #20] ; 24 <main+0x24>
13 c: ebfffffe bl 0 <puts>
14 10: e3a03000 mov r3, #0
15 14: e1a00003 mov r0, r3
16 18: e24bd004 sub sp, fp, #4
17 1c: e8bd4800 pop {fp, lr}
18 20: e12fff1e bx lr
19 24: 00000000 .word 0x00000000
We can ask the compiler to link the object file to generate an executable.
In the following example, because the library which we need is the standard
C library, we do not need to explicitly specify the path to the library (for
which we would have needed to specify the linker flag
-L) or the name of the
library to be found at that path (for which we would have needed to specify
the linker flag
-l).
1 % arm-none-eabi-gcc -Tmkl25z4.ld -o hello hello.o
2 % arm-none-eabi-objdump -d hello
3
4 examples/hello: file format elf32-littlearm
5
6
7 Disassembly of section .text:
8
9 00000410 <
__
do
_
global
_
dtors
_
aux>:
foundations of embedded systems 43
10 410: e92d4010 push {r4, lr}
11 414: e59f4028 ldr r4, [pc, #40] ; 444 <
__
do
_
global
_
dtors
_
aux+0x34>
12
13 /
*
36 lines omitted
*
/
14
15 000004a4 <
_
mainCRTStartup>:
16 4a4: e59f30f0 ldr r3, [pc, #240] ; 59c <
_
mainCRTStartup+0xf8>
17 4a8: e3530000 cmp r3, #0
18 4ac: 059f30e4 ldreq r3, [pc, #228] ; 598 <
_
mainCRTStartup+0xf4>
19
20 /
*
64 lines omitted
*
/
21
22 000005b8 <main>:
23 5b8: e92d4800 push {fp, lr}
24 5bc: e28db004 add fp, sp, #4
25 5c0: e59f0014 ldr r0, [pc, #20] ; 5dc <main+0x24>
26 5c4: eb000096 bl 824 <puts>
27 5c8: e3a03000 mov r3, #0
28 5cc: e1a00003 mov r0, r3
29 5d0: e24bd004 sub sp, fp, #4
30 5d4: e8bd4800 pop {fp, lr}
31 5d8: e12fff1e bx lr
32 5dc: 000034fc .word 0x000034fc
33
34 000005e0 <exit>:
35 5e0: e92d4008 push {r3, lr}
36
37 /
*
3162 lines omitted
*
/
The output above is trimmed by 3262 lines.
4.19 Linking to Assembly Sources
You can use the -c flag to compile assembly language files into object files.
You can then link those object files together with other object files (compiled
from either C or assembler) to obtain your final binary.
The C compiler, by convention, prefixes the names of all functions in your
C source file with an underscore (
_
) when generating the object file. You do
not have functions per se in assembly, but you can name sections of assembly
language code with labels. You can then expose these labels within the ob-
ject file compiled from your assembly language source by using the assembly
language directive .global. Unlike the C compiler, the assembler does not
rename labels (or references to labels) to add an underscore. To reference a
function in your C code from your assembly language code, you therefore
need to prefix the C function’s name with
_
when referring to it from assem-
bly. Similarly, if you prefix your labels with
_
in your assembly language code
(e.g.,
_
sleep) and include the appropriate assembler directive to make the la-
bel visible in the object file, you will be able to call the corresponding block
of assembler from your C program without the underscore (e.g., sleep).
44 phillip stanley-marbell
4.20 Makefiles
The program
make provides a way to automate sequences of commands at
the command line, in particular when those sequences of commands have
dependencies between them. This concept of automating the execution of
commands that have dependencies between them is central to understanding
what
make is and how it works. The facility for automating sequences of de-
pendent programs means that make program is ideally suited for automating
the process of compiling source files into object files using a compiler like
gcc, and grouping those object files into archives or libraries using ar. The
make utility allows you to manage dependencies such as ensuring that only
those object files whose source files have been changed need to be rebuilt,
and so on. Every makefile has two main kinds of entries or constituent lines:
definitions and rules.
Definition statements introduce synonym variables for constants or ex-
pressions that are needed repeatedly inside the rest of the makefile. Typi-
cally, these definitions are all uppercase. So, for example, to define the set of
warning flags that should be used in all C c ompiler invocations, a makefile
might contain the following definition statement:
1 WFLAGS = -Wall -Werror
Once a variable (e.g., WFLAGS) has been defined, it can be referred to using the
variable name in parenthesis preceded by a $ (e.g., $(WFLAGS)).
41
41
There are a number of
different variations on
how to refer to variables
in makefiles depending
on which version of the
implementation of
make
you are using. The two
common version of
make
are the BSD make and the
GNU
make (also called
gmake). The program cmake
on the other hand is a
different thing altogether.
Rules in makefiles consist of three parts: (1) a target name, followed by a
colon, followed by (2) a list of dependent targets. On the lines which follow,
which must each be offset with a tab (not with spaces), are commands to be ex-
ecuted to generate the target of the rule. Thus, for example, the following
simple makefile will build the executable
simple by first building the ob-
ject files simple.o and then invoking the linker to link simple.o to yield the
executable.
1 TARGET = simple
2
3 OBJECTS = simple.o
4
5 all: $(OBJECTS)
6 gcc $(OBJECTS) -o $(TARGET)
7
8 simple.o: simple.c
9 gcc -c simple.c
10
11 clean:
12 rm -f $(OBJECTS) $(TARGET)
The first target (the first string followed by a colon) is the default target
that will be built by
make. This target is conventionally named all. In the ex-
ample above, the default target, all, depends on the variable $(OBJECTS). In
foundations of embedded systems 45
this example, $(OBJECTS) refers to a single object file, but it would typically
be a list of object files. To build the default target,
make will detect that it first
needs to have a file simple.o. It uses the rule for simple.o (which in turn
depends on there being a file
simple.c), and builds the object file using the
command gcc -c simple.c. With simple.o generated and the prerequisites
for the rule all satisfied, make then builds the executable simple using the
command
gcc $(OBJECTS) -o $(TARGET) where $(OBJECTS) will be substi-
tuted with simple.o and $(TARGET) will be substituted with simple. Finally,
the target clean provides a quick way to remove the intermediate files and
outputs of the compilation process by typing
make clean at the command
prompt/shell in the directory containing the makefile.
The program make and its input makefiles support both a rich input lan-
guage as well as a rich set of command line parameters and there are many
ways to make the above makefile more succinct.
4.21 Summary
In embedded systems, there is often no operating system below your pro-
grams or firmware (software running directly over the processor). The pro-
grams you write typically have unfettered access to control the underlying
hardware. Platforms such as Arduino shield you from having to deal with
all the intricacies of hardware, but they also shield you from developing a
complete understanding of what goes on under the hood.
This chapter introduced the inner workings of C programs for embedded
systems compiled with the GCC compiler. We used the Sunflower embed-
ded system emulator
42,43
to allow us to interactively explore the execution
42
Stanley-Marbell and
Hsiao
2001.
43
Stanley-Marbell and
Marculescu
2007.
of programs. The concepts and examples introduced in this Chapter apply
largely unchanged to other target processor architectures such as the ARM
Cortex-M0+ used in the coursework, and also apply largely unchanged to
other embedded compiler toolchains.
4.22 Exercises
Go over the questions in Section
4.1.5 and make sure you can explain the
answers to each of the questions to one of your peers. Many of the questions
in that list were covered in this chapter, so you should be able to answer them
once you have understood the material in this chapter. In addition, you will
find the books listed in Section
4.23 and Section 4.24 useful.
4.23 Further Reading
The book The C Programming Language (2nd Edition) by the creators of the
language is a concise introduction to most of what you’ll ever need to know
46 phillip stanley-marbell
about C programming. If you don’t already know how to program in C, it is
a must-read. The book C: A Reference Manual (5th Edition), is a reference book
that you will want to own a copy of, if you do any C programming. A good
introductory online course on C programming is available on
EdX.
4.24 Relevant Books Available in the Engineering Library
1. Linkers and Loaders, ISBN: 978-1558604964.
2. Programming Embedded Systems: With C and GNU Development Tools, 2nd
Edition, ISBN: 978-0596009830.
3. C: A Reference Manual, 5th Edition, ISBN: 978-0130895929.
4. The Practice of Programming, ISBN: 978-0201615869.
5. Expert C Programming, ISBN: 978-0131774292.
foundations of embedded systems 47
48 phillip stanley-marbell
4.25 The Muddiest Point
Think about the following two questions and submit your responses through
this
link.
1. What was least clear to you in this chapter? (You can simply list the section
numbers or write a few words.)
2. What was most clear to you in this chapter? (You can simply list the
section numbers or write a few words.)