5.程序的表示转换和链接

高级语言程序中的运算是通过指令来完成的,参与运算的变量或常量就是指令中的操作数,所以了解了运算的底层逻辑以后,来进一步了解指令。

目前主流计算机都是intel架构。

5.1

5.1.1 程序和指令的关系

还是得看编程模型,硬件结构,第一章的东西。

“存储程序”的工作方式。

程序由指令组成:

  • 程序在执行之前,数据和指令存放在存储器里,指令和数据都有地址,指令由OP、ADDR字段组成,(操作码字段,地址字段),程序的起始地址放在PC(程序计数器)里。

  • 开始执行程序

    • 1.根据PC取得指令

    • 2.指令译码

    • 3.取操作数

    • 4.指令执行

    • 5.回写结果

    • 6.修改PC的值

指令和数据在形式上没有区别,都是01序列。指令执行时,指令和数据从存储器挪动到CPU,指令放在指令寄存器里,数据放在通用寄存器里。

指令中需要给出的信息:

  • 操作性质(操作码)

  • 源操作数1;源操作数2(可选)。(立即数,寄存器编号,存储器地址)

  • 目的操作数地址(寄存器编号,存储地址)

存储地址的描述与操作数的数据结构有关

指令描述对谁干什么。操作数在指令里给出,就是个立即数,参考微机原理。

除了上面这些,再来回顾一下不同层次语言之间的等价转换关系。

高级语言无法直接执行,要用编译程序编译位汇编语言程序,再用汇编程序转换成机器指令,机器指令就是01序列,可以直接在硬件上执行,可以被机器理解。机器指令实际上是控制信号,控制数字电路动作。任何高级语言最终是通过执行若干条指令来完成的。

软件和硬件的交界面有个接口,这个接口就是指令系统,或者叫指令集体系结构,最关键的就是指令。

指令有微指令、机器指令、伪(宏)指令之分

这里讲的指令都是机器指令。微指令是硬件范畴,是微程序级指令,“微过程”。伪指令之由若干机器指令组成的指令序列,相当于子程序,属于软件范畴。汇编指令是机器指令的汇编表示形式,即符号表示,机器指令和汇编指令一一对应,都和机器结构有关,都是机器级指令。

机器指令是01序列,由若干字段组成

mov [bx+di-6], cl
movb %cl, -6(%bx, %di)

汇编指令来表示机器指令,可能有不同格式,比如intel格式,或AT&T格式

指令的功能可以用寄存器传送语言来描述(Register Transfer Language)

M[ R[bx] + R[di] - 6 ] ← R[cl]

mov,bx,movb都是助记符

最后再回顾一下高级语言转换为机器代码的过程。

[hello.c]预处理[hello.i]编译[hello.s]汇编[hello.o]链接[hello]

  • 预处理:高级语言源程序中#开头的文本处理

  • 编译:生成汇编语言程序

  • 汇编:生成机器语言文件

  • 链接:生成最终的可执行文件

5.1.2 目标代码和ISA

gcc使用举例

# 基本使用
gcc -O1 main.c test.c -o test

其中的 -O1 为一级优化,一般用 -O2 就可以,使用 -O0 时,得到的机器码最接近原来的 C 程序。使用 gcc 的一些选项可以转化过程的中间内容

# 源文件处理成预处理文件
gcc -E main.c -o main.i

# 预处理文件生成汇编程序文件
gcc -S main.i -o main.s

# 源文件直接处理成汇编程序文件
gcc -S main.c -o main.s

# 汇编程序文件生成可重定位的目标代码
gcc -c main.s -o main.o

# 

可重定位的目标程序已经是机器码了,二进制文件,想要看里面的内容需要用到反汇编工具,这个程序的起始地址都是0,显然这不是最终程序指令存放的地址,所以叫做可重定位,而且起始地址一定会重新定位到其他地方。

反汇编工具 objdump ,这个工具把机器码反汇编为汇编语言程序。

objdump -d test.o

编译得到的汇编语言程序和反汇编的略有区别。

两种可执行目标文件 main.omain 都是二进制文件。

  • main.o 可重定位目标文件

  • main 可执行目标文件

分别 dump 出来的结果,可以看出指令都是一样的,区别在于地址不同。可执行文件地址是一个虚拟地址。

实际看看

int add(int i, int j)
{
  int x = i + j;
  return x;
}

回顾指令系统,(指令集体系结构,ISA,Instruction Set Architecture),这是个规约(Specification),规定了如何使用硬件。

使用硬件,即硬件提供的功能,实际上是被抽象成指令被软件使用的,比如说一个 CPU 支持硬件浮点运算功能,那么它一定有和浮点运算相关的专用指令。

ISA 规定了计算机硬件可以执行的指令,指令的格式操作种类,操作数数据类型,寄存器名称,编号,操作数存放的空间,编制方式,大小端,寻址方式.........所有和硬件实现相关的东西。

因此 ISA 是计算机系统里必不可少的抽象层,是对硬件的抽象。

如果没有指令系统,一个数字电路就没法叫做通用计算机。

ISA 和计算机组成(微体系结构)的关系:微结构是指令系统的电路实现。

不同的ISA 如 IA-32 ,MIPS,ARM等,可以由不同的微体系结构来实现。

同一种ISA可以有不同的计算机组成,如乘法器可用ALU或乘法器实现。

所以ISA是计算机组成的抽象。

5.2

intel,IA-32的体系结构

5.2.1 Intel 处理器

停产的:

  • x86前的产品:4004,8008,8080

  • x87外置浮点运算器

    • 8/16bit 8087

    • 16bit 80187,80287

    • 32bit 80387

  • x86-16(16 bit):8086,8088,80186,80286

  • x86-32/IA-32(32bit):80386,80486,Pentium

  • x86-64/intel 64:Core 2

  • 微控制器:8051,MCS-96

现在有的产品

  • x86-32/IA-32:EP80579,Atom

  • x86-64/Intel 64:Xeon e3 e5 e7,Core i3 i5 i7 i9

x86是Intel开发的一类处理器体系结构的泛称。架构就叫做x86,后来数据无法注册商标,就叫英文名了,如Core i9,现在Intel把32位x86架构的名称从x86-32改称为IA-32。

AMD提出了一个兼容IA-32指令集的64位版本,amd称为AMD64,Intel叫其位Intel64,这个体系命名为x86-64或者直接x64。

IA-32规定了

  • 8个通用寄存器(GPR)0-7

  • 一个 EFLAGS

  • PC为EIP

  • 可寻址空间4GB(0-0xFFFFFFFF)

  • 指令格式变长,操作码变长,指令由若干字段组成

  • 数据存放在通用寄存器GPR里,指令存放在存储器里

    • 因此指令中要给出的信息:

    • 操作性质(操作码)

    • 源操作数1 源操作数2(立即数,寄存器编号,存储地址)

    • 目的操作数地址(寄存器编号,存储地址)

5.2.2 IA-32寄存器组织

IA-32支持的数据类型

C声明
操作数类型
指令后缀
存储长度

unsigned char

整数/字节

b

8

unsigned short

整数/字

w

16

unsigned int

整数/双字

l

32

unsigned long int

整数/双字

l

32

unsigned long long int

-

-

32

char*

整数/双字

l

32

float

单精度浮点数

s

32

double

双精度浮点数

l

64

因此随着机器位数的发展,寄存器也是在不断兼容发展,新的机器也可以使用老的东西,这就出现了许多设计上的妥协。

寄存器组织:

  • EAX

  • EBX

  • ECX

  • EDX

8个寄存器的编号从000-111正好3个bit,寄存器的组织反映了体系结构发展的组织,字长扩充,指令兼容。

其中有个比较特殊的是标志寄存器,标志也叫条件码,这里要回顾ALU的硬件结构,通过数字逻辑电路,运算产生条件码保存在这个寄存器里。

6个条件标志,3个控制标志,这几个在8086里面就有,此外还有一些新机器有的标志。

5.2.3 IA-32的寻址方式

  • 寻址方式

    • 如何根据指令给定信息得到操作数或操作数地址

  • 操作数所在的位置

    • 指令中:立即数

    • 寄存器中:寄存器寻址

    • 存储单元中:存储器操作数,比较复杂的寻址方式

  • 存储器操作数的寻址方式和处理器工作模式有段

    • 实地址模式和保护模式

  • 实地址模式基本用不到,是为了和8086保持兼容

    • 在上电或复位时的模式

    • 寻址空间1MB,CS<<4 + IP

  • 保护模式

    • 上电后,采用虚拟存储管理,多任务在隔离、保护

    • 80286以上的处理器工作模式

    • 寻址空间4GB,32位线性地址分段

保护模式下的寻址方式,除了直接寻址,和寄存器寻址,其他的都是存储器操作数的寻址方式,操作数都放在存储器里,最终得到的地址时存储空间里的地址。

最终的线性地址(LA),段基址 + 偏移量,

寻址过程涉及到了分段虚拟管理方式,会在后面学习。

5.2.4 高级语言中寻址举例

int x;
float a[100];
short b[4][4];
char c;
double d[10];

double类型变量在linux里4Byte对齐存放,windows里8Byte对其存放。

通过基址找到指定索引的数据。

5.2.5 IA-32 指令格式

指令段:
操作码
寻址方式
SIB
位移
直接数据

字节数:

1或2

0或1

0或1

1、2、4

立即数

操作码和和机器模式一起确定寄存器位数(AL, AX, EAX)

对于寻址方式字段,可有可无

mod
reg/op
r/m

2bit

3bit

3bit

存储器操作数,指出基址寄存器和变址寄存器,B基址,I变址,ss比例因子,1/2/4/8这四种情况因此3bit就够了。

SS
Index
Base

2bit

3bit

3bit

举个例子,机器指令

8d 04 02 leal (%edx, %eax, 1),%eax

1000 1101 0000 0100 0000 0010

不难,但是多。各个位的划分是由第一个字节op操作码决定的。

IA-32是典型的CISC(复杂指令集计算机)风格的ISA

  • 8个通用寄存器(8,16,32)

  • 2个专用寄存器eip, eflasg

  • 6个段寄存器(间接给出段基址)

  • 存储器地址空间为4GB,按字节编址,小端方式

  • 寻址方式

    • 立即,寄存器,存储器(SR:[B]+[I]*s+A)段基址+有效地址(基址+变址x比例因子+偏移)

    • 相对寻址(对于指令来说的,跳转的时候,需要当前地址进行相对寻址)

  • 指令位变长指令字、变长操作码

  • 两种汇编语言格式

    • Intel格式

    • AT&T格式

8(%edx,%eax,4)

最后更新于