汇编学习(5),函数,栈帧

2022-12-07 15:53:46 浏览数 (2)

本篇介绍

本篇介绍下汇编中的函数,栈帧内容。

函数

汇编也支持函数调用,如下是一个例子:

代码语言:javascript复制
extern printf
section .data                           
    radius  dq  10.0                
    pi      dq  3.14                
    fmt     db  "The area of the circle is %.2f, radius is %.2f",10,0
section .bss                            
section .text                                               
    global main                     
;----------------------------------------------
main:
push    rbp
mov     rbp, rsp 
    call surface            ; call the function
    mov rdi,fmt             ; print format
    movsd xmm1, [radius]    ; move float to xmm1
    mov rax,2               ; surface in xmm0
    call printf
        leave
ret
;----------------------------------------------
surface:
push    rbp
mov     rbp, rsp        
    movsd xmm0, [radius]    ; move float to xmm0
    mulsd xmm0, [radius]    ; multiply xmm0 by float
    mulsd xmm0, [pi]        ; multiply xmm0 by float
leave
ret             

输出:
The area of the circle is 314.00, radius is 10.00

使用call 标号就可以实现函数调用,对于浮点,返回值会通过xmm0传递。

这儿又出现了一个leave指令,leave就等同于 mov rsp,rbp,pop rbp。

函数也可以拥有自己的代码段和数据段,如下面的例子:

代码语言:javascript复制
; function2.asm
extern printf
section .data                           
    radius  dq  10.0                                
section .bss                            
section .text
area:
    section .data
        .pi dq  3.141592654 ; local to area
    section .text 
push rbp
mov rbp, rsp
        movsd xmm0, [radius]
        mulsd xmm0, [radius]
        mulsd xmm0, [.pi]
leave
ret
circum:
    section .data
        .pi dq  3.14        ; local to circum
    section .text
push rbp
mov rbp, rsp    
        movsd xmm0, [radius]
        addsd xmm0, [radius]
        mulsd xmm0, [.pi]
leave
ret
circle:
    section .data
        .fmt_area   db  "The area is %f",10,0
        .fmt_circum db  "The circumference is %f",10,0
    section .text
push rbp
mov rbp, rsp        
        call area   
        mov rdi,.fmt_area
        mov rax,1           ; area in xmm0
        call printf
        call circum
        mov rdi,.fmt_circum
        mov rax,1           ; circumference in xmm0
        call printf
leave
ret
    global main                     
main:
    mov rbp, rsp; for correct debugging
push rbp
mov rbp, rsp
    call circle
leave
ret

输出:
The area is 314.159265
The circumference is 62.800000

这儿可以看到在函数内部也可以定义数据段,用来存放局部变量。

栈帧

对于intel处理器,在调用函数的时候需要保证rsp是16字节对齐的,这样设计是为了更好的支持SIMD。那体现到代码上是怎样呢?可以看下如下的代码:

代码语言:javascript复制
extern printf
section .data                           
    fmt db  "2 times pi equals %.14f",10,0 
    pi  dq  3.14159265358979
section .bss                            
section .text
func3:
    push rbp
        movsd   xmm0, [pi]
        addsd   xmm0, [pi]
        mov     rdi,fmt
        mov     rax,1       
        call    printf  ; print a float
    pop rbp
    ret
func2:
    push rbp
        call    func3   ; call the third function
    pop rbp
    ret
func1:
    push rbp
        call    func2   ; call the second function
    pop rbp
    ret

    global main
main:
    push rbp
        ;push rbp ; for align 16 byte, uncomment it will cause crash
        call func1  ; call the first function
        ;pop rbp
    pop rbp
    ret

结果:
2 times pi equals 6.28318530717958

如果把main中注释的代码去掉,那么就会crash,因为这时候调用func1时 rsp就不是16字节对齐的。这儿可以单步看看:

image.png

这时候rsp没有16字节对齐,执行push rbp后,就会对齐。 这就是prologue和epilogue的一个作用,保证调用函数时的rsp 16字节对齐。

本来在调用main函数之前rsp是16字节对齐的,可是在调用main时候,由于会将返回地址压栈,这时候rsp就不是16字节对齐了,就需要prologue中再次执行一个进栈操作,就可以保证是对齐的了。

0 人点赞