汇编语言(王爽)第三版-读书笔记

Central Processing Unit 中央处理单元 简称CPU

汇编语言的组成
  • 汇编指令:机器码助记符 有对应机器码
  • 伪指令:没有对应机器码 由编译器执行 计算机不执行
  • 其他符号:如+ - * / 等 又编译器识别 计算机不执行

  • 存储器 = 128存储单元 = 128Byte

  • Byte = 8bit =一个内存单元
  • 1bit = 1个二进制位 一个bit只能表示一个开关量,例如 l代表“开关闭合”,0代表“开关断开”。
Cpu对存储器的读写
  • 存储单元信息(地址信息)
  • 器件选择 读或写的命令(控制信息)
  • 读或写的数据(数据信息)
CPU与其他存储芯片的导线-总线
  • 地址总线 发出地址信息
  • 控制总线 发出内存读命令 选中存储芯片 通知他从中读取数据
  • 数据总线 将读取到的数据送入CPU

  • 地址总线 一根线表示一位 N根表示地址总线宽度为N,寻址范围为0-2^N。

  • 数据总线 总线宽度决定了传输的速度,8根表示一个1Byte,16根表示2Byte.
  • 控制总线 总线宽度决定了CUP对外部器件的控制能力
存储器
  • 随机存储器
  • 只读存储器
  • BOIS(Basic Input?output System,基本输入/输出系统)
  • 接口卡上的RAM
内存地址空间 书中11页 作者对其作出了很好的解释

CUP通过总线于各个物理存储器相连,将他们看作一个个存储器单元,他们分别占据一个地址段,CUP在这段地址空间读写数据 实际上就是在相应的物理存储器上读写数据。而汇编语言就是面对的内存地址空间。

寄存器

CPU内部
- 运算器:进行信息计算
- 寄存器:进行信息存储
- 控制器:控制各器件进行工作
- 内部总线:连接各个器件 在他们工作时进行数据传递

通用寄存器 兼容8位寄存器
  • AX : 分AH和AL 书中16页介绍了 AX 20000(4E20) AH 78(4E) AL 32(20)
  • BX : 分BH和BL
  • CX : 分CH和CL
  • DX : 分DH和DL
  • ADD AX ,BX AX为16位 最多存4位16进制 最高位去掉 即0000H
  • ADD AL ,BL AL为8位 最多存2位16进制 最高位去掉 00H
注意:
  • MOV AX,BL
  • 8位寄存器与16位寄存器之间传递数据
  • MOV BH,AX
  • 16位寄存器与8位寄存器之间传递数据
  • MOV AL,2000
  • 8位最多存储255的数值
  • MOV AL,100H
  • 不要将一个高于8位的数加到8位寄存器
物理地址

内存单元位==线性空间== 拥有唯一地址 即为物理地址

16位寄存器
  • 运算器一次处理16位数据
  • 寄存器最大宽度16位
  • 寄存器和运算器通路位16位
8086CPU的物理地址
  • 提供两个16位地址,段地址 偏移地址
  • 通过内部总线送入地址假发器
  • 地址加法器合成为一个20位物理地址
  • 地址加法器送入BIOS
  • BOIS将20位物理地址送入地址总线
  • 地址总线送到储存器

物理地址 = 段地址 * 16 + 偏移地址

下图对物理地址左移做了很详细的总结

物理地址的计算

那么 16位地址 * 16 其实是乘10H,也可以说是16位地址转成10进制成16。最简单的方式就是16位地址左移1位

本质:基础地址+偏移地址  = 物理地址
      基础地址 = 段地址 * 16

书中22对其本质做出了生动的解释

段的概念 书中24页
  • 段地址 大小一定为16的倍数
  • 偏移地址最大为16位 则最大寻址能力为64kb
段寄存器 书中25页
  • 8086CPU提供四个段寄存器CS DS SS ES
  • CS 代码段寄存器
  • IP 指令指针寄存器
书中28页将CS配合IP寻址 查询 的具体图解

当进入指令缓冲器 即为读取一条指令 IP 值自动增加并读取下一条指令根据第一条指令的大小增加值

复位或者加电启动 CS=FFFFH IP=0000H FFFF0H即为8086PC启动执行的第一条指令

转移指令 修改CS IP 的值
  • jmp段地址:偏移地址
  • jmp 3:0B16 CS= 0003H IP= 0B16H 即从00B46H读取指令
  • jmp 2AE3:3 CS= 2AE3 IP= 0003H 即从2AE33H读取指令
  • jmp bx 等同于 mov IP,ax CS不变 BX赋值给IP
书中34对jmp做了图文并茂的解释

jmp 1000:3 就是跳转到1000*16+0003H = 10003H处读取指令。

实验一:CPU 的debug使用

先找个引路篇-安转篇https://blog.csdn.net/try2find/article/details/39619999

以及debug的命令行使用 https://blog.csdn.net/liuer2004_82/article/details/52638516#t2

第三章

内存中字的存储
  • 字单元:存放一个字型数据(16位)的内存单元,两个连续的内存单元
  • 高地址存字型数据的高位字节 低地址存字型数据的地位字节
  • 地址单元和地址子单元:每个地址单元包含一个高位字节单元和一个低位字节单元
DS和[address]书中48页
  • mov al,[0] 表示: mov 寄存器名,内存单元地址
  • […] 表示内存单元 […]中的0表示内存单元的偏移地址。
  • mov ds,bx ds会被默认取值 作为段地址 所以在调用mov al,[0]前必须给值ds.
  • mov ds,1000H 为非法操作 数据不能直接送入段寄存器 即ds 需要先mov bx,1000H
字的传送

书中51页对字的传送做了图文并茂的介绍 表3.2

mov add sub
  • mov 寄存器,数据 mov ax,8
  • mov 寄存器,寄存器 mov ax,bx
  • mov 寄存器,内存单元 mov ax,[0]
  • mov 内存单元,寄存器 mov [],ax
  • mov 段寄存器,寄存器 mov ds,ax
书中52验证了mov [0] cs

我学会debug指令 r,t,d查看 a修改

image

数据段

数据段:将一组长度为N(N<=64KB),地址连续,起始地址为16的倍数的内存单元当做专门存储数据的内存空间,即为数据段。

各寄存器初始值:CS = 2000H,IP = 0,DS = 1000H,AX = 0,BX = 0;

栈 后进先出
CPU 栈机制
  • 如何确定栈空间?
  • 如何确定栈顶?
  • 段寄存器SS 存放栈顶栈地址
  • 寄存器SP 存放偏移地址
  • 任意时刻 SS:SP指向栈顶 SS 为段寄存器 SP为 偏移地址
栈超界

栈满push 栈空pop都会造成栈超界问题
8086CPU并没有给出解决方法 需自己注意

push 和 pop 指令 书中64页
//基本操作
mov ax,1000H
mov ss,ax   ;设置栈的段地址 值必须由ax传递
push [0]    ;将1000:0处的字压入栈中 
pop [2]     ;出栈 出栈的数据送入1000:2中
书中65页问题3.8将栈操作写的很完善

push时:CPU操作:先改变SP:SP-2 后向SS:SP处传送

pop时: CPU操作:先读取SS:SP处的数据,后改变SP :SP+2

栈段
  • DS 内存区间 段地址
  • CS 代码段 段地址
  • SS 栈 段地址
-r ds
:1000
-d ds:0 ;查看1000:0开始的内存区间 128字节

-r ds 
:1000
-d ds:10 18 ;查看10000~1000:18 中的内容

-d cs:0     ;查看当前代码段中的指令代码

-d ss:0     ;查看当前栈中的内容

E 直接修改内存
U 显示代码 以汇编的形式
A 汇编指令修改内存

-r ds 
:1000
-e ds:0 11 12 33 11 44 ;在从1000:0开始的内存区间写入数据

-u cs:0  ;以汇编指令的形式,显示代码段中的代码,0是段地址

-r ds 
:1000
-a ds:0     ;以汇编指令的形式,向1000:0开始的内存单元写入指令
代码练习 书中78页
assume cs:codesg;‘假设’编译程序将段寄存器和某个具体的段相关联
codesg segment  ;与codesg ends 成对使用 定义一个代码段,名称为‘codesg’
        mov ax,0123H
        mov bx,0456H
        add ax,bx
        add ax,ax

        mov ax,4c00H
        int 21H
codesg ends

end ;汇编程序结束的标志
段结束 程序结束 程序返回
目的 相关指令 指令性质 指令执行者
通知编译器一个段结束 段名 ends 伪指令 编译时 编译器
通知编译器程序结束 end 伪指令 编译时 编译器
程序返回 mov ax,4c00H int 21H 汇编指令 编译时 由CPU行
编译asm文件过程 书中 第84页

首先需要一套工具 这边提供下载链接 https://blog.csdn.net/wybliw/article/details/53259602

设置完环境后打开dosbox 输入Masm 输入文件路径 一路enter会得到 .OBJ后缀文件 而实际上会得到三个文件

  • 目标文件 obj
  • 列表文件 lst
  • 交叉引用文件 crf
连接 obj -> exe

输入Link 然后输入文件名 一路enter 生成 exe

简化编译连接 注意加上 ‘;’。
  • masm newone.asm;
  • link newone.obj;
cmd命令行是怎么执行程序的 书中90页

cmd命令行是怎么运行的

关于DOSBOX的快捷键操作

https://blog.csdn.net/lovefengruoqing/article/details/79009456

DEBUG追踪exe执行过程
  • debug newone.exe 可跟踪执行
  • CX 指定机器码字节数
  • PSP 为DOS创建程序前缀 与被加载程序通信 256字节
  • 可用段地址为 PSP 段地址+偏移地址为 SA+10H:0
  • U命令查看汇编命令
  • int 21H 使用 P命令结束

第五章 [BX]和loop命令

[bx]和内存单元
  • [bx]同样表示一个内存单元 偏移地址为bx
  • loop 循环指令
  • ‘()’表示一个寄存器或者一个内存单元中的内容
  • ‘()’可用元素的种类型 1 寄存器 2 段寄存器 3 内存单元物理地址(20位)
  • ‘()’表示两种数据类型 1 字型 2字节型 (al) = (20000H)取字节型 (ax)=(20000H) 20000H取字型
    -idata 表示常量
Loop循环
mov cx ,循环次数
s: 
    循环执行程序块
    loop s
通过编译连接的asm文件 对[]偏移地址的解释错误

使用[bx]间接传递偏移地址

mov ax , 2000H
mov ds , ax         ;段地址2000H送入ds
mov bx , 0          ;偏移地址0送入bx
mov al , [bx]       ;ds:bx的送入al

或者

mov al , ds:[0]
段前缀
  • ds 默认段地址
  • cs 代码段地址
  • ss 栈段地址
  • es 显存段地址
书中120页给出了loop循环很好的实例

第六章 包含多个段的程序

在代码中使用数据
assume cs:code   ;与系统cs相关联 固定写法
code segment    ;代码段 名称 'code' 
    dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h  ;声明字型数据
    mov bx,0                ;赋值语句
    mov ax,0                ;赋值语句
    mov cx,8                ;循环8次
s:add ax,cs:[bx]            ;(ax) =((cs*16)+bx)
    add bx,2                ;偏移地址+2
    loop s                  

    mov ax,4c00h            ;类似于return 固定写法
    int 21h
code ends

end                         ; 代码段结束标记  固定写法
标记程序开始部分 类似于smali语言中的 .prologue 书中126页
assume cs:code   ;与系统cs相关联 固定写法
code segment    ;代码段 名称 'code' 
      dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h  ;声明字型数据
      start:  mov bx,0                ;赋值语句
              mov ax,0                ;赋值语句
              mov cx,8                ;循环8次
          s:add ax,cs:[bx]            ;(ax) =((cs*16)+bx)
              add bx,2                ;偏移地址+2
              loop s                  

              mov ax,4c00h            ;类似于return 固定写法
              int 21h
          code ends
end start                    ; 代码段结束标记  固定写法
自定义段 书中130页
  • 一个段的数据段可以由段名表示,如:data:6表示’0cbah’
  • 这也是JAVA思想的雏形啊
  • CPU 将 cs指向code ds指向data ss指向stack(可以这样理解)
assume cs:code,ds:data,ss:stack         ;关联系统段地址 :固定写法

data segment        ;自定义段 data
    dw 0123h,0456h,0789h,0abch,0defh,0cbah,0987h            ;16字节内存单元
data ends           ;段结束

stack segment           ;   自定义栈 stack
    dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0                  ;16字节内存单元
stack ends          ;段结束

code segment        ;自定义代码段  code

start:mov ax,stack                          ;start 开始标记  stack段地址给寄存器ax
      mov ss,ax                             ;ax赋给栈段地址   
      mov sp,20h                            ;设置栈顶ss:sp 指向stack:20

      mov ax,data                           ;data段地址给寄存器ax
      mov ds,ax                             ;ds 指向 data 

      mov cx,8                              ;设置循环次数8
    s:push [bx]                             ;将ds:[bx]的值入栈,也就是data:0~15的16个字型数据
      add bx,2                              ;bx 自加 2
      loop                                  ;循环标志 固定写法

      mov bx,0                              ;将bx置空

      mov cx,8                              ;循环标志 固定写法
    s0:pop [bx]                             ;将栈stack依次出栈 赋值给 ds:[bx]
      add bx,2                              ;bx 自加 2
      loop s0                               ;循环标志 固定写法

      mov ax,4c00h                          ;结束标志 固定写法
      int 21h
code ends                               ;代码段结束标记

end start                               ;程序结束标记

第七章

or and 指令

and: 与指令 同为1 则为1
or : 只要有1 则为1

mov al 1100100B
add al 1000000B
//执行和结果为  10000000B
mov al 1000111B
or  al 1110111B
//执行结果为 1110111B
书中139页介绍了ASCII从键盘到显示器过程

ASCII码表 http://ascii.911cha.com/

字符串数据

这边先介绍一下 db dw dd
- db定义字节类型变量,一个字节数据占1个字节单元,读完一个,偏移量加1
- dw定义字类型变量,一个字数据占2个字节单元,读完一个,偏移量加2
- dd定义双字类型变量,一个双字数据占4个字节单元,读完一个,偏移量加4

转换大小写案例

这里有意思的是
- 大写转小写 即 and 11011111B 也就是-20H 也就是第五位置0 大写转大写无变化
- 小写转大写 即 and 00100000B 也就是+20H 也就是第五位置1 小写转小写无变化

assume cs:codesg,ds:datasg

datasg segment
    db 'BaSiC'
    db 'iNfOrMaTiOn'
datasg ends

codesg segment
    start:mov ax,datasg
          mov ds,ax
          mov bx,0
          mov cx,5
        s:mov al,[bx]           ;取出ds:bx所指的第一个字母
             and al,11011111B   ;将第五位置0 就是-20H 
             mov [bx],al        ;将值返还给ds:[bx]
             inc bx
         loop s          
          mov bx,5              ;从ds:5开始指向information的第一个字母 
          mov cx,11
        s0:mov al,[bx]          
               or al,00100000B  ;将第五位置1 就是+20H 
               mov [bx],al
               inc bx
        loop s0           
          mov ax,4c00h
          int 21h

codesg ends
end start
[bx+idata]进行数组处理 简化上面的程序
mov al,[5+bx]
mov [5+bx],al
  • C语言:a[i],b[i]
  • 汇编语言:0[bx],5[bx] 那这里的 0 5 代表的就是 数组的首地址了 而c语言用数组名表示
SI DI 与bx功能相似 不能分成两个8位寄存器 书中147页
mov ax ,[bx][si] 
//等同于
mov ax,[bx=si]
//描述为
(ax) = (ds*16 + bx + si)
寻址方式的灵活运用 书中152页
  • [idata]
  • [bx]
  • [bx+idata]
  • [bx+si]
  • [bx+si+idata]
为了培养我们汇编中数组的概念 书中153页

定义如下数据段 6个字符串 等同于下图 的一个

datasg segment;注意每个字符被扩展为16位
    db  '0. file        '
    db  '1. edit        '
    db  '2. search      '
    db  '3. veiw        '
    db  '4. options     '
    db  '5. helps       '
datasg ends

image

循环嵌套

最终:是将datasg数组中的数据全部大写

datasg segment;注意每个字符被扩展为16位
    db  'fil              '
    db  'edi              '
    db  'sea              '
    db  'vei              '
datasg ends

mov ax,datasg
mov ds,ax
mov bx,0        ;ds:bx 指向datasg
mov cx,4        ;循环次数4行

s0:mov si,0     ;外层循环 第一行 当si=3退出循环
   mov cx,3     ;循环次数3列

   s: mov al,[bx+si]    ;内层循环 ds:[bx][si]  理解为 第bx行 第si列
    and al,11011111b    ;第五位置0 也就是-20H 也就是 小写 -> 大写
    mov [bx+si],al      ;
    inc si              si加1
    loop s

add bx,16               ;外层循环 +16 即+10H 即换行
loop s0
改进之-栈存储数据(节省寄存器的使用数量)

用栈的方式暂时保存cx的值

datasg segment;注意每个字符被扩展为16位
    db  'fil              '
    db  'edi              '
    db  'sea              '
    db  'vei              '
datasg ends

stacksg segment
    dw 0,0,0,0,0,0,0,0
stacksg segment

codesg segment
    start:mov ax ,stacksg       
          mov ss,ax
          mov sp,16             ;ss:sp 指向 stacksg:10H
          mov ax,datasg         
          mov ds,ax
          mov bx,0              ;ds:bx 指向 datasg:0
          mov cx,4                  
    s0:   push cx               ;入栈 cx 
          mov si,0              
          mov cx,3

    s:    mov al,[bx+si]        ;(al) = (ds*16+bx+si) 也就是[bx][si] bx行si列
          and al,11011111b      ;改成大写
          mov [bx+si],al        ;(ds*16+bx+si) = (al)
          inc si                ;si自加1
          loop s

          add bx,16             ;换行
          pop cx                ;出栈 给cx
          loop s0

          mov ax,4c00h
          int 21h

codesg ends

end start

第八章 数据处理

  • 处理数据在什么地方?
  • 要处理的数据有多长?

  • reg:寄存器 ax bx cx dx al ah bl bh cl ch dl dh cl ch sp bp si di

  • sreg:段寄存器 ds cs ss es
使用规则 mov ax […]
  • bx si bp di 可用于内存单元寻址
  • bx si bp di 只能单独出现,且 bx与bp,si与di不能组合使用
  • []中使用bp 默认使用段寄存器 ss 即 ss*16+bp
机器指令处理的数据在什么地方
机器码 汇编指令 指令执行前数据的位置
8E1E0000 mov bx,[0] 内存,ds:0单元
89c3 mov bx,ax CPU内部,ax寄存器
BB0100 mov bx,1 CPU内部,指令缓冲器
数据位置的表达
  • 立即数(idata)
  • 寄存器
  • 段地址(SA)和偏移地址(EA)
  • 存放段地址的寄存器 如:mov ax ds:[bx]
寻址方式

image

指令处理的数据长度
  • ax 字单元
  • al 字节单元
  • mov byte ptr ds:[0],1 字节单元
  • mov word ptr ds:[0],1 字单元
  • [bx].idata [bx].idata[si] bx定位结构体 idata定位数据项 si定位数组元素 (书中169页)
div指令 除法指令
  • 除数:8位和16位 在一个reg或内存单元中
  • 除号前面位被除数,后面为除数
  • 被除数:默认放在AX或者DX和AX中 除数8位则被除数16位 除数16位则被除数32位,由DX(高16位)和AX存放(底16位)
  • 结果:除数8位 AL存商 AH存余数 除数16位 AX存商 DX存余数

实例

div word ptr es:[0]
(ax) = [(dx)*10000H+(ax)]/((es)*16+0)的商   //注意这里是10000H
(dx) = [(dx)*10000H+(ax)]/((es)*16+0)的余数

div byte ptr ds:[0]
(al) = (ax)/((ds)*16+0)的商
(ah) = (ax)/((ds)*16+0)的余数

实例计算 100001/100

mov dx,1
mov ax,86a1h
mov bx,100
div bx

根据公式一:
100 = 64H
- (ax) = [1*10000H+ax]/100 = 186a1H/64H = 03e8h
- (dx) = [1*10000H+ax]%100 = 186a1H%64H = 1

伪指令dd ((dword)double word 双字型 即四个字节 32位)
  • db 00H 2个8位
  • dw 0000H 2个byte
  • dd 00000000H 2个word
dup(两份) 配合dd 进行数据重复 书中172页
  • db 3 dup (0) == db 0,0,0
试验七 书中173页 请认真完成!!

答案地址 https://blog.csdn.net/love_jing_forever/article/details/53204818

第九章 转移指令原理

定义:可以修改IP,或同事修改CS和IP的指令统称为转移指令。

转移行为:
- 段内转移 只修改IP jmp ax
- 段间转移 同事修改 CS IP jmp 1000:0

对IP修改范围不同
- 短转移 对IP修改范围 -128~127
- 近转移 对IP修改范围 -32768~32767

指令分类
- 无条件转移指令 jmp
- 条件转移指令
- 循环指令 loop
- 过程
- 中断

操作符 offset 获取标号的偏移地址
start:mov ax ,offset start  ;相当于mov ax ,0
    s:mov ax ,offset s      ;相当于 mov ax s

jmp指令并不需要转移的目的地址 只需要转移的位移

assume cs:codesg
codesg segment

    start:mov ax,0      
          jmp short s ;short 表示范围在 -128~127之间 即‘段内短转移’ 
          add ax,1  
         s:inc ax 
codesg ends

end start
我们先复习一下补码 书中329页
  • 8位无符号数 00000000B~11111111F 共255个数
  • 8位有符号数
  • 10000000B~11111111F -127~127 共254个数 注意:10000000B不算-0没有意义
  • 最高位为1 表示负数
  • 整数补码取反加1或,为其对应负数的补码,负数补码取反后加1,位其绝对值
  • 1的补码:11111111B,表示-1
  • -1的补码:00000001B,其绝对值为1.
  • 补码计算法定义:非负数的补码是其原码本身;负数的补码是其绝对值的原码最高位符号位不变,其它位取反,再加1。

  • 模定义:一个负整数与其补码的和。

整理下对补码的知识

求0-9的补码
0000H-0009H
因为指定jmp short 转成二进制:
00000000B-00001001B 
应为没有减法器 所以理解为加法
00000000B+(-00001001B) 
也就是求-9(10001001B)的补码
10001001B 取反且首位不变(11110111B)
即F7H
  • jmp near ptr 标号(IP) = (IP)+16位位移
  • jmp far ptr 标号 段间转移 远转移 (CS)=标号所在段的段地址;(IP)=标号在段中的偏移地址
转移地址在内存中的jmp指令
  • jmp word ptr ds:[0] 将内存单元中的存放的赋给 偏移地址 (IP) = (ds:[0])
  • jmp word ptr [bx]将内存单元中的存放的赋给 偏移地址 (IP) = ([bx])
  • jump dword ptr 内存单元地址(段地址) (CS) = (内存单元地址+2) (IP) = (内存单元地址)
mov ax ,0123H
mov ds:[0],ax
mov word ptr ds:[2],0
jmp dword ptr ds:[0]
//(CS)=0,(IP)=0123H,CS:IP指向0000:0123  
jcxz指令 条件转移指令(短指令)

JCXZ CX: 为零时转移

格式:jcxz 标号
用高级语言描述:if (cx == 0) 
                 jmp short 标号     ;------> 当cx= 0 转移到标号处
            else
                next                ;------>当cx != 0 程序什么也不做,继续向下执行
(短转移,通过位移进行转移,IP修改范围-128127
loop指令前面讲过了
书中189页 试验8.9部分以后再看

第十章 Call和Ret指令

  • call和ret都是转移指令 都可修改IP 或同时修改CS和IP
  • ret 修改IP 实现近转移 相当于 pop IP
  • retf 修改SS和IP 实现远转移 相当于 POP IP POP CS

实例:

assume cs:codesg
stack segment
db 16 dup (0)  ;申请一个16字节的空间
stack ends

code segment
    mov ax,4c00h
    int 12h
start: mov ax,stack     ;代码开始部分
       mov ss,ax        
       mov sp,16
       mov ax,0
       push cs      
       push ax
       mov bx,0
       retf        cs:IP指向 cs:ax即 cs:0000H;代码第一行mov ax,4c00h
code ends

end start
Call指令
  • 将当前IP或者CS和IP压入栈中
  • 转移
  • 不能实现短转移 范围2个有符号字节

call 标号

汇编表示: 入栈IP后

(SP) = (SP)-2
((ss)*16+(sp))=(IP)  将IP对应的值存入ss:sp中  sp偏移向上2个字节
(IP)=(IP)+16
push IP     
jmp near ptr 标号    ;对应(IP)=(IP)+16  就是跳过一行

cal far ptr 标号 段间转移
汇编表示:入栈CS 和 IP后

push CS
push IP
jmp far ptr 标号  cs:ip指向 标号处

call 16位reg
汇编表示:

push IP
jmp 16位 reg   ; 直接跳转到ss:reg位置

call word ptr
汇编表示: 跳转到 标号处 SP+2

push IP
jmp word ptr 内存单元地址

call dword ptr
汇编表示: 跳转到 标号处 SP+4

push CS
push IP
jmp word ptr 内存单元地址
call 和 ret 配合使用
  • 子程序:具有一定功能的程序。
  • call:调用子程序 并将IP压入栈中
    ret:返回子程序 POP IP弹栈后 调用cs:ip处的代码 即 call的下一行。
mul指令 乘法指令
  • 想成两数 必须同时为16位或8位
  • 8位时 一个默认放在AL中,另外一个放在8位reg或者内存单元中
  • 16位 一个默认放在AX中,另一个放在16位reg或者内存单元中

对于结果:
- 8位乘法 结果默认放在AX中
- 16位乘法 结果高位放在DX,低位放在AX中

mul byte ptr ds:[0]
;含义: (ax) = (al)*((ds)*16+0);   (ax) = (al)*(ds:bx)

mul word ptr [bx+si+8]
;(ax) = (ax)*((ds)*16+(bx)+si+8)结果的底16位
;(dx) = (ax)*((ds)*16+(bx)+si+8)结果的高16位

计算100*10
分析:10010 都小于255  可以做8位乘法
 mov al ,100
 mov bl ,10
 mul bl
 结果:(ax) = al*bl = 1000 = 0e38H

 计算10000*100
 100小于25510000大于255,所以做16位乘法

 mov ax,100
 mov bx,10000
 mul bx

 结果: ax*bx = F4240 = 11110100001001000000 =
 1111 0100 0010 0100 0000
 (ax) = 4240H dx = 0000 0000 0000 1111 = 0000FH
参数和结果的传递

子程序:也就java中的Util方法 或者普通方法

assume cs:code
    data segment
     dw 1,2,3,4,5,6,7,8       ;这边申请的是word 也就是一个字16位
     dd 0,0,0,0,0,0,0,0       ;这边申请的是dword 也就是一个双字32位
    data ends

code segment

    start:mov ax,data
          mov ds,ax
          mov si,0              ;ds:si 指向第一组word单元
          mov di,16             ;ds:di 指向第二组的dword单元
                                ;si di等同于bx  不能分成两个8位
          mov cx 8              ;循环8次
        s:mov bx,[si]           ;(bx) = ([ds:si])=([ds:0])=1
          call cube             ;假设bx = 8 
          mov [di],ax           ;([di]) = 0040H     低16位
          mov [di].2,ax         ;向后移动2个字节  高16位 
          add si,2              ;ds:si 指向下一个word单元
          add di,4              ;ds:di 指向下一个dword单元
          loop s

          mov ax,4c00h
          int 21h

    cube:mov ax,bx          ;该方法被调用 因为bx小于255(FF) 用8位
         mul bx             ;(ax) = (al)*(bx) = 0*H*0008H =8 * 8 = 64 =0040H
         mul bx             ;(ax) = (al)*(bx) = 04H*0040H =4 * 64 = 64 =0100H
         ret                ;返回调用处的下一行,即:mov [di],ax

code ends

end start
批量数据传递

作者告诉我们为什么定义字符串使用 db https://blog.csdn.net/weixin_42553435/article/details/80919722

assume cs:code
    data segment
      db 'conversation'  
      ;存的byte字节 是顺序的 
      ;可以理解成 db 'c','o','n','v','e','r','s','a','t','i','o','n' 总共12个字符
    data ends

code segment
    start:mov ax,data
    mov ds,ax
    mov si,0
    mov cx,12
    call capital
    mov ax,4c00h
    int 21h

    capital:and byte ptr [si],11011111B
            inc si
            loop capital
            ret

code ends

end start

编写子程序的标准

子程序开始: 子程序中使用的寄存器入栈
            子程序内容
            子程序中使用的寄存器出栈
            返回(ret,retf)
试验10-编写子程序 书中206页

第十一章 标志寄存器

  • 存储相关指令的执行结果
  • 位CPU执行指令提供行为依据
  • 控制CUP的工作方式

image

ZF标志 零标志位 zero flag
  • 记录指令执行的结果是否为0
  • 1则zf = 0 , 0则 zf = 1
  • 影响zf指令:add,sub,mul,div,inc,or,and
  • 不影响zf指令:mov push pop
mov ax,1
sub ax,1
//ax=0,zf =1
PF标志 奇偶标志 parity flag
  • 记录指令执行的结果 的bit位中1的个数是否为偶数,偶数则pf=1 奇数则pf=0
mov ax,1
add al,10
//ax=00001011B,zf =0
SF标志 符号标志位 symbol flag
  • 记录指令执行的结果 是否为负,为负数则sf=1 非负则sf=0
  • 当做无符号数计算 SF无意义
  • 只记录实际结果的正负 不记录逻辑结果的正负 (考虑溢出)
mov al,10000001B
add al,1
//结果:(al) = 10000010B
//SF=1
mov al,10000001B
add al,01111111B
//结果:(al) = 100000000B //溢出则 去掉最高位1 取00000000B = 00H 
//SF=0 结果非负数
CF标志 进位标志 carry flag
  • 无符号位的计算中
  • 记录运算结果的: 1.最高有效位 2.更高位进位值 3.从更高位的借位值
  • 比如一个8位数 第7位位最高有效位 第8位即相对于最高有效位的更高位
  • 8位溢出后 由CF记录这个值
  • 97H-98H 将产生借位
mov al,98H
add al,al       ;98H*2 = 0130H执行后:(al) = 30H, CF = 1,,CF记录最高有效位的更高位的进位值
add al,al       ;30H*2 = 60H ,CF = 0
mov al,97H
sub al,98H      ;借位后即: (al) =  197H-98H = FFH ,CF=1,记录向更高位的借位值
sub al,al       ;(al) = FFH-FFH = 0H ,CF = 0 
OF标志 溢出标志位 overflow Flag
  • OF只对有符号数有意义 计算出理想值进行比较 如8位范围-128~127
  • 8位有效范围:-128~127
  • 溢出OF =1
  • CF只对无符号数有意义
  • CPU 在执行指令时 会对数据分别做有符号和无符号的计算 并对CF OF分别赋值
mov al,98 
add al,97  
    ;结果为::98+97 = 195
    ;实际结果位 195 = C3H=11000011B
    //11000011B 为有符号数 
    //10111100B+1
    //10111101B  =-61
  • 对于无符号数:没有产生进位 CF = 0
  • 对于有符号数:产生溢出 OF = 1
mov al,0F0H     
            ; FOH 为有符号数    -16 
                //11110000
                //10001111+1
                //10010000

add al,088H     
            ; 088H 为有符号数   -120
                //10001000
                //11110111+1
                //11111000 

            ;0F0H+ 088H 结果为 101111000
               //101111000  
               //110000111+1
               //110001000  -136 溢出
            ;但是实际结果为
            //01111000  超界则去掉最高位
            //78H
  • 对于无符号数:没有产生进位 CF = 1
  • 对于有符号数:产生溢出 OF = 1
adc指令 add +carry 带进位的加法指令
adc ax,bx 实现功能:(ax) = (ax)+(bx)+CF 

mov ax,2
mov bx,1
sub bx,ax    ;(bx)<(ax) 产生借位:CF=1
adc ax,1     ;(ax)+1+CF = 2+1+1=4 
sbb指令 subtraction+borrow 带进位的减法指令
cmp指令 compare 比较指令
  • 于减法指令,只是不保存结果
  • p指令执行后,将对标志寄存器产生影响
mov ax,8
mov bx,3
cmp ax,bx
//执行后:(ax)-(bx) =5, zf = 0,pf = 1,sf = 0,cf = 0,of =0;
书中225页进行了一通cmp分析
  • zf = 1 说明:ax = bx
  • zf = 0 说明:ax != bx
  • cf = 1 说明:ax < bx
  • cf = 0 说明:ax >= bx
  • cf = 0 并且 zf = 0 说明:ax > bx
  • ==cf = 1 或 zf =1 说明:ax <= bx==
  • sf = 1 并且 of = 1 (ah) > (bh)
  • sf = 0 并且 of = 1 (ah) < (bh)
  • sf = 0 并且 0f = 0 (ah) >=(bh)
检测比较结果的条件转移指令
指令 含义 检测相关标志位
je(equal) 等于则转移 zf=1
jne 不等于 zf!=0
jb(below) 低于 cf=1
jnb 不低于 cf=0
ja(above) 高于 cf = 0 并且 zf = 0
jna 不高于 cf = 1 后者 zf = 1
cmp ah,bh
je s            ;如果(ah)= (bh) 其实只要ZF = 1 就执行 :跳转到 s  否则向下执行 
add ah,bh
jmp short ok
s:add ah,ah
ok:...
DF标志 和 串传送指令
  • DF标志:direction flag 方向标志
  • 串处理指令:控制每次操作后 si di 的增减
  • df = 0 每次操作后 si di 递增
  • df = 1 每次操作后 si di 递减
  • movsb(byte) 描述:mov es:[di],byte ptr ds:[si] df=0: di si 加1 ,df=0: di si 减1
  • movsw(word) 描述:mov es:[di],byte ptr ds:[si] df=0: di si 加2 ,df=0: di si 减2
  • cld指令 将df置1
  • std指令 将df置0
  • rep movsb 配合movsb 和 movsw 使用 实现循环 汇编描述:
s:movsb
  loop s
实现内存的传送
data segment
    db 'Welcome to masm!'
    db 16 dup (0)
data ends

code segment
    mov ax ,data
    mov ds,ax
    mov si,0        ;ds:si 指向 data:0
    mov es,ax   
    mov di:16       ;es:di 指向 data:0010H
    mov cx,16       
    cld             ;df=0 正向传送
    rep movsb       
code ends
pushf 和 popf 针对标志寄存器压弹栈 书中234页

检测点11.4 记得这是存的flag 是一个16位的数

下面指令执行后,(ax)=0045h

mov ax,0            ;(ax)=0

push ax             ;将(ax)入栈

popf                ;将flag寄存器的所有为都初始化为0.因为它们使用的是同一个栈结构。

mov ax,0fff0h       ;(ax)=0fff0H

add ax,0010h        ;执行add后,结果是:(ax)=0000H(作为无符号数,产生进位cf=1),

                    ;作为有符号数没有溢出,of=0;其它zf=1,sf=0,pf=1.我们把这些标                           ; 位按照顺序组合起来(不能确定的我们不管了):00000xxx010x0101                                     ;我们不能确定的都用X表示。没有使用的用0表示吧             

pushf               ;将flag的值入栈。

pop ax              ;弹栈,(ax)= 00000xxx010x0101B

and al,11000101B    ;ax8位按位与,    010x0101 and 11000101=01000101B=45H

and ah,00001000B    ;ax8位按位与,00000xxx and 00001000=0000000B=00H
Debug中的flag 标记

image

试验11 书中234页

第十二章 内中断

  • CPU不再接着(刚执行完的的指令)向下执行,而是转去处理这种中断信息
  • 中断信息由CPU的内部和外部产生。
内中断的产生
  • 除法错误 执行div产生除法溢出
  • 单步执行
  • 执行into指令
  • 执行int指令
中断类型
  • 中断类型码:一个字节型数据 256中种中断信息
  • 中断信息来源:中断源
  • 除法错误:0
  • 单步执行:1
  • 执行into指令:4
  • 执行int指令:格式为 int n ,n为字节型立即数
中断向量表:中断处理程序入口地址列表
  • 中断向量表:通过中断类型码找到中断入口地址的的先决条件
  • 8086CPU中 0000:0000~003F 共1024个单元 存放中断向量表
  • 一个表存放一个地址 = 段地址+偏移地址 = 2个字大小
中断过程
  • 中断过程:通过中断类型码找到中断入口地址 设置CS:IP CPU执行中断程序
  • (中断信息中)取得中断类型
  • 标志寄存器入栈
  • 设置标志寄存器 第八位TF 和第九位IF 为0
  • CS 内容入栈
  • IP 内容入栈
  • 从内存地址为中断类型码*4 和中断类型码*4+2 的两个字节单元中读取中断处理程序的入口地址设置IP和CS(*4是因为每个向量地址有4个字节)
iret指令和中断处理程序

CPU随时可能执行中断程序,所以中断程序必须一直存储在内存的某段中

  • 保存用到的寄存器
  • 处理中断
  • 恢复用到的寄存器
  • 用iret指令返回

iret指令功能用汇编描述:

pop IP
pop CS
popf
除法溢出错误
mov ax,1000h
mov bh,1
div bh

计算:
(al) = (ax)/((ds)*16+0)的商 = 1000h/0001H = 1000H (溢出 al范围为00H~FFH)
(ah) = (ax)/((ds)*16+0)的余数  = 0H
do0 修改 除法溢出的错误提示
  • 编写显示“overflow!”的中断程序:do0
  • 将do0送入内存0000:0200处
  • 将do0的入口地址0000:0200存储在中断向量表0号表项中

实现代码:

assume cs:code
code segment
    start:do0 安装程序 
          设置中断向量表
          mov ax,4c00h
          int 21h

    do0:  显示字符串“overflow!”
          mov ax,4c00h
          int 21h

code ends

end start
安装 do0
assume cs:code
code segment
    start:mov ax,cs
          mov ds,ax 
          mov si,offset do0                 ;设置ds:si指向源地址

          mov ax,0
          mov es,ax                         
          mov di,200h                       ;设置es:di指向目的地址

          mov cx,offset do0end-offset do0   ;do0 部分代码的长度        ;设置cx为传输长度
          cld                               ;设置传输方向
          rep movb

          设置中环向量表

          mov ax,4c00h
          int 21h

    do0:  显示字符串“overflow!”
          mov ax,4c00h
          int 21h

code ends

end start
编写do0程序

这里存在一个问题 当code segment执行玩之后data
segment内存随时可能被释放。所以需将data实时生成。

do0代码不执行 只是装载到200H处

assume cs:code

data segment
 db "overflow!"
data ends

code segment
    start:mov ax,cs
          mov ds,ax 
          mov si,offset do0                 ;设置ds:si指向源地址

          mov ax,0
          mov es,ax                         
          mov di,200h                       ;设置es:di指向目的地址

          mov cx,offset do0end-offset do0   ;do0 部分代码的长度        ;设置cx为传输长度
          cld                               ;设置传输方向
          rep movsb

          ;设置中环向量表
          mov ax,4c00h
          int 21h

    do0:  mov ax,data                       
          mov ds,ax
          mov si,0                          ;设置ds:si指向字符串

          mov ax,0b800H         
          mov es,ax
          mov di,12*160+36*2                ;设置es:di指向显存空间的中间位置

          mov cx 9                          ;设置cx字符串长度
    s:    mov al,[si]
          mov es:[di],al
          inc si
          add di,2
          loop s

          mov ax,4c00h
          int 21h
    do0end:nop

code ends

end start
改进do0
assume cs:code
code segment
    start:mov ax,cs
          mov ds,ax 
          mov si,offset do0                 ;设置ds:si指向源地址

          mov ax,0
          mov es,ax                         
          mov di,200h                       ;设置es:di指向目的地址

          mov cx,offset do0end-offset do0   ;do0 部分代码的长度        ;设置cx为传输长度
          cld                               ;设置传输方向
          rep movsb

          ;设置中环向量表
          mov ax,4c00h
          int 21h

    do0: jmp short do0start
         db "overflow!"

    do0start:  mov ax,cs                    
          mov ds,ax
          mov si,0                          ;设置ds:si指向字符串

          mov ax,0b800H         
          mov es,ax
          mov di,12*160+36*2                ;设置es:di指向显存空间的中间位置

          mov cx, 9                         ;设置cx字符串长度
    s:    mov al,[si]
          mov es:[di],al
          inc si
          add di,2
          loop s

          mov ax,4c00h
          int 21h
    do0end:nop

code ends

end start
设置中断向量

do0 入口地址0:200 0:0存偏移地址 0:200子单元存段地址.

单步中断 类似Debug的t命令单步调试
  • TF为1 则(Step Flag)
  • 去除中断类型码1 (提前置0 防止无限循环)
  • 标志寄存器入栈,TF,IF 设置为0
  • CS,IP入栈
  • (IP)=(1*4),(cs)= (1*4+2)
响应中断的特殊情况

中断后 并不会设置 ss:SP 且当执行栈操作时 不会去响应中断,此时我们应该将SS和SP连续存放

mov ax,1000h
mov ss,ax
mov sp,0
试验12 编写0号中断处理程序 书中251页

第十三章 int指令:内中断之一

格式:int n,n为中断类型码 引发中断
执行过程:
- 去除中断类型码1 (提前置0 防止无限循环)
- 标志寄存器入栈,TF,IF 设置为0
- CS,IP入栈
- (IP)=(1*4),(cs)= (1*4+2)

书中253页 编写一个中断程序

问题 编写安装中断7ch中断例程
求2*3456^2
第一步:

assume cs:code

 code segment

    start :mov ax,3456
           int 7ch
           add ax,ax
           adc dx,dx
           mov ax,4c00h
           int 21h

 code ends

end start

第二步:

assume cs:code

 code segment

    start :mov ax,cs
           mov ds,ax
           mov si,offset sqr
           mov ax,0
           mov di,200h
           mov cx,offset sqrend-offset sqr  ;安装子程序到 es:di 也就是 0:200H处
           cld  ;df=0 正向传送  di si 加1
           rep movsb;   循环复制 (mov es:[di],byte ptr ds:[si] df=0)

           mov ax,0
           mov es,ax
           mov word ptr es:[7ch*4],200h         ;es:[7ch*4]中断向量表 偏移地址
           mov word ptr es:[7ch*4+2],0          ;es:[7ch*4+2]中断向量表 段地址

           mov ax,4c00h
           int 21h

    sqr:   mul ax
           iret

    sqrend:nop

 code ends

end start

其中iret为 恢复数 CS IP 和 flag

pop IP
pop CS
popf
int和iret和栈深入理解 书中256页已经变得很复杂了。可能是前面基础没打好。

先休息一下 本文共17章 且 课后试验部分未做,可谓是任重而道远,未完待续

猜你喜欢

转载自blog.csdn.net/qq_20330595/article/details/81183291