汇编语言学习

目录

汇编语言

  • 日期 19.07.19

## 软件安装

1.DOSBox

    1. 无脑下一步
    1. 修改配置文件
      添加以下命令
mount c: d:\asm
  c:
  //d盘下的文件是自行创建其中包含debug.exe就可以了

2.Vim

    1. 安装完后打开其文件位置
    1. 修改配置文件
      在开头写入简单的配置文件
set number
color evening
//保存退出

# 编译和链接

#### 将源代码 生成最终 的 exe 文件 然后执行

  • 这一部分刚开始跟上做就好了,不用了解清楚

    DOSBox代码
    masm t1; link t1; // t1 为自己创建的asm文件 //在创建txt文件把后缀改为asm //用vim编辑
    asm文件代码
    as assume cs:code

code segment

  

        mov bx,0B800H
        mov es,bx
        
        mov bx,160*10 + 40*2

        mov word ptr es:[bx],5535H

        mov ax,4C00H
        int 21H

  

  code ends
  end

# 进制

10进制

437->>>
\[4* 100+3* 10+7* 1\]
\[4*10^2+3*10^1+7*10^0\]
0.1.2.3就是他在数字中的位置

2进制

111->>
\[1*2^2+1*2^1+1*2^0\] >>转换成10进制

数字10
含义
位置210
有代表421

进制快速转换

拆分

字节转换

\[1 byte = 8 bit\]
\[1 KB = 1024 byte\] >> \[1KB = 2^{10} byte\]
\[1 MB = 1024 * 1KB\] 1MB = 1024 * 1024 byte >> \[1MB = 2^{20} byte\]
\[1 GB = 1024 * 1MB\] >> \[1GB = 2^{30} byte\]

小结

  1. 汇编指令是 机器指令的 注记符,同机器指令一一对应
  2. 每一种CPU都有自己的汇编指令集
  3. CPU 可以直接使用的 信息 在存储器中 存放
    4.在存储器中指令和数据没用任何区别, 都是二进制数
    5.存储器单元从 0 开始 编号
    6.一个 存储单元 可以存储 8 个 bit 即 8 为二进制数
    7.1byte=8bit 1KB=1024byte 1MB=1024KB 1GB=1024MB
    总线

    地址总线 决定 CPU 的 寻址能力
    数据总线 决定 CPU 的 一次传输数据量
    控制总线 决定 CPU 的 对其他器件的控制能力

# 寄存器

  1. 小例子
    1.1 B800:0400 回车
    1.2 1空格 1空格
    1.3 2空格 2空格
    1.4 ...

  2. 汇编程序员 就是 通过 汇编语言 中的 汇编指令 去修改 寄存器的值 从而 控制 CPU 控制整个计算机

通用寄存器

AX,BX,CX,DX

  1. 他们各自可分为两个 8 位寄存器(only)
    \[ax=ah+al\] \[(h==high,l==low)\]
  2. 1 byte = 8 bit(8位寄存器)==字节型数据
    2 byte =16 bit(16位寄存器)==字型数据 2个字节
    一个字型数据==2个字节型数据=高位字节+低位字
  3. 数据与寄存器之间 要 保持一致性,8位寄存器给8位数据,16为寄存器给16位数据
    ==不区==分大小写

(地址寄存器)指令寄存器 CS(段地址)和IP(偏移地址)

jmp指令 jmp 2000:0 ====> cs==2000,ip===0;

mov ax,1000
jmp ax
==> ip=1000;

只能用jmp指令修改cs,ip

1.CPU从cs:ip 所指的内存单元中读取内容,存取到 指令缓存器当中
2.然后IP跳转到下一个指令位置,并且在执行指令缓存器当中的指令
3.重复1。

段地址寄存器偏移地址寄存器
ds(内存),es,ss(栈),cssp(栈),bp,si,di,ip,bx

指令的执行过程

  1. CPU从cs:ip所指向的内存单元 读取 指令 然后 存放到 指令缓存器当中
  2. IP = IP + 所读指令的长度,从而指向下一条指令
  3. 执行指令缓存器的内容,回到步骤1

debug

-r 查看和修改寄存器中的内容

-r cs
cs value
enter

-d 查看内存中的内容 段地址加偏移地址

-d ss:00

-v 将机器指令翻译成汇编指令
-a 以汇编指令的格式 在内存中写入一条汇编指令 每次debug都的写
-t 执行当前 cs:ip 所指的机器指令 代码段
-e 可以改写 内存中的内容(数据)

-p 快速执行完loop 指令
-g 地址 ==== 一直执行到 地址 的 位置

寄存器(内存访问)


3个段


数据段

1. 字的存储

一次存放两个字节

2.

内存地址由 段地址偏移地址 构成
其中段地址默认保存在DS寄存器当中
偏移地址由 [address] 保存告知

3. mov,add,sub 指令

汇编语言学习

4. -d 段地址:偏移地址

5. 在内存中存放自己定义的数据,通过 ds和[] 来 让CPU访问数据

代码段

1. 段地址存放在cs寄存器中

2. 偏移地址存放在ip寄存器当中

3. 内存中存放代码

4. 修改cs:ip中的值就可使CPU执行代码

栈段

1. 栈的作用

  1. 临时性保存数据
  2. 进行数据交换
-a
mov ax,1000
mov bx,2000
push ax
push bx
pop ax
pop bx

2. 栈的寄存器ss:sp

3. 操作指令push&ip

push 执行过程

1.sp=sp-2(栈顶标记)
2.传入字型数据

pop 执行过程

1.传出字或字节
2.sp=sp+2(栈顶标记)

栈顶标记 在 数据(内存地址)的上面 的 内存地址
sp 偏移地址寄存器 ss 段地址寄存器

4. 处理数据时要 ,临时存放数据

5. 修改ss:sp中的值,决定栈顶位置,CPU在执行的过程中把我们定义的栈段当作栈使用

6. 一段连续的内存地址

7. 栈的容量的最大极限

sp 的变化范围 0~ffffH 32768 个字型数据
call 将指令IP 保存到内存的哪里? ret 可以拿回

保存到栈中 为了让 ret 从栈中取回

8.每执行 一条 -t 指令 就会将寄存器的值保存到 栈中

内存的安全访问

  1. 安全空间 0:200~0: 2ffH
  2. 内存分配的时间 1. 系统加载程序的时候 为程序分配的内存。2. 程序执行过程中,向系统再去要内存空间

# 承上启下

  • 我们可以把内存任意的划分为 栈,数据,指令 ,他们可以是同一块内存,亦可以是不同的内存
  • cpu 通过 ss:sp 所指向的 内存作为 栈
  • ds:[] 所指向的 内存 作为数据
  • cs:ip 所指向的 内存 作为指令

指令从哪里?数据从哪来?临时性的数据存放到哪里?

第一个程序


编译和链接

  1. 编译 masm .mas --> .obj
  2. 链接 link .obj --> .exe

exe 文件 的描述信息中 保存的程序入口 地址
然后 系统 通过 描述文件 来设置 cs:ip 和 其它内存

asm 文件 -- 汇编语言(1.汇编指令2.伪指令3.符号体系

  1. 汇编指令 由编译器 翻译成010101 的机器指令 最后由 CPU 执行
  2. 伪指令和符号体系 由编译器执行
  • 程序返回功能
    把系统加载程序的时候 给程序分配的内存 , 设置的寄存器 返还给系统,因为 系统资源 是有限的
mov ax,4c00
 int 21H

## 程序的跟踪 debug + 程序名

  • p 执行 int 指令
  • q 退出
  • cx == 程序长度

  • PSP区 从 ds:0 开始的 256 个字节

快速编译

  • 字母型数字前面 必须加 0;

默认代码(目前)
```
assum cs:code

code segment

;填写内容
  
  mov ax,4c00H
  int 21H

code ends

end
```

偏移地址寄存器 bx

自己分配内存

自己分配内存

  • 一个 segement 最少占据 16 个字节
  • 假设 数据段 有 N个字节 则 实际占用 \[(N/16 + 1)*16\]

  • 都是 16 的倍数
//实验5
assume cs:codesg

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

codesg segment
    
start:  
        
        
        mov ax,c
        mov es,ax

        
        sub cx,cx
        sub bx,bx
        add cx,8

addnum: mov ax,a
        mov ds,ax
        
        sub dx,dx
    
        mov dl,ds:[bx] ;拿出第一个数据
        
        mov ax,b
        mov ds,ax
        
        add dl,ds:[bx]  ;拿出第二个数据,并且相加
        
        mov es:[bx],dl
        
        inc bx
    
        loop addnum


       
    mov ax,4c00h
    int 21h

codesg ends

end start

总结

  • db 字节型
  • dw 字型

定位内存地址的方法

and 和 or

  1. and 逻辑与 0 置为0
    1. 全为 1 才出 1 否则全部为 0
    2. 可用于 对 二进制位的数字 设 0
  2. or 逻辑或 1 置为1
    1. 只要有1 就为 1
    2. 可用于对 二进制数字设 1

以字符的形式给出数据

  • like ‘………’ 其中单引号包含的 内容 编译器将把 其中的内容 转化为相应的 ASCII

大小写转换

and 置为大写 1101 1111b

or 置为小写 0100 0000b

[bx+idata]

  • idata 是立即数

常用格式

  1. mov ax,[200+bx]
  2. mov ax,200[bx]
  3. mov ax,[bx].200
  • 可以处理数组

SI 和 DI

  • 类似于BX ==但是== 不能 分成两个 8 为寄存器
  • 全为偏移地址寄存器 ==bx为基址寄存器==

[BX+SI] 和 [BX+DI]

常用格式

mov ax,[bx] [si]

[BX+SI+idata] 和 [bx+di+idata]

数据处理的两个基本问题

sreg 段地址寄存器

reg 寄存器

bx,si,di和bp

  • bx si/di组合
  • bp si/di组合

指令要处理的数据

  1. 保存在CPU
  2. 在内存中
  3. 在端口中

数据位置的表达

  1. 立即数(idata)
  2. 寄存器
  3. 段地址加偏移地址

数据的长度

  • byte 和 word

在处理数据的时候要 告知 CPU 要处理的数据有多大可以通过一些方法来告知

1. 通过寄存器来指明 如 ==ax==,代表对word操作而 ==al==,代表对byte 操作

2. 无寄存器 则用 ==X ptr== 来表示 X 为byte 或者 word 如 : mov word ptr ds:[0],1

3. 用 push or pop 就不用 声明 因为 栈就是 对字进行操作

div 指令

  • 除数 有8位和16位 在一个reg或内存单元中
  • 被除数 默认 放在 ==ax(16位)== 或者 ==dx(高16位) 和 ax(低16位)== 中
  • 结果 ==al(商) ah(余数)== 或者 ==ax(商) dx(余数)==

伪指令 dd (占两个字)

  • 相当于 两个 ==dw==
  • 四个 ==db==

dup

用来重复数据

  • db 3 dup (0) ==> db 0 ,0, 0

转移指令

操作符 offset

  • 取得标号的偏移地址

jmp 指令


  • 无条件转移指令
  • 可同时修改 cs 和 ip 或者 ip

jmp short 标号


  • 在编译是就已经处理好 要偏移的地址
  • 无论 本 命令在哪 只有 偏移地址

jmp near ptr 标号


  • 段内短转移

jmp far ptr 标号


  • 同时修改 cs 和 ip

在内存中转移


jmp word ptr 标号

  • jmp word ptr ds:[0]
  • 只修改 IP
  • 段内转移

jmp dword ptr 标号

  • 段间转移
  • ip[X+0],cs[X+2]

jcxz (短转移)


  • jmp cx zero
  • 只有在cx 为0 的情况下 才 执行 转移

loop(短转移)


  • cx 不为0 执行loop

ret 和 call


  • 指令执行过程
    • 汇编语言学习

通过栈中的数据来修改 cs 和 ip 同时还会 修改栈顶标志

ret(用栈中的数据)


  • 弹栈
  1. 近转移 ret 修改 IP pop ip
    1. \((ip)=((ss)*16+(sp))\)
    2. \((sp)=(sp)+2\)
  2. 远转移 retf 修改 cs:ip pop ip,pop cs
    1. \((ip)=((ss)*16+(sp))\)
    2. \((sp)=(sp)+2\)
    3. \((cs)=((ss)*16+(sp))\)
    4. \((sp)=(sp)+2\)

call(不能实现短转移)


  • 类似jmp
  • call程序处理的数据一般要进行压栈

1.根据位移进行转移


push ip
jmp near ptr 标号
  • 执行过程 原理
    • call下一条指令的IP压栈后,转到标号处

2.转移目的地址在指令中


call far ptr
  • 执行过程 原理
    • call下一条指令的CS:IP压栈后,转到标号处

3.转移地址在寄存器中


?assembly call 16 位 reg

  • 执行过程 原理
    • call下一条指令的IP压栈后,转到==reg== 处

4. 转移地址在内存中


1


call word ptr 内存单元地址
  • 执行过程 原理
    • call下一条指令的IP压栈后,转到==内存单元地址==

2


call dword ptr 内存单元地址
  • 执行过程 原理
    • call下一条指令的CS:IP压栈后,转到标号处

call 和 ret 共同应用


  • 就像函数调用

批量数据处理


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

data segment    
    db 'conversation'
data ends

stack segment
    db 16 dup(0)
stack 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 ds:[si],11011111b
             inc si;
             loop capital
             ret


code ends



end start

寄存器冲突问题


  • 在子程序执行开头,把所需要用到的寄存器压栈
  • 在子程序完成后,从栈中弹出各个寄存其的值
assume cs:code,ds:data,ss:stack

data segment    
    db 'word',0
    db 'unix',0
    db 'wind',0
    db 'good',0
data ends

stack segment
    db 128 dup(0)
stack ends

code segment


    start:  mov ax,data
            mov ds,ax
            
            mov cx,4
            mov bx,0
            
    s:      mov di,bx
            call capital
            add bx,5
            loop s
            
            mov ax,4c00h
            int 21h
    
    capital: push cx;执行子程序前压栈
             push si
             
    change:  mov cl,ds:[si]
             mov ch,0
             jcxz ok
             and byte ptr ds:[si],11011111b
             inc si
             jmp change
             
        ok:  pop si;执行完后弹栈
             pop cx
             ret


code ends



end start

mul


1. 8位


一个默认放在==AL==,另一个放在==内存字节单元==或者==8位reg==。

结果 默认 ==AX==。

2. 16位

一个默认放在==AX==,另一个放在==内存字单元==或者==16位reg==。

结果 默认 高位在==DX== ,低位在==AX==。

模块化程序设计


  • 通过==ret==,==call==.

参数和结果的传递

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

data segment    
    dw 1,2,3,4,5,6,7,8
    dd 0,0,0,0,0,0,0,0
    db 'word',0
    db 'unix',0
    db 'wind',0
    db 'good',0
data ends

stack segment
    db 128 dup(0)
stack ends

code segment


    start:  mov ax,data
            mov ds,ax
            mov si,0
            mov bp,0
            call r_start
            
            
            mov ax,4c00h
            int 21h

            
    r_start:    mov bx,ds:[si]
                call cube
                mov ds:[16+bp],ax
                add si,2
                add bp,4
                loop r_start
                ret
            
        cube:   mov ax,bx
                mul bx
                mul bx
                ret

标志寄存器


汇编语言学习


  • CF标志位Carry Flag

    • 汇编语言学习
    • 进位(最高位进位)add
    • 和运算相关的指令会影响标志位 like ==add , sub==
    • 把操作数当作无符号数字
  • ZF标志位Zero Flag

    • 汇编语言学习
  • 判断相等

  • 最后结果是否为零

  • PF标志位pairty Flag
    • 一的个数是否位偶数0 or 奇数1
    • 汇编语言学习
  • SF标志位Sign Flag
    • 正0负1
    • 汇编语言学习
    • 计算的结果看陈整数和负数
    • add sub 影响sf
    • mul 不影响sf
  • OF标志位Overflow

    • 汇编语言学习
    • 运算过程中看是否溢出
    • 两个操作数都当做有符号 运算过程中决定是否溢出
  • adc 带进位的加法寄存器

    • 可以对更大的数字进行加法运算
  • sbb 带借位减法

    • 实现对更大数的减法
  • cmp 比较指令

    • 类似于减法指令 只是不保存结果,只是影响相关的标志位寄存器

    • 可以判断两个操作数的大小 通过 sf of 标志位

      sfof大小
      101<2
      011<2
      111>2
      001>2
  • 检测比较结果的转移指令

    • 和 cmp指令配合使用
      • 指令含义检测相关的标志位
        jeequalzf=1
        jnenot equalzf=0
        jbbelowcf=0
        jnbnot belowcf=1
        jaabovecf=0 && zf=1
        jnanot abovecf=1 || zf=1
  • DF 标志和串传送指令

    • movsb

    • movsw

    • 配合rep 使用 rep like loop 由cx 的大小决定 执行 上述 两条指令的 次数

      • exp

        • ;-========movsb=====
          mov cx,16
          rep movsb
          ;循环16次 每次执行完后 si di ++
        • ;========movsw
          mov cx,16
          rep movsw
          ;循环16次每次 执行完 si,di --
    • cld -> df==0++

    • std -> df==1--

  • pushf && popf

    • 使 标志位寄存器 压栈和出栈

内中断

1.产生

  1. 除法溢出
  2. 单步执行
  3. 执行into指令
  4. 执行Int 指令

2. 中断处理程序

3. 中断向量表

  • 中断程序的入口地址
  • 存放256个中断程序入口地址
  • 存放在 0000:00000000:03FF
  • 一个表项占两个字,高->段地址CS 低->偏移地址IP

4.中断的过程

  1. 取得中断类型码N
  2. pushf
  3. TF=0,IF=0
  4. push CS
  5. push IP
  6. \[(IP)=(N*4),CS=(N*4)+2\]
  7. 开始执行中断程序

5.中断处理程序和iret指令

  • 中断程序写法的常规步骤
    • 保存要用的寄存器
    • 处理中断
    • 回复寄存器
    • 用iret指令返回 --> pop ip,pop cs,pop popf(pop psw)

6. 除法错误中断的处理

1. 出现溢出
2. 产生0号中断信息
3. 执行0号中断
4. 返回操作系统

7. 编程处理0号中断

分析:

  1. 当发生除法溢出时,产生0号中断信息,从而引发中断过程
    1. 取得中断类型码N
    2. pushf
    3. TF=0,IF=0
    4. push CS
    5. push IP
    6. \[(IP)=(N*4),CS=(N*4)+2\]
  2. 发生0号中断时,Cpu转去执行中断处理程序
    1. 相关处理
    2. 向显示缓冲区送字符串
    3. 返回DOS
    4. ==do0==
  3. do0的程序应该放在那里
    1. 放在0号中断的向量表中0000:0200-0000:02FF
  4. 中断程序的入口地址放在那里
    1. cs:0000:0002,ip:0000:0000

总结

  1. 编写中断处理程序do0
  2. 将do0送入0000:0200
  3. 将do0的入口地址送到存储在中断向量表0号表项中
assume cs:code

code segment

start:  do0安装程序
        设置中断向量表
        mov ax,4c00h
        int 21h
do0:    显示字符串“overflow”
        mov ax,4c00h
        int 21h

code ends
end start

端口


1. 端口读写指令


  • in
  • out

访问端口

in al , 60h;把60h中的数据读入al中
;1.cpu 通过地址线 将地址信息 60h 发出
;2.cpu 通过控制总线发出读命令 选择端口所在的芯片 并通知他 要从中读取数据
;3.端口所在的芯片 将60h端口中的数据通过数据线送入cpu

2.CMOS RAM


  • 地址端口==70h==
  • 数据端口 ==71h==

3.shl,shr


移位指令

左移--> *2

右移–> \2

ul

相关推荐