小码哥汇编周末班
总线
地址总线
宽度决定CPU寻址能力
8086地址总线宽度20,寻址能力是1M
数据总线
宽度决定了CPU单次数据传送量,也是数据传送速度
8086数据总线宽度是16,所以单词最大传递是2个字节数据
控制总线
它的宽度决定了CPU对其他器件控制能力,能有多少种控制
8088数据总线是8,8086是16
一些题
内存
所有内存单元都有唯一id地址,物理地址
8086地址总线宽度20,可以定位220个不同的内存单元(内存地址范围0x00000~ 0xFFFFF),内存空间大小是1MB
0x00000~ 0xFFFFF 主存储器
0xA0000~ 0xBFFFF 向显存写数据
0xC0000~ 0xFFFFF存储各种硬件信息
CPU访问内存单元是,要给出内存单元的地址
8086 20位地址总线,可以传送20位地址 1M寻址能力
16位结构CPU,它内部都能够一次性处理传输存储16位,蒋迪志从内部简单发出,只能送出16位地址,表现出来地址能力只有64KB
cpu可以用不同段地址和偏移地址形成同一个物理地址
内存分段管理
8086是用“起始地址(段地址×16) + 偏移地址 = 物理地址”的方式给出物理地址
为了开发方便,我们可以采取分段的方法来管理内存,比如:
地址10000H~100FFH的内存单元组成一个段,该段的起始地址为10000H,段地址为1000H,大小为100H
地址10000H~1007FH、10080H~100FFH的内存单元组成2个段,它们的起始地址为:10000H和10080H,段地址为1000H和1008H,大小都为80H
偏移地址为16位,16位地址的寻址能力为64KB,所以一个段的长度最大为64KB
cpu
寄存器:信息存储
运算器:信息处理
控制器:控制其他器件工作
对程序员来说,CPU中最主要部件是寄存器,可以通过改变寄存器的内容来实现对CPU的控制
不同的CPU,寄存器的个数、结构是不相同的(8086是16位结构的CPU)
8086有14个寄存器
都是16位的寄存器
可以存放2个字节
AX、BX、CX、DX这4个寄存器通常用来存放一般性的数据,称为通用寄存器(有时也有特定用途)
通常,CPU会先将内存中的数据存储到通用寄存器中,然后再对通用寄存器中的数据进行运算
假设内存中有块红色内存空间的值是3,现在想把它的值加1,并将结果存储到蓝色内存空间
CPU首先会将红色内存空间的值放到AX寄存器中:mov ax,红色内存空间
然后让AX寄存器与1相加:add ax,1
最后将值赋值给内存空间:mov 蓝色内存空间,ax
通用寄存器
AX、BX、CX、DX这4个寄存器通常用来存放一般性的数据,称为通用寄存器(有时也有特定用途)
通常,CPU会先将内存中的数据存储到通用寄存器中,然后再对通用寄存器中的数据进行运算
假设内存中有块红色内存空间的值是3,现在想把它的值加1,并将结果存储到蓝色内存空间
CPU首先会将红色内存空间的值放到AX寄存器中:mov ax,红色内存空间
然后让AX寄存器与1相加:add ax,1
最后将值赋值给内存空间:mov 蓝色内存空间,ax
字节与字
在汇编的数据存储中,有2个比较常用的单位
字节:byte,1个字节由8bit组成,可以存储在8位寄存器中
字:word,1个字由2个字节组成,这2个字节分别称为字的高字节和低字节
比如数据20000(4E20H,0100111000100000B),高字节的值是78,低字节的值是32
1个字可以存在1个16位寄存器中,这个字的高字节、低字节分别存储在这个寄存器的高8位寄存器、低8位寄存器中
段寄存器
8086在访问内存时要由相关部件提供内存单元的段地址和偏移地址,送入地址加法器合成物理地址
是什么部件提供段地址?段地址在8086的段寄存器中存放
8086有4个段寄存器:CS、DS、SS、ES,当CPU需要访问内存时由这4个段寄存器提供内存单元的段地址
CS (Code Segment):代码段寄存器
DS (Data Segment):数据段寄存器
SS (Stack Segment):堆栈段寄存器
ES (Extra Segment):附加段寄存器
每个段寄存器的具体作用是?
CS为代码段寄存器,IP为指令指针寄存器,它们指示了CPU当前要读取指令的地址
任意时刻,8086CPU都会将CS:IP指向的指令作为下一条需要取出执行的指令
指令执行的过程
1.从cs:ip指向的内存单元读取指令,读取的指令进入指令缓冲器
2.IP=IP+读取指令的长度,从而指向下一条指令
3.执行指令,转到步骤1,重复这个过程
指令和数据
在内存或者磁盘上,指令数据没有任何区别,都是二进制
cpu在工作时候把有信息看做指令,有的信息看做数据,为同样信息赋予不同意义
jmp段地址:偏移地址,指令功能,用指令中给出的段地址修改cs,偏移地址修改IP
若想仅修改IP内容,可以用
jmp ax, 指令执行前,ax=1000H,CS=2000H,IP=0003H
指令执行后,ax=1000H,CS=2000H,IP=1000H
jmp bx,指令执行前,bx=0B16H,CS =2000H,IP=0003H
指令执行前,bx=0B16H,CS =2000H,IP=0B16H
另外,也可以“jmp 直接值”来改变IP的值,比如“jmp 0100H”
练习
debug
debug用R命令查看,改变cpu寄存器内容
R命令查看,改变CPU寄存器内容
D命令查看内存中内容
E命令改写内存中内容
U命令将内存中机器指令翻译成汇编指令
T命令执行一条机器指令
A命令以汇编指令个是在内存中写入一条机器指令
q命令,退出debug
p命令 类似于step over,可以跳过loop循环
g命令,跳过前面的代码,停留在指定的代码位置
R
输入“r”可以查看所有寄存器的值
输入“r 寄存器名称”可以修改寄存器的值
输入“r ax”将ax寄存器的值改为0100H
D
输入“d”可以查看内存中的内容
输入“d 段地址:偏移地址”查看特定位置的内存数据
输入“d 段地址:起始偏移地址 结尾偏移地址”查看特定位置和特定范围的内存数据
输入“d 偏移地址”、 “d 起始偏移地址 结尾偏移地址” ,会将DS的内容作为段地址
E
输入 e段地址,偏移地址:数据串,修改特定位置内存数据
输入e段地址:偏移地址,后按Enter 也可以修改特定位置内幕才能数据,数据用空格隔开
U
输入u可以将内存中内容翻译为对应的汇编指令
A
输入 ‘a’
a段地址:偏移地址,可以从某位置开始写入汇编指令
练习
8086 的显存地址空间是 A0000H~BFFFFH,其中 B8000H~BFFFFH 为 80*25
彩色字符模式显示缓冲区,当向这个地址空间写入数据时,这些数据会立即出现
在显示器上
CPU要读写一个内存单元时,必须要先给出这个内存单元的地址,在8086中,内存地址由段地址和偏移地址组成
8086中有一个DS段寄存器,通常用来存放要访问数据的段地址
mov bx,1000H
mov ds,bx
mov al,[0]
上面3条指令的作用将10000H(1000:0)中的内存数据赋值到al寄存器中
mov al,[address]的意思将DS:address中的内存数据赋值到al寄存器中
由于al是8位寄存器,所以是将一个字节的数据赋值给al寄存器
8086不支持将数据直接送入段寄存器中,mov ds,1000H是错误的
大小端
大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中(高低\低高) (Big Endian)
小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中(高高\低低) (Little Endian)
练习
栈
栈:是一种具有特殊的访问方式存储空间,后进先出,
8086会将CS作为代码段的段地址,将CS:IP指向的指令作为下一条需要取出执行的指令
8086会将DS作为数据段的段地址,mov ax,[address]就是取出DS:address的内存数据放到ax寄存器中
8086会将SS作为栈段的段地址,任意时刻,SS:SP指向栈顶元素
8086提供了PUSH(入栈)和POP (出栈)指令来操作栈段的数据
比如push ax是将ax的数据入栈,pop ax是将栈顶的数据送入ax
push ax
pop ax
问题
栈顶超界push
栈顶超界 pop
在8086中,push和pop都是两个字节
练习
hello,world
编译步骤
编写源码
编译链接
调试运行
汇编指令组成
mov sub add这些指令可以编译为机器指令,被cpu执行
伪指令
assume segment ends 。。。
没有对应的机器指令,由编译器执行
伪指令
segment ends 作用是定义一个段,segment 代表是一个段的开始,ends代表结束
声明以下 code是代码段,ds是数据段
退出程序
mov ah,4c00h
int 21h
中断
中断
中断是由于软件的或硬件的信号,使得CPU暂停当前的任务,转而去执行另一段子程序
也就是说,在程序运行过程中,系统出现了一个必须由CPU立即处理的情况,此时,CPU暂时中止当前程序的执行转而处理这个新情况的过程就叫做中断
中断的分类
硬中断(外中断),由外部设备(比如网卡、硬盘)随机引发的,比如当网卡收到数据包的时候,就会发出一个中断
软中断(内中断),由执行中断指令产生的,可以通过程序控制触发
从本质上来讲,中断是一种电信号,当设备有某种事件发生时,它就会产生中断,通过总线把电信号发送给中断控制器。如果中断的线是激活的,中断控制器就把电信号发送给处理器的某个特定引脚。处理器于是立即停止自己正在做的事,跳到中断处理程序的入口点,进行中断处理
可以通过指令int n产生中断
n是中断码,内存中有一张中断向量表,用来存放中断码对应中断处理程序的入口地址
CPU在接收到中断信号后,暂停当前正在执行的程序,跳转到中断码对应的中断向量表地址处,去执行中断处理程序
常见中断
int 10h用于执行BIOS中断
int 3是“断点中断”,用于调试程序
int 21h用于执行DOS系统功能调用,AH寄存器存储功能号
DOS系统功能调用
DOS系统功能调用
由DOS提供的一组实现特殊功能的子程序供程序员在编写自己的程序时调用,以减轻编程的工作量
涉及屏幕显示、文件管理、I/O管理等等
每个子程序都有一个功能号,所有的功能调用的格式都是一致的。调用的步骤大致如下:
系统功能号送到寄存器AH中;
入口参数送到指定的寄存器中;
用INT 21H指令执行功能调用;
根据出口参数分析功能调用执行情况。
loop指令
loop指令和cx配合使用,用于循环执行重复的操作,类似于高级语言中的for、while循环
使用格式
mov cx ,循环次数
标号:
循环执行的程序段
loop 标号
loop指令的执行流程
让cx的值减一,即cx = cx – 1
判断cx的值
如果不为零转至标号处执行程序,然后重复①
如果为零则执行loop后面的代码
练习
mov ax,[bx] 中 bx 是偏移地址,段地址默认在ds
我们也可以明确地标明段地址,比如
mov ax, ds:[bx]
mov ax, cs:[bx]
mov ax, ss:[bx]
mov ax, es:[bx]
上面的“ds:”、“cs:”、“ss:”、“es:”称为段前缀
段前缀的使用
练习
dw(define word)
使用dw定义了3个字型数据,数据之间用逗号隔开
类似的还有db(define byte)、dd(define double word)
start和end start是对应的,end start标记程序的执行入口
在代码段中使用栈
假设代码中有数据1122h、3344h、5566h、7788h、99aah、0aabbh,利用栈将它们逆序存放
多个段程序
-
如果将代码、数据、栈都放到一个段里面会显得混乱,编程时要随时注意何处是数据、何处是栈、何处是代码
一个段的大小<=64KB,这样就会让数据、代码、栈的大小受到极大的限制所以,一般会考虑使用多个段来存放数据、代码、栈
栈如果不平衡,栈空间迟早会被用完
外平栈
函数外部调用
push 1122h
push 3344h
call sum3
add sp,4 ;恢复栈平衡
内平栈
函数内部
mov bp,sp
mov ax,ss:[bp+2]
sub ax,ss:[bp+4]
ret 4
一般函数是外平栈
函数调用约定
__cdecl 外平栈,参数从右从右到左入栈
__stdcall 内平栈,参数从右到左入栈
__fastcall 内平栈,eax,edx分别传递前面2个参数,其他参数从右到左入栈
函数内部局部变量
- 函数调用流程
- push 参数
- push 函数返回地址
- push bp 保留bp之前的值,方便以后恢复
- mov bp,sp 保留sp之前的值,方便以后恢复
- sub sp,空间大小,分配空间给局部变量
- 保护可能用到的寄存器
- 使用CC填充局部变量的空间
- 执行业务逻辑
执行业务逻辑
- 恢复寄存器
- mov sp,bp (恢复sp之前的值)
- pop bp 恢复bp之前的值
- ret 将函数返回的地址出栈,回到返回地址,执行下一条指令
- 恢复栈平衡 add sp,参数所占空间 add sp,4
assume cs:code ,ds:data,ss:stack
stack segment
stack ends
data segment
string db 'hello$'
data ends
code segment
start:
mov ax,data
mov ds,ax
mov ax,stack
mov ss,ax
;业务逻辑
push 1
push 2
call sum
add sp,4
;退出
mov ax,4c00h
int 21h
sum:
push bp ; 保护bp
;访问栈参数
;保存bp以前的值
mov bp,sp
sub sp,10 ; 预留十个字节给局部变量
;保护可能会用到的寄存器
push si
push di
push bx
;业务逻辑
;定义两个局部变量
mov word ptr ss:[bp-2],3
mov word ptr ss:[bp-4],4
mov ax,ss:[bp-2]
add ax,ss:[bp-4]
mov ss:[bp-6],ax
mov si,1
;访问栈中的参数
mov ax,ss[bp+2]
add ax,ss:[bp+4]
add ax,ss:[bp-6]
;业务逻辑
; 恢复寄存器值
pop bx
pop di
pop si
mov sp,bp ; 恢复sp
pop bp
ret
code ends
end start
给数据起标号
寻址方式
指令处理的数据长度
8086指令能处理2种尺寸的数据:byte、word
思考:“mov [0], 20H”指令是否正确?
mov byte ptr [0], 20H 将20H放入0位置内存的字节单元,占用1个字节
mov word ptr [0], 20H 将20H放入0位置内存的字单元,占用2个字节
很多指令都可以通过“byte ptr”或者“word ptr”来指明所需要操作内存的数据长度
inc byte ptr [0]
add word ptr [0], 2
有些指令有默认的操作数据长度,比如push [0]、pop [0]的操作数据长度只能是2个字节
call ret 指令
call 标号:
将下一条指令的偏移地址入栈后
转到标号处执行指令
ret:将栈顶的值出栈,赋值给ip
call和ret联合使用的作用其实就是高级语言中的函数调用
实践,考虑以下几种情况
有无参数
有无返回值
现场保护
局部变量
堆栈平衡
堆栈平衡
函数调用前后栈顶指针一致
栈帧
栈帧(Stack Frame Layout)
就是一个函数执行的环境
包括:参数、局部变量、返回地址等