1、配置环境
资料:http://t.csdnimg.cn/mnbxA
http://t.csdnimg.cn/9o6lx
补充:stc ah,10H:给ah赋10(16进制),并把进位标志cf设置为1
下载:GitHub
使用参考:vscode进行汇编编程,你想要的都有_哔哩哔哩_bilibili
建议在浏览器扩展商店搜索GitHub加速插件并安装
点击下面绿色链接
复制这些网址到设置页面
注:我在按右上角运行符号使用时发现,更改代码后第一次运行无效,第二次运行才是更改后的结果!(也可以ctrl+s保存后运行)
或者按ctrl+b选择dosbox运行
汇编不区分大小写
点开dosbox后,通过mount指令把工作区挂靠到指定的文件夹下,然后c:进入工作区,dir查看工作区文件
输入masm、ASM文件名开始编译,生成obj文件,如果masm报错就把MASM.EXE复制一个到工作区
连接
输入debug learn.exe执行(说非法依赖就粘贴一个debug.exe过来)
2、debug
EAX为32位寄存器
AX为EAX的低16位的 “ 子寄存器 ”
——这么叫是为了方便理解
巧记:truead,t:execute(执行),r:read,u:Translate,a:add,d:du(读)
在dosbox打dir,查看当前环境下有哪些文件
再打debug进入debug,此时可运行debug指令,输入q可以退出debug
r:查看各寄存器的值
r ax:修改AX的值
d 1000:0 :查看段地址1000处的内存
d:使地址不断偏移
d 1000:0000 f :限制输出15个 ,后面的数字可任意更改
d 1000:9:从9位置开始输出(即偏移地址为9)
e 1000:0010 12 23 34:修改0010上数据
e 1000:0010回车出现第一个,写上值,空格出现第二个,写值,然后回车,完成修改:
选择cs寄存器写程序: a 0740:0100,回车,add ax,bx(把ax+bx的结果放进bx),回车,mov cx,ax(把ax拷一份放到cx)
t:执行一步程序,0740:0104即CS:IP
g 0012:执行到ip=0012
p:遇到loop循环时,直接执行完这个循环
d CS:原IP(这里是0740:0100): 查看机器码(机器码在内存中样子)
u CS:原IP(这里是0740:0100):把机器码翻译成汇编代码,这里显示的是之前写入的指令
3、MOV、ADD、SUB(只能操作ax,bx,cx,dx寄存器)
mov ax,8:给ax赋值8 (把8移入ax)
mov bx,ax:给bx赋ax的值(自己赋值给自己也可以)
mov ch,10:给cx的高位(cx由两个十六进制数拼成)赋值10,得到1000
mov dl,12:给dx的低位赋值12(t执行完后直接a回车可以再写汇编代码)
mov cl,al:给cx的低位赋值ax的低位
add ax,15:给ax加上15
add cx,bx:给cx加上ax的值(自己加自己也能用,如果加越界了会把超出的高位忽略)
add bh,f0:给bx高位加上f0(给低位加超限了也不会向高位进位)
add dh,bh:给dx的高位加上bx的高位
sub ax,d:给ax减去d(如果减越界了,会把ax补成1001d再减d,如果低位减越界了,也不会向高位借位)
sub ax,bx:给ax减去bx的值,结果存在ax
4、mul、div、and、or 、xor、neg、not
mul bl:将al与bl相乘,结果存在ax中(ax中存的下,不会溢出)
mul bx:将ax与bx相乘,结果高位存在dx,低位存在ax(同理,不会溢出)
div bl:拿ax除bl,商存在al中,余数存ah中
div bx:拿dxax除bx,ax存商,dx存余数
and al,3b:取63和3b的逻辑与(同为1才1),存在al中
想保留16进制最后一位,就与上000f,前三位与0与全0,最后一位全和1与不变
and al,3b:取63和3b的逻辑或(有1就为1),存在al中
xor al,dh:按位二进制异或(先写出二进制,再每位异或)
NEG:把操作数按位取反加一 (可以用来求一个数的相反数)
NOT:把操作数按位取反
遇到0f0f0H取f0f0
5、shl、shr
切记al是8位,移位的时候不要分成两个四位分别移!!!
shl ax,1:普通左移,移一位,在右边补0
shr ax,1:普通右移,在最左边补0
sal:将数视为有符号数进行左移,不挪走符号位(首位),后面补0
sar:将数视为有符号数右移,不挪走符号位,负数补1,正数补0(补符号位的数)
rol ax,1:循环左移:把最左边的数移到最右边
ror ax,1:循环右移
rcl:把原来CF的值移入最低位,在把最高位的值移出给CF
RCR ah,1:这时候CF很重要,先把原来CF的值移入最高位,在把最低位的值移出给CF
RCL:带进位循环左移,先将CF的值给最低位,然后最高位的值在进CF
inc ax:使ax加1(越界按加法抹头)
dec ax:使ax减1 (越界按减法借位)
nop:空指令,执行后什么都不变,用来占位,刚好占一个字节
xchg ax,bx:交换ax,bx的值
neg ax:给ax取反(取-ax的补码)
int 0:中断指令,不同错误会产生不同值,后会跳到0000:0000的对应值的位置
算10ax=8ax+2ax:
6、段地址、偏移地址
2000:1f60和2100:0f60是同一个物理地址
ds寄存器存的是段地址,给它赋值
mov bl,[61]:把21f0:0061处的值赋值给bl
add bl,[60]:21f0:0060处的值加给bl
mov bx,[62]:0062处的值赋值给bl,0063处的值赋值给bh(mov ax,[bx+1]、mov [bx+1],ax也可以,只有bx、si、di、bp(默认使用ss:bp为地址,要改ss)、bx(bp也可以)+si+1、bx+di+1可以这样当偏移地址用)
cs、ip是代码段寄存器,cs存的是段地址,ip存的是偏移地址,es也可以存段地址
ds是数据段寄存器
在某个地址写好代码后,要给cs、ip赋值再输入t执行
输入t执行后,ip会自动加一定数,移到下一句代码处
jmp 1000:3:跳转到1000:0003处,cs改成1000,ip改成0003
jmp bx:跳转到ip=bx处
汇编代码和数据不加区分,可以到处赋值
下面的写法都是表示把各元素相加
7、栈
ss:sp指向栈顶元素,压栈sp-2,弹栈sp+2,栈弹完了继续弹,sp正常+2,因为读到0的数据,会赋值0,要自己小心
mov ss,ax
mov sp,2设置栈顶
push ax:把ax压入栈
push [0]:把ds:0000处数据压栈
pop ax:把栈顶元素弹出,并赋值给ax
pop [1]:把栈顶元素弹出,并赋值给ds:0001
8、标志位寄存器
zf:如果执行指令后结果为0,zf=1(显示ZR),否则zf=0 (显示NZ)
cmp ax,bx:运算为ax-bx,但是不保存结果,通过看zf可以判断两者是否相等(zf=1,相等)
ax<bx:cf=1 ,ax>bx:cf=0,zf=0 ,ax>=bx:cf=0 , ax<=bx:cf=1或zf=1
下面的跳转指令可以跳到正在执行的代码中任意一条代码的地址
pf:执行指令后如果1的个数为偶数,pf=1(显示PE),否则pf=0 (显示PO)
sf:执行指令后为负数,sf=1(显示NG),否则sf=0 (显示PL)
cf:执行指令后如果需要进位(比如shl把1移没了)或者借位(无符号数),cf=1(显示CY),否则cf=0(显示NC)
adc ax,1:带进位加法指令,结果是ax+1+cf
sbb ax,bx:带借位减法,结果是ax-bx-cf
of:执行指令后,以无符号数首位为符号位的有符号数如果溢出(比如符号是1,左移移没了),of=1(显示OV),否则of=0(显示NV)
TEST指令:一种用于逻辑运算的指令。它可以对两个操作数进行逻辑AND运算,并根据运算结果设置标志位。
stc:把cf的值置1 (setcf),同理有clc置0
std:把df置1
cld:把df置0 (cleardf,这里的0可能表示二进制正号,表示正向移动)
df标志位用于数据串操作,即通过MOVS/MOVSB(db写的字节数组专用)/MOVSW(dw写的字数组专用)把段地址在DS中,数组地址在SI中的串中的字符拷贝到段地址在ES中,数组地址在DI中的串(一般前面的两个数组都在数据段data,可以MOV AX,data,再mov ds,ax、mov es,ax),其中df=0在执行MOVSB等指令后使SI、DI加一(要拷贝整个数组,则SI、DI从首位开始,一个个加到末位),df=1使SI、DI减一(要拷贝整个数组,则SI、DI从末位(可以数字符串长度后再减一得到,再加到SI上)开始,一个个减到首位)
如果不想用loop循环控制MOVSB执行,可以在cx中写入需循环次数,再rep movsb循环执行movsb相应次数(拷贝整个数组一般mov cx,sizeof 需拷贝数组名)
与MOVSB、MOVSW同理的有以下指令:
lodsb指令,将si指向的地址处的数据取出来赋给AL寄存器,si=si+1;
lodsw指令则取得是一个字:将si指向的地址处的数据取出来赋给AX寄存器,si=si+2
lodsd指令,取得是双字节,即mov eax,[esi],esi=esi+4;
stosb指令,将AL寄存器的值取出来赋给di所指向的地址处。mov [di],AL;di=di+1;
stosw指令去的是一个字。
stosd指令,取得是双字节,mov [di],ax;di=di+4;
jnz、jne这两条指令完全没区别! 它们对应于完全相同的机器代码,含义为ZF标志位不等于0 则执行
jcxz(jump if cx is zero):是短转移,当cx=0就跳转
jge ax,1:ax大于或者等于1转移
JA ;大于跳转
JAE ;无符号大于等于则跳转
JB ;无符号小于则跳转
JNB ;无符号不小于则跳转
JBE ;无符号小于等于则跳转
JG ;有符号数大于则跳转
JL ;有符号数小于则跳转
JLE ;有符号数小等于则跳转
JC ;CF进位则跳转(无符号为进位)
JO ;OF溢出则跳转(有符号为溢出)
笔试答案可以写二进制如00001000B,如果题目用到了字符也可以写字符(65是A,97是a)
9、源文件写代码
文件中可以mov ax,0010B存二进制,不写默认十进制,写H16进制(不能字母打头),写O8进制
align:用来对齐
loop指向函数(即图中的s)开始处的ip(那句mov的IP),以cx为计算器,cx!=0时,每次执行到loop都会跳到对应的ip,实现循环,每执行(遇到一次loop)一次loop指令cx-1 (减完为0就退出循环,cx赋值0会死循环!)
小心循环中两个移位语句产生的cf互相影响(不是rcl、rcr不受影响)
汇编函数本质更接近c++的goto
call(近转移,只把call下一句的IP压入栈)、call far ptr(远转移,同时压栈cs和ip,当call和函数距离太远,cs不同时用)指令调用函数,ret(近转移,只修改IP,即把栈里的数据弹给ip)、retf(远转移,同时修改cs和ip,当call和函数距离太远,cs不同时用)即renturn,标志函数结束,这种写法函数和主程序的代码不会分开存,还是按行数顺序存,这里删了结束语句int 12H会死循环,因为跳过call后继续下面的add,然后又ret回来
如果函数没写ret,会继续执行函数下面的代码直到结束
如果运行到ret时没有call与之对应,会返回程序的第一句,造成死循环
标准文件:
if、if else翻译成汇编
10、代码段、数据段、栈段
可以mov ax,代码段名(栈段名、数据段名)获取段的段地址
下面这样写数据,会把数据写到代码段,因为汇编不区分代码和数据,会产生混淆(数据和mov指令存在一起,导致不能识别mov)
dw等写法一写好就存入数据,不用t运行,按顺序放,不看高低位,dw是16位,如果值是8位会补0再存
存字符串要用db(占一个字节),不能用dw(占一个字)
若遇到编译问题,注意dw等中十六进制尾部加H,不能字母打头,头部加0
用start标记代码开始执行位置,避免混淆(数据仍然存在代码段,和代码存一起且不能正常翻译,但是能正常执行)
分多个段存数据、栈、代码
db 10 dup(0):存10(十进制)个0
如果没有start:数据在前面,代码在后面,识别从前面开始,读不到代码
11、offset转移指令
SI (Source Index):源变址寄存器
DI (Destination Index):目的变址寄存器
offset:获取s的地址
jmp short s、jmp near ptr s:跳转到s函数的第一行代码,只改ip
jmp far ptr s:跳转到s函数的第一行代码,改cs、ip
jmp word ptr ds:[0]:从ds:0000处读4个16进制数作为跳转目标,近转移,远转移是dword
mov word ptr ds:[0],0:把0当0000存入这个地址
lea dx,arr1代替mov dx,offset arr1
下面代码只执行s部分,把mov拷到s0中(mov占2字节)
12、数组
数据段中 arr db 12,34定义数组
mov ax,type arr获取arr每个元素占的字节大小(1是byte型(db写的),2是word型(dw))
mov ax,数据段名
mov ds,ax(相当于mov si,offset arr)
mov ax,arr[0](相当于mov ax,ds:[si+0]):获取数据三步(取0可以不写[0]),反过来可以写入数组
代码段中 arr db 12,34定义数组 (注意start应该在它下面写)
mov si,offset arr:获取数组段地址
mov ax,arr[0]:获取数据(数组是dw写的,0位到1位只移动一个字,而不是一个字节,所以真正的1位在2)
mov ax,word ptr arr[0]: 数组是db写的时,转为16位存入ax(读两个字出来)
byte ptr xxx 从xxx的低位开始,取第一个字节的值
word ptr xxx 从xxx的低位开始,取前2个字节的值
dword ptr xxx 从xxx的低位开始,取前4个字节的值
二重循环语法:
13、实战代码
comment*
这是多行注释语法
这个asm文件名叫learn.asm
*comment
ASSUME CS:daima,DS:shuju,ss:zhan;没有指定会报Can't address with currently ASSUMEd segment registers
shuju SEGMENT
arr DB "你好世界b",10,'下一行',32,'空格','$'
;10是换行符,32是空格(即它们对应的十进制ascall码)
;'$'是截止符,避免dosbox运行learn.exe文件时出现乱码
arr1 DB 10,"HeLlo",32,"WoRLd",'$'
arr2 DB 13 dup(0),'$';用dup开13个空间,填0,注意没有'$'就会把arr3当成arr2的一部分
arr3 DB 210,223,243,210,222,228,'$'
arr4 DB 10,13 dup(0),'$'
shuju ENDS
zhan SEGMENT
DB 100 dup(0)
zhan ENDS
daima SEGMENT
hanshu:
MOV AX,shuju
MOV DS,AX;存入数据段地址
mov AX,zhan
mov ss,AX;存入栈段地址
MOV DX,OFFSET arr;把要显示的地址输给dx
;显示字符串
MOV AH,9;代表显示字符串的ah值
INT 21H;中断指令,根据ah的值不同执行不同操作
;因为大小写变换是ascall码加减20,20的二进制是0100000
;任何字母与上1011111变成大写(会破坏空格的32导致不能正常显示后半部分)
;任何字母或上0100000 变成小写
MOV CX,10;定义循环次数
MOV bx,1;只能用bx处理循环数据,赋值1是为了跳过换行符
xunhun:
MOV al,arr1[bx]
and al,1011111B;与运算
;or al,0100000B;或运算
MOV arr1[bx],al
a:inc bx;bx++
cmp arr1[bx],32
je a;上面相等时goto到a处代码,避免乱改空格
LOOP xunhun;循环cx次执行goto到xunhun代码
;MOV DX,OFFSET arr1;把要显示的地址输给dx
lea DX,arr1;把要显示的地址输给dx
;显示字符串
MOV AH,9;代表显示字符串的ah值
INT 21H;中断指令,根据ah的值不同执行不同操作
;求arr1每位及以前的最大数,存在arr2中
MOV arr2[0],10
MOV bx,1
MOV cx,12
MOV AX,0
MOV ah,0FFH
xh:
MOV al,arr1[bx]
cmp ah,al
;jnb xh1;前面最大的大等于现在的,则ah也是现在的最大值
jna xh1;前面最小的小等于现在的,则ah也是现在的最小值
MOV ah,al;如果现在的大,给ah赋值完再存
xh1:
MOV arr2[bx],ah;最大值存在ah中,把ah存入数组
inc bx
loop xh
lea DX,arr2;把要显示的地址输给dx
;显示字符串
MOV AH,9;代表显示字符串的ah值
INT 21H;中断指令,根据ah的值不同执行不同操作
;给数组arr3求和
MOV cx,6
MOV bx,0
MOV AX,0
xh2:
add Al,arr3[bx];给ax加八位必须这样写
;word ptr读数组会直接读十六位,而不是把八位转16,对寄存器则不能用
adc ah,0;加的时候带上进位
inc bx
loop xh2
MOV bx,1
MOV ch,10
xh3:div ch
add ah,'0'
mov arr2[bx],ah
mov ah,0
inc bx
cmp al,0
jne xh3;商不为0就继续除
mov arr2[bx],'$';使数字后的其他字符不显示
mov arr4[bx],'$';把arr2倒着拷到arr4,正常显示数组和
dec bx
mov si,bx;只有si、di、bx可以用来当数组下标,用别的报Illegal indexing mode
mov di,0
xh4:
mov cl,arr2[si];两数组元素不能互相赋值,要用寄存器转接一下
mov arr4[di+1],cl
dec si
inc di
cmp si,0
ja xh4
;用栈反转arr2,正常显示数组和
mov si,1
mov cx,bx
mov dx,0
for:
mov dl,arr2[si];因为栈只能操作16位,要把数组元素转成16位入栈
push dx;只有dw的数组元素可以直接入栈
inc si
loop for
mov si,1
mov cx,bx
for1:
pop Dx
mov arr2[si],dl
inc si
loop for1
lea DX,arr2;把要显示的地址输给dx
;显示字符串
MOV AH,9;代表显示字符串的ah值
INT 21H;中断指令,根据ah的值不同执行不同操作
lea DX,arr4;把要显示的地址输给dx
;显示字符串
MOV AH,9;代表显示字符串的ah值
INT 21H;中断指令,根据ah的值不同执行不同操作
;程序正常退出
MOV AH,4CH;给ah赋表示退出的值
INT 21H
daima ENDS;记住代码段和函数的结束语法
END hanshu
14、补充
15、笔试题分析
常用断点功能汇总
输出从ds位置开始的字符串(需要'$'作为终止符,否则不能正常停止输出)
MOV AH,9;代表显示字符串的ah值
INT 21H;中断指令,根据ah的值不同执行不同操作
输出dl中的字符
MOV ah,2H
INT 21H
将读取到的字符串输入dx位置的数组(第一个有效位是arr1[2])
MOV DX,OFFSET arr1[0];把字符串读入arr1
mov ah,0AH
INT 21H
将读取到的一个字符输入AL
mov ah,1
INT 21H
正常退出程序
MOV AX,4C00H
INT 21H
常见思维总结
注意题目要求输出什么、以什么形式输出(会在[]规范输出格式如[1,1]要求输出1,1,要有逗号)
注意求什么不等于输出什么!!
注意题目要求的数据段形式,如果出现 .DATA则表示要求用下面的标准形式写代码
此写法或许只能mov bx,OFFSET ARRAY 、mov ax,[bx]、add bx,2(word型)访问数组元素?
答案:笔试标准是这种写法,mov ax,ARRAY[bx]可能不给分
.MODEL SMALL
.DATA
DATA DW 12345
ARRAY WORD 82,13,13
.CODE
MAIN PROC FAR
MOV AX,@DATA
MoV DS,AX
;代码……
END MAIN
字符0是30H,加上它使数字转字符,字符9是57,如果转字符的结果大等于58,就要再加7转成字母(字母A是65,a是97)
10=0AH是换行符,13=0DH是回车符,输出dl中的字符成为字符串后,要依次输出回车、换行(可以事先放数组里,用时输出数组)再终止程序
任何字母与(AND)上1011111B变成大写(会破坏空格的32导致不能正常显示后半部分)
任何字母或(OR)上0100000B 变成小写
要先判断字母是否在范围内再用
如果要比较绝对值且可能出现负数,要先比较是否小于0,如果小于0,对它取反加1即取补再与其他数比较(NEG指令),注意正常比较不要这样操作!!
数据段运算符
TYPE运算符返回变量的当个元素的大小,这个大小是以字节为单位计算的。比如,TYPE为字节,返回值位1;TYPE为字,返回值是2;TYPE为双字,返回值为4;TYPE为四字,返回值为8.示例如下:
.data
var1 BYTE ? ;TYPE=1
var2 WORD ? ;TYPE=2
var3 DWORD ? ;TYPE=4
var4 QWORD ? ;TYPE=8
LENGTHOF运算符计算数组中元素的个数,元素个数是由数组标号同一行出现的数值来定义的。示例如下:
.data
byte1 BYTE 10,20,30 ;3
array1 WORD 30 dup(?),0,0 ;30+2
array2 WORD 5 dup(3 dup(?)) ;5*3 循环填充5*3次?
array3 DWORD 1,2,3,4 ;4
digitStr BYTE "12345678",0 ;9
myArray BYTE 10,20,30,40,50, 结果是5,即第一行的数字数
60,70,80,90,100
SIZEOF运算符返回值等于LENGTHOF与TYPE返回值的乘积。如下例所示,intArray数组的TYPE=2,LENGTHOF=32,因此,SIZEOF intArray=64:
.data
intArray WORD 32 DUP(0)
.code
mov eax,SIZEOF intArray ;EAX=64
将二进制数转换成十六进制数
这个汇编程序的目的是将一个二进制数(数据段中的12345)转换为十六进制数并输出。我们将一步步分析这个程序的操作。
1. 设置数据段:
```
.MODEL SMALL
.DATA
DATA DW 12345
```
这里设置了一个名为"DATA"的数据段,其中包含一个双字(2字节)长度的数值12345。
2. 设置代码段和主程序:
```
.CODE
MAIN PROC FAR
```
这里定义了代码段(CODE)和一个远程过程(PROC FAR)。远程过程允许跨段调用。
3. 初始化数据段寄存器:
```
MOV AX,@DATA
MOV DS,AX
```
将数据段的地址加载到AX寄存器中,然后将AX的值复制到DS寄存器。DS寄存器用于访问数据段。
4. 加载数据到BX寄存器:
```
MOV BX,DATA
```
将DATA处的二进制数12345加载到BX寄存器中。
5. 设置计数器:
```
MOV CH,4
```
将4存入CH寄存器,这是一个计数器,表示要循环4次,因为十六进制数的每个字符表示4位二进制数。
6. 循环开始:
```
ROTATE:
```
这是一个标签,用作循环开始的地方。
7. 左移数据:
```
MOV CL,4
ROL BX,CL
```
将4存入CL寄存器,然后将BX寄存器的值左移4位。这是为了得到二进制数的下一个4位数字。
8. 截取低4位并转换为ASCII:
```
MOV AL,BL
AND AL,0FH
ADD AL,30H
```
将BL寄存器(存放BX中的低8位)中的值移动到AL寄存器,与0FH(即00001111)执行AND运算以保留低4位。接着,将ASCII码基数30H(即48,表示字符'0')加到AL上,将其转换为ASCII字符。
9. 判断并处理字母A-F:
```
CMP AL,3AH
JL PRINTF
ADD AL,7H
```
比较AL是否小于3AH(即58,表示字符':')。如果小于,说明AL已经是一个有效的数字字符,转到PRINTF。否则,加上7H(即7),将其转换为大写字母A-F。
10. 输出字符:
```
PRINTF:
MOV DL,AL
MOV AH,2H
INT 21H
```
在PRINTF标签处,将AL中的字符移动到DL寄存器。设置AH寄存器为2H,这是DOS的字符输出功能。调用INT 21H,DL中的字符将被显示。
11. 检查是否完成循环:
```
dec ch(原题这里填MOV DL,AL,执行出现死循环)
JNZ ROTATE
```
检查是否输出了4个字符。如果DL中的值不为零,则跳回ROTATE继续循环。否则,循环结束。
12. 退出程序:
```
MOV AX,4C00H
INT 21H
MOV AX,4C0H
END MIAN
```
设置AH为4CH,表示DOS退出程序功能。然后调用INT 21H。最后,设置AX为4C0H,表示程序正常结束。
此程序将十进制数12345转换为十六进制表示并输出。分析完整汇编代码后,可以看到程序如何一步步完成这个任务。