Computer Systems Experiments 15 | Makefile and C Function Stack

Series: Computer Systems Experiments

Computer Systems Experiments 15 | Makefile and C Function Stack

  1. 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 , and LDFLAGS . The first one is the name for our file, well, in this case, it should be blink (we can change the value of this variable for some other files). The variables CFLAGS and LDFLAGS are the options (flags) of the gcc command and the ld 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.