2、Linux汇编——函数的工作原理

第四章 函数的工作原理

1、函数的组成部分

    函数主要由以下几个成分组成:函数名、函数参数、局部变量、静态变量、全局变量、返回地址、返回值

(1)函数参数及几个变量:这是在逻辑上对函数的涉及到的数据进行规划,实际上当前运行的指令只能通过直接、间接、立即数三种方式访问数据。

(2)返回地址:在汇编语言中,实际上是某个指令的地址,即IP寄存器、程序段标号等存储或代表的地址。

(3)返回值:程序只能有一个返回值,具体表现为该返回值:存储在某个寄存器中,存储在某个内存单元中。

2、C调用约定的汇编语言函数

       1、关于栈

        在内存的具体实现中,“栈底”位于“高地址”区域,“栈顶”位于“低地址”区域,其中%ess为栈的段基址,%esp为栈顶指针(注意:栈顶非空)。当进行“压栈”操作时,%esp的值由大到小变化;当进行“弹栈”操作时,%esp的值由小到大变化。

        同时,由于当前机器为32 位机,因此,每次%esp都会跨过4 byte。

    2、关于如何调用函数

     C约定的汇编调用实际上是利用“栈”暂存信息:被调用函数的地址、被调用函数的参数、调用函数的地址。当前执行的程序调用某个函数时,进行如下操作:

(0)为了防止寄存器中的数据被破坏,在进入“被调用函数”之前,需要在当前执行的函数中,将所有寄存器的值压栈保存。待返回当前函数时,再重新加载这些值,即常说的“恢复现场”。

(1)逆序将被调用函数的“参数”压栈:如函数fun(para 1, para 2, para 3, para 4,....),则para 4最先入栈,para 1最后入栈。此时栈顶元素为para 1。

(2)将当前的IP地址压栈:该地址为返回地址,当被调用函数结束执行后,利用ret(return的缩写)指令,返回到调用函数

(3)将当前%ebp(基址指针寄存器)值压栈

(4)movl %esp , %ebp:此时,正如“基址指针寄存器”表明的,可以通过%ebp来根据“基址寻址”方式对“被调用函数”中的局部变量进行访问。如:-8(%ebp),在x86架构中,采用%ebp进行基址寻址较使用其他寄存器快。

(5)修改%esp值,为“被调用函数”开辟栈空间,存储局部变量——可以得到被调函数的局部变量空间的范围为:%ebp~%esp。

1#示例:演示进入被调函数的代码 2#说明: 1、被调函数的参数保存在数据域的item标签下,此处为索引,实际情况不会这样 3#        2、被调函数的标签为called_func 4.section .data 5    item: .long 1, 2, 3 #使用long类型,是为了配合%esp每次移动都为4 byte,否则需要做其他处理 6                        #函数即为:called_func(1,2,3) 7.section .text 8.globl _start 9_start:     #(0)保存“上下文环境” 10            pushl %eax  #假设"调用函数"只涉及到%eax和%ebx的使用 11            pushl %ebx     12            movl  3, %ebx 13            #(1)对参数进行逆序压栈,此处采用“索引寻址”  14load_data:  subl 1,%ebx             #将函数参数压入栈中 15            pushl item(,%ebx,4) 16            cmpl 0,%ebx 17            jne  load_data 18             19            #(2)将当前函数的指令地址压入栈中,并跳转 20            call called_func 21called_func:    pushl %ebp          #(3)暂存%ebp的值于栈中 22                movl %esp , %ebp    #(4)改变%ebp的值 23                subl 8,%ebp        #(5)为“被调函数”开辟2个字的空间,存储局部变量 24                 25                #如果需要对“局部变量”进行访问,则利用%ebp进行“基址访址”形式即可 26                movl $2, -4(%ebp) 27                ...... 28                ...... 29                #使用下面的指令,返回到“调用函数”中

以上即为进入一个“被调用函数”需要做的准备工作。当退出“被调函数”时,需要做如下工作:

(1)将返回值存入%eax中

(2)清除“被调函数”栈内数据

(3)返回“调用函数”。从“被调函数”返回的代码如下:

1movl %ebp, %esp 2popl %ebp 3ret

3、对于寄存器

        在进入“被调函数”前,一定要将当前的寄存器值暂存在栈中,从而保证“被调函数”有充足的寄存器可以使用。如果要在“被调函数”保存寄存器,则破坏了函数之间的封闭性。调用函数和被调函数之间,一定只能通过全局变量、被调函数的参数进行通信,否则,将不易于程序的管理。

3、程序1:

1#目的:本程序计算2^3+5^2 2#程序所有内容存入寄存器中,数据段无数据 3#变量说明:%eax存储函数power的计算结果,并作为返回值,返回给“调用函数” 4 5.section .data 6.section .text 7.globl _start 8_start:    #计算第一个加数 9           pushl $3     #压入第二个参数,指数 10           pushl $2     #压入第一个参数,底数 11           call power   #调用函数 12            13           addl $8, %esp #清空“被调函数”存储局部变量的栈空间 14           pushl %eax     #将第一个结果压入栈中 15            16           #计算第二加数 17           pushl $2 18           pushl $5 19           call power 20            21           addl $8, %esp     #清空“被调函数”的栈空间 22           popl %ebx        #将第一个结果弹栈,存入%ebx中。第二个结果已经存入%eax中 23            24           addl %eax, %ebx  #两个结果相加,作为返回给系统的状态之,存储在%ebx中 25            26           movl $1, %eax    #调用中断,退出程序 27           int 0x80 28     29#目的:计算一个整数的幂 30#输入:参数1:底数a 31#      参数2:幂b 32#输出:a^b 33#注意:指数为不小于1的整数 34#变量:%ebx:存底数 35#       %ecx:存指数 36#       -4(%ebp):存当前结果 37#       %eax:临时存储 38.type power, @function 39power:    pushl %ebp         #暂存%ebp的值于栈中 40          movl %esp, %ebp    #将%ebp指向局部变量存储区域的开始 41          subl $4, %esp      #开辟局部不变量存储区域 42           43          #从栈中获取参数,注意4(%ebp)存储“调用函数”的地址 44          movl 8(%ebp), %ebx      #底数 45          movl 12(%ebp), %ecx     #指数 46          movl %ebx, -4(%ebp)    #存储结果,注意:由于栈空间是从“高地址”区域向“低地址”区域移动 47power_loop_start:    cmpl $1, %ecx   #如果是1次方,则结束循环乘法 48                     je end_power 49                      50                     movl -4(%ebp), %eax 51                     imull %ebx, %eax      # %eax = %eax * %ebx 52                     movl %eax, -4(%ebp)    #存回栈中 53                      54                     decl %ecx            #指数递减 55                     jmp power_loop_start 56end_power:    movl -4(%ebp), %eax 57              movl %ebp, %esp 58              popl %ebp 59              ret
说明:

1、由于返回给程序的状态码需不大于255,所以计算的结果不能过大

2、.type power, @function指令告诉连接器:将符号power作为函数处理。

4、递归函数——程序2

问题背景:计算某个整数的阶乘。由于每个函数都有自己的“栈帧”,所以当函数调用自己的时候,使用局部数据空间不会互相干扰。

1#目标:计算某个给定数字的阶乘。程序将使用递归思想 2#变量:%ebx作为临时变量 3.section .data 4.section .text 5.globl _start 6.globl factorial #通过该项,可将该函数共享给其他程序调用 7 8_start :    pushl $4    #需要计算阶乘的整数 9            call factorial  #调用函数,计算阶乘 10            addl $4, %esp   #清空“被调函数”开辟的存储局部变量的空间 11            movl %eax, %ebx #将存储在%eax中的factiorial的返回值,作为状态字存储在%ebx中 12             13            movl $1, %eax 14            int 0x080 15#此为实际的函数定义 16.type factorial, @funciton 17factorial:    pushl %ebp          #初始化局部存储空间    18              movl %esp, %ebp 19               20              movl 8(%ebp), %eax  #4(%ebp)存返回地址,8(%ebp)存第一个参数,即某个整数 21               22              cmpl $1, %eax       #为1,则退出阶乘的计算 23              je end_factorial 24               25              decl %eax            #大于1,则递归调用该函数 26               27              pushl %eax            #与上面的指令——pushl $4相呼应 28              call factorial 29               30              #核心计算代码 31              movl 8(%ebp), %ebx 32              imull %ebx, %eax     #%ebx存储上一次运算的结果,%eax存储当前整数, 33end_factorial:    movl %ebp, %esp 34                  popl %ebp 35                  ret
程序说明:

.type指令告诉链接器factorial为一个函数。

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )