程序

源程序

☀️ 由计算机执行、处理的指令和数据称为程序

  1. assume cs:codesg
  2. codesg segment
  3. ;以下是程序
  4. mov ax,0123H
  5. mov bx,0456H
  6. add ax,bx
  7. add ax,ax
  8. mov ax,4c00H
  9. int 21H
  10. codesg ends
  11. end

数据的后面写 H 表示十六进制数据,写 B 表示二进制,写 D 表示十进制,写 O 表示八进制,不写默认十进制 而debug方式写的时候不能写后面的代号,只能写十六进制

伪指令

定义一个段

  1. 段名 segment
  2. 段名 ends

程序结束

  1. ends

📢 ends 与 assume 不是配套的,assume 只是声明段

关联段寄存器

  1. assume cs:codesg ; codesg 最终会被编译成“段地址”

伪指令是由编译器执行的,不是由计算机执行的,也就是说计算机不会把 cs 指向 codesg 的段地址

程序返回

单任务操作系统上,正在运行的 汇编程序 - 图1 程序将 汇编程序 - 图2 载入内存,汇编程序 - 图3 将控制权交给 汇编程序 - 图4汇编程序 - 图5 才能运行,运行结束后,汇编程序 - 图6 又将控制权交给 汇编程序 - 图7,称为程序返回

  1. mov ax,4c00H
  2. int 21H

在程序的末尾写上上面的两条指令,即可实现程序返回
在 DOS 中,汇编程序 - 图8 可以是 command.com即命令解释器
image.png
如果不写程序返回,则会变成
image.png

语法错误

  1. sume cs:codesg ; 编译器不知道 sume 是什么
  2. codesg segment
  3. mov ax,1000 ; 代码段没有 ends
  4. end

逻辑错误

  1. assume cs:codesg
  2. codesg segment
  3. mov ax,1000
  4. ; 不存在程序返回, 但能通过编译
  5. codesg ends
  6. end

编译

image.png
如果文件名不是.asm结尾的需要输入全名,如 1.txt,如果文件和 masm.exe不在同一个目录,还要输入路径
如果默认忽略各种中间文件,且目标文件名不变,可以直接编译输入下面的代码进行编译,;不能省略

  1. masm 1;

image.png

连接

image.png

  1. link 1;

跟踪

  1. debug 1.exe

注意exe不能省略

程序载入过程

会自动寻找足够大的空闲空间

image.png
image.png
可以输入t进行程序运行过程的监控
image.png

[BX] 与 loop

描述符 ()

  • (寄存器)表示寄存器里的内容,如(ax)=1000H
  • (物理地址)表示物理地址里的内容,如(2000H)=0010H
  • (段寄存器)表示段寄存器的内容,如((ds)×16+(ax))=1000H

    约定符 idata

    idata 表示常量

  • mov ax,[idata]表示 mov ax,[1]``mov ax,[2]

  • mov ax,idata表示 mov ax,1``mov ax,2

    [bx]

    mov ax,[bx]表示将偏移地址为(bx)的字型数据送入 ax,即 (ax)=((ds)×16+(bx))
    如果是 mov al,[bx][bx]代表字节型数据(即 1 个字节)

    注意,[...]只能放入bx``si``di``bp寄存器,[ax]``[cx]``[dx]``[ds]是非法的

  1. mov ax,0ffffh ; 汇编不允许字母开头的数据
  2. mov ds,ax
  3. mov bx,6
  4. ;将字节型(8位)数据 ffff:0 送入 16 位的 ax
  5. ;如果直接 mov ax,[bx] 则会送入字型数据(16位), ah 不一定是 0
  6. mov al,[bx]
  7. mov ah,0

自增 inc

  1. inc bx ; 表示 bx=bx+1

不能操作段寄存器 image.png

loop

  1. assume cs:code
  2. code segment
  3. mov ax,2
  4. mov cx,11
  5. s: add ax,ax
  6. loop s
  7. mov ax,4c00h
  8. int 21h
  9. code ends
  10. end

执行到 汇编程序 - 图18 会先让 汇编程序 - 图19 然后再判断是否 汇编程序 - 图20,若成立则退出(汇编程序 - 图21 是通用寄存器)
image.png

指定跟踪位置

  1. assume cs:code
  2. code segment
  3. mov ax,0ffffh
  4. mov ds,ax
  5. mov bx,6
  6. mov al,[bx]
  7. mov ah,0
  8. mov cx,123
  9. s: add dx,ax ;这里需要循环
  10. loop s
  11. mov ax,4c00H ; 这里循环结束
  12. int 21H
  13. code ends
  14. end
  1. g IP

汇编程序 - 图23 指需要循环的指令的 汇编程序 - 图24,即指令 add dx,ax
image.png

跳过循环

  1. p

image.png

  1. g IP

汇编程序 - 图27 指循环结束后指令的 汇编程序 - 图28,即 mov ax,4c00H

循环嵌套

有问题的程序

  1. assume cs:code,ds:data
  2. data segment
  3. db 'ibm '
  4. db 'dec '
  5. db 'dos '
  6. db 'vax '
  7. data ends
  8. code segment
  9. start: mov ax,data
  10. mov ds,ax
  11. mov bx,0
  12. mov cx,4 ; 四个单词, 外循环需要循环 4
  13. s0: mov si,0
  14. mov cx,3 ; 三个字母, 内循环需要循环 3
  15. s1: mov al,[bx+si]
  16. and al,11011111B
  17. mov [bx+si],al
  18. inc al
  19. loop s1
  20. add bx,16
  21. loop s0
  22. code ends
  23. end start

当一次内循环结束后遇到loop s0循环次数cx就会变成FFFF这样外循环的循环次数不断被覆盖,无法结束循环
image.png

解决方案一

使用寄存器dx临时保存外循环的cx

  1. assume cs:code,ds:data
  2. data segment
  3. db 'ibm '
  4. db 'dec '
  5. db 'dos '
  6. db 'vax '
  7. data ends
  8. code segment
  9. start: mov ax,data
  10. mov ds,ax
  11. mov bx,0
  12. mov cx,4
  13. s0: mov dx,cx ; 保存外循环的 cx
  14. mov si,0
  15. mov cx,3
  16. s1: mov al,[bx+si]
  17. and al,11011111B
  18. mov [bx+si],al
  19. inc si
  20. loop s1
  21. add bx,16
  22. mov cx,dx ; 这里恢复外循环的 cx
  23. loop s0
  24. mov ax,4c00H
  25. int 21H
  26. code ends
  27. end start

解决方法二

使用栈来保存多个循环的cx

  1. assume cs:code,ds:data
  2. data segment
  3. db 'ibm '
  4. db 'dec '
  5. db 'dos '
  6. db 'vax '
  7. data ends
  8. stack segment
  9. dw 0 ; 这里写一个 0 就可以创建 16 个字节的栈
  10. stack ends
  11. code segment
  12. start: mov ax, stack
  13. mov ss,ax
  14. mov sp,16
  15. mov ax,data
  16. mov ds,ax
  17. mov bx,0
  18. mov cx,4
  19. s0: push cx ; cx 入栈
  20. mov si,0
  21. mov cx,3
  22. s1: mov al,[bx+si]
  23. and al,11011111B
  24. mov [bx+si],al
  25. inc si
  26. loop s1
  27. add bx,16
  28. pop cx ; cx 出栈
  29. loop s0
  30. mov ax,4c00H
  31. int 21H
  32. code ends
  33. end start

汇编代码的不同处理

debug 里[0]就代表ds:0里的数据,而源程序中代表 0
image.png

解决方法

  1. 使用 bx 中转

    1. mov bx,0
    2. mov ax[bx]
  2. 显示声明内存单位,又称为段前缀

    1. mov ax,ds:[0]

    8 位二进制数累加

  3. 8 位二进制范围 0~255,累加可能会导致进位丢失

  4. 不能直接把 8 位二进制放到 ax 中,因为位数不匹配

    解决方法

    1. s:mov al,[bx]
    2. mov ah,0
    3. add dx,ax ; 利用 dx 进行“中转”, dx 16 位的, 累加用 dx, ax 通过 al,ah 拼成 16
    4. inc bx
    5. loop s

    多段程序

    代码段中使用数据 dw

    使用 dw让系统自动帮我们分配数据的内存 image.png 不够 16 个字节的会占满 16 个字节,超了会到下一行,即 汇编程序 - 图32 个字节数据,实际占用 汇编程序 - 图33 字节 这里要用cs:[bx]不能直接写[bx],因为数据是存在指令地址空间上的,而[bx]代表 ds:[bx]是数据地址空间

  1. assume cs:code
  2. code segment
  3. dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
  4. mov bx,0
  5. mov ax,0
  6. mov cx,8 ;循环 8
  7. s: add ax,cs:[bx] ; cs[0]指向 dw 所指代的数据
  8. add bx,2
  9. loop s
  10. mov ax,4c00H
  11. int 21H
  12. code ends
  13. end

直接使用 u查看是一堆看不懂的指令,即由 dw定义的数据被编译器看成了指令
image.png
需要跳过前面的dw才能看到正确的指令
image.png
为了不让编译器误执行前面不对的代码,需要指定代码执行的入口,编译器会根据end start自动设置程序的 汇编程序 - 图36

start只是个单词,写什么都可以,只要end xxxxxx:对应即可

  1. assume cs:code
  2. code segment
  3. dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
  4. start: mov bx,0
  5. mov ax,0
  6. mov cx,8 ;循环 8
  7. s: add ax,cs:[bx] ; cs[0]指向 dw 所指代的数据
  8. add bx,2
  9. loop s
  10. mov ax,4c00H
  11. int 21H
  12. code ends
  13. end start

代码段中使用栈

使用 dw开辟栈空间

  1. assume cs:code
  2. code segment
  3. dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
  4. dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ; 申请 16 个字空间
  5. start: mov ax,cs
  6. mov ss,ax
  7. mov sp,30h ; 栈顶从 8×2 + 16×2 = 48 = 30h 开始
  8. mov bx,0
  9. mov cx,8
  10. s: push cs:[bx]
  11. add bx,2
  12. loop s
  13. mov bx,0
  14. mov cx,8
  15. s0: pop cs:[bx]
  16. add bx,2
  17. loop s0
  18. mov ax,4c00H
  19. int 21H
  20. code ends
  21. end start

入栈本来只需要 8 个字即可,但是由于不明确的原因,必须申请 16 个
image.png

数据、代码放在不同段

  1. assume cs:code,ds:data,ss:stack
  2. data segment
  3. dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
  4. data ends
  5. stack segment
  6. dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  7. stack ends
  8. code segment
  9. start: mov ax,stack
  10. mov ss,ax ; 栈段地址指向 stack 的段地址
  11. mov sp,20h
  12. mov ax,data ; 数据指向 data 段地址
  13. mov ds,ax ; data 会被解析成一个数值, 因此 mov ds,data 不合法
  14. mov bx,0
  15. mov cx,8
  16. s: push [bx] ; 由于数据不在指令地址空间了, 因此不需要写 cs:[bx]
  17. add bx,2
  18. loop s
  19. mov bx,0
  20. mov cx,8
  21. s0: pop [bx]
  22. add bx,2
  23. loop s0
  24. mov ax,4c00H
  25. int 21H
  26. code ends
  27. end start

如果不指定end start,则会从第一个段开始执行,即 data 段
image.png
image.png
因此如果不想写end start则把 code 段写在第一个即可

更灵活寻址方法

and 按位与

  1. mov al,01100011B
  2. and al,00111011B
  3. ; 结果 al=00100011B

or 按位或

  1. mov al,01100011B
  2. or al,00111011B
  3. ; 结果 al=01111011B

ASCII码

伪指令 db

db不会像dw一样默认占 16 个字节,db定义字节型数据

  1. assume cs:code,ds:data
  2. data segment
  3. db 'ubIX'
  4. db 'foRK'
  5. data ends
  6. code segment
  7. start: mov al,'a'
  8. mov bl,'b'
  9. mov ax,4c00h
  10. int 21h
  11. code ends
  12. end start

image.png

转成大写

  1. assume cs:code,ds:data
  2. data segment
  3. db 'ubIX'
  4. data ends
  5. code segment
  6. start: mov ax,data
  7. mov ds,ax
  8. mov bx,0
  9. mov cx,4 ; 单词数 4 就循环 4
  10. s:mov al,[bx]
  11. and al,11011111B ; 小写字母的 ASCII 5 个数变成 0, 则变成大写
  12. mov [bx],al
  13. inc bx
  14. loop s
  15. mov ax,4c00h
  16. int 21h
  17. code ends
  18. end start

image.png

转成小写

  1. assume cs:code,ds:data
  2. data segment
  3. db 'ubIX'
  4. data ends
  5. code segment
  6. start: mov ax,data
  7. mov ds,ax
  8. mov bx,0
  9. mov cx,4 ; 单词数 4 就循环 4
  10. s:mov al,[bx]
  11. or al,00100000B ; 大写字母的 ASCII 5 个数变成 1, 则变成小写
  12. mov [bx],al
  13. inc bx
  14. loop s
  15. mov ax,4c00h
  16. int 21h
  17. code ends
  18. end start

[bx+idata]

偏移地址可以使用加法

  1. mov ax,[200+bx]
  1. mov ax,[bx+200]
  1. mov ax,200[bx]
  1. mov ax,[bx].200

以上代码都表示(ax)=((ds)×16+(bx)+200)

C语言对比版

  1. ; 汇编语言
  2. assume cs:code,ds:data
  3. data segment
  4. db 'BaSic'
  5. db 'MinIX'
  6. data ends
  7. code segment
  8. start: mov ax,data
  9. mov ds,ax
  10. mov bx,0
  11. mov cx,5
  12. s:mov al,0[bx]
  13. and al,11011111B ; 转大写
  14. mov 0[bx],al
  15. mov al,5[bx]
  16. or al,00100000B ; 转小写
  17. mov 5[bx],al
  18. inc bx
  19. loop s
  20. mov ax,4c00h
  21. int 21h
  22. code ends
  23. end start
  1. // c 语言
  2. char a[5]="BaSic";
  3. char b[5]="MinIx";
  4. int main(){
  5. int i=0;
  6. do{
  7. a[i]=a[i]&0xDF;
  8. b[i]=b[i]|0x20;
  9. i++;
  10. } while(i<5);
  11. }

[SI] 和 [DI]

汇编程序 - 图42 寄存器功能类似,不过不能分成两个 8 位寄存器来使用 [si+di]是非法的

复制字符串

  1. assume cs:code,ds:data
  2. data segment
  3. db 'welcome to masm!'
  4. db '..........'
  5. data ends
  6. code segment
  7. start: mov ax,data
  8. mov ds,ax
  9. mov si,0
  10. mov di,16
  11. mov cx,8
  12. s:mov ax,[si]
  13. mov [di],ax
  14. add si,2
  15. add di,2
  16. loop s
  17. mov ax,4c00h
  18. int 21h
  19. code ends
  20. end start

[bx+si] 和 [bx+di]

[bx+idata]功能类似,不过是将idata换成寄存器

  1. mov ax,[bx+si]
  1. mov ax,[bx][si]

以上代码都表示(ax)=((ds)×16+(bx)+(si))

[bx+si/di+idata]

  1. mov ax,[bx+si+200]
  1. mov ax,[bx][si].200
  1. mov ax,200[bx][si]
  1. mov ax,[bx].200[si]

[BP]

[bp]的默认段地址是ss,即 ss:[bp]

[bx]的默认段地址是ds,即ds:[bx] [bx+bp]是非法的,但是bp可以和si``di合用 mov [bp],bx可行,但mov [sp],bx不可行,因此要修改栈元素需要使用bp中转

数据处理

除法 div

用法 div 除数

  • 如果除数是 8 位的,则被除数在ax里,为 16 位,商保存在al里,余数保存在ah
  • 如果除数是 16 位的,则被除数高位在dx里,低位在ax里,为 32 位,商保存在ax里,余数保存在dx

    1. div byte ptr [bx+si+8]

    上面的代码表示 (al)=(ax)/((ds)×16+(bx)+(si)+8))的商``(ah)=(ax)/((ds)×16+(bx)+(si)+8))的余数

    计算 100001/100

  • 被除数 100001 不能放到 16 位里,因此只能放在 32 位里

  • 除数 100 可以是 8 位的,但是由于被除数只能是 32 位,因此除数也只能是 16 位的
  • 100001 的 16 进制为 186A1 ``` assume cs:code

code segment mov dx,1 mov ax,86A1H mov bx,100 div bx

  1. mov ax,4c00H
  2. int 21H

code ends

end

  1. <a name="ukRIg"></a>
  2. ## 乘法 mul
  3. 用法`mul 内存单元``mul 寄存器`
  4. > 两个乘数位数必须相同
  5. - 都是 8 位,一个数放在`al`中,另一个放在内存单元或寄存器中,结果保存在`ax`中
  6. - 都是 16 位,一个数放在`ax`中,另一个放在内存单元或寄存器中,结果高位放在`dx`中,低位放在`ax`中

mul byte ptr ds:[0] ; 8 位

  1. ```
  2. mul word ptr ds:[0] ; 16 位

image.png

计算 100×10

100 和 10 都小于 8 位

  1. mov al,10
  2. mov bl,100
  3. mul bl

注意如果写成mul bx则会被当作 16 位,进而计算ax·bx所以不会报错

伪指令 dd

dwdb类似,dd定义双字型数据,也默认占满 16 个字节

  1. dd 1

上面代码的结果为01 00 00 00

数据重复 dup

dup重复数不能为 0

  1. dw 3 dup (1,2,3) ; 重复 3

上面的代码等价于 dw 1,2,3,1,2,3,1,2,3