《汇编语言》王爽著学习笔记记录(前半部分)

看了看书发现书上的一些概念容易忘记,就顺手打下来,考虑到篇幅过长,分为前后两个部分。

第一章:基础知识

  1. 汇编指令是机器指令的助记符,同机器指令一一对应。

  2. 每一种CPU都有自己的汇编指令集

  3. 在存储器中指令和数据没有任何区别,都是二进制信息。

  4. CPU可以直接使用的信息在存储器中存放。

  5. 存储器单元从零开始顺序编号。

  6. 一个存储单元可以存储8 bit,即8个二进制位。

  7. 1Byte = 8bit….

  8. 每一个CPU芯片都有许多管脚,它们与总线相连。其分为三类:

    • 地址总线的宽度决定了CPU的寻址能力
    • 数据总线的宽度决定了CPU与其它器件进行数据传送时一次数据传送量
    • 控制总线的宽度决定了CPU对系统中其它器件的控制能力
  9. 内存地址空间:最终运行程序的是CPU,对CPU来讲,系统中所有的存储器的存储单元都处于一个统一的逻辑存储器中,它的容量受CPU寻址能力(读取内存地址的范围)的限制。这个逻辑存储器即内存地址空间。

第二章:寄存器

  1. 8086CPU所有的寄存器都是16位的,可以存放两个字节。

  2. AX、BX、CX、DX这四个寄存器用来存放一般性数据,称为通用寄存器。为了兼容,通常把它们掰为两半来使用,它们可以独立使用:

    • AX → AH + AL
    • BX → BH + BL
    • CX → CH + CL
    • DX → DH + DL
  3. 在进行数据传送或运算时,指令的两个操作对象的位数应当是一致的。

  4. 8086cpu一次性能处理16位地址,但有20位地址总线,于是采用两个16位地址合成来形成一个20位的物理地址。

  5. 物理地址 = 段地址 * 16 + 偏移地址,其含义是用一个基础地址(段地址 * 16)和一个相对于基础地址的偏移地址相加得到物理地址。

  6. 将若干地址连续的内存单元看作是一个段,段地址就是该段的起始地址,用偏移地址定位段中的具体位置。其中段地址必然是16的倍数,偏移地址为16位,所以寻址能力为64K,故一个段的长度最大为64K

  7. 段寄存器:CS、DS、SS、ES、

  8. 8086pc机中,设CS中的内容为M(段地址),IP中的内容为N(偏移地址),任意时刻,CPU将从M * 16 + N内存单元读取指令执行。

  9. “ jmp 某一合法寄存器” :用寄存器中的值修改 IP。

  10. Debug基本命令:

    命令 功能 格式
    R(Register) 查看、改变CPU寄存器中的内容 ①查看:直接输入r,②修改寄存器值:r ax,按enter然后输入数据
    D(Dump) 查看内存中的内容 d 段地址:偏移地址 或者 d 段地址:偏移地址段 或d 起始位置 L长度
    E(Enter) 改写内存中的内容 e 起始地址 数据 数据 … 空格键表示处理完成
    U(Unassemble) 将内存中的机器指令翻译为汇编指令 u 段地址:偏移地址
    T(Trace) 执行一条机器指令 输入t ,CPU执行CS:IP指向的指令
    A(Assemble) 以汇编指令的格式在内存中写入一条机器指令 a 段地址:偏移地址,接着输入汇编指令

第三章:寄存器(内存访问)

  1. 8086CPU用16位来存储一个字,高8位存放在高位字节,低8位存放在低位字节。一个字用两个地址连续的内存单元存放,低位字节在低位地址,高位字节在高位地址。

  2. DS寄存器通常用来存放要要访问的数据的段地址。不支持直接将数据送入段寄存器。

  3. mov、add、sub指令

    指令 格式 示例
    mov 寄存器,数据 mov ax,6
    mov 寄存器,寄存器 mov ax,bx
    mov 寄存器,内存单元 mov ax,[8]
    mov 内存单元,寄存器 mov [9],ax
    mov 段寄存器,寄存器 mov ds,ax
    add 寄存器,数据 add ax,0
    add 寄存器,寄存器 add ax,bx
    add 寄存器,内存单元 add ax,[0]
    add 内存单元,寄存器 add [0],ax
    sub 寄存器,数据 sub ax,0
    sub 寄存器,寄存器 aub ax,bx
    sub 寄存器,内存单元 sub ax,[9]
    sub 内存单元,寄存器 sub [9],ax
  4. 入栈:将新元素放到栈顶,出栈:从栈顶取出一个元素。遵循先入后出原则(LIFO)

  5. PUSH(入栈),POP(出栈),以字为单位进行,

  6. 任意时刻,SS:SP指向栈顶元素,push和pop执行时,cpu从ss:sp中得到栈顶地址。

  7. 执行PUSH指令时,SP=SP-2,表示栈顶向上移动;执行pop指令时,SP=SP+2,表示栈顶向下移动;当栈为空时,SS:SP指向栈顶内存地址+2的内存单元。

  8. 在使用栈的时候,应当注意不要出现越界。

  9. 出栈的顺序应该和入栈的顺序相反,这与栈后入先出的特性相关。

  10. PUSH过程:①CPU先改变SP=SP+2,②向SS:SP处传送;

    POP过程:①CPU先向SS:SP读取数据,②改变SP的值SP=SP-2。

    push和pop指令操作栈的时候,修改的只是SP。

  11. 栈的栈顶变化范围:0~FFFFH,所以一个栈段最大容量为64K.

第四章:第一个程序

  1. 伪指令:由编译器执行的指令,CPU是不知道它们的,由编译器执行它们

    指令 功能
    段名 segment … 段名 ends 定义一个段,segment说明一个段开始,ends说明一个段结束
    ebd 汇编程序结束的标志
    assume assume cs:code,将段code与寄存器cs关联起来
    mov ax,4c00H int 21H 两行汇编指令,实现程序返回
  2. 连接(LINK)作用:

    1. 当源程序很大时,可以将它分为几个源程序文件来编译,每个源程序编译成为目标文件后,再用连接程序将他们连接到一起,生成一个可执行文件;
    2. 程序中调用来某个库文件中的子程序,需要将这个库文件和该程序生成的目标文件连接到一起,生成一个可执行文件;
    3. 一个源程序编译后,得到了存有机器码的目标文件,目标文件中的有些内容还不能直接用来生成可执行文件,连接程序将这些内容处理为最终的可执行信息。所以,在只有一个源程序文件,而又不需要调用某个库中的子程序的情况下,也必须用连接程序对目标文件进行处理,生成可执行文件。
  3. EXE文件加载过程:找到一段起始地址为0的空闲区,创建一个程序段前缀(PSP),在PSP后256字节开始,即SA+10H:0为段地址载入ds寄存器中,并初始化CS:IP指向这个地址。

第五章:[BX]和loop指令

  1. [BX]:

    指令 功能
    mov ax,[bx] bx中存放的数据作为偏移地址EA,段地址SA默认在ds中,将SA:EA处的数据送入ax中,即(ax) = ((ds)*16 + (bx))
    mov [bx],ax bx中存放的数据作为偏移地址EA,段地址SA默认在ds中,将ax中的数据送入SA:EA处,即((ds)*16 + (bx)) = (ax)
  2. loop指令:

    指令 格式 功能
    loop loop 标号 实现循环功能,cx中存放着循环次数,就相当于for,cx为0向下执行,不为0循环
    使用方法: ①在cx中存放循环次数②loop指令中的标号所标识地址在前面③循环执行的程序段在标号和lpp指令之间
  3. loop指令框架:

         mov cx,循环次数
    s:
         循环执行的程序段
         loop s
  4. 在汇编源程序中,数据不能以字母开头。所以大于9FFFH的数要在前面加0。

  5. loop指令的每一次都先判断cx的值,不为0则CS:IP指向下一步循环指令的地址,CPU执行。

  6. 在汇编源程序中,如果用指令访问一个内存单元,则在指令中必须使用 “[…]” 来表示内存单元,如果在 “[]” 里用一个常量 idata 直接给出内存单元的偏移地址,就要在 “[]” 的前面显示的给出段地址所在的段寄存器。

    mov al,ds:[0]
    //正确的表示
    
    mov al,[0]
    //masm就将[idata] 解释为 idata ,而不是一个内存单元地址
  7. 用于显示的指明内存单元的段地址的 “ds:” 、”cs:”、”ss:”、”es:”,在汇编语言中成为段前缀

  8. 在pc机中,一般 0:200~0:2ff 的256个字节的空间是安全的,即没有其它应用使用这段内存单元。

第六章:包含多个段的程序

  1. 在操作系统环境中,合法的通过操作系统取得的空间都是安全的,需要在汇编源程序中做出说明

  2. dw(define word) 关键字的意思是定义字型数据,数据之间使用逗号分隔。

  3. 将 start 放在第一条指令前面,然后在伪指令 end 后面加上 start,表示程序第一条指令从前面的 start 开始。

  4. 内存空间的开辟:使用 dw 关键字定义一些字型数据,通常为 0,然后将这一段空间当作栈段来使用。

  5. 使用内存 0:0~0:15 单元中的内容改写为程序中的数据:

    assume cs:codesg
    codesg segment
            dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
    
    start:  mov ax,0
            mov ds,ax
            mov bx,0           ;ds = 0,bx = 0
            mov cx,8           ;循环8次
    
        s:  mov ax,[bx]        ;(ax) = ((ds)*16 + (bx)),将对应的数据送入ax
            mov cs:[bx],ax     ;将ax中的数据放入内存地址 ((cs)*16 + (bx)) 处,[bx]为偏移地址,段地址在cs中
            add bx,2           ;bx + 2
            loop s
    
            mov ax,4c00h
            int 21h
    codesg ends
    end start
  1. 使用内存 0:0~0:15 单元中的内容改写为程序中的数据(数据的传送用栈来进行。栈空间设置在程序内):

    assume cs:codesg
    codesg segment
            dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
            dw 0,0,0,0,0,0,0,0,0,0
    
    start:  mov ax,cs 
            mov ss,ax
            mov sp,24h          ;或mov sp, 36  ,ss:sp = cs:24h
            mov ax,0
            mov ds,ax           ;ds = 0
            mov bx,0            ;bx = 0
            mov cx,8            ;循环8次
    
        s:  push [bx]           ;将[bx]中的数据压入栈中
            pop cs:[bx]         ;或 pop ss:[bx],
            add bx,2            ;bx + 2
            loop s
    
            mov ax,4c00h
            int 21h
    codesg ends
    end start
  2. 在汇编程序中,一个段的段名就代表了这个段的段地址,而偏移地址需要根据段中的数据来确定。

  3. 8086CPU不允许直接将一个数值送入段寄存器,应该由一个寄存器比如 ax 来中转。

  4. 汇编语言实验五:

    (1)将下面的程序编译连接,用Debug加载、跟踪,然后回答问题

    assume cs:code,ds:data,ss:stack 
    
    data segment 
            dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h 
    data ends 
    
    stack segment 
            dw 0,0,0,0,0,0,0,0 
    stack ends 
    
    code segment 
    
    start:  mov ax,stack 
            mov ss,ax 
            mov sp,16 
            mov ax,data 
            mov ds,ax 
    
            push ds:[0] 
            push ds:[2] 
            pop ds:[2] 
            pop ds:[0] 
    
            mov ax,4c00h 
            int 21h 
    
    code ends 
    end start 

    ①CPU执行程序,程序返回前,data段中的数据 不变 。

    ②CPU执行程序,程序返回前,CS= 0C88H ,SS= 0C87H ,DS= 0C86H 。

    ③设程序加载后,CODE段的段地址为X,则DATA段的段地址为 X-2 ,STACK段的段地址为 X-1 。

(2)将下面的程序编译连接,用Debug加载、跟踪,然后回答问题。

assume cs:code,ds:data,ss:stack 

data segment 
        dw 0123h,0456h
data ends 

stack segment 
        dw 0,0
stack ends 

code segment 

start:  mov ax,stack 
        mov ss,ax 
        mov sp,16 
        mov ax,data 
        mov ds,ax 

        push ds:[0] 
        push ds:[2] 
        pop ds:[2] 
        pop ds:[0] 

        mov ax,4c00h 
        int 21h 

code ends 
end start 

①CPU执行程序,程序返回前,data段中的数据 不变 。

②CPU执行程序,程序返回前,CS= 0C88H ,SS= 0C87H ,DS= 0C86H 。

③设程序加载后,CODE段的段地址为X,则DATA段的段地址为 X-2 ,STACK段的段地址为 X-1 。

④对于如下定义的段:

name segment

……

name ends

如果段中的数据占N个字节,则程序加载后,该段实际占有的空间为 ((N+15)/16)*16 。

④解析:

N分为被16整除和不被16整除。

当N被16整除时: 占有的空间为(N/16)*16

当N不被16整除时: 占有的空间为(N/16+1)*16,N/16得出的是可以整除的部分,还有一个余数,余数肯定小于16,加上一个16。

程序加载后分配空间是以16个字节为单位的,也就是说如果不足16个字节的也分配16个字节。

两种情况总结成一个通用的公式:((N+15)/16)*16

(3)将下面的程序编译连接,用Debug加载、跟踪,然后回答问题。

assume cs:code,ds:data,ss:stack 

code segment 

start:  mov ax,stack 
        mov ss,ax 
        mov sp,16 
        mov ax,data 
        mov ds,ax 

        push ds:[0] 
        push ds:[2] 
        pop ds:[2] 
        pop ds:[0] 

        mov ax,4c00h 
        int 21h 

code ends 

data segment 
        dw 0123h,0456h
data ends 

stack segment 
        dw 0,0
stack ends 

end start 

①CPU执行程序,程序返回前,data段中的数据 不变 。

②CPU执行程序,程序返回前,CS= 0C86H ,SS= 0C8AH ,DS= 0C89H 。

③设程序加载后,CODE段的段地址为X,则DATA段的段地址为 X+3 ,STACK段的段地址为 X+4 。

(4)如果将(1)、(2)、(3)题中的最后一条伪指令“end start”改为“end”(也就是说,不指明程序的入口),则哪个程序仍然可以正确执行?请说明原因。

答:start 作为汇编程序的一个标号,定义了程序的入口,如果不指名入口,程序会从加载进内存的第一个单元起开始执行,前二个题中,前面定义的是数据段、栈段,CPU 能够执行,但在整个程序上来讲,逻辑顺序错误,这样会导致空间二次分配等等情况。

​ 如果指明了程序的入口,CPU会直接从入口处开始执行真正的机器码,直到遇到中断指令返回,比如指令进行到需要栈段空间的时候,CPU 会跳到前面定义的栈段得到分配空间的地址。此种方式能够确保程序逻辑上的正确。因此有必要为程序来指明入口。

​ 综上,只有(3)可以在没有指明程序入口的情况下正确执行。

(5)程序如下,编写code段中代码,将 a 段和 b 段中的数据依次相加,将结果存到 c 段中。

assume cd:code

a segment
    db 1,2,3,4,5,6,7,8
a ends

b segment 
    db 1,2,3,4,5,6,7,8
b ends

c segment 
   db 0,0,0,0,0,0,0,0
c ends

code segment

start: mov ax,a
       mov ds,ax           ;ds段寄存器地址指向a

       mov ax,b
       mov es,ax           ;es段寄存器地址指向b

       mov ax,c
       mov ss,ax           ;ss段寄存器地址指向c

       mov bx,0            ;偏移地址为0
       mov cx,8            ;循环8次

    s: mov ax,[bx]         ;(ax) = ((ds)*16 + (bx)),将对应的数据送入ax
       mov ss:[bx],ax      ;将ax中的数据放入内存地址 ((ss)*16 + (bx)) 处,[bx]为偏移地址,段地址在ss中
       mov ax,es:[bx]      ;(ax) = ((es)*16 + (bx)),将对应的数据送入ax
       mov ss:[bx],ax      ;将ax中的数据放入内存地址 ((ss)*16 + (bx)) 处,[bx]为偏移地址,段地址在ss中
       inc bx              ;bx + 1
       loop s


       mov ax,4c00h
       int 21h

code ends
end start

(6)程序如下,编写 code 段中代码,用 push 指令将 a 段中的前8个字型数据,逆序存储到 b 段中。

assume cs:code

a segment
    dw 1,2,3,4,5,6,7,8
a ends

b segment
    dw 0,0,0,0,0,0,0,0
b ends

code segment

start: mov ax,a
       mov ds,ax   ;ds指向a段

       mov ax,b
       mov bx,0    ;ds:bx 指向a段的第1个单元
       mov ss,ax

       mov sp,16   ;设置栈顶指向 b:16
       mov cx,8    ;循环8次

    s: push [bx]
       add bx,2    ;bx + 2
       loop s      ;将a段中0~16个单元逆次入栈

code ends
end start

   转载规则


《《汇编语言》王爽著学习笔记记录(前半部分)》 Tyzhao 采用 知识共享署名 4.0 国际许可协议 进行许可。
  目录