`

从汇编角度分析C语言的过程调用

阅读更多

原文出自【听云技术博客】:http://blog.tingyun.com/web/article/detail/1132

基本术语定义

1.系统栈(system stack)是一个内存区,位于进程地址空间的末端。

2.在将数据压栈时,栈是自顶向下增长的,该内存区用于函数的局部变量提供内存。它也支持在调用函数时传递参数。

3.如果调用了嵌套的过程,栈会自上而下增长,并接受新的活动记录(activation record)来保存一个过程所需的所有数据。

4.当前执行过程的活动记录,由标记顶部位置的帧指针(frame point)和标记底部位置的栈指针(stack point)定义。

5.在过程执行时,虽然其顶部的限制是固定的,但底部的限制是可以扩展的(在需要更多内存空间时)。

分析栈帧(分析如下)

上图第2个栈帧的分析如下: 

1、在栈帧顶部是返回地址,以及保存的旧的帧指针。返回地址指定了当前过程结束时代码的控制流转向的内存地址,而保存的旧的帧指针则是前一个活动记录的帧指针。在当前过程结束后,该帧指针的值可用于重建调用过程的栈帧,在试图调试调用栈回溯时,这一点很重要。

2、活动记录的主要部分是为过程调用局部变量分配的内存空间。在C中,这种变量也称为自动变量(automatic variable)。

3、在函数调用时,以参数形式传递到函数的值,存储在栈的底部。

4、所有常见的计算机体系结构都提供了以下两个栈操作指令:

  • push指令将一个值放置在栈上,并将栈指针esp减去该值所占用的内存字节数。栈的末端下移到更低的地址;

  • pop指令从栈中弹出一个值,并相应增加栈指针esp的值,也就是说,栈的末端上移了。

5、一般体系结构另外提供两个指令,用于调用和退出函数(自动返回到调用过程),它们也会自动操作栈:

  • call指令将指令指针的当前值压栈,跳转到被调用函数的起始地址。  call 指令 :在AT&T汇编中,call foo(foo是一个标号)等效于以下汇编指令: pushl %eip ,movl f, %eip  ;   

  • return指令从栈上弹出返回地址,并跳转到该地址。过程的实现必须将rerurn作为最后一条指令,由call放置在栈上的返回地址位于栈的底部(实际上是上一个活动记录的底部,当前活动记录的顶部)。  ret指令: 在AT&T汇编中,ret等效于以下汇编指令: popl %eip

过程调用两个组成步骤

1、在栈中建立参数列表。传递到被调用函数的第一个参数最后入栈(从右到左)。这使得C中可以传递可变数目的参数,然后将其从栈上逐一弹出(pop)。

2、调用call,这将指令指针的当前值(call之后的下一条指令)压栈,代码的控制流转向被调用的函数。被调用的过程负责管理帧指针ebp,需要执行下列步骤: 

  • 前一个帧指针压栈,因而栈指针下移。

  • 将栈指针的当前值copy给帧指针,标记当前执行函数的栈区的起始位置。

  • 执行当前函数的代码。

  • 在函数结束时,存储的旧帧指针位于栈的底部。其值从栈弹出到帧指针寄存器(ebp),使之指向前一个函数的栈区起始位置。现在,对当前函数执行call指令时压栈的返回地址位于栈低。

  • 调用return,将返回地址从栈弹出。cpu转移到返回地址,代码的控制流也返回到调用函数。

具体C 语言例子分析

初看起来,这种方法似乎有些混乱,因此,我们先看一个简单的C语言例子:

在IA-32系统上,汇编代码本身必须是AT&T表示法给出。 

AT&T汇编语法总结为以下5条规则,就足够了。 

1.寄存器通过在名称前加百分号(%)前缀引用。example:为使用eax寄存器,汇编代码中将使用%eax。(如果在C中内联汇编的话,C代码必须指定两个百分号,才能在转给汇编器的输出中形成一个百分号)。

2.源寄存器总是在目的寄存器之前指定。 example,在mov语句中,这意味着 mov a,b 将 寄存器a中的值 内容copy到寄存器b中。

3.操作数的长度由汇编语句的后缀指定。b代步byte,w代表word,l代表long。在IA-32上,将一个长整型从eax寄存器移动到ebx寄存器中,需要指定movl %eax,%ebx。

4.间接内存引用(指针反引用)需要将寄存器包含在括号中,example:movl(%eax),%ebx 将寄存器eax的值指向的内存地址中的长整型copy到ebx寄存器中。

5.offset(register)指定寄存器值与一个偏移量联用,将偏移量加到寄存器的实际值上。example: 8(%eax)指定将eax+8用作一个操作数。该表示法主要用于内存访问,例如指定与栈指针或帧指针的偏移量,以访问某些局部变量。

我们来分析一下 main.s 汇编代码: 

1.从main 主函数开始分析. 在IA-32系统中,ebp寄存器用于帧指针(栈顶),pushl  %ebp 将该ebp寄存器中的值压入系统栈上最低位置,这导致栈顶指针向下移动4byte,这是因为IA-32系统上需要4byte来表示一个指针(pushl中的后缀l,在AT&T汇编中表示一个long型)。

2.第3行,movl   %esp, %ebp  将esp(栈指针)寄存器 的值 copy到ebp(帧指针)寄存器中;把当前的栈指针作为本函数的帧指针。

3.第4行,subl $24,%esp 从栈指针减去0x18 byte,使得栈指针下移,将栈的空间增大了0x18=24byte;

  • 调整栈指针,为局部变量保留空间。局部变量必须放置在栈上,在C代码中,a与b两个局部变量,两者都是整型变量,在内存中都需要4个byte。

  • 因为栈的前4个byte保存了 帧指针的旧值(上一个活动记录),编译器将接下来的两个 4byte内存分配给了这两个局部变量。

  • ebp - 0xC 存着局部变量a的值 3    ; ebp - 0x8 存着局部变量b的值 4 (这里可以看到参数是从右到左 压入栈的)。

4.第5行 ,第6行 movl $0x3, -0xC(%ebp)    movl $0x4, -0x8(%ebp) : 为了向分配的内存空间设置初始值(对应C中 局部变量的初始化),编译器使用了处理器的指针反引用选项。 这两天指令通知编译器,引用“帧指针减12”得到的值 在内存中指向的位置。使用mov指令将值3 写入该位置。

  • 编译器接下来用同样的方法处理第2个局部变量,其在栈的位置稍低,ebp - 0x8 (ebp - 8byte) 位置 ,值为4。

5.第7行,第8行设置第2个参数(b),第9行,第10行负责设置第1个参数(a)。     movl    -8(%ebp), %eax   ;   movl    %eax, 4(%esp)  ; movl    -12(%ebp), %eax;    movl    %eax, (%esp)

  • 局部变量a和b必须用作即将调用的add过程调用的参数。编译器通过将适当的值放置在栈的末端来建立参数列表。

  • 如前所述,第一个参数在最低部。栈指针用于查找栈的末尾。

  • 内存中对应的位置通过指针反引用确定。将栈上的两个局部变量的值分别读入eax寄存器,然后将eax的值写入参数列表中对应的位置。(一般情况)

6.上图描述了 add()函数调用前后,栈的状态。现在可以使用call 指令调用add()函数。call指令 将eip(指令指针寄存器)压入栈,代码控制流在add例程的开始处恢复执行。

  • 根据调用约定,例程首先将此前的帧指针(ebp)压入栈,并将栈指针(esp)赋值给 帧指针(ebp)。

  • 过程的参数可以根据帧指针(ebp)查找。编译器知道参数就在调用函数的活动记录末尾,而在当前活动记录开始处又存储了两个4byte的值(返回地址,旧帧指针)。因此参数可以通过反引用ebp+8和ebp+12访问。

  • add 指令用于 加法,而eax寄存器用作工作空间。结果值就保存在该寄存器中,使它可以传递给调用函数(这里是main())。

  • 为了返回到调用函数,需要执行以下两个操作: <a>使用pop将存储的帧指针(ebp)从栈弹出到ebp寄存器。栈帧的顶端重新恢复到main()的设置;<b>ret将返回地址从栈弹出到 eip(指令指针)寄存器,控制流转向该地址。

7.因为main()中还使用了另一个局部变量(ret)来存储add()函数的返回值,返回后需要将eax寄存器的值 copy 到ret在栈上的位置。

总结

关于AT&T汇编

enter指令 
在AT&T汇编中,enter等效于以下汇编指令: 
pushl %ebp # 将%ebp压栈
movl %esp %ebp # 将%esp保存到%ebp, 这两步是函数的标准开头
leave指令 
在AT&T汇编中,leave等效于以下汇编指令: 
movl %ebp, %esp
popl %ebp
call指令
在AT&T汇编中,call foo(foo是一个标号)等效于以下汇编指令: 
pushl %eip
movl f, %eip
ret指令
在AT&T汇编中,ret等效于以下汇编指令: 
popl %eip

(个人理解)汇编可以用一句话概括:汇编就是在(寄存器和寄存器)或 (寄存器和内存)之间来回move 数据;就是指:数据在内存和寄存器间来回流动,流动的越频繁就代表程序越复杂,比如office这样的大型软件。

从C语言层面分析: 

EBP-xx     一般 是局部变量

EBP+xx   一般都是参数 

EBP+4  返回地址 ,制高点,   很多攻击都是攻击这里, 杀毒软件,这里是重点会扫描。

C函数堆栈中分配的空间,并不会清零,所以在写C代码的时候,局部变量一定要初始化赋值。

参数的传递形式、传递顺序已经栈平衡并不是固定的(不同的函数调用约定)。

关于 寄存器 与内存的区别:   

寄存器位于cpu内部,执行速度快,但比较贵。

内存速度相对较慢,成本低,所以容量能做很大。

寄存器和内存没有本质区别,都是用于存储数据的容器,都是定宽的。

寄存器常用的8个通用寄存器 :EAX,ECX,EDX,EBX,   ESP, EBP, ESI, EDI.

计算机中的几个常用计量单位:BYTE, WORD, DWORD :BYTE(字节) = 8bit ; WORD (字 ) = 16bit ; DWORD (双字)=32bit; 

内存的数量特别庞大,无法每个内存单元都命名一个名字,所以用编号来替代。

我们称计算机CPU是32bit或者64bit,有很多书上说之所以叫32bit计算机是因为寄存器的宽度是32bit,这是不准确的,因为还有很多寄存器是大于32bit的。

0
0
分享到:
评论

相关推荐

    单片机汇编语言和C语言混合调用资料及实例

    总的来说,单片机汇编语言和C语言的混合调用是提高程序效率和灵活性的有效手段。理解两种语言的特性和接口机制,是实现高效混合编程的关键。通过不断实践和学习,开发者可以更好地驾驭这两种语言,从而在单片机编程...

    采用汇编语言对C语言函数调用方法求平均数

    使用汇编语言对C语言函数调用方法求平均数 在计算机编程中,汇编语言和C语言都是常用的编程语言。汇编语言是一种低级语言,它可以直接控制计算机硬件,具有高效、灵活和可靠的特点。但是,汇编语言的编程难度较高,...

    c调用汇编实例 学习单片机c语言调用汇编

    c调用汇编实例 学习单片机c语言调用汇编 c调用汇编实例 学习单片机c语言调用汇编 c调用汇编实例 学习单片机c语言调用汇编 c调用汇编实例 学习单片机c语言调用汇编 ..................

    CCS 中C语言程序调用线性汇编

    在CCS中,C语言调用汇编代码时,参数的传递遵循一定的规则: - 第1、3、5、7、9个参数通过A4、A6、A8、A10、A12寄存器传递。 - 第2、4、6、8、10个参数通过B4、B6、B8、B10、B12寄存器传递。 - 第10个及之后的参数...

    8051C语言调用汇编详细注释源代码

    这就涉及到C语言调用汇编的过程。 在8051C语言调用汇编的过程中,主要涉及以下几个关键知识点: 1. **函数调用约定**:C语言与汇编语言的交互主要通过函数调用来完成。在8051上,函数的参数传递和返回值的处理需要...

    利用C语言调用汇编子程序

    C语言调用汇编子程序的过程大致分为以下几个步骤: 1. **定义接口**:在C语言中声明一个函数,这个函数的实现将在汇编代码中提供。函数声明包括参数类型和返回值类型,例如: ```c extern int add(int a, int b)...

    C语言函数调用汇编语言函数[归纳].pdf

    C语言函数调用汇编语言函数知识点总结 C语言函数调用汇编语言函数是软件开发中的一种常见技术。本文将对C语言函数调用汇编语言函数进行总结,详细介绍相关知识点。 一、C语言函数调用汇编语言函数的定义 C语言...

    微处理器:从汇编语言到C语言.rar

    它以英文版的形式呈现,详细阐述了从微处理器的底层汇编语言编程到更高级的C语言编程的过渡过程,为读者提供了一条清晰的学习路径。 汇编语言,作为与硬件最为接近的编程语言,其每一条指令都与微处理器的机器码相...

    C语言中调用汇编程序

    总结起来,C语言调用汇编程序的过程涉及以下步骤: 1. 在C代码中声明汇编实现的函数。 2. 编写汇编代码实现该函数,遵循适当的函数调用约定。 3. 使用汇编器将汇编代码编译为对象文件。 4. 使用C编译器编译C源代码,...

    汇编语言和c语言矩阵A*B+C*D的运算

    本文将从两个方面对其进行讲解:C语言实现和汇编语言实现。 C语言实现: 在C语言中,实现矩阵A*B+C*D的运算可以使用以下步骤: 1. 定义矩阵A、B、C和D,并将其初始化为全局变量。 2. 实现矩阵乘法函数multi,用于...

    c语言调用汇编子程序

    本实验旨在探讨如何在C语言中调用汇编子程序,并处理输入参数和返回结果。C语言与汇编的混合编程使得我们可以利用C语言的易读性和汇编的高效性,以解决复杂的问题。 实验的目的是编写一个C语言程序,它调用一个汇编...

    ARM汇编 C语言 C++ 相互调用

    - C语言调用汇编程序时,需要先通过extern声明汇编程序模块,并确保参数传递遵循ATPCS(ARM Procedure Call Standard)规则。参数通过寄存器R0-R3传递前四个参数,后续参数通过堆栈传递。调用汇编程序使用B、BL指令...

    利用反汇编手段解析c语言函数

    本文将详细介绍如何使用反汇编技术来深入解析C语言函数的工作原理,包括函数调用过程中的堆栈动态分配、变量初始化等关键细节。 #### 二、基础知识 在深入探讨之前,我们需要了解一些基础概念: 1. **反汇编**:...

    ARM中C语言调用汇编语言方法浅析[1].doc.doc

    最后,C语言调用汇编函数通常是通过函数原型声明和链接过程来实现的。汇编函数在C代码中声明为`extern`,然后在汇编文件中定义。这样,C编译器知道函数的存在,但在链接阶段会查找汇编函数的实际实现。这种方式适用...

    汇编语言实现对引脚底层的配置,使用C语言调用汇编语言函数进行点灯.zip

    汇编语言实现对引脚底层的配置,使用C语言调用汇编语言函数进行点灯.zip汇编语言实现对引脚底层的配置,使用C语言调用汇编语言函数进行点灯.zip汇编语言实现对引脚底层的配置,使用C语言调用汇编语言函数进行点灯....

    如何在C语言程序中调用汇编语言1

    总的来说,C语言调用汇编语言是为了结合两者的优点:C语言的高级抽象和易读性,以及汇编语言的效率和对硬件的直接控制。混合编程使得开发者能够在需要的时候深入底层,从而优化性能关键的部分,同时保持大部分代码的...

    在C语言函数中调用汇编语言程序实现字符串的拷贝

    在C语言中,我们可以这样调用这个汇编函数: ```c #include extern int str_copy(char *dst, const char *src); int main() { const char *srcstr = "abcdefghij"; char dststr[20]; int len = str_copy...

    浅析ARM汇编 C语言 C++ 相互调用的方法

    ### 浅析ARM汇编 C语言 C++ 相互调用的方法 #### 一、引言 在嵌入式开发领域,ARM架构因其低功耗、高性能的特点被广泛应用。随着软件开发需求的多样化,不同编程语言间的交互成为了一个重要的技术问题。其中,ARM...

Global site tag (gtag.js) - Google Analytics