程序的表示、转换和链接(二) 程序的编译汇编、指令

  计算机中的存储程序工作方式:

  数据和指令都储存在内存中,且有相应的地址。指令按顺序存放,由OP操作码和ADDR操作数字段决定,程序起始地址放在PC(程序计数器)中。

  指令自动执行的流程:

  1. 根据PC取指令

  2. 指令译码

  3. 取操作数

  4. 指令执行

  5. 回写结果

  6. 修改PC的值,回到1

  程序启动前,指令和数据都存放在内存中,以01序列的形式存在。

  指令执行过程中,指令和数据被取到CPU中的寄存器中,指令在指令寄存器IR中,数据在通用寄存器GPR中。

  指令中需要给出:

  操作码:进行什么操作

  操作数:指令中直接给出,称为立即数;或者是寄存器编号;或者是存储器地址

  目的操作数地址:寄存器编号,存储器地址

  

  汇编指令就是机器指令的符号表示形式。可以有不同的格式(Intel格式/ATT格式)

  总之,指令无非是描述寄存器和内存和立即数之类的加加减减!这些“加加减减”就是指令集。

  程序编译的过程:预处理->编译->汇编,最后得到.o目标文件,是二进制机器码无法识读,但可以反汇编为汇编语言程序。

  注意,.o为可重定位目标文件,而一般的可执行文件没有.o后缀。对test.o和test用objdump -d反汇编得到汇编语言源程序:可重定位目标文件test.o的位移量从0开始(还没有链接生成可执行文件),每次根据一条指令的长度进行加操作;而test的位移量是虚拟地址(形如80483d4)

  可重定位目标文件与可执行目标文件有何区别?

  编译之后产生的目标文件是可重定位目标文件.o,并不能直接被运行,其实只是一个程序模块,而链接就是把.o文件与系统提供的标准库函数链接到一起,生成可执行的目标文件。

  后面我们将学习可执行文件的存储器映像,重定位和符号解析等

    

  ISA,指令集体系结构,规定了硬件提供了哪些接口

  • 可执行的指令的集合,指令格式/操作种类/操作数
  • 操作数类型
  • 寄存器组的结构:有哪些寄存器/名称编号长度用途等
  • 操作数所能存放的存储器空间大小和编址方式(每个单元多少位)
  • 操作数的存放方式:大端小端?
  • 寻址方式(?)
  • 控制方式(程序计数器等)

  ISA和微体系结构(计算机组成)的关系?不同的ISA如IA-32,MIPS,ARM指令集不同!而计算机组成必须能够实现ISA规定的功能,提供GPRs,标志,运算电路等。 同一套ISA可以有不同的计算机组成,如乘法指令可以由ALU或者乘法器实现。

 

  IA-32的体系结构

  8个通用寄存器GPRs, 一个标志寄存器,可寻址的空间是4GB的空间

  IA-32支持的数据类型及格式:当我们编写C语言程序时,定义的C语言变量实际上在底层归结于Intel操作数,其实就是字节、字、双字,单精度浮点数、双精度浮点数等;注意IA-32由16位结构发展而来,因此一个字为16位,后缀为w。

  知道寄存器组织、标志寄存器(零标志、溢出标志等)、

  计算机中所有运算都是通过加法器实现的,加法器本身并不知道进行计算的是带符号数还是无符号数,加法器在运算后只是简单地取低n位作为结果,并生成标志信息。  

   操作数寻址方式:如何根据指令中给定的信息,获得操作数或者操作数地址?操作数可以是立即数(立即数寻址),寄存器中存放(寄存器寻址),也可能在存储单元中存放。

  存储器操作数寻址:按字节编址,有实地址模式保护模式

  实地址模式基本用不到,8086/8088兼容,才20位。286就是16MB,合24位了,采用保护模式寻址方式。

  加电过程中还是实地址模式,加电后就是保护模式了。

  段寄存器存放段基址,确定操作数位于哪个段内。(16位段寄存器,则每一段可能有4GB/16=256MB这么大)段的话有代码段、堆栈段、数据段、附加段等。

  段基址+有效地址,得到线性地址。

  保护模式下存储器寻址的方法很多:位移、基址寻址、基址加位移等等!为什么要这么多方式呢?因为我们需要对不同的数据类型采用不同的寻址方式!

  例如:二维数组用 位移+基址+比例变址 的寻址方式

  总之,IA-32是一种CISC(复杂指令集),可寻址4G空间,字节编址,小端方式。了解其8个通用寄存器,EIP(PC),标志寄存器,6个段寄存器(间接给出段基址),各种寻址方式,变长指令。

  最后我们稍微了解一下IA-32的常用指令类型。

  • 传送指令
    • MOV PUSH LEA等  
    • 注意PUSH指令用于入栈。栈用于嵌套过程调用,栈顶在低地址。
    • 函数如test.c里定义了一个add函数,那么机器语言的每一条指令都有其虚拟地址,EIP被初始化存放头地址。所谓执行代码/程序/函数无非是一系列指令的执行!
  • 定点算术运算指令
    • 不论加减都直接进行运算,不关心有无符号(有标志位做事)
    • 加减,加一减一运算,取负运算,都只关心标志位。分别都有8/16/32位指令(后缀为bwl)
    • 乘除运算则区分符号
  • 移位运算(主要是算术移位与逻辑移位)

     int类型的变量在被编译为底层指令是算术移位(因为需要保留符号),而unsigned是逻辑移位。

  • 控制转移指令
    • 无条件转移指令
    • 条件转移指令
    • 调用和返回指令(用于过程调用)
    • 中断指令  
  • 浮点处理指令
    • 浮点处理环境是与x86配套的浮点协处理器架构x87 FPU, 包括8个浮点数据寄存器,都是80位的扩展精度格式。数据入栈和出栈时会自动进行数据类型的转换。8个FPU寄存器首尾相接形成堆栈,称为浮点数据栈。栈顶为ST(0),浮点数可以在内存和浮点栈之间传输(同时进行类型转换)。
    • 常用指令主要有:装入(内存到寄存器)、存储(寄存器到内存)、出栈入栈等;加法减法乘法除法,结果可以直接出栈

 

  • 什么是栈帧Stack Frame?

      栈帧,又称过程活动记录,是一个内存块,当一个方法A(无非就是函数嘛!)被调用时就产生一个栈帧F1,并存在于栈顶。A函数又调用了B函数,于是栈帧F2又被压入栈顶……

    栈帧中存放着一个函数执行期间的局部变量、中间数据。

  • 的方向是从高地址向地址,即栈顶的地址最低,ebp指向当前栈帧的底部,实际上底部的地址更高,esp指向当前栈帧的顶部,再往下一步就是未知区域啦,实际上地址较低。 
    • #include<stdio.h>
      double(int x){
            return 1.0/x;  
      }    
      void main(){
          double a,b;
          int i;
          a = f(10);
          b = f(10);
          i = a == b;
          return ;
      }    

      我们考虑这个程序,结果显示i的值为false,也就是a与b不相等,为什么会这样呢?

      首先我们考虑f(int x)函数的汇编指令形式:

push   %ebp      ; ebp是基址指针寄存器,存放的指针指向系统栈最上面一个栈帧的底部
mov    %esp,%ebp ; esp是栈顶指针寄存器,存放的指针指向系统栈最上面一个栈帧的顶部
;这里没有更新esp,开辟新的栈帧
fld1 ; fidivl 0x8(%ebp) ; leave ; ret ;

  push %ebp  ebp基址指针是寄存器,存放着当前活动记录(栈帧)的底部(实际上并不是栈顶)

                                  先将当前栈帧的栈底地址存入栈中,也就是当前栈顶存放着当前栈帧的起始位置

  mov %ebp,%esp  将esp的值赋给ebp,也就是新的栈底是原来的栈顶

  再将esp的值更新,加上(从地址上看是减去)一个偏移,从而开辟出一块新的内存

  fldl   将1.0压入浮点栈的栈顶ST(0)

  fidivl 0x8(%ebp)  将ebp这个寄存器存放的地址(也就是活动记录的底部加8)加偏移后取内存中的机器数,用ST(0)的值除之,然后将结果存入ST(0)中

  leave    函数已经运算完了,将原来的ebp和esp恢复

  ret        将栈顶的返回地址弹出到EIP,然后按照EIP此时指示的指令地址继续执行程序

  上面的i为0,说白了就是因为a在计算完后存放在内存中,经过了由浮点栈ST(0)到内存80->64位转换的精度损失之后,已经不是80位的机器数的值了,而马上与b作==操作,并不需要把b也弹出浮点栈,因此80位的b与64位的a当然不相等。

  最后我们了解了MMX、SSE这样的并行计算加速指令集,即SIMD(单条指令处理多个数据)。SIMD指令比普通指令运行周期要长,加速没有理想中的那么大。

  问:32位电脑能加内存条吗?

  答案是不能。32位电脑最大只有4GB的寻址空间。

  问:IA-32, x86, IA-64, AMD64 都是什么东西?

  IA-32为Intel Architecture 32bit简称,即英特尔32位体系架构,在英特尔公司1985年推出的80386微处理器中首先采用。通常也被称为i386、x86-32、x86等。

  IA64是后来intel和惠普联合推出的64位体系架构,但是不兼容原有的32位体系结构的应用程序,导致市场惨淡。

  AMD推出了兼容32位的64位集关于IA-32的扩展,之后改名为AMD64,Intel后来也采用该架构,称为x86-64或者x64。

  问:什么是字节对齐?为什么需要对齐?

  存储器的最小存储单元是8位的字节Byte,但是有的数据所占空间的长度并不是8的倍数,例如扩展精度浮点数的长度为80,而在内存中占用96位空间,其中高16位无意义,这就是字节对齐。

  问:为什么应用程序在新的系统上需要兼容呢?应用程序、操作系统、编译器与CPU的位数(32/64)有什么关系?

  答:高级语言是机器语言的抽象,机器语言是基于微体系结构的,因此在16位机器上编译的程序不能在32位机器上运行。

  

  问:指令和数据的区别?所有的变量都是数据吗?常量const呢?立即数在源代码中是什么形式存在?(还没有搞明白)

  使用#define宏定义得到的常量是立即数,是没有类型的,也不分配内存;const常量有类型,存放在内存的静态区域内,程序运行期间只有一个拷贝;

  在编译时, 编译器通常不为const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。

  一个字面值常量在编译时被直接解析为立即数,编译器内部自行解释字面值常量的类型。

posted @ 2019-07-14 21:05  LiaoQian1996  阅读(267)  评论(0编辑  收藏  举报