zoukankan      html  css  js  c++  java
  • 通过汇编语言了解程序的实际构成

    问题

    1. 本地代码的指令中,表示其功能的英语缩写称为什么?

      助记符

    2. 汇编语言的源代码转换成本地代码的方式称为什么?

      汇编

    3. 本地代码转换成汇编语言的源代码的方式称为什么?

      反汇编

    4. 汇编语言的源文件的扩展名,通常是什么格式?

      .asm

    5. 汇编语言程序中的段定义指的是什么?

      构成程序的命令和数据的集合组

    6. 汇编语言的跳转指令,是在何种情况下使用的?

      将程序流程跳转到其他地址时需要用到跳转指令。在汇编语言中,跳转指令可以实现循环和条件分支。

    汇编语言和本地代码是一一对应的

    表示其功能的英语缩写单词称为助记符,使用助记符的编程语言成为汇编语言。汇编语言编写的源代码最终也要转换成本地代码才能运行,负责转换工作的程序称为汇编器,转换这一处理本身称为汇编。用汇编语言编写的源代码和本地代码是一一对应的。因而,本地代码也可以反过来转换成汇编语言的源代码,这一过程称为反汇编,持有该功能的逆变换程序称为反汇编程序。

    不过,本地代码变换成高级语言源代码的反编译,则要比反汇编要困难许多,几乎是不太可能的,因为高级语言的源代码和本地代码不是一一对应的。

    通过编译器输出汇编语言的源代码

    大部分C语言编译器,都可以把利用C语言编写的源代码转换成汇编语言的源代码,而不是本地代码。利用该功能,就可以对C语言的源代码和汇编语言的源代码进行比较研究。

    汇编语言源文件的扩展名通常用.asm来表示。

    不会转换成本地代码的伪指令

    汇编语言的源代码,是由转换成本地代码的指令和针对汇编器的伪指令构成的。伪指令负责把程序的构造即汇编的方法只是给汇编器。不过伪指令本身是无法汇编转换成本地代码的。

    由伪指令segment和ends围起来的部分,是给构成程序的命令和数据的集合体加上一个名字而得到的,称为段定义。一个程序由多个段定义构成。

    group这一伪指令,表示的是把多个段定义汇总成一个新名字的组。

    伪指令proc和endp围起来的部分,表示的是过程procedure的范围。在汇编语言中,过程相当于C语言的函数的形式。

    伪指令end表示的是源代码的结束。

    汇编语言的语法是操作码+操作数

    也存在只有操作码,没有操作数的指令

    操作码表示的是指令动作,操作数表示的是指令对象。

    存在多个操作数时,用逗号分隔。

    能够使用何种形式的操作码,是由CPU的种类决定的。

    常用的操作码功能:

    操作码 操作数 功能
    mov A, B 把B的值赋给A
    and A, B 把A同B的值相加,并将结果赋给A
    push A 把A的值存储在栈中
    pop A 从栈中读取出值,并将其赋给A
    call A 调用函数A
    ret 将处理返回到函数的调用源

    寄存器是CPU中的存储区域。不过,寄存器不仅仅具有存储指令和数据的功能,也有运算功能。

    主要寄存器:

    寄存器名 名称 主要功能
    eax 累加寄存器 运算
    ebx 基址寄存器 存储内存地址
    ecx 计数寄存器 计算循环次数
    edx 数据计数器 存储数据
    esi 源基址寄存器 存储数据发送源的内存地址
    edi 目标基址寄存器 存储数据发送目标的内存地址
    ebp 扩展基址指针寄存器 存储数据存储领域基点的内存地址
    esp 扩展栈指针寄存器 存储栈中最高位数据的内存地址

    最常用的mov指令

    操作数可以指定寄存器、常数、标签,以及方括号围起来的内容。如果指令了没有用方括号围起来的内容,就表示对该值进行处理;如果指令了用方括号围起来的内容,方括号中的值则会被解释为内存地址,然后就会对该内存地址对应的值进行读写操作。

    dword ptr(double word pointer)表示的是从指定内存地址读出4字节的数据。

    对栈进行push和pop

    栈是存储临时数据的区域,它的特点是通过push指令和pop指令进行数据的存储和读出,称为入栈和出栈。push和pop指令都只有一个操作数,这是因为,对栈进行读写的内存地址是由esp寄存器进行管理的。我是怕寄存器的值会自动进行更新,因而程序员就没有必要指定内存地址了。

    函数调用机制

    形参中位于后面的数值先入栈,这是C语言的规定。

    在汇编语言中,函数名表示的是函数所在的内存地址。

    虽然通过两次pop指令也可以实现湛清里,不过采用esp寄存器加8的方式会更有效率。

    push和pop必须以4字节为单位对数据进行入栈和出栈处理,长度小于4字节的数据也占用了4字节的栈区域。

    编译器有最优化功能。最优化功能是编译器在本地代码上费劲功夫实现的,其目的是让编译后的程序运行速度更快、文件更小。

    函数内部的处理

    CPU拥有的寄存器是由数量限制的。在函数调用前,调用源有可能已经在使用寄存器了。因而,在函数内部利用的寄存器,要尽量返回到函数调用前的状态。为此,我们就需要将其暂时保存在栈中,然后 再在函数处理完毕之前出栈,使其返回到原来的状态。

    在C语言中,函数的返回值必须通过eax寄存器返回,这也是规定。不过,eax寄存器无需还原到原始状态。

    函数的参数是通过栈来传递的,返回值是通过寄存器来返回的。

    始终确保全局变量用的内存空间

    C语言中,在函数外部定义的变量称为全局变量,在函数内部定义的变量称为局部变量。

    初始化的全局变量会被汇总到名为_DATA的段定义中;没有初始化的全局变量会被汇总到名为_BSS的段定义中;指令会被汇总到名为_TEXT的段定义中。

    标签表示的是相对于段定义起始位置的位置。编译后的函数名和变量名会附加一个下划线,这也是Borland C++的规定。

    dd(define double word)表示的是由两个长度为2的字节领域(word),也就是4字节的意思。

    db(define byte)表示有1个长度是1字节的内存空间,db 4 dup(?)就是4字节的内存空间,但值尚未确定。

    临时确保局部变量用的内存空间

    局部案例是临时保存在寄存器和栈中的。函数内部利用的栈,在函数处理完毕后会恢复到初始状态,因此局部变量的值也就被销毁了,而寄存器也可能会被用于其他目的。

    寄存器先时就使用寄存器,寄存器空间不足时就使用栈。局部变量利用寄存器,是Borland C++编译器最优化的运行结果。旧的编译器没有类似的最优化功能,局部变量就可能会仅仅使用栈。至于使用哪个寄存器则要由编译器来决定。这种情况下,寄存器只是被单纯地用于存储变量的值,和其本身的角色没有任何关系。

    循环处理、条件分支的实现方法

    C语言的for语句是通过在括号中指定循环计数器的初始值、循环的继续条件、循环计数器的更新来进行循环处理的。与此相对,在汇编语言的源代码汇总,循环是通过比较指令cmp和跳转指令jl来实现的。

    与mov指令相比,xor指令的处理速度更快。

    汇编语言中比较指令的结果,会存储在CPU的标志寄存器中。不过,标志寄存器的值,程序是无法直接参考的。实际上,汇编语言有多个跳转指令,这些跳转指令会根据标志寄存器的值来判定是否需要跳转。

    虽然大部分的C语言参考书中都写着“为了便于理解程序的结构,应尽量避免使用无条件分支的goto语句”,不过,在汇编语言这一领域中,如果不使用相当于goto语句的jmp指令,就无法实现循环和条件分支。

    了解程序运行方式的必要性

    多线程处理的Bug,如果没有调查过汇编语言的源代码,即对程序的实际运行方式不了解的话,是很难找到其原因的。

    为了避免这种Bug,我们可以采用以函数或C语言源代码的行为单位来禁止线程切换的锁定方法。通过锁定,在特定范围内的处理完成之前,处理不会被切换到其他函数中。

    现在基本上没有人用汇编语言来编写程序了。因为C语言等高级编程语言用1行就可以完成的事,汇编语言需要多行,效率很低。不过,汇编语言的经验还是很重要的。因为借助汇编语言,我们可以更好地了解计算机的机制。特别是对专业程序员来说,至少要有一次使用汇编语言的经验。

  • 相关阅读:
    js注意点:数组比较大小方法及数组与对象的区别
    ubuntu开通ftp虚拟用户
    linux(centos)禁止升级内核的办法
    解决ubuntu新建用户后,tab键不能使用的问题
    ubuntu下php7+mysql+nginx安装笔记
    使用nginx+lua+GraphicsMagick实现图片自动 裁剪
    mysql5.7配置文件(仅供参考)
    ubuntu下安装基于Apache的SVN服务器
    Linux CentOS 7.X 如何修改内核启动默认顺序
    Linxu下Redis安装
  • 原文地址:https://www.cnblogs.com/fr-ruiyang/p/14622734.html
Copyright © 2011-2022 走看看