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

Wesley13
• 阅读 372

第四章 函数的工作原理

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。

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

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

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

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

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

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

movl %ebp, %esp
popl %ebp
ret

3、对于寄存器

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

3、程序1:

#目的:本程序计算2^3+5^2
#程序所有内容存入寄存器中,数据段无数据
#变量说明:%eax存储函数power的计算结果,并作为返回值,返回给“调用函数”

.section .data
.section .text
.globl _start
_start:    #计算第一个加数
           pushl $3     #压入第二个参数,指数
           pushl $2     #压入第一个参数,底数
           call power   #调用函数
           
           addl $8, %esp #清空“被调函数”存储局部变量的栈空间
           pushl %eax     #将第一个结果压入栈中
           
           #计算第二加数
           pushl $2
           pushl $5
           call power
           
           addl $8, %esp     #清空“被调函数”的栈空间
           popl %ebx        #将第一个结果弹栈,存入%ebx中。第二个结果已经存入%eax中
           
           addl %eax, %ebx  #两个结果相加,作为返回给系统的状态之,存储在%ebx中
           
           movl $1, %eax    #调用中断,退出程序
           int 0x80
    
#目的:计算一个整数的幂
#输入:参数1:底数a
#      参数2:幂b
#输出:a^b
#注意:指数为不小于1的整数
#变量:%ebx:存底数
#       %ecx:存指数
#       -4(%ebp):存当前结果
#       %eax:临时存储
.type power, @function
power:    pushl %ebp         #暂存%ebp的值于栈中
          movl %esp, %ebp    #将%ebp指向局部变量存储区域的开始
          subl $4, %esp      #开辟局部不变量存储区域
          
          #从栈中获取参数,注意4(%ebp)存储“调用函数”的地址
          movl 8(%ebp), %ebx      #底数
          movl 12(%ebp), %ecx     #指数
          movl %ebx, -4(%ebp)    #存储结果,注意:由于栈空间是从“高地址”区域向“低地址”区域移动
power_loop_start:    cmpl $1, %ecx   #如果是1次方,则结束循环乘法
                     je end_power
                     
                     movl -4(%ebp), %eax
                     imull %ebx, %eax      # %eax = %eax * %ebx
                     movl %eax, -4(%ebp)    #存回栈中
                     
                     decl %ecx            #指数递减
                     jmp power_loop_start
end_power:    movl -4(%ebp), %eax
              movl %ebp, %esp
              popl %ebp
              ret
说明:

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

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

4、递归函数——程序2

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

#目标:计算某个给定数字的阶乘。程序将使用递归思想
#变量:%ebx作为临时变量
.section .data
.section .text
.globl _start
.globl factorial #通过该项,可将该函数共享给其他程序调用

_start :    pushl $4    #需要计算阶乘的整数
            call factorial  #调用函数,计算阶乘
            addl $4, %esp   #清空“被调函数”开辟的存储局部变量的空间
            movl %eax, %ebx #将存储在%eax中的factiorial的返回值,作为状态字存储在%ebx中
            
            movl $1, %eax
            int 0x080
#此为实际的函数定义
.type factorial, @funciton
factorial:    pushl %ebp          #初始化局部存储空间   
              movl %esp, %ebp
              
              movl 8(%ebp), %eax  #4(%ebp)存返回地址,8(%ebp)存第一个参数,即某个整数
              
              cmpl $1, %eax       #为1,则退出阶乘的计算
              je end_factorial
              
              decl %eax            #大于1,则递归调用该函数
              
              pushl %eax            #与上面的指令——pushl $4相呼应
              call factorial
              
              #核心计算代码
              movl 8(%ebp), %ebx
              imull %ebx, %eax     #%ebx存储上一次运算的结果,%eax存储当前整数,
end_factorial:    movl %ebp, %esp
                  popl %ebp
                  ret
程序说明:

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

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
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
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Stella981 Stella981
2年前
JS 对象数组Array 根据对象object key的值排序sort,很风骚哦
有个js对象数组varary\{id:1,name:"b"},{id:2,name:"b"}\需求是根据name或者id的值来排序,这里有个风骚的函数函数定义:function keysrt(key,desc) {  return function(a,b){    return desc ? ~~(ak
Stella981 Stella981
2年前
HIVE 时间操作函数
日期函数UNIX时间戳转日期函数: from\_unixtime语法:   from\_unixtime(bigint unixtime\, string format\)返回值: string说明: 转化UNIX时间戳(从19700101 00:00:00 UTC到指定时间的秒数)到当前时区的时间格式举例:hive   selec
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这