下面的笔记是根据鱼c工作室里的汇编教学视频里的总结的,方便复习
1: Debug的使用: 配置好DosBox后,打开运行,输入e:,回车(之前配置的)。然后就可以使用Debug了
2: 命令: .R命令查看、改变cpu寄存器的内容 执行后,会看到寄存器的内容,然后输入r ax,就可以在下面输入值,ax就会是改变后的值
. D命令查看内存中的内容 . E命令改写内存中的内容(机器指令的格式) //比如-e 1000:0 23 11 22 66就是将段地址为1000,偏移地址为0的数据写为23,1000:1为11,1000:2为22... . U命令将内存中的机器指令翻译成汇编指令 . T命令执行一条机器指令 . A命令以汇编指令的格式在内存中写入一条机器指令编写程序时,比如想将ffff赋值给ax,不可以mov ax,ffff,因为编译器不认识以字母开头的值,必须在前面加上0
汇编中可以用loop循环,cx保存循环的次数,每次循环,cx-1,cx为0时结束循环,比如88*99,可以 mov ax,0 mov cx,99
s: add ax,88 loop s5: 难道88*99真的要循环那么多次吗?于是乎,引入g命令来解决!也可以是p命令 比如执行时,查看一下cs和ip的值,用-u指令查看汇编指令的地址,然后直接-g 后面加上偏移地址 就可以直接跳转到该地址,相当于vs中在循环下面加个断点,然后跳到断点执行,就跳过了循环了
6:一段安全空间 并不是所有的内存空间都可以拿来用的,有些空间中可能有系统或其他程序的数据或代码,所以写入的话会引发错误 需要直接向内存写入内容时,就使用0:300-02ff这段空间
7:在8086CPU中,只有下面4个寄存器(bx,si,di,bp)可以用在”[]”中来进行内存单元的寻址 而且这四个寄存器可以单个出现,或者只能以四中组合出现: bx和si,bx和di,bp和si,bp和di
* bp默认段地址在ss中8:指令中要处理的数据是有长度的,比如c,c++里面int,char,double等等,汇编中通长有db(字节型),dw(字型),dd(双字型 )这几种类型,那么如何知道要处理的数据到底多长呢?
1)通过寄存器名指明: mov ax,bx //字型 mov al,bl //字节型 2) 在没有寄存器名存在的情况下,用操作符X ptr指明内存单元的长度,X在汇编指令中可以为word或byte mov word ptr ds:[bx],1 mov byte ptr ds:[bx],2 注意,有些指令只能进行字单元操作或者字节操作,比如push只能对字单元操作,因为每次push前都会sp=sp-2 3)其他...9:div(division)指令 注意: 1):除数,8为或16位,在寄存器或内存单元中
2):被除数,(默认)放在AX或AX和DX中 !!为什么是AX 或者 AX和DX 呢? 除数 被除数 8位 16位(AX) 16位 32位(AX+DX,AX存放低16位,DX存放高16位) 除数:8位 16位 商: AL AX 余数:AH DX 3): 指令格式 div reg //div加上一个寄存器 div 内存单元10:伪指令dup 格式: db(或者dw,dd等) 重复的次数 (重复的数据)
实例: # db 3 dup(0) 相当于db 0,0,0 # db 3 dup(0,1,2) 相当于db 0,1,2,0,1,2,0,1,2 dup是编译器区识别的,比如要定义容量为30个字节的栈段,可以 stack segment db 30 dup(0) stack ends 这样就很方便了11:转移指令 # 8086cpu的转移指令分为以下几类: * 无条件转移指令(如:jmp) * 条件转移指令 * 循环指令(如:loop) * 过程(相当于函数) * 中断
# 操作符offset在汇编语言中是由编译器处理的符号,它的功能是取得标号的偏移地址 a. 无条件转移: # jmp为无条件转移,可以只改变IP,也可以同时修改CS和IP jmp指令要提供两条信息: * 转移的目的地址 * 转移的距离(段间距离,段内短转移,段内近转移) //==============================段内转移==================================== 1)jmp short 标号 (转到标号处执行指令) 这种格式的jmp指令实现的是段内短转移,它对IP的修改范围为-128-127,即向前转移最多 可以越过128个字节,向后转移最多可以越过127个字节 assume cs:codesg codesg segment start: mov ax,0 jmp short s add ax,1 //此处ax+1未执行,由jmp直接跳过了,所以最终ax为1 s:inc ax codesg ends end start 实际上,指令"jmp short 标号"的功能为ip=ip+8位位移 * 8位位移="标号"处的地址->jmp指令后的第一个字节的地址 * short指令表明此处的位移为8位位移 * 8位位移的范围为-128-127,用补码表示 * 8位位移由编译程序在编译时算出 2) jmp near ptr 标号(和jmp short 标号类似,不过是段内近转移) 指令"jmp near ptr 标号"的功能为ip=ip+16位位移 * 16位位移="标号"处的地址->jmp指令后的第一个字节的地址 * near指令表明此处的位移为16位位移 * 16位位移的范围为-32769-32768,用补码表示 * 16位位移由编译程序在编译时算出 3)jmp 16位寄存器 功能:IP=16位寄存器 4)jmp word ptr 内存单元地址 功能:从内存单元地址处开始存放一个字,是转移的目的的偏移地址 //==============================段间转移======================================= 1)jmp far ptr 标号(实现的是段间转移,又称为远转移) 功能: * cs=标号所在段的段地址 * ip=标号所在段中的偏移地址 2)jmp dword ptr 内存单元地址 功能:从内存单元地址处开始存放两个字,高地址处的字是转移目的的段地址,低地址处是 偏移地址b.有条件转移
1)jcxz指令 jcxz指令为有条件转移指令,所有的有条件转移指令都是短转移,在对应的机器码中包含转移的位移 而不是目的地址。对IP的修改范围都为-128-127 指令格式: jcxz 标号 如果cx=0,则转移到标号处执行,相当于(if(cx==0) jmp short 标号) 2)loop loop为循环指令,所有的循环指令都是短转移,在对应的机器码中包含转移的位移,对ip的修改范围 为-128-127 指令格式:loop 标号 每次执行后cx=cx-1,如果cx!=0,则转移到标号处执行,与jcxz相反,相当于 if(--cx!=0) jmp short 标号12 call和ret指令 # call和ret指令都是转移指令,他们都修改IP,或同时修改CS和IP
1)ret指令用栈中的数据,修改IP的内容,从而实现近转移 Cpu执行ret指令时,进行下面两步操作 a: IP=ss*16+sp b: sp=sp+2 //相当于pop IP 2) retf指令用栈中的数据,修改CS和IP的内容,从而实现远转移 Cpu执行ret指令时,进行下面四步操作 a: IP=ss*16+sp b: sp=sp+2 c: CS=ss*16+sp d: sp=sp=2 //相当于pop IP pop CS 3)call指令经常跟ret指令配合使用,因此cpu执行call指令,进行两步操作 a:将当前的IP或CS和IP压入栈中 b:转移(jmp) # call指令不能实现短转移,除此之外,call指令实现转移的方法和jmp指令的原理相同 格式:* call 标号(将当前IP压入栈后,转移到标号处执行指令) cpu执行此种格式的call指令时,进行如下操作: sp=sp-2 ss*16+sp=ip ip=ip+16位位移(“标号”处的地址减去call指令后的第一个字节的地址) //相当于push IP jmp near ptr 标号 * call far ptr 标号 (实现的是段间转移) cpu执行此种格式的call指令时,进行如下操作: sp=sp-2 ss*16+sp=cs sp=sp-2 ss*16+sp=ip cs=标号所在的段地址 ip=标号所在的偏移地址 //相当于push CS push IP jmp far ptr 标号 * call 16位寄存器 功能: sp=sp-2 ss*16+sp=ip ip=16位寄存器 相当于push IP jmp 16位寄存器 # 转移地址在内存中的call指令(有两种格式) * call word ptr 内存单元地址 相当于 push IP jmp word ptr 内存单元地址 * call dword ptr 内存单元地址 相当于 push CS push IP jmp dword ptr 内存单元地址 #可以写一个具有一定功能的程序段,我们称其为子程序,在需要的时候,用call指令转去执行,可是执行完子程序之后,如何 让cpu继续向下执行呢?就是ret了,call指令后面的指定的地址被存储在栈中,所以可以在子程序到后面使用ret指令,用栈中 的数据设置IP的值从而转到call指令后面的代码处继续执行,这样就可以利用call和ret来实现子程序的机制13:mul指令(乘法指令) * 相乘的两个数,要么都是8位,要么都是16位 8位:AL中和8位寄存器或内存字节单元中,结果在AX中 16位:AX中和16位寄存器或内存字单元中,结果在DX(高位)和AX(低位)中 * 格式: mul reg mul 内存单元 int(): 描述性运算符,取商,比如int(11/3)=9 rem(): 描述性运算符,取余数,比如rem(11/3)=2
13:标志寄存器 * 8086cpu的flag寄存器的结构 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 OP DF IF TF SF ZF AF PF CF
1):ZF标志 (Zero) falg的第6位,零标志位。它记录相关指令执行后,结果为0,则ZF=1,否则为0 注意:在8086cpu的指令集中,有的指令的执行是影响标志位的,比如:add,sub,mul,div,inc,or,and等,它们大多 都是运算指令(进行逻辑或算数运算);而有的指令没有影响,比如:mov,push,pop等,大都是传送指令 2):PF标志 (Parity n.平价,价值对等; 同等,平等; 奇偶性; ) falg的第2位,奇偶标志位。记录指令执行后,结果的最低位字节中“1”的个数若为偶数,则PF=1,否则为0 3):SF标志 (sign) falg的第7位,符号标志位。它记录执行指令后,若结果为负,SF=1,否则SF=0,所以说,SF是cpu对有符号数元算 结果的一种记录,它记录数据的正负,无符号数就不用管此标志了 4): CF标志 falg的第0位,进位标志位。主要用来反映运算符是否产生进位或借位,如果进位,CF=1,否则为0 5):OF标志 falg的第11位,溢出标志位。 * CF和OF到区别: # CF是对无符号数运算有意义的标志位; # 而OF是对有符号数运算有意义的标志位 6) : adc指令 带进位加法指令。它利用了CF位上记录的进位值。 格式: adc 操作对象1,操作对象2 功能: 操作对象1=操作对象1+操作对象2+CF 7):sbb指令 带借位减法指令,它利用了CF位上记录的借位值 格式:sbb 操作对象1,操作对象2 功能:操作对象1=操作对象1-操作对象2-CF 8):cmp指令 格式:cmp 操作对象1,操作对策2 功能:计算操作对象1-操作对象2但并不保存结果,仅仅根据计算结果对标志寄存器进行设置 * 根据无符号数的比较结果进行转移的条件转移指令,他们检测ZF,CF的值 * 根据有符号数的比较结果进行转移的条件转移指令,他们检测SF,OF和ZF的值 9):检测比较结果的条件转移指令 //=================检测比较结果的条件转移指令=========================== 条件转移指令小结 指令 含义 检测的相关标志位 je 等于则转移 ZF=1 jump equal jne 不等于则转移 ZF=0 jump not equal jb 低于则转移 CF=1 jump below jnb 不低于则转移 CF=0 ja 高于则转移 CF=0,ZF=0 jump above jna 不高于则转移 CF=1或ZF=1 *上面这些都是检测无符号数的指令 10):DF标志和串传送指令 falg的第10位,方向标志位。 # 在串处理指令中,控制每次操作后si,di的增减 * DF=0时:每次操作后,si,di递增 * DF=1时,每次操作后,si,di递减 格式1:movsb 功能:(以字节为单位传送) a. es*16+di=ds*16+si b. 如果DF=0,则si=si+1 di=di+1 否则: si=si-1 di=di-1 movsb的功能是将ds:si指向的内存单元的字节送入es:di中,然后根据标志寄存器DF位的值,将si和di 递增或递减 格式2:movsw 功能:(以字为单位传送) 将ds:si指向的内存单元的字单元送入es:di中,然后根据标志寄存器DF位的值,将si和di 递增2或递减2 一般来说,movsb和movsw都和rep配合使用,格式:rep movsb rep作用是根据cx的值,重复执行后面传传送指令,这rep movsb可以循环实现cx个字符的传送,那么有没有设置DF 的值的指令呢?下面两条指令就是: cld指令:将标志寄存器的DF位设置为0 std指令:将标志寄存器的DF位设置为1 11):pushf和popf pushf:将标志寄存器的值压栈 popf:从栈中弹出数据,送入标志寄存器 12):标志寄存器在Debug中的表示14:内中断* 1):内中断的产生 根据中断源的不同,可以把中断分为硬件中断和软件中断两大类,而硬件中断又可以分为外部中断和内部中断两类 ,主要讲解硬件中断。外部中断(如:键盘中断,打印机中断等)是可以屏蔽的中断,内部中断(如:除数为0,突然断电 ,运算溢出等)不可以屏蔽。 软件中断其实并不是真正的中断,只是可被调用执行的一般程序以及Dos系统功能调用(INT 21h)等,都是软件中断
2):中断处理程序 中断产生后,要定位中断处理程序(中断类型码就是用来定位中断处理程序的),需要知道其段地址和偏移地址,但 是如何根据中断类型码得到中断处理程序的段地址和偏移地址呢?这就要引入“中断向量表”了。 3):中断向量表 cpu用8位中断类型码通过中断向量表找到相应的中断处理程序的入口地址,中断向量表在内存中存放,其中存放着 256个中断源所对应的中断处理程序的入口,在8086cpu中,中断向量表在内存0000:0000到0000:03ff处的1024个字 节,(一个物理地址由段地址和偏移地址组成,即2个字,4个字节,4*256=1024) 4): 中断过程 a.(从中断信息中)取得中断类型码N b. 标志寄存器的值入栈(保护标志位)(pushf) c.设置标志寄存器的第8位TF和第9位IF的值为0(这样做的目的后面再说)(TF=0,IF=0) d.CS的内容入栈(push CS) e.IP的内容入栈(push IP) f.从内存地址为中断类型码*4和中断类型码*4+2的两个字单元中读取中断处理程序的入口地址设置IP和CS ((IP)=(N*4),(CS)=(N*4+2)) 5): 中断处理程序 常规的步骤: a. 保存用到的寄存器 b. 处理中断 c. 恢复用到的寄存器 d. 用iret指令返回 * iret指令的功能用汇编语法描述为: pop IP pip CS popf 6):单步中断 cpu在执行完一条指令之后,如果检测到标志寄存器的TF位为1,则产生单步中断,引发中断过程15: int指令 1)int指令 int格式:int n //n为中断类型码,功能是引发中断过程
2)编写供应用程序调用的中断例程 3)对int,iret和栈的深入理解 4)BIOS和DOS所提供的中断例程 5)BIOS和DOS中断例程的安装过程 6)BIOS中断例程应用 int 10h中断例程是BIOS提供的中断例程,其中包含了多个和屏幕输出相关的子程序 BIOS和DOS提供的中断例程,都是用ah来传递内部子程序的编号 7)DOS中断例程应用 int 21h中断例程是DOS提供的中断例程,其中包含了DOS提供给程序员在编程时调用的子程序 之前一直是这么用的: mov ah,4ch //程序返回 mov al,0 //返回值 int 21h16:端口 cpu可以直接读写3个地方的数据(cpu内部寄存器,内存单元,端口)
1)端口的读写 * 对端口的读写只有两条(不能用mov,push,pop等内存读写指令),in和out,注意:在int和out指令中,只能用 ax或al来存放从端口中读取或写入的数据,访问8位端口用al,16位端口用ax * 对0-255以内的端口进行读写: int al,20h //从20h端口读入一个字节 out 20h,al //向20h断口写入一个字节 * 对256-65535的端口进行读写时,端口号放在dx中 mov dx,3f8h //将端口号3f8h送入dx in al,dx //从3f8h端口读入一个字节 out dx,al //向3f8h端口写入一个字节 2)CMOS RAM芯片 pc机中有一个CMOS RAM芯片,其特征如下: * 包含一个实时钟和一个有128个存储单元(存储的就是一个开机信息,该信息给BIOS主板)的RAM存储器。( 早期的计算机为64个字节) * 该芯片主要靠电池供电,因此,关机后其内部的实时钟任可工作,RAM的信息不会丢失 * 128个字节的RAM中,内部实时钟占用0-0dh单元来保存时间信息,其余大部分单元用于保存系统配置信息 供系统启动时BIOS读取 * 该芯片内部有两个端口,端口地址为70h和71h,cpu通过这两个端口读写CMOS RAM * 70h为地址端口,存放要访问的CMOS RAM单元的地址:71h为数据端口,存放从选定的CMOS RAM单元中读 取的数据,或者写入到其中的数据 3) shl和shr指令 shl和shr是逻辑移位指令,后面会用到移位指令,这里先讲一下 * shl:逻辑左移指令,功能: a.将一个寄存器或内存单元中的数据向左移位 b.将最后移出的一位写入CF中 c.最低位用0补充 例如: mov al,01001000b shl al,1 ;将al中的数据左移一位 执行后,(al)=10010000b,CF=0(最高位移出的放入CF) 注意:如果移动位数大于1时,必须将移动位数放在cl中,比如: mov al,01010001b mov cl,3 shl al,cl 执行后,(al)=10001000b,这里移出的是前面010三位,所以最后移出的一位就是右边从0,所以CF=0、 * shr:自己体会,不用写了 4)CMOS RAM存储的时间信息 * 在CMOS RAM中,存放着当前时间: 秒: 00h 分: 02h 时: 04h 日: 07h 月: 08h 年: 09h 这6个信息的长度都为1个字节,这些数据以BCD码(会计用到比较多)的方式存放: 数码:0 1 2 3 4 BCD码:0000 0001 0010 0011 0100 数码:5 6 7 8 9 BCD码:0101 0010 0111 1000 1001 比如十进制数26,对应的BCD码为:0010 0010 所以,一个字节可以表示两个BCD码,CMOS RAM存储 时间信息的单元中,存储了两个BCD码表示的两位十 进制数,高4位的BCD码表示十位,低4位的BCD码表示个位17:外中断 * 以前我们讨论的都是CPU对指令的执行。我们知道,CPUA在计算机系统中,除了能够执行指令,进行运算之外,还应该能够 对外部设备进行控制,接收它们的输入,向它们输出。也就是说,CPU除了有运算能力外,还要有I/O能力 1):接口芯片和端口 2):外中断信息 在PC系统中,外中断源一共有两类: a. 可屏蔽中断 b. 不可屏蔽中断
* 可屏蔽中断时CPU可以不响应的中断,CPU是否响应可屏蔽中断,要看标志寄存器的IF位的设置,现在知道了之前 内中断将IF设置为0的原因了吧,原因就是在进入中断处理程序后,禁止其他的可屏蔽中断 * 8086cpu提供的设置IF的指令如下: sti,用于设置IF=1 cli,用于设置IF=0 * 外中断中,不可屏蔽中断的中断类型码固定为2,所以中断过程中,不必像内中断那样取中断类型码 * 不可屏蔽中断的中断过程: a. 标志寄存器入栈,IF=0,TF=0 b. CS,IP入栈 c. (IP)=(8),(CS)=(0AH) //国定的一个地址 3):pc机键盘出处理过程 a.键盘输入 b.引发9号中断 c.执行int9中断例程 * BIOS提供了int 9中断例程,用来进行基本的键盘输入处理 4):编写int9中断例程(对键盘所处理的一个中断) 5):安装新的int'9中断例程