汇编

谈到汇编必须了解CPU和内存,可以这么理解:

汇编即在寄存器和寄存器 或 寄存器和内存 之间来回移动数据。

CPU 包含 寄存器,控制器,运算器(ALU) 和 时钟四部分组成。我们关系的是寄存器,因此可以看成CPU是寄存器的集合体。

CPU指定执行过程:

  • 取指令: 将内存中的指令读入CPU寄存器,程序计数寄存器存储下一条指令的地址
  • 指令译码:在取指令后完成后,立马进入指令译码阶段,在指令译码阶段,指令译码器按照预定的指令格式,对取回的指令进行拆分和解释,识别区分不同指令类别以及各种获取操作数的方法
  • 执行指令:完成指令所规定的各种操作
  • 访问取数:根据指令地址码,得到操作数在主存中的地址,并从主存中读取该操作数用于运算
  • 结果写回:将执行阶段的运行结果写回到某种存储形式,结果数据经常被写到cpu的内部寄存器中,以便后续的指令快速存取。

inter格式和AT&T格式

在格式上,汇编语言主要分为Inter格式和AT&T格式。两者的主要区别可以从寄存器的名字上看出。AT&T相较于Inter格式更加复杂,就像PHP有很多$符号一样,不能给人一种简洁的感觉。

inter格式 AT&T格式 解释
push 1 pushl %eax 将eax寄存器内容入栈
add eax, 1 addl $1, %eax 将寄存器eax中的值加1存在eax中
mov eax, [ebp-4] movl -4(%ebp), %eax 将ebp中地址往前4个单元的值放入eax寄存器中

两者在看的时候mov的顺序不同,比如Inter格式是[eax]=[ebp - 4],返回值返回到指令mov后的第一个值,而AT&T却是返回到后面这个值

寄存器

x86 cpu主要寄存器

esp 指向栈顶【高地址向低地址扩展】

eax 是一个通用的寄存器,根据 cdesl 调用约定(即 C 语言调用约定),在函数返回时,返回值必须保存在 eax 寄存器中,交给调用者处理

rbp寄存器 是ebp寄存器64位扩展,ebp寄存器扩展基址指针寄存器(extended base pointer)  其内存放一个指针,该指针指向系统栈最上面一个栈帧的底部。

BP为基指针(Base Pointer)寄存器,用它可直接存取堆栈中的数据,它的作用是在调用函数时保存ESP使函数结束时可以正确返回;加E(enhance)代表增强型寄存器,用于32位数据处理

eip: 成为程序计数器寄存器(Program Counter)程序计数器控制着程序的流程。用于存放下一条指令的地址,在函数最后一条指令之后,eip中能正确存放函数之后的下一条指令的位置,也就返回地址

CS: code segment , 代码段寄存器,存储当前代码段的段地址
DS:data segment,数据段寄存器,存储当前数据段的段地址
SS:stack segment, 堆栈段寄存器,存储当前堆栈段的段地址
PSW: program status word,程序状态字寄存器

CFI指令:Call Frame Instruction

cfi表示调用框架指令,cfi提供的调用框架信息,为实现堆栈回绕(stack unwiding)或异常处理(exception handling)提供了方便

常见的CFI指令:

  • .cfi_startproc 用在每个函数的开始,用于初始化一些内部数据结构

  • .cfi_endproc 在函数结束的时候用与.cfi_startproc相配合使用

  • .cfi_def_cfa_offset offset 修改offset的规则,在一个已定义的寄存器计算CFA地址时会加上这个绝对编译量

    .cfi_def_cfa_offset 8 表示此处距离CFA地址为8字节,CFA表示标准框架地址,它被定义为在前一个调用框架中调用当前函数时的栈顶指针。

  • .cfi_offset register, offset 将寄存器的值保存到offset处

    .cfi_offset 5, -8 表示将5号寄存器原来的值保存到距离CFA-8的位置

  • .cfi_def_cfa_register register

    .cfi_def_cfa_register 5 表示从这里开始,使用ebp作为计算CFA的基址寄存器

关于AT&T汇编

AT&T汇编语法总结以下5条规则:

  • 寄存器通过在名称前加%前缀引用。如:为使用eax寄存器,汇编代码中将使用%eax.

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

  • 操作数的长度由汇编语句的后缀指定。 b代表byte,w代表word,l代表long, q代表4字节(64位), t代表10字节(80位浮点)。在IA-32上,将一个长整型从eax寄存器移动到ebx寄存器中,需指定 movl %eax, %ebx

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

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

push xxx : push指令用于将运算子放入stack,操作流程为: 先取出ESP寄存器里面的地址,将其减去对应的对应字节,然后将新地址写入ESP寄存器

mov 8(%esp), %eax : 将ESP寄存器里地址加入8个字节,得到一个新地址,然后按照这个地址在stack取出数据放入eax寄存器中

add %eax, %ebx : 将eax寄存器中的值加上EBX寄存器中值,再将这个结果写入ebx寄存器中

pop %ebx : 取出stack中最近一个写入的值(即低位地址的值),并将这个值写入ebx寄存器中, 同时ESP寄存器的地址加上对应的字节,即回收相应的字节。

jump 地址: 表示跳转到相应地址

enter 指令等效于以下汇编指令:
pushl %ebp # 将%ebp压栈
movl %esp %ebp # 将%esp保存到%ebp

leave指令等效于:
movl %ebp, %esp
popl %ebp

call 指令,如 call foo等效于:
pushl %eip 将返回地址压入栈
movl f, %eip 将f地址传入eip

ret 指令等效于:
popl %eip 将上次保存的返回地址弹出放入eip寄存器中
jump 处理器根据eip无条件跳转到相应位置获取新的指令

实例

源码:

#include <stdio.h>

int add(int a, int b) {
   return a + b;
}
int main(int argc, char *argv[]) {
   int i,j;
   i=2;
   j=3;
   int res =  add(i, j);
   printf("res=%d\n", res);
   return 0;
}

执行 gcc -S v14.c -o v14.s 生成编译语言文件, 摘取核心部分

add:
.LFB0:
        .cfi_startproc  # cfi开始
        pushq   %rbp  # 将堆栈基地址寄存器中的值写入栈中,esp往下移动4个字节
        .cfi_def_cfa_offset 16  # 表明cfa标准框架地址为往下偏移16个字节
        .cfi_offset 6, -16   # 将6号寄存器的值写入CFA-16的位置
        movq    %rsp, %rbp    # 将rsp寄存器的值移动到rbp中
        .cfi_def_cfa_register 6  # 此处开始使用6号寄存器的地址作为基址
        movl    %edi, -4(%rbp)  # 将edi寄存器中值存入rbp寄存器中地址-4的内存
        movl    %esi, -8(%rbp)  # 将esi寄存器中值存入rbp寄存器中地址-8的内存
        movl    -8(%rbp), %eax  # 将rbp寄存器中地址-8内存的值移动到eax寄存器
        movl    -4(%rbp), %edx # 将rbp寄存器中地址-4内存的值移动到edx寄存器
        addl    %edx, %eax  # 将edx寄存器与eax寄存器进行加法运算,并把内容存入eax寄存器
        popq    %rbp  # 弹出栈顶数据,存入rbp寄存器,即恢复rbp寄存器
        .cfi_def_cfa 7, 8  # 现在重新定义CFA,将7号寄存器所指位置加8字节
        ret  # 返回
        .cfi_endproc
main:
.LFB1:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp   # 
        .cfi_def_cfa_register 6
        subq    $32, %rsp  # 将rsp往下移动32个字节
        movl    %edi, -20(%rbp)  # 将edi寄存器中的内容保存到rbp-20的内存
        movq    %rsi, -32(%rbp)  # 将rsi寄存器中的内容保存到rbp-32的内存
        movl    $2, -4(%rbp)  # 将2 移动到rbp-4的内存
        movl    $3, -8(%rbp) # 将3 移动到 rbp -8的内存
        movl    -8(%rbp), %edx # 将rbp-8内存中的值复制到edx寄存器
        movl    -4(%rbp), %eax # 将rbp-4内存中的值复制到eax寄存器
        movl    %edx, %esi  # 将edx寄存器的值移动到esi寄存器
        movl    %eax, %edi # 将eax寄存器的值移动到edi寄存器
        call    add  # 调用add
        movl    %eax, -12(%rbp)  # 将eax寄存器中的值移动到rbp-12的内存
        movl    -12(%rbp), %eax  # 将rbp-12的内存值移动到eax
        movl    %eax, %esi  # 将eax移动到esi寄存器
        movl    $.LC0, %edi  
        movl    $0, %eax
        call    printf  # 调用printf
        movl    $0, %eax
        leave
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
文档更新时间: 2021-03-11 13:42   作者:周国强