AT&T汇编语言(第七章 CALLING FUNCTIONS)

系列文章目录

第七章 CALLING FUNCTIONS

提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

如何用汇编语言写函数调用,如何传入参数,传出参数,返回数据

提示:以下是本篇文章正文内容,下面案例可供参考

一、函数概述

1.1 定义函数

在这里插入图片描述

.section .text
.type area, @function
area:
    ret
.globl main
main:
    call area

老师的这个例子比较简单,我在我的64位CPU的linux虚机上,写了一个函数,编译成汇编。
C语言源程序:

#include <stdio.h>


void* my_memset(void *dst, int val, size_t count)
{
    void *start = dst;

    while(count--)
    {
        *(char*)dst = (char) val;
        dst = (char*)dst + 1;
    }

    return start;
}

int main(void)
{
    int    array[1000];

    my_memset((void*)&array[0], 0, sizeof(array));
    return 0;

}

gcc -S memset.c编译成汇编文件memset.s:

        .file   "memset.c"
        .text
        .globl  my_memset             # 默认是globl
        .type   my_memset, @function  # 与老师给的例子一样
my_memset:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        movq    %rdi, -24(%rbp)       # 用寄存器传递参数,然后压入栈中,-24 = %rbp占8字节 + 返回地址占8字节+%rdi占8字节
        movl    %esi, -28(%rbp)       # 第2个参数占4字节
        movq    %rdx, -40(%rbp)       # 第3个参数占8字节,这里为什么是-40?不是-36?应该是第2个参数8字节对齐后
        movq    -24(%rbp), %rax
        movq    %rax, -8(%rbp)
        jmp     .L2
.L3:
        movl    -28(%rbp), %eax
        movl    %eax, %edx
        movq    -24(%rbp), %rax
        movb    %dl, (%rax)
        addq    $1, -24(%rbp)
.L2:
        movq    -40(%rbp), %rax
        leaq    -1(%rax), %rdx
        movq    %rdx, -40(%rbp)
        testq   %rax, %rax
        jne     .L3
        movq    -8(%rbp), %rax
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE0:
        .size   my_memset, .-my_memset
        .globl  main
        .type   main, @function
main:
.LFB1:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        subq    $4000, %rsp           # 局部变量申请栈内存
        leaq    -4000(%rbp), %rax     # 将栈顶指针赋值给%rax
        movl    $4000, %edx           # memset第3个参数, %edx初始化个数4000
        movl    $0, %esi              # memset第2个参数, %esi初始化值0
        movq    %rax, %rdi            # memset第1个参数,%rdi保存栈顶指针
        call    my_memset             # 与调用C LIB一样
        movl    $0, %eax              # 出栈前,要先将%eax赋值为0,为啥还不知道呢
        leave
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE1:
        .size   main, .-main
        .ident  "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-28)"
        .section        .note.GNU-stack,"",@progbits

这个例子,参数是用寄存器传输的。my_memset参数没有压栈。
函数操作使用栈内存时,栈顶没有变化。
在这里插入图片描述

1.2 指令说明

  1. push指令
    pushl %eax
    将eax数值压入栈中,可分解为:
    subl $4, %esp ——> esp = esp - 4
    movl %eax, (%esp) ——> *(int32_t *)esp = eax
  2. popl指令
    pop %eax
    将eax数值弹出栈,可分解为:
    movl (%esp), %eax ——> eax = *(int32_t *)esp
    addl $4, %esp ——> esp = esp + 4
  3. call指令
    call 0x12345
    调用0x12345这个地址,可分解为:
    pushl %eip ——> 将cpu下一条要执行的指令压入栈中
    movl $0x12345, %eip ——> eip = 0x12345
    注意:CPU下一条指令将会从地址0x12345中取。
  4. ret指令
    ret
    返回call之前的地址,可分解为:
    popl %eip ——> 将call压入栈的指令弹出赋给eip

二、传递函数参数

2.1 概述

在这里插入图片描述
传递参数/返回值的三种方法:

  • 寄存器
  • 全局变量

寄存器,其中寄存器传参数,大家可以参考前面调用系统提供的接口函数和C LIB的调用。都是用寄存器传递参数的。但是也只是传递这一下,进入函数中如何处理从自己写的函数来看,还是会入栈。但是这里有一个疑问,如果在函数调用前参数都赋值好了,但是此时线程切换了,再切换回来寄存器会不会变化?应该不会,不同线程应该保留各自的寄存器副本。

全局变量,是最不推荐的,会被其他线程修改掉。

栈,栈是必须用的。

2.2 用寄存器传参

2.2.1 一个例子:正方形求面积

在这里插入图片描述

movl %eax, %ecx   # 一个乘数放在%eax中,另一个乘数放在%ecx中
mul %ecx          # 结果放在edx和eax中

例子中没有用到edx,默认认为数字比较小,只处理了eax。只作为一个函数示例,没有很严谨。
在1.1 定义函数 一节中,memset.c编译的memset.s,可以看出使用寄存器传递参数。

2.3 全局变量传参:

在这里插入图片描述
例子中有个问题:

end:
    movl value, %ebx  # 原来写错了movl result1, %ebx

2.4 用栈传参

用栈传递函数参数,需要手工将参数push?到栈中,调用完成之后,需要手工恢复栈顶,即%esp。
在这里插入图片描述
栈:

  • 从高地址向低地址生长
  • 先进后出
  • 每个线程都有自己的线程栈
  • call 函数后面一个语句的地址压入栈,在函数ret时,会出栈赋值给EIP,执行后续操作

C语言参数入栈:从右至左依次入栈,最右边的参数更靠近栈底,最左边的参数更靠近栈顶。
%esp — 保存栈顶地址
%ebp — 保存栈底地址

调用函数:
将当前%ebp压入栈,并栈底移动到当前栈顶
pushl %ebp
movl %esp, %ebp

参数使用:
老师的例子里都是使用%esp:

  • parameter1, 4(%esp)
  • parameter2, 8(%esp)
  • parameter3, 12(%esp)
    但是我用gcc编译汇编,发现都用的是%ebp:
  • parameter1, -16(%ebp)
  • parameter2, -12(%ebp)
  • parameter3, -8(%ebp)
  • next instruction
    不用esp的原因是,%esp随着后续的压栈会变化,所以用%ebp更靠谱。

在这里插入图片描述
PPT中movl %esp, %ebp写反了,应该是movl %ebp, %esp

movl %ebp, %esp      # 把%esp移动到%ebp指向的栈地址
popl %ebp            # 弹出保存的main的栈底赋值给%ebp,同时%esp=%esp+4
ret                  # ret指令,将%esp指向的返回地址弹出,赋值给rip

在这里插入图片描述
因为%esp会在程序使用局部变量时移动,所以不用%esp访问参数,而是用%ebp来访问。

2.5 局部变量压栈

在这里插入图片描述

main:
    pushl %eax        # 参数3压栈
    pushl %eax        # 参数2压栈
    pushl %eax        # 参数1压栈
    call func1        # 调用func1函数,隐含着将addl %12, %esp指令地址压栈
    addl %12, %esp    # 栈顶恢复到3个参数压栈前

2.6 一个例子(用栈传参)

在这里插入图片描述
这个例子有个问题,就是main函数中没有平栈。另外,返回值还是用寄存器%eax才传递(%edx保存乘法高位,例子程序简化了,没有取%edx的值,默认这个值没有那么大)

pushl $2
call area
# 此处应该平栈,popl %ebx,或者addl $4, %esp # 32位cpu

修改后

.section .bss
    .lcomm values, 8
.section .text
.globl main
main:
    leal values, %edi
    pushl $2
    call area
    addl $4, %esp      # 32位cpu
    movl %eax, (%edi)  # %edi的括号很关键,表明是赋值给%edi指向的内存,即:values
    pushl $3
    call area
    addl $4, %esp
    movl %eax, 4(%edi) # values高地址的4字节
end:
    movl (%edi), %ebx   # values低地址4字节保存的值赋值给%ebx
    addl 4(%edi), %ebx  # 高4字节值+4字节值,赋值给%ebx
    movl $1, %eax       # 指定系统调用为:exit
    .int 0x80            # 执行调用:exit(%ebx)

.type area, @function
area:
    pushl %ebp           # 保存当前%ebp
    movl %esp, %ebp      # 新的%ebp指向当前栈顶,即保存之前%ebp位置
    subl $4, %esp        # 栈顶向低地址移动4字节,用于局部变量
    movl 8(%ebp), %eax   # 8(%ebp)跳过压入栈的%ebp和返回地址,指向$2,即将2赋值给%eax
    mull 8(%ebp)         # 8(%ebp)*%eax,即2*2,保存在%edx,%eax
    movl %eax, -4(%ebp)  # 将结果保存在局部变量中,%edx省略了
    movl %ebp, %esp      # 将栈顶指回area栈底,即保存main,%ebp低栈内存
    popl %ebp            # %ebp = main:%ebp; addl $4, %esp
    ret                  # popl %eip

在gcc -m32 -o …编译之后,执行报segment fault。

2.7 独立文件

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值