ARM assembler in Raspberry Pi – Chapter 20
Today we will see how to make indirect calls.
Labels
One of the distinguishing features of assemblers is the shortage of symbolic information. The only symbolic support available at this (low) level are labels. We already know that labels are just addresses to the memory of the program (both data and code).
When we define a function in assembler, we define a label for it.
Later (or before, assemblers usually do not care) we use the label. So a call like
Is saying to the assembler, I'm using
.
fun
here, but you have to put the appropiate address there when generating machine code, ok?
In reality, calling a function is usually much more involved but at the end there is a label that brings us to the function.
Our first indirect call
What if rather than using the label of a function, we were able to keep the addres of a function (or several of them) somewhere and call a function indirectly? Let's try that. First, we will start with a basic Hello world
that uses a label. We will call this a direct call.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
.data /* data section */
.align 4 /* ensure the next label is 4-byte aligned */
message: .asciz "Hello world\n"
.text /* text section (= code) */
.align 4 /* ensure the next label is 4-byte aligned */
say_hello:
push {r4, lr} /* keep lr because we call printf,
we keep r4 to keep the stack 8-byte
aligned, as per AAPCS requirements */
/* Prepare the call to printf */
ldr r0, addr_of_message /* r0 ← &message */
bl printf /* call printf */
pop {r4, lr} /* restore r4 and lr */
bx lr /* return to the caller */
.align 4 /* ensure the next label is 4-byte aligned */
addr_of_message: .word message
.globl main /* state that 'main' label is global */
.align 4 /* ensure the next label is 4-byte aligned */
main:
push {r4, lr} /* keep lr because we call say_hello,
we keep r4 to keep the stack 8-byte
aligned, as per AAPCS requirements */
bl say_hello /* call say_hello, directly, using the label */
mov r0, #0 /* return from the program, set error code */
pop {r4, lr} /* restore r4 and lr */
bx lr /* return to the caller (the system) */
Now let's add some storage in the data section to keep the address of say_hello
.
Now we will add a new function make_indirect_call
that does the indirect call using the value stored in ptr_of_fun
.
Doing an indirect call is done using the instruction blx
. It behaves like bl
but expects a register rather than a label.
Yoy may be wondering whether we could have used bx
rather than blx
. We cannot. The instruction bx
does not set the lr
register to the next instruction, like bl
and blx
do. Thus, we would call the function but it would not be able to return: it would jump back to the wrong place! (try to think which one).
Now in the main
we will keep the address of say_hello
in ptr_of_fun
and call make_indirect_call
.
Note that, in the function make_indirect_call
we did
while in the main
we do
This is a similar case like arrays: when we load an array address, we do not need to load again (as it happens when we load simple scalars). This is because if we did that, we would be loading the first element of the array. With functions a similar thing happens: the function itself, its label, is already an address. If we did another load we would be loading an instruction into the register!! Not quite what we want :)
In the function make_indirect_call
we are not loading a function but a pointer to a function (addr_ptr_of_fun
), so we have to do the typical double load we do for scalars (because at the end, a pointer is just an integer that happens to be an address of the memory of our program).
Feel the power
The last example does not look very interesting, but being able to call a function indirectly is a very powerful thing. It allows us to keep the address of a function somewhere and call it. It allows us to pass the address of a function to another function. Why would we want to do that? Well, it is a rudimentary, yet effective, way of passing code to another function.
As an example, let's make a generic greeter function which receives a greeting function as a parameter. This way the exact greeting is actually deferred to another function.
If we run it
You are probably not impressed by the output of this previous program. So let's try to make it more interesting: we will greet people generically, some people will be greeted in English and some other will be greeted in French.
Let's start defining a bunch of data that we will require for this example. First greeting messages in English and French. Note that we will greet the person by name, so we will use a printf format string.
1
2
3
4
5
6
.data /* data section */
.align 4 /* ensure the next label is 4-byte aligned */
message_hello: .asciz "Hello %s\n"
.align 4 /* ensure the next label is 4-byte aligned */
message_bonjour: .asciz "Bonjour %s\n"
Next we will define some tags that we will use to tag people as English or French. This tag will contain the address to the specific greeting function. The English tag will have the address of say_hello
and the French tag will have the address of say_bonjour
.
7
8
9
10
11
12
13
14
15
/* tags of kind of people */
.align 4 /* ensure the next label is 4-byte aligned */
person_english : .word say_hello /* tag for people
that will be greeted
in English */
.align 4 /* ensure the next label is 4-byte aligned */
person_french : .word say_bonjour /* tag for people
that will be greeted
in French */
Let's define some names that we will use later, when defining people.
18
19
20
21
22
23
24
25
26
/* several names to be used in the people definition */
.align 4
name_pierre: .asciz "Pierre"
.align 4
name_john: .asciz "John"
.align 4
name_sally: .asciz "Sally"
.align 4
name_bernadette: .asciz "Bernadette"
And now define some people. Every person is actually a pair formed by an address to their name and an address to their tag.
28
29
30
31
32
33
34
35
.align 4
person_john: .word name_john, person_english
.align 4
person_pierre: .word name_pierre, person_french
.align 4
person_sally: .word name_sally, person_english
.align 4
person_bernadette: .word name_bernadette, person_french
Finally let's group every person in an array. The array contains addresses to each people (not the people themselves).
38
39
/* array of people */
people : .word person_john, person_pierre, person_sally, person_bernadette
Now let's define the code. These are the two specific functions for each language (English and French). Note that we already named their labels in the tags above.
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
.text /* text section (= code) */
.align 4 /* ensure the next label is 4-byte aligned */
say_hello:
push {r4, lr} /* keep lr because we call printf,
we keep r4 to keep the stack 8-byte
aligned, as per AAPCS requirements */
/* Prepare the call to printf */
mov r1, r0 /* r1 ← r0 */
ldr r0, addr_of_message_hello
/* r0 ← &message_hello */
bl printf /* call printf */
pop {r4, lr} /* restore r4 and lr */
bx lr /* return to the caller */
.align 4 /* ensure the next label is 4-byte aligned */
addr_of_message_hello: .word message_hello
.align 4 /* ensure the next label is 4-byte aligned */
say_bonjour:
push {r4, lr} /* keep lr because we call printf,
we keep r4 to keep the stack 8-byte
aligned, as per AAPCS requirements */
/* Prepare the call to printf */
mov r1, r0 /* r1 ← r0 */
ldr r0, addr_of_message_bonjour
/* r0 ← &message_bonjour */
bl printf /* call printf */
pop {r4, lr} /* restore r4 and lr */
bx lr /* return to the caller */
.align 4 /* ensure the next label is 4-byte aligned */
addr_of_message_bonjour: .word message_bonjour
Before we go to the interesting function, let's define the main
function.
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
.globl main /* state that 'main' label is global */
.align 4 /* ensure the next label is 4-byte aligned */
main:
push {r4, r5, r6, lr} /* keep callee saved registers that we will modify */
ldr r4, addr_of_people /* r4 ← &people */
/* recall that people is an array of addresses (pointers) to people */
/* now we loop from 0 to 4 */
mov r5, #0 /* r5 ← 0 */
b check_loop /* branch to the loop check */
loop:
/* prepare the call to greet_person */
ldr r0, [r4, r5, LSL #2] /* r0 ← *(r4 + r5 << 2) this is
r0 ← *(r4 + r5 * 4)
recall, people is an array of addresses,
so this is
r0 ← people[r5]
*/
bl greet_person /* call greet_person */
add r5, r5, #1 /* r5 ← r5 + 1 */
check_loop:
cmp r5, #4 /* compute r5 - 4 and update cpsr */
bne loop /* if r5 != 4 branch to loop */
mov r0, #0 /* return from the program, set error code */
pop {r4, r5, r6, lr} /* callee saved registers */
bx lr /* return to the caller (the system) */
addr_of_people : .word people
As you can see, what we do here is to load elements 0 to 3 of the people
array and call the function greet_person
. Every element in people
array is a pointer, so we can put them in a register, in this case r0
because it will be the first parameter of greet_person
.
Let's see now the code for the function greet_person
.
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
/* This function receives an address to a person */
.align 4
greet_person:
push {r4, lr} /* keep lr because we call printf,
we keep r4 to keep the stack 8-byte
aligned, as per AAPCS requirements */
/* prepare indirect function call */
mov r4, r0 /* r0 ← r4, keep the first parameter in r4 */
ldr r0, [r4] /* r0 ← *r4, this is the address to the name
of the person and the first parameter
of the indirect called function*/
ldr r1, [r4, #4] /* r1 ← *(r4 + 4) this is the address
to the person tag */
ldr r1, [r1] /* r1 ← *r1, the address of the
specific greeting function */
blx r1 /* indirect call to r1, this is
the specific greeting function */
pop {r4, lr} /* restore r4 and lr */
bx lr /* return to the caller */
In register r0
we have the address of a person. We move it to r4
for convenience as r0
will be used for the indirectly called function. Then we load the name of the person, found in [r4]
, this is [r4, #0]
(this is *(r4 + 0)
, so *r4
) into r0
. Then we load the person tag, found 4 bytes after the name (remember that the name of the person is an address, so it takes 4 bytes in ARM). The tag itself is not very useful except because it allows us to get the specific greeting function (either say_hello
or say_bonjour
). So we load [r4, #4]
, the address of the tag, in r1
. Ok, now r1
contains the address of the tag and we know that the first 4 bytes of a tag contain the specific greeting function.
If we run this program the output is:
Late binding and object orientation
In the last example we have implemented, in a very simple way, a feature of the object-oriented programming (OOP) called late binding, which means that one does not know which function is called for a given object.
In our example the objects are of kind Person
. Every Person
can be greeted, this is what greet_person
does. We do not have objects of kind Person
really, but EnglishPerson
and FrenchPerson
. When you greet an EnglishPerson
you expect to greet him/her with Hello
, when you greet a FrenchPerson
you expect to greet him/her with Bonjour
.
If you know C++ (or Java), you'll quickly realize that our last example actually implements something like this.
In the snippet above, this
is the Person
we passed to our function greet_person
. That parameter allowed us to retrieve the name of the person (this->name
) and the specific version of greet
we wanted.
I hope that this last example, albeit a bit long, actually shows you the power of indirect calls.
This is all for today.