汇编语言-基础知识
汇编语言-基础知识
汇编语言的产生
计算机作为一个只能读懂和执行二进制的东西,在其刚被发明出时,都是用机器语言(01二进制形式表示)来写入程序,随着程序越来越复杂,需要更长的组合,不光一个微小的0->1将会导致错误,以及过于难记忆与分辨。
随后找到了一种解决方法,将这些最基础二进制的指令,用一串字符表示,再由电脑的汇编器(Assembler)将这串字符转化成二进制机器语言,程序员只需要负责写汇编指令的源代码即可。
汇编语言由三部分组成:
- 汇编指令(有与其专门对应的机器码)
- 伪指令(由汇编器执行,没有与其专门对应的机器码)
- 其他符号(如+,-,*,/由汇编器执行,没有与其专门对应的机器码)
程序放在哪里
CPU是电脑能够运算的核心部件,但是想要指挥CPU,需要有指令和数据,储存运行中程序的指令和数据的就是常说的内存。对于磁盘中的数据, CPU无法直接直行,需要加载到内存中才可以被CPU所使用。
指令和数据一样,都是一串二进制码, CPU在工作的时候,会根据需要把它们解释成指令或者数据,例如1000101111000011这一串二进制码,它既可以表示数据8BC3_H,又可以表示指令mov ax, bx (8086CPU中)。
对于内存,目前的方式均为以8个bit即1byte为最小的存储单元(1byte可以保存8位二进制数), CPU想从内存中读取或者存储数据,首先需要找到要存在内存的地址,传达控制信息是读入还是写入,以及读或写的数据。
在一台计算机中, CPU通过总线,与内存等外部设备进行连接,总线可分为三种,分别是地址总线,数据总线以及控制总线,字如其意,地址总线负责找到要读取或者储存的那个位置,数据总线负责数据的传输,控制总线负责传输要执行的行为是读入还是写入。对于8086CPU,他的地址总线有20根,数据总线有16根,也就是说他的寻址空间为2^20Byte,也就是最多可以寻址1MB内存,最多每次只能传输16bit(2Byte)的数据。控制总线与上面所说的地址总线和数据总线不同,他只是一个总称,是计算机不同控制线的集合,控制线分别连接于不同的 外部设备,控制线越多,能控制的外部设备就越多。
内存的地址空间
以典型的8086CPU为例子,他含有20条地址总线,寻址空间最大可达到2^20(00000 -> FFFFF )yte。CPU有20条地址针脚,这些针脚被接在主板上的CPU槽位,通过主板上的地址线连接到各种bios(Base input/output system),和拓展接口。
这些内存地址被分配给不同的功能器件,不同的计算机系统分配内存地址空间 的方式也不同,以8086CPU为例,00000到9FFFF为主存(RAM)空间,A0000到BFFFF为显存的地址空间,C0000到FFFFF为各类ROM(内容无法更改)地址空间。
拆开CPU
一个典型的CPU内部,由运算器,寄存器和控制器等组成,这些器件之间靠内部总线连接,在上面部分说到的均为外部总线,他们被埋在主板中。简单来说:运算器负责执行运算操作,寄存器负责储存运算器要使用的数据,控制器负责控制各种器件协同工作。
对CPU来说,最能直接控制的是它的寄存器,我们通过控制寄存器,来实现对CPU的操作,不同的CPU寄存器的数量与结构不同。8086CPU包含14个寄存器,均为16bit,分别是:
- AX,BX,CX,DX,4个通用寄存器,可分为高低8位独立使用
- CS,DS,ES,SS,4个段寄存器
- BP,SP,SI,DI,4个指针寄存器
- FR,标志寄存器
- IP,程序计寄存数器
8086的寻址方式
对于8086CPU它包含20条地址总线,和16条数据总线,如何用16位达到20位的寻址呢?8086CPU采用的是:物理地址=段地址*16+段内偏移地址 的方式。比如说段地址是2000H,段内偏移地址是0016H,它所指向的物理地址就是20000H+0016H=20016H。在8086CPU中执行将断地址和段内偏移地址转化成物理地址的器件是地址加法器。
CS段寄存器和IP寄存器
CS和IP可以说是CPU中最重要的两个寄存器,他们指示出CPU当前要读取的指令地址, CPU将CS16+IP所指向的内存地址之后的若干个内存单元压入指令缓存区,通过对指令的解码来拆分出指令,和指令长度N,IP变为IP+N。(8086CPU作为初代X86架构,是CISC指令集,每个指令及长度不等)*。
如果计算机只能按照IP不仅自增顺序执行的话那肯定是不行的,因为很显然,如果计算机不能循环,那么只能把重复的指令不停的写,这是非常不聪明的做法。想要实现循环就需要修改CS和IP寄存器中的内容,在之前有过例子,如果我们想将0045H放到寄存器ax,可以用mov ax, 0045H,但是这对CS和IP寄存器是不可以,系统没有提供直接对他们赋值的操作(mov ip, 0045H ;这样的是非法操作),改变他们内容的有一套专门的指令,被称为跳转指令。
;跳转指令的用法
jmp 6000H:0984H ;同时修改CS段地址和IP地址 jmp CS段地址:IP地址
jmp 0984H ;只修改IP地址,直接常数赋值
jmp AX ;只修改IP地址,把ax的内容赋值给IP
数据在内存中的存储
以8086CPU为例,在16位的寄存器,高8位储存高位字节,低8位储存低位字节。在内存里,16位的数据,高八位储存在高地址,低八位储存在低地址。
mov ax,2000H ;将2000h放入ax
mov ds,ax ;令ds为2000h
mov [0],8967H;把8967h放入2000:0000的位置
mov [2],3457H;把8967h放入2000:0002的位置
假想内存
|67| 20000
|89| 20001
|57| 20002
|34| 20003
CPU要读写一个内存单元的时候, 必须先给出这个内存单元的地址, 在8086PC 中,内存地址由段地址和偏移地址组成。8086CPU中有一个DS寄存器,通常用来存放耍访问数据的段地址。比如我们要读取20000H单元的字内容(取一个字,16bit), 可以用如下的程序段进行。
mov ax,2000H ;将2000h放入ax
mov ds,ax ;令ds为2000h
mov ax,[0] ;把8967h放入ax的位置
计算机中的栈
栈是一种很常用的数据结构,在计算机系统中通常用于函数嵌套时,保存调用函数的临时变量,8086 CPU同样也提供了栈功能,用指令push入栈,pop出栈。
mov ax,2000H ;将2000h放入ax
push ax ;将ax寄存器中的值压入栈中
pop bx ;从栈中取出一个字放入bx中
和上面谈到数据如何储存在内存中,以及指令如何储存在内存中一样,栈是通过段寄存器 SS 和寄存器 SP,栈顶的段地址存放在 SS 中,偏移地址存放在 SP 中。任意时刻,SS:SP 指向栈顶元素。push 指令和 pop 指令执行时,CPU 从 SS 和 SP 中得到栈顶的地址。与指令与数据存储不同,计算机中的栈空间是从地址高位储存向低位,每次push压入元素,SP都会自减2,每次POP出栈,SP都会自增2。
mov ax,1000H ;将2000h放入ax
mov ss,ax
mov ax,000FH
mov sp,ax
mov ax,7892H
push ax
pop bx
---------------PUSH前----------------
| |10007
| |10008
| |10009
| |1000A SS=1000
| |1000B SP=000F
| |1000C
| |1000D
| |1000E
| |1000F
---------------PUSH后----------------
| |10007
| |10008
| |10009
| |1000A SS=1000
| |1000B SP=000D
| |1000C
|92|1000D
|78|1000E
| |1000F
----------------POP后----------------
| |10007
| |10008
| |10009
| |1000A SS=1000
| |1000B SP=000F
| |1000C
|92|1000D
|78|1000E
| |1000F
------------------------------------
可以注意到在执行PUSH操作时,SP是先自减2,然后再将SS:SP所指的内存地址中放入ax寄存器中的值,依旧遵照高位放在高地址,低位放在低地址。而执行POP操作时,并不改变内存中的值,只是将将SS:SP所指的内存地址中的值放入bx寄存器,随后SP自增2。
使用栈还有可能出现超出栈空间的问题,比如我们将10000-1000F设为栈地址,当连续10几次PUSH或POP的时候,SS:SP所指向的位置已经偏移出了栈地址,在8086CPU中并未对栈越界提供保护,只能靠自己小心不要超出。
除了上面用到的对通用寄存器PUSH和POP,还支持对段寄存器的操作,如PUSH DS,以以及对内存数据的操作 PUSH [0].
如何设置栈段:假如说我们希望将10000-1FFFF这块儿内存设为栈,初始的SS和SP应该设备多少呢?SS毋庸置疑为1000,上面说过SS:SP指所指向的地址是栈顶元素,当有一个16位的数据在栈中时,SP为FFFE,出栈后,SP=SP+2,SP=0,初始的SP应该是0。