本篇介绍
本篇介绍下汇编中的外部函数和调用约定。
外部函数
在前面已经多次见过使用printf了,这次我们也可以自己写一些外部函数,下面是一个例子: 首先定义2个外部函数,分别是c_area和c_circum。
代码语言:javascript复制; circle.asm
extern pi
section .data
section .bss
section .text
global c_area
c_area:
section .text
push rbp
mov rbp,rsp
movsd xmm1, qword [pi]
mulsd xmm0,xmm0 ;radius in xmm0
mulsd xmm0, xmm1
mov rsp,rbp
pop rbp
ret
global c_circum
c_circum:
section .text
push rbp
mov rbp,rsp
movsd xmm1, qword [pi]
addsd xmm0,xmm0 ;radius in xmm0
mulsd xmm0, xmm1
mov rsp,rbp
pop rbp
ret
这儿再定义2个函数,r_area 和r_circum。
代码语言:javascript复制; rect.asm
section .data
section .bss
section .text
global r_area
r_area:
section .text
push rbp
mov rbp,rsp
mov rax, rsi
imul rax, rdi
mov rsp,rbp
pop rbp
ret
global r_circum
r_circum:
section .text
push rbp
mov rbp,rsp
mov rax, rsi
add rax, rdi
add rax, rax
mov rsp,rbp
pop rbp
ret
接下来调用下上面定义的函数:
代码语言:javascript复制; function4.asm
extern printf
extern c_area
extern c_circum
extern r_area
extern r_circum
global pi
section .data
pi dq 3.141592654
radius dq 10.0
side1 dq 4
side2 dq 5
fmtf db "%s %f",10,0
fmti db "%s %d",10,0
ca db "The circle area is ",0
cc db "The circle circumference is ",0
ra db "The rectangle area is ",0
rc db "The rectangle circumference is ",0
section .bss
section .text
global main
main:
enter 0,0
; circle area
movsd xmm0, qword [radius] ; radius xmm0 argument
call c_area ; area returned in xmm0
; print the circle area
mov rdi, fmtf
mov rsi, ca
mov rax, 1
call printf
; circle circumference
movsd xmm0, qword [radius] ; radius xmm0 argument
call c_circum ; circumference returned in xmm0
; print the circle circumference
mov rdi, fmtf
mov rsi, cc
mov rax, 1
call printf
; rectangle area
mov rdi, [side1]
mov rsi, [side2]
call r_area ; area returned in rax
; print the rectangle area
mov rdi, fmti
mov rsi, ra
mov rdx, rax
mov rax, 0
call printf
; rectangle circumference
mov rdi, [side1]
mov rsi, [side2]
call r_circum ; circumference returned in rax
; print the rectangle circumference
mov rdi, fmti
mov rsi, rc
mov rdx, rax
mov rax, 0
call printf
leave
ret
结果:
The circle area is 314.159265
The circle circumference is 62.831853
The rectangle area is 20
The rectangle circumference is 18
这儿的关键信息如下:
- 涉及浮点运算的函数,参数是通过xmm0 系列寄存器传递的,返回值是通过xmm0传递的
- 涉及整数运算的函数,参数是通过rdi,rsi,rdx等寄存器传递的,返回值是通过rax传递的
- 需要使用外部函数,需要使用关键字external, 定义外部函数,需要使用关键字global,变量也一样。
调用约定
调用约定(Calling Convertions)就是调用函数时传参和返回值的约定。不同的平台约定也不一样,比如linux和windows 就都有自己的一套调用约定。 对于非浮点场景,传参规则如下:
代码语言:javascript复制The 1st argument goes into rdi.
The 2nd argument goes into rsi.
The 3rd argument goes into rdx.
The 4th argument goes into rcx.
The 5th argument goes into r8.
The 6th argument goes into r9.
如果参数超过6个,比如10个,那么规则继续如下:
The 10th argument is pushed first.
Then the 9th argument is pushed.
Then the 8th argument is pushed.
The 7th argument is pushed.
当调用函数的时候,返回地址rip也会压栈,prologue中保存rbp也会压栈一次,这样如果需要通过rsp拿到第7个参数,就需要是rsp 16。 浮点传参规则如下:
代码语言:javascript复制The 1st argument goes into xmm0.
The 2nd argument goes into xmm1.
The 3rd argument goes into xmm2.
The 4th argument goes into xmm3.
The 5th argument goes into xmm4.
The 6th argument goes into xmm5.
The 7th argument goes into xmm6.
The 8th argument goes into xmm7.
参数超过8个就需要通过栈了,不过不像整数压栈那样,这块等到了SIMD那块继续介绍。
接下来看一个传参的例子:
代码语言:javascript复制extern printf
section .data
first db "A",0
second db "B",0
third db "C",0
fourth db "D",0
fifth db "E",0
sixth db "F",0
seventh db "G",0
eighth db "H",0
ninth db "I",0
tenth db "J",0
fmt1 db "The string is: %s%s%s%s%s%s%s%s%s%s",10,0
fmt2 db "PI = %f",10,0
pi dq 3.14
section .bss
section .text
global main
main:
mov rbp, rsp; for correct debugging
push rbp
mov rbp,rsp
mov rdi,fmt1
mov rsi, first ; the correct registers
mov rdx, second
mov rcx, third
mov r8, fourth
mov r9, fifth
push 0 ; 16 byte align the stack,保证调用printf时候rsp是16字节对齐
push tenth ; now start pushing in
push ninth ; reverse order
push eighth
push seventh
push sixth
mov rax, 0
;and rsp , 0xfffffffffffffff0 ; 16 byte align the stack
call printf
;and rsp , 0xfffffffffffffff0 ; 16 byte align the stack
movsd xmm0,[pi] ; print a float
mov rax, 1
mov rdi, fmt2
call printf
leave
ret
结果:
The string is: ABCDEFGHIJ
PI = 3.140000
这儿就同时用到了寄存器和栈传参。 再看一个栈传参的例子,看看callee是如何获取栈上参数的:
代码语言:javascript复制; function5.asm
extern printf
section .data
first db "A"
second db "B"
third db "C"
fourth db "D"
fifth db "E"
sixth db "F"
seventh db "G"
eighth db "H"
ninth db "I"
tenth db "J"
fmt db "The string is: %s",10,0
section .bss
flist resb 11 ;length of string plus end 0
section .text
global main
main:
push rbp
mov rbp, rsp
mov rdi, flist ; length
mov rsi, first ; the correct registers
mov rdx, second
mov rcx, third
mov r8, fourth
mov r9, fifth
push 0 ; 让rsp 16字节对齐
push tenth ; now start pushing in
push ninth ; reverse order
push eighth
push seventh
push sixth
call lfunc ;call the function
; print the result
mov rdi, fmt
mov rsi, flist
mov rax, 0
call printf
leave
ret
;---------------------------------------------------------------------------
lfunc:
push rbp
mov rbp,rsp
xor rax,rax ;clear rax (especially higher bits)
mov al,byte[rsi] ; move content argument to al
mov [rdi], al ; store al to memory
mov al, byte[rdx]
mov [rdi 1], al
mov al, byte[rcx]
mov [rdi 2], al
mov al, byte[r8]
mov [rdi 3], al
mov al, byte[r9]
mov [rdi 4], al
xor rbx,rbx
mov rax, qword [rbp 16] ;initial stack rip rbp
mov bl,[rax]
mov [rdi 5], bl
mov rax, qword [rbp 24]
mov bl,[rax]
mov [rdi 6], bl
mov rax, qword [rbp 32]
mov bl,[rax]
mov [rdi 7], bl
mov rax, qword [rbp 40]
mov bl,[rax]
mov [rdi 8], bl
mov rax, qword [rbp 48]
mov bl,[rax]
mov [rdi 9], bl
mov bl,0
mov [rdi 10], bl
mov rsp,rbp
pop rbp
ret
结果:
The string is: ABCDEFGHIJ
可以看到lfunc解析栈上的参数,就是通过rbp 加偏移得到的。 在调用函数时,对于寄存器的保存也有一套约定,有的寄存器值需要caller保存,有的需要callee保存,具体如下:
image.png
image.png
关键信息如下:
- 对于callee save 的寄存器,caller会认为寄存器值不会变化,因此callee需要使用这些寄存器,就需要通过push/pop保存并恢复他们的值
- 对于caller save的寄存器,callee直接使用就行
- 浮点寄存器xmm0全部都是caller save的寄存器