精华内容
下载资源
问答
  • linux内嵌汇编语言

    2018-09-03 10:14:52
    虽然Linux的核心代码大部分是用C语言编写的,但是不可避免的其中还是有一部分是用汇编语言写成的。有些汇编语言代码是直接写在汇编源程序中的,特别是Linux的启动代码部分;还有一些则是利用gcc的内嵌汇编语言嵌在...
  • linux汇编语言

    2012-12-31 09:36:35
    linux 下的汇编语言教材,编译及内核学习必备
  • Linux 汇编语言开发指南Linux 汇编语言开发指南Linux 汇编语言开发指南
  • Linux_汇编语言开发指南.doc
  • Linux汇编语言及嵌入式汇编Linux汇编语言及嵌入式汇编
  • Linux 汇编语言

    2009-04-22 15:35:50
    Linux汇编语言 by Bjorn Chambless
  • Linux下的汇编语言

    2015-12-12 16:26:06
    Linux下的汇编语言 AT&T的386汇编语言
  • Linux GNU汇编语言

    2011-11-14 14:02:20
    不错的介绍GNU汇编的文档,通过这个文档可以了解GNU汇编的基本知识。
  • linux 汇编语言设计

    2013-01-09 14:26:53
    linux下的汇编语言的书 pdf格式 详细介绍了Linux下汇编的语法,和相应的应用
  • Linux 汇编语言开发指南 汇编语言的优点是速度快 可以直接对硬件进行操作 这对诸如图形处理等关键应用是非常重要的Linux 是一个用 C 语言开发的操作系统 这使得很多程序员开始忘记在 Linux 中还可以直接使用汇编这一...
  • linux汇编语言教程

    热门讨论 2009-05-12 12:20:43
    linux下最好的汇编教程,适合需要学习linux汇编linux内核的人使用
  • Linux汇编语言开发指南 Linux汇编语言开发指南
  • linux汇编语言开发

    千次阅读 2018-10-15 21:42:42
    但是这并不表示汇编语言就已经没有用武之地了,通过阅读汇编代码,有助于我们理解编译器的优化能力,并分析代码中隐含的低效率,所以能够阅读和理解汇编代码也是一项很重要的技能。因为我平时都是在linux环境下工作...

    汇编语言是直接对应系统指令集的低级语言,在语言越来越抽象的今天,汇编语言并不像高级语言那样使用广泛,仅仅在驱动程序,嵌入式系统等对性能要求苛刻的领域才能见到它们的身影。但是这并不表示汇编语言就已经没有用武之地了,通过阅读汇编代码,有助于我们理解编译器的优化能力,并分析代码中隐含的低效率,所以能够阅读和理解汇编代码也是一项很重要的技能。因为我平时都是在linux环境下工作的,这篇文章就讲讲linux下的汇编语言。

    一、汇编语法风格

    汇编语言分为intel风格和AT&T风格,前者被Microsoft Windows/Visual C++采用,Linux下,基本采用的是AT&T风格汇编,两者语法有很多不同的地方。

    1. 寄存器访问格式不同。在 AT&T 汇编格式中,寄存器名要加上 '%' 作为前缀;而在 Intel 汇编格式中,寄存器名不需要加前缀。例如:

    AT&T

    Intel

    pushl %eax

    push eax

    2. 立即数表示不同。在 AT&T 汇编格式中,用 '$' 前缀表示一个立即操作数;而在 Intel 汇编格式中,立即数的表示不用带任何前缀。例如:

    AT&T

    Intel

    pushl $1

    push 1

    3. 操作数顺序不同。在 Intel 汇编格式中,目标操作数在源操作数的左边;而在 AT&T 汇编格式中,目标操作数在源操作数的右边。例如:

    AT&T

    Intel

    addl $1, %eax

    add eax, 1

    4. 字长表示不同。在 AT&T 汇编格式中,操作数的字长由操作符的最后一个字母决定,后缀'b'、'w'、'l'分别表示操作数为byte、word和long;而在 Intel 汇编格式中,操作数的字长是用 "byte ptr" 和 "word ptr" 等前缀来表示的。例如:

    AT&T

    Intel

    movb val, %eax

    mov al, byte ptr val

    5. 寻址方式表示不同。在 AT&T 汇编格式中,内存操作数的寻址方式是 

    section:disp(base, index, scale)

    而在 Intel 汇编格式中,内存操作数的寻址方式为:

    section:[base + index*scale + disp]

    由于 Linux 工作在保护模式下,用的是 32 位线性地址,所以在计算地址时不用考虑段基址和偏移量,而是采用如下的地址计算方法:

    disp + base + index * scale

    由此分为以下几种寻址方式:

     

    Intel

    AT&T

    内存直接寻址

    seg_reg: [base + index * scale + immed32]

    seg_reg: immed32 (base, index, scale)

    寄存器间接寻址

    [reg]

    (%reg)

    寄存器变址寻址

    [reg + _x]

    _x(%reg)

    立即数变址寻址

    [reg + 1]

    1(%reg)

    整数数组寻址

    [eax*4 + array]

    _array (,%eax, 4)

    二、IA32寄存器

    1.通用寄存器

    顾名思义,通用寄存器是那些你可以根据自己的意愿使用的寄存器,但有些也有特殊作用,IA32处理器包括8个通用寄存器,分为3组

    1) 数据寄存器

    EAX 累加寄存器,常用于运算;在乘除等指令中指定用来存放操作数,另外,所有的I/O指令都使用这一寄存器与外界设备传送数据。

    EBX 基址寄存器,常用于地址索引

    ECX 计数寄存器,常用于计数;常用于保存计算值,如在移位指令,循环(loop)和串处理指令中用作隐含的计数器.
    EDX 数据寄存器,常用于数据传递。

    2) 变址寄存器

    ESI 源地址指针

    EDI 目的地址指针

    3) 指针寄存器

    EBP为基址指针(Base Pointer)寄存器,存储当前栈帧的底部地址。

    ESP为堆栈指针(Stack Pointer)寄存器,一直记录栈顶位置,不可直接访问,push时ESP减小,pop时增大。

    2. 指令指针寄存器

    EIP 保存了下一条要执行的指令的地址, 每执行完一条指令EIP都会增加当前指令长度的位移,指向下一条指令。用户不可直接修改EIP的值,但jmp、call和ret等指令也会改变EIP的值,jmp将EIP修改为目的指令地址,call修改EIP为被调函数第一条指令地址,ret从栈中取出(pop)返回地址存入EIP。

    三、函数调用过程

    函数调用时的具体步骤如下:

    1. 调用函数将被调用函数参数入栈,入栈顺序由调用约定规定,包括cdecl,stdcall,fastcall,naked call等,c编译器默认使用cdecl约定,参数从右往左入栈。

    2. 执行call命令。

    call命令做了两件事情,一是将EIP寄存器内的值压入栈中,称为返回地址,函数完成后还要到这个地址继续执行程序。然后将被调用函数第一条指令地址存入EIP中,由此进入被调函数。

    3. 被调函数开始执行,先准备当前栈帧的环境,分为3步

    pushl %ebp 保存调用函数的基址到栈中,

    movl %esp, %ebp 设置EBP为当前被调用函数的基址指针,即当前栈顶

    subl $xx, %esp 为当前函数分配xx字节栈空间用于存储局部变量

    4. 执行被调函数主体

    5. 被调函数结束返回,恢复现场,第3步的逆操作,由leave和ret两条指令完成,

    leave 主要恢复栈空间,相当于

    movl %ebp, %esp 释放被调函数栈空间

    popl %ebp 恢复ebp为调用函数基址

    ret 与call指令对应,等于pop %EIP,

    6. 返回到调用函数,从下一条语句继续执行

    我们来看两个具体例子,第一个求数组和,

    int ArraySum(int *array, int n)
    {
      int t = 0;
      for(int i=0; i<n; ++i) 
        t += array[i];
      return t;
    }
    
    int main() 
    {
      int a[5] = {1, 2, 3, 4, 5 };
      int sum = ArraySum(a, 5);
      return sum;
    }
     

    编译成汇编代码

    gcc -std=c99 -S -o sum.s sum.c

    gcc加入了很多汇编器和连接器用到的指令,与我们讨论的内容无关,简化汇编代码如下:

    ArraySum:
        pushl    %ebp
        movl    %esp, %ebp   
        subl    $16, %esp  //分配16字节栈空间
        movl    $0, -8(%ebp)  //初始化t
        movl    $0, -4(%ebp)  //初始化i
        jmp    .L2
    .L3:
        movl    -4(%ebp), %eax
        sall    $2, %eax  //i<<2, 即i*4, 一个int占4字节
        addl    8(%ebp), %eax  //得到array[i]地址,array+i*4
        movl    (%eax), %eax   //array[i]
        addl    %eax, -8(%ebp) //t+=array[i]
        addl    $1, -4(%ebp)
    .L2:
        movl    -4(%ebp), %eax   
        cmpl    12(%ebp), %eax  //比较i<n
        jl    .L3
        movl    -8(%ebp), %eax //return t; 默认eax存函数返回值
        leave
        ret
    
    main:
    .LFB1:
        pushl    %ebp
        movl    %esp, %ebp
        subl    $40, %esp       
        movl    $1, -24(%ebp) //初始化a[0]
        movl    $2, -20(%ebp) //初始化a[1]
        movl    $3, -16(%ebp) //初始化a[2]
        movl    $4, -12(%ebp) //初始化a[3]
        movl    $5, -8(%ebp)   //初始化a[4]
        movl    $5, 4(%esp)    //5作为第二个参数传给 ArraySum
        leal    -24(%ebp), %eax  //leal产生数组a的地址
        movl    %eax, (%esp)   //作为第一个参数传给ArraySum
        call    ArraySum
        movl    %eax, -4(%ebp)  //返回值传给sum
        movl    -4(%ebp), %eax  //return sum
        leave
        ret
     

    复制代码

    栈变化过程如下:

                             执行call指令前                                 执行call指令后

    从图中可以看出

    1. 数组连续排列,用move指令逐个赋值,读取数组元素方法是,用leal得到数组首地址,再计算偏移量

    2. 参数从右往左入栈

    3. gcc为了保证数据是严格对齐的,分配的空间大于使用的空间,有部分空间是浪费的

    下面这个例子说明了struct结构的实现方法,

    struct Point
    {
      int x;
      int y;
    };
    void PointInit(struct Point *p, int x, int y)
    {
      p->x = x;
      p->y = y;
    }
    
    int main() 
    {
      struct Point p;
      int x = 10;
      int y = 20;
      PointInit(&p, x, y);
      return 0;
    }
     

    复制代码

    编译成汇编代码,简化如下:

    PointInit:
        pushl    %ebp
        movl    %esp, %ebp
        movl    8(%ebp), %eax    //p的地址
        movl    12(%ebp), %edx  //x
        movl    %edx, (%eax)      //p->x=x
        movl    8(%ebp), %eax
        movl    16(%ebp), %edx  //y
        movl    %edx, 4(%eax)    //p->y=y
        popl    %ebp
        ret
    
    main:
        pushl    %ebp
        movl    %esp, %ebp
        subl    $28, %esp
        movl    $10, -8(%ebp)  //x=10
        movl    $20, -4(%ebp)  y=20
        movl    -4(%ebp), %eax
        movl    %eax, 8(%esp)
        movl    -8(%ebp), %eax
        movl    %eax, 4(%esp)
        leal    -16(%ebp), %eax  //取p地址&p
        movl    %eax, (%esp)
        call    PointInit
        movl    $0, %eax
        leave
        ret
     

    复制代码

    栈图就不画了,可以清楚地看出struct跟数组类似,连续排列,通过相对位移访问struct的成员,p->y与*(p+sizeof(p->x))有一样的效果。

    四、disassemble和objdump

    在linux下有两个跟汇编有重要关系的命令,一个是objdump,另一个是gdb中的disassemble。

    objdump帮助我们从可执行文件中反汇编出汇编代码,从而逆向分析工程。

    objdump -d sum

    部分汇编代码如下

    080483b4 <ArraySum>:
     80483b4:    55                       push   %ebp
     80483b5:    89 e5                    mov    %esp,%ebp
     80483b7:    83 ec 10                 sub    $0x10,%esp
     80483ba:    c7 45 f8 00 00 00 00     movl   $0x0,-0x8(%ebp)
     80483c1:    c7 45 fc 00 00 00 00     movl   $0x0,-0x4(%ebp)
     80483c8:    eb 12                    jmp    80483dc <ArraySum+0x28>
     80483ca:    8b 45 fc                 mov    -0x4(%ebp),%eax
     80483cd:    c1 e0 02                 shl    $0x2,%eax
     80483d0:    03 45 08                 add    0x8(%ebp),%eax
     80483d3:    8b 00                    mov    (%eax),%eax
     80483d5:    01 45 f8                 add    %eax,-0x8(%ebp)
     80483d8:    83 45 fc 01              addl   $0x1,-0x4(%ebp)
     80483dc:    8b 45 fc                 mov    -0x4(%ebp),%eax
     80483df:    3b 45 0c                 cmp    0xc(%ebp),%eax
     80483e2:    7c e6                    jl     80483ca <ArraySum+0x16>
     80483e4:    8b 45 f8                 mov    -0x8(%ebp),%eax
     80483e7:    c9                       leave  
     80483e8:    c3                       ret
     

    复制代码

    disassemble可以显示调试程序的汇编代码,用法如下

    disas 反汇编当前函数

    disas sum 反汇编sum函数

    disas 0x801234 反汇编位于地址 0x801234附近的函数

    disas 0x801234 0x802234 返汇编指定范围内函数

     

    博文转自:https://blog.csdn.net/q_l_s/article/details/52722667

    展开全文
  • linux汇编语言开发总结

    千次阅读 2016-10-02 08:11:25
    但是这并不表示汇编语言就已经没有用武之地了,通过阅读汇编代码,有助于我们理解编译器的优化能力,并分析代码中隐含的低效率,所以能够阅读和理解汇编代码也是一项很重要的技能。因为我平时都是在linux环境下工作...


    汇编语言是直接对应系统指令集的低级语言,在语言越来越抽象的今天,汇编语言并不像高级语言那样使用广泛,仅仅在驱动程序,嵌入式系统等对性能要求苛刻的领域才能见到它们的身影。但是这并不表示汇编语言就已经没有用武之地了,通过阅读汇编代码,有助于我们理解编译器的优化能力,并分析代码中隐含的低效率,所以能够阅读和理解汇编代码也是一项很重要的技能。因为我平时都是在linux环境下工作的,这篇文章就讲讲linux下的汇编语言。

    一、汇编语法风格

    汇编语言分为intel风格和AT&T风格,前者被Microsoft Windows/Visual C++采用,Linux下,基本采用的是AT&T风格汇编,两者语法有很多不同的地方。

    1. 寄存器访问格式不同。在 AT&T 汇编格式中,寄存器名要加上 '%' 作为前缀;而在 Intel 汇编格式中,寄存器名不需要加前缀。例如:

    AT&T

    Intel

    pushl %eax

    push eax

    2. 立即数表示不同。在 AT&T 汇编格式中,用 '$' 前缀表示一个立即操作数;而在 Intel 汇编格式中,立即数的表示不用带任何前缀。例如:

    AT&T

    Intel

    pushl $1

    push 1

    3. 操作数顺序不同。在 Intel 汇编格式中,目标操作数在源操作数的左边;而在 AT&T 汇编格式中,目标操作数在源操作数的右边。例如:

    AT&T

    Intel

    addl $1, %eax

    add eax, 1

    4. 字长表示不同。在 AT&T 汇编格式中,操作数的字长由操作符的最后一个字母决定,后缀'b'、'w'、'l'分别表示操作数为byte、word和long;而在 Intel 汇编格式中,操作数的字长是用 "byte ptr" 和 "word ptr" 等前缀来表示的。例如:

    AT&T

    Intel

    movb val, %eax

    mov al, byte ptr val

    5. 寻址方式表示不同。在 AT&T 汇编格式中,内存操作数的寻址方式是 

    section:disp(base, index, scale)

    而在 Intel 汇编格式中,内存操作数的寻址方式为:

    section:[base + index*scale + disp]

    由于 Linux 工作在保护模式下,用的是 32 位线性地址,所以在计算地址时不用考虑段基址和偏移量,而是采用如下的地址计算方法:

    disp + base + index * scale

    由此分为以下几种寻址方式:
     

    Intel

    AT&T

    内存直接寻址

    seg_reg: [base + index * scale + immed32]

    seg_reg: immed32 (base, index, scale)

    寄存器间接寻址

    [reg]

    (%reg)

    寄存器变址寻址

    [reg + _x]

    _x(%reg)

    立即数变址寻址

    [reg + 1]

    1(%reg)

    整数数组寻址

    [eax*4 + array]

    _array (,%eax, 4)

    二、IA32寄存器

    1.通用寄存器

    顾名思义,通用寄存器是那些你可以根据自己的意愿使用的寄存器,但有些也有特殊作用,IA32处理器包括8个通用寄存器,分为3组

    1) 数据寄存器

    EAX 累加寄存器,常用于运算;在乘除等指令中指定用来存放操作数,另外,所有的I/O指令都使用这一寄存器与外界设备传送数据。

    EBX 基址寄存器,常用于地址索引

    ECX 计数寄存器,常用于计数;常用于保存计算值,如在移位指令,循环(loop)和串处理指令中用作隐含的计数器.
    EDX 数据寄存器,常用于数据传递。

    2) 变址寄存器

    ESI 源地址指针

    EDI 目的地址指针

    3) 指针寄存器

    EBP为基址指针(Base Pointer)寄存器,存储当前栈帧的底部地址。

    ESP为堆栈指针(Stack Pointer)寄存器,一直记录栈顶位置,不可直接访问,push时ESP减小,pop时增大。

    2. 指令指针寄存器

    EIP 保存了下一条要执行的指令的地址, 每执行完一条指令EIP都会增加当前指令长度的位移,指向下一条指令。用户不可直接修改EIP的值,但jmp、call和ret等指令也会改变EIP的值,jmp将EIP修改为目的指令地址,call修改EIP为被调函数第一条指令地址,ret从栈中取出(pop)返回地址存入EIP。

    三、函数调用过程

    函数调用时的具体步骤如下:

    1. 调用函数将被调用函数参数入栈,入栈顺序由调用约定规定,包括cdecl,stdcall,fastcall,naked call等,c编译器默认使用cdecl约定,参数从右往座入栈。

    2. 执行call命令。

    call命令做了两件事情,一是将EIP寄存器内的值压入栈中,称为返回地址,函数完成后还要到这个地址继续执行程序。然后将被调用函数第一条指令地址存入EIP中,由此进入被调函数。

    3. 被调函数开始执行,先准备当前栈帧的环境,分为3步

    pushl %ebp 保存调用函数的基址到栈中,

    movl %esp, %ebp 设置EBP为当前被调用函数的基址指针,即当前栈顶

    subl $xx, %esp 为当前函数分配xx字节栈空间用于存储局部变量

    4. 执行被调函数主体

    5. 被调函数结束返回,恢复现场,第3步的逆操作,由leave和ret两条指令完成,

    leave 主要恢复栈空间,相当于

    movl %ebp, %esp 释放被调函数栈空间

    popl %ebp 恢复ebp为调用函数基址

    ret 与call指令对应,等于pop %EIP,

    6. 返回到调用函数,从下一条语句继续执行

    我们来看两个具体例子,第一个求数组和,

    复制代码
    int ArraySum(int *array, int n){
      int t = 0;
      for(int i=0; i<n; ++i) t += array[i];
      return t;
    }
    
    int main() {
      int a[5] = {1, 2, 3, 4, 5 };
      int sum = ArraySum(a, 5);
      return sum;
    }
    复制代码

    编译成汇编代码

    gcc -std=c99 -S -o sum.s sum.c

    gcc加入了很多汇编器和连接器用到的指令,与我们讨论的内容无关,简化汇编代码如下:

    复制代码
    ArraySum:
        pushl    %ebp
        movl    %esp, %ebp   
        subl    $16, %esp  //分配16字节栈空间
        movl    $0, -8(%ebp)  //初始化t
        movl    $0, -4(%ebp)  //初始化i
        jmp    .L2
    .L3:
        movl    -4(%ebp), %eax
        sall    $2, %eax  //i<<2, 即i*4, 一个int占4字节
        addl    8(%ebp), %eax  //得到array[i]地址,array+i*4
        movl    (%eax), %eax   //array[i]
        addl    %eax, -8(%ebp) //t+=array[i]
        addl    $1, -4(%ebp)
    .L2:
        movl    -4(%ebp), %eax   
        cmpl    12(%ebp), %eax  //比较i<n
        jl    .L3
        movl    -8(%ebp), %eax //return t; 默认eax存函数返回值
        leave
        ret
    
    main:
    .LFB1:
        pushl    %ebp
        movl    %esp, %ebp
        subl    $40, %esp       
        movl    $1, -24(%ebp) //初始化a[0]
        movl    $2, -20(%ebp) //初始化a[1]
        movl    $3, -16(%ebp) //初始化a[2]
        movl    $4, -12(%ebp) //初始化a[3]
        movl    $5, -8(%ebp)   //初始化a[4]
        movl    $5, 4(%esp)    //5作为第二个参数传给 ArraySum
        leal    -24(%ebp), %eax  //leal产生数组a的地址
        movl    %eax, (%esp)   //作为第一个参数传给ArraySum
        call    ArraySum
        movl    %eax, -4(%ebp)  //返回值传给sum
        movl    -4(%ebp), %eax  //return sum
        leave
        ret
    复制代码

    栈变化过程如下:

                             执行call指令前                                 执行call指令后

    从图中可以看出

    1. 数组连续排列,用move指令逐个赋值,读取数组元素方法是,用leal得到数组首地址,再计算偏移量

    2. 参数从右往左入栈

    3. gcc为了保证数据是严格对齐的,分配的空间大于使用的空间,有部分空间是浪费的

    下面这个例子说明了struct结构的实现方法,

    复制代码
    struct Point{
      int x;
      int y;
    };
    void PointInit(struct Point *p, int x, int y){
      p->x = x;
      p->y = y;
    }
    
    int main() {
      struct Point p;
      int x = 10;
      int y = 20;
      PointInit(&p, x, y);
      return 0;
    }
    复制代码

    编译成汇编代码,简化如下:

    复制代码
    PointInit:
        pushl    %ebp
        movl    %esp, %ebp
        movl    8(%ebp), %eax    //p的地址
        movl    12(%ebp), %edx  //x
        movl    %edx, (%eax)      //p->x=x
        movl    8(%ebp), %eax
        movl    16(%ebp), %edx  //y
        movl    %edx, 4(%eax)    //p->y=y
        popl    %ebp
        ret
    
    main:
        pushl    %ebp
        movl    %esp, %ebp
        subl    $28, %esp
        movl    $10, -8(%ebp)  //x=10
        movl    $20, -4(%ebp)  y=20
        movl    -4(%ebp), %eax
        movl    %eax, 8(%esp)
        movl    -8(%ebp), %eax
        movl    %eax, 4(%esp)
        leal    -16(%ebp), %eax  //取p地址&p
        movl    %eax, (%esp)
        call    PointInit
        movl    $0, %eax
        leave
        ret
    复制代码

    栈图就不画了,可以清楚地看出struct跟数组类似,连续排列,通过相对位移访问struct的成员,p->y与*(p+sizeof(p->x))有一样的效果。

    四、disassemble和objdump

    在linux下有两个跟汇编有重要关系的命令,一个是objdump,另一个是gdb中的disassemble。

    objdump帮助我们从可执行文件中反汇编出汇编代码,从而逆向分析工程。

    objdump -d sum

    部分汇编代码如下

    复制代码
    080483b4 <ArraySum>:
     80483b4:    55                       push   %ebp
     80483b5:    89 e5                    mov    %esp,%ebp
     80483b7:    83 ec 10                 sub    $0x10,%esp
     80483ba:    c7 45 f8 00 00 00 00     movl   $0x0,-0x8(%ebp)
     80483c1:    c7 45 fc 00 00 00 00     movl   $0x0,-0x4(%ebp)
     80483c8:    eb 12                    jmp    80483dc <ArraySum+0x28>
     80483ca:    8b 45 fc                 mov    -0x4(%ebp),%eax
     80483cd:    c1 e0 02                 shl    $0x2,%eax
     80483d0:    03 45 08                 add    0x8(%ebp),%eax
     80483d3:    8b 00                    mov    (%eax),%eax
     80483d5:    01 45 f8                 add    %eax,-0x8(%ebp)
     80483d8:    83 45 fc 01              addl   $0x1,-0x4(%ebp)
     80483dc:    8b 45 fc                 mov    -0x4(%ebp),%eax
     80483df:    3b 45 0c                 cmp    0xc(%ebp),%eax
     80483e2:    7c e6                    jl     80483ca <ArraySum+0x16>
     80483e4:    8b 45 f8                 mov    -0x8(%ebp),%eax
     80483e7:    c9                       leave  
     80483e8:    c3                       ret
    复制代码

    disassemble可以显示调试程序的汇编代码,用法如下

    disas 反汇编当前函数

    disas sum 反汇编sum函数

    disas 0x801234 反汇编位于地址 0x801234附近的函数

    disas 0x801234 0x802234 返汇编指定范围内函数

     

     

    reference:

    http://zh.wikipedia.org/wiki/%E6%B1%87%E7%BC%96

    http://www.ibm.com/developerworks/cn/linux/l-assembly/

    展开全文
  • Linux中的汇编语言

    2014-10-24 16:02:35
    详细描述了Linux系统中使用的AT&T汇编语言,有IBM汇编语言基础的同学学会应该不困难
  • 本书全面介绍Linux平台下的32位AT&T的汇编语言程序设计,是学习汇编语言的很好的参考书
  • 汇编语言基于Linux

    2018-11-29 09:28:09
    学习linux内核,一定要学习汇编语言,这是一本完全基于linux讲解汇编的书
  • linux下的汇编语言

    2011-11-01 12:06:41
    linux下的汇编语言,跟8086的汇编语言还是有差距的哦。
  • 什么汇编语言

    万次阅读 多人点赞 2018-11-19 21:21:37
    汇编语言(assembly language)是一种用于电子计算机、微处理器、微控制器或其他可编程器件的低级语言,亦称为符号语言。在汇编语言中,用助记符(Mnemonics)代替机器指令的操作码,用地址符号(Symbol)或标号...

           汇编语言(assembly language)是一种用于电子计算机微处理器微控制器或其他可编程器件的低级语言,亦称为符号语言。在汇编语言中,助记符(Mnemonics)代替机器指令操作码,用地址符号(Symbol)或标号(Label)代替指令或操作数地址。在不同的设备中,汇编语言对应着不同的机器语言指令集,通过汇编过程转换成机器指令。普遍地说,特定的汇编语言和特定的机器语言指令集是一一对应的,不同平台之间不可直接移植。

           许多汇编程序为程序开发、汇编控制、辅助调试提供了额外的支持机制。有的汇编语言编程工具经常会提供宏,它们也被称为宏汇编器。

           汇编语言不像其他大多数的程序设计语言一样被广泛用于程序设计。在今天的实际应用中,它通常被应用在底层,硬件操作和高要求的程序优化的场合。驱动程序、嵌入式操作系统和实时运行程序都需要汇编语言。

            Microsoft 宏汇编器(称为 MASM)是windows下常用的汇编器。Microsoft Visual Studio 的大多数版本(专业版,旗舰版,精简版……)都包含 MASM。在运行 Microsoft Windows 的 x86 系统中,其他一些有名的汇编器包括:TASM(Turbo 汇编器),NASM(Netwide 汇编器)和 MASM32(MASM 的一种变体)。GAS(GNU 汇编器)和 NASM 是两种基于 Linux 的汇编器。在这些汇编器中,NASM 的语法与 MASM 的最相似。汇编语言最古老的编程语言,在所有的语言中,它与原生机器语言最为接近。它能直接访问计算机硬件,要求用户了解计算机架构和操作系统。

    什么是汇编器和链接器?

           汇编器(assembler)是一种工具程序,用于将汇编语言源程序转换为机器语言。链接器(linker)也是一种工具程序,它把汇编器生成的单个文件组合为一个可执行程序。还有一个相关的工具,称为调试器(debugger),使程序员可以在程序运行时,单步执行程序并检查寄存器和内存状态。

    MASM 能创建哪些类型的程序?

    32 位保护模式(32-Bit Protected Mode)32 位保护模式程序运行于所有的 32 位和 64 位版本的 Microsoft Windows 系统。它们通常比实模式程序更容易编写和理解。从现在开始,将其简称为 32 位模式。
    64 位模式(64-Bit Mode)64 位程序运行于所有的 64 位版本 Microsoft Windows 系统。
    16 位实地址模式(16-Bit Real-Address Mode)16 位程序运行于 32 位版本 Windows 和嵌入式系统。 64 位 Windows 不支持这类程序。

    汇编语言与机器语言有什么关系?

           机器语言(machine language)是一种数字语言, 专门设计成能被计算机处理器(CPU)理解。所有 x86 处理器都理解共同的机器语言。
    汇编语言(assembly language)包含用短助记符如 ADD、MOV、SUB 和 CALL 书写的语句。汇编语言与机器语言是一对一(one-to-one)的关系:每一条汇编语言指令对应一条机器语言指令。寄存器(register)是 CPU 中被命名的存储位置,用于保存操作的中间结果

    C++ 和 Java 与汇编语言有什么关系?

           高级语言如 Python、C++ 和 Java 与汇编语言和机器语言的关系是一对多(one-to-many)。比如,C++ 的一条语句就会扩展为多条汇编指令或机器指令。

    汇编语言可移植吗?

    一种语言,如果它的源程序能够在各种各样的计算机系统中进行编译和运行,那么这种语言被称为是可移植的(portable)。
    例如,一个 C++ 程序,除非需要特别引用某种操作系统的库函数,否则它就几乎可以在任何一台计算机上编译和运行。Java 语言的一大特点就是,其编译好的程序几乎能在所有计算机系统中运行。
    汇编语言不是可移植的,因为它是为特定处理器系列设计的。目前广泛使用的有多种不同的汇编语言,每一种都基于一个处理器系列。
    对于一些广为人知的处理器系列如 Motorola 68x00、x86、SUN Sparc、Vax 和 IBM-370,汇编语言指令会直接与该计算机体系结构相匹配,或者在执行时用一种被称为微代码解释器(microcode interpreter)的处理器内置程序来进行转换。

    发展历程

           说到汇编语言的产生,首先要讲一下机器语言。机器语言是机器指令的集合。机器指令展开来讲就是一台机器可以正确执行的命令。电子计算机的机器指令是一列二进制数字。计算机将之转变为一列高低电平,以使计算机的电子器件受到驱动,进行运算。

           上面所说的计算机指的是可以执行机器指令,进行运算的机器。这是早期计算机的概念。在我们常用的PC机中,有一个芯片来完成上面所说的计算机的功能。这个芯片就是我们常说的CPU(Central Processing Unit,中央处理单元)。每一种微处理器,由于硬件设计和内部结构的不同,就需要用不同的电平脉冲来控制,使它工作。所以每一种微处理器都有自己的机器指令集,也就是机器语言。CPU 只负责计算,本身不具备智能。你输入一条指令(instruction),它就运行一次,然后停下来,等待下一条指令。这些指令都是二进制的,称为操作码(opcode),比如加法指令就是00000011。编译器的作用,就是将高级语言写好的程序,翻译成一条条操作码。对于人类来说,二进制程序是不可读的,根本看不出来机器干了什么。为了解决可读性的问题,以及偶尔的编辑需求,就诞生了汇编语言。汇编语言是二进制指令的文本形式,与指令是一一对应的关系。比如,加法指令00000011写成汇编语言就是 ADD。只要还原成二进制,汇编语言就可以被 CPU 直接执行,所以它是最底层的低级语言

           早期的程序设计均使用机器语言。程序员们将用0, 1数字编成的程序代码打在纸带或卡片上,1打孔,0不打孔,再将程序通过纸带机或卡片机输入计算机,进行运算。这样的机器语言由纯粹的0和1构成,十分复杂,不方便阅读和修改,也容易产生错误。程序员们很快就发现了使用机器语言带来的麻烦,它们难于辨别和记忆,给整个产业的发展带来了障碍,于是汇编语言产生了。

           汇编语言的主体是汇编指令。汇编指令和机器指令的差别在于指令的表示方法上。汇编指令是机器指令便于记忆的书写格式。

    操作:寄存器BX的内容送到AX中
    1000100111011000              机器指令
    mov ax,bx                    汇编指令

           此后,程序员们就用汇编指令编写源程序。可是,计算机能读懂的只有机器指令,那么如何让计算机执行程序员用汇编指令编写的程序呢?这时,就需要有一个能够将汇编指令转换成机器指令的翻译程序,这样的程序我们称其为编译器。程序员用汇编语言写出源程序,再用汇编编译器将其编译为机器码,由计算机最终执行。 

    语言特点

           汇编语言是直接面向处理器(Processor)的程序设计语言。处理器是在指令的控制下工作的,处理器可以识别的每一条指令称为机器指令。每一种处理器都有自己可以识别的一整套指令,称为指令集。处理器执行指令时,根据不同的指令采取不同的动作,完成不同的功能,既可以改变自己内部的工作状态,也能控制其它外围电路的工作状态。

           汇编语言的另一个特点就是它所操作的对象不是具体的数据,而是寄存器或者存储器,也就是说它是直接和寄存器和存储器打交道,这也是为什么汇编语言的执行速度要比其它语言快,但同时这也使编程更加复杂,因为既然数据是存放在寄存器或存储器中,那么必然就存在着寻址方式,也就是用什么方法找到所需要的数据。例如上面的例子,我们就不能像高级语言一样直接使用数据,而是先要从相应的寄存器AX、BX 中把数据取出。这也就增加了编程的复杂性,因为在高级语言中寻址这部分工作是由编译系统来完成的,而在汇编语言中是由程序员自己来完成的,这无异增加了编程的复杂程度和程序的可读性。

           再者,汇编语言指令是机器指令的一种符号表示,而不同类型的CPU 有不同的机器指令系统,也就有不同的汇编语言,所以,汇编语言程序与机器有着密切的关系。所以,除了同系列、不同型号CPU 之间的汇编语言程序有一定程度的可移植性之外,其它不同类型(如:小型机和微机等)CPU 之间的汇编语言程序是无法移植的,也就是说,汇编语言程序的通用性和可移植性要比高级语言程序低。

           正因为汇编语言有“与机器相关性”的特性,程序员用汇编语言编写程序时,可充分对机器内部的各种资源进行合理的安排,让它们始终处于最佳的使用状态。这样编写出来的程序执行代码短、执行速度快。汇编语言是各种编程语言中与硬件关系最密切、最直接的一种,在时间和空间的效率上也最高的一种。

    总体特点

    1.机器相关性

           这是一种面向机器的低级语言,通常是为特定的计算机或系列计算机专门设计的。因为是机器指令的符号化表示,故不同的机器就有不同的汇编语言。使用汇编语言能面向机器并较好地发挥机器的特性,得到质量较高的程序。

    2.高速度和高效率

           汇编语言保持了机器语言的优点,具有直接和简捷的特点,可有效地访问、控制计算机的各种硬件设备,如磁盘、存储器、CPUI/O端口等,且占用内存少,执行速度快,是高效的程序设计语言

    3.编写和调试的复杂性

           由于是直接控制硬件,且简单的任务也需要很多汇编语言语句,因此在进行程序设计时必须面面俱到,需要考虑到一切可能的问题,合理调配和使用各种软、硬件资源。这样,就不可避免地加重了程序员的负担。与此相同,在程序调试时,一旦程序的运行出了问题,就很难发现。

    优点

    1、因为用汇编语言设计的程序最终被转换成机器指令,故能够保持机器语言的一致性,直接、简捷,并能像机器指令一样访问、控制计算机的各种硬件设备,如磁盘、存储器CPUI/O端口等。使用汇编语言,可以访问所有能够被访问的软、硬件资源。

    2、目标代码简短,占用内存少,执行速度快,是高效的程序设计语言,经常与高级语言配合使用,以改善程序的执行速度和效率,弥补高级语言在硬件控制方面的不足,应用十分广泛。

    缺点

    1、汇编语言是面向机器的,处于整个计算机语言层次结构的底层,故被视为一种低级语言,通常是为特定的计算机或系列计算机专门设计的。不同的处理器有不同的汇编语言语法和编译器,编译的程序无法在不同的处理器上执行,缺乏可移植性;

    2、难于从汇编语言代码上理解程序设计意图,可维护性差,即使是完成简单的工作也需要大量的汇编语言代码,很容易产生bug,难于调试;

    3、使用汇编语言必须对某种处理器非常了解,而且只能针对特定的体系结构和处理器进行优化,开发效率很低,周期长且单调。 

    语言组成

    数据传送指令

    这部分指令包括通用数据传送指令MOV、条件传送指令CMOVcc、堆栈操作指令PUSH/PUSHA/PUSHAD/POP/POPA/POPAD、交换指令XCHG/XLAT/BSWAP、地址或段描述符选择子传送指令LEA/LDS/LES/LFS/LGS/LSS等。注意,CMOVcc不是一条具体的指令,而是一个指令簇,包括大量的指令,用于根据EFLAGS寄存器的某些位状态来决定是否执行指定的传送操作。

    整数和逻辑运算指令

    这部分指令用于执行算术和逻辑运算,包括加法指令ADD/ADC、减法指令SUB/SBB、加一指令INC、减一指令DEC、比较操作指令CMP、乘法指令MUL/IMUL、除法指令DIV/IDIV、符号扩展指令CBW/CWDE/CDQE、十进制调整指令DAA/DAS/AAA/AAS、逻辑运算指令NOT/AND/OR/XOR/TEST等。

    移位指令

    这部分指令用于将寄存器或内存操作数移动指定的次数。包括逻辑左移指令SHL、逻辑右移指令SHR、算术左移指令SAL、算术右移指令SAR、循环左移指令ROL、循环右移指令ROR等。

    位操作指令

    这部分指令包括位测试指令BT、位测试并置位指令BTS、位测试并复位指令BTR、位测试并取反指令BTC、位向前扫描指令BSF、位向后扫描指令BSR等。

    条件设置指令

    这不是一条具体的指令,而是一个指令簇,包括大约30条指令,用于根据EFLAGS寄存器的某些位状态来设置一个8位的寄存器或者内存操作数。比如SETE/SETNE/SETGE等等。

    控制转移指令

    这部分包括无条件转移指令JMP、条件转移指令Jcc/JCXZ、循环指令LOOP/LOOPE/LOOPNE、过程调用指令CALL、子过程返回指令RET、中断指令INTn、INT3、INTOIRET等。注意,Jcc是一个指令簇,包含了很多指令,用于根据EFLAGS寄存器的某些位状态来决定是否转移;INT n是软中断指令,n可以是0到255之间的数,用于指示中断向量号。

    串操作指令

    这部分指令用于对数据串进行操作,包括串传送指令MOVS、串比较指令CMPS、串扫描指令SCANS、串加载指令LODS、串保存指令STOS,这些指令可以有选择地使用REP/REPE/REPZ/REPNE和REPNZ的前缀以连续操作。

    输入输出指令

    这部分指令用于同外围设备交换数据,包括端口输入指令IN/INS、端口输出指令OUT/OUTS。

    高级语言辅助指令

    这部分指令为高级语言的编译器提供方便,包括创建栈帧的指令ENTER和释放栈帧的指令LEAVE。

    控制和特权指令

    这部分包括无操作指令NOP、停机指令HLT、等待指令WAIT/MWAIT、换码指令ESC、总线封锁指令LOCK、内存范围检查指令BOUND、全局描述符表操作指令LGDT/SGDT、中断描述符表操作指令LIDT/SIDT、局部描述符表操作指令LLDT/SLDT、描述符段界限值加载指令LSR、描述符访问权读取指令LAR、任务寄存器操作指令LTR/STR、请求特权级调整指令ARPL、任务切换标志清零指令CLTS、控制寄存器和调试寄存器数据传送指令MOV、高速缓存控制指令INVD/WBINVD/INVLPG、型号相关寄存器读取和写入指令RDMSR/WRMSR、处理器信息获取指令CPUID、时间戳读取指令RDTSC等。

    浮点和多媒体指令

    这部分指令用于加速浮点数据的运算,以及用于加速多媒体数据处理的单指令多数据(SIMD及其扩展SSEx)指令。这部分指令数据非常庞大,无法一一列举,请自行参考INTEL手册。

    虚拟机扩展指令

    这部分指令包括INVEPT/INVVPID/VMCALL/VMCLEAR/VMLAUNCH/VMRESUME/VMPTRLD/VMPTRST/VMREAD/VMWRITE/VMXOFF/VMON等。 

    相关技术

    汇编器

    典型的现代汇编器(assembler)建造目标代码,由解译组语指令集的易记码(mnemonics)到操作码(OpCode),并解析符号名称(symbolic names)成为存储器地址以及其它的实体。使用符号参考是汇编器的一个重要特征,它可以节省修改程序后人工转址的乏味耗时计算。基本就是把机器码变成一些字母而已,编译的时候再把输入的指令字母替换成为晦涩难懂机器码。

    编译环境

    用汇编语言等非机器语言书写好的符号程序称为源程序,汇编语言编译器的作用是将源程序翻译成目标程序。目标程序是机器语言程序,当它被安置在内存的预定位置上后,就能被计算机的CPU处理和执行。

    汇编的调试环境总的来说比较少,也很少有非常好的编译器。编译器的选择依赖于目标处理器的类型和具体的系统平台。一般来说,功能良好的编译器用起来应当非常方便,比如,应当可以自动整理格式、语法高亮显示,集编译、链接和调试为一体,方便实用。

    对于广泛使用的个人计算机来说,可以自由选择的汇编语言编译器有MASMNASMTASMGAS、FASM、RADASM等,但大都不具备调试功能。如果是为了学习汇编语言,轻松汇编因为拥有一个完善的集成环境,是一款非常适合初学者的汇编编译器。 

    发展前景

    汇编语言是机器语言的助记符,相对于比枯燥的机器代码易于读写、易于调试和修改,同时优秀的汇编语言设计者经过巧妙的设计,使得汇编语言汇编后的代码比高级语言执行速度更快,占内存空间少等优点,但汇编语言的运行速度和空间占用是针对高级语言并且需要巧妙设计,而且部分高级语言在编译后代码执行效率同样很高,所以此优点慢慢弱化。而且在编写复杂程序时具有明显的局限性,汇编语言依赖于具体的机型,不能通用,也不能在不同机型之间移植。常说汇编语言是低级语言,并不是说汇编语言要被弃之,相反,汇编语言仍然是计算机(或微机)底层设计程序员必须了解的语言,在某些行业与领域,汇编是必不可少的,非它不可适用。只是,现在计算机最大的领域为IT软件,也是我们常说的计算机应用软件编程,在熟练的程序员手里,使用汇编语言编写的程序,运行效率与性能比其它语言写的程序相对提高,但是代价是需要更长的时间来优化,如果对计算机原理及编程基础不扎实,反而增加其开发难度,实在是得不偿失,对比2010年前后的软件开发,已经是市场化的软件行业,加上高级语言的优秀与跨平台,一个公司不可以让一个团队使用汇编语言来编写所有的东西,花上几倍甚至几十倍的时间,不如使用其它语言来完成,只要最终结果不比汇编语言编写的差太多,就能抢先一步完成,这是市场经济下的必然结果。

     

    但是,迄今为止,还没有程序员敢断定汇编语言是不需要学的,同时,汇编语言(Assembly Language)是面向机器的程序设计语言,设计精湛的汇编程序员,部分已经脱离软件开发,挤身于工业电子编程中。对于功能相对小巧但硬件对语言设计要求苛刻的行业,如4位单片机,由于其容量及运算,此行业的电子工程师一般负责从开发设计电路及软件控制,主要开发语言就是汇编,c语言使用只占极少部分,而电子开发工程师是千金难求,在一些工业公司,一个核心的电子工程师比其它任何职员待遇都高,对比起来,一般电子工程师待遇是程序员的十倍以上。这种情况是因为21世纪以来,学习汇编的人虽然也不少,但是真正能学到精通的却不多,它相对于高级语言难学,难用,适用范围小,虽然简单,但是过于灵活,学习过高级语言的人去学习汇编比一开始学汇编的人难得多,但是学过汇编的人学习高级语言却很容易,简从繁易,繁从简难。对于一个全面了解微机原理的程序员,汇编语言是必修语言。

    实际应用

    随着现代软件系统越来越庞大复杂,大量经过了封装的高级语言如C/C++Pascal/Object Pascal也应运而生。这些新的语言使得程序员在开发过程中能够更简单,更有效率,使软件开发人员得以应付快速的软件开发的要求。而汇编语言由于其复杂性使得其适用领域逐步减小。但这并不意味着汇编已无用武之地。由于汇编更接近机器语言,能够直接对硬件进行操作,生成的程序与其他的语言相比具有更高的运行速度,占用更小的内存,因此在一些对于时效性要求很高的程序、许多大型程序的核心模块以及工业控制方面大量应用。 

    历史上,汇编语言曾经是非常流行的程序设计语言之一。随着软件规模的增长,以及随之而来的对软件开发进度和效率的要求,高级语言逐渐取代了汇编语言。但即便如此,高级语言也不可能完全替代汇编语言的作用。就拿Linux内核来讲,虽然绝大部分代码是用C语言编写的,但仍然不可避免地在某些关键地方使用了汇编代码。由于这部分代码与硬件的关系非常密切,即使是C语言也会显得力不从心,而汇编语言则能够很好扬长避短,最大限度地发挥硬件的性能。

    首先,汇编语言的大部分语句直接对应着机器指令,执行速度快,效率高,代码体积小,在那些存储器容量有限,但需要快速和实时响应的场合比较有用,比如仪器仪表和工业控制设备中。

    其次,在系统程序的核心部分,以及与系统硬件频繁打交道的部分,可以使用汇编语言。比如操作系统的核心程序段、I/O接口电路的初始化程序、外部设备的低层驱动程序,以及频繁调用的子程序动态连接库、某些高级绘图程序、视频游戏程序等等。

    再次,汇编语言可以用于软件的加密和解密、计算机病毒的分析和防治,以及程序的调试和错误分析等各个方面。

    最后,通过学习汇编语言,能够加深对计算机原理和操作系统等课程的理解。通过学习和使用汇编语言,能够感知、体会和理解机器的逻辑功能,向上为理解各种软件系统的原理,打下技术理论基础;向下为掌握硬件系统的原理,打下实践应用基础。

    附注:寄存器和内存模型

    学习汇编语言,首先必须了解两个知识点:寄存器和内存模型。

    先来看寄存器。CPU 本身只负责运算,不负责储存数据。数据一般都储存在内存之中,CPU 要用的时候就去内存读写数据。但是,CPU 的运算速度远高于内存的读写速度,为了避免被拖慢,CPU 都自带一级缓存和二级缓存。基本上,CPU 缓存可以看作是读写速度较快的内存。

    但是,CPU 缓存还是不够快,另外数据在缓存里面的地址是不固定的,CPU 每次读写都要寻址也会拖慢速度。因此,除了缓存之外,CPU 还自带了寄存器(register),用来储存最常用的数据。也就是说,那些最频繁读写的数据(比如循环变量),都会放在寄存器里面,CPU 优先读写寄存器,再由寄存器跟内存交换数据。

    寄存器不依靠地址区分数据,而依靠名称。每一个寄存器都有自己的名称,我们告诉 CPU 去具体的哪一个寄存器拿数据,这样的速度是最快的。有人比喻寄存器是 CPU 的零级缓存。

    1、寄存器的种类

    早期的 x86 CPU 只有8个寄存器,而且每个都有不同的用途。现在的寄存器已经有100多个了,都变成通用寄存器,不特别指定用途了,但是早期寄存器的名字都被保存了下来。

    EAX

    EBX

    ECX

    EDX

    EDI

    ESI

    EBP

    ESP

    上面这8个寄存器之中,前面七个都是通用的。ESP 寄存器有特定用途,保存当前 Stack 的地址。

    我们常常看到 32位 CPU、64位 CPU 这样的名称,其实指的就是寄存器的大小。32 位 CPU 的寄存器大小就是4个字节。

    2、内存模型:Heap

    寄存器只能存放很少量的数据,大多数时候,CPU 要指挥寄存器,直接跟内存交换数据。所以,除了寄存器,还必须了解内存怎么储存数据。

    程序运行的时候,操作系统会给它分配一段内存,用来储存程序和运行产生的数据。这段内存有起始地址和结束地址,比如从0x1000到0x8000,起始地址是较小的那个地址,结束地址是较大的那个地址

    程序运行过程中,对于动态的内存占用请求(比如新建对象,或者使用malloc命令),系统就会从预先分配好的那段内存之中,划出一部分给用户,具体规则是从起始地址开始划分(实际上,起始地址会有一段静态数据,这里忽略)。举例来说,用户要求得到10个字节内存,那么从起始地址0x1000开始给他分配,一直分配到地址0x100A,如果再要求得到22个字节,那么就分配到0x1020。

    这种因为用户主动请求而划分出来的内存区域,叫做 Heap(堆)。它由起始地址开始,从低位(地址)向高位(地址)增长。Heap 的一个重要特点就是不会自动消失,必须手动释放,或者由垃圾回收机制来回收。

    3、内存模型:Stack

    除了 Heap 以外,其他的内存占用叫做 Stack(栈)。简单说,Stack 是由于函数运行而临时占用的内存区域

    请看下面的例子。

    int main(){int a=2;int b=3;}

    上面代码中,系统开始执行main函数时,会为它在内存里面建立一个帧(frame),所有main的内部变量(比如a和b)都保存在这个帧里面。main函数执行结束后,该帧就会被回收,释放所有的内部变量,不再占用空间。

    如果函数内部调用了其他函数,会发生什么情况?

    int main(){int a=2;int b=3;return add_a_and_b(a,b);}

    上面代码中,main函数内部调用了add_a_and_b函数。执行到这一行的时候,系统也会为add_a_and_b新建一个帧,用来储存它的内部变量。也就是说,此时同时存在两个帧:main和add_a_and_b。一般来说,调用栈有多少层,就有多少帧。

    等到add_a_and_b运行结束,它的帧就会被回收,系统会回到函数main刚才中断执行的地方,继续往下执行。通过这种机制,就实现了函数的层层调用,并且每一层都能使用自己的本地变量。

    所有的帧都存放在 Stack,由于帧是一层层叠加的,所以 Stack 叫做栈。生成新的帧,叫做"入栈",英文是 push;栈的回收叫做"出栈",英文是 pop。Stack 的特点就是,最晚入栈的帧最早出栈(因为最内层的函数调用,最先结束运行),这就叫做"后进先出"的数据结构。每一次函数执行结束,就自动释放一个帧,所有函数执行结束,整个 Stack 就都释放了。

    Stack 是由内存区域的结束地址开始,从高位(地址)向低位(地址)分配。比如,内存区域的结束地址是0x8000,第一帧假定是16字节,那么下一次分配的地址就会从0x7FF0开始;第二帧假定需要64字节,那么地址就会移动到0x7FB0。

    汇编语言保留字

    保留字(reserved words)有特殊意义并且只能在其正确的上下文中使用。默认情况下,保留字是没有大小写之分的。比如,MOV 与 mov、Mov 是相同的。

    保留字有不同的类型:

    • 指令助记符,如 MOV、ADD 和 MUL。
    • 寄存器名称。
    • 伪指令,告诉汇编器如何汇编程序。(不是机器指令)
    • 属性,提供变量和操作数的大小与使用信息。例如 BYTE 和 WORD。
    • 运算符,在常量表达式中使用。
    • 预定义符号,比如 @data,它在汇编时返回常量的整数值。

    下表是常用的保留字列表。

    $PARITY?DWORDSTDCALL
    ?PASCALFARSWORD
    @BQWORDFAR16SYSCALL
    @FREAL4FORTRANTBYTE
    ADDRREAL8FWORDVARARG
    BASICREAL10NEARWORD
    BYTESBYTENEAR16ZERO?
    CSDORDOVERFLOW? 
    CARRY?SIGN?  

    伪指令

    DW 定义字(2字节).

    PROC 定义过程.

    ENDP 过程结束.

    SEGMENT 定义段.

    ASSUME 建立段寄存器寻址.

    ENDS 段结束.

    END 程序结束.

    伪指令 (directive) 是嵌入源代码中的命令,由汇编器识别和执行。伪指令不在运行时执行,但是它们可以定义变量、宏和子程序;为内存段分配名称,执行许多其他与汇编器相关的日常任务。

    默认情况下,伪指令不区分大小写。例如,.data,.DATA 和 .Data 是相同的。

    下面的例子有助于说明伪指令和指令的区别。DWORD 伪指令告诉汇编器在程序中为一个双字变量保留空间。另一方面,MOV 指令在运行时执行,将 myVar 的内容复制到 EAX 寄存器中:

    myVar DWORD 26
    mov eax,myVar

    尽管 Intel 处理器所有的汇编器使用相同的指令集,但是通常它们有着不同的伪指令。比如,Microsoft 汇编器的 REPT 伪指令对其他一些汇编器就是无法识别的。

    定义段

    汇编器伪指令的一个重要功能是定义程序区段,也称为段 (segment)。程序中的段具有不同的作用。如下面的例子,一个段可以用于定义变量,并用 .DATA 伪指令进行标识:

    .data

    .CODE 伪指令标识的程序区段包含了可执行的指令:

    .code

    .STACK 伪指令标识的程序区段定义了运行时堆栈,并设置了其大小:

    .stack 100h

    指令

    指令(instruction)是一种语句,它在程序汇编编译时变得可执行。汇编器将指令翻译为机器语言字节,并且在运行时由 CPU 加载和执行。

    一条指令有四个组成部分:

    • 标号(可选)
    • 指令助记符(必需)
    • 操作数(通常是必需的)
    • 注释(可选)

    不同部分的位置安排如下所示:

    [label: ] mnemonic [operands] [;comment]

    现在分别了解每个部分,先从标号字段开始。

    1) 标号

    标号(label)是一种标识符,是指令和数据的位置标记。标号位于指令的前端,表示指令的地址。同样,标号也位于变量的前端,表示变量的地址。标号有两种类型:数据标号和代码标号。

    数据标号标识变量的位置,它提供了一种方便的手段在代码中引用该变量。比如,下面定义了一个名为 count 的变量:

    count DWORD 100

    汇编器为每个标号分配一个数字地址。可以在一个标号后面定义多个数据项。在下面的例子中,array 定义了第一个数字(1024)的位置,其他数字在内存中的位置紧随其后:

    array DWORD 1024, 2048
    DWORD 4096, 8192

    程序代码区(指令所在区段)的标号必须用冒号(:)结束。代码标号用作跳转和循环指令的目标。例如,下面的 JMP 指令创建一个循环,将程序控制传递给标号 target 标识的位置:

    target:
    mov ax,bx
    ...
    jmp target

    代码标号可以与指令在同一行上,也可以自己独立一行:

    L1: mov ax, bx
    L2 :

    标号命名规则要求,只要每个标号在其封闭子程序页中是唯一的,那么就可以多次使用相同的标号。

    2) 指令助记符

    指令助记符(instruction mnemonic)是标记一条指令的短单词。在英语中,助记符是帮助记忆的方法。相似地,汇编语言指令助记符,如 mov, add 和 sub,给出了指令执行操作类型的线索。下面是一些指令助记符的例子:

    助记符说明助记符说明
    MOV传送(分配)数值MUL两个数值相乘
    ADD两个数值相加JMP跳转到一个新位置
    SUB从一个数值中减去另一个数值CALL调用一个子程序

    3) 操作数

    操作数是指令输入输出的数值。汇编语言指令操作数的个数范围是 0〜3 个,每个操作数可以是寄存器、内存操作数、整数表达式和输入输岀端口。
    生成内存操作数有不同的方法,比如,使用变量名、带方括号的寄存器等。变量名暗示了变量地址,并指示计算机使用给定地址的内存内容。下表列出了一些操作数示例:

    示例操作数类型示例 操作数类型
    96 整数常量eax寄存器
    2+4整数表达式count内存

    现在来考虑一些包含不同个数操作数的汇编语言指令示例。比如,STC 指令没有操作数:

    stc ;进位标志位置 1

    INC 指令有一个操作数:

    inc eax ;EAX 加 1

    MOV 指令有两个操作数:

    mov count, ebx ;将 EBX 传送给变量 count

    操作数有固有顺序。当指令有多个操作数时,通常第一个操作数被称为目的操作数,第二个操作数被称为源操作数(source operand)。

    一般情况下,目的操作数的内容由指令修改。比如,在 mov 指令中,数据就是从源操作数复制到目的操作数。

    IMUL 指令有三个操作数,第一个是目的操作数,第二个和第三个是进行乘法的源操作数:

    imul eax,ebx,5

    在上例中,EBX 与 5 相乘,结果存放在 EAX 寄存器中。

    4) 注释

    注释是程序编写者与阅读者交流程序设计信息的重要途径。程序清单的开始部分通常包含如下信息:

    • 程序目标的说明
    • 程序创建者或修改者的名单
    • 程序创建和修改的日期
    • 程序实现技术的说明

    注释有两种指定方法:

    • 单行注释,用分号(;)开始。汇编器将忽略在同一行上分号之后的所有字符。
    • 块注释,用 COMMENT 伪指令和一个用户定义的符号开始。汇编器将忽略其后所有的文本行,直到相同的用户定义符号出现为止。

    示例如下:

    COMMENT !
    This line is a comment.
    This line is also a comment.
    !

    其他符号也可以使用,只要该符号不出现在注释行中:

    COMMENT &
    This line is a comment.
    This line is also a comment.
    &

    当然,程序员应该在整个程序中提供注释,尤其是代码意图不太明显的地方。

    5) NOP(空操作)指令

    最安全(也是最无用)的指令是 NOP(空操作)。它在程序空间中占有一个字节,但是不做任何操作。它有时被编译器和汇编器用于将代码对齐到有效的地址边界

    在下面的例子中,第一条指令 MOV 生成了 3 字节的机器代码。NOP 指令就把第三条指令的地址对齐到双字边界(4 的偶数倍)

    00000000 66 8B C3 mov ax,bx
    00000003 90 nop ;对齐下条指令
    00000004 8B D1 mov edx,ecx

    x86 处理器被设计为从双字的偶数倍地址处加载代码和数据,这使得加载速度更快。

    展开全文
  • Linux汇编语言书两本

    2018-07-02 15:12:18
    涉及到Linux的嵌入式开发的两本汇编语言。都是非常经典的外文翻译过来的,对Linux汇编语言编写嵌入式程序有很大帮助。
  • Linux汇编语言

    千次阅读 2018-07-18 08:13:07
    .word相当于int类型,是GNU汇编中的元素,word=32bit=4字节。 2.关键字.global .global就是相当于C语言中的Extern,声明此变量,并且告诉链接器此变量是全局的,外部可以访问 。 3.标号 _start后面加上一个...

    1.类型.word

    .word相当于int类型,是GNU汇编中的元素,word=32bit=4字节。

    2.关键字.global

    .global就是相当于C语言中的Extern,声明此变量,并且告诉链接器此变量是全局的,外部可以访问

    3.标号

    _start后面加上一个冒号’ : ,表示其是一个标号Label,类似于C语言goto后面的标号。而_start标号后面的:

    b    reset

    就是跳转到对应的标号为reset的位置。

    4.如何查看C或者汇编的源代码对应的真正的汇编代码

    首先解释一下,由于汇编代码中会存在一些伪指令等内容,所以,写出来的汇编代码,并不一定是真正可以执行的代码,这些 类似于伪指令的汇编代码,经过汇编器,转换或翻译成真正的可以执行的汇编指令。所以,上面才会有将“汇编源代码”转换为“真正的汇编代码”这说。然后,此处对于有些人不是很熟悉的,如何查看源代码真正对应的汇编代码呢?此处,对于汇编代码,有两种:

    一种是只是经过编译阶段,生成了对应的汇编代码。

    另外一种是,编译后的汇编代码,经过链接器链接后,对应的汇编代码。

    总的来说,两者区别很小,后者主要是更新了外部函数的地址等等,对于汇编代码本身,至少对于我们一般所去查看源代码所对应的汇编来说,两者可以视为没区别。

    【查看源码所对应的汇编代码】

    1)对于编译所生成的汇编的查看方式是:

    用交叉编译器的 dump 工具去将汇编代码都导出来:

    arm-linux-objdump –d cpu/s5pc11x/start.o > uboot_start.o_dump_result.txt

    返样就把 start.o 中的汇编代码导出到 uboot_start.o_dump_result.txt 中了。

    然后查看 uboot_start.o_dump_result.txt,即可找到对应的汇编代码。

    2)对于链接所生成的汇编的查看方式是:

    和上面方法一样,即:

    arm-linux-objdump –d u-boot > whole_uboot_dump_result.txt

    然后打开该 txt,找到 stack_setup 部分的代码:

    两者不一样地方在于,我们 uboot 设置了 text_base,即代码段的基地址,上面编译后的汇编代码,经过链接后,更新了对应的基地址,所以看起来,所以代码对应的地址,都变了,但是具体地址中的汇编代码,除了个别调用凼数的地址和跳转到某个标号的地址外,其他都还是一样的。

    5.LDR指令

    首先我们要看一下ARM架构:LDR/STR架构:

    LDR指令的格式为:

    LDR{条件} 目的寄存器,<存储器地址>

    LDR指令用于从存储器中将一个32位的字数据传送到目的寄存器中。该指令通常用于从存储器中读取32位的字数据到通用寄存器,然后对数据进行处理。当程序计数器PC作为目的寄存器时,指令从存储器中读取的字数据被当作目的地址,从而可以实现程序流程的跳转。该指令在程序设计中比较常用,取寻址方式灵活多样,请读者认真掌握。

    指令示例:

    LDR R0,[R1] ;                     将存储器地址为R1的字数据读入寄存器R0

    LDR R0,[R1,R2] ;                    将存储器地址为R1+R2的字数据读入寄存器R0

    LDR R0,[R1,#8] ;                          将存储器地址为R1+8的字数据读入寄存器R0

    LDR R0,[R1,R2]!;                 将存储器地址为R1+R2的字数据读入寄存器R0,幵将新地址R1+R2

    入R1

    LDR R0,[R1,#8];            将存储器地址为R1+8的字数据读入寄存器R0,幵将新地址R1+8写入R1

    LDR R0,[R1],R2 ;          将存储器地址为R1的字数据读入寄存器R0,幵将新地址R1+R2写入R1

    LDR R0,[R1,R2,LSL#2]!;       将存储器地址为R1+R2×4的字数据读入寄存器R0,幵将新地址R1+R2×4写入R1

    LDRR0,[R1],R2,LSL#2 ;           将存储器地址为R1的字数据读入寄存器R0,幵将新地址

    R1+R2×4写入R1

     “ARM是RISC结构,数据从内存到CPU之间的移动只能通过L/S指令来完成,也就是ldr/str指令。比如想把数据从内存中某处读取到寄存器中,只能使用ldr

    比如:

    ldr r0, 0x12345678

    就是把0x12345678这个地址中的值存放到r0中。

    6.MRS指令

    MRS指令的格式为:

    MRS{条件} 通用寄存器,程序状态寄存器(CPSR戒SPSR

    MRS指令用于将程序状态寄存器的内容传送到通用寄存器中。该指令一般用在以下两种情冴:

    Ⅰ.当需要改变程序状态寄存器的内容时,可用MRS将程序状态寄存器的内容读入通用寄存器,修改后再写回程序状态寄存器。

    Ⅱ.当在异常处理或进程切换时,需要保存程序状态寄存器的值,可先用该指令读出程序状态寄存器的值,然后保存。

    指令示例:

    MRS R0,CPSR ;传送CPSR的内容到R0

    MRS R0,SPSR ;传送SPSR的内容到R0

    所以,上述汇编代码含义为,将CPSR的值赋给R0寄存器

    7.bic指令

    BIC指令的格式为:

    BIC{条件}{S} 目的寄存器,操作数1,操作数2

    BIC指令用于清除操作数1的某些位,并把结果放置到目的寄存器中。操作数1应是一个寄存器,操作数2可以是一个寄存器,被移位的寄存器,或一个立即数。操作数2为32位的掩码,如果在掩码中设置(置1)了某一位,则清除返一位。未设置的掩码位保持不变。

    而0x1f=11111b

    所以,此行代码的含义就是,清除r0的bit[4:0]

    8.orr指令

    ORR指令的格式为:

    ORR{条件}{S} 目的寄存器,操作数1,操作数2

    ORR指令用于在两个操作数上迕行逻辑戒运算,幵把结果放置到目的寄存器中。操作数1应是一个寄存器,操作数2可以是一个寄存器,被移位的寄存器,或一个立即数。该指令常用于设置操作数1的某些位。

    指令示例:

    ORR R0,R0,#3 该指令设置R0的0、 1位,其余位保持不变。

    所以此行汇编代码的含义为:

    而0xd3=1101 0111

    将r0与0xd3算数或运算,然后将结果给r0,即把r0的bit[7:6]和bit[4]和bit[2:0]置为1

    9.msr指令

    MSR指令的格式为:

    MSR{条件} 程序状态寄存器(CPSR或SPSR)_<域>,操作数

    MSR指令用于将操作数的内容传送到程序状态寄存器的特定域中。其中,操作数可以为通用寄存器戒立即数。 <域>用于设置程序状态寄存器中需要操作的位,32位的程序状态寄存器可分为

    4个域:

    位[31:24]为条件标志位域,用f表示;

    位[23:16]为状态位域,用s表示;

    位[15:8]为扩展位域,用x表示;

    位[7:0]为控制位域,用c表示;

    该指令通常用于恢复戒改变程序状态寄存器的内容,在使用时,一般要在MSR指令中指明将要操作的域。

    指令示例:

    MSR CPSR,R0 ;传送R0的内容到CPSR

    MSR SPSR,R0 ;传送R0的内容到SPSR

    MSR CPSR_c,R0 ;传送R0的内容到SPSR,但仅仅修改CPSR中的控制位域”

    此行汇编代码含义为,将r0的值赋给CPSR

    10.align

    .balignl是.balign的变体

     .align伪操作用于表示对齐方式:通过添加填充字节使当前位置

    满足一定的对齐方式。.balign的作用同.align

    .align {alignment} {,fill} {,max} 其中:alignment用于指定对齐方式,可能的取值为2的次幂,缺省为4。fill是填充内容,缺省用0填充。max是填充字节@数最大值,如果填充字节数超过max,  就不进行对齐,例如:

    .align 4  /* 指定对齐方式为字对齐 */

    11.macro----.endm

    https://www.crifan.com/files/doc/docbook/uboot_starts_analysis/release/htmls/images/macro_syntax.png

    所以,此处就相当于一个无参数的宏bad_save_user_regs,也就相当于一个函数了。

    12.ldmstm指令

    https://www.crifan.com/files/doc/docbook/uboot_starts_analysis/release/htmls/images/ldm_stm_syntax.png

    其中条件码的含义:

    https://www.crifan.com/files/doc/docbook/uboot_starts_analysis/release/htmls/images/cond_format.png

    批量数据加载/存储指令ARM微处理器所支持批量数据加载/存储指令可以一次在一片连续的存储器单元和多个寄存器之间传送数据,批量加载指令用于将一片连续的存储器中的数据传送到多个寄存器,批量数据存储指令则完成相反的操作。常用的加载存储指令如下:

    LDM(或STM)指令的格式为:

    LDM(或STM){条件}{类型} 基址寄存器{!},寄存器列表{∧}

    LDM(或STM)指令用于从由基址寄存器所指示的一片连续存储器到寄存器列表所指示的多个寄存器之间传送数据,该指令的常见用途是将多个寄存器的内容入栈或出栈。

    其中,{类型}为以下几种情况:IA 每次传送后地址加1;IB 每次传送前地址加1;DA 每次传送后地址减1;DB 每次传送前地址减1;FD 满递减堆栈;ED 空递减堆栈;FA 满递增堆栈;EA 空递增堆栈;{!}为可选后缀,若选用该后缀,则当数据传送完毕之后,将最后的地址写入基址寄存器,否则基址寄存器的内容不改变。基址寄存器不允许为R15,寄存器列表可以为R0~R15的任意组合。{∧}为可选后缀,当指令为LDM且寄存器列表中包含R15,选用该后缀时表示:除了正常的数据传送之外,还将SPSR复制到CPSR。同时,该后缀还表示传入或传出的是用户模式下的寄存器,而不是当前模式下的寄存器。

    指令示例:

    STMFD R13!,{R0,R4-R12,LR} ;将寄存器列表中的寄存器(R0,R4到R12,LR)存入堆栈。LDMFD R13!,{R0,R4-R12,PC} ;将堆栈内容恢复到寄存器(R0,R4到R12,LR)。所以,此行的含义是,将r0到r12的值,一个个地传送到对应的地址上,基地址是sp的值,传完一个,sp的值加4,一直到传送完为止。此处,可见,前面那行代码:sp = sp - 72就是为此处传送r0到r12,共13个寄存器,地址空间需要13*4=72个字节,

    即前面sp减去72,就是为了腾出空间,留此处将r0到r12的值,放到对应的位置的。

    此处的含义就是,将_armboot_start中的值,参考前面内容,即为_start,而_start的值:从 Nor Flash启动时:_stat=0relocate代码之后为:_start=TEXT_BASE=0x33D00000此处是已经relocate代码了,所以应该理解为后者,即_start=0x33D00000所以:r2=0x33D00000

    13.Ldr伪指令

    伪指令,就是“伪”的指令,是针对“真”的指令而言的。真的指令就是那些常见的指令,比如上面说的arm的ldr,bic,msr等等指令,是arm体系架构中真正存在的指令,你在arm汇编指令集中找得到对应的含义。而伪指令是写出来给汇编程序看的,汇编程序能看的伪指令具体表示的是啥意思,然后将其翻译成真正的指令戒者迕行相应的处理。

    伪指令ldr诧法和含义:

    http://blog.csdn.net/lihaoweiV/archive/2010/11/24/6033003.aspx

    “另外迓还有一个就是ldr伪指令,虽然ldr伪指令和ARM的ldr指令很像,但是作用不太一样。 ldr伪指令可以在立即数前加上=,以表示把一个地址写到某寄存器中,比如:ldr r0, =0x12345678这样样,就把 0x12345678 这个地址写到 r0 中了。所以,ldr 伪指令和 mov 是比较相似的。”

    叧丌过 mov 指令后面的立即数是有限制的,返个立即数,能够必须由一个 8 位的二迚制数,即属亍 0x00-0xFF 内的某个值,经过偶数次右移后得到,这样才是合法数据,而 ldr 伪指令没有返个限制。

    那为何 ldr 伪指令的操作数没有限制呢,那是因为其是伪指令,写出来的伪指令,最终会被编译器解释成为真正的,合法的指令的,一般都是对应的 mov 指令。返样的话,写汇编程序的时候,使用 MOV 指令是比较麻烦的,因为有些简单的数据比较容易看出来,有些数据即丌容易看出来是否是合法数据。所以,对此,ldr 伪指令的出现,就是为了解决返个问题的,你叧管放心用 ldr 伪指令,丌用关心操作数,而写出的 ldr 伪指令,编译器会帮你翻译成对应的真正的汇编指令的。而关亍编译器是如何将返些ldr伪指令翻译成为真正的汇编指令的,我的理解是,其自劢会去算出来对应的操作数,是否是合法的mov 的操作数,如果是,就将该ldr伪指令翻译成mov指令,否则就用别的方式处理,我所观察到的,其中一种方式就是,单独申请一个4字节的空间用于存放操作数,然后用ldr指令实现。

    14.Mov指令

    mov指令语法:

    “1、 MOV指令

    MOV指令的格式为:

    MOV{条件}{S} 目的寄存器,源操作数

    MOV指令可完成从另一个寄存器、被移位的寄存器或将一个立即数加载到目的寄存器。其中S选项决定指令的操作是否影响CPSR中条件标志位的值,当没有S时指令丌更新CPSR中条件标志位的值。

    指令示例:

    MOV R1,R0 ;将寄存器R0的值传送到寄存器R1

    MOV PC,R14 ;将寄存器R14的值传送到PC,常用亍子程序返回

    MOV R1,R0,LSL#3 ;将寄存器R0的值左移3位后传送到R1

    不过对于MOV指令多说一句,那就是,一般可以用类似亍:

    MOV R0,R0

    的指令来实现NOP操作。

    15.cmp指令

    cmp是比较指令,cmp的功能是相当于减法指令,只是不保存结果.cmp指令执行后,将对标志寄存器产生影响.其他相关指令通过识别这些被影响的标志寄存器来得知比较结果.

    功能: 计算操作对象1 - 操作对象2 但不保存结果,仅仅根据计算结果对标志寄存器进行设置.比如cmp ax,ax  是做ax - ax 的运算,结果为0,但并不在ax中保存,仅影响flag的相关各位.

    指令执行后: zf = 1,pf = 1,sf = 0,cf = 0,of = 0;

      假设现在AX寄存器中的数是0002H,BX寄存器中的数是0003H。
    执行的指令是:CMP  AX,  BX
       执行这条指令时,先做用AX中的数减去BX中的数的减法运算。

    列出二进制运算式子:

               0000 0000 0000 0010
          -  0000 0000 0000 0011
    _________________________________
    (借位1) 1111 1111 1111 1111
    所以,运算结果是 0FFFFH
    根据这个结果,各标志位将会被分别设置成以下值:
             CF=1,因为有借位
             OF=0,未溢出
             SF=1,结果是负数
             ZF=0,结果不全是零
          还有AF, PF等也会相应地被设置。
        CMP 比较指令做了减法运算以后,根据运算结果设置了各个标志位。标志位设置过以后,0FFFFH这个减法运算的结果就没用了,它被丢弃,不保存。执行过了CMP指令以后,除了CF,ZF,OF, SF,等各个标志位变化外,其它的数据不变。

    对照普通的减法指令 SUB   AX,  BX,它们的区别就在于:

         SUB指令执行过以后,原来AX中的被减数丢了,被换成了减法的结果。
         CMP指令执行过以后,被减数、减数都保持原样不变。

    16.Sub指令

    SUB指令介绍:

    SUB:不带借位的减法指令。

    指令格式:SUB OP1,OP2
     

    指令功能:(OP1)←(OP1)-(OP2),将OP1-OP2的值,保存在OP1中,如:SUB [EAX],1 以EAX寄存器为内存地址,将该地址的值减1,

    指令介绍

    目的操作数减去源操作数,结果放在目的操作数中。源操作数原有内容不变,并根据运算结果置标志位SF,ZF,AF,PF,CF,OF

    SUB指令可以进行字节或字的减法运算,源操作数和目的操作数的约定与ADD指令相同。

    操作数的类型可以根据程序员的要求约定为带符号数或者无符号数。当无符号数的较小数减去较大数时,因不够减而产生借位,此时进位标志CF置1.当带符号数的较小数减去较大数时,将会得到负的结果,则符号位SF置1.带符号数相减,如果溢出,则OF置1.
    【例】
    1. SUB BL,AL
    设(BL)=23H,(AL)=78H,(BL)=23H-78H=ABH(1010101)
    根据运算结果,各标志位为:CF=1,ZF=0,SF=1,OF=0,PF=0,AF=1
    2. SUB SI,SI
    寄存器自身相减,则结果为零,此时:
    OF=0,SF=0,ZF=1,PF=1,CF=0

    展开全文

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 73,408
精华内容 29,363
关键字:

为什么linux没有汇编语言

linux 订阅