20. Indirect calls
- Labels
- Our first indirect call
- 장점?
- 다음 일련의 예제들을 보면 장점이 더욱 명확해 진다.
- Late binding and object orientation
Indirect calls 은 이름(label)을 가지고 함수를 호출하는 것이 아니라, 주소를 이용해 함수를 호출하는 것을 의미한다. 이방법을 사용하면 주소를 직접 계산해서 호출 할수 있고, 배열이나 구조체 클래스 처럼 offset이 있는 함수들의 주소를 쉽게 계산해서 호출할 수 있다.
Labels
label은 assembler 가 머신코드로 변환할때 주소로 바꿈.
fun: /* label 'fun' */
push {r4, r5}
...
pop {r4, r5}
bx lr
...
bl fun
Our first indirect call
.data /* data section */
...
.align 4 /* ensure the next label is 4-byte aligned */
ptr_of_fun: .word 0 /* we set its initial value zero */
.align 4
make_indirect_call:
push {r4, lr} /* keep lr because we call printf,
we keep r4 to keep the stack 8-byte
aligned, as per AAPCS requirements */
ldr r0, addr_ptr_of_fun /* r0 ← &ptr_of_fun */
ldr r0, [r0] /* r0 ← *r0 */
blx r0 /* indirect call to r0 */
pop {r4, lr} /* restore r4 and lr */
bx lr /* return to the caller */
addr_ptr_of_fun: .word ptr_of_fun
blx r0
:bl label
대신에 register에 있는 address 로 indirect call을 했다.
이전장에 언급했드시bx
는lr
값을 자동으로 save하지 않기 때문에 indirect call로 사용할 수 없다.
main:
...
ldr r1, addr_say_hello /* r1 ← &say_hello */
ldr r0, addr_ptr_of_fun /* r0 ← &addr_ptr_of_fun */
str r1, [r0] /* *r0 ← r1
this is
ptr_of_fun ← &say_hello */
...
bl make_indirect_call
이런식으로 주소를 변경 가능
장점?
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.
아래 코드를 보자. indirect call의 장점을 확인할 수 있다.
.data /* data section */
...
.text /* text section (= code) */
say_hello:
...중략 print hello 하는 함수
say_bonjour:
...중략 print bonjour 하는 함수
.align 4
greeter:
push {r4, lr} /* keep lr because we call printf,
we keep r4 to keep the stack 8-byte
aligned, as per AAPCS requirements */
blx r0 /* indirect call to r0 */
pop {r4, lr} /* restore r4 and lr */
bx lr /* return to the caller */
.globl main /* state that 'main' label is global */
main:
push {r4, lr} /* keep lr because we call printf,
we keep r4 to keep the stack 8-byte
aligned, as per AAPCS requirements */
ldr r0, addr_say_hello /* r0 ← &say_hello */
bl greeter /* call greeter */
ldr r0, addr_say_bonjour /* r0 ← &say_bonjour */
bl greeter /* call greeter */
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) */
addr_say_hello : .word say_hello
addr_say_bonjour : .word say_bonjour
길지만 별거 없고, greeter 함수에서 인자로 전달받은 함수 주소를
blx r0
로 indirectly 하게 call 할 수 있다는 예제이다.
다음 일련의 예제들을 보면 장점이 더욱 명확해 진다.
- printf format 문자열
.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"
/* 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 */
/* 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"
.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
/* array of people */
people : .word person_john, person_pierre, person_sally, person_bernadette
- hello 출력 함수 정의
.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
- main 함수
.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
ldr r0, [r4, r5, LSL #2]
: 호출할 함수의 이름을 알 필요없이 주소값을 계산해서 call할 수 있다는 장점이있다.
greet_person 함수 호출 (parameter with 계산된 함수 주소)
- greet_person 함수 정의
이 함수에서 매개변수로 전달 받은 주소값에서 실제 함수 주소를 추출해서 branch 한다.
/* 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 */
이름 tag를 찾으면 그에 해당하는 함수 호출이 가능함. 마치 구조체처럼.
Indirect call 을 사용하면 OOP처럼 함수 호출이 가능해짐.
Late binding and object orientation
위의 예제는 아래와 같은 객체지향 C++ 코드와 동일하다.
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
struct Person {
const char* name;
virtual void greet() = 0;
};
struct EnglishPerson : Person {
virtual void greet() {
printf("Hello %s\n", this->name);
}
};
struct FrenchPerson : Person {
virtual void greet() {
printf("Bonjour %s\n", this->name);
}
};