宝宝汇编学习总结

来源:互联网 发布:平面效果图制作软件 编辑:程序博客网 时间:2024/05/01 18:54

     下面的汇编知识都是从王爽老师的书籍中自学的(这本书写的非常好,在我看此书的整个过程中就一个感觉“看了这一章就想看下一章,感觉这东西很神秘,步步都在激发我的兴趣”,所以非常感谢王爽老师的这部经典作品,居然让我这么笨的人都能看懂的书肯定是本经典之作,呵呵),学习汇编目的不是为了找份汇编的工作,只是兴趣而已,因为我是一个从JAVA转向C的初级程序员,很想了解程序到底怎么运行的,很想看看底层的一些东西,所以自学了一遍汇编,以下是在做练习题时的主要笔记:

首先说下汇编的产生,英特尔公司于1976年又生产了增强型8085,但这些芯片基本没有改变8080的基本特点,都属于第二代微处理器。它们均采用NMOS工艺,集成度约9000只晶体管,平均指令执行时间为1μS~2μS,采用汇编语言、BASIC、Fortran编程,使用单用户操作系统,这时汇编语言诞生了。

 

8086汇编要点:
1、8086 16位CPU的16代表着它的寄存器一次性只能存储16位数据即两个字节,数据总线也是16位,它的地址总线是20位也可以说是20根。

2、CPU是有控制器、运算器、寄存器等器件构成,寄存器就是在CPU内部临时存放数据的存储器。

3、8086CPU有14个寄存器,所有寄存器都是16位的,4个段寄存器,其中CS是用来指令的段地址。

4、8086机中,任意时刻,CPU将CS:IP指向的内容当作指令执行。

5、CS一般都默认指向预设地址,即在debug窗口用命令a回车后得到的段地址值:偏移地址值和目前CS/IP中的值相等,所以在此处置入指令后,用t命令就可以直接执行。

6、Debug的T命令在执行修改寄存器SS的指令时,下一条指令也紧接着被执行。       

7、在实模式下运行汇编程序就是在纯DOS方式下运行。在保护模式下运行程序是指在装有Windows 2000、Unix等等这些操作系统中运行程序。这是书中的原话,还得好好琢磨呀!

8、command(cmd)进入DOC,cls是清空DOC,exit是退出DOC即关闭黑窗口,debug进入汇编调试,Q是退出debug。

9、debug调试程序肯定内置了汇编语言的编译器,否则计算机也没法认识汇编指令。

10、DS寄存器通常用来存放要访问数据的段地址,DS是一种段寄存器,8086CPU不支持将数据直接送入段寄存器的操作,所以只能用另一个寄存器(通用寄存器)进行中转。
   
11、伪指令由汇编编译程序(编译器)执行,汇编指令由CPU执行。

12、dos方式打开edit编辑器:cmd->edit    (1)alt+f进入file菜单,就可以保存和退出edit!   (2)利用“Alt+Enter”组合键可实现全屏方式与窗口方式之间的切换!

13、在汇编源程序中数据不能以字母开头:mov ax,0ffffh这里由于ffffh是以字母f开头的,所以加个0就解决了。

14、我们可以把内存中连续的内存单元当做一个段,而且段的起始地址是16的整数倍,而段空间要连续,那么一个段最小占16个字节,这样地址最后一位才能是0即就是16的整数倍。程序中用

segment声明一个段,在一个segment中的代码在内存中只能存储在一个段中,段最大是多少与偏移地址有关,即一个段能存的代码数位FFFFH个字节。

15、在DOC系统中.exe文件被加载到内存后,程序所使用的内存区(包括psp和程序)的段地址会存入ds中,而真正的代码开始从psp也叫程序段前缀的后面开始存储,由于psp占256个字节,所以

程序的第一句从psp的段地址+10H处开始,切记psp和代码开始的内存区域是连续的,但却在不同的段中。也就是说真正的代码是从ds+10H处开始存储的。

16、一个字节可以存储256个不同的数字,即最大十进制为255。两个字节最大十进制为2的16次方减一即65535。四个字节的最大十进制为4294967296。

17、汇编语言不同的编译器会有不同的注释,我用的是微软的masm 5.0,这个只支持用分号单行注释即;后面跟注释的内容。

18、有符号数都是由补码组成的,不论正数还是负数,最高位为1就是负数,为0就是正数,正数的补码(也就是此数)取反加1后为其负数的补码(也就是此负数),负数的补码取反加1后为其绝对值。

19、栈不是内存定义的,而是CPU提供了相关的指令把内存中的一段空间以栈的方式进行访问(后进先出),所以这块内存空间才被看做是一个栈这段内存可以用push和pop指令分别往里面存数据(入栈)、取数据(出栈 ),8086CPU的入栈和出栈操作都是以字(双字节)为单位进行的。栈的最主要特点是当执行入栈和出栈操作时,数据就像盒子里书一样,是先进后出的(后进先出),而怎么去确定哪段内存是栈呢,这就要靠CPU对栈的支持情况了,在8086CPU中SS:SP这两个寄存器就确定了栈段和栈顶,但不能控制栈的大小,所以很容易出现栈顶超界问题,8086CPU只知道栈在哪,但不知道栈有多大,因此为了防止覆盖掉其他内存空间的数据程序员自己要小心。执行出栈后栈顶原先的数据依然在内存单元中,只是这个字数据不再属于栈空间了,所以再次入栈时会覆盖此数据。一个栈段的容量最大是64KB,如果当栈段已经压满时,还继续入栈操作,则SP+2即FFFF+2又变成了0,所以新内容会覆盖了栈中的旧内容。

20、8086CPU的数据总线宽度为16,地址总线宽度为20,所以物理地址就是20位。

21、控制总线主要是传输各种控制命令的,比如内存的读、写就是由CPU通过控制总线把信息发个内存的。

22、一般的独立显卡的显存就在显卡内部,而集成显卡的显存是从主存中划分出的一部分。

23、CPU将系统中的各类存储器看做一个逻辑存储器,即整个主板的主存、扩展内存、网卡上的显存、和别的ROM都被CPU看成了一个整体内存地址空间,而每个物理设备(显卡、声卡、网卡等)

存储器都在这个整体的内存地址空间中有对应的空间段,CPU在这段地址空间中读写数据就相当于操作具体的物理设备的存储器,所以不管显存是在显卡上还是再主存上分的,CPU都是从某一个固定的内存地址(即显存区B8000H—BFFFFH)中获取显存数据的。

24、内存单元的唯一地址也称物理地址。

25、8086CPU是在内部根据段地址*16+偏移地址获得了物理地址再去找相应的内存单元,内存并没有分段,段的划分来自于CPU,一个段最小占16个字节,因为物理地址要是16的倍数。

26、CPU可以用不同的段地址和偏移地址形成同一个物理地址。

27、可以根据需要将地址连续、起始地址为16的倍数的一组内存单元定义为一个段。

28、在CUP中,程序员能够用指令读写的部件只有寄存器。

29、连接作用:一个源程序编译后,得到了存有机器码的目标文件(.job),目标文件中的有些内容还不能直接用来生成可执行文件,连接程序将这些内容处理为最终的可执行信息。所以,在只有一个源程序的文件中,即使不需要调用某个库中的子程序,也必须用连接程序对目标文件进行处理,生成可执行文件。

30、任何通用的操作系统都要提供一个称为shell(外壳)的程序,用户使用这个程序来操作计算机系统进行工作,就是shell把我们要运行的程序加载入内存,然后设置CS:IP指向程序的入口,

此后shell程序暂停运行,CPU控制权交个被加载的程序,程序运行结束后再把CPU的控制权交给shell程序。比如:在DOS中,command.com就是shell程序。

31、汇编语言中,标号代表一个地址,它实际上标识了一个物理地址,这个地址处有一条或N条指令。
    如:s: add ax,ax,loop s。这里的s就代表了一个地址。

32、以后课程中reg表示一个寄存器,sreg表示一个段寄存器。

33、指令在执行前,所要处理的数据可以在3个地方:CPU内部、内存、端口。

34、idata代表一个立即数,它一般在CPU内部的指令缓冲器中。比如:mov ax,728这个指令在执行前,728就存在于CPU的指令缓冲器中。

35、在没有寄存器名存在的情况下,用操作符X ptr指明内存单元的长度,比如:mov word ptr ds:[0],1  操作的是字单元。
    add byte ptr ds:[0],2  这里操作的是一个字节单元。   

36、当我们要在屏幕上显示728这个十进制数据时,在显存中存的不是728这个数字所对应的二进制,而是7、2、8这三个字符所对应的ASCII码值,显卡遵循的是ASCII编码。

37、一个十进制数的每一位数值+30H就是此位数对应的ASCII码值,如:12666中1的ASCII=31H,2的ASCII=32H,6的ASCII=36H。

38、汇编源程序名字不能太长,比如:test10_3_1.asm这个源程序在编译时就会报这样的错“Unable to open input file: test_10_3_1.asm”,但把名字改成test.asm就可以了。

39、标志(flag)寄存器中有用的位值代表了不同的含义,有0标志位(ZF);有奇偶标志位(PF);符号标志位(SF);进位标志位(CF)针对无符号数运算而言的,在进行inc和loop指令时不会影响CF位的值,所以有时候在做有进位的运算中宁可用inc也不能用add;溢出标志位(OF)针对有符号数运算而言的;方向标志位(DF)。

40、CPU在执行add(加法)等指令的时候,就包含了两种含义,无符号数运算和有符号数运算。

41、所有条件转移指令的转移位移都是[-128,127]。

42、在后面加“:”的地址标号只能在代码段中使用,不能在其他段中使用,而不加“:”的地址标号在任何段都可以使用。

做练习时发现的两个小规律:
1、2的n次方加2的n次方等于2的n+1次方。

2、如果一个8位的二进制要分成5个和3个两段的相加,即11111111=11111*2的3次方+111(后面有3位),10进制举例:12345=123*10的2次方+45(因为后面有两位45),所以这样把一个数分成两个相加的数时,与进制(N)和后面的位数(M)有关,前面的数*N的M次方再加后面的数。

 


各种汇编指令:
★mov指令被称为传送指令,两个操作数位数要一样,即都是8位或16位。(不能设置CS、IP的值)
    mov 寄存器,数据       mov ax,8(8在书面上是十进制的,但debug中运行都是十六进制,在debug不允许后面加H)
    mov 寄存器,寄存器     mov ax,bx   
    mov 寄存器,段寄存器   mov ax,ds
    mov 寄存器,内存单元   mov ax,[0](debug中)或者mov ax,ds:[0](源程序中)
    mov 内存单元,寄存器   mov es:[0],ax
    mov 内存单元,段寄存器 mov [0],cs  切记:mov [bx],ip 这种写法是完全错误的,不能把ip值付给内存单元,它是不断在变化的由于自增特性所以就没固定值,因此不能进行赋值。
    mov 段寄存器,寄存器   mov ds,ax   
    mov 指明内存单元长度 内存单元,数据  mov word ptr ds:[0],1   
    mov的源操作数与目标操作数类型必须一致
    mov指令的操作数不能全为内存变量
    mov指令的操作数不能全为段寄存器

★add是相加并传送指令(add、sub和mov一样,都有两个操作对象,而且都和上面的形式一样)。
★jmp无条件转移指令,专门修改CS和IP的内容。(CPU在执行JMP指令的时候并不需要转移的目的地址)
  jmp用法jmp 2AE3:3这是debug中的指令,在源程序中不能用,编译时会报错。
       jmp ax 把IP值改为AX中的值。(转移地址在寄存器中)
       jmp short 标号(s):段内短移位(属于短转移即8位位移,这个8位位移在编译时算出,范围在-128到127),标号处的地址-jmp指令后的第一个字 节的地址。(根据位移转移)   
       jmp near ptr 标号:段内近移位(机器码中包含了16位位移)。
       jmp far ptr 标号(远转移):段间转移。(根据机器码中的目标地址改变CS:IP,而不是根据位移)
       jmp word ptr 内存单元地址(ds:[0]),段内转移,直接修改IP值。(转移地址在内存中)
       jmp dword ptr 内存单元地址,段间转移。
★ret指令用栈中的数据,修改IP的内容,从而实现近转移。
★retf指令用栈中的数据,修改CS和IP的内容,从而实现远转移。
★call执行将当前的IP或CS和IP压入栈中,然后转移到标号处执行代码(根据位移转移)。
  call指令跟jmp差不多,就是在跳转前多了一步把IP当前值压入栈。
  可以用call和ret组合实现子程序,一般情况下用call调用子程序。
★jcxz有条件转移指令(属于短转移即8位位移,这个8位位移在编译时算出,范围-128到127,在对应的机器码中包含转移的位移,而不是目的地址),当cx=0时转移到标签处执行代码。
★div是除法指令,除数有8位和16位两种,在一个普通寄存器或内存单元中。被除数默认放在AX或DX和AX中。
    div byte ptr ds:[0],除数即此内存单元ds:[0]为8位,被除数则为16位,默认AX中存被除数,商在AL中,AH存余数。
    div word ptr es:[0],除数为16位,被除数为32位(即DX中存高16位,AX中存低16位),商在AX中存,余数在DX中存。
★push和pop指令是入栈和出栈指令(操作的都是字单元而不是字节)
    push  寄存器
    push  段寄存器
    push  内存单元(将一个内存字单元入栈)
      〓push指令不能把立即数(如:1、25等)直接入栈。
    pop   寄存器
    pop   段寄存器
    pop   内存单元(出栈的是一个内存字单元数据)
   
★程序返回指令:
    mov ax,4c00H
    int 21H
★loop循环指令,也都属于短转移,而且机器码中包含了转移的位移。
★inc加1指令:mov bx,1 inc bx 执行后,bx=2。
★adc是带进位加法指令,它利用了CF位上记录的进位值。
★sub减法传送指令如sub bx,10(这里10没加H代表十进制,但debug中不加H也代表十六进制)意思是bx=bx-000A。
★sbb是带借位减法指令,它利用了CF位上记录的借位值。
★dec减1指令:mov bx,1 dec bx 执行后,bx=0。
★mul乘法指令:都是8位或者都是16位。
    mul byte ptr ds:[0],8位乘法结果默认放在AX中(其中一个乘数在AL中)。
    mul word ptr [bx+si],16位结果高位在DX中,低位在AX中(其中一个乘数在AX中)。
★and逻辑与指令,按位进行与运算。
★or逻辑或指令,按位进行或运算。
★nop空指令,什么也不执行,但它占用一个指令的执行时间,让CPU等待一个周期,可能等待外设输入,内存读写等等。
★cmp是比较指令,相当于减法指令,只是不保存结果,cmp指令执行后将对标志寄存器产生影响。
  常用格式:cmp al,bl;cmp byte ptr [bx],8
  下面是经常和cmp配合使用的条件转移指令:
    je   等于则转移  (zf=1)
    jne  不等于则转移(zf=0)
    jb   低于则转移 (cf=1)
    jnb  不低于则转移(cf=0)
    ja   高于则转移  (cf=0且zf=0)
    jna  不高于则转移(cf=1或zf=1)
★movsb指令的功能是将ds:si指向的内存单元中的字节送入es:di中,然后根据标志寄存器df位的值,将si和di递增或递减。
★movsw指令的功能是将ds:si指向的内存字单元中的字送入es:di中,然后根据标志寄存器df位的值,将si和di递增2或递减2。
(上面这两条指令一般和rep指令配合使用,比如:rep movsb相当于s:movsb ;loop s)
★cld指令将标志寄存器的df位置0,使di和si递增。
★std指令将标志寄存器的df位置1,使di和si递减。
★int是引发中断指令,格式int n,n为终端类型码,它会调用n号中断处理程序。
★iret指令功能为:pop IP,pop CS,popf。
  int和iret经常配合使用,这和call跟ret的配合使用具有相同的思路。
★popf出栈并把值传给标志寄存器flag。
★in是从端口读入数据命令,out是向端口中写入数据,这两条指令只能使用ax或al来存放读入或写入的数据。
  如:in al,20h从20h端口读入一个字节;out 20h,al往20h端口写入一个字节;
★shl和shr是逻辑移位指令,shl是逻辑左移指令,shr是逻辑右移指令。如果移动位数大于1时,必须把移动位数先放在cl中。
    比如:移位等于1时:mov al,01001000b shl al,1; 大于1时:mov al,01010001b mov cl,3 shl al,cl;
★sti和cli指令设置IF=1和IF=0,CPU检测到可屏蔽中断信息时就要查看标志寄存器的IF位,如果为1响应中断,引发终端过程,   是0则不响应。

各种寄存器:
    ★通用寄存器:
     ●AX、BX([bx]可以代表偏移地址)、CX(cx可以存放loop循环的次数,但默认情况下当程序被操作系统的shell程序加载到内存后,CX存的就是程序的长度即机器码占用的字节数)、DX都是通用寄存器(存一般数据)。这4个寄存器为了兼容在上一代8位寄存器的CPU上运行,它们都可以分成两个8位的独立寄存器使用。比如:AX可以分为AH和AL,BX可以分为BH和BL等等。
    ★段寄存器包括CS、DS、SS、ES:
     ●CS是代码段寄存器(存代码、指令的段地址)、IP是指令指针寄存器(存代码、指令的偏移地址)。默认当代码载入内存后CS=DS+10H
     ●DS数据段寄存器,通常用来存放要访问数据的段地址,DS是一种段寄存器,8086CPU不支持将数据直接送入段寄存器的操作,所以只能用另一个寄存器(通用寄存器)进行中转。
     ●SS是栈段寄存器,栈顶的段地址存放在SS中,SP和IP有些相似,都存储偏移地址,栈顶的偏移地址存放在SP中,任意时刻,SS:SP指向栈顶元素,push和pop指令执行时,CPU从SS和SP

中得到栈顶的指令。
     ●cpu默认会从DS中取数据段地址,但也可以显式的用其他段寄存器的段前缀来获取段地址,如:mov es:[bx],dl
    ★SI、DI、BP和BX的功能很相似即都是可以存储偏移地址的寄存器,不过SI和DI不能分成两个8位的寄存器使用,除过这四个寄存器以外,别的寄存器都不能用于[存储器]这种格式进行内存单元的寻址。如果以组合的形式出现,只能有以下四种格   式:bx+si、bx+di、bp+si、bp+di。比如:mov ax,[bx+si]这是正确的,下面再举个错误的例子:mov ax,[bx+bp]。
      比如:mov ax,[bx+si+200]、mov ax,200[bx][di]、mov ax,[si].200[bx]
      等价关系:mov ax,[bx+idata]等于mov ax,[idata+bx]等于mov ax,idata[ax]等于mov ax,[bx].idata
      (idata代表一个立即数)
    ★标志(flug)寄存器的各位数值含义均不相同。
    ★mov ax,[bp···]等这种格式,即如果在[]中有bp参与的话cpu默认会在ss中找段地址,其他都在ds中。
★DOS下的debug命令(debug默认所有数据都用十六进制显示):
    cls:清空DOS
    exit:退出DOS
    q:退出debug
    r:查看、改变寄存器的内容   如查看:r直接回车  修改:r ax然后回车,r和ax之间的空格可有可无。
    d:查看内存中的内容   如:d 1000:0查看从段地址为1000偏移地址为0共128个内存单元即128个字节。
    e:改写内存中的内容   如:e 1000:0 1 'a' 3 "java" 5,即从1000:0开始的几个内存单元的内容被修改成1、a、3、java、5。或者直接e 1000:0回车,这样可以一个一个修改内存单元的内容。
    u:将内存中的机器指令翻译成汇编指令   如:u 1000:0查看此处内存单元的内容和被翻译后的汇编指令。
    t:执行一条指令如:t回车就执行了CS:IP处的指令。
    a:以汇编指令的格式在内存中写入一条机器指令如:a 1000:0回车,就会依次出现1000:0000 mov ax,1等等,
      指令是由咱自己填写的,或者直接a回车就从一个预设的地址(当前CS:IP)开始输入指令。
    g:让debug直接跟踪到g所指向的指令位置,如:g 0012,直接让debug显示到CS:IP中IP等于0012的代码处。
    p:在执行int 21h时就用p命令,也就是程序结束时用P命令,而且如果要命令一次执行完所有的循环,前提是当我们的debug已经走到了LOOP 0016处,就直接用P命令就可以了,不要用T命令。
★汇编源程序包括汇编指令(汇编指令是有对应的机器码指令,可以被编译器编译成机器指令,最终为CPU所执行)和伪指令(没有对应的机器指令,最终不被CPU所执行,而是由编译器执行的),其实汇编程序除了汇编指令和伪指令还有一些标号,比如:code、data等这些段名,他们将被编译、连接程序处理为一个段的段地址。
★汇编程序的伪指令(CPU不会执行的指令):
     (1)定义一个段(包括数据段、代码段、栈段),一个汇编程序是一个或多个段组成的。
    *** segment
        ·
        ·
        ·
    *** ends
     (2)end是一个汇编程序的结束标记,end start也说明源程序的入口标志在start那。
     (3)assume假设伪指令,假设某个段寄存器和某个段关联起来。
     (4)db(define byte)定义字节型数据。
     (5)dw(define word)定义字型数据,每个字所占的内存空间为2个字节。   
     (6)dd即dword(double word)定义双字型数据的。
     (7)dup是一个操作符,和db、dw、dd等伪指令配合使用的,用来进行数据的重复定义,比如:db 3 dup (0,1,2)定义了9个字节,内容分别为0、1、2、0、1、2、0、1、2。
     (8)offset操作符功能是取得标号的偏移地址。
     (9)seg操作符功能是取得标号的段地址。

 

好了,这次就先记录这些东西,方便以后复习查阅!