写好汇编代码后,借助 masm.exe 程序编译,产生 .obj 文件,再使用 link.exe 连接生成可执行文件。
我们写的指令包括伪指令,没有对应的机器码的指令,由编译器处理;和汇编指令,编译为机器码。
一个汇编程序由多个段(至少一个)组成,我们知道段是我们自己定义的逻辑上以16B为单位的划分。
形如:
XXX segment #段开始
XXX ends #段结束
end #汇编程序的结束标记
assume:假设某个寄存器和某个段相关联。
编写程序思路:
abc segment
abc ends
end
mov ax,2
add ax,ax
add ax,ax
add ax,ax
assume cs:abc
DOS 是单任务操作系统。如果当前是P1程序在运行,想切换到P2,需要P1把P2加载入内存,把 CPU 控制权交给P2,然后P2才能运行,P1暂停运行直到P2结束运行控制权交还给P1。这个过程叫程序返回。
mov ax,4c00H
int 21H
这两条指令就是程序返回。这条指令不是编译器执行,而是CPU执行。
注意,前面的指令都要用 t 逐条执行,表示 step into。int 21 指令要用 p 命令执行,表示 step over。
因此第一个程序完整内容为:
assume cs:abc
abc segment
mov ax,2
add ax,ax
add ax,ax
add ax,ax
mov ax,4c00H
int 21H
abc ends
end
命名为.asm文件。
asm 文件用 masm.exe 编译,obj 文件用 link.exe 链接,或者直接用 ml 编译和链接。
编译是把文件编译为机器码文件,链接是把其和相关文件、相关库链接起来,方能生成可执行文件。
灵魂三问:
输入文件名直接执行,如我们链接完的程序叫 1.exe,直接输入 1.exe 运行。输入 debug 1.exe
进入调试,可以逐行运行。
开始调试时可以发现,其他通用寄存器都初始化为0000,只有 cx 有值。这是因为 cx 初始化值为程序的长度。
然后可以发现CS=DS+0010H,段地址相差0010H,则物理地址相差 100H 即256,也就是 CPU 自动分配了256个字节给DS。这256字节里存放的东西叫 PSP,是 DOS 用于和程序通信的,了解即可。DS+256 才是存放程序的 CS.
因此 PSP 的段地址 SA 可从 DS 中得到。PSP 物理地址范围是 SA * 16+0~(SA+0010H) * 16+0-1.
(SA+16) * 16+0 开始是 CS 的范围了。
正常情况下 mov ax, [0]
表示把 ds:0 的数据传入 ax。但是我们的编译器 (ml) 会把其翻译为 mov ax, 0
进而产生错误。
解决办法:在 ml 中我们先把数据传入 bx,然后 mov ax, [bx]
。或者 mov ax ds:[0]
。或者直接在 debug 中写程序,用 a ,这样写出的程序无该问题。
assume cs:codesg
codesg segment
proj: mov ax, 2000H
mov ds, ax
mov bx, 1000H
mov ax, [bx]
inc bx
inc bx
mov [bx], ax
inc bx
inc bx
mov [bx], ax
inc bx
mov [bx], al
inc bx
mov [bx], al
mov ax, 4C00H
int 21H
codesg ends
end proj
isc:字+1。
上面的函数做的操作:把 21000H 处的值赋给 ax,再赋给 21002H,21004H……
比如我最开始 21000H 值是 FF,21001H 值是 46,赋值结果为:
就是复制了几遍值。
汇编中可以通过一个寄存器值实现 loop 循环,值=0时停止,!=0 一直继续。
例:计算 2^12.
assume cs:code
code segment
mov ax, 2
mov cx, 11
s: add ax, ax
loop s
mov ax, 4c00h
int 21h
code ends
end
cx 是 loop 用的寄存器,自动每轮循环–,=0时退出循环。因此赋值多少就循环几次。
使用 loop 对 s 标号名里的内容进行循环。
例:123*236.
当然可以循环236次,但是更优的算法是ax不断加倍,算到 ax 的128位之后再循环剩余的次数。
注意16位寄存器和8位数据之间传递的关系,直接传可能报错,要传给 h or l。
还有一个要注意的地方:汇编中数据不能以字母开头,比如 ffffh 直接写不对,开头要加个0:0ffffh。
当然这样循环次数太多,写代码的时候我们可以用 loop 省略,debug 的时候还是要一行一行执行,有没有快捷方法?
比如上面,我们可以看到 0014 偏移地址结束循环。
g 0014
或 p
都可以执行完当前循环。
例:ffff0:ffff11 12个地址,把值全部累加到 ds 中。
我们知道 ds 是16位的寄存器,这12个地址里是12个字节数据,直接传入会出错。
那可不可以全部传入 ds 的低位 dl?不可以,会溢出。
那怎么办?可以先传入每个值给一个16位的中间寄存器, 那个寄存器加到 dx。
mov al, ds:[0]
mov ah, 0
add ds, ax
mov al, ds:[1]
mov ah, 0
add ds, ax
mov al, ds:[2]
mov ah, 0
add ds, ax
我们随便去赋值,访问操作系统空间是有危险的。直接操作硬件就是会有这种风险。
内存空间中 0:200h 到 0:2ffh 是安全空间。以防万一可以先 d 一下看看里面的内容。
例:把 ffff:0 11 的值复制到 0020:0 11 处。
assume cs:code
code segment
mov bx,0
mov cx,12
s: mov ax,0ffffh
mov ds,ax
mov dl,[bx]
mov ax,0020h
mov ds,ax
mov [bx],dl
inc bx
loop s
mov ax, 4c00h
int 21h
code ends
end
不过这样换=两次 ds 比较麻烦。我们可以使用 es 附加寄存器存储。
assume cs:code
code segment
mov bx,0
mov cx,12
mov ax,0ffffh
mov ds,ax
mov ax,0020h
mov es,ax
s: mov dl,[bx]
mov es:[bx],dl
inc bx
loop s
mov ax, 4c00h
int 21h
code ends
end