汇编语言学习
目录
汇编语言
- 日期 19.07.19
## 软件安装
1.DOSBox
- 无脑下一步
- 修改配置文件
添加以下命令
- 修改配置文件
mount c: d:\asm c: //d盘下的文件是自行创建其中包含debug.exe就可以了
2.Vim
- 安装完后打开其文件位置
- 修改配置文件
在开头写入简单的配置文件
- 修改配置文件
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进制
数字 | 1 | 0 |
---|---|---|
含义 | 有 | 无 |
位置 | 2 | 1 | 0 |
---|---|---|---|
有代表 | 4 | 2 | 1 |
进制快速转换
拆分
字节转换
\[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\]
小结
- 汇编指令是 机器指令的 注记符,同机器指令一一对应
- 每一种CPU都有自己的汇编指令集
CPU 可以直接使用的 信息 在存储器中 存放
4.在存储器中指令和数据没用任何区别, 都是二进制数
5.存储器单元从 0 开始 编号
6.一个 存储单元 可以存储 8 个 bit 即 8 为二进制数
7.1byte=8bit 1KB=1024byte 1MB=1024KB 1GB=1024MB
总线地址总线 决定 CPU 的 寻址能力
数据总线 决定 CPU 的 一次传输数据量
控制总线 决定 CPU 的 对其他器件的控制能力
# 寄存器
小例子
1.1 B800:0400 回车
1.2 1空格 1空格
1.3 2空格 2空格
1.4 ...汇编程序员 就是 通过 汇编语言 中的 汇编指令 去修改 寄存器的值 从而 控制 CPU 控制整个计算机
通用寄存器
AX,BX,CX,DX
- 他们各自可分为两个 8 位寄存器(only)
\[ax=ah+al\] \[(h==high,l==low)\]- 1 byte = 8 bit(8位寄存器)==字节型数据
2 byte =16 bit(16位寄存器)==字型数据2个字节
一个字型数据==2个字节型数据=高位字节+低位字- 数据与寄存器之间 要 保持一致性,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(栈),cs sp(栈),bp,si,di,ip,bx
指令的执行过程
- CPU从cs:ip所指向的内存单元 读取 指令 然后 存放到 指令缓存器当中
- IP = IP + 所读指令的长度,从而指向下一条指令
- 执行指令缓存器的内容,回到步骤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. 栈的作用
- 临时性保存数据
- 进行数据交换
-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 指令 就会将寄存器的值保存到 栈中
内存的安全访问
- 安全空间 0:200~0: 2ffH
- 内存分配的时间 1. 系统加载程序的时候 为程序分配的内存。2. 程序执行过程中,向系统再去要内存空间
# 承上启下
- 我们可以把内存任意的划分为 栈,数据,指令 ,他们可以是同一块内存,亦可以是不同的内存
- cpu 通过 ss:sp 所指向的 内存作为 栈
- ds:[] 所指向的 内存 作为数据
- cs:ip 所指向的 内存 作为指令
指令从哪里?数据从哪来?临时性的数据存放到哪里?
第一个程序
编译和链接
- 编译 masm .mas --> .obj
- 链接 link .obj --> .exe
exe 文件 的描述信息中 保存的程序入口 地址
然后 系统 通过 描述文件 来设置 cs:ip 和 其它内存
asm 文件 -- 汇编语言(1.汇编指令2.伪指令3.符号体系)
- 汇编指令 由编译器 翻译成010101 的机器指令 最后由 CPU 执行
- 伪指令和符号体系 由编译器执行
- 程序返回功能
把系统加载程序的时候 给程序分配的内存 , 设置的寄存器 返还给系统,因为 系统资源 是有限的
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
- and 逻辑与 0 置为0
- 全为 1 才出 1 否则全部为 0
- 可用于 对 二进制位的数字 设 0
- or 逻辑或 1 置为1
- 只要有1 就为 1
- 可用于对 二进制数字设 1
以字符的形式给出数据
- like ‘………’ 其中单引号包含的 内容 编译器将把 其中的内容 转化为相应的 ASCII
大小写转换
and 置为大写 1101 1111b
or 置为小写 0100 0000b
[bx+idata]
- idata 是立即数
常用格式
- mov ax,[200+bx]
- mov ax,200[bx]
- 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组合
指令要处理的数据
- 保存在CPU
- 在内存中
- 在端口中
数据位置的表达
- 立即数(idata)
- 寄存器
- 段地址加偏移地址
数据的长度
- 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(用栈中的数据)
- 弹栈
- 近转移 ret 修改 IP pop ip
- \((ip)=((ss)*16+(sp))\)
- \((sp)=(sp)+2\)
- 远转移 retf 修改 cs:ip pop ip,pop cs
- \((ip)=((ss)*16+(sp))\)
- \((sp)=(sp)+2\)
- \((cs)=((ss)*16+(sp))\)
- \((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 标志位
sf of 大小 1 0 1<2 0 1 1<2 1 1 1>2 0 0 1>2
检测比较结果的转移指令
- 和 cmp指令配合使用
指令 含义 检测相关的标志位 je equal zf=1 jne not equal zf=0 jb below cf=0 jnb not below cf=1 ja above cf=0 && zf=1 jna not above cf=1 || zf=1
- 和 cmp指令配合使用
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.产生
- 除法溢出
- 单步执行
- 执行into指令
- 执行Int 指令
2. 中断处理程序
3. 中断向量表
- 中断程序的入口地址
- 存放256个中断程序入口地址
- 存放在 0000:0000 到 0000:03FF
- 一个表项占两个字,高->段地址CS 低->偏移地址IP
4.中断的过程
- 取得中断类型码N
- pushf
- TF=0,IF=0
- push CS
- push IP
- \[(IP)=(N*4),CS=(N*4)+2\]
- 开始执行中断程序
5.中断处理程序和iret指令
- 中断程序写法的常规步骤
- 保存要用的寄存器
- 处理中断
- 回复寄存器
- 用iret指令返回 --> pop ip,pop cs,pop popf(pop psw)
6. 除法错误中断的处理
1. 出现溢出 2. 产生0号中断信息 3. 执行0号中断 4. 返回操作系统
7. 编程处理0号中断
分析:
- 当发生除法溢出时,产生0号中断信息,从而引发中断过程
- 取得中断类型码N
- pushf
- TF=0,IF=0
- push CS
- push IP
- \[(IP)=(N*4),CS=(N*4)+2\]
- 发生0号中断时,Cpu转去执行中断处理程序
- 相关处理
- 向显示缓冲区送字符串
- 返回DOS
- ==do0==
- do0的程序应该放在那里
- 放在0号中断的向量表中0000:0200-0000:02FF
- 中断程序的入口地址放在那里
- cs:0000:0002,ip:0000:0000
总结
- 编写中断处理程序do0
- 将do0送入0000:0200
- 将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