美文网首页
汇编学习(6), 外部函数,调用约定

汇编学习(6), 外部函数,调用约定

作者: android小奉先 | 来源:发表于2022-12-10 17:25 被阅读0次

    本篇介绍

    本篇介绍下汇编中的外部函数和调用约定。

    外部函数

    在前面已经多次见过使用printf了,这次我们也可以自己写一些外部函数,下面是一个例子:
    首先定义2个外部函数,分别是c_area和c_circum。

    ; 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。

    ; 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
    

    接下来调用下上面定义的函数:

    ; 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
    
    

    这儿的关键信息如下:

    1. 涉及浮点运算的函数,参数是通过xmm0 系列寄存器传递的,返回值是通过xmm0传递的
    2. 涉及整数运算的函数,参数是通过rdi,rsi,rdx等寄存器传递的,返回值是通过rax传递的
    3. 需要使用外部函数,需要使用关键字external, 定义外部函数,需要使用关键字global,变量也一样。

    调用约定

    调用约定(Calling Convertions)就是调用函数时传参和返回值的约定。不同的平台约定也不一样,比如linux和windows 就都有自己的一套调用约定。
    对于非浮点场景,传参规则如下:

    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。
    浮点传参规则如下:

    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那块继续介绍。
    

    接下来看一个传参的例子:

    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是如何获取栈上参数的:

    ; 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

    关键信息如下:

    1. 对于callee save 的寄存器,caller会认为寄存器值不会变化,因此callee需要使用这些寄存器,就需要通过push/pop保存并恢复他们的值
    2. 对于caller save的寄存器,callee直接使用就行
    3. 浮点寄存器xmm0全部都是caller save的寄存器

    相关文章

      网友评论

          本文标题:汇编学习(6), 外部函数,调用约定

          本文链接:https://www.haomeiwen.com/subject/ooizfdtx.html