函数主要由如下几个成分组成:函数名、函数参数、局部变量、静态变量、全局变量、返回地址、返回值架构
(1)函数参数及几个变量:这是在逻辑上对函数的涉及到的数据进行规划,实际上当前运行的指令只能经过直接、间接、当即数三种方式访问数据。函数
(2)返回地址:在汇编语言中,其实是某个指令的地址,即IP寄存器、程序段标号等存储或表明的地址。oop
(3)返回值:程序只能有一个返回值,具体表现为该返回值:存储在某个寄存器中,存储在某个内存单元中。spa
在内存的具体实现中,“栈底”位于“高地址”区域,“栈顶”位于“低地址”区域,其中%ess为栈的段基址,%esp为栈顶指针(注意:栈顶非空)。当进行“压栈”操做时,%esp的值由大到小变化;当进行“弹栈”操做时,%esp的值由小到大变化。指针
同时,因为当前机器为32 位机,所以,每次%esp都会跨过4 byte。
code
C约定的汇编调用其实是利用“栈”暂存信息:被调用函数的地址、被调用函数的参数、调用函数的地址。当前执行的程序调用某个函数时,进行以下操做:递归
(0)为了防止寄存器中的数据被破坏,在进入“被调用函数”以前,须要在当前执行的函数中,将全部寄存器的值压栈保存。待返回当前函数时,再从新加载这些值,即常说的“恢复现场”。
索引
(1)逆序将被调用函数的“参数”压栈:如函数fun(para 1, para 2, para 3, para 4,....),则para 4最早入栈,para 1最后入栈。此时栈顶元素为para 1。
内存
(2)将当前的IP地址压栈:该地址为返回地址,当被调用函数结束执行后,利用ret(return的缩写)指令,返回到调用函数ci
(3)将当前%ebp(基址指针寄存器)值压栈
(4)movl %esp , %ebp:此时,正如“基址指针寄存器”代表的,能够经过%ebp来根据“基址寻址”方式对“被调用函数”中的局部变量进行访问。如:-8(%ebp),在x86架构中,采用%ebp进行基址寻址较使用其余寄存器快。
(5)修改%esp值,为“被调用函数”开辟栈空间,存储局部变量——能够获得被调函数的局部变量空间的范围为:%ebp~%esp。
#示例:演示进入被调函数的代码 #说明: 一、被调函数的参数保存在数据域的item标签下,此处为索引,实际状况不会这样 # 二、被调函数的标签为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
在进入“被调函数”前,必定要将当前的寄存器值暂存在栈中,从而保证“被调函数”有充足的寄存器可使用。若是要在“被调函数”保存寄存器,则破坏了函数之间的封闭性。调用函数和被调函数之间,必定只能经过全局变量、被调函数的参数进行通讯,不然,将不易于程序的管理。
#目的:本程序计算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
一、因为返回给程序的状态码需不大于255,因此计算的结果不能过大
二、.type power, @function指令告诉链接器:将符号power做为函数处理。
问题背景:计算某个整数的阶乘。因为每一个函数都有本身的“栈帧”,因此当函数调用本身的时候,使用局部数据空间不会互相干扰。
#目标:计算某个给定数字的阶乘。程序将使用递归思想 #变量:%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为一个函数。