ArmAssemblyInt:SmallEx:C

Copyright 2018, Clinton A Staley

Concepts

  1. Stack frame review/reinforcement
  2. Code reading

Overview

In this segment we'll finish our Prg5.s example by looking at main, mostly via a series of in-lecture questions, like the prior segment.

Main

Before we start with ILQs, however, there are a few details you should know

Main is "called" by other code

Recall that gcc sets up code that calls main. This setup code is the true starting point (e.g. where a _start label might be found) of the program. So main behaves like any other called function in setting up and tearing down a stack frame.

Scanf takes one character pointer and addresses to target variables.

Like printf, a scanf call like scanf("%d%d%d", &valA, &valB, &valC) takes a first parameter that is the address of the format string (exactly, the address of the first % in this example). Unlike printf, however, the following parameters are also addresses, the addresses of each variable into which data is to be read. Scanf needs variable addresses because it needs to know where in memory to put data that it reads.

Also, gcc translates that scanf call to a call of __isoc99_scanf, which is a particular scanf version.

Analyzing the code

OK, so with that background, here are a few questions:

Question 1 What do lines 59-61 do? (Recall earlier mention of the push pseudoinstruction.)

push	{fp, lr}
add	fp, sp, #4
sub	sp, sp, #16

Answer 1

The store the fp and lr on the RTS (push is stmfd sp!, ...), reset the fp to point to the base of the new frame, and then reserve 4 words of local variable storage

Question 2

What do lines 62-64 do? (Note that they precede the scanf call.) Can you deduce from them what variables are stored in what order in the stack frame?

sub	r3, fp, #20
sub	r2, fp, #16
sub	r1, fp, #12

Answer 2

They are setting r1, r2, and r3 to addresses within the local variable area (by offsetting from fp), preparatory to passing them as parameters to scanf. Since the second, third and fourth parameters are valA valB and valC, respectively, this means that those variables appear in that order in the RTS, with valC on top. (And there is one more space not yet used, at fp - 8.)

Question 3 How about ldr R0, .L7? What's that about? And, can you replace it with a version that doesn't use .L7?

Answer 3

It's getting the address of the format string at .LC0 innto r0, since that's the first parameter scanf expects. It could have been done via ldr r0, =.LC0, letting the assembler create the address-holding word at .L7

Apparently gcc doesn't like relying on the assembler and prefers setting up its own address-holding words.

Question 4

The call of scanf is obvious, but what are lines 67-70 after it doing? And, could you shorten them by one line? And, how did values get into those memory locations we're loading?

	ldr	r3, [fp, #-12]
	ldr	r1, [fp, #-16]
	ldr	r2, [fp, #-20]
	mov	r0, r3

Answer 4

They're setting up the three parameters for the maxOf3 call, copying valA in r3, valB into r2, valC into r1, and then moving r3 into r0 so we have valA-C in r0-r2.

Changing r3 to r0 in the first ldr would make the final mov needless. But, as we've seen before, sometimes nonoptimizing compilers can be dumb. And, the values got into the memory locations via scanf, which used the addresses we passed to do strs into those variables when it read in data.

Question 5

So, once we've called maxOf3, what does str r0, [fp, #-8] accomplish? And what variable is in mem location [fp #-8]?

Answer 5

r0 has the return value of maxOf3 -- the max value. The instruction stores this into the one remaining open space in the stack frame, which apparently contains variable max.

Question 6

And lines 73-75?

	ldr	r1, [fp, #-8]
	ldr	r0, =.LC1
	bl	printf

Answer 6

They load r0 and r1 with printf parameters and call printf. r1 is loaded from max. r0 gets the address of the "Max is %d ..." format string.

Apparently sometimes gcc does trust the assembler to make an address-holding memory word... Go figure.

Question 7

And finally, these lines 76-79? (And yes, the first two are just more compiler stupidity -- that could and should have been mov r0, #0.) Make a reasonable guess as to what pop does.

	mov	r3, #0
	mov	r0, r3
	sub	sp, fp, #4
	pop	{fp, pc}

Answer 7

These lines are returning from main! Main returns a 0 value, so that goes in to r0. Then the code changes sp to point just above fp in the stack, which is where it was after the initial push instruction. It then does a pop, which is a ldmfd sp!, .. shorthand, to copy the lr and old fp values into pc and fp, respectively, restoring the original fp and returning to whoever called main.