Computer Systems Experiments 15 | Makefile and C Function Stack
Computer Systems Experiments 15 | Makefile and C Function Stack

- Makefile
Before we begin to learn how the C compiles the function, let’s first go through the Makefile
file from the last experiment. The content of this file is,
NAME = blink
CFLAGS = -Og -Wall -std=c99 -ffreestanding
LDFLAGS = -nostdlib -e main
.PRECIOUS: %.elf %.o
all : $(NAME).bin
%.bin: %.elf
arm-none-eabi-objcopy $< -O binary $@
%.elf: %.o
arm-none-eabi-ld $(LDFLAGS) $< -o $@
%.o: %.c
arm-none-eabi-gcc $(CFLAGS) -c $< -o $@
install: $(NAME).bin
rpi-install.py $<
clean:
rm -f *.o *.elf *.bin
After we type the $ make
command, the command we are actually running is,
$ arm-none-eabi-gcc -Og -Wall -std=c99 -ffreestanding -c blink.c -o blink.o
$ arm-none-eabi-ld -nostdlib -e main blink.o -o blink.elf
$ arm-none-eabi-objcopy blink.elf -O binary blink.bin
By this result, we can start to discover what’s the meaning of each part of this makefile,
- The first three lines assign three variables:
NAME
,CFLAGS
, andLDFLAGS
. The first one is the name for our file, well, in this case, it should beblink
(we can change the value of this variable for some other files). The variablesCFLAGS
andLDFLAGS
are the options (flags) of thegcc
command and theld
command, respectively. - The word after
make
means where we start and end the makefile script. For example,
$ make all
where all
can be omitted starts the command from all:
and ends the command before install:
.
all : $(NAME).bin
%.bin: %.elf
arm-none-eabi-objcopy $< -O binary $@
%.elf: %.o
arm-none-eabi-ld $(LDFLAGS) $< -o $@
%.o: %.c
arm-none-eabi-gcc $(CFLAGS) -c $< -o $@
Similarly,
$ make install
will run,
install: $(NAME).bin
rpi-install.py $<
And also,
$ make clean
will run,
clean:
rm -f *.o *.elf *.bin
- If we want to cite the value of a variable, we have to add the
$()
symbol to show that this is the value of a variable. For example,
$(CFLAGS)
%
acts as a wildcard for anything. For example,
%.o
means any file ending with .o
.
$@
is the name of the target being generated, and$<
the first prerequisite (usually a source file). For example, for
%.elf: %.o
$<
means the source file %.o
and $@
means the target file %.elf
. Thus, the statement above actually determines how to compile a file ending with .o
to a file ending with .elf
.
- Let’s now find out what does
.PRECIOUS: %.elf %.o
do for us. We can actually delete this line and then retry the compiling process by,
$ make clean
$ make
Now, the commands we actually ran are,
$ arm-none-eabi-gcc -Og -Wall -std=c99 -ffreestanding -c blink.c -o blink.o
$ arm-none-eabi-ld -nostdlib -e main blink.o -o blink.elf
$ arm-none-eabi-objcopy blink.elf -O binary blink.bin
$ rm blink.elf blink.o
We can notice that the last command automatically deletes all the intermediate files for us. So the .PRECIOUS: %.elf %.o
is the command for us to keep the intermediate files.
Now, let’s continue to analyze how does the compiler constructs the functions.
2. C Function Stack
Let’s see the simplest function in C,
void main(){}
This main function is useless but it will show us how does the compiler work. Let’s go to godbolt.org to check our code. Remember you must choose C
, ARM gcc 5.4.1 (none)
and -g0
as the options,

main:
str fp, [sp, #-4]!
add fp, sp, #0
mov r0, r0
sub sp, fp, #0
ldr fp, [sp], #4
bx lr
Before we analyze this assembly code, we have to know the meaning of sp
and fp
. sp
is the stack pointer register which contains the address of the last written value. Actually, it is pointing to the last written value (or the top of that stack) on the stack. While fp
stands for the frame pointer register which is used as a base pointer to local variables on the stack. It is also treated as the pointer pointing to the bottom of a stack.
Let’s now try this code in the VisUAL. You should know that the VisUAL doesn’t support sp
and fp
, so we have to replace them with the r0
and r1
register.
Because r0
and r1
have initial value 0x0
in the first place and this is obviously not the case that’s going to happen in the real world, so we have to assign the values of them firstly. Note that the r1
is used to represent fp
, so the value of r0
should be no more than r1
. In our case, we create a fake stack with its sp
pointing towards 0x3f000000
and its fp
pointing to 0x3f000008
.
ldr r0, =0x3f000000
ldr r1, =0x3f000008
Then, we use the following code to test the stack and this code can also be found from my repo.
ldr r0, =0x3f000000
ldr r1, =0x3f000008
str r1, [r0, #-4]!
add r1, r0, #0
sub r0, r1, #0
ldr r1, [r0], #4
After the first two assignment, the register r0
and r1
is assigned with the given address,

Then,
str r1, [r0, #-4]!
means to store the present address in r1
(or fp
) to the r0-4
(or sp-4
) address. This is called the base-register addressing and the !
in the end, means that the value of r0
(or sp
) will also be changed.

Then,
add r1, r0, #0
replaces the value in r1
with the value of r0
. This command shift to a new stack above the previous stack. Since this moment, a new stack for this main
function is now created. The following commands are used to remove this stack,
sub r0, r1, #0
is the opposite operation of the add
command and,
ldr r1, [r0], #4
gets the value from the value that r0
(sp) is pointing to and then add 4 to the r0
to retain the original condition.

In the next experiment, we are going to create a counter from 0 to 9 based on the C language.