在汇编语言程序设计时,都将用到debug程序:
R命令:查看,改变CPU中的内容
D命令:查看内存中的内容
E命令:改写内存中的内容
U命令:将内存中的机器指令翻译为汇编指令
T命令:执行一条机器指令
A命令:以汇编的形式在内存中写入一条机器指令。
mov指令,add指令,sub指令:
mov指令的作用是将变量复制,例如:
在编译器中输入a以后,mov (被赋值的变量)(变量的值被利用的变量)
例如:mov ax,bx(意义就是将bx的值赋给ax变量)
add指令,其实大家也能看得出来,就是加法
sub指令就是减法
注意,以上的几个指令都是将左边的为主体,操作右边的事物,最终的结果还是保存在左边。
bl是低位,bh是高位,有进位的话会直接舍掉,不会进到前面一位来。
我们在使用r指令时,会出现很多的寄存器,比如ax,bx,cx,dx等等这一类的寄存器,
每一个寄存器都有自己的含义
mul为乘法
如果是八位乘法,结果默认存放在AX中,八位,在16进制中,最高为FF,如果是16位乘法,高位默认在DX,低位在AX。
div为除法
除数:有8位和16位两种,在一个reg或内存单元中
被除数:默认在AX或DX和AX中,如果除数为8位,被除数则为16位,默认在AX中存放,如果除数为16位,被除数为32位,在DX和AX中存放,DX存放高16位,AX存放低16位。
总结除法:8位:AX/BL=AL
16位:DXAX/BX=AX
乘法:ALBL=AX
AXBX=DXAX;
and和or指令:
与运算和或运算
在debug中,不能直接输入二进制,需要将二进制转化为16进制再进行运算。
左移和右移指令SHL,SHR
将数据左边位,就是将数据最右边添加一个0。
将数据右移一个单位,就是在数据的最左边添加一个0.
循环左移:rol ax,1
循环右移:ror ax,1
再介绍两个指令:
1.inc ax
2.dec ax
就是a++,a–
空指令nop,就是null。
交换指令:
xchg,就是将两个寄存器的数据互换。
取反指令 neg
就是二进制取反,比如ax的值为2,那么二进制为 0000 0010,取反后就为1111 1101,也就是FFFE
段地址X16+偏移地址=物理地址
段地址*16就是将其左移一位
这里解释一下什么是段地址,偏移地址
段地址:在实模式下,内存被划分为许多不同的段(Segment)。每个段都有一个地址,称为段地址。段地址是一个16位的值,它指定了内存中一个段的起始位置。在汇编语言中,通常使用段寄存器(如CS、DS、ES、SS)来存储段地址。
偏移地址:偏移地址是相对于段的起始位置的偏移量。偏移地址是相对于段地址的地址。它指定了在特定段内的位置。偏移地址也是一个16位的值。
DS寄存器就是数据段地址
想要将ax,bx中的数据变成物理地址,需要牢牢抓住ds这个寄存器,因为ds是偏移地址的寄存器,如果ds中的数值为21f0,那么就对应着地址为21f0的这个地址,使用语句 mov ax,[60]就是将21f0这个地址的第60为的值赋给ax.不能直接给ds赋值,需要先将想要的段地址段赋给bx,再使用mov语句赋值给ds。ds是个密集数。
使用add语句也是一样的,可以将段地址中的数据赋值给变量中,例如我使用e 21f0:60这个指令改写了21f0这个偏移地址中的内容,然后将ds的值也改变为21f0,我想将21f0:0060这个地址的内容数据赋值给ax变量,我有两种方式可以实现:1.add ax,[60]
2.mov ax,[60]
cs和ip寄存器是指向代码的寄存器,我们在使用a命令时,可以在内存中缓存代码,这些代码的机器语言是可以通过d命令查看内存读到的,我们也可以根据u命令来查看代码,会发现和我们当时写下的代码是一样的
使用t命令后,ip地址会自加,代表执行了一个语
句了。
当然,在计算机中,代码和数据是不加区分的。
jmp指令:跳转指令
其实我们也可以写出jmp的平替指令,就是将cs和ip指令进行修改,修改到想要执行的而代码的段地址和偏移地址处
其中可以这样理解,jmp bx,就是短地址不变,偏移地址变到了bx所在值的地方
在汇编语言中,CPU提供的栈的机制:
例如:mov ax,0123 //这一段的意思就是将ax的值赋为0123了
然后再 push ax //这个意思就是将ax压栈,那么压栈之后的结果就是将ax的高位01压到栈的底部,然后23这个低位压到01的后面
如果需要弹栈,我们以给ax赋值为例:
e 1000:0000 00.ab 00.cd
mov bx,1000
mov ds,bx
到这个时候,我们使用d 1000:0000指令来看看这一段地址的数据,会发现在1000:0000处,数据显示为 ab,cd
我们接着写指令
mov ax,[0]//这一指令的意思就是将1000这一段指令,由0000这一偏移地址赋值给ax
最后使用r指令来看看ax的值,会发现,ax的值为cdab
说明压栈的时候,是将高位ah赋值给底部,弹栈的时候就是将上面的cd先弹出来赋值给ax的高位
我们来执行一段指令,这个时候,ds是1000H,所以我们把1000H这一段地址当作栈来使用
我们开始指令:
mov ax,0123
push ax //将ax压栈
mov bx,2266
push bx
mov cx,1122
push cx
pop ax
pop bx
pop cx
之后我们使用t指令,将上述指令实现,最后使用r指令来看
会发现 ax=1122,bx=2266,cx=0123
可以很清楚的说明压栈弹栈的过程
高位先压栈,弹栈的时候,先弹给低位,然后是高位
总的来说,就是高位对高位,低位对低位,在dosbox中,我们使用d命令查看内存情况时,右边是高位。
寄存器中,ss:sp在任意时刻都指向栈顶的元素
每次执行一段指令后,ss:sp都会往下弹,此时再弹栈的话,就会弹出指针所指向的元素
如果爆栈后,比如说一个端地址中的某一行偏移地址的元素全弹出后,再执行一次pop指令弹栈,
这个时候,指针会指向下一个偏移地址,不会报错,并没有哪一个寄存器存储的是栈的容量,也不知道栈的空间会有多大,所以只能人为的去小心爆栈。
push 和 pop指令也可以将数据存放在寄存器中。
这个时候我们看ss:sp指针在哪儿,比如我使用mov和add指令将指针指向了1000:0013这个时候就已经指向这儿了,我们再使用push[0]指令,就可以将1000:0000这一地址的数据赋值给1000:0013这个位置了
SI和DI寄存器:寻址寄存器,比如这个指令mov cx,[si]我已经提前将si的值赋为1002了,所以如果赋值的话,就是将2000:1002这一地址的值赋给cx了。所以si和di就是和bx这个寄存器有相似的功能。
所以si和di寄存器使用的目的就是可以将其作为一个偏移地址使用,bx也有相近的功能。在使用的时候,也需要观察一下ds寄存器的位置,ds寄存器对应的位置是当下的段地址。
bp也可以看作是bp的替代,但是两个寄存器又不完全一样。
在使用bp寄存器时,如果没有显性的给出段地址,那么会默认的将ss寄存器所对应的地址作为段地址,也就是栈的位置
如果没有显性的给出段地址:
[bx]对应的就是ds:bx
[bp]对应的就是ss:bp
这里也有区别
1.直接寻址:直接是数字
2.寄存器间接寻址 [bx]
3.寄存器相对寻址:[bx+1]
同等的是[bx].1与上面这个语句的意思是相同的。
同等的也可以是1[bx]
还有[bx][1]
但是不能是[bx][bp]
两个只能用一个,si和di两个也只能用一个。
4.基址变址寻址{bx+si]
5.相对基址变址寻址[bx+si+1]
最后一个寄存器是ES寄存器,就是附加段寄存器
标志寄存器:
ZF标志寄存器表示零标志位
如果结果为0,ZF寄存器就是1,如果结果不为零,ZF=0,是偶数的话PF=1,不是的话,PF=0.、
在dubug中,我们查看的是NZ,ZR(0)
PF标志位:表示的是所有的比特位中1的个数是否位偶数
在debug中,我们看的是PE和PO
PE(1) PO(0)
SF标志位:符号标志位:记录的是在执行相关指令后,结果是否为负。是负数,SF=1,不是,SF=0。
在debug中,看的是pl,pl的意思是正数 ,NG的意思是负数
CF标志位:进位标志位,在mov指令,不会使CF标志位出现变化,add指令时,有可能会发生进位,在debug中,进位为CY,没进位为NC。
OF标志位:溢出标志位,在debug中,如果发生了溢出OV,没有溢出就是NV