以下为汇编学习记录,内容全部出自王爽的16位《汇编语言》,如有错误,可直接去查看原书。

汇编语言

  机器语言是机器指令集的集合,机器指令是一列二进制数字,计算机将其翻译成高低电平,从而使器件收到驱动。而程序员很难看懂!例如:8086 CPU 完成运算 s = 768 + 12288 – 1280,对应的机器码是:

10110000000000000000011
00000101000000000110000
00101101000000000000101

  汇编语言的主体是汇编指令。汇编指令是机器指令便于记忆的书写格式。其最终由编译器将他们处理成对应的机器语言,由机器执行。

汇编语言的组成:

  • 汇编指令:机器码的助记符,有对应的机器码 每条指令语句在汇编时都产生一个供CPU执行的机器目标代码。
  • 伪指令:由汇编器执行,没有对应的机器码 它所指示的操作是由汇编程序在汇编源程序时完成的,在汇编时,它不产生目标代码,在将源程序汇编成目标程序后,它就不复存在。
  • 宏指令:
  • 其他符号:如:+、-、*、等,由汇编器执行,没有对应的机器码

  指令和数据存放在存储器中,也就是平常说的内存中。在内存或硬盘上存储的数据和指令没有区别,都是二进制信息。例如:内存中有 1000100111011000,作为数据看是 89D8 H,作为指令看是 mov ax, bx。存储器被划分为若干单元,单元从 0 开始编号,最小信息单位为 bit(位),8 个 bit 组成一个 byte(字节),微机存储器以字节为最小单位来计算。

CPU 对存储器的读写

CPU 从 3 号单元中读取数据过程:

地址总线

  CPU 通过地址总线选定存储单元。一个 CPU 有 N 根地址总线,则可以说这个 CPU 的地址总线宽度是 N,其最多可以寻找 2N 个内存单元。地址总线决定了其寻址能力。


注意高低地址。上图实际选定的内存单元是 0000001011(即:11号单元)

数据总线

  CPU 和内存或其他器件的数据传送是通过数据总线进行的。

对于不能一次传送的数据,将先传送低字节,后传送高字节。例如:对于 89D82 H,将先传送 9D82,后传送 8。

控制总线

  CPU 对外部器件的控制是通过控制总线进行的。CPU 有多少控制总线,对外部器件就有多少种控制。控制总线决定了 CPU 对外部器件的控制能力。

  每个物理存储器在逻辑存储器中占据一定的地址空间,CPU 在相应的地址空间中写入数据,实际上就是向该物理存储器中写数据。例如:在 8086PC 中,

  • 0 ~ 7fffH 的 32KB 空间为主存储器的地址空间
  • 8000H~9fffH 的 8K 空间为显存的地址空间
  • A000H~ffffH 的 24K 空间为各个 ROM 的地址空间

那么,向8001H里面写入数据,实际就是把数据写入到显存中

不同的计算机内存地址空间的分配是不同的

寄存器

  8086 CPU 共有 14 个寄存器,AX、BX、CX、DX,SI、DI,SP、BP、IP,CS、DS、SS、ES,PSW。

通用寄存器

  8086 CPU 中,AX、BX、CX、DX通常用来存放一般性数据,称为通用寄存器。他们均为16位。并且都可以分为两个8位的寄存器(高8位和低为位)使用

  • AX 可分为 AH 和 AL
  • BX 可分为 BH 和 BL
  • CX 可分为 CH 和 CL
  • DX 可分为 DH 和 DL

      出于兼容性,8086 CPU 可以一次处理两种尺寸的数据:字节型字型(双字节。高地址存放高字节,低地址存放低地址)。在进行数据传送或运算时,指令操作对象的位数应该一致,例如:不能在字与字节类型之间传送数据:
    mov ax, bl (错误的指令)
    mov bh, ax (错误的指令)

物理地址

8086 CPU 是 16 位结构:

  • 运算器一次最多处理 16 位数据
  • 寄存器最大宽度是16 位
  • 寄存器和运算器之间的通路是16位

8086 CPU 有 20 根地址线,然而其又是 16 位结构,所以 8086 CPU 内部用两个 16 位地址合成一个 20 位地址

地址加法器采用 物理地址 = 段地址 x 16 + 偏移地址 的方法合成 20 位的物理地址。

段的概念

  段的划分源自于 8086 的寻址方式,实际上,内存并不会分段。我们把连续的一段内存用段加以描述,从而方便 8086 的寻址。由计算式可知,段的起始地址一定是 16 的倍数,偏移地址为 16 位,16 位的寻址能力为 64K,则一个段的最大长度为 64K。

段寄存器

  8086 CPU 中,用CS、DS、SS、ES 四个段寄存器来存放内存单元的段地址。CS 和 IP 是 8086 CPU 中两个关键的寄存器,他指出了 CPU当前要读取的指令地址。CS 称为代码段寄存器,IP 称为指令指针寄存器 。任意时刻,8086 CPU 将 CS: IP 指向的内容当做指令执行
  8086 CPU 工作过程:

  1. 从CS:IP指向的内存单元中读取指令,读取的指令进入指令缓冲区
  2. IP = IP+指令长度,从而指向像一条指令
  3. 执行指令,转到(1)循环

修改 cs 和 ip 的值

jmp 指令
格式: jmp 段地址:偏移地址 ;执行后, cs = 段地址, IP = 偏移地址
例如: jmp 2AE3H:3 ;执行后,CS =2AE3 , IP = 3
格式: jmp 寄存器 ;执行后 , ip = 寄存器的值
例如: jmp ax ;若执行前,ax = 1000H cs = 2000H ip = 0003H ;则执行后, ax = 1000H cs = 2000H ip = 1000H

注意:mov指令不能修改CS和IP的值

实验一 debug的使用

注意在 Debug 中,数据都是用十六进制表示,且不用加 H

  1. 用 R 命令查看、修改寄存器的内容

    • 显示所有寄存器和标志位状态
    • 显示当前 CS:IP指向的指令。


2. 用 D 命令查看内存内容

  1. debug 默认列出 128 个字节单元的内容
  2. 若指定的地址不是 16 的倍数(如 d 1000 : 9 ),仍会显示128字节内容( 从1000 : 9到1000 : 88 )
  3. debug列出了三部分内容: 最左边是每行的起始地址 中间是内容的16进制 最右边是对应的ASCII ( 没有对应时用 . 表示)
  • 直接输入d,查看当前 cs:ip 指向的内存内容,注意:如果继续输入d,则可继续查看后面的内存的内容
  • 输入d 段地址 : 偏移地址,查看指定的内存地址的内容。如果继续输入d,则可继续查看后面的内存的内容
  • 输入d 段地址: 偏移地址 结束地址,查看指定地址内存内容。如果继续输入d,则可继续查看后面的内存的内容
  1. 用E命令改写内存中的内容
  • 输入 e 段地址: 偏移地址 数据1 数据2 数据3 …,修改指定地址内存中的内容
  • 输入 e段地址: 偏移地址,可以逐个字节进行修改,注意:不修改直接输空格,输完数后,按空格输入下一个,回车直接结束

    • 使用 e 命令可以输入字符(单引号标识)或字符串(双引号标识),都是存储的ASCII
    • 用 e 命令向内存中写入机器码,用U命令查看内存中机器码的含义,用T命令执行内存中的机器码
  1. 用 A 命令在内存中输入汇编指令
  • 直接输入a,在当前内存(CS:ip指向的内存)中输入汇编指令
  • 输入a 段地址: 偏移地址,在指定的内存中输入汇编指令
  • 输入 a 偏移地址,向 cs:偏移地址 指向的内存中的写入汇编指令
  1. 用 u 命令查看内存中机器码对应的汇编指令

    • 直接输入u,查看当前内存(CS:ip指向的内存)中机器码对应的汇编指令
    • 输入u 段地址: 偏移地址,查看指定的内存中的机器码对应的汇编指令
  2. 使用T命令执行内存中的汇编代码

    • 直接输入t,执行当前指令
  3. 使用G命令将程序执行到指定地址处

    • 输入g 偏移地址,表示将指令执行到当前偏移地址处
  4. 使用 P 命令可以一次性执行完循环,且int 21H指令必须用P命令执行

    • 当遇到循环时,输入p,即可直接执行完循环
  5. 用 DEBUG 跟踪程序

    • 输入debug 要跟踪的程序全名,debug 将程序加载进内存

      注意:
  6. 加载进内存后,cx中存放的是程序的长度(占用机器码的字节数),上图说明3-1.exe占的机器码是22个字节(十六进制表示为16H)

  7. debug 中,对于最后的 int 21 指令,需要用 p 命令执行

    说明: 当加载进内存后,CS变被赋予SA+10H,IP被赋值0

数据段寄存器DS

mov 指令

mov 寄存器, 立即数                    ; 将数据直接送入寄存器              例:mov ax,2
mov 寄存器, 寄存器                    ; 将一个寄存器中的值送入另一个寄存器中 mov ax,bx
mov 寄存器, 内存单元                  ; 将一个内存单元中的数据送入寄存器      mov ax , [0]
mov 内存单元, 寄存器                  ; 将一个寄存器中的数据送入指定的内存单元  mov [1], bx
mov 段寄存器, 寄存器                  ; 将一个寄存器的值送入段寄存器    mov ds, ax
mov 寄存器, 段寄存器                  ; 将一个段寄存器中的值送入一般寄存器  mov ax, ds
mov 段寄存器, 内存单元

注意:

  1. [ 偏移地址 ] 表示一个内存单元,8086CPU默认使用ds作为数据段的段寄存器
  2. 8086CPU规定,不能直接给段寄存器赋值 例如 mov ds, 2 是错误的
  3. add 和 sub 指令同上

CPU 提供的栈机制

push(进栈)和pop(出栈)都是以字为单位进行的。POP 和 PUSH 指令:

push 寄存器                ;将一个寄存器中的数据入栈
pop 寄存器                 ;用一个寄存器接受出栈的栈顶元素
push 段寄存器                ;将一个段寄存器中的数据入栈
pop 段寄存器                 ;用一个段寄存器接受出栈的栈顶元素
push 内存单元                ;将一个内存字单元处的数据入栈
pop 内存单元                 ;用一个内存字单元接受出栈的栈顶元素

注意:

  1. push 和 pop 指令对内存单元操作时,自动 ds 中读取数据段的段地址
  2. push 和 pop 指令与 mov 指令不同,cpu 执行 push 和 pop 指令需要两步,而执行 mov 指令只需要一步。
  3. push 和 pop 指令只能修改 SP,也就是说,栈顶的最大变化范围是 0~FFFFH

例如: mov ax, 1000H
mov ds, ax ; 存放数据段的段地址
push [0] ; 将1000:0内存字单元中的数据进栈
pop [2] ; 将出栈的数据放到1000:2内存字单元中

  8086 CPU 提供 SS 和 SP 两个寄存器来标识栈。SS 存放栈顶段地址,SP 存放偏移地址。任意时刻,SS:SP 指向栈顶。push 和pop 指令执行时,自动从 SS:SP 指向处取得栈顶地址

  • PUSH 指令的执行过程

    注意:
  1. 入栈时,栈是从高地址向低地址扩展的
  2. 栈空时,SS:SP指向栈底的下一个位置
  • POP指令的执行过程

    注意:出站后,SS:SP 指向新栈顶,pop 执行前的栈顶元素仍然存在(如上图的 2266H),只是它已不再栈中(栈顶已改变),再一次使用 push 指令时,将覆盖原有数据。

8086 CPU不保证对栈的操作不会越界

  • PUSH入栈越界
  • POP出栈越界
    和上图基本相似

编程实例

要求:
(1)将10000H~1000FH作为栈空间,初始状态栈空
(2)设置 ax = 001AH,bx = 001BH
(3)将ax和bx的值入栈
(4)然后将ax和bx清零
(5)最后从栈中恢复ax和bx的值
程序:

mov ax, 1000H
mov ss, ax                      ; 设置栈的段地址,不能直接给段寄存器赋值
mov sp, 0010H                  ; 栈空时,SS:SP指向栈底的下一个位置(000F + 1 = 0010H)注意:栈由高地址向低地址增长
mov ax, 001AH
mov bx, 001BH
push ax
push bx
sub ax, ax                      ; 此处也可以使用 mov ax, 0 ,但是sub ax, ax 的机器码为2个字节,而占mov ax, 0的机器码为三个字节
sub bx, bx                      ; 同上
pop bx                        ; 注意,出栈顺序和进栈顺序相反(先进后出)
pop ax

段的综述

  我们可以将一段内存定义为一个段,用一个段地址指示段,用偏移地址访问段内的单元,这完全是我们自己的安排。

  • 可以用一个段存放数据,将它定义为“数据段”;
  • 可以用一个段存放代码,将它定义为“代码段”;
  • 可以用一个段当做栈,将它定义为”栈段“;

我们可以这样安排,但是若要 CPU 按照这种安排来访问这些段,就要:

  • 对于数据段,将它的段地址存放在DS中,用mov、add、sub等访问内存单元的指令时,CPU就将我们定义的数据段中的内容当作数据来访问。
  • 对于代码段,将它的段地址存放在CS中,将段中第一条指令的偏移地址存放在IP中,这样cpu就将执行我们定义的代码段中的指令。
  • 对于栈段,将它的段地址放在SS中,将栈顶单元的偏移地址存放在SP中,这样cpu在需要进行栈的操作时,如执行push, pop指令等,就将我们定义的栈当作栈空间来用。

  可见,不管我们如何安排,CPU将内存中的某段内容当作代码,是因为CS:IP指向了那里;CPU将某段内存当作栈,是因为SS:SP指向了那里;我们一定要清楚,什么是我们的安排,以及如何让CPU按我们的安排行事。要非常清楚CPU的工作机理,才能在控制CPU按照我们的安排运行的时候做到游刃有余。
  一段内存,可以既是代码段的存储空间,又是数据的存储空间,还可以是栈空间,也可以什么都不是。关键在于CPU中寄存器的设置,即 CS、IP,SS、DS 的指向。

段前缀

  在汇编程序中,可以显示给出段地址,这些显示的段地址称为段前缀。例如:ds:[bx] 、ds: [0]、 ss: sp 、cs: sp 、cs: ip 等。显示给出段前缀时,将使用给出的寄存器作为段地址,而不是使用默认段寄存器

第一个汇编程序

基本格式(包含多个段):

assume cs: code, ds: data, ss: stack                                       ; 伪指令,将寄存器和各段联系起来
data segment                         ; 数据段                        ; 伪指令,格式:段名  segment,表示一个段的开始,段名表示一个地址,被编译时翻译成地址dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
data ends                                                            ; 伪指令,和他上面的段名  segment成对存在,格式:段名 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, axpush ds: [0] push ds: [2] pop ds: [2] pop ds: [0] mov ax,4c00h int 21h                                ; 这两条语句为一组,表示程序的返回
code ends
end start                                   ; 伪指令,end标志着一个汇编程序的结束,编译器遇到end就结束对程序的编译,同时指出了程序的入口为start处

注意:在多个段中,各段空间相互独立,地址都是从 0 到段大小。 例如上例:数据段空间 0 ~ 15(字节空间),栈空间 15 ~ 0(字节空间),代码段从 start 开始

基本格式(只有一个段)

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, codesg                ;或mov ax, cs mov ss, ax mov sp, 24h                         ;上三条指令设置栈顶指针,使指向了codesg:24H    ;或mov sp, 36  ; 36是十进制对应的16进制是24Hmov ax,0 mov ds, ax mov bx,0         ; 这三条指令设置数据指针,使其指向了0: 0,即本段数据的开始处mov cx,8        ; 循环次数s:   push  [bx]      ; 将0123H入栈,默认数据段寄存器ds,此处是 ds: [bx]。注意:push和pop指令一次操作一个字(两个字节)pop cs: [bx]     ; 或 pop ss: [bx] add bx, 2        ; 注意:push和pop指令一次操作一个字(两个字节)loop s mov ax,4c00h int 21h
codesg ends
end start

注意:只有一个段时,各种代码公用一段空间。例如上例:数据从 dodesg 开始占(0 ~ 15),栈则跟在后面,从 16~31 ,start 处的指令实际是从 32 开始

[bx]和 loop 指令

[bx] 使用:

mov ax, [bx]       ; 其中,[bx]表示内存单元,段地址默认ds中,该指令表示将ds:[bx]字单元的内容送入寄存器ax
mov [bx], ax       ; 其中,[bx]表示内存单元,段地址默认ds中,该指令表示将寄存器ax的内容送入ds:[bx]字单元

例:指令执行后的内存情况

loop指令:

mov  cx, 循环次数
标示符:要循环的指令
loop  标示符                               ; CPU执行过程:(1)cx = cx-1 (2)判断cx中的值,不为零则转至标号处,为零时继续往下执行

例:用 loop 指令计算 211

assume cs:code
code segment
strat:mov ax, 2mov cx, 11s: add ax, axloop s
mov ax, 4c00H
int 21H
code ends
end start

DEBUG与汇编编译器对指令的不同处理

  1. 在 debug 中,mov ax, [0] 表示将 ds:[0] 内存单元中的数据送入 ax 中,而在汇编编译器中表示 mov ax, 0。因此,汇编中使用 mov bx, 0 mov ax,[bx] 来实现。或显示指出段地址 mov ax, ds:[0] 实现。
  2. Debug中,所有数据都是16进制的,汇编编译器中则不是

实例:计算 ffff:0~ffff:b 中的数据之和,结果存在 dx 中

分析:

  1. 结果是否会超过 dx 的容量:12 个字节型数据相加不会大于 65535,可以在dx中存放
  2. 是否将其中数据直接累加到 dx 中:当然不行,因为数据是 8 位的,dx 是 16 位的,类型不匹配
  3. 是否将其中数据直接累加到 dl 中:当然也不行,dl最大为 255,可能会超出存储范围

解决方法:用一个 16 位寄存器做中介,先将数据存入ax中,用 ax 和 dx 做加法,加过存在 dx 中。程序:

assume cs:code
code segment
start:
mov ax, 0ffffH                       ; 注意,汇编中数据不能以字母开头,因此要在前面加0
mov ds, ax
mov bx, 0                           ; 初始化,使ds: bx指向ffff: 0
mov dx, 0                           ; 初始化累加器 dx = 0
mov cx, 12                          ; 循环次数
s:
mov ah, 0
mov al, ds: [bx]                      ; ax作为中间寄存器
add dx, ax
inc bxloop smov ax, 4c00H
int 21H
code ends
end start

更灵活的内存定位方法

and 指令: 按位与运算,通过该指令可以将操作对象的相应位设为0,其他位不变
例如:

mov al, 01100011B
and al, 00111011B

or 指令: 按位或运算,通过该指令可以 操作对象的相应位设为1,其他位不变

mov al, 01100011B
or  al, 00111011B

在汇编中,我们可以使用 英文单引号(’’) 来指明数据是字符。例如:db ’asm‘ ,编译器将他们转换为对应的ASCII码。

大小写转换问题

就 ASCII 码的二进制来看,除第五位外,大小写字母的其他位都相同。大写字母的第五位是 0,而小写的第五位是 1。

[bx+idata] 的寻址方式

SI 和 DI 两个寄存器,功能和 BX 相近,但 SI 和 DI 不能被分成两个 8 位寄存器使用

  • 实例分析:用 SI 和DI 将字符串 ’Welcome to masm!’ 复制到他后面的内存空间中。
assume cs: code, ds: data
data segment
db ‘Welcome to masm’
db ‘. . . . . . . . . . . . . . . .
data ends
code segment
start:
mov ax, data
mov ds, ax
mov si, 0
mov di, 16
mov cx, 8                   ; 循环8次
s:   mov ax, [si]                 ; 一次传送两个字节(16位寄存器)
mov [di], ax
add si, 2                    ; 每次两个字节
add di, 2
loop smov ax, 4c00H
int 21h
code ends
end start
  • 实例分析:编程,将datasg段中每个单词的前4个字母改为大写字母。
assume cs: codesg, ds: datasg, ss: stacksg
datasg segmentdb  '1. display       '      ; 16 Byte             ; 注意, 空格也是字符。每个字符串占16个字节db  '2. brows        '     ; 16 Bytedb  '3. replace       '     ; 16 Bytedb  '4. modify       '      ; 16 Byte
datasg ends
stacksg segmentdw 0,0,0,0,0,0,0,0                           ; 对于临时数据,我们一般用栈来存放
stacksg ends
codesg segment
start:   mov ax, datasgmov ds, axmov bx, 0                 ; ds:bx指向数据段开始,即指向第一组数据mov ax,stacksgmov ss,axmov sp,16                 ; ss:sp 指向栈底的下一位置mov cx,4                  ; 循环次数s0:  push cx                   ; 将cx的值放到上面的栈中mov si,0mov cx,4s:  mov al, [bx+3][si]           ; 注意,每个字符串中1 和 . 和空格 占三个字节,所以是[bx+si+3], 注意书写形式的区别and al,11011111b           ; 使用and指令将小写ASCII码二进制的第五位由1置为0,即由小写变大写mov [bx+3][si],al            ; 将转换后的字符放回原位置inc siloop sadd bx,16                  ; 指向下一组字符串pop cx                     ; 重置循环次数,用于第二组字符串中loop s0mov ax, 4c00hint 21h
codesg ends
end start

数据处理的两个基本问题

  1. 问题一:指令所处理数据的位置
      8086 CPU中,只有 si、di、bx、bp 四个寄存器可以在 [ ] 中使用。四个寄存器可以单独使用,也可以以以下组合出现:bx 和 si、bx 和 di、bp 和 si、bp 和 di。下面的指令都是正确的:
mov ax, [bx]
mov ax, [si]
mov ax, [di]
mov ax, [bx+si]
mov ax, [bx+di]
mov ax, [bp+si]
mov ax, [bp+di]
mov ax, [bx+si+idata]         ; 默认的段寄存器是SS
mov ax, [bx+di+idata]        ; 默认的段寄存器是SS
mov ax, [bp+si+idata]        ; 默认的段寄存器是SS
mov ax, [bp+di+idata]        ; 默认的段寄存器是SS

注意:

  1. bx 和 bp 不能搭配,si 和 di 也不能搭配
  2. 只要使用了 bp,而没有显示给出段寄存器的,默认段寄存器是 ss

8086 CPU 寻址方式:

  1. 问题二:指令要处理的数据有多长
    1. 通过寄存器指明处理数据的长度。在指令所使用的寄存器是多长,数据就是多长。例如: mov ax, [0] ax为16位的,所以处理的是两个字节的内容,即偏移地址[0]和[1]两个字节
    2. 在没有寄存器名存在的情况下,用操作符 X ptr 指明操作数的长度,X 可以是 Byte 和 Word。例如:

    mov word ptr ds:[0], 1              ; 操作字单元
    inc word ptr [bx]                  ; 操作字单元
    mov byte ptr ds:[0], 1              ; 操作字节单元
    inc byte ptr [bx]                  ; 操作字节单元
    
  2. 某些指令默认长度,如 pop 和 push 指令默认是对字单元操作

    • div 除法指令。格式: div 除数

      div 寄存器
      div 内存单元
      

      div 使用默认寄存器

      被除数位数 被除数默认存放的寄存器 余数
      16位 ax al ah
      32位 (高位)dx + ax(低位) ax dx

      只能出现以上两种组合对应 ,不足时要不足位数。例如:

      div byte ptr ds:[0]           ; al = ax / (ds*16+0) 的商; ah = ax / (ds*16+0) 的余数
      div word ptr es:[0]          ; ax = (dx*10000H+ax) / (es*16+0)的商; dx = (dx*10000H+ax) / (es*16+0)的余数
      

      实例:编程计算100001/100
      分析:100001>65535,所以不能用ax存放,只能用dx和ax存放,被除数是32位的,因此除数必须是16位的(尽管100<255)
      程序:

      assume cs: code
      code segment
      start:mov dx, 1mov ax, 86A1H       ; 注意:100001转换为十六进制为186A1H,高位1给dx,低位86A1H给axmov bx, 100div bx
      code ends
      end start
      
    1. dd 伪指令: 用来定义double word(双字)
    2. dup 操作符: 用来进行数据的重复.例如: db 3 dup(0) ; 定义了三个字节,初值都是0。格式:db/dw/dd 重复次数 dup (重复的数据)

实验7 寻址方式在结构化数据访问中的应用

assume cs: code, ds: data, es: tabledata segment
db '1975','1976','1977','1978','1979','1980','1981','1982','1983'
db '1984','1985','1986','1987','1988','1989','1990','1991','1992'
db '1993','1994','1995'
;以上是表示21年的字符串 4 * 21 = 84dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
;以上是表示21年每年公司总收入的dword型数据 4 * 21 = 84dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
dw 11542,14430,15257,17800
;以上是表示21年每年公司雇员人数的21个word型数据 2 * 21 = 42
data endstable segment
db 21 dup ('year summ ne ?? ')     ; 'year summ ne ?? ' 刚好16个字节
table endscode segment
start:
mov ax, data
mov ds, ax
mov ax, table
mov es, ax
mov bx,0                ;ds:[bx]在data中数据定位(和idata结合,用于年份和收入)
mov si,0                ;es:[si]在table中定位(和idata给合用于定位存放数据的相对位置)
mov di,0                ;ds:[di]在data中用于得到员工数
mov cx,21               ;cx循环次数
s:;将年从data 到 table 分为高16位和低16位mov ax, [bx]mov es:[si], ax           ; 高16位mov ax, [bx+2]mov es:[si+2], ax        ; 低16位;table 增加空格mov byte ptr es:[si+4],20h               ; 0~3的四个字节是年份,第4个字节是空格;将雇员数从data 到 tablemov ax, [di + 168]mov es:[si + 10], ax;table 增加空格mov byte ptr es:[si+12],20h             ; 10~11的四个字节是雇员数,第12个字节是空格;将收入从data 到 table 分为高16位和低16位mov ax, [bx+84]mov es:[si+5], ax         ; 高16位mov dx, [bx+86]mov es:[si+7], dx         ; 低16位;table 增加空格mov byte ptr es:[si+ 9],20h              ; 5~8的四个字节是收入,第9个字节是空格;计算工资;取ds处工资,32位;mov ax,[bx + 84]
;mov dx,[bx + 86];计算人均收入, 注意:上面在将收入放到table中时刚好将数据放到了dx和ax中,因此不用再重新设置被除数div word ptr ds:[di + 168]             ; ax = (dx*10000H+ax)/ ds:[di + 168]的商mov es:[si+13],ax                       ;将结果存入table处;table 增加空格mov byte ptr es:[si + 0fh],20h        ; 13~14的四个字节是人均收入,第15个字节是空格(15的十六进制是f);改变三个寄存器值add si,16                               ; table的下一行add di,2add bx,4loop smov ax,4c00h
int 21h
code ends
end start

转移指令的原理

可以修改 IP 或同时修改 cs 和 ip 的指令称为转移指令。

  1. 依据位移进行转移的 call 指令 ---- 机器码中包含转移的位移,不包含目的地址。
    格式:call 标号 ; 段内近转移
    操作:

    1. SP = SP – 2
      SS X 16 + SP = IP ; ip进栈
    2. IP = IP + 16位位移 ;跳转

    注意:

    1. 16位位移 = “标号”处的地址 – call 指令后第一个字节的地址
    2. 16位位移的范围是:-32768 ~ 32767,用补码表示
    3. 16位位移由编译程序在编译时计算出。

    相当于:push ip + jmp near ptr 标号

    实例:下面的程序执行后,ax中的数值为多少?

     内存地址    机器码      汇编指令     执行后情况1000:0     b8 00 00     mov ax,0     ax=0 ip指向1000:31000:3     e8 01 00     call s       注意此处,CPU先将call s 读到指令缓冲区中,使得ip增加,实际进栈的IP为call指令之后第一个地址。此处是6进栈1000:6     40           inc ax1000:7     58         s:pop ax       ax=6
    
  2. 转移的目的地址在指令中的 call 指令
    格式:call far ptr 标号 ; 段间转移
    操作:

    1. SP = SP – 2
      SS X 16 + SP = CS ; cs进栈
      SP = SP – 2
      SS X 16 + SP = IP ; ip进栈
    2. CS = 标号所在段的段地址
      IP = 标号相对于所在段的偏移地址 ;完成跳转

    相当于:push cs + push ip + jmp far ptr 标号

    实例:下面的程序执行后,ax中的数值为多少?

    内存地址   机器码           汇编指令            执行后情况
    1000:0    b8 00 00          mov ax,0           ax=0,ip指向1000:3
    1000:3    9a 09 00 00 10    call far ptr s     注意此处,CPU先将call far ptr s 读到指令缓冲区中,使得ip增加,实际进栈的IP为call指令之后第一个地址。此处是cs = 1000先进栈,然后ip = 8进栈
    1000:8    40                inc ax
    1000:9    58                s:pop ax           ax=8h,从上面的执行可看出add ax,ax          ax=10h pop bx             bx=1000h add ax,bx          ax=1010h
    
  3. 转移地址在寄存器中的call指令
    格式:call 16位寄存器
    操作:

    1. SP = SP – 2
      SS X 16 + SP = IP ; ip进栈
    2. IP = 16位寄存器的值 ; 跳转

    相当于:push ip + jmp 16位寄存器

    实例:下面的程序执行后,ax中的数值为多少?

    内存地址   机器码        汇编指令       执行后情况
    1000:0     b8 06 00       mov ax,6       ax=6,ip指向1000:3
    1000:3     ff d0          call ax        此处同上,进栈的仍是call指令后第一个字节的地址5
    1000:5     40            inc ax
    1000:6     58            mov bp,sp      bp=sp=fffehadd ax,[bp]    ax=[6+ds:(fffeh)]=6+5=0bh
    
  4. 转移地址在内存中的call指令
    格式 1:call word ptr 内存单元 ; 段内近转移
    功能:ip = 该字型内存单元的值(2个字节)
    操作:

    1. SP = SP – 2
      SS X 16 + SP = IP ; ip进栈
    2. IP = 内存单元的值 ; 跳转

    相当于:push IP + jmp word ptr 内存单元
    例如:

    mov sp, 10H
    mov ax, 0123H
    mov ds:[0], ax
    call word ptr ds:[0]        ; 执行后 ip
    

    格式 2:call dword ptr 内存单元 ; 段间转移
    功能:cs = 该内存单元+2(高16位) ip = 该内存单元(低16位)
    操作:

    1. SP = SP – 2
      SS X 16 + SP = CS ; cs进栈
      SP = SP – 2
      SS X 16 + SP = IP ; ip进栈
    2. cs = 该内存单元+2(高16位)
      ip = 该内存单元(低16位) ;完成跳转

    相当于: push cs + push IP + jmp word ptr 内存单元
    实例:下面的程序执行后,ax和bx中的数值为多少?

    assume cs: codesg
    stack segmentdw 8 dup(0)stack endscodesg segmentstart:mov ax, stack                ;占3字节mov ss, ax                  ;占2字节mov sp, 10h                ;占3字节mov word ptr ss:[0],offset s    ; 占7字节,(ss:[0])=1ahmov ss:[2],cs                ; 占5字节,(ss:[2])=cscall dword ptr ss:[0]          ; 占5字节,cs入栈,ip=19h(十进制是25)入栈(此时的IP是call指令后第一个字节的地址),ip = ss:[0] = 1aH转到cs:1ah处执行指令;(ss:[4])=cs,(ss:[6])=ipnops:   mov ax, offset s              ; ax = 1ah  (十进制是26)sub ax, ss:[0ch]               ; ax = 1ah-(ss:[0ch]) = 1ah - 19h=1   0cH对应的十进制是12,栈地址为0~15, 12和13字节存放的是call压栈的ip = 19Hmov bx, cs                   ; bx = cs=0c5bhsub bx, ss:[0eh]               ;bx=cs-cs=0                     0eH对应的十进制是14,栈地址为0~15, 14和15字节存放的是call压栈的cs 的值mov ax,4c00hint 21hcodesg endsend start
    
    利用 call 和 ret 来实现子程序的机制

    格式:

                  ……code segmentmain:……call sub1                           ; call指令将其后第一个字节地址压栈后,跳转……mov ax, 4c00Hint 21Hsub1:子程序用到的寄存器入栈            ; 主要是为了防止子程序用的寄存器和主程序冲突…call sub2…子程序用到的寄存器出栈
    ret                                ; ret指令恢复之前call压栈的值,注意:此处要保证子程序中没有修改栈中的数据,否则将不能返回sub2:子程序用到的寄存器入栈
    ……子程序用到的寄存器出栈
    Retcode ends
    end main
    
  • mul 乘法指令
    格式:div 寄存器div 内存单元
    mul 使用默认寄存器

    只能出现以上两种组合。例如:

    mul byte ptr ds:[0]           ; ax = ah * (ds*16+0) 的积
    mul word ptr es:[0]          ; ax = ax * (es*16+0)的低8位; dx = ax * (es*16+0)的高8位
    

参数和结果传递问题

  1. 对于少量参数和返回值----------可以使用寄存器来存储参数和返回值
    实例:设计一个子程序,计算data段中第一组数的3次方,保存在后面的一组dword单元中
    程序:

       assume cs: code, ds: datadata segmentdw 1, 2, 3, 4, 5, 6, 7, 8dd 0, 0, 0, 0, 0, 0, 0, 0data endscode segmentstart:mov ax, datamov ds, axmov si, 0         ; ds:[si]读取dw数据mov di, 0         ; ds:[di]将数据保存到dd数据中mov cx, 8s:mov bx, [si]      ; 用bx传递参数call cubemov ds:[di], ax         ; ax是低位,注意dd是双字(占四个字节)mov ds:[di+2], dx       ; 高位add si, 2add di, 4loop smov ax, 4c00hint 21h
    ; 说明:计算n的3次方
    ; 参数:bx = n
    ; 返回值:dx = 结果高位
    ;         ax = 结果低位
    cube:mov ax, bx       ; 注意给出的数据时16位的mul bx          ; ax中数* bx中数,结果的高位自动放入dx,mul bxret
    code ends
    end start
    
  2. 批量数据传递--------------将数据放到内存中,而将数据地址给寄存器,传个子程序
    实例:设计一个子程序,计算data段中中的字符串转化为大写
    程序:

       assume cs: code, ds: datadata segmentdb ’convensation’data endscode segmentstart:mov ax, datamov ds, axmov si, 0           ; ds:[si]z=指向字符串mov cx,12call touppermov ax, 4c00hint 21htoupper:and byte ptr ds:[si], 11011111Binc siloop toupperretcode endsend start
    
  3. 批量数据传递--------------用堆栈来存放参数和返回值

寄存器冲突问题

解决方法:在程序的开始将所用的寄存器中的内容保存起来,子程序返回前在恢复,可以用栈保存寄存器中的数据

未完待续…

王爽 16 位汇编语言学习记录相关推荐

  1. 16位汇编语言学习笔记(1)——基础知识

    文章目录 1.配置汇编学习环境 1.1 工具下载 1.2 配置环境 2. 汇编命令基础 2.1 简单使用 2.2 常用命令 3. 汇编语言基础 3.1 汇编语言程序与汇编程序 3.2 汇编语言程序的格 ...

  2. 16位汇编语言学习笔记(2)—— 汇编程序设计

    文章目录 4. 顺序程序设计 4.1 十进制的算数运算 4.2 输入输出功能调用 4.3 综合案例 5. 分支程序设计 5.1 转移指令 5.1.1 条件转移指令 单标志条件转移指令 无符号数专用条件 ...

  3. 32位汇编语言学习笔记(45)--测试简单文件操作接口(完)

     这是<Assembly Language step by step programming with linux>书中的最后一个程序,也是全书中的最复杂的一个程序. 首先看一下这个程 ...

  4. windows下32位汇编语言学习笔记

    windows下32位汇编语言学习笔记 第一章  第一章 背景知识 80x86处理器的存储器 4个数据寄存器 EAX,EBX,ECX,EDX EAX寄存器 所有API函数的返回值都保存在EAX里,注意 ...

  5. 16位汇编语言第二讲系统调用原理,以及各个寄存器详解

    16位汇编语言第二讲系统调用原理,以及各个寄存器详解 昨天已将简单的写了一下汇编代码,并且执行了第一个显示到屏幕的helloworld 问题? helloworld怎么显示出来了. 一丶显卡,显存的概 ...

  6. 王爽之《汇编语言》学习重点三

    2.5  16位结构的CPU 我们说8086CPU的上一代CPU(8080.8085)等是8位机,而8086是16位机,也可以说8086是16位结构的CPU.那么什么是16位结构的CPU呢? 概括地讲 ...

  7. 王爽之《汇编语言》学习重点二

    第2章  寄存器(CPU 工作原理) 一个典型的CPU(此处讨论的不是某一具体的CPU)由运算器.控制器.寄存器(CPU工作原理)等器件构成,这些器件靠内部总线相连.前一章所说的总线,相对于CPU内部 ...

  8. 解决 王爽写的汇编语言的第七个验七- 寻址方式在结构化数据访问中的应用

    原文: 汇编语言-(第三版) 王爽-著 (实验七) 寻址方式在结构化数据访问中的应用 https://www.52pojie.cn/thread-1241289-1-1.html (出处: 吾爱破解论 ...

  9. 手把手教你在64位Win7下部署16位汇编学习环境

    实现方式是VirtualBox虚拟机+精简的32位xp系统.指导小白用,高手就直接跳过吧. 一.背景 初学者学习汇编语言通常是从16位汇编开始,但是现在的64位Win7系统明确表示不支持16位的程序. ...

最新文章

  1. 20155222卢梓杰 实验三 免杀原理与实践
  2. php insert into values 可以是数组吗,PHP INSERT INTO插入不了数据有关问题
  3. linux的阻塞waitqueue,Linux阻塞控制 wait_event与wait_event_interruptible函数详解
  4. 转:我是如何向老婆解释MapReduce的?
  5. idea中连接mysql插入成功数据 在navicat中刷新表格没有数据_第九篇 数据分析的进阶学习-SQL入门...
  6. LeetCode MySQL 601. 体育馆的人流量(row_number+over+cast)
  7. 作者:丁伟(1972-),男,博士,中国联合网络通信有限公司网络技术研究院高级工程师。...
  8. oracle ro,ORACLE学习笔记一
  9. iis+php解析漏洞修复,服务器解析漏洞分析和漏洞修复方法
  10. Github 下载单个文件
  11. 为什么每次进入命令都要重新source /etc/profile 才能生效?
  12. BZOJ1014 [JSOI2008]火星人
  13. centos安装7zip
  14. 计算机辅助绘图包括,计算机辅助绘图实用教程
  15. windows10定时关机如何设置
  16. Sqlserver2000服务器安装配置
  17. 对图像高通滤波matlab,高通巴特沃斯滤波器在MATLAB中对图像进行滤波
  18. 120帧手机动态壁纸_星空陨石动态壁纸手机版下载-星空陨石动态壁纸app安卓版下载v1.7最新版...
  19. 交换机路由器端口配置
  20. 工具 svn 介绍和简单用法

热门文章

  1. Gitlab自动触发Jenkins构建打包
  2. 【工具使用系列】关于 MATLAB 电路与系统分析,你需要知道的事
  3. PowerDesigner概念模型详解
  4. HtmlAgilityPack中SelectSingleNode的XPath和CSS选择器
  5. 品牌到底要不要做全渠道?且听他们怎么说……
  6. zookeeper在Dubbo中的作用
  7. HDU 1828 Picture 线段树 扫描线
  8. moss 2007 单点登录的配置
  9. linux常用命令(2)常用系统工作命令
  10. synchronized(xxx.class)