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。

110257_OUYT_438386.png

#示例:演示进入被调函数的代码
#说明: 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为一个函数。







转载于:https://my.oschina.net/u/438386/blog/542393

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值