汇编语言——直接在硬件之上工作的编程语言

汇编指令(机器码的助记符)

cpu对存储器的读写逻辑上划分为:

  • 地址总线:地址总线上能传送多少个不同的信息,cpu就可以对多少个存储单元进行寻址。一个cpu有N根地址总线,则可以说这个cpu的地址总线的宽度为N,这样的cpu最多可以寻找2的N次方个内存单元(8086/8088cpu的cpu地址总线宽度为20位,寻址能力为$2^{20}=1024*1024字节=1024K字节=1M字节$)(8086/80286—16位,80386—32位)

    1
    2
    3
    寻址能力的判断:
    寻址能力=2的N次方(N为地址总线的条数)
    32位地址总线为例子,cpu的寻址能力2^32Bit=4G
    1
    2
    3
    4
    5
    6
    7
    8
    bit单位间的换算关系:
    1字节=1byte=1B=8位
    1KB=1024B
    1MB=1024KB
    1GB=1024MB

    1TB=1024GB
    1PB=1024TB
  • 数据总线:cpu和内存或其他器件之间的数据传送是通过数据总线来进行的,数据总线的宽度决定了cpu和外界的数据传送速度

  • 控制总线:cpu对外部器件的控制是通过控制总线来进行的。在这里控制总线是个总称,控制总线是一些不同控制线的集合。有多少根控制总线,就意味着cpu提供了对外部器件的多少种控制

    屏幕截图_20250108_144520

原码,反码,补码:

  • 原码:最高位(符号位0为正,1为负)+低位(数值)

  • 反码:分正反

  • 补码:分正反

    1
    2
    3
    4
    5
    6
    7
    8
    用8位表示7的原码: 00000111
    -7:10000111
    用8位表示7的反码:
    1.对于正数来讲反码和原码是一样的 00000111
    2.对于负数来讲除最高位以外,其余位进行取反 -7:11111000
    用8位表示7的补码:
    1.对于正数来讲补码和原码是一样的 00000111
    2.对于负数来讲补码等于反码加1 11111000+1->11111001

十进制转化BCD码:

bcd码:用四位二进制表示0~9数据
也就是从右向左每四位分!

屏幕截图_20250108_153030

寄存器(16位寄存器组,可以存放两个字节的数据):

  • 8个通用寄存器

    • 累加器AX:算数运算的主要寄存器(AH,AL两个独立寄存器,为高位和低位)
    • 基址寄存器BX:存放地址的偏移地址
    • 计数寄存器CX:作为循环和串操作等指令中的计数器,存放循环次数或重复次数
    • 数据寄存器DX:常用来存放双字长数据的高16位,或在间接寻址的I/O指令中存放I/O端口地址
    • 堆栈指针寄存器SP:用以指出在堆栈段中当前栈顶的地址
    • 基址指针寄存器BP:指出要处理的数据在堆栈段中的基地址
  • 段寄存器

    • 代码段寄存器CS:用于存放当前正在运行的程序。存放当前执行程序所在段的段地址,将其内容左移4位再加上IP指针的内容即为下一条执行指令的地址。
    • 堆栈段寄存器SS:是内存中开辟的专用存储区,用来暂时保存寄存器中的数据。存放当前堆栈段的段地址,将其内容左移4位再加上SP的内容即为栈顶地址
    • 数据段寄存器DS:存放当前数据段的段地址,将其内容左移4为再加上计算所得的偏移地址即为对数据段指定单元进行读/写的地址。
    • 附加数据段寄存器ES:是附加的数据,在串操作指令中用于存放目的操作数
  • 标志寄存器FLAGS

  • 指令指针寄存器IP:CS和IP两个关键的寄存器,cpu将CS:IP指向的内容当作指令执行

八位标志寄存器信息如下

寄存器标志位的判断(ZF,SF,OF,CF)

  • ZF:对于有符号数和无符号数都可以判断,结果为0,ZF=1;结果为1,ZF=0
  • SF:对于有符号数才有意义,SF=结果最高位(溢出时,只看最高位)
  • OF:对于有符号数才有意义,没有进行符号位扩展:OF=CS异或C1(CS是符号位进位,C1是最高数位进位);进行符号位扩展,双符号位位01/10时,OF=1
  • CF:对于无符号数,做加法时,有进位(CF=cout)CF=1;做减法时,有借位(CF=cout)CF=1。更简单的对于减法的判断方法—->直接观察,被减数<减数时CF=1

8086/8088寻址方式(获取操作数所在地址的寻址方式):

  • 操作数分为

    • 立即数:立即寻址助剂符 目的操作数(本身含有地址的含义) 源操作数(立即寻址只针对源操作数)

      1
      MOV AX,0FFFH 
    • 存储器操作数

      • 直接寻址”【(存放数据的偏移地址,也可以进行段重设)】“

        1
        2
        MOV BL,[1200H] ;默认DS段
        MOV BL,ES:[1200H] ;段重设
      • 寄存器间接寻址

        1
        2
        3
        4
        5
        6
        MOV AX,[SI] ;SI内容为1200H,把1200H所对应的内容传给AX
        BX ;默认在数据段DS
        BP ;默认在堆栈段SS
        SI ;DS
        DI ;DS
        SX ;DS
      • 寄存器相对寻址(在寄存器寻址的基础上再加一个相对位移量)用来存取表格或一维数组里的内容

        1
        2
        MOV AX,DATA[BX] ;DATA+[BX]如果DATA=0010H,BX=1200H,DS=1000H则完整地址=1000H:0010H+1200H=1000H:1210H=11210H
        ;DATA[BX] [BX]DATA DATA+[BX] [BX]+DATA [DATA+BX] [BX+DATA]这几种写法都行
      • 基址变址寻址

        1
        MOV AX [BX(BP)][SI(DI)] ;基准变址相加,BX默认DS段,BP默认SS段
      • 基址变址相对寻址

        1
        MOV AX [BX(BP)][SI(DI)]+(8/16bit的偏移量)
    • 寄存器操作数:寄存器寻址(其操作数是CPU寄存器中的数)

      1
      MOV SI AX

指令计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mov ax,18			;将18送入AX  AX=18
mov ah,78 ;将78送入AH AH=78
add ax,8 ;将寄存器AX中的数值加上8,并放入前面的操作数中 AX=AX+8
mov ax,bx ;将寄存器BX中的数据送入寄存器AX AX=BX
add ax,bx ;将AX,BX内容相加,结果存在AX中 AX=AX+BX

add ax,bx ;假设此时AX=8226H=BX,则结果应为1044CH,但结果为044CH,因为汇编语言也会出现溢出的情况

mov ax,001AH ;AX=001AH
mov bx,0026H ;BX=0026H
add al,bl ;al=1AH+26H=40H,则此时AX=0040HBX=0026H
add ah,bl ;AX=2640H,BX=0026H
add bh,al ;AX=2640H,BX=4026H
mov ah,0 ;AX=0040H,BX=4026H
add al,85H ;AX=00C5H,BX=4026H
add al,93H ;AX=0058H,因为发生溢出0158H变成0058H,不能进入到高位,因为这里相当于两个八位相加,BX=4026H

物理地址:8086有20位地址总线,可传输20位地址,寻址能力为1M;8086是16位结构的cpu,运算器一次最多可以处理16位的数据,寄存器的最大宽度位16位,在8086内部处理的,传输,暂存的地址也是16位,寻址能力也只有64KB

解决方案:用两个16位地址(段地址,偏移地址)合成一个20位物理地址16进制左移一位相当于16进制左移1位

屏幕截图_20250108_153224

屏幕截图_20250108_160408

屏幕截图_20250108_160416

  • 段地址×16必然是16的倍数,所以一个段的起始地址也一定是16的倍数
  • 偏移地址位16位,16位地址的 寻址能力位64K,所以一个段的长度最大位64K(0~FFFFFH)

屏幕截图_20250108_160441

  1. 从CS:IP指向内存单元读取指令,读取的指令进入指令缓冲器
  2. IP=IP+所读取指令的长度,从而指向下一条指令
  3. 执行指令转到步骤1,重复这个过程

如何修改CS,IP的指令(Debug中的R命令可以改变,但是Debug是调试手段不是程序方式)(CS和代码段的问题)

注意不能用指令mov修改
1
2
3
;mov cs 2000H 错误
;mov ip 0000h 错误
;mov ip cs 错误
转移指令可以修改ip和cs内容
1
2
3
4
5
6
;同时修改cs和ip内容  jmp 段地址:偏移地址
jmp 2AE3:3
jmp 3:0B16
;仅仅修改ip的内容 jmp某一和法寄存器
jmp ax(类似于 mov IP,ax但事实不能使用这个语句)
jmp bx
从2000H开始,执行的顺序是:
  1. mov ax,6622
  2. jmp 1000:3
  3. mov ax,0000
  4. mov bx,ax

屏幕截图_20250108_160454

屏幕截图_20250108_160501

DS(数据段寄存器)和[address]配合得出cpu要访问的内存单元的地址

1
2
3
4
5
6
7
8
mov bx,1000H
mov ds,bx
mov al,[0] ;将10000H(1000:0)中的字型数据读到al中,默认段地址保存在ds中,[0]代表偏移地址

mov bx,1000H
mov ds,bx
mov [0],al ;将al中的数据写到10000H(1000:0)中 内存和寄存器之间的数据可以相互传输
mov [0],cx ;cx中的16位数据送到1000:0中

将段地址送入DS的两种方式

  • mov ds,1000H 错误

  • mov bx,1000H

    mov ds,bx 正确(数据->一般寄存器->段寄存器)

1
2
3
4
5
6
7
8
9
10
11
12
;内存中:10000H 23
; 10001H 11
; 10002H 22
; 10003H 66
;指令:
mov ax,1000H
mov ds,ax
mov ax,[0]
mov bx,[2]
mov cx,[1];x含有ah,al两个字节,ax是字;DS=1000,AX=1123,BX=6622,CX=2211
add bx,[1];BX=8833(2211+6622)
add cx,[2];CX=8833(6622+2211)

屏幕截图_20250108_160511

DS与数据段的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
;累加数据段中的前3个单元中的数据,将123B0H~123BAH的内存单元定义为数据段
mov ax,123BH
mov ds,ax
mov al,0 ;为了把累加数据放入al,将其清零
add al,[0]
add al,[1]
add al,[2]

;累加数据段中的前3个字型数据,将123B0H~123BAH的内存单元定义为数据段
mov ax,123BH
mov ds,ax
mov ax,0
add ax,[0]
add ax,[2]
add ax,[4]

栈结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
;PUSH(入栈),POP(出栈),以字为单位对栈进行操作
push ax;将ax中的数据传入到栈中
pop ax;从栈顶取出数据ax

;将10000H~1000FH内存当作栈来使用
mov ax,0123H
(mov ss,ax)
(mov sp,0001H)
push ax
mov bx,2266H
push bx
mov cx,1122H
push cx
pop ax
pop bx
pop cx;ax=1122,bx=2266,cx=0123

;push ax
;(1)SP=SP-2;
;(2)将ax中的内容送到SS:SP指向的内存单元处,SS:SP此时指向新栈顶
;pop ax
;(1)将SS:SP指向的内存单元处的数据送入ax中
;(2)SP=SP+2,SS:SP指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶

;执行入栈(或出栈)时,栈顶超出栈空间,需要编程人员注意

image-20240324173005622

此图为debug运行情况

屏幕截图_20250108_160522

  • CPU如何知道一段内存空间被当作栈使用?

  • 执行push和pop的时候,如何知道哪个单元是栈顶单元?

    8086cpu中,有两个与栈相关的寄存器:栈段寄存器SS---存放栈顶的段地址

    栈顶指针寄存器SP---存放栈顶的偏移地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
;按要求设置段并执行代码
mov bx,1000H
mov ds,bx;数据段的设置
mov bx,1001H
mov ss,bx;栈段的地址
mov sp,10H;为寻找栈底地址:10010+10=10020
mov ax,[0]
mov bx,[2]
push ax
push bx
pop ax
pop bx
mov [0],ax
mov [2],bx

屏幕截图_20250108_160534

汇编程序中的伪指令

1
2
3
4
5
6
7
8
9
10
assume cs:codesg   ;assume:含义是假设某一段寄存器和程序中的某一个用segment...ends定义的段相关联--assume cs:codesg指CS寄存器与codesg关联,将定义的codesg当作程序的代码段使用(伪指令2)
codesg segment ;段定义:一个有意义的汇编程序中至少有一个段,用来存放代码。segment--段的开始,ends--段的结束(伪指令1)
mov ax,0123H
mov bx,0456H
add ax,bx
add ax,ax
mov ax,4c00h
int 21h
codesg ends
end ;汇编程序的结束标记(伪指令3)

[…]与(…)

  • […]—-汇编语法规定表示一个内存单元
  • (…)—-表示一个内存单元或寄存器中的内容
  • idata表示常量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
mov ax,2000H   ;ax=2000H
mov ds,ax ;ds=2000H
mov bx,1000H ;bx=1000H
mov ax,[bx] ;mov ax,[bx]---(ax)=((ds)*16+(bx))---现在操作的是21000H~21001H这个地址的内容,是字操作,此时ax=00BE
inc bx ;inc是加一的意思
inc bx ;bx=1002H
mov [bx],ax ;mov [bx],ax---((ds)*16+(bx))=(ax)---现在操作的是21002H~21003H这个地址的内容,是字操作,将2100H~2101H的内容放在21002H~21003H中,此时21002H~21003H中内容为00BE
inc bx
inc bx ;bx=1004H
mov [bx],ax ;现在操作的是21004H~21005H这个地址的内容,是字操作,此时21004H~21005H中内容为00BE
inc bx ;bx=1005H;
mov [bx],al ;是字节操作,将21004H的内容放在21005H上(al低八位寻址),此时21005H中存放的是BE
inc bx ;bx=1006H
mov [bx],al ;21006H中存放的是BE

屏幕截图_20250108_160546

loop指令

cpu执行loop指令时要进行的操作:

  • (cx)=(cx)-1;
  • 判断cx中的值,不为零则转至标号处执行程序,为零则向下执行,cx中要提前存放循环次数,因为(cx)影响着loop指令的执行结果
1
2
3
4
5
6
7
8
9
10
11
assume cs:code
code segment
mov ax,2
mov cx,11
s: add ax,ax ; ax=2 4 8 16 ...2^12
loop s, ;loop 标号,在执行这个指令时,cx不为零就会跳转到标号的行数(在这里也就是上一行) 执行循环的次数是根据一开始赋值给cx的值(11)来决定的,也就是循环11次

mov ax,4c00h
int 21h
code ends
end

用loop指令计算123×236,结果储存在ax中(用循环把乘法转换成加法)

1
2
3
4
5
6
7
8
9
10
11
assume CS:code
code segment
mov ax,0
mov cx,236
s: add ax,123
loop s

mov ax,4c00h
int 21h
code ends
end

计算ffff:0006字节单元中的数乘以3,结果存储在dx中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
;dx是16位,字节是8位,不能同时进行
assume cs:code
code segment
mov ax,0fffh ;在汇编源程序中,数据不能以字母开头,要在ffff前面加0
mov ds,ax
mov bx,6
mov al,[bx]
mov ah,0 ;(ax)=((ds)*16+(bx))

mov dx,0
mov cx,3
s: add dx,ax
loop s

mov ax,4c00h
int 21h
code ends;
end
  • 运算后的结果是否会超出dx所能承受的范围:ffff:0006单元中的数是一个字节型的数据,范围在0~255之间,则用它和3相乘结果不会大于65535,不会出现越界

段前缀:在[idata]前显式地写上段寄存器

1
2
3
mov ax,2000h
mov ds,ax
mov al,ds:[0] ;ds:就是段前缀为了防止 mov al,[0]在汇编程序变成--->mov al,00,正常情况应该是mov al,[0001]

计算ffff:0~ffff:b字节单元中的数据的和,结果存储在dx中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
assume cs:code
code segment
mov ax,0ffffh
mov ds,ax

mov dx,0

mov al,ds:[0]
mov ah,0
add dx,ax

mov al,ds:[1]
mov ah,0
add dx,ax

mov al,ds:[2]
mov ah,0
add dx,ax

...

mov al,ds:[9]
mov ah,0
add dx,ax

mov al,ds:[0ah]
mov ah,0
add dx,ax

mov al,ds:[0bh]
mov ah,0
add,ax,bx

mov ax,4c00h
int 21h
code ends
end
  • 是否可以将ffff:0~ffff:b中的数据直接累加带dx中?

不行:add dx ds:[addr] ;(dx)=(dx)+? 因为ds:[addr]中取出的是字节单元,而dx存储的是字单元

  • 是否可以将ffff:0~ffff:b中的数据直接累加带dl中?

不行:add dl,ds:[addr] ; (dl)=(dl)+? 因为dl只能存储8位在加法的过程中进位会丢失,只能取出8位数据,加到16为寄存器

1
2
3
mov al,ds:[addr]
mov ah,0
add dx,ax
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
assume cs:code
code segment
mov ax,0ffffh
mov ds,ax

mov bx,0
mov dx,0
mov cx,12

s: mov al,[bx]
mov ah,0
add dx,ax
inc bx ;inc是加一的操作
loop s

mov ax,4c00h
int 21h
code ends
end

将内存ffff:0到ffff:b中的数据拷贝到0:200到0:20b单元中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
assume cs:code
code segment
mov bx,0 ;bx存储偏移地址从0~b,200~20b
mov cx,12 ;循环次数12次

s: mov ax,0ffffh
mov ds,ax
mov dl,[bx] ;[bx]存放的是0fffh:0的内容

mov ax,0020h ;20:0表示的物理地址和0:200的段地址一样,所以把20放在ax中,把ax放在ds中
mov ds,ax
mov [bx],dl ;[bx]存放的是0:200的内容,因为虽然偏移地址没变,但是段地址ds从ffffh变成20了

inc bx
loop s

mov ax,4c00h
int 21h
code ends
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
;使用附加段寄存器es
assume cs:code
code segment
mov ax,0fffh
mov ds,ax
mov ax,0020h
mov es,ax

mov bx,0
mov cx,12

s: mov dl,ds:[bx]
mov es:[bx],dl ;shi'x
inc bx
loops

mov ax,4c00h
int 21h
code ends
end

在代码段中使用数据

编程计算以下8个数据的和,结果存在ax寄存器中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
assume cs:code
code segment
dw 0123H,0456H,0789H,0abcH,0defH,0fedH,0cbaH,0987H ;8个数据,每个数据2字节,一共16个字节 这里的dw指的是dw:define word定义字型数据,后面的每个逗号隔开的都是字型数据(dw:定义一个字,db:定义一个字节,dd:定义一个双字)

;mov bx,0
start: mov bx,0 ;从CS:0000处开始的依然是数据
mov ax,0
mov cx,8

s: add ax,cs:[bx] ;这个时候数据的段地址就是现在代码段的段地址(cs就是代码段地址)
add bx,2
loop s

mov ax,4c00h
int 21h
code ends
;end
end start
;以上代码有问题:真正的代码不应该从0000开始,而应从CS:0010开始,而数据从CS:0000开始.改进:在第一行代码前加入start:定义一个标号,指示代码开始的位置 ,在end后面也加一个start:可以通知编译程序的入口在什么地方

在代码段中使用栈

完成下面的程序,利用栈,将程序中定义的数据逆序存放

  • 程序运行时,定义的数据存放在CS:0~CS:F单元中,共8个字单元
  • 依次将这8个字单元中的数据入栈,然后再依次出栈到这8个字单元中,从而实现数据的逆序存放
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
;只适用于要处理的数据很少,用到的栈空间也小,加上没有多长的代码
assume cs:coding ;用栈将数据逆序存放
codesg segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbsh,0987h
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
start: mov ax,cs
mov ss,ax ;栈段SSji'cun开始的位置就是CS开始的位置,CS:0
mov sp,30h ;栈顶指针SP也设置好在30h
;入栈
mov bx,0
mov cx,8
push cs:[bx]
add bx,2
loop s
;出栈
mov bx,0
mov cx,8
s0: pop cs:[bx]
add bx,2
loop s0

code ends
end start

屏幕截图_20250108_160558

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
;将数据,代码,栈放入不同段
assume cs:code,ds:data,ss:stack
data segment
dw 0123H,0456H,0789H,0abcH,0defH,0fedH,0cbaH,0987H
data ends
stack segment
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
stack ends
code segment
start:
;初始化各段寄存器(数据段,栈段),其中cs不需要初始化因为程序自动给代码段起始地址
mov ax,stack
mov ss,ax
mov sp,20h
mov ax,data
mov ds,ax
;入栈
mov bx,0
mov cx,8
s:push[bx] ;这里没有指定push[bx]时,段地址是多少,默认ds
add bx,2
loop s
;出栈
mov bx,0
mov cx,8
s0:pop [bx]
add bx,2
loop s0

mov ax,4c00h
int 21h
code ends
end start

屏幕截图_20250108_160609

处理字符问题

在汇编程序中,用’……’的方式指明数据是以字符的形式给出的,编译器将他们转化为相对应的ASCII码

大小写转换问题:对第一个字符串小写转大写,大写不变;对于第二个字符串大写转小写,小写不变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
;技巧如下,例如让小写b转成大写B
b 62H 0110 0010B
B 42H 0100 0010B
;此运算可以让小写变成大写,大写不变
0100 0010 (b)
and 1101 1111 ;逻辑与,存在任何一个数据为0,结果返回0
--------------------
= 0100 0010 (B)
;逻辑指令为: and dest,src

;技巧如下,例如让大写I转成小写i
I 49H 0100 1001B
i 69H 0110 1001B
;此运算可以让大写变成小写,小写不变
0100 1010 (b)
or 0010 0000 ;逻辑或,存在任何一个数据为0,结果返回1
--------------------
= 0110 1001 (i)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
assume cs:codesg ds:datasg
datasg segment
db 'BaSiC'
db 'iNfOrMaTiOn'
datasg ends

codeseg segment
start:
mov ax,datasg
mov ds,ax
;第一个字符串:小写字母转大写
mov bx,0
mov cx,5
s:mov al,[bx] ;这里面取出的是B
and al,11011111b
mov [bx],al ;把b覆盖B
inc bx ;减一
loop s

;第二个字符串:大写转小写
mov bx,5 ;起始位置不同
mov cx,11
s0:mov al,[bx]
or al,00100000b
mov [bx],al
inc bx
loop s0


mov ax,4c00h
int 21h
codesg ends
end st